diff options
author | Christian Grothoff <christian@grothoff.org> | 2018-11-07 19:25:35 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2018-11-07 19:25:35 +0100 |
commit | 59e432288a4d83a371a9a3efaba2376b44873473 (patch) | |
tree | f89957b703d69b55b079138b99a091841f411018 /src/transport/transport_api2_communication.c | |
parent | 4103a2bf2aef3fb3a6059a99e0a941bc35406cfb (diff) | |
download | gnunet-59e432288a4d83a371a9a3efaba2376b44873473.tar.gz gnunet-59e432288a4d83a371a9a3efaba2376b44873473.zip |
get new libgnunettransportcommunicator to build
Diffstat (limited to 'src/transport/transport_api2_communication.c')
-rw-r--r-- | src/transport/transport_api2_communication.c | 161 |
1 files changed, 97 insertions, 64 deletions
diff --git a/src/transport/transport_api2_communication.c b/src/transport/transport_api2_communication.c index d446516bd..e5be53150 100644 --- a/src/transport/transport_api2_communication.c +++ b/src/transport/transport_api2_communication.c | |||
@@ -90,6 +90,11 @@ struct AckPending | |||
90 | struct AckPending *prev; | 90 | struct AckPending *prev; |
91 | 91 | ||
92 | /** | 92 | /** |
93 | * Communicator this entry belongs to. | ||
94 | */ | ||
95 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch; | ||
96 | |||
97 | /** | ||
93 | * Which peer is this about? | 98 | * Which peer is this about? |
94 | */ | 99 | */ |
95 | struct GNUNET_PeerIdentity receiver; | 100 | struct GNUNET_PeerIdentity receiver; |
@@ -134,17 +139,17 @@ struct GNUNET_TRANSPORT_CommunicatorHandle | |||
134 | /** | 139 | /** |
135 | * DLL of messages awaiting transmission confirmation (ack). | 140 | * DLL of messages awaiting transmission confirmation (ack). |
136 | */ | 141 | */ |
137 | struct AckPending *ac_tail; | 142 | struct AckPending *ap_tail; |
138 | 143 | ||
139 | /** | 144 | /** |
140 | * DLL of queues we offer. | 145 | * DLL of queues we offer. |
141 | */ | 146 | */ |
142 | struct QueueHandle *queue_head; | 147 | struct GNUNET_TRANSPORT_QueueHandle *queue_head; |
143 | 148 | ||
144 | /** | 149 | /** |
145 | * DLL of queues we offer. | 150 | * DLL of queues we offer. |
146 | */ | 151 | */ |
147 | struct QueueHandle *queue_tail; | 152 | struct GNUNET_TRANSPORT_QueueHandle *queue_tail; |
148 | 153 | ||
149 | /** | 154 | /** |
150 | * Our configuration. | 155 | * Our configuration. |
@@ -168,6 +173,11 @@ struct GNUNET_TRANSPORT_CommunicatorHandle | |||
168 | void *mq_init_cls; | 173 | void *mq_init_cls; |
169 | 174 | ||
170 | /** | 175 | /** |
176 | * Queue to talk to the transport service. | ||
177 | */ | ||
178 | struct GNUNET_MQ_Handle *mq; | ||
179 | |||
180 | /** | ||
171 | * Maximum permissable queue length. | 181 | * Maximum permissable queue length. |
172 | */ | 182 | */ |
173 | unsigned long long max_queue_length; | 183 | unsigned long long max_queue_length; |
@@ -202,6 +212,17 @@ struct GNUNET_TRANSPORT_CommunicatorHandle | |||
202 | */ | 212 | */ |
203 | struct GNUNET_TRANSPORT_QueueHandle | 213 | struct GNUNET_TRANSPORT_QueueHandle |
204 | { | 214 | { |
215 | |||
216 | /** | ||
217 | * Kept in a DLL. | ||
218 | */ | ||
219 | struct GNUNET_TRANSPORT_QueueHandle *next; | ||
220 | |||
221 | /** | ||
222 | * Kept in a DLL. | ||
223 | */ | ||
224 | struct GNUNET_TRANSPORT_QueueHandle *prev; | ||
225 | |||
205 | /** | 226 | /** |
206 | * Handle this queue belongs to. | 227 | * Handle this queue belongs to. |
207 | */ | 228 | */ |
@@ -308,7 +329,7 @@ send_add_address (struct GNUNET_TRANSPORT_AddressIdentifier *ai) | |||
308 | env = GNUNET_MQ_msg_extra (aam, | 329 | env = GNUNET_MQ_msg_extra (aam, |
309 | strlen (ai->address) + 1, | 330 | strlen (ai->address) + 1, |
310 | GNUNET_MESSAGE_TYPE_TRANSPORT_ADD_ADDRESS); | 331 | GNUNET_MESSAGE_TYPE_TRANSPORT_ADD_ADDRESS); |
311 | aam->expiration = GNUNET_TIME_relative_to_nbo (ai->expiration); | 332 | aam->expiration = GNUNET_TIME_relative_hton (ai->expiration); |
312 | aam->nt = htonl ((uint32_t) ai->nt); | 333 | aam->nt = htonl ((uint32_t) ai->nt); |
313 | memcpy (&aam[1], | 334 | memcpy (&aam[1], |
314 | ai->address, | 335 | ai->address, |
@@ -334,7 +355,7 @@ send_del_address (struct GNUNET_TRANSPORT_AddressIdentifier *ai) | |||
334 | return; | 355 | return; |
335 | env = GNUNET_MQ_msg (dam, | 356 | env = GNUNET_MQ_msg (dam, |
336 | GNUNET_MESSAGE_TYPE_TRANSPORT_DEL_ADDRESS); | 357 | GNUNET_MESSAGE_TYPE_TRANSPORT_DEL_ADDRESS); |
337 | dam.aid = htonl (ai->aid); | 358 | dam->aid = htonl (ai->aid); |
338 | GNUNET_MQ_send (ai->ch->mq, | 359 | GNUNET_MQ_send (ai->ch->mq, |
339 | env); | 360 | env); |
340 | } | 361 | } |
@@ -352,18 +373,18 @@ send_add_queue (struct GNUNET_TRANSPORT_QueueHandle *qh) | |||
352 | struct GNUNET_MQ_Envelope *env; | 373 | struct GNUNET_MQ_Envelope *env; |
353 | struct GNUNET_TRANSPORT_AddQueueMessage *aqm; | 374 | struct GNUNET_TRANSPORT_AddQueueMessage *aqm; |
354 | 375 | ||
355 | if (NULL == ai->ch->mq) | 376 | if (NULL == qh->ch->mq) |
356 | return; | 377 | return; |
357 | env = GNUNET_MQ_msg_extra (aqm, | 378 | env = GNUNET_MQ_msg_extra (aqm, |
358 | strlen (ai->address) + 1, | 379 | strlen (qh->address) + 1, |
359 | GNUNET_MESSAGE_TYPE_TRANSPORT_ADD_QUEUE); | 380 | GNUNET_MESSAGE_TYPE_TRANSPORT_QUEUE_SETUP); |
360 | aqm.receiver = qh->peer; | 381 | aqm->receiver = qh->peer; |
361 | aqm.nt = htonl ((uint32_t) qh->nt); | 382 | aqm->nt = htonl ((uint32_t) qh->nt); |
362 | aqm.qid = htonl (qh->qid); | 383 | aqm->qid = htonl (qh->queue_id); |
363 | memcpy (&aqm[1], | 384 | memcpy (&aqm[1], |
364 | ai->address, | 385 | qh->address, |
365 | strlen (ai->address) + 1); | 386 | strlen (qh->address) + 1); |
366 | GNUNET_MQ_send (ai->ch->mq, | 387 | GNUNET_MQ_send (qh->ch->mq, |
367 | env); | 388 | env); |
368 | } | 389 | } |
369 | 390 | ||
@@ -380,13 +401,13 @@ send_del_queue (struct GNUNET_TRANSPORT_QueueHandle *qh) | |||
380 | struct GNUNET_MQ_Envelope *env; | 401 | struct GNUNET_MQ_Envelope *env; |
381 | struct GNUNET_TRANSPORT_DelQueueMessage *dqm; | 402 | struct GNUNET_TRANSPORT_DelQueueMessage *dqm; |
382 | 403 | ||
383 | if (NULL == ai->ch->mq) | 404 | if (NULL == qh->ch->mq) |
384 | return; | 405 | return; |
385 | env = GNUNET_MQ_msg (dqm, | 406 | env = GNUNET_MQ_msg (dqm, |
386 | GNUNET_MESSAGE_TYPE_TRANSPORT_DEL_QUEUE); | 407 | GNUNET_MESSAGE_TYPE_TRANSPORT_QUEUE_TEARDOWN); |
387 | dqm.qid = htonl (qh->qid); | 408 | dqm->qid = htonl (qh->queue_id); |
388 | dqm.receiver = qh->peer; | 409 | dqm->receiver = qh->peer; |
389 | GNUNET_MQ_send (ai->ch->mq, | 410 | GNUNET_MQ_send (qh->ch->mq, |
390 | env); | 411 | env); |
391 | } | 412 | } |
392 | 413 | ||
@@ -444,7 +465,8 @@ error_handler (void *cls, | |||
444 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; | 465 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; |
445 | 466 | ||
446 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | 467 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, |
447 | "MQ failure, reconnecting to transport service.\n"); | 468 | "MQ failure %d, reconnecting to transport service.\n", |
469 | error); | ||
448 | disconnect (ch); | 470 | disconnect (ch); |
449 | /* TODO: maybe do this with exponential backoff/delay */ | 471 | /* TODO: maybe do this with exponential backoff/delay */ |
450 | reconnect (ch); | 472 | reconnect (ch); |
@@ -460,7 +482,7 @@ error_handler (void *cls, | |||
460 | */ | 482 | */ |
461 | static void | 483 | static void |
462 | handle_incoming_ack (void *cls, | 484 | handle_incoming_ack (void *cls, |
463 | struct GNUNET_TRANSPORT_IncomingMessageAck *incoming_ack) | 485 | const struct GNUNET_TRANSPORT_IncomingMessageAck *incoming_ack) |
464 | { | 486 | { |
465 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; | 487 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; |
466 | 488 | ||
@@ -470,7 +492,7 @@ handle_incoming_ack (void *cls, | |||
470 | { | 492 | { |
471 | if ( (fc->id == incoming_ack->fc_id) && | 493 | if ( (fc->id == incoming_ack->fc_id) && |
472 | (0 == memcmp (&fc->sender, | 494 | (0 == memcmp (&fc->sender, |
473 | incoming_ack->sender, | 495 | &incoming_ack->sender, |
474 | sizeof (struct GNUNET_PeerIdentity))) ) | 496 | sizeof (struct GNUNET_PeerIdentity))) ) |
475 | { | 497 | { |
476 | GNUNET_CONTAINER_DLL_remove (ch->fc_head, | 498 | GNUNET_CONTAINER_DLL_remove (ch->fc_head, |
@@ -499,11 +521,12 @@ handle_incoming_ack (void *cls, | |||
499 | */ | 521 | */ |
500 | static int | 522 | static int |
501 | check_create_queue (void *cls, | 523 | check_create_queue (void *cls, |
502 | struct GNUNET_TRANSPORT_CreateQueue *cq) | 524 | const struct GNUNET_TRANSPORT_CreateQueue *cq) |
503 | { | 525 | { |
504 | uint16_t len = ntohs (cq->header.size) - sizeof (*cq); | 526 | uint16_t len = ntohs (cq->header.size) - sizeof (*cq); |
505 | const char *addr = (const char *) &cq[1]; | 527 | const char *addr = (const char *) &cq[1]; |
506 | 528 | ||
529 | (void) cls; | ||
507 | if ( (0 == len) || | 530 | if ( (0 == len) || |
508 | ('\0' != addr[len-1]) ) | 531 | ('\0' != addr[len-1]) ) |
509 | { | 532 | { |
@@ -522,11 +545,13 @@ check_create_queue (void *cls, | |||
522 | */ | 545 | */ |
523 | static void | 546 | static void |
524 | handle_create_queue (void *cls, | 547 | handle_create_queue (void *cls, |
525 | struct GNUNET_TRANSPORT_CreateQueue *cq) | 548 | const struct GNUNET_TRANSPORT_CreateQueue *cq) |
526 | { | 549 | { |
527 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; | 550 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; |
528 | const char *addr = (const char *) &cq[1]; | 551 | const char *addr = (const char *) &cq[1]; |
529 | 552 | struct GNUNET_TRANSPORT_CreateQueueResponse *cqr; | |
553 | struct GNUNET_MQ_Envelope *env; | ||
554 | |||
530 | if (GNUNET_OK != | 555 | if (GNUNET_OK != |
531 | ch->mq_init (ch->mq_init_cls, | 556 | ch->mq_init (ch->mq_init_cls, |
532 | &cq->receiver, | 557 | &cq->receiver, |
@@ -535,8 +560,17 @@ handle_create_queue (void *cls, | |||
535 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | 560 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, |
536 | "Address `%s' invalid for this communicator\n", | 561 | "Address `%s' invalid for this communicator\n", |
537 | addr); | 562 | addr); |
538 | // TODO: do we notify the transport!? | 563 | env = GNUNET_MQ_msg (cqr, |
564 | GNUNET_MESSAGE_TYPE_TRANSPORT_QUEUE_CREATE_FAIL); | ||
539 | } | 565 | } |
566 | else | ||
567 | { | ||
568 | env = GNUNET_MQ_msg (cqr, | ||
569 | GNUNET_MESSAGE_TYPE_TRANSPORT_QUEUE_CREATE_OK); | ||
570 | } | ||
571 | cqr->request_id = cq->request_id; | ||
572 | GNUNET_MQ_send (ch->mq, | ||
573 | env); | ||
540 | } | 574 | } |
541 | 575 | ||
542 | 576 | ||
@@ -550,11 +584,12 @@ handle_create_queue (void *cls, | |||
550 | */ | 584 | */ |
551 | static int | 585 | static int |
552 | check_send_msg (void *cls, | 586 | check_send_msg (void *cls, |
553 | struct GNUNET_TRANSPORT_SendMessageTo *smt) | 587 | const struct GNUNET_TRANSPORT_SendMessageTo *smt) |
554 | { | 588 | { |
555 | uint16_t len = ntohs (smt->header.size) - sizeof (*smt); | 589 | uint16_t len = ntohs (smt->header.size) - sizeof (*smt); |
556 | const struct GNUNET_MessageHeader *mh = (const struct GNUNET_MessageHeader *) &smt[1]; | 590 | const struct GNUNET_MessageHeader *mh = (const struct GNUNET_MessageHeader *) &smt[1]; |
557 | 591 | ||
592 | (void) cls; | ||
558 | if (ntohs (mh->size) != len) | 593 | if (ntohs (mh->size) != len) |
559 | { | 594 | { |
560 | GNUNET_break (0); | 595 | GNUNET_break (0); |
@@ -584,9 +619,9 @@ send_ack (struct GNUNET_TRANSPORT_CommunicatorHandle *ch, | |||
584 | 619 | ||
585 | env = GNUNET_MQ_msg (ack, | 620 | env = GNUNET_MQ_msg (ack, |
586 | GNUNET_MESSAGE_TYPE_TRANSPORT_SEND_MSG_ACK); | 621 | GNUNET_MESSAGE_TYPE_TRANSPORT_SEND_MSG_ACK); |
587 | ack->status = htonl (GNUNET_OK); | 622 | ack->status = htonl (status); |
588 | ack->mid = ap->mid; | 623 | ack->mid = mid; |
589 | ack->receiver = ap->receiver; | 624 | ack->receiver = *receiver; |
590 | GNUNET_MQ_send (ch->mq, | 625 | GNUNET_MQ_send (ch->mq, |
591 | env); | 626 | env); |
592 | } | 627 | } |
@@ -623,18 +658,18 @@ send_ack_cb (void *cls) | |||
623 | */ | 658 | */ |
624 | static void | 659 | static void |
625 | handle_send_msg (void *cls, | 660 | handle_send_msg (void *cls, |
626 | struct GNUNET_TRANSPORT_SendMessageTo *smt) | 661 | const struct GNUNET_TRANSPORT_SendMessageTo *smt) |
627 | { | 662 | { |
628 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; | 663 | struct GNUNET_TRANSPORT_CommunicatorHandle *ch = cls; |
629 | const struct GNUNET_MessageHeader *mh; | 664 | const struct GNUNET_MessageHeader *mh; |
630 | struct GNUNET_MQ_Envelope *env; | 665 | struct GNUNET_MQ_Envelope *env; |
631 | struct AckPending *ap; | 666 | struct AckPending *ap; |
632 | struct QueueHandle *qh; | 667 | struct GNUNET_TRANSPORT_QueueHandle *qh; |
633 | 668 | ||
634 | for (qh = ch->queue_head;NULL != qh; qh = qh->next) | 669 | for (qh = ch->queue_head;NULL != qh; qh = qh->next) |
635 | if ( (qh->queue_id == smt->qid) && | 670 | if ( (qh->queue_id == smt->qid) && |
636 | (0 == memcmp (&qh->peer, | 671 | (0 == memcmp (&qh->peer, |
637 | &smt->target, | 672 | &smt->receiver, |
638 | sizeof (struct GNUNET_PeerIdentity))) ) | 673 | sizeof (struct GNUNET_PeerIdentity))) ) |
639 | break; | 674 | break; |
640 | if (NULL == qh) | 675 | if (NULL == qh) |
@@ -653,7 +688,7 @@ handle_send_msg (void *cls, | |||
653 | ap->receiver = smt->receiver; | 688 | ap->receiver = smt->receiver; |
654 | ap->mid = smt->mid; | 689 | ap->mid = smt->mid; |
655 | GNUNET_CONTAINER_DLL_insert (ch->ap_head, | 690 | GNUNET_CONTAINER_DLL_insert (ch->ap_head, |
656 | cp->ap_tail, | 691 | ch->ap_tail, |
657 | ap); | 692 | ap); |
658 | mh = (const struct GNUNET_MessageHeader *) &smt[1]; | 693 | mh = (const struct GNUNET_MessageHeader *) &smt[1]; |
659 | env = GNUNET_MQ_msg_copy (mh); | 694 | env = GNUNET_MQ_msg_copy (mh); |
@@ -679,7 +714,7 @@ reconnect (struct GNUNET_TRANSPORT_CommunicatorHandle *ch) | |||
679 | struct GNUNET_TRANSPORT_IncomingMessageAck, | 714 | struct GNUNET_TRANSPORT_IncomingMessageAck, |
680 | ch), | 715 | ch), |
681 | GNUNET_MQ_hd_var_size (create_queue, | 716 | GNUNET_MQ_hd_var_size (create_queue, |
682 | GNUNET_MESSAGE_TYPE_TRANSPORT_CREATE_QUEUE, | 717 | GNUNET_MESSAGE_TYPE_TRANSPORT_QUEUE_CREATE, |
683 | struct GNUNET_TRANSPORT_CreateQueue, | 718 | struct GNUNET_TRANSPORT_CreateQueue, |
684 | ch), | 719 | ch), |
685 | GNUNET_MQ_hd_var_size (send_msg, | 720 | GNUNET_MQ_hd_var_size (send_msg, |
@@ -689,7 +724,7 @@ reconnect (struct GNUNET_TRANSPORT_CommunicatorHandle *ch) | |||
689 | GNUNET_MQ_handler_end() | 724 | GNUNET_MQ_handler_end() |
690 | }; | 725 | }; |
691 | 726 | ||
692 | ch->mq = GNUNET_CLIENT_connect (cfg, | 727 | ch->mq = GNUNET_CLIENT_connect (ch->cfg, |
693 | "transport", | 728 | "transport", |
694 | handlers, | 729 | handlers, |
695 | &error_handler, | 730 | &error_handler, |
@@ -798,32 +833,15 @@ GNUNET_TRANSPORT_communicator_receive (struct GNUNET_TRANSPORT_CommunicatorHandl | |||
798 | struct GNUNET_TRANSPORT_IncomingMessage *im; | 833 | struct GNUNET_TRANSPORT_IncomingMessage *im; |
799 | uint16_t msize; | 834 | uint16_t msize; |
800 | 835 | ||
801 | if (NULL == ai->ch->mq) | 836 | if (NULL == ch->mq) |
802 | return GNUNET_SYSERR; | 837 | return GNUNET_SYSERR; |
803 | if (NULL != cb) | 838 | if ( (NULL == cb) && |
804 | { | 839 | (GNUNET_MQ_get_length (ch->mq) >= ch->max_queue_length) ) |
805 | struct FlowControl *fc; | ||
806 | |||
807 | im->fc_on = htonl (GNUNET_YES); | ||
808 | im->fc_id = ai->ch->fc_gen++; | ||
809 | fc = GNUNET_new (struct FlowControl); | ||
810 | fc->sender = *sender; | ||
811 | fc->id = im->fc_id; | ||
812 | fc->cb = cb; | ||
813 | fc->cb_cls = cb_cls; | ||
814 | GNUNET_CONTAINER_DLL_insert (ch->fc_head, | ||
815 | ch->fc_tail, | ||
816 | fc); | ||
817 | } | ||
818 | else | ||
819 | { | 840 | { |
820 | if (GNUNET_MQ_get_length (ch->mq) >= ch->max_queue_length) | 841 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, |
821 | { | 842 | "Dropping message: transprot is too slow, queue length %llu exceeded\n", |
822 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | 843 | ch->max_queue_length); |
823 | "Dropping message: transprot is too slow, queue length %u exceeded\n", | 844 | return GNUNET_NO; |
824 | ch->max_queue_length); | ||
825 | return GNUNET_NO; | ||
826 | } | ||
827 | } | 845 | } |
828 | 846 | ||
829 | msize = ntohs (msg->size); | 847 | msize = ntohs (msg->size); |
@@ -839,7 +857,22 @@ GNUNET_TRANSPORT_communicator_receive (struct GNUNET_TRANSPORT_CommunicatorHandl | |||
839 | memcpy (&im[1], | 857 | memcpy (&im[1], |
840 | msg, | 858 | msg, |
841 | msize); | 859 | msize); |
842 | GNUNET_MQ_send (ai->ch->mq, | 860 | if (NULL != cb) |
861 | { | ||
862 | struct FlowControl *fc; | ||
863 | |||
864 | im->fc_on = htonl (GNUNET_YES); | ||
865 | im->fc_id = ch->fc_gen++; | ||
866 | fc = GNUNET_new (struct FlowControl); | ||
867 | fc->sender = *sender; | ||
868 | fc->id = im->fc_id; | ||
869 | fc->cb = cb; | ||
870 | fc->cb_cls = cb_cls; | ||
871 | GNUNET_CONTAINER_DLL_insert (ch->fc_head, | ||
872 | ch->fc_tail, | ||
873 | fc); | ||
874 | } | ||
875 | GNUNET_MQ_send (ch->mq, | ||
843 | env); | 876 | env); |
844 | return GNUNET_OK; | 877 | return GNUNET_OK; |
845 | } | 878 | } |
@@ -927,9 +960,9 @@ GNUNET_TRANSPORT_communicator_address_add (struct GNUNET_TRANSPORT_CommunicatorH | |||
927 | ai->address = GNUNET_strdup (address); | 960 | ai->address = GNUNET_strdup (address); |
928 | ai->nt = nt; | 961 | ai->nt = nt; |
929 | ai->expiration = expiration; | 962 | ai->expiration = expiration; |
930 | ai->aid = handle->aid_gen++; | 963 | ai->aid = ch->aid_gen++; |
931 | GNUNET_CONTAINER_DLL_insert (handle->ai_head, | 964 | GNUNET_CONTAINER_DLL_insert (ch->ai_head, |
932 | handle->ai_tail, | 965 | ch->ai_tail, |
933 | ai); | 966 | ai); |
934 | send_add_address (ai); | 967 | send_add_address (ai); |
935 | return ai; | 968 | return ai; |