Changeset 43 for pjproject/main/pjsip/src/pjsip/sip_transport.c
- Timestamp:
- Nov 11, 2005 7:01:31 PM (19 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
pjproject/main/pjsip/src/pjsip/sip_transport.c
- Property svn:keywords set to Id
r3 r43 1 1 /* $Id$ 2 *3 2 */ 4 3 #include <pjsip/sip_transport.h> … … 7 6 #include <pjsip/sip_msg.h> 8 7 #include <pjsip/sip_private.h> 8 #include <pjsip/sip_errno.h> 9 9 #include <pj/os.h> 10 10 #include <pj/log.h> … … 13 13 #include <pj/string.h> 14 14 #include <pj/pool.h> 15 #include <pj/assert.h> 15 16 16 17 #define MGR_IDLE_CHECK_INTERVAL 30 … … 45 46 * listener list. 46 47 */ 47 PJ_DECL_LIST_MEMBER(struct pjsip_transport_t) 48 PJ_DECL_LIST_MEMBER(struct pjsip_transport_t); 48 49 49 50 /** Transport's pool. */ … … 67 68 /** I/O Queue key */ 68 69 pj_ioqueue_key_t *key; 70 71 /** Accept key. */ 72 pj_ioqueue_op_key_t accept_op; 69 73 70 74 /** Receive data buffer */ … … 116 120 pj_ioqueue_t *ioqueue; 117 121 pj_time_val next_idle_check; 122 pj_size_t send_buf_size; 123 pj_size_t recv_buf_size; 118 124 void (*message_callback)(pjsip_endpoint*, pjsip_rx_data *rdata); 119 125 }; … … 145 151 struct transport_callback 146 152 { 147 PJ_DECL_LIST_MEMBER(struct transport_callback) 153 PJ_DECL_LIST_MEMBER(struct transport_callback); 148 154 149 155 /** User defined token to be passed to the callback. */ … … 174 180 }; 175 181 176 static void on_ioqueue_read(pj_ioqueue_key_t *key, pj_ssize_t bytes_read); 177 static void on_ioqueue_write(pj_ioqueue_key_t *key, pj_ssize_t bytes_sent); 178 static void on_ioqueue_accept(pj_ioqueue_key_t *key, int status); 179 static void on_ioqueue_connect(pj_ioqueue_key_t *key, int status); 182 static void on_ioqueue_read(pj_ioqueue_key_t *key, 183 pj_ioqueue_op_key_t *op_key, 184 pj_ssize_t bytes_read); 185 static void on_ioqueue_write(pj_ioqueue_key_t *key, 186 pj_ioqueue_op_key_t *op_key, 187 pj_ssize_t bytes_sent); 188 static void on_ioqueue_accept(pj_ioqueue_key_t *key, 189 pj_ioqueue_op_key_t *op_key, 190 pj_sock_t newsock, 191 int status); 192 static void on_ioqueue_connect(pj_ioqueue_key_t *key, 193 int status); 180 194 181 195 static pj_ioqueue_callback ioqueue_transport_callback = … … 195 209 key->type = (pj_uint8_t)tr->type; 196 210 key->zero = 0; 197 key->addr = pj_sockaddr_ get_addr(&tr->remote_addr);198 key->port = pj_sockaddr_ get_port(&tr->remote_addr);211 key->addr = pj_sockaddr_in_get_addr(&tr->remote_addr).s_addr; 212 key->port = pj_sockaddr_in_get_port(&tr->remote_addr); 199 213 /* 200 214 if (key->port == 0) { 201 key->port = pj_sockaddr_ get_port(&tr->local_addr);215 key->port = pj_sockaddr_in_get_port(&tr->local_addr); 202 216 } 203 217 */ … … 213 227 key->type = (pj_uint8_t)type; 214 228 key->zero = 0; 215 key->addr = pj_sockaddr_ get_addr(addr);216 key->port = pj_sockaddr_ get_port(addr);229 key->addr = pj_sockaddr_in_get_addr(addr).s_addr; 230 key->port = pj_sockaddr_in_get_port(addr); 217 231 } 218 232 #endif … … 221 235 const pj_sockaddr_in *addr) 222 236 { 223 PJ_UNUSED_ARG(addr) 237 PJ_UNUSED_ARG(addr); 224 238 225 239 /* This is to detect alignment problems. */ … … 237 251 pj_sockaddr_in addr; 238 252 pj_sockaddr_set_str_addr(&addr, &localaddr); 239 key->addr = pj_sockaddr_ get_addr(&addr);253 key->addr = pj_sockaddr_in_get_addr(&addr); 240 254 } 241 255 #endif … … 298 312 * Create new transmit buffer. 299 313 */ 300 pjsip_tx_data* pjsip_tx_data_create( pjsip_transport_mgr *mgr ) 314 pj_status_t pjsip_tx_data_create( pjsip_transport_mgr *mgr, 315 pjsip_tx_data **p_tdata ) 301 316 { 302 317 pj_pool_t *pool; 303 318 pjsip_tx_data *tdata; 319 pj_status_t status; 304 320 305 321 PJ_LOG(5, ("", "pjsip_tx_data_create")); 322 323 PJ_ASSERT_RETURN(mgr && p_tdata, PJ_EINVAL); 306 324 307 325 pool = pjsip_endpt_create_pool( mgr->endpt, "ptdt%p", … … 309 327 PJSIP_POOL_INC_TDATA ); 310 328 if (!pool) { 311 return NULL;329 return PJ_ENOMEM; 312 330 } 313 331 tdata = pj_pool_calloc(pool, 1, sizeof(pjsip_tx_data)); 314 332 tdata->pool = pool; 315 333 tdata->mgr = mgr; 316 sprintf(tdata->obj_name,"txd%p", tdata);317 318 tdata->ref_cnt = pj_atomic_create(tdata->pool, 0);319 if ( !tdata->ref_cnt) {334 pj_sprintf(tdata->obj_name,"txd%p", tdata); 335 336 status = pj_atomic_create(tdata->pool, 0, &tdata->ref_cnt); 337 if (status != PJ_SUCCESS) { 320 338 pjsip_endpt_destroy_pool( mgr->endpt, tdata->pool ); 321 return NULL;339 return status; 322 340 } 323 341 324 return tdata; 342 *p_tdata = tdata; 343 return PJ_SUCCESS; 325 344 } 326 345 … … 340 359 { 341 360 pj_assert( pj_atomic_get(tdata->ref_cnt) > 0); 342 if (pj_atomic_dec (tdata->ref_cnt) <= 0) {361 if (pj_atomic_dec_and_get(tdata->ref_cnt) <= 0) { 343 362 PJ_LOG(6,(tdata->obj_name, "destroying txdata")); 344 363 pj_atomic_destroy( tdata->ref_cnt ); … … 376 395 } else 377 396 #else 378 PJ_UNUSED_ARG(flag) 397 PJ_UNUSED_ARG(flag); 379 398 #endif 380 399 { … … 453 472 { 454 473 pj_assert(tr->ref_cnt > 0); 455 if (pj_atomic_dec (tr->ref_cnt) == 0) {474 if (pj_atomic_dec_and_get(tr->ref_cnt) == 0) { 456 475 pj_gettimeofday(&tr->close_time); 457 476 tr->close_time.sec += PJSIP_TRANSPORT_CLOSE_TIMEOUT; … … 462 481 * Open the underlying transport. 463 482 */ 464 static pj_sock_t create_socket( pjsip_transport_type_e type, 465 pj_sockaddr_in *local ) 483 static pj_status_t create_socket( pjsip_transport_type_e type, 484 pj_sockaddr_in *local, 485 pj_sock_t *p_sock) 466 486 { 467 487 int sock_family; … … 469 489 int sock_proto; 470 490 int len; 491 pj_status_t status; 471 492 pj_sock_t sock; 472 493 … … 484 505 #endif 485 506 } else { 486 PJ_LOG(2,("", "create_socket: unsupported transport type %s", 487 get_type_name(type))); 488 return PJ_INVALID_SOCKET; 507 return PJ_EINVAL; 489 508 } 490 509 491 510 /* Create socket. */ 492 sock = pj_sock_socket( sock_family, sock_type, sock_proto, PJ_SOCK_ASYNC); 493 if (sock == PJ_INVALID_SOCKET) { 494 PJ_PERROR((THIS_FILE, "%s socket()", get_type_name(type))); 495 return PJ_INVALID_SOCKET; 496 } 511 status = pj_sock_socket( sock_family, sock_type, sock_proto, &sock); 512 if (status != PJ_SUCCESS) 513 return status; 497 514 498 515 /* Bind the socket to the requested address, or if no address is … … 501 518 if (/*local->sin_addr.s_addr != 0 &&*/ local->sin_port != 0) { 502 519 /* Bind to the requested address. */ 503 if (pj_sock_bind(sock, local, sizeof(*local)) != 0) { 504 PJ_PERROR((THIS_FILE, "bind() to %s %s:%d", 505 get_type_name(type), 506 pj_sockaddr_get_str_addr(local), 507 pj_sockaddr_get_port(local))); 520 status = pj_sock_bind(sock, local, sizeof(*local)); 521 if (status != PJ_SUCCESS) { 508 522 pj_sock_close(sock); 509 return PJ_INVALID_SOCKET;523 return status; 510 524 } 511 525 } else if (type == PJSIP_TRANSPORT_UDP) { … … 517 531 pj_memset(local, 0, sizeof(*local)); 518 532 local->sin_family = PJ_AF_INET; 519 if (pj_sock_bind(sock, local, sizeof(*local)) != 0) { 520 PJ_PERROR((THIS_FILE, "bind() to %s 0.0.0.0:0", get_type_name(type)));533 status = pj_sock_bind(sock, local, sizeof(*local)); 534 if (status != PJ_SUCCESS) { 521 535 pj_sock_close(sock); 522 return PJ_INVALID_SOCKET;536 return status; 523 537 } 524 538 525 539 /* Get the local address. */ 526 540 len = sizeof(pj_sockaddr_in); 527 if (pj_sock_getsockname(sock, local, &len)) { 528 PJ_PERROR((THIS_FILE, "getsockname()"));541 status = pj_sock_getsockname(sock, local, &len); 542 if (status != PJ_SUCCESS) { 529 543 pj_sock_close(sock); 530 return -1; 531 } 532 } 533 534 return sock; 544 return status; 545 } 546 } 547 548 *p_sock = sock; 549 return PJ_SUCCESS; 535 550 } 536 551 … … 548 563 * Create a new transport object. 549 564 */ 550 static pjsip_transport_t* create_transport( pjsip_transport_mgr *mgr, 551 pjsip_transport_type_e type, 552 pj_sock_t sock_hnd, 553 const pj_sockaddr_in *local_addr, 554 const pj_sockaddr_in *addr_name) 565 static pj_status_t create_transport( pjsip_transport_mgr *mgr, 566 pjsip_transport_type_e type, 567 pj_sock_t sock_hnd, 568 const pj_sockaddr_in *local_addr, 569 const pj_sockaddr_in *addr_name, 570 pjsip_transport_t **p_transport ) 555 571 { 556 572 pj_pool_t *tr_pool=NULL, *rdata_pool=NULL; 557 573 pjsip_transport_t *tr = NULL; 574 pj_status_t status; 558 575 559 576 /* Allocate pool for transport from endpoint. */ … … 563 580 PJSIP_POOL_INC_TRANSPORT ); 564 581 if (!tr_pool) { 582 status = PJ_ENOMEM; 565 583 goto on_error; 566 584 } … … 572 590 PJSIP_POOL_INC_RDATA ); 573 591 if (!rdata_pool) { 592 status = PJ_ENOMEM; 574 593 goto on_error; 575 594 } … … 583 602 pj_memcpy(&tr->local_addr, local_addr, sizeof(pj_sockaddr_in)); 584 603 pj_list_init(&tr->cb_list); 585 sprintf(tr->obj_name, transport_get_name_format(type), tr);604 pj_sprintf(tr->obj_name, transport_get_name_format(type), tr); 586 605 587 606 if (type != PJSIP_TRANSPORT_UDP) { … … 596 615 597 616 /* Create atomic */ 598 tr->ref_cnt = pj_atomic_create(tr_pool, 0);599 if ( !tr->ref_cnt) {617 status = pj_atomic_create(tr_pool, 0, &tr->ref_cnt); 618 if (status != PJ_SUCCESS) 600 619 goto on_error; 601 }602 620 603 621 /* Init rdata in the transport. */ … … 608 626 609 627 /* Init transport mutex. */ 610 tr->tr_mutex = pj_mutex_create(tr_pool, "mtr%p", 0); 611 if (!tr->tr_mutex) { 612 PJ_PERROR((tr->obj_name, "pj_mutex_create()")); 628 status = pj_mutex_create_recursive(tr_pool, "mtr%p", &tr->tr_mutex); 629 if (status != PJ_SUCCESS) 613 630 goto on_error; 614 }615 631 616 632 /* Register to I/O Queue */ 617 tr->key = pj_ioqueue_register(tr_pool, mgr->ioqueue,618 (pj_oshandle_t)tr->sock, tr,619 &ioqueue_transport_callback);620 if (tr->key == NULL) { 621 PJ_PERROR((tr->obj_name, "pj_ioqueue_register()")); 633 status = pj_ioqueue_register_sock( tr_pool, mgr->ioqueue, 634 tr->sock, tr, 635 &ioqueue_transport_callback, 636 &tr->key); 637 if (status != PJ_SUCCESS) 622 638 goto on_error; 623 } 624 625 return tr;639 640 *p_transport = tr; 641 return PJ_SUCCESS; 626 642 627 643 on_error: … … 635 651 pjsip_endpt_destroy_pool(mgr->endpt, rdata_pool); 636 652 } 637 return NULL;653 return status; 638 654 } 639 655 … … 641 657 * Destroy transport. 642 658 */ 643 static void destroy_transport( pjsip_transport_mgr *mgr, pjsip_transport_t *tr 659 static void destroy_transport( pjsip_transport_mgr *mgr, pjsip_transport_t *tr) 644 660 { 645 661 transport_key hash_key; 646 662 647 663 /* Remove from I/O queue. */ 648 pj_ioqueue_unregister( mgr->ioqueue,tr->key );664 pj_ioqueue_unregister( tr->key ); 649 665 650 666 /* Remove from hash table */ … … 669 685 670 686 671 static int transport_send_msg( pjsip_transport_t *tr, pjsip_tx_data *tdata, 672 const pj_sockaddr_in *addr) 687 static pj_status_t transport_send_msg( pjsip_transport_t *tr, 688 pjsip_tx_data *tdata, 689 const pj_sockaddr_in *addr, 690 pj_ssize_t *p_sent) 673 691 { 674 692 const char *buf = tdata->buf.start; 675 int sent; 676 int len; 693 pj_ssize_t size; 694 pj_status_t status; 695 696 /* Can only send if tdata is not being sent! */ 697 if (pj_ioqueue_is_pending(tr->key, &tdata->op_key)) 698 return PJSIP_EPENDINGTX; 677 699 678 700 /* Allocate buffer if necessary. */ … … 685 707 /* Print the message if it's not printed */ 686 708 if (tdata->buf.cur <= tdata->buf.start) { 687 len = pjsip_msg_print(tdata->msg, tdata->buf.start, 688 tdata->buf.end - tdata->buf.start); 689 if (len < 1) { 690 return len; 691 } 692 tdata->buf.cur += len; 693 tdata->buf.cur[len] = '\0'; 694 } 695 696 /* BUG BUG BUG */ 697 /* MUST CHECK THAT THE SOCKET IS READY TO SEND (IOQueue)! */ 698 PJ_TODO(BUG_BUG_BUG___SENDING_DATAGRAM_WHILE_SOCKET_IS_PENDING__) 709 size = pjsip_msg_print( tdata->msg, tdata->buf.start, 710 tdata->buf.end - tdata->buf.start); 711 if (size < 0) { 712 return PJSIP_EMSGTOOLONG; 713 } 714 pj_assert(size != 0); 715 tdata->buf.cur += size; 716 tdata->buf.cur[size] = '\0'; 717 } 699 718 700 719 /* Send the message. */ 701 720 buf = tdata->buf.start; 702 len= tdata->buf.cur - tdata->buf.start;721 size = tdata->buf.cur - tdata->buf.start; 703 722 704 723 if (tr->type == PJSIP_TRANSPORT_UDP) { … … 707 726 "%s" 708 727 "------------ end msg -------------", 709 pj_ sockaddr_get_str_addr(addr),710 pj_sockaddr_ get_port(addr),711 len, buf));712 713 s ent = pj_ioqueue_sendto( tr->mgr->ioqueue, tr->key,714 buf, len, addr, sizeof(*addr));728 pj_inet_ntoa(addr->sin_addr), 729 pj_sockaddr_in_get_port(addr), 730 size, buf)); 731 732 status = pj_ioqueue_sendto( tr->key, &tdata->op_key, 733 buf, &size, 0, addr, sizeof(*addr)); 715 734 } 716 735 #if PJ_HAS_TCP … … 720 739 "%s" 721 740 "------------ end msg -------------", 722 len, buf));723 724 s ent = pj_ioqueue_write (tr->mgr->ioqueue, tr->key, buf, len);741 size, buf)); 742 743 status = pj_ioqueue_send(tr->key, &tdata->op_key, buf, &size, 0); 725 744 } 726 745 #else 727 746 else { 728 pj_assert( 0);729 s ent = -1;747 pj_assert(!"Unsupported transport"); 748 status = PJSIP_EUNSUPTRANSPORT; 730 749 } 731 750 #endif 732 751 733 if (sent == len || sent == PJ_IOQUEUE_PENDING) { 734 return len; 735 } 736 737 /* On error, clear the flag. */ 738 PJ_PERROR((tr->obj_name, tr->type == PJSIP_TRANSPORT_UDP ? "pj_ioqueue_sendto()" : "pj_ioqueue_write()")); 739 return -1; 752 *p_sent = size; 753 return status; 740 754 } 741 755 … … 744 758 * in the outgoing data. 745 759 */ 746 PJ_DEF(int) pjsip_transport_send_msg( pjsip_transport_t *tr, 747 pjsip_tx_data *tdata, 748 const pj_sockaddr_in *addr) 749 { 750 int sent; 751 760 PJ_DEF(pj_status_t) pjsip_transport_send_msg( pjsip_transport_t *tr, 761 pjsip_tx_data *tdata, 762 const pj_sockaddr_in *addr, 763 pj_ssize_t *sent) 764 { 752 765 PJ_LOG(5, (tr->obj_name, "pjsip_transport_send_msg(tdata=%s)", tdata->obj_name)); 753 766 754 sent = transport_send_msg(tr, tdata, addr ); 755 return sent; 767 return transport_send_msg(tr, tdata, addr, sent ); 756 768 } 757 769 … … 761 773 * Create a new transport manager. 762 774 */ 763 PJ_DEF(pjsip_transport_mgr*) 764 pjsip_transport_mgr_create( pj_pool_t *pool, 765 pjsip_endpoint * endpt, 766 void (*cb)(pjsip_endpoint*,pjsip_rx_data *) ) 775 PJ_DEF(pj_status_t) pjsip_transport_mgr_create( pj_pool_t *pool, 776 pjsip_endpoint * endpt, 777 void (*cb)(pjsip_endpoint*, 778 pjsip_rx_data *), 779 pjsip_transport_mgr **p_mgr) 767 780 { 768 781 pjsip_transport_mgr *mgr; 782 pj_status_t status; 769 783 770 784 PJ_LOG(5, (LOG_TRANSPORT_MGR, "pjsip_transport_mgr_create()")); … … 773 787 mgr->endpt = endpt; 774 788 mgr->message_callback = cb; 775 789 mgr->send_buf_size = DEFAULT_SO_SNDBUF; 790 mgr->recv_buf_size = DEFAULT_SO_RCVBUF; 791 776 792 mgr->transport_table = pj_hash_create(pool, MGR_HASH_TABLE_SIZE); 777 793 if (!mgr->transport_table) { 778 PJ_LOG(3, (LOG_TRANSPORT_MGR, "error creating transport manager hash table")); 779 return NULL; 780 } 781 mgr->ioqueue = pj_ioqueue_create(pool, PJSIP_MAX_TRANSPORTS); 782 if (!mgr->ioqueue) { 783 PJ_LOG(3, (LOG_TRANSPORT_MGR, "error creating IO queue")); 784 return NULL; 785 } 786 mgr->mutex = pj_mutex_create(pool, "tmgr%p", 0); 787 if (!mgr->mutex) { 788 PJ_LOG(3, (LOG_TRANSPORT_MGR, "error creating mutex")); 794 return PJ_ENOMEM; 795 } 796 status = pj_ioqueue_create(pool, PJSIP_MAX_TRANSPORTS, &mgr->ioqueue); 797 if (status != PJ_SUCCESS) { 798 return status; 799 } 800 status = pj_mutex_create_recursive(pool, "tmgr%p", &mgr->mutex); 801 if (status != PJ_SUCCESS) { 789 802 pj_ioqueue_destroy(mgr->ioqueue); 790 return NULL;803 return status; 791 804 } 792 805 pj_gettimeofday(&mgr->next_idle_check); 793 806 mgr->next_idle_check.sec += MGR_IDLE_CHECK_INTERVAL; 794 return mgr; 807 808 *p_mgr = mgr; 809 return status; 795 810 } 796 811 … … 798 813 * Destroy transport manager. 799 814 */ 800 PJ_DEF( void) pjsip_transport_mgr_destroy( pjsip_transport_mgr *mgr )815 PJ_DEF(pj_status_t) pjsip_transport_mgr_destroy( pjsip_transport_mgr *mgr ) 801 816 { 802 817 pj_hash_iterator_t itr_val; … … 824 839 825 840 pj_mutex_unlock(mgr->mutex); 841 842 return PJ_SUCCESS; 826 843 } 827 844 … … 837 854 pjsip_transport_t *tr; 838 855 struct transport_key *hash_key; 839 int opt_val; 840 841 opt_val = DEFAULT_SO_SNDBUF; 842 if (pj_sock_setsockopt( sock_hnd, SOL_SOCKET, SO_SNDBUF, &opt_val, sizeof(opt_val)) != PJ_SUCCESS) { 843 PJ_LOG(3, (LOG_TRANSPORT_MGR, "create listener: error setting SNDBUF to %d", DEFAULT_SO_SNDBUF)); 844 // Just ignore the error. 845 } 846 847 opt_val = DEFAULT_SO_RCVBUF; 848 if (pj_sock_setsockopt( sock_hnd, SOL_SOCKET, SO_RCVBUF, &opt_val, sizeof(opt_val)) != PJ_SUCCESS) { 849 PJ_LOG(3, (LOG_TRANSPORT_MGR, "create listener: error setting RCVBUF to %d", DEFAULT_SO_SNDBUF)); 850 // Just ignore the error 851 } 852 853 tr = create_transport(mgr, type, sock_hnd, local_addr, addr_name); 854 if (!tr) { 856 const pj_str_t loopback_addr = { "127.0.0.1", 9 }; 857 pj_status_t status; 858 859 if (mgr->send_buf_size != 0) { 860 int opt_val = mgr->send_buf_size; 861 status = pj_sock_setsockopt( sock_hnd, PJ_SOL_SOCKET, 862 PJ_SO_SNDBUF, 863 &opt_val, sizeof(opt_val)); 864 865 if (status != PJ_SUCCESS) { 866 return status; 867 } 868 } 869 870 if (mgr->recv_buf_size != 0) { 871 int opt_val = mgr->recv_buf_size; 872 status = pj_sock_setsockopt( sock_hnd, PJ_SOL_SOCKET, 873 PJ_SO_RCVBUF, 874 &opt_val, sizeof(opt_val)); 875 if (status != PJ_SUCCESS) { 876 return status; 877 } 878 } 879 880 status = create_transport(mgr, type, sock_hnd, local_addr, addr_name, &tr); 881 if (status != PJ_SUCCESS) { 855 882 pj_sock_close(sock_hnd); 856 return -1;883 return status; 857 884 } 858 885 #if PJ_HAS_TCP 859 886 if (type == PJSIP_TRANSPORT_TCP) { 860 pj_status_t status; 861 862 if (pj_sock_listen(tr->sock, BACKLOG) != 0) { 863 PJ_PERROR((tr->obj_name, "listen()")); 887 888 status = pj_sock_listen(tr->sock, BACKLOG); 889 if (status != 0) { 864 890 destroy_transport(mgr, tr); 865 return -1; 866 } 867 tr->accept_data.addrlen = sizeof(tr->accept_data.local); 868 status = pj_ioqueue_accept(mgr->ioqueue, tr->key, 869 &tr->accept_data.sock, 870 &tr->accept_data.local, 871 &tr->accept_data.remote, 872 &tr->accept_data.addrlen); 873 if (status != PJ_IOQUEUE_PENDING) { 874 PJ_PERROR((tr->obj_name, "pj_ioqueue_accept()")); 875 destroy_transport(mgr, tr); 876 return -1; 877 } 891 return status; 892 } 893 894 /* Discard immediate connections. */ 895 do { 896 tr->accept_data.addrlen = sizeof(tr->accept_data.local); 897 status = pj_ioqueue_accept(tr->key, &tr->accept_op, 898 &tr->accept_data.sock, 899 &tr->accept_data.local, 900 &tr->accept_data.remote, 901 &tr->accept_data.addrlen); 902 if (status==PJ_SUCCESS) { 903 pj_sock_close(tr->accept_data.sock); 904 } else if (status != PJ_EPENDING) { 905 destroy_transport(mgr, tr); 906 return status; 907 } 908 } while (status==PJ_SUCCESS); 878 909 879 910 } else 880 911 #endif 881 912 if (type == PJSIP_TRANSPORT_UDP) { 882 pj_status_t status; 883 884 tr->rdata->addr_len = sizeof(tr->rdata->addr); 885 status = pj_ioqueue_recvfrom( mgr->ioqueue, tr->key, 886 tr->rdata->packet, PJSIP_MAX_PKT_LEN, 887 &tr->rdata->addr, 888 &tr->rdata->addr_len); 889 if (status != PJ_IOQUEUE_PENDING) { 890 PJ_PERROR((tr->obj_name, "pj_ioqueue_recvfrom()")); 891 destroy_transport(mgr, tr); 892 return -1; 893 } 913 pj_ssize_t bytes; 914 915 /* Discard immediate data. */ 916 do { 917 tr->rdata->addr_len = sizeof(tr->rdata->addr); 918 bytes = PJSIP_MAX_PKT_LEN; 919 status = pj_ioqueue_recvfrom( tr->key, &tr->rdata->op_key, 920 tr->rdata->packet, &bytes, 0, 921 &tr->rdata->addr, 922 &tr->rdata->addr_len); 923 if (status == PJ_SUCCESS) { 924 ; 925 } else if (status != PJ_EPENDING) { 926 destroy_transport(mgr, tr); 927 return status; 928 } 929 } while (status == PJ_SUCCESS); 894 930 } 895 931 … … 902 938 * See further comments on struct pjsip_transport_t definition. 903 939 */ 904 if (type == PJSIP_TRANSPORT_UDP && local_addr->sin_addr.s_addr == inet_addr("127.0.0.1")) { 940 if (type == PJSIP_TRANSPORT_UDP && 941 local_addr->sin_addr.s_addr == pj_inet_addr(&loopback_addr).s_addr) 942 { 905 943 pj_str_t localaddr = pj_str("127.0.0.1"); 906 pj_sockaddr_ set_str_addr( &tr->remote_addr, &localaddr);944 pj_sockaddr_in_set_str_addr( &tr->remote_addr, &localaddr); 907 945 } 908 946 hash_key = pj_pool_alloc(tr->pool, sizeof(transport_key)); … … 910 948 911 949 pj_mutex_lock(mgr->mutex); 912 pj_hash_set(tr->pool, mgr->transport_table, hash_key, sizeof(transport_key), tr); 950 pj_hash_set(tr->pool, mgr->transport_table, 951 hash_key, sizeof(transport_key), tr); 913 952 pj_mutex_unlock(mgr->mutex); 914 953 915 954 PJ_LOG(4,(tr->obj_name, "Listening at %s %s:%d", 916 955 get_type_name(tr->type), 917 pj_ sockaddr_get_str_addr(&tr->local_addr),918 pj_sockaddr_ get_port(&tr->local_addr)));956 pj_inet_ntoa(tr->local_addr.sin_addr), 957 pj_sockaddr_in_get_port(&tr->local_addr))); 919 958 PJ_LOG(4,(tr->obj_name, "Listener public address is at %s %s:%d", 920 959 get_type_name(tr->type), 921 pj_ sockaddr_get_str_addr(&tr->addr_name),922 pj_sockaddr_ get_port(&tr->addr_name)));960 pj_inet_ntoa(tr->addr_name.sin_addr), 961 pj_sockaddr_in_get_port(&tr->addr_name))); 923 962 return PJ_SUCCESS; 924 963 } … … 933 972 { 934 973 pj_sock_t sock_hnd; 974 pj_status_t status; 935 975 936 976 PJ_LOG(5, (LOG_TRANSPORT_MGR, "pjsip_create_listener(type=%d)", type)); 937 977 938 s ock_hnd = create_socket(type, local_addr);939 if (s ock_hnd == PJ_INVALID_SOCKET) {940 return -1;978 status = create_socket(type, local_addr, &sock_hnd); 979 if (status != PJ_SUCCESS) { 980 return status; 941 981 } 942 982 … … 952 992 { 953 993 pj_sockaddr_in local_addr; 994 pj_status_t status; 954 995 int addrlen = sizeof(local_addr); 955 996 956 if (pj_sock_getsockname(sock, (pj_sockaddr_t*)&local_addr, &addrlen) != 0) 957 return -1; 958 959 return create_listener(mgr, PJSIP_TRANSPORT_UDP, sock, &local_addr, addr_name); 997 status = pj_sock_getsockname(sock, (pj_sockaddr_t*)&local_addr, &addrlen); 998 if (status != PJ_SUCCESS) 999 return status; 1000 1001 return create_listener(mgr, PJSIP_TRANSPORT_UDP, sock, 1002 &local_addr, addr_name); 960 1003 } 961 1004 … … 974 1017 pjsip_transport_t *tr; 975 1018 pj_sockaddr_in local; 976 int sock_hnd;1019 pj_sock_t sock_hnd; 977 1020 pj_status_t status; 978 1021 struct transport_callback *cb_rec; … … 1030 1073 pj_memset(&local, 0, sizeof(local)); 1031 1074 local.sin_family = PJ_AF_INET; 1032 s ock_hnd = create_socket(type, &local);1033 if (s ock_hnd == PJ_INVALID_SOCKET) {1075 status = create_socket(type, &local, &sock_hnd); 1076 if (status != PJ_SUCCESS) { 1034 1077 pj_mutex_unlock(mgr->mutex); 1035 (*cb_rec->cb)(NULL, cb_rec->token, -1);1078 (*cb_rec->cb)(NULL, cb_rec->token, status); 1036 1079 return; 1037 1080 } 1038 tr = create_transport(mgr, type, sock_hnd, &local, NULL);1039 if ( !tr) {1081 status = create_transport(mgr, type, sock_hnd, &local, NULL, &tr); 1082 if (status != PJ_SUCCESS) { 1040 1083 pj_mutex_unlock(mgr->mutex); 1041 (*cb_rec->cb)(NULL, cb_rec->token, -1);1084 (*cb_rec->cb)(NULL, cb_rec->token, status); 1042 1085 return; 1043 1086 } … … 1046 1089 if (type == PJSIP_TRANSPORT_TCP) { 1047 1090 pj_memcpy(&tr->remote_addr, remote, sizeof(pj_sockaddr_in)); 1048 status = pj_ioqueue_connect( mgr->ioqueue, tr->key,1049 &tr->remote_addr,sizeof(pj_sockaddr_in));1091 status = pj_ioqueue_connect(tr->key, &tr->remote_addr, 1092 sizeof(pj_sockaddr_in)); 1050 1093 pj_assert(status != 0); 1051 if (status != PJ_ IOQUEUE_PENDING) {1052 PJ_ PERROR((tr->obj_name, "pj_ioqueue_connect()"));1094 if (status != PJ_EPENDING) { 1095 PJ_TODO(HANDLE_IMMEDIATE_CONNECT); 1053 1096 destroy_transport(mgr, tr); 1054 1097 pj_mutex_unlock(mgr->mutex); 1055 (*cb_rec->cb)(NULL, cb_rec->token, -1);1098 (*cb_rec->cb)(NULL, cb_rec->token, status); 1056 1099 return; 1057 1100 } … … 1059 1102 #endif 1060 1103 if (type == PJSIP_TRANSPORT_UDP) { 1061 int len;1104 pj_ssize_t size; 1062 1105 1063 1106 do { 1064 1107 tr->rdata->addr_len = sizeof(tr->rdata->addr); 1065 len = pj_ioqueue_recvfrom( mgr->ioqueue, tr->key, 1066 tr->rdata->packet, PJSIP_MAX_PKT_LEN, 1067 &tr->rdata->addr, 1068 &tr->rdata->addr_len); 1069 pj_assert(len < 0); 1070 if (len != PJ_IOQUEUE_PENDING) { 1071 PJ_PERROR((tr->obj_name, "pj_ioqueue_recvfrom()")); 1108 size = PJSIP_MAX_PKT_LEN; 1109 status = pj_ioqueue_recvfrom( tr->key, &tr->rdata->op_key, 1110 tr->rdata->packet, &size, 0, 1111 &tr->rdata->addr, 1112 &tr->rdata->addr_len); 1113 if (status == PJ_SUCCESS) 1114 ; 1115 else if (status != PJ_EPENDING) { 1072 1116 destroy_transport(mgr, tr); 1073 1117 pj_mutex_unlock(mgr->mutex); 1074 (*cb_rec->cb)(NULL, cb_rec->token, -1);1118 (*cb_rec->cb)(NULL, cb_rec->token, status); 1075 1119 return; 1076 1120 } … … 1083 1127 PJ_TODO(FIXED_BUG_ON_IMMEDIATE_TRANSPORT_DATA); 1084 1128 1085 } while ( len != PJ_IOQUEUE_PENDING);1129 } while (status == PJ_SUCCESS); 1086 1130 1087 1131 //Bug: cb will never be called! … … 1093 1137 } else { 1094 1138 pj_mutex_unlock(mgr->mutex); 1095 (*cb_rec->cb)(NULL, cb_rec->token, -1);1139 (*cb_rec->cb)(NULL, cb_rec->token, PJSIP_EUNSUPTRANSPORT); 1096 1140 return; 1097 1141 } 1098 1142 1099 pj_assert(status==PJ_ IOQUEUE_PENDING || status==PJ_SUCCESS);1143 pj_assert(status==PJ_EPENDING || status==PJ_SUCCESS); 1100 1144 pj_mutex_lock(tr->tr_mutex); 1101 1145 hash_key = pj_pool_alloc(tr->pool, sizeof(transport_key)); 1102 1146 pj_memcpy(hash_key, &search_key, sizeof(transport_key)); 1103 pj_hash_set(tr->pool, mgr->transport_table, hash_key, sizeof(transport_key), tr); 1147 pj_hash_set(tr->pool, mgr->transport_table, 1148 hash_key, sizeof(transport_key), tr); 1104 1149 if (status == PJ_SUCCESS) { 1105 1150 pj_mutex_unlock(tr->tr_mutex); … … 1125 1170 pjsip_transport_t *tr; 1126 1171 transport_key *hash_key; 1172 pj_ssize_t size; 1127 1173 1128 1174 pj_assert (listener->type == PJSIP_TRANSPORT_TCP); 1129 1175 1130 1176 if (status != PJ_SUCCESS) { 1131 PJ_PERROR((listener->obj_name, "accept() returned error")); 1132 return; 1177 PJSIP_ENDPT_LOG_ERROR((mgr->endpt, listener->obj_name, status, 1178 "Error in accept() completion")); 1179 goto on_return; 1133 1180 } 1134 1181 1135 1182 PJ_LOG(4,(listener->obj_name, "incoming tcp connection from %s:%d", 1136 pj_sockaddr_get_str_addr(&listener->accept_data.remote), 1137 pj_sockaddr_get_port(&listener->accept_data.remote))); 1138 1139 tr = create_transport(mgr, listener->type, 1140 listener->accept_data.sock, 1141 &listener->accept_data.local, 1142 NULL); 1143 if (!tr) { 1183 pj_inet_ntoa(listener->accept_data.remote.sin_addr), 1184 pj_sockaddr_in_get_port(&listener->accept_data.remote))); 1185 1186 status = create_transport(mgr, listener->type, 1187 listener->accept_data.sock, 1188 &listener->accept_data.local, 1189 NULL, &tr); 1190 if (status != PJ_SUCCESS) { 1191 PJSIP_ENDPT_LOG_ERROR((mgr->endpt, listener->obj_name, status, 1192 "Error in creating new incoming TCP")); 1144 1193 goto on_return; 1145 1194 } … … 1155 1204 tr->rdata->addr_len = listener->accept_data.addrlen; 1156 1205 1157 status = pj_ioqueue_read (mgr->ioqueue, tr->key, tr->rdata->packet, PJSIP_MAX_PKT_LEN); 1158 if (status != PJ_IOQUEUE_PENDING) { 1159 PJ_PERROR((tr->obj_name, "pj_ioqueue_read()")); 1206 size = PJSIP_MAX_PKT_LEN; 1207 status = pj_ioqueue_recv(tr->key, &tr->rdata->op_key, 1208 tr->rdata->packet, &size, 0); 1209 if (status != PJ_EPENDING) { 1210 PJSIP_ENDPT_LOG_ERROR((mgr->endpt, listener->obj_name, status, 1211 "Error in receiving data")); 1212 PJ_TODO(IMMEDIATE_DATA); 1160 1213 destroy_transport(mgr, tr); 1161 1214 goto on_return; 1162 1215 } 1163 1216 1164 pj_memcpy(&tr->remote_addr, &listener->accept_data.remote, listener->accept_data.addrlen); 1217 pj_memcpy(&tr->remote_addr, &listener->accept_data.remote, 1218 listener->accept_data.addrlen); 1165 1219 hash_key = pj_pool_alloc(tr->pool, sizeof(transport_key)); 1166 1220 init_key_from_transport(hash_key, tr); 1167 1221 1168 1222 pj_mutex_lock(mgr->mutex); 1169 pj_hash_set(tr->pool, mgr->transport_table, hash_key, sizeof(transport_key), tr); 1223 pj_hash_set(tr->pool, mgr->transport_table, hash_key, 1224 sizeof(transport_key), tr); 1170 1225 pj_mutex_unlock(mgr->mutex); 1171 1226 … … 1173 1228 /* Re-initiate asynchronous accept() */ 1174 1229 listener->accept_data.addrlen = sizeof(listener->accept_data.local); 1175 status = pj_ioqueue_accept( mgr->ioqueue, listener->key,1230 status = pj_ioqueue_accept(listener->key, &listener->accept_op, 1176 1231 &listener->accept_data.sock, 1177 1232 &listener->accept_data.local, 1178 1233 &listener->accept_data.remote, 1179 1234 &listener->accept_data.addrlen); 1180 if (status != PJ_IOQUEUE_PENDING) { 1181 PJ_PERROR((listener->obj_name, "pj_ioqueue_accept()")); 1235 if (status != PJ_EPENDING) { 1236 PJSIP_ENDPT_LOG_ERROR((mgr->endpt, listener->obj_name, status, 1237 "Error in receiving data")); 1238 PJ_TODO(IMMEDIATE_ACCEPT); 1182 1239 return; 1183 1240 } … … 1194 1251 struct transport_callback new_list; 1195 1252 struct transport_callback *cb_rec; 1196 1197 PJ_UNUSED_ARG(mgr) 1253 pj_ssize_t recv_size; 1254 1255 PJ_UNUSED_ARG(mgr); 1198 1256 1199 1257 /* On connect completion, we must call all registered callbacks in … … 1222 1280 if (status == PJ_SUCCESS) { 1223 1281 int addrlen = sizeof(tr->local_addr); 1224 int rc; 1225 if ((rc=pj_sock_getsockname(tr->sock, (pj_sockaddr_t*)&tr->local_addr, &addrlen)) == 0) { 1282 1283 status = pj_sock_getsockname(tr->sock, 1284 (pj_sockaddr_t*)&tr->local_addr, 1285 &addrlen); 1286 if (status == PJ_SUCCESS) { 1226 1287 pj_memcpy(&tr->addr_name, &tr->local_addr, sizeof(tr->addr_name)); 1227 } else {1228 PJ_LOG(4,(tr->obj_name, "Unable to get local address (getsockname=%d)", rc));1229 1288 } 1230 1289 } … … 1245 1304 if (status != PJ_SUCCESS) { 1246 1305 destroy_transport(mgr, tr); 1306 PJ_TODO(WTF); 1247 1307 return; 1248 1308 } 1249 1309 1250 1310 /* Initiate read operation to socket. */ 1251 status = pj_ioqueue_read (mgr->ioqueue, tr->key, tr->rdata->packet, PJSIP_MAX_PKT_LEN); 1252 if (status != PJ_IOQUEUE_PENDING) { 1253 PJ_PERROR((tr->obj_name, "pj_ioqueue_read()")); 1311 recv_size = PJSIP_MAX_PKT_LEN; 1312 status = pj_ioqueue_recv( tr->key, &tr->rdata->op_key, tr->rdata->packet, 1313 &recv_size, 0); 1314 if (status != PJ_EPENDING) { 1254 1315 destroy_transport(mgr, tr); 1316 PJ_TODO(IMMEDIATE_DATA); 1255 1317 return; 1256 1318 } … … 1270 1332 { 1271 1333 pjsip_msg *msg; 1272 pjsip_cid_hdr *call_id;1273 1334 pjsip_rx_data *rdata = tr->rdata; 1274 1335 pj_pool_t *rdata_pool; … … 1316 1377 1317 1378 /* Get source address and port for logging purpose. */ 1318 src_addr = pj_ sockaddr_get_str_addr(&rdata->addr);1319 src_port = pj_sockaddr_ get_port(&rdata->addr);1379 src_addr = pj_inet_ntoa(rdata->addr.sin_addr); 1380 src_port = pj_sockaddr_in_get_port(&rdata->addr); 1320 1381 1321 1382 /* Print the whole data to the log. */ … … 1334 1395 /* For TCP transport, check if the whole message has been received. */ 1335 1396 if (tr->type != PJSIP_TRANSPORT_UDP) { 1336 pj_bool_t is_complete; 1337 is_complete = pjsip_find_msg(rdata->packet, rdata->len, PJ_FALSE, &msg_fragment_size); 1338 if (!is_complete) { 1397 pj_status_t msg_status; 1398 msg_status = pjsip_find_msg(rdata->packet, rdata->len, PJ_FALSE, 1399 &msg_fragment_size); 1400 if (msg_status != PJ_SUCCESS) { 1339 1401 if (rdata->len == PJSIP_MAX_PKT_LEN) { 1340 PJ_LOG(1,(tr->obj_name, 1341 "Transport buffer full (%d bytes) for TCP socket %s:%d " 1342 "(probably too many invalid fragments received). " 1343 "Buffer will be discarded.", 1344 PJSIP_MAX_PKT_LEN, src_addr, src_port)); 1402 PJSIP_ENDPT_LOG_ERROR((mgr->endpt, tr->obj_name, 1403 PJSIP_EOVERFLOW, 1404 "Buffer discarded for %s:%d", 1405 src_addr, src_port)); 1345 1406 goto on_return; 1346 1407 } else { … … 1358 1419 src_addr, src_port)); 1359 1420 1360 msg = pjsip_parse_msg( rdata->pool, rdata->packet, msg_fragment_size, 1361 &rdata->parse_err); 1421 msg = pjsip_parse_rdata( rdata->packet, msg_fragment_size, rdata); 1362 1422 if (msg == NULL) { 1363 1423 PJ_LOG(3,(tr->obj_name, "Bad message (%d bytes from %s:%d)", msg_fragment_size, … … 1366 1426 } 1367 1427 1368 /* Attach newly created message to rdata. */ 1369 rdata->msg = msg; 1370 1371 /* Extract Call-ID, From and To header and tags, topmost Via, and CSeq 1372 * header from the message. 1373 */ 1374 call_id = pjsip_msg_find_hdr( msg, PJSIP_H_CALL_ID, NULL); 1375 rdata->from = pjsip_msg_find_hdr( msg, PJSIP_H_FROM, NULL); 1376 rdata->to = pjsip_msg_find_hdr( msg, PJSIP_H_TO, NULL); 1377 rdata->via = pjsip_msg_find_hdr( msg, PJSIP_H_VIA, NULL); 1378 rdata->cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, NULL); 1379 1380 if (call_id == NULL || rdata->from == NULL || rdata->to == NULL || 1381 rdata->via == NULL || rdata->cseq == NULL) 1428 /* Perform basic header checking. */ 1429 if (rdata->call_id.ptr == NULL || rdata->from == NULL || 1430 rdata->to == NULL || rdata->via == NULL || rdata->cseq == NULL) 1382 1431 { 1383 1432 PJ_LOG(3,(tr->obj_name, "Bad message from %s:%d: missing some header", … … 1385 1434 goto finish_process_fragment; 1386 1435 } 1387 rdata->call_id = call_id->id;1388 rdata->from_tag = rdata->from->tag;1389 rdata->to_tag = rdata->to->tag;1390 1436 1391 1437 /* If message is received from address that's different from the sent-by, … … 1402 1448 */ 1403 1449 if (rdata->via->rport_param == 0) { 1404 rdata->via->rport_param = pj_sockaddr_ get_port(&rdata->addr);1450 rdata->via->rport_param = pj_sockaddr_in_get_port(&rdata->addr); 1405 1451 } 1406 1452 … … 1409 1455 if (msg->type == PJSIP_RESPONSE_MSG) { 1410 1456 hdr = (pjsip_hdr*)rdata->via->next; 1411 if (hdr ) {1457 if (hdr != &rdata->msg->hdr) { 1412 1458 hdr = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, hdr); 1413 1459 if (hdr) { … … 1446 1492 rdata->addr_len = sizeof(rdata->addr); 1447 1493 if (tr->type == PJSIP_TRANSPORT_UDP) { 1448 pj_ioqueue_recvfrom( tr->mgr->ioqueue, tr->key, 1449 tr->rdata->packet, PJSIP_MAX_PKT_LEN, 1494 pj_ssize_t size = PJSIP_MAX_PKT_LEN; 1495 pj_ioqueue_recvfrom(tr->key, &tr->rdata->op_key, 1496 tr->rdata->packet, &size, 0, 1450 1497 &rdata->addr, &rdata->addr_len); 1498 PJ_TODO(HANDLE_IMMEDIATE_DATA); 1451 1499 } 1452 1500 … … 1457 1505 tcp_read_packet: 1458 1506 if (tr->type == PJSIP_TRANSPORT_TCP) { 1459 pj_ioqueue_read( tr->mgr->ioqueue, tr->key, 1507 pj_ssize_t size = PJSIP_MAX_PKT_LEN - tr->rdata->len; 1508 pj_ioqueue_recv( tr->key, &tr->rdata->op_key, 1460 1509 tr->rdata->packet + tr->rdata->len, 1461 PJSIP_MAX_PKT_LEN - tr->rdata->len); 1510 &size, 0); 1511 PJ_TODO(HANDLE_IMMEDIATE_DATA_1); 1462 1512 } 1463 1513 #endif … … 1508 1558 } 1509 1559 1510 static void on_ioqueue_read(pj_ioqueue_key_t *key, pj_ssize_t bytes_read) 1560 static void on_ioqueue_read(pj_ioqueue_key_t *key, 1561 pj_ioqueue_op_key_t *op_key, 1562 pj_ssize_t bytes_read) 1511 1563 { 1512 1564 pjsip_transport_t *t; … … 1516 1568 } 1517 1569 1518 static void on_ioqueue_write(pj_ioqueue_key_t *key, pj_ssize_t bytes_sent) 1519 { 1520 PJ_UNUSED_ARG(key) 1521 PJ_UNUSED_ARG(bytes_sent) 1570 static void on_ioqueue_write(pj_ioqueue_key_t *key, 1571 pj_ioqueue_op_key_t *op_key, 1572 pj_ssize_t bytes_sent) 1573 { 1574 PJ_UNUSED_ARG(key); 1575 PJ_UNUSED_ARG(bytes_sent); 1522 1576 1523 1577 /* Completion of write operation. … … 1526 1580 } 1527 1581 1528 static void on_ioqueue_accept(pj_ioqueue_key_t *key, int status) 1582 static void on_ioqueue_accept(pj_ioqueue_key_t *key, 1583 pj_ioqueue_op_key_t *op_key, 1584 pj_sock_t newsock, 1585 int status) 1529 1586 { 1530 1587 #if PJ_HAS_TCP … … 1534 1591 handle_new_connection( t->mgr, t, status ); 1535 1592 #else 1536 PJ_UNUSED_ARG(key) 1537 PJ_UNUSED_ARG(status) 1593 PJ_UNUSED_ARG(key); 1594 PJ_UNUSED_ARG(status); 1538 1595 #endif 1539 1596 } … … 1547 1604 handle_connect_completion( t->mgr, t, status); 1548 1605 #else 1549 PJ_UNUSED_ARG(key) 1550 PJ_UNUSED_ARG(status) 1606 PJ_UNUSED_ARG(key); 1607 PJ_UNUSED_ARG(status); 1551 1608 #endif 1552 1609 }
Note: See TracChangeset
for help on using the changeset viewer.