diff options
author | Gabor X Toth <*@tg-x.net> | 2015-09-08 01:36:33 +0000 |
---|---|---|
committer | Gabor X Toth <*@tg-x.net> | 2015-09-08 01:36:33 +0000 |
commit | a86a539258282bedabd24919e8af9bd113a6538a (patch) | |
tree | 84d67a3ed9adca66e71709dda6c86f69ad3e9360 | |
parent | 8752370da610cd9313b4dbfcb261420b6c181344 (diff) | |
download | gnunet-a86a539258282bedabd24919e8af9bd113a6538a.tar.gz gnunet-a86a539258282bedabd24919e8af9bd113a6538a.zip |
social: advertise; farewell fix
-rw-r--r-- | src/include/gnunet_social_service.h | 43 | ||||
-rw-r--r-- | src/psyc/gnunet-service-psyc.c | 12 | ||||
-rw-r--r-- | src/social/gnunet-service-social.c | 7 | ||||
-rw-r--r-- | src/social/social_api.c | 77 | ||||
-rw-r--r-- | src/social/test_social.c | 36 |
5 files changed, 131 insertions, 44 deletions
diff --git a/src/include/gnunet_social_service.h b/src/include/gnunet_social_service.h index 62f6ab7c7..0af48d168 100644 --- a/src/include/gnunet_social_service.h +++ b/src/include/gnunet_social_service.h | |||
@@ -38,6 +38,7 @@ extern "C" | |||
38 | #include "gnunet_util_lib.h" | 38 | #include "gnunet_util_lib.h" |
39 | #include "gnunet_env_lib.h" | 39 | #include "gnunet_env_lib.h" |
40 | #include "gnunet_identity_service.h" | 40 | #include "gnunet_identity_service.h" |
41 | #include "gnunet_namestore_service.h" | ||
41 | #include "gnunet_psyc_service.h" | 42 | #include "gnunet_psyc_service.h" |
42 | 43 | ||
43 | 44 | ||
@@ -459,22 +460,44 @@ GNUNET_SOCIAL_host_eject (struct GNUNET_SOCIAL_Host *host, | |||
459 | * @param nym | 460 | * @param nym |
460 | * Pseudonym to map to a cryptographic identifier. | 461 | * Pseudonym to map to a cryptographic identifier. |
461 | * | 462 | * |
462 | * @return Public key of nym; | 463 | * @return Public key of nym. |
463 | */ | 464 | */ |
464 | const struct GNUNET_CRYPTO_EcdsaPublicKey * | 465 | const struct GNUNET_CRYPTO_EcdsaPublicKey * |
465 | GNUNET_SOCIAL_nym_get_key (const struct GNUNET_SOCIAL_Nym *nym); | 466 | GNUNET_SOCIAL_nym_get_key (const struct GNUNET_SOCIAL_Nym *nym); |
466 | 467 | ||
467 | 468 | ||
468 | /** | 469 | /** |
470 | * Get the hash of the public key of a @a nym. | ||
471 | * | ||
472 | * @param nym | ||
473 | * Pseudonym to map to a cryptographic identifier. | ||
474 | * | ||
475 | * @return Hash of the public key of nym. | ||
476 | */ | ||
477 | const struct GNUNET_HashCode * | ||
478 | GNUNET_SOCIAL_nym_get_key_hash (const struct GNUNET_SOCIAL_Nym *nym); | ||
479 | |||
480 | |||
481 | /** | ||
469 | * Advertise the place in the GNS zone of the @e ego of the @a host. | 482 | * Advertise the place in the GNS zone of the @e ego of the @a host. |
470 | * | 483 | * |
471 | * @param host Host of the place. | 484 | * @param hst |
472 | * @param name The name for the PLACE record to put in the zone. | 485 | * Host of the place. |
473 | * @param peer_count Number of elements in the @a peers array. | 486 | * @param name |
474 | * @param peers List of peers in the PLACE record that can be used to send join | 487 | * The name for the PLACE record to put in the zone. |
475 | * requests to. | 488 | * @param peer_count |
476 | * @param expiration_time Expiration time of the record, use 0 to remove the record. | 489 | * Number of elements in the @a peers array. |
477 | * @param password Password used to encrypt the record. | 490 | * @param peers |
491 | * List of peers to put in the PLACE record to advertise | ||
492 | * as entry points to the place in addition to the origin. | ||
493 | * @param expiration_time | ||
494 | * Expiration time of the record, use 0 to remove the record. | ||
495 | * @param password | ||
496 | * Password used to encrypt the record. | ||
497 | * @param result_cb | ||
498 | * Function called with the result of the operation. | ||
499 | * @param result_cls | ||
500 | * Closure for @a result_cb | ||
478 | */ | 501 | */ |
479 | void | 502 | void |
480 | GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *host, | 503 | GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *host, |
@@ -482,7 +505,9 @@ GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *host, | |||
482 | size_t peer_count, | 505 | size_t peer_count, |
483 | const struct GNUNET_PeerIdentity *peers, | 506 | const struct GNUNET_PeerIdentity *peers, |
484 | struct GNUNET_TIME_Relative expiration_time, | 507 | struct GNUNET_TIME_Relative expiration_time, |
485 | const char *password); | 508 | const char *password, |
509 | GNUNET_NAMESTORE_ContinuationWithStatus result_cb, | ||
510 | void *result_cls); | ||
486 | 511 | ||
487 | 512 | ||
488 | /** | 513 | /** |
diff --git a/src/psyc/gnunet-service-psyc.c b/src/psyc/gnunet-service-psyc.c index e20b2280e..070fd8e47 100644 --- a/src/psyc/gnunet-service-psyc.c +++ b/src/psyc/gnunet-service-psyc.c | |||
@@ -1,4 +1,3 @@ | |||
1 | |||
2 | /* | 1 | /* |
3 | * This file is part of GNUnet | 2 | * This file is part of GNUnet |
4 | * Copyright (C) 2013 Christian Grothoff (and other contributing authors) | 3 | * Copyright (C) 2013 Christian Grothoff (and other contributing authors) |
@@ -534,7 +533,7 @@ cleanup_slave (struct Slave *slv) | |||
534 | static void | 533 | static void |
535 | cleanup_channel (struct Channel *chn) | 534 | cleanup_channel (struct Channel *chn) |
536 | { | 535 | { |
537 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | 536 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, |
538 | "%p Cleaning up channel %s. master? %u\n", | 537 | "%p Cleaning up channel %s. master? %u\n", |
539 | chn, GNUNET_h2s (&chn->pub_key_hash), chn->is_master); | 538 | chn, GNUNET_h2s (&chn->pub_key_hash), chn->is_master); |
540 | message_queue_drop (chn); | 539 | message_queue_drop (chn); |
@@ -569,7 +568,6 @@ client_disconnect (void *cls, struct GNUNET_SERVER_Client *client) | |||
569 | 568 | ||
570 | struct Channel * | 569 | struct Channel * |
571 | chn = GNUNET_SERVER_client_get_user_context (client, struct Channel); | 570 | chn = GNUNET_SERVER_client_get_user_context (client, struct Channel); |
572 | chn->is_disconnected = GNUNET_YES; | ||
573 | 571 | ||
574 | if (NULL == chn) | 572 | if (NULL == chn) |
575 | { | 573 | { |
@@ -584,6 +582,8 @@ client_disconnect (void *cls, struct GNUNET_SERVER_Client *client) | |||
584 | chn, (GNUNET_YES == chn->is_master) ? "master" : "slave", | 582 | chn, (GNUNET_YES == chn->is_master) ? "master" : "slave", |
585 | GNUNET_h2s (&chn->pub_key_hash)); | 583 | GNUNET_h2s (&chn->pub_key_hash)); |
586 | 584 | ||
585 | chn->is_disconnected = GNUNET_YES; | ||
586 | |||
587 | struct Client *cli = chn->clients_head; | 587 | struct Client *cli = chn->clients_head; |
588 | while (NULL != cli) | 588 | while (NULL != cli) |
589 | { | 589 | { |
@@ -1548,9 +1548,11 @@ mcast_recv_request (void *cls, | |||
1548 | struct Master *mst = cls; | 1548 | struct Master *mst = cls; |
1549 | uint16_t size = ntohs (req->header.size); | 1549 | uint16_t size = ntohs (req->header.size); |
1550 | 1550 | ||
1551 | char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&req->member_key); | ||
1551 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | 1552 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, |
1552 | "%p Received multicast request of size %u.\n", | 1553 | "%p Received multicast request of size %u from %s.\n", |
1553 | mst, size); | 1554 | mst, size, str); |
1555 | GNUNET_free (str); | ||
1554 | 1556 | ||
1555 | uint16_t first_ptype = 0, last_ptype = 0; | 1557 | uint16_t first_ptype = 0, last_ptype = 0; |
1556 | if (GNUNET_SYSERR | 1558 | if (GNUNET_SYSERR |
diff --git a/src/social/gnunet-service-social.c b/src/social/gnunet-service-social.c index 56612b488..3f558730e 100644 --- a/src/social/gnunet-service-social.c +++ b/src/social/gnunet-service-social.c | |||
@@ -599,6 +599,13 @@ psyc_recv_message (void *cls, | |||
599 | const struct GNUNET_PSYC_MessageHeader *msg) | 599 | const struct GNUNET_PSYC_MessageHeader *msg) |
600 | { | 600 | { |
601 | struct Place *plc = cls; | 601 | struct Place *plc = cls; |
602 | |||
603 | char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&msg->slave_key); | ||
604 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
605 | "%p Received PSYC message of size %u from %s.\n", | ||
606 | plc, ntohs (msg->header.size), str); | ||
607 | GNUNET_free (str); | ||
608 | |||
602 | client_send_msg (plc, &msg->header); | 609 | client_send_msg (plc, &msg->header); |
603 | 610 | ||
604 | /* FIXME: further processing */ | 611 | /* FIXME: further processing */ |
diff --git a/src/social/social_api.c b/src/social/social_api.c index 23a977beb..a49f1160d 100644 --- a/src/social/social_api.c +++ b/src/social/social_api.c | |||
@@ -455,7 +455,8 @@ host_recv_notice_place_leave_method (void *cls, | |||
455 | const char *method_name) | 455 | const char *method_name) |
456 | { | 456 | { |
457 | struct GNUNET_SOCIAL_Host *hst = cls; | 457 | struct GNUNET_SOCIAL_Host *hst = cls; |
458 | if (NULL == nym) | 458 | if (0 == memcmp (&(struct GNUNET_CRYPTO_EcdsaPublicKey) {}, |
459 | &nym->pub_key, sizeof (nym->pub_key))) | ||
459 | return; | 460 | return; |
460 | 461 | ||
461 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | 462 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, |
@@ -464,6 +465,12 @@ host_recv_notice_place_leave_method (void *cls, | |||
464 | 465 | ||
465 | hst->notice_place_leave_nym = (struct GNUNET_SOCIAL_Nym *) nym; | 466 | hst->notice_place_leave_nym = (struct GNUNET_SOCIAL_Nym *) nym; |
466 | hst->notice_place_leave_env = GNUNET_ENV_environment_create (); | 467 | hst->notice_place_leave_env = GNUNET_ENV_environment_create (); |
468 | |||
469 | char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&hst->notice_place_leave_nym->pub_key); | ||
470 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
471 | "_notice_place_leave: got method from nym %s (%s).\n", | ||
472 | GNUNET_h2s (&hst->notice_place_leave_nym->pub_key_hash), str); | ||
473 | GNUNET_break (0); | ||
467 | } | 474 | } |
468 | 475 | ||
469 | 476 | ||
@@ -506,6 +513,11 @@ host_recv_notice_place_leave_eom (void *cls, | |||
506 | if (NULL == hst->notice_place_leave_env) | 513 | if (NULL == hst->notice_place_leave_env) |
507 | return; | 514 | return; |
508 | 515 | ||
516 | char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&hst->notice_place_leave_nym->pub_key); | ||
517 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
518 | "_notice_place_leave: got EOM from nym %s (%s).\n", | ||
519 | GNUNET_h2s (&hst->notice_place_leave_nym->pub_key_hash), str); | ||
520 | |||
509 | if (GNUNET_YES != cancelled) | 521 | if (GNUNET_YES != cancelled) |
510 | { | 522 | { |
511 | if (NULL != hst->farewell_cb) | 523 | if (NULL != hst->farewell_cb) |
@@ -658,10 +670,12 @@ slicer_message (void *cls, const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, | |||
658 | GNUNET_assert (message_id == slicer->message_id); | 670 | GNUNET_assert (message_id == slicer->message_id); |
659 | } | 671 | } |
660 | 672 | ||
673 | char *nym_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (slave_key); | ||
661 | LOG (GNUNET_ERROR_TYPE_DEBUG, | 674 | LOG (GNUNET_ERROR_TYPE_DEBUG, |
662 | "Slicer received message of type %u and size %u, " | 675 | "Slicer received message of type %u and size %u, " |
663 | "with ID %" PRIu64 " and method %s\n", | 676 | "with ID %" PRIu64 " and method %s from %s\n", |
664 | ptype, ntohs (msg->size), message_id, slicer->method_name); | 677 | ptype, ntohs (msg->size), message_id, slicer->method_name, nym_str); |
678 | GNUNET_free (nym_str); | ||
665 | 679 | ||
666 | slicer->msg = msg; | 680 | slicer->msg = msg; |
667 | 681 | ||
@@ -1661,8 +1675,8 @@ GNUNET_SOCIAL_host_eject (struct GNUNET_SOCIAL_Host *hst, | |||
1661 | * | 1675 | * |
1662 | * @param nym | 1676 | * @param nym |
1663 | * Pseudonym to map to a cryptographic identifier. | 1677 | * Pseudonym to map to a cryptographic identifier. |
1664 | * @param[out] nym_key | 1678 | * |
1665 | * Set to the public key of the nym. | 1679 | * @return Public key of nym. |
1666 | */ | 1680 | */ |
1667 | const struct GNUNET_CRYPTO_EcdsaPublicKey * | 1681 | const struct GNUNET_CRYPTO_EcdsaPublicKey * |
1668 | GNUNET_SOCIAL_nym_get_key (const struct GNUNET_SOCIAL_Nym *nym) | 1682 | GNUNET_SOCIAL_nym_get_key (const struct GNUNET_SOCIAL_Nym *nym) |
@@ -1672,6 +1686,21 @@ GNUNET_SOCIAL_nym_get_key (const struct GNUNET_SOCIAL_Nym *nym) | |||
1672 | 1686 | ||
1673 | 1687 | ||
1674 | /** | 1688 | /** |
1689 | * Get the hash of the public key of a @a nym. | ||
1690 | * | ||
1691 | * @param nym | ||
1692 | * Pseudonym to map to a cryptographic identifier. | ||
1693 | * | ||
1694 | * @return Hash of the public key of nym. | ||
1695 | */ | ||
1696 | const struct GNUNET_HashCode * | ||
1697 | GNUNET_SOCIAL_nym_get_key_hash (const struct GNUNET_SOCIAL_Nym *nym) | ||
1698 | { | ||
1699 | return &nym->pub_key_hash; | ||
1700 | } | ||
1701 | |||
1702 | |||
1703 | /** | ||
1675 | * Obtain the private-public key pair of the hosted place. | 1704 | * Obtain the private-public key pair of the hosted place. |
1676 | * | 1705 | * |
1677 | * The public part is suitable for storing in GNS within a PLACE record, | 1706 | * The public part is suitable for storing in GNS within a PLACE record, |
@@ -1689,13 +1718,6 @@ GNUNET_SOCIAL_host_get_place_key (struct GNUNET_SOCIAL_Host *hst) | |||
1689 | } | 1718 | } |
1690 | 1719 | ||
1691 | 1720 | ||
1692 | static void | ||
1693 | namestore_result_host_advertise (void *cls, int32_t success, const char *emsg) | ||
1694 | { | ||
1695 | |||
1696 | } | ||
1697 | |||
1698 | |||
1699 | /** | 1721 | /** |
1700 | * Connected to core service. | 1722 | * Connected to core service. |
1701 | */ | 1723 | */ |
@@ -1710,13 +1732,23 @@ core_connected_cb (void *cls, const struct GNUNET_PeerIdentity *my_identity) | |||
1710 | /** | 1732 | /** |
1711 | * Advertise the place in the GNS zone of the @e ego of the @a host. | 1733 | * Advertise the place in the GNS zone of the @e ego of the @a host. |
1712 | * | 1734 | * |
1713 | * @param hst Host of the place. | 1735 | * @param hst |
1714 | * @param name The name for the PLACE record to put in the zone. | 1736 | * Host of the place. |
1715 | * @param peer_count Number of elements in the @a peers array. | 1737 | * @param name |
1716 | * @param peers List of peers in the PLACE record that can be used to send join | 1738 | * The name for the PLACE record to put in the zone. |
1717 | * requests to. | 1739 | * @param peer_count |
1718 | * @param expiration_time Expiration time of the record, use 0 to remove the record. | 1740 | * Number of elements in the @a peers array. |
1719 | * @param password Password used to encrypt the record. | 1741 | * @param peers |
1742 | * List of peers to put in the PLACE record to advertise | ||
1743 | * as entry points to the place in addition to the origin. | ||
1744 | * @param expiration_time | ||
1745 | * Expiration time of the record, use 0 to remove the record. | ||
1746 | * @param password | ||
1747 | * Password used to encrypt the record. | ||
1748 | * @param result_cb | ||
1749 | * Function called with the result of the operation. | ||
1750 | * @param result_cls | ||
1751 | * Closure for @a result_cb | ||
1720 | */ | 1752 | */ |
1721 | void | 1753 | void |
1722 | GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *hst, | 1754 | GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *hst, |
@@ -1724,7 +1756,9 @@ GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *hst, | |||
1724 | size_t peer_count, | 1756 | size_t peer_count, |
1725 | const struct GNUNET_PeerIdentity *peers, | 1757 | const struct GNUNET_PeerIdentity *peers, |
1726 | struct GNUNET_TIME_Relative expiration_time, | 1758 | struct GNUNET_TIME_Relative expiration_time, |
1727 | const char *password) | 1759 | const char *password, |
1760 | GNUNET_NAMESTORE_ContinuationWithStatus result_cb, | ||
1761 | void *result_cls) | ||
1728 | { | 1762 | { |
1729 | struct GNUNET_SOCIAL_Place *plc = &hst->plc; | 1763 | struct GNUNET_SOCIAL_Place *plc = &hst->plc; |
1730 | if (NULL == namestore) | 1764 | if (NULL == namestore) |
@@ -1748,8 +1782,7 @@ GNUNET_SOCIAL_host_advertise (struct GNUNET_SOCIAL_Host *hst, | |||
1748 | rd.data = rec; | 1782 | rd.data = rec; |
1749 | 1783 | ||
1750 | GNUNET_NAMESTORE_records_store (namestore, &hst->plc.ego_key, | 1784 | GNUNET_NAMESTORE_records_store (namestore, &hst->plc.ego_key, |
1751 | name, 1, &rd, namestore_result_host_advertise, | 1785 | name, 1, &rd, result_cb, result_cls); |
1752 | hst); | ||
1753 | } | 1786 | } |
1754 | 1787 | ||
1755 | 1788 | ||
diff --git a/src/social/test_social.c b/src/social/test_social.c index ad4101bd6..50150449c 100644 --- a/src/social/test_social.c +++ b/src/social/test_social.c | |||
@@ -127,7 +127,9 @@ enum | |||
127 | TEST_GUEST_LOOK_AT = 12, | 127 | TEST_GUEST_LOOK_AT = 12, |
128 | TEST_GUEST_LOOK_FOR = 13, | 128 | TEST_GUEST_LOOK_FOR = 13, |
129 | TEST_GUEST_LEAVE = 14, | 129 | TEST_GUEST_LEAVE = 14, |
130 | TEST_HOST_LEAVE = 15, | 130 | TEST_HOST_ADVERTISE = 15, |
131 | TEST_GUEST_ENTER_BY_NAME = 16, | ||
132 | TEST_HOST_LEAVE = 17, | ||
131 | } test; | 133 | } test; |
132 | 134 | ||
133 | 135 | ||
@@ -307,17 +309,36 @@ schedule_host_leave (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) | |||
307 | 309 | ||
308 | 310 | ||
309 | static void | 311 | static void |
312 | host_recv_advertise_result (void *cls, int32_t success, const char *emsg) | ||
313 | { | ||
314 | GNUNET_assert (GNUNET_YES == success); | ||
315 | GNUNET_SCHEDULER_add_now (&schedule_host_leave, NULL); | ||
316 | } | ||
317 | |||
318 | |||
319 | static void | ||
320 | host_advertise () | ||
321 | { | ||
322 | test = TEST_HOST_ADVERTISE; | ||
323 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Test #%u: Advertising place.\n", test); | ||
324 | GNUNET_SOCIAL_host_advertise (hst, "home", 1, &this_peer, | ||
325 | GNUNET_TIME_UNIT_MINUTES, "let.me*in!", | ||
326 | host_recv_advertise_result, hst); | ||
327 | } | ||
328 | |||
329 | |||
330 | static void | ||
310 | host_farewell (void *cls, | 331 | host_farewell (void *cls, |
311 | const struct GNUNET_SOCIAL_Nym *nym, | 332 | const struct GNUNET_SOCIAL_Nym *nym, |
312 | struct GNUNET_ENV_Environment *env) | 333 | struct GNUNET_ENV_Environment *env) |
313 | { | 334 | { |
314 | const struct GNUNET_CRYPTO_EcdsaPublicKey *nym_key = GNUNET_SOCIAL_nym_get_key (nym); | 335 | const struct GNUNET_CRYPTO_EcdsaPublicKey * |
315 | char *str; | 336 | nym_key = GNUNET_SOCIAL_nym_get_key (nym); |
316 | 337 | ||
317 | str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key); | 338 | char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key); |
318 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | 339 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, |
319 | "Farewell: nym %s has left the place.\n", | 340 | "Farewell: nym %s (%s) has left the place.\n", |
320 | str); | 341 | GNUNET_h2s (GNUNET_SOCIAL_nym_get_key_hash (nym)), str); |
321 | GNUNET_free (str); | 342 | GNUNET_free (str); |
322 | GNUNET_assert (1 == GNUNET_ENV_environment_get_count (env)); | 343 | GNUNET_assert (1 == GNUNET_ENV_environment_get_count (env)); |
323 | if (0 != memcmp (&guest_pub_key, nym_key, sizeof (*nym_key))) | 344 | if (0 != memcmp (&guest_pub_key, nym_key, sizeof (*nym_key))) |
@@ -328,8 +349,7 @@ host_farewell (void *cls, | |||
328 | GNUNET_free (str); | 349 | GNUNET_free (str); |
329 | GNUNET_assert (0); | 350 | GNUNET_assert (0); |
330 | } | 351 | } |
331 | 352 | host_advertise (); | |
332 | GNUNET_SCHEDULER_add_now (&schedule_host_leave, NULL); | ||
333 | } | 353 | } |
334 | 354 | ||
335 | 355 | ||