From 67d6a30732fd1e1fae2f98f646d97356b2eaa8c9 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 5 Jan 2006 23:35:46 +0000 Subject: Added loop transport to test transaction git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@107 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/build/pjsip_core.dsp | 20 + pjsip/build/test_pjsip.dsp | 8 + pjsip/include/pjsip/sip_config.h | 14 +- pjsip/include/pjsip/sip_endpoint.h | 2 +- pjsip/include/pjsip/sip_errno.h | 5 + pjsip/include/pjsip/sip_msg.h | 35 +- pjsip/include/pjsip/sip_transaction.h | 221 ++- pjsip/include/pjsip/sip_transport.h | 6 +- pjsip/include/pjsip/sip_transport_loop.h | 94 ++ pjsip/include/pjsip/sip_types.h | 6 + pjsip/include/pjsip/sip_util.h | 82 ++ pjsip/include/pjsip_core.h | 1 + pjsip/src/pjsip/sip_endpoint.c | 4 +- pjsip/src/pjsip/sip_errno.c | 3 +- pjsip/src/pjsip/sip_msg.c | 104 +- pjsip/src/pjsip/sip_transaction.c | 2151 +++++++++++++--------------- pjsip/src/pjsip/sip_transport.c | 24 +- pjsip/src/pjsip/sip_transport_loop.c | 423 ++++++ pjsip/src/pjsip/sip_transport_udp.c | 19 +- pjsip/src/pjsip/sip_util.c | 66 +- pjsip/src/pjsip/sip_util_proxy.c | 66 + pjsip/src/test-pjsip/msg_test.c | 32 +- pjsip/src/test-pjsip/test.c | 10 +- pjsip/src/test-pjsip/test.h | 9 +- pjsip/src/test-pjsip/transport_loop_test.c | 127 ++ pjsip/src/test-pjsip/transport_test.c | 33 +- pjsip/src/test-pjsip/transport_udp_test.c | 12 +- pjsip/src/test-pjsip/tsx_uac_test.c | 306 ++++ pjsip/src/test-pjsip/uri_test.c | 6 +- 29 files changed, 2522 insertions(+), 1367 deletions(-) create mode 100644 pjsip/include/pjsip/sip_transport_loop.h create mode 100644 pjsip/src/pjsip/sip_transport_loop.c create mode 100644 pjsip/src/pjsip/sip_util_proxy.c create mode 100644 pjsip/src/test-pjsip/transport_loop_test.c create mode 100644 pjsip/src/test-pjsip/tsx_uac_test.c (limited to 'pjsip') diff --git a/pjsip/build/pjsip_core.dsp b/pjsip/build/pjsip_core.dsp index 264799c7..608d7a6a 100644 --- a/pjsip/build/pjsip_core.dsp +++ b/pjsip/build/pjsip_core.dsp @@ -124,7 +124,15 @@ SOURCE=..\src\pjsip\sip_tel_uri.c # Begin Source File SOURCE=..\src\pjsip\sip_transaction.c + +!IF "$(CFG)" == "pjsip_core - Win32 Release" + # PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "pjsip_core - Win32 Debug" + +!ENDIF + # End Source File # Begin Source File @@ -132,6 +140,10 @@ SOURCE=..\src\pjsip\sip_transport.c # End Source File # Begin Source File +SOURCE=..\src\pjsip\sip_transport_loop.c +# End Source File +# Begin Source File + SOURCE=..\src\pjsip\sip_transport_udp.c # End Source File # Begin Source File @@ -144,6 +156,10 @@ SOURCE=..\src\pjsip\sip_util.c # End Source File # Begin Source File +SOURCE=..\src\pjsip\sip_util_proxy.c +# End Source File +# Begin Source File + SOURCE=..\src\pjsip\sip_util_statefull.c # PROP Exclude_From_Build 1 # End Source File @@ -221,6 +237,10 @@ SOURCE=..\include\pjsip\sip_transport.h # End Source File # Begin Source File +SOURCE=..\include\pjsip\sip_transport_loop.h +# End Source File +# Begin Source File + SOURCE=..\include\pjsip\sip_transport_udp.h # End Source File # Begin Source File diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp index 1c8ad9f2..649a8ae7 100644 --- a/pjsip/build/test_pjsip.dsp +++ b/pjsip/build/test_pjsip.dsp @@ -101,6 +101,10 @@ SOURCE="..\src\test-pjsip\test.c" # End Source File # Begin Source File +SOURCE="..\src\test-pjsip\transport_loop_test.c" +# End Source File +# Begin Source File + SOURCE="..\src\test-pjsip\transport_test.c" # End Source File # Begin Source File @@ -109,6 +113,10 @@ SOURCE="..\src\test-pjsip\transport_udp_test.c" # End Source File # Begin Source File +SOURCE="..\src\test-pjsip\tsx_uac_test.c" +# End Source File +# Begin Source File + SOURCE="..\src\test-pjsip\txdata_test.c" # End Source File # Begin Source File diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index 592aea41..158d4a75 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -45,6 +45,14 @@ #define PJSIP_RFC3261_BRANCH_ID "z9hG4bK" #define PJSIP_RFC3261_BRANCH_LEN 7 +/* Transaction related constants. */ +#define PJSIP_POOL_TSX_LAYER_LEN 4000 +#define PJSIP_POOL_TSX_LAYER_INC 4000 +#define PJSIP_MAX_TSX_COUNT (16*1024) +#define PJSIP_POOL_TSX_LEN 1536 //768 +#define PJSIP_POOL_TSX_INC 256 +#define PJSIP_MAX_TSX_KEY_LEN (PJSIP_MAX_URL_SIZE*2) + /* Message/URL related constants. */ #define PJSIP_MAX_CALL_ID_LEN PJ_GUID_STRING_LENGTH #define PJSIP_MAX_TAG_LEN PJ_GUID_STRING_LENGTH @@ -52,12 +60,6 @@ #define PJSIP_MAX_URL_SIZE 256 #define PJSIP_MAX_HNAME_LEN 64 -/* Transction related constants. */ -#define PJSIP_MAX_TSX_COUNT (16*1024) -#define PJSIP_POOL_LEN_TSX 1536 //768 -#define PJSIP_POOL_INC_TSX 256 -#define PJSIP_MAX_TSX_KEY_LEN (PJSIP_MAX_URL_SIZE*2) - /* Dialog related constants. */ #define PJSIP_MAX_DIALOG_COUNT (16*1024) #define PJSIP_POOL_LEN_DIALOG 1200 diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index d9d03a97..527cd886 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -209,7 +209,7 @@ PJ_DECL(pj_pool_t*) pjsip_endpt_create_pool( pjsip_endpoint *endpt, * @param endpt The endpoint. * @param pool The pool to be destroyed. */ -PJ_DECL(void) pjsip_endpt_destroy_pool( pjsip_endpoint *endpt, +PJ_DECL(void) pjsip_endpt_release_pool( pjsip_endpoint *endpt, pj_pool_t *pool ); /** diff --git a/pjsip/include/pjsip/sip_errno.h b/pjsip/include/pjsip/sip_errno.h index 4d50c75d..253e6c00 100644 --- a/pjsip/include/pjsip/sip_errno.h +++ b/pjsip/include/pjsip/sip_errno.h @@ -151,6 +151,11 @@ PJ_DECL(pj_str_t) pjsip_strerror( pj_status_t status, char *buffer, * Expecting response message. */ #define PJSIP_ENOTRESPONSEMSG (PJSIP_ERRNO_START_PJSIP + 31) /* 171031 */ +/** + * @hideinitializer + * Invalid header field. + */ +#define PJSIP_EINVALIDHDR (PJSIP_ERRNO_START_PJSIP + 32) /* 171032 */ /************************************************************ diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index e1866048..29bd9766 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -499,9 +499,13 @@ typedef struct pjsip_msg_body * For incoming messages, the parser will fill in this member with the * content type found in Content-Type header. * - * For outgoing messages, application must fill in this member with + * For outgoing messages, application may fill in this member with * appropriate value, because the stack will generate Content-Type header * based on the value specified here. + * + * If the content_type is empty, no Content-Type AND Content-Length header + * will be added to the message. The stack assumes that application adds + * these headers themselves. */ pjsip_media_type content_type; @@ -543,6 +547,19 @@ typedef struct pjsip_msg_body int (*print_body)(struct pjsip_msg_body *msg_body, char *buf, pj_size_t size); + /** Clone the data part only of this message body. Note that this only + * duplicates the data part of the body instead of the whole message + * body. If application wants to duplicate the entire message body + * structure, it must call #pjsip_msg_body_clone(). + * + * @param pool Pool used to clone the data. + * @param data The data inside message body, to be cloned. + * @param len The length of the data. + * + * @return New data duplicated from the original data. + */ + void* (*clone_data)(pj_pool_t *pool, const void *data, unsigned len); + } pjsip_msg_body; /** @@ -561,6 +578,22 @@ typedef struct pjsip_msg_body PJ_DECL(int) pjsip_print_text_body( pjsip_msg_body *msg_body, char *buf, pj_size_t size); +/** + * Clone the message body in src_body to the dst_body. This will duplicate + * the contents of the message body using the \a clone_data member of the + * source message body. + * + * @param pool Pool to use to duplicate the message body. + * @param dst_body Destination message body. + * @param src_body Source message body to duplicate. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_msg_body_clone(pj_pool_t *pool, + pjsip_msg_body *dst_body, + const pjsip_msg_body *src_body ); + + /** * @} */ diff --git a/pjsip/include/pjsip/sip_transaction.h b/pjsip/include/pjsip/sip_transaction.h index c0627aab..5d2a5126 100644 --- a/pjsip/include/pjsip/sip_transaction.h +++ b/pjsip/include/pjsip/sip_transaction.h @@ -25,7 +25,7 @@ */ #include -#include +#include #include PJ_BEGIN_DECL @@ -36,42 +36,26 @@ PJ_BEGIN_DECL * @{ */ -/* Forward decl. */ -struct pjsip_transaction; - - /** - * Transaction state. + * This enumeration represents transaction state. */ typedef enum pjsip_tsx_state_e { - PJSIP_TSX_STATE_NULL, - PJSIP_TSX_STATE_CALLING, - PJSIP_TSX_STATE_TRYING, - PJSIP_TSX_STATE_PROCEEDING, - PJSIP_TSX_STATE_COMPLETED, - PJSIP_TSX_STATE_CONFIRMED, - PJSIP_TSX_STATE_TERMINATED, - PJSIP_TSX_STATE_DESTROYED, - PJSIP_TSX_STATE_MAX, + PJSIP_TSX_STATE_NULL, /**< For UAC, before any message is sent. */ + PJSIP_TSX_STATE_CALLING, /**< For UAC, just after request is sent. */ + PJSIP_TSX_STATE_TRYING, /**< For UAS, just after request is received.*/ + PJSIP_TSX_STATE_PROCEEDING, /**< For UAS/UAC, after provisional response.*/ + PJSIP_TSX_STATE_COMPLETED, /**< For UAS/UAC, after final response. */ + PJSIP_TSX_STATE_CONFIRMED, /**< For UAS, after ACK is received. */ + PJSIP_TSX_STATE_TERMINATED, /**< For UAS/UAC, before it's destroyed. */ + PJSIP_TSX_STATE_DESTROYED, /**< For UAS/UAC, will be destroyed now. */ + PJSIP_TSX_STATE_MAX, /**< Number of states. */ } pjsip_tsx_state_e; /** - * State of the transport in the transaction. - * The transport is progressing independently of the transaction. - */ -typedef enum pjsip_tsx_transport_state_e -{ - PJSIP_TSX_TRANSPORT_STATE_NULL, - PJSIP_TSX_TRANSPORT_STATE_RESOLVING, - PJSIP_TSX_TRANSPORT_STATE_CONNECTING, - PJSIP_TSX_TRANSPORT_STATE_FINAL, -} pjsip_tsx_transport_state_e; - - -/** - * Transaction state. + * This structure describes SIP transaction object. The transaction object + * is used to handle both UAS and UAC transaction. */ struct pjsip_transaction { @@ -79,18 +63,18 @@ struct pjsip_transaction * Administrivia */ pj_pool_t *pool; /**< Pool owned by the tsx. */ + pjsip_module *tsx_user; /**< Transaction user. */ pjsip_endpoint *endpt; /**< Endpoint instance. */ pj_mutex_t *mutex; /**< Mutex for this tsx. */ - char obj_name[PJ_MAX_OBJ_NAME]; /**< Tsx name. */ - int tracing; /**< Tracing enabled? */ /* * Transaction identification. */ + char obj_name[PJ_MAX_OBJ_NAME]; /**< Log info. */ pjsip_role_e role; /**< Role (UAS or UAC) */ pjsip_method method; /**< The method. */ int cseq; /**< The CSeq */ - pj_str_t transaction_key;/**< hash table key. */ + pj_str_t transaction_key;/**< Hash table key. */ pj_str_t branch; /**< The branch Id. */ /* @@ -98,7 +82,8 @@ struct pjsip_transaction */ int status_code; /**< Last status code seen. */ pjsip_tsx_state_e state; /**< State. */ - int handle_ack; /**< Should we handle ACK? */ + int handle_200resp; /**< UAS 200/INVITE retrsm.*/ + int tracing; /**< Tracing enabled? */ /** Handler according to current state. */ pj_status_t (*state_handler)(struct pjsip_transaction *, pjsip_event *); @@ -106,123 +91,115 @@ struct pjsip_transaction /* * Transport. */ - pjsip_tsx_transport_state_e transport_state;/**< Transport's state. */ - pjsip_host_info dest_name; /**< Destination address. */ - pjsip_server_addresses remote_addr; /**< Addresses resolved. */ - int current_addr; /**< Address currently used. */ - pjsip_transport *transport; /**< Transport to use. */ + pj_sockaddr addr; /**< Destination address. */ + int addr_len; /**< Address length. */ + pjsip_response_addr res_addr; /**< Response address. */ + unsigned transport_flag; /**< Miscelaneous flag. */ /* * Messages and timer. */ pjsip_tx_data *last_tx; /**< Msg kept for retrans. */ - int has_unsent_msg; /**< Non-zero if tsx need to - transmit msg once resolver - completes. */ int retransmit_count;/**< Retransmission count. */ pj_timer_entry retransmit_timer;/**< Retransmit timer. */ pj_timer_entry timeout_timer; /**< Timeout timer. */ /** Module specific data. */ - void *module_data[PJSIP_MAX_MODULE]; + void *mod_data[PJSIP_MAX_MODULE]; }; /** - * Create new transaction. Application would normally use - * #pjsip_endpt_create_tsx rather than this function. - * - * @param pool Pool to use by the transaction. - * @param endpt Endpoint. - * @param p_tsx Pointer to return the transaction. - * - * @return PJ_SUCCESS or the appropriate error code. + * Create and register transaction layer module to the specified endpoint. * - * @see pjsip_endpt_create_tsx + * @param endpt The endpoint instance. * + * @return PJ_SUCCESS on success. */ -PJ_DEF(pj_status_t) pjsip_tsx_create( pj_pool_t *pool, - pjsip_endpoint *endpt, - pjsip_transaction **p_tsx); - -/** - * Init transaction as UAC from the specified transmit data (\c tdata). - * The transmit data must have a valid \c Request-Line and \c CSeq header. - * If \c Route headers are present, it will be used to calculate remote - * destination. - * - * If \c Via header does not exist, it will be created along with a unique - * \c branch parameter. If it exists and contains branch parameter, then - * the \c branch parameter will be used as is as the transaction key. - * - * The \c Route headers in the transmit data, if present, are used to - * calculate remote destination. - * - * At the end of the function, the transaction will start resolving the - * addresses of remote server to contact. Transport will be acquired as soon - * as the resolving job completes. - * - * @param tsx The transaction. - * @param tdata The transmit data. +PJ_DECL(pj_status_t) pjsip_tsx_layer_init(pjsip_endpoint *endpt); + +/** + * Get the instance of the transaction layer module. * - * @return PJ_SUCCESS if successfull. + * @return The transaction layer module. */ -PJ_DECL(pj_status_t) pjsip_tsx_init_uac( pjsip_transaction *tsx, - pjsip_tx_data *tdata); +PJ_DECL(pjsip_module*) pjsip_tsx_layer_instance(void); /** - * Init transaction as UAS. + * Unregister and destroy transaction layer module. * - * @param tsx The transaction to be initialized. - * @param rdata The received incoming request. - * - * @return PJ_SUCCESS if successfull. + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_tsx_init_uas( pjsip_transaction *tsx, - pjsip_rx_data *rdata); +PJ_DECL(pj_status_t) pjsip_tsx_layer_destroy(void); /** - * Process incoming message for this transaction. + * Find a transaction with the specified key. The transaction key normally + * is created by calling #pjsip_tsx_create_key() from an incoming message. * - * @param tsx The transaction. - * @param rdata The incoming message. + * @param key The key string to find the transaction. + * @param lock If non-zero, transaction will be locked before the + * function returns, to make sure that it's not deleted + * by other threads. + * + * @return The matching transaction instance, or NULL if transaction + * can not be found. */ -PJ_DECL(void) pjsip_tsx_on_rx_msg( pjsip_transaction *tsx, - pjsip_rx_data *rdata); +PJ_DECL(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key, + pj_bool_t lock ); /** - * Transmit message with this transaction. + * Create, initialize, and register a new transaction as UAC from the + * specified transmit data (\c tdata). The transmit data must have a valid + * \c Request-Line and \c CSeq header. * - * @param tsx The transaction. - * @param tdata The outgoing message. + * If \c Via header does not exist, it will be created along with a unique + * \c branch parameter. If it exists and contains branch parameter, then + * the \c branch parameter will be used as is as the transaction key. If + * it exists but branch parameter doesn't exist, a unique branch parameter + * will be created. + * + * @param tsx_user Module to be registered as transaction user of the new + * transaction, which will receive notification from the + * transaction via on_tsx_state() callback. + * @param tdata The outgoing request message. + * @param p_tsx On return will contain the new transaction instance. + * + * @return PJ_SUCCESS if successfull. */ -PJ_DECL(void) pjsip_tsx_on_tx_msg( pjsip_transaction *tsx, - pjsip_tx_data *tdata); - +PJ_DECL(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user, + pjsip_tx_data *tdata, + pjsip_transaction **p_tsx); /** - * Transmit ACK message for 2xx/INVITE with this transaction. The ACK for - * non-2xx/INVITE is automatically sent by the transaction. - * This operation is only valid if the transaction is configured to handle ACK - * (tsx->handle_ack is non-zero). If this attribute is not set, then the - * transaction will comply with RFC-3261, i.e. it will set itself to - * TERMINATED state when it receives 2xx/INVITE. + * Create, initialize, and register a new transaction as UAS from the + * specified incoming request in \c rdata. * - * @param tsx The transaction. - * @param tdata The ACK request. + * @param tsx_user Module to be registered as transaction user of the new + * transaction, which will receive notification from the + * transaction via on_tsx_state() callback. + * @param rdata The received incoming request. + * @param p_tsx On return will contain the new transaction instance. + * + * @return PJ_SUCCESS if successfull. */ -PJ_DECL(void) pjsip_tsx_on_tx_ack( pjsip_transaction *tsx, - pjsip_tx_data *tdata); +PJ_DECL(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user, + pjsip_rx_data *rdata, + pjsip_transaction **p_tsx ); /** - * Force terminate transaction. + * Transmit message in tdata with this transaction. It is possible to + * pass NULL in tdata for UAC transaction, which in this case the request + * message which was specified in #pjsip_tsx_create_uac() will be sent. * * @param tsx The transaction. - * @param code The status code to report. + * @param tdata The outgoing message. + * + * @return PJ_SUCCESS if successfull. */ -PJ_DECL(void) pjsip_tsx_terminate( pjsip_transaction *tsx, - int code ); +PJ_DECL(pj_status_t) pjsip_tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata); + /** * Create transaction key, which is used to match incoming requests @@ -243,6 +220,29 @@ PJ_DECL(pj_status_t) pjsip_tsx_create_key( pj_pool_t *pool, const pjsip_rx_data *rdata ); +/** + * Force terminate transaction. + * + * @param tsx The transaction. + * @param code The status code to report. + */ +PJ_DECL(pj_status_t) pjsip_tsx_terminate( pjsip_transaction *tsx, + int st_code ); + + +/** + * Get the transaction instance in the incoming message. If the message + * has a corresponding transaction, this function will return non NULL + * value. + * + * @param rdata The incoming message buffer. + * + * @return The transaction instance associated with this message, + * or NULL if the message doesn't match any transactions. + */ +PJ_DECL(pjsip_transaction*) pjsip_rdata_get_tsx( pjsip_rx_data *rdata ); + + /** * @} */ @@ -262,9 +262,6 @@ PJ_DECL(const char *) pjsip_tsx_state_str(pjsip_tsx_state_e state); PJ_DECL(const char *) pjsip_role_name(pjsip_role_e role); -/* Thread Local Storage ID for transaction lock (initialized by endpoint) */ -extern long pjsip_tsx_lock_tls_id; - PJ_END_DECL #endif /* __PJSIP_TRANSACT_H__ */ diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h index 6e6ba428..c296054c 100644 --- a/pjsip/include/pjsip/sip_transport.h +++ b/pjsip/include/pjsip/sip_transport.h @@ -171,7 +171,7 @@ struct pjsip_rx_data pj_uint32_t zero; /** The length of the packet received. */ - int len; + pj_ssize_t len; /** The source address from which the packet was received. */ pj_sockaddr src_addr; @@ -251,9 +251,9 @@ struct pjsip_rx_data struct { /** - * This the transaction key generated for the message. + * Data attached by modules to this message. */ - pj_str_t key; + void *mod_data[PJSIP_MAX_MODULE]; } endpt_info; diff --git a/pjsip/include/pjsip/sip_transport_loop.h b/pjsip/include/pjsip/sip_transport_loop.h new file mode 100644 index 00000000..bfcb93f4 --- /dev/null +++ b/pjsip/include/pjsip/sip_transport_loop.h @@ -0,0 +1,94 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSIP_TRANSPORT_LOOP_H__ +#define __PJSIP_TRANSPORT_LOOP_H__ + +#include + +PJ_BEGIN_DECL + +/** + * Create and start datagram loop transport. + * + * @param endpt The endpoint instance. + * @param transport Pointer to receive the transport instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt, + pjsip_transport **transport); + + +/** + * Enable/disable flag to discard any packets sent using the specified + * loop transport. + * + * @param tp The loop transport. + * @param discard If non-zero, any outgoing packets will be discarded. + * @param prev_value Optional argument to receive previous value of + * the discard flag. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_loop_set_discard( pjsip_transport *tp, + pj_bool_t discard, + pj_bool_t *prev_value ); + + +/** + * Enable/disable flag to simulate network error. When this flag is set, + * outgoing transmission will return either immediate error or error via + * callback. If error is to be notified via callback, then the notification + * will occur after some delay, which is controlled by #pjsip_loop_set_delay(). + * + * @param tp The loop transport. + * @param fail_flag If set to 1, the transport will return immediate error. + * If set to 2, the transport will return error via + * callback. If zero, the transport will deliver + * the packet. + * @param prev_value Optional argument to receive previous value of + * the failure flag. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_loop_set_failure( pjsip_transport *tp, + int fail_flag, + int *prev_value ); + + +/** + * Set delay (in miliseconds) before packet is delivered. This will also + * control the delay for error notification callback. + * + * @param tp The loop transport. + * @param delay Delay, in miliseconds. + * @param prev_value Optional argument to receive previous value of the + * delay. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_loop_set_delay( pjsip_transport *tp, + unsigned delay, + unsigned *prev_value); + +PJ_END_DECL + + +#endif /* __PJSIP_TRANSPORT_LOOP_H__ */ + diff --git a/pjsip/include/pjsip/sip_types.h b/pjsip/include/pjsip/sip_types.h index 516d50d5..72fe3af5 100644 --- a/pjsip/include/pjsip/sip_types.h +++ b/pjsip/include/pjsip/sip_types.h @@ -52,6 +52,12 @@ typedef enum pjsip_transport_type_e /** SCTP. */ PJSIP_TRANSPORT_SCTP, + /** Loopback (stream, reliable) */ + PJSIP_TRANSPORT_LOOP, + + /** Loopback (datagram, unreliable) */ + PJSIP_TRANSPORT_LOOP_DGRAM, + } pjsip_transport_type_e; diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h index ea0fcf3f..f0d55bdf 100644 --- a/pjsip/include/pjsip/sip_util.h +++ b/pjsip/include/pjsip/sip_util.h @@ -327,6 +327,29 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, pj_ssize_t sent, pj_bool_t *cont)); +/** + * This composite function sends response message statelessly to an incoming + * request message. Internally it calls #pjsip_endpt_create_response() and + * #pjsip_endpt_send_response(). + * + * @param endpt The endpoint instance. + * @param rdata The incoming request message. + * @param st_code Status code of the response. + * @param st_text Optional status text of the response. + * @param hdr_list Optional header list to be added to the response. + * @param body Optional message body to be added to the response. + * + * @return PJ_SUCCESS if response message has successfully been + * created. + */ +PJ_DECL(pj_status_t) pjsip_endpt_respond_stateless(pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *hdr_list, + const pjsip_msg_body *body); + + /** * Send outgoing request and initiate UAC transaction for the request. * This is an auxiliary function to be used by application to send arbitrary @@ -352,6 +375,65 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, void *token, void (*cb)(void*,pjsip_event*)); +/** + * Create new request message to be forwarded upstream to new destination URI + * in uri. The new request is a full/deep clone of the request received in + * rdata, unless if other copy mechanism is specified in the options. + * The branch parameter, if not NULL, will be used as the branch-param in + * the Via header. If it is NULL, then a unique branch parameter will be used. + * + * @param endpt The endpoint instance. + * @param rdata The incoming request message. + * @param uri The URI where the request will be forwarded to. + * @param branch Optional branch parameter. + * @param options Optional option flags when duplicating the message. + * @param tdata The result. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_endpt_create_request_fwd( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + const pjsip_uri *uri, + const pj_str_t *branch, + unsigned options, + pjsip_tx_data **tdata); + +/** + * Create new response message to be forwarded downstream by the proxy from + * the response message found in rdata. Note that this function practically + * will clone the response as is, i.e. without checking the validity of the + * response or removing top most Via header. This function will perform + * full/deep clone of the response, unless other copy mechanism is used in + * the options. + * + * @param endpt The endpoint instance. + * @param rdata The incoming response message. + * @param options Optional option flags when duplicate the message. + * @param tdata The result + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + unsigned options, + pjsip_tx_data **tdata); + + +/** + * Create a globally unique branch parameter based on the information in + * the incoming request message. This function guarantees that subsequent + * retransmissions of the same request will generate the same branch id. + * This function can also be used in the loop detection process. + * If the same request arrives back in the proxy with the same URL, it will + * calculate into the same branch id. + * Note that the returned string was allocated from rdata's pool. + * + * @param rdata The incoming request message. + * + * @return Unique branch-ID string. + */ +PJ_DECL(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata ); + /** * @} diff --git a/pjsip/include/pjsip_core.h b/pjsip/include/pjsip_core.h index 9f3c7e9b..5947d4b1 100644 --- a/pjsip/include/pjsip_core.h +++ b/pjsip/include/pjsip_core.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index 61741cb1..d299cfff 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -490,9 +490,9 @@ PJ_DEF(pj_pool_t*) pjsip_endpt_create_pool( pjsip_endpoint *endpt, * Return back pool to endpoint's pool manager to be either destroyed or * recycled. */ -PJ_DEF(void) pjsip_endpt_destroy_pool( pjsip_endpoint *endpt, pj_pool_t *pool ) +PJ_DEF(void) pjsip_endpt_release_pool( pjsip_endpoint *endpt, pj_pool_t *pool ) { - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_destroy_pool(%s)", pj_pool_getobjname(pool))); + PJ_LOG(5, (THIS_FILE, "pjsip_endpt_release_pool(%s)", pj_pool_getobjname(pool))); pj_mutex_lock(endpt->mutex); pj_pool_release( pool ); diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c index f3c7319b..f7d2ddd3 100644 --- a/pjsip/src/pjsip/sip_errno.c +++ b/pjsip/src/pjsip/sip_errno.c @@ -1,4 +1,4 @@ -/* $Id: $ */ +/* $Id$ */ /* * Copyright (C) 2003-2006 Benny Prijono * @@ -47,6 +47,7 @@ static const struct { PJSIP_EINVALIDREQURI, "Invalid Request URI" }, { PJSIP_ENOTREQUESTMSG, "Expecting request message"}, { PJSIP_ENOTRESPONSEMSG, "Expecting response message"}, + { PJSIP_EINVALIDHDR, "Invalid header field"}, /* Transport errors */ { PJSIP_EUNSUPTRANSPORT, "Unsupported transport"}, diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 1d5ffe06..cee850eb 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -365,37 +366,44 @@ PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, /* Process message body. */ if (msg->body) { - pj_str_t ctype_hdr = { "Content-Type: ", 14}; - int len; - const pjsip_media_type *media = &msg->body->content_type; - char *clen_pos; + char *clen_pos = NULL; - /* Add Content-Type header. */ - if ( (end-p) < 24+media->type.slen+media->subtype.slen+media->param.slen) { - return -1; - } - pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen); - p += ctype_hdr.slen; - p += print_media_type(p, media); - *p++ = '\r'; - *p++ = '\n'; - - /* Add Content-Length header. */ - if ((end-p) < clen_hdr.slen+12+2) { - return -1; - } - pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen); - p += clen_hdr.slen; - - /* Print blanks after "Content-Type:", this is where we'll put - * the content length value after we know the length of the - * body. + /* Automaticly adds Content-Type and Content-Length headers, only + * if content_type is set in the message body. */ - pj_memset(p, ' ', 12); - clen_pos = p; - p += 12; - *p++ = '\r'; - *p++ = '\n'; + if (msg->body->content_type.type.slen) { + pj_str_t ctype_hdr = { "Content-Type: ", 14}; + const pjsip_media_type *media = &msg->body->content_type; + + /* Add Content-Type header. */ + if ( (end-p) < 24 + media->type.slen + media->subtype.slen + + media->param.slen) + { + return -1; + } + pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen); + p += ctype_hdr.slen; + p += print_media_type(p, media); + *p++ = '\r'; + *p++ = '\n'; + + /* Add Content-Length header. */ + if ((end-p) < clen_hdr.slen + 12 + 2) { + return -1; + } + pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen); + p += clen_hdr.slen; + + /* Print blanks after "Content-Type:", this is where we'll put + * the content length value after we know the length of the + * body. + */ + pj_memset(p, ' ', 12); + clen_pos = p; + p += 12; + *p++ = '\r'; + *p++ = '\n'; + } /* Add blank newline. */ *p++ = '\r'; @@ -411,8 +419,10 @@ PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, /* Now that we have the length of the body, print this to the * Content-Length header. */ - len = pj_utoa(len, clen_pos); - clen_pos[len] = ' '; + if (clen_pos) { + len = pj_utoa(len, clen_pos); + clen_pos[len] = ' '; + } } else { /* There's no message body. @@ -1464,7 +1474,7 @@ static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool, /////////////////////////////////////////////////////////////////////////////// /* - * General purpose function to textual data in a SIP body. + * Message body manipulations. */ PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) { @@ -1473,3 +1483,33 @@ PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t pj_memcpy(buf, msg_body->data, msg_body->len); return msg_body->len; } + +PJ_DEF(pj_status_t) pjsip_msg_body_clone( pj_pool_t *pool, + pjsip_msg_body *dst_body, + const pjsip_msg_body *src_body ) +{ + /* First check if clone_data field is initialized. */ + PJ_ASSERT_RETURN( src_body->clone_data!=NULL, PJ_EINVAL ); + + /* Duplicate content-type */ + pj_strdup(pool, &dst_body->content_type.type, + &src_body->content_type.type); + pj_strdup(pool, &dst_body->content_type.subtype, + &src_body->content_type.subtype); + pj_strdup(pool, &dst_body->content_type.param, + &src_body->content_type.param); + + /* Duplicate data. */ + dst_body->data = (*src_body->clone_data)(pool, src_body->data, + src_body->len ); + + /* Length. */ + dst_body->len = src_body->len; + + /* Function pointers. */ + dst_body->print_body = src_body->print_body; + dst_body->clone_data = src_body->clone_data; + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index 1497fbd6..cd673338 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -17,413 +17,64 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include -#include #include -#include +#include #include #include -#include -#include -#include -#include +#include +#include #include +#include +#include #include +#include +#include -#if 0 // XXX JUNK - /* Initialize TLS ID for transaction lock. */ - status = pj_thread_local_alloc(&pjsip_tsx_lock_tls_id); - if (status != PJ_SUCCESS) { - goto on_error; - } - pj_thread_local_set(pjsip_tsx_lock_tls_id, NULL); - - - /* Create hash table for transaction. */ - endpt->tsx_table = pj_hash_create( endpt->pool, PJSIP_MAX_TSX_COUNT ); - if (!endpt->tsx_table) { - status = PJ_ENOMEM; - goto on_error; - } - - -/* - * Create a new transaction. - * Endpoint must then initialize the new transaction as either UAS or UAC, and - * register it to the hash table. - */ -PJ_DEF(pj_status_t) pjsip_endpt_create_tsx(pjsip_endpoint *endpt, - pjsip_transaction **p_tsx) -{ - pj_pool_t *pool; - - PJ_ASSERT_RETURN(endpt && p_tsx, PJ_EINVAL); - - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_create_tsx()")); - - /* Request one pool for the transaction. Mutex is locked there. */ - pool = pjsip_endpt_create_pool(endpt, "ptsx%p", - PJSIP_POOL_LEN_TSX, PJSIP_POOL_INC_TSX); - if (pool == NULL) { - return PJ_ENOMEM; - } - - /* Create the transaction. */ - return pjsip_tsx_create(pool, endpt, p_tsx); -} - - -/* - * Register the transaction to the endpoint. - * This will put the transaction to the transaction hash table. Before calling - * this function, the transaction must be INITIALIZED as either UAS or UAC, so - * that the transaction key is built. - */ -PJ_DEF(void) pjsip_endpt_register_tsx( pjsip_endpoint *endpt, - pjsip_transaction *tsx) -{ - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_register_tsx(%s)", tsx->obj_name)); - - pj_assert(tsx->transaction_key.slen != 0); - //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL); - - /* Lock hash table mutex. */ - pj_mutex_lock(endpt->tsx_table_mutex); - - /* Register the transaction to the hash table. */ - pj_hash_set( tsx->pool, endpt->tsx_table, tsx->transaction_key.ptr, - tsx->transaction_key.slen, tsx); - - /* Unlock mutex. */ - pj_mutex_unlock(endpt->tsx_table_mutex); -} - -/* - * Find transaction by the key. - */ -PJ_DEF(pjsip_transaction*) pjsip_endpt_find_tsx( pjsip_endpoint *endpt, - const pj_str_t *key ) -{ - pjsip_transaction *tsx; - - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_find_tsx()")); - - /* Start lock mutex in the endpoint. */ - pj_mutex_lock(endpt->tsx_table_mutex); - - /* Find the transaction in the hash table. */ - tsx = pj_hash_get( endpt->tsx_table, key->ptr, key->slen ); - - /* Unlock mutex. */ - pj_mutex_unlock(endpt->tsx_table_mutex); - - return tsx; -} - -/* - * Create key. - */ -static void rdata_create_key( pjsip_rx_data *rdata) -{ - pjsip_role_e role; - if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { - role = PJSIP_ROLE_UAS; - } else { - role = PJSIP_ROLE_UAC; - } - pjsip_tsx_create_key(rdata->tp_info.pool, &rdata->endpt_info.key, role, - &rdata->msg_info.cseq->method, rdata); -} - - -/* - * This is the callback that is called by the transport manager when it - * receives a message from the network. - */ -static void endpt_transport_callback( pjsip_endpoint *endpt, - pj_status_t status, - pjsip_rx_data *rdata ) +/***************************************************************************** + ** + ** Declarations and static variable definitions section. + ** + ***************************************************************************** + +/* Prototypes. */ +static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt); +static pj_status_t mod_tsx_layer_start(void); +static pj_status_t mod_tsx_layer_stop(void); +static pj_status_t mod_tsx_layer_unload(void); +static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata); + +/* Transaction layer module definition. */ +static struct mod_tsx_layer { - pjsip_msg *msg = rdata->msg_info.msg; - pjsip_transaction *tsx; - pj_bool_t a_new_transaction_just_been_created = PJ_FALSE; - - PJ_LOG(5, (THIS_FILE, "endpt_transport_callback(rdata=%p)", rdata)); - - if (status != PJ_SUCCESS) { - const char *src_addr = rdata->pkt_info.src_name; - int port = rdata->pkt_info.src_port; - PJSIP_ENDPT_LOG_ERROR((endpt, "transport", status, - "Src.addr=%s:%d, packet:--\n" - "%s\n" - "-- end of packet. Error", - src_addr, port, rdata->msg_info.msg_buf)); - return; - } - - /* For response, check that the value in Via sent-by match the transport. - * If not matched, silently drop the response. - * Ref: RFC3261 Section 18.1.2 Receiving Response - */ - if (msg->type == PJSIP_RESPONSE_MSG) { - const pj_str_t *addr_addr; - int port = rdata->msg_info.via->sent_by.port; - pj_bool_t mismatch = PJ_FALSE; - if (port == 0) { - int type; - type = rdata->tp_info.transport->key.type; - port = pjsip_transport_get_default_port_for_type(type); - } - addr_addr = &rdata->tp_info.transport->local_name.host; - if (pj_strcmp(&rdata->msg_info.via->sent_by.host, addr_addr) != 0) - mismatch = PJ_TRUE; - else if (port != rdata->tp_info.transport->local_name.port) { - /* Port or address mismatch, we should discard response */ - /* But we saw one implementation (we don't want to name it to - * protect the innocence) which put wrong sent-by port although - * the "rport" parameter is correct. - * So we discard the response only if the port doesn't match - * both the port in sent-by and rport. We try to be lenient here! - */ - if (rdata->msg_info.via->rport_param != rdata->tp_info.transport->local_name.port) - mismatch = PJ_TRUE; - else { - PJ_LOG(4,(THIS_FILE, "Response %p has mismatch port in sent-by" - " but the rport parameter is correct", - rdata)); - } - } - - if (mismatch) { - pjsip_event e; - - PJSIP_EVENT_INIT_DISCARD_MSG(e, rdata, PJSIP_EINVALIDVIA); - endpt_do_event( endpt, &e ); - return; - } - } - - /* Create key for transaction lookup. */ - rdata_create_key( rdata); - - /* Find the transaction for the received message. */ - PJ_LOG(5, (THIS_FILE, "finding tsx with key=%.*s", - rdata->endpt_info.key.slen, rdata->endpt_info.key.ptr)); - - /* Start lock mutex in the endpoint. */ - pj_mutex_lock(endpt->tsx_table_mutex); - - /* Find the transaction in the hash table. */ - tsx = pj_hash_get( endpt->tsx_table, rdata->endpt_info.key.ptr, rdata->endpt_info.key.slen ); - - /* Unlock mutex. */ - pj_mutex_unlock(endpt->tsx_table_mutex); - - /* If the transaction is not found... */ - if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { - - /* - * For response message, discard the message, except if the response is - * an 2xx class response to INVITE, which in this case it must be - * passed to TU to be acked. - */ - if (msg->type == PJSIP_RESPONSE_MSG) { - - /* Inform TU about the 200 message, only if it's INVITE. */ - if (PJSIP_IS_STATUS_IN_CLASS(msg->line.status.code, 200) && - rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) - { - pjsip_event e; - - /* Should not happen for UA. Tsx theoritically lives until - * all responses are absorbed. - */ - pj_assert(0); - - PJSIP_EVENT_INIT_RX_200_MSG(e, rdata); - endpt_do_event( endpt, &e ); - - } else { - /* Just discard the response, inform TU. */ - pjsip_event e; - - PJSIP_EVENT_INIT_DISCARD_MSG(e, rdata, - PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_CALL_TSX_DOES_NOT_EXIST)); - endpt_do_event( endpt, &e ); - } - - /* - * For non-ACK request message, create a new transaction. - */ - } else if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) { - - pj_status_t status; - - /* Create transaction, mutex is locked there. */ - status = pjsip_endpt_create_tsx(endpt, &tsx); - if (status != PJ_SUCCESS) { - PJSIP_ENDPT_LOG_ERROR((endpt, THIS_FILE, status, - "Unable to create transaction")); - return; - } - - /* Initialize transaction as UAS. */ - pjsip_tsx_init_uas( tsx, rdata ); - - /* Register transaction, mutex is locked there. */ - pjsip_endpt_register_tsx( endpt, tsx ); - - a_new_transaction_just_been_created = PJ_TRUE; - } - } - - /* If transaction is found (or newly created), pass the message. - * Otherwise if it's an ACK request, pass directly to TU. - */ - if (tsx && tsx->state != PJSIP_TSX_STATE_TERMINATED) { - /* Dispatch message to transaction. */ - pjsip_tsx_on_rx_msg( tsx, rdata ); - - } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) { - /* - * This is an ACK message, but the INVITE transaction could not - * be found (possibly because the branch parameter in Via in ACK msg - * is different than the branch in original INVITE). This happens with - * SER! - */ - pjsip_event event; - - PJSIP_EVENT_INIT_RX_ACK_MSG(event,rdata); - endpt_do_event( endpt, &event ); - } - - /* - * If a new request message has just been receieved, but no modules - * seem to be able to handle the request message, then terminate the - * transaction. - * - * Ideally for cases like "unsupported method", we should be able to - * answer the request statelessly. But we can not do that since the - * endpoint shoule be able to be used as both user agent and proxy stack, - * and a proxy stack should be able to handle arbitrary methods. - */ - if (a_new_transaction_just_been_created && tsx->status_code < 100) { - /* Certainly no modules has sent any response message. - * Check that any modules has attached a module data. - */ - int i; - for (i=0; imodule_data[i] != NULL) { - break; - } - } - if (i == PJSIP_MAX_MODULE) { - /* No modules have attached itself to the transaction. - * Terminate the transaction with 501/Not Implemented. - */ - pjsip_tx_data *tdata; - pj_status_t status; - - if (tsx->method.id == PJSIP_OPTIONS_METHOD) { - status = pjsip_endpt_create_response(endpt, rdata, 200, - &tdata); - } else { - status = pjsip_endpt_create_response(endpt, rdata, - PJSIP_SC_METHOD_NOT_ALLOWED, - &tdata); - } - - if (status != PJ_SUCCESS) { - PJSIP_ENDPT_LOG_ERROR((endpt, THIS_FILE, status, - "Unable to create response")); - return; - } - - if (endpt->allow_hdr) { - pjsip_msg_add_hdr( tdata->msg, - pjsip_hdr_shallow_clone(tdata->pool, endpt->allow_hdr)); - } - pjsip_tsx_on_tx_msg( tsx, tdata ); - - } else { - /* - * If a module has registered itself in the transaction but it - * hasn't responded the request, chances are the module wouldn't - * respond to the request at all. We terminate the request here - * with 500/Internal Server Error, to be safe. - */ - pjsip_tx_data *tdata; - pj_status_t status; - - status = pjsip_endpt_create_response(endpt, rdata, 500, &tdata); - if (status != PJ_SUCCESS) { - PJSIP_ENDPT_LOG_ERROR((endpt, THIS_FILE, status, - "Unable to create response")); - return; - } - - pjsip_tsx_on_tx_msg(tsx, tdata); - } - } -} - - - - /* Transaction tables. */ - count = pj_hash_count(endpt->tsx_table); - PJ_LOG(3, (THIS_FILE, " Number of transactions: %u", count)); - - if (count && detail) { - pj_hash_iterator_t it_val; - pj_hash_iterator_t *it; - pj_time_val now; - - PJ_LOG(3, (THIS_FILE, " Dumping transaction tables:")); - - pj_gettimeofday(&now); - it = pj_hash_first(endpt->tsx_table, &it_val); - - while (it != NULL) { - int timeout_diff; - - /* Get the transaction. No need to lock transaction's mutex - * since we already hold endpoint mutex, so that no transactions - * will be deleted. - */ - pjsip_transaction *tsx = pj_hash_this(endpt->tsx_table, it); - - const char *role = (tsx->role == PJSIP_ROLE_UAS ? "UAS" : "UAC"); - - if (tsx->timeout_timer._timer_id != -1) { - if (tsx->timeout_timer._timer_value.sec > now.sec) { - timeout_diff = tsx->timeout_timer._timer_value.sec - now.sec; - } else { - timeout_diff = now.sec - tsx->timeout_timer._timer_value.sec; - timeout_diff = 0 - timeout_diff; - } - } else { - timeout_diff = -1; - } - - PJ_LOG(3, (THIS_FILE, " %s %s %10.*s %.9u %s t=%ds", - tsx->obj_name, role, - tsx->method.name.slen, tsx->method.name.ptr, - tsx->cseq, - pjsip_tsx_state_str(tsx->state), - timeout_diff)); - - it = pj_hash_next(endpt->tsx_table, it); - } + struct pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_mutex_t *mutex; + pj_hash_table_t *htable; +} mod_tsx_layer = +{ { + NULL, NULL, /* List's prev and next. */ + { "mod-tsx-layer", 13 }, /* Module name. */ + -1, /* Module ID */ + PJSIP_MOD_PRIORITY_TSX_LAYER, /* Priority. */ + NULL, /* User_data. */ + 0, /* Methods count. */ + { NULL }, /* Array of methods. */ + mod_tsx_layer_load, /* load(). */ + mod_tsx_layer_start, /* start() */ + mod_tsx_layer_stop, /* stop() */ + mod_tsx_layer_unload, /* unload() */ + mod_tsx_layer_on_rx_request, /* on_rx_request() */ + mod_tsx_layer_on_rx_response, /* on_rx_response() */ + NULL } +}; +/* Thread Local Storage ID for transaction lock */ +static long pjsip_tsx_lock_tls_id; - -#endif // XXX JUNK - -/* Thread Local Storage ID for transaction lock (initialized by endpoint) */ -long pjsip_tsx_lock_tls_id; - -/* State names */ +/* Transaction state names */ static const char *state_str[] = { "Null", @@ -439,8 +90,17 @@ static const char *state_str[] = /* Role names */ static const char *role_name[] = { - "Client", - "Server" + "UAC", + "UAS" +}; + +/* Transport flag. */ +enum +{ + TSX_HAS_PENDING_TRANSPORT = 1, + TSX_HAS_PENDING_RESCHED = 2, + TSX_HAS_PENDING_SEND = 4, + TSX_HAS_PENDING_DESTROY = 8, }; /* Transaction lock. */ @@ -468,65 +128,78 @@ enum Transaction_Timer_Id TSX_TIMER_TIMEOUT, }; -/* Function Prototypes */ -static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx, + +/* Prototypes. */ +static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck); +static pj_status_t unlock_tsx( pjsip_transaction *tsx, + struct tsx_lock_data *lck); +static pj_status_t tsx_on_state_null( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx, +static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_trying( pjsip_transaction *tsx, +static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, +static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_proceeding_uac( pjsip_transaction *tsx, +static pj_status_t tsx_on_state_proceeding_uac( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_completed_uas( pjsip_transaction *tsx, +static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx, +static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_confirmed(pjsip_transaction *tsx, +static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_terminated(pjsip_transaction *tsx, +static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx, pjsip_event *event); -static pj_status_t pjsip_tsx_on_state_destroyed(pjsip_transaction *tsx, +static pj_status_t tsx_on_state_destroyed( pjsip_transaction *tsx, pjsip_event *event); +static void tsx_timer_callback( pj_timer_heap_t *theap, + pj_timer_entry *entry); +static pj_status_t tsx_create( pjsip_module *tsx_user, + pjsip_transaction **p_tsx); +static void tsx_destroy( pjsip_transaction *tsx ); +static void tsx_resched_retransmission( pjsip_transaction *tsx ); +static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched); +static int tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void tsx_on_rx_msg( pjsip_transaction *tsx, + pjsip_rx_data *rdata ); -static void tsx_timer_callback( pj_timer_heap_t *theap, - pj_timer_entry *entry); -static int tsx_send_msg( pjsip_transaction *tsx, - pjsip_tx_data *tdata); -static void lock_tsx( pjsip_transaction *tsx, struct - tsx_lock_data *lck ); -static pj_status_t unlock_tsx( pjsip_transaction *tsx, - struct tsx_lock_data *lck ); /* State handlers for UAC, indexed by state */ static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *, pjsip_event *) = { - &pjsip_tsx_on_state_null, - &pjsip_tsx_on_state_calling, - &pjsip_tsx_on_state_trying, - &pjsip_tsx_on_state_proceeding_uac, - &pjsip_tsx_on_state_completed_uac, - &pjsip_tsx_on_state_confirmed, - &pjsip_tsx_on_state_terminated, - &pjsip_tsx_on_state_destroyed, + &tsx_on_state_null, + &tsx_on_state_calling, + NULL, + &tsx_on_state_proceeding_uac, + &tsx_on_state_completed_uac, + &tsx_on_state_confirmed, + &tsx_on_state_terminated, + &tsx_on_state_destroyed, }; /* State handlers for UAS */ static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *, pjsip_event *) = { - &pjsip_tsx_on_state_null, - &pjsip_tsx_on_state_calling, - &pjsip_tsx_on_state_trying, - &pjsip_tsx_on_state_proceeding_uas, - &pjsip_tsx_on_state_completed_uas, - &pjsip_tsx_on_state_confirmed, - &pjsip_tsx_on_state_terminated, - &pjsip_tsx_on_state_destroyed, + &tsx_on_state_null, + NULL, + &tsx_on_state_trying, + &tsx_on_state_proceeding_uas, + &tsx_on_state_completed_uas, + &tsx_on_state_confirmed, + &tsx_on_state_terminated, + &tsx_on_state_destroyed, }; +/***************************************************************************** + ** + ** Utilities + ** + ***************************************************************************** + */ /* * Get transaction state name. */ @@ -544,45 +217,6 @@ PJ_DEF(const char *) pjsip_role_name(pjsip_role_e role) } - -/* - * Unregister the transaction from the hash table, and destroy the resources - * from the transaction. - */ -PJ_DEF(void) pjsip_endpt_destroy_tsx( pjsip_endpoint *endpt, - pjsip_transaction *tsx) -{ - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_destroy_tsx(%s)", tsx->obj_name)); - - pj_assert(tsx->state == PJSIP_TSX_STATE_DESTROYED); - - /* No need to lock transaction. - * This function typically is called from the transaction callback, which - * means that transaction mutex is being held. - */ - pj_assert( pj_mutex_is_locked(tsx->mutex) ); - - /* Lock endpoint. */ - pj_mutex_lock( endpt->tsx_table_mutex ); - - /* Unregister from the hash table. */ - pj_hash_set( NULL, endpt->tsx_table, tsx->transaction_key.ptr, - tsx->transaction_key.slen, NULL); - - /* Unlock endpoint mutex. */ - pj_mutex_unlock( endpt->tsx_table_mutex ); - - /* Destroy transaction mutex. */ - pj_mutex_destroy( tsx->mutex ); - - /* Release the pool for the transaction. */ - pj_pool_release(tsx->pool); - - PJ_LOG(4, (THIS_FILE, "tsx%p destroyed", tsx)); -} - - - /* * Create transaction key for RFC2543 compliant messages, which don't have * unique branch parameter in the top most Via header. @@ -643,15 +277,6 @@ static pj_status_t create_tsx_key_2543( pj_pool_t *pool, *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's'); *p++ = SEPARATOR; - /* Add Request-URI */ - /* This is BUG! - * Response doesn't have Request-URI! - * - len = req_uri->vptr->print( PJSIP_URI_IN_REQ_URI, req_uri, p, end-p ); - p += len; - *p++ = SEPARATOR; - */ - /* Add method, except when method is INVITE or ACK. */ if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) { pj_memcpy(p, method->name.ptr, method->name.slen); @@ -770,68 +395,347 @@ PJ_DEF(pj_status_t) pjsip_tsx_create_key( pj_pool_t *pool, pj_str_t *key, } } +/***************************************************************************** + ** + ** Transaction layer module + ** + ***************************************************************************** /* - * Create new transaction. + * Create transaction layer module and registers it to the endpoint. */ -PJ_DEF(pj_status_t) pjsip_tsx_create( pj_pool_t *pool, - pjsip_endpoint *endpt, - pjsip_transaction **p_tsx) +PJ_DEF(pj_status_t) pjsip_tsx_layer_init(pjsip_endpoint *endpt) { - pjsip_transaction *tsx; + pj_pool_t *pool; pj_status_t status; - tsx = pj_pool_calloc(pool, 1, sizeof(pjsip_transaction)); - tsx->pool = pool; - tsx->endpt = endpt; - tsx->retransmit_timer.id = TSX_TIMER_RETRANSMISSION; - tsx->retransmit_timer._timer_id = -1; - tsx->retransmit_timer.user_data = tsx; - tsx->retransmit_timer.cb = &tsx_timer_callback; - tsx->timeout_timer.id = TSX_TIMER_TIMEOUT; - tsx->timeout_timer._timer_id = -1; - tsx->timeout_timer.user_data = tsx; - tsx->timeout_timer.cb = &tsx_timer_callback; - pj_sprintf(tsx->obj_name, "tsx%p", tsx); - status = pj_mutex_create_recursive(pool, "mtsx%p", &tsx->mutex); + PJ_ASSERT_RETURN(mod_tsx_layer.endpt==NULL, PJ_EINVALIDOP); + + + /* Initialize TLS ID for transaction lock. */ + status = pj_thread_local_alloc(&pjsip_tsx_lock_tls_id); + if (status != PJ_SUCCESS) + return status; + + pj_thread_local_set(pjsip_tsx_lock_tls_id, NULL); + + /* + * Initialize transaction layer structure. + */ + + /* Create pool for the module. */ + pool = pjsip_endpt_create_pool(endpt, "tsxlayer", + PJSIP_POOL_TSX_LAYER_LEN, + PJSIP_POOL_TSX_LAYER_INC ); + if (!pool) + return PJ_ENOMEM; + + + /* Initialize some attributes. */ + mod_tsx_layer.pool = pool; + mod_tsx_layer.endpt = endpt; + + + /* Create hash table. */ + mod_tsx_layer.htable = pj_hash_create( pool, PJSIP_MAX_TSX_COUNT ); + if (!mod_tsx_layer.htable) { + pjsip_endpt_release_pool(endpt, pool); + return PJ_ENOMEM; + } + + /* Create mutex. */ + status = pj_mutex_create_recursive(pool, "tsxlayer", &mod_tsx_layer.mutex); if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool(endpt, pool); + return status; + } + + /* + * Register transaction layer module to endpoint. + */ + status = pjsip_endpt_register_module( endpt, &mod_tsx_layer.mod ); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(mod_tsx_layer.mutex); + pjsip_endpt_release_pool(endpt, pool); return status; } - *p_tsx = tsx; return PJ_SUCCESS; } + /* - * Lock transaction and set the value of Thread Local Storage. + * Get the instance of transaction layer module. */ -static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck) +PJ_DEF(pjsip_module*) pjsip_tsx_layer_instance(void) { - struct tsx_lock_data *prev_data; - - pj_mutex_lock(tsx->mutex); - prev_data = (struct tsx_lock_data *) - pj_thread_local_get(pjsip_tsx_lock_tls_id); - lck->prev = prev_data; - lck->tsx = tsx; - lck->is_alive = 1; - pj_thread_local_set(pjsip_tsx_lock_tls_id, lck); + return &mod_tsx_layer.mod; } /* - * Unlock transaction. - * This will selectively unlock the mutex ONLY IF the transaction has not been - * destroyed. The function knows whether the transaction has been destroyed - * because when transaction is destroyed the is_alive flag for the transaction - * will be set to zero. + * Unregister and destroy transaction layer module. */ -static pj_status_t unlock_tsx( pjsip_transaction *tsx, - struct tsx_lock_data *lck) +PJ_DEF(pj_status_t) pjsip_tsx_layer_destroy(void) { - pj_assert( (void*)pj_thread_local_get(pjsip_tsx_lock_tls_id) == lck); - pj_assert( lck->tsx == tsx ); + /* Are we registered? */ + PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, PJ_EINVALIDOP); + + /* Unregister from endpoint. + * Clean-ups will be done in the unload() module callback. + */ + return pjsip_endpt_unregister_module( mod_tsx_layer.endpt, + &mod_tsx_layer.mod); +} + + +/* + * Register the transaction to the hash table. + */ +static void mod_tsx_layer_register_tsx( pjsip_transaction *tsx) +{ + pj_assert(tsx->transaction_key.slen != 0); + //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL); + + /* Lock hash table mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Register the transaction to the hash table. */ + pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, tsx); + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); +} + + +/* + * Unregister the transaction from the hash table. + */ +static void mod_tsx_layer_unregister_tsx( pjsip_transaction *tsx) +{ + pj_assert(tsx->transaction_key.slen != 0); + //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL); + + /* Lock hash table mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Register the transaction to the hash table. */ + pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, NULL); + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); +} + + +/* + * Find a transaction. + */ +PJ_DEF(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key, + pj_bool_t lock ) +{ + pjsip_transaction *tsx; + + pj_mutex_lock(mod_tsx_layer.mutex); + tsx = pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen ); + pj_mutex_unlock(mod_tsx_layer.mutex); + + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it. + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + if (tsx && lock) + pj_mutex_lock(tsx->mutex); + + return tsx; +} + + +/* This module callback is called when module is being loaded by + * endpoint. It does nothing for this module. + */ +static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt) +{ + PJ_UNUSED_ARG(endpt); + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being started by + * endpoint. It does nothing for this module. + */ +static pj_status_t mod_tsx_layer_start(void) +{ + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being stopped by + * endpoint. + */ +static pj_status_t mod_tsx_layer_stop(void) +{ + pj_hash_iterator_t it_buf, *it; + + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Destroy all transactions. */ + it = pj_hash_first(mod_tsx_layer.htable, &it_buf); + while (it) { + pjsip_transaction *tsx = pj_hash_this(mod_tsx_layer.htable, it); + if (tsx) + tsx_destroy(tsx); + it = pj_hash_next(mod_tsx_layer.htable, it); + } + + pj_mutex_unlock(mod_tsx_layer.mutex); + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being unloaded by + * endpoint. + */ +static pj_status_t mod_tsx_layer_unload(void) +{ + /* Destroy mutex. */ + pj_mutex_destroy(mod_tsx_layer.mutex); + + /* Release pool. */ + pjsip_endpt_release_pool(mod_tsx_layer.endpt, mod_tsx_layer.pool); + + /* Mark as unregistered. */ + mod_tsx_layer.endpt = NULL; + + return PJ_SUCCESS; +} + + +/* This module callback is called when endpoint has received an + * incoming request message. + */ +static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata) +{ + pj_str_t key; + pjsip_transaction *tsx; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS, + &rdata->msg_info.cseq->method, rdata); + + /* Find transaction. */ + pj_mutex_lock( mod_tsx_layer.mutex ); + tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen ); + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Transaction not found. + * Reject the request so that endpoint passes the request to + * upper layer modules. + */ + pj_mutex_unlock( mod_tsx_layer.mutex); + return PJ_FALSE; + } + + /* Unlock hash table. */ + pj_mutex_unlock( mod_tsx_layer.mutex ); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it + * in tsx_on_rx_msg(). + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + + /* Pass the message to the transaction. */ + tsx_on_rx_msg(tsx, rdata ); + + return PJ_TRUE; +} + + +/* This module callback is called when endpoint has received an + * incoming response message. + */ +static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata) +{ + pj_str_t key; + pjsip_transaction *tsx; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, + &rdata->msg_info.cseq->method, rdata); + + /* Find transaction. */ + pj_mutex_lock( mod_tsx_layer.mutex ); + tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen ); + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Transaction not found. + * Reject the request so that endpoint passes the request to + * upper layer modules. + */ + pj_mutex_unlock( mod_tsx_layer.mutex); + return PJ_FALSE; + } + + /* Unlock hash table. */ + pj_mutex_unlock( mod_tsx_layer.mutex ); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it + * in tsx_on_rx_msg(). + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + + /* Pass the message to the transaction. */ + tsx_on_rx_msg(tsx, rdata ); + + return PJ_TRUE; +} + + +/* + * Get transaction instance in the rdata. + */ +PJ_DEF(pjsip_transaction*) pjsip_rdata_get_tsx( pjsip_rx_data *rdata ) +{ + return rdata->endpt_info.mod_data[mod_tsx_layer.mod.id]; +} + + +/***************************************************************************** + ** + ** Transaction + ** + ***************************************************************************** + +/* + * Lock transaction and set the value of Thread Local Storage. + */ +static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck) +{ + struct tsx_lock_data *prev_data; + + pj_mutex_lock(tsx->mutex); + prev_data = (struct tsx_lock_data *) + pj_thread_local_get(pjsip_tsx_lock_tls_id); + lck->prev = prev_data; + lck->tsx = tsx; + lck->is_alive = 1; + pj_thread_local_set(pjsip_tsx_lock_tls_id, lck); +} + + +/* + * Unlock transaction. + * This will selectively unlock the mutex ONLY IF the transaction has not been + * destroyed. The function knows whether the transaction has been destroyed + * because when transaction is destroyed the is_alive flag for the transaction + * will be set to zero. + */ +static pj_status_t unlock_tsx( pjsip_transaction *tsx, + struct tsx_lock_data *lck) +{ + pj_assert( (void*)pj_thread_local_get(pjsip_tsx_lock_tls_id) == lck); + pj_assert( lck->tsx == tsx ); pj_thread_local_set(pjsip_tsx_lock_tls_id, lck->prev); if (lck->is_alive) pj_mutex_unlock(tsx->mutex); @@ -839,6 +743,86 @@ static pj_status_t unlock_tsx( pjsip_transaction *tsx, return lck->is_alive ? PJ_SUCCESS : PJSIP_ETSXDESTROYED; } + +/* Create and initialize basic transaction structure. + * This function is called by both UAC and UAS creation. + */ +static pj_status_t tsx_create( pjsip_module *tsx_user, + pjsip_transaction **p_tsx) +{ + pj_pool_t *pool; + pjsip_transaction *tsx; + pj_status_t status; + + pool = pjsip_endpt_create_pool( mod_tsx_layer.endpt, "tsx", + PJSIP_POOL_TSX_LEN, PJSIP_POOL_TSX_INC ); + if (!pool) + return PJ_ENOMEM; + + tsx = pj_pool_zalloc(pool, sizeof(pjsip_transaction)); + tsx->pool = pool; + tsx->tsx_user = tsx_user; + tsx->endpt = mod_tsx_layer.endpt; + + pj_sprintf(tsx->obj_name, "tsx%p", tsx); + + tsx->handle_200resp = 1; + tsx->retransmit_timer.id = TSX_TIMER_RETRANSMISSION; + tsx->retransmit_timer._timer_id = -1; + tsx->retransmit_timer.user_data = tsx; + tsx->retransmit_timer.cb = &tsx_timer_callback; + tsx->timeout_timer.id = TSX_TIMER_TIMEOUT; + tsx->timeout_timer._timer_id = -1; + tsx->timeout_timer.user_data = tsx; + tsx->timeout_timer.cb = &tsx_timer_callback; + + status = pj_mutex_create_recursive(pool, "tsx%p", &tsx->mutex); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool); + return status; + } + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* Destroy transaction. */ +static void tsx_destroy( pjsip_transaction *tsx ) +{ + pj_mutex_destroy(tsx->mutex); + pjsip_endpt_release_pool(tsx->endpt, tsx->pool); +} + + +/* + * Callback when timer expires. + */ +static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry) +{ + pjsip_event event; + pjsip_transaction *tsx = entry->user_data; + struct tsx_lock_data lck; + + PJ_UNUSED_ARG(theap); + + PJ_LOG(5,(tsx->obj_name, "got timer event (%s timer)", + (entry->id==TSX_TIMER_RETRANSMISSION ? "Retransmit":"Timeout"))); + + + if (entry->id == TSX_TIMER_RETRANSMISSION) { + PJSIP_EVENT_INIT_TIMER(event, &tsx->retransmit_timer); + } else { + PJSIP_EVENT_INIT_TIMER(event, &tsx->timeout_timer); + } + + /* Dispatch event to transaction. */ + lock_tsx(tsx, &lck); + (*tsx->state_handler)(tsx, &event); + unlock_tsx(tsx, &lck); +} + + /* * Set transaction state, and inform TU about the transaction state change. */ @@ -847,8 +831,6 @@ static void tsx_set_state( pjsip_transaction *tsx, pjsip_event_id_e event_src_type, void *event_src ) { - pjsip_event e; - PJ_LOG(4, (tsx->obj_name, "STATE %s-->%s, cause = %s", state_str[tsx->state], state_str[state], pjsip_event_str(event_src_type))); @@ -864,18 +846,21 @@ static void tsx_set_state( pjsip_transaction *tsx, } /* Inform TU */ - PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src); - pjsip_endpt_send_tsx_event( tsx->endpt, &e ); + if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) { + pjsip_event e; + PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src); + (*tsx->tsx_user->on_tsx_state)(tsx, &e); + } + /* When the transaction is terminated, release transport, and free the * saved last transmitted message. */ if (state == PJSIP_TSX_STATE_TERMINATED) { + pj_time_val timeout = {0, 0}; /* Decrement transport reference counter. */ - if (tsx->transport && - tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) - { + if (tsx->transport) { pjsip_transport_dec_ref( tsx->transport ); tsx->transport = NULL; } @@ -895,15 +880,15 @@ static void tsx_set_state( pjsip_transaction *tsx, tsx->retransmit_timer._timer_id = -1; } - /* If transport is not pending, reschedule timeout timer to - * destroy this transaction. - */ - if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) { - pj_time_val timeout = {0, 0}; + /* Reschedule timeout timer to destroy this transaction. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_DESTROY; + } else { pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout); } + } else if (state == PJSIP_TSX_STATE_DESTROYED) { /* Clear TLS, so that mutex will not be unlocked */ @@ -914,188 +899,61 @@ static void tsx_set_state( pjsip_transaction *tsx, } lck = lck->prev; } - } -} -/* - * Look-up destination address and select which transport to be used to send - * the request message. The procedure used here follows the guidelines on - * sending the request in RFC3261 chapter 8.1.2. - * - * This function also modifies the message (request line and Route headers) - * accordingly. - */ -static pj_status_t tsx_process_route( pjsip_transaction *tsx, - pjsip_tx_data *tdata, - pjsip_host_info *send_addr ) -{ - pjsip_route_hdr *route_hdr; - - pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG); + /* Unregister transaction. */ + mod_tsx_layer_unregister_tsx(tsx); - /* Get the first "Route" header from the message. If the message doesn't - * have any "Route" headers but the endpoint has, then copy the "Route" - * headers from the endpoint first. - */ - route_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); - if (!route_hdr) { - const pjsip_route_hdr *hdr_list; - const pjsip_route_hdr *hdr; - hdr_list = (const pjsip_route_hdr*)pjsip_endpt_get_routing(tsx->endpt); - hdr = hdr_list->next; - while (hdr != hdr_list { - route_hdr = pjsip_hdr_shallow_clone(tdata->pool, hdr); - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route_hdr); - hdr = hdr->next; - } + /* Destroy transaction. */ + tsx_destroy(tsx); } - - return pjsip_get_request_addr(tdata, send_addr); } /* - * Callback from the transport job. - * This callback is called when asychronous transport connect() operation - * has completed, with or without error. + * Create, initialize, and register UAC transaction. */ -static void tsx_transport_callback(pjsip_transport *tr, - void *token, - pj_status_t status) +PJ_DEF(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user, + pjsip_tx_data *tdata, + pjsip_transaction **p_tsx) { - char addr[PJ_MAX_HOSTNAME]; - pjsip_transaction *tsx = token; + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_cseq_hdr *cseq; + pjsip_via_hdr *via; struct tsx_lock_data lck; + pj_status_t status; - pj_memcpy(addr, tsx->dest_name.addr.host.ptr, tsx->dest_name.addr.host.slen); - addr[tsx->dest_name.addr.host.slen] = '\0'; - - - if (status == PJ_SUCCESS) { - PJ_LOG(4, (tsx->obj_name, "%s connected to %s:%d", - tr->type_name, - addr, tsx->dest_name.addr.port)); - } else { - PJ_LOG(4, (tsx->obj_name, "%s unable to connect to %s:%d, status=%d", - tr->type_name, - addr, tsx->dest_name.addr.port, status)); - } - - /* Lock transaction. */ - lock_tsx(tsx, &lck); - - if (status != PJ_SUCCESS) { - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL; - tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; - - tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_TRANSPORT_ERROR, (void*)status); - - /* Unlock transaction. */ - unlock_tsx(tsx, &lck); - return; - } - - /* See if transaction has already been terminated. - * If so, schedule to destroy the transaction. - */ - if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { - pj_time_val timeout = {0, 0}; - pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, - &timeout); - - /* Unlock transaction. */ - unlock_tsx(tsx, &lck); - return; - } - - /* Add reference counter to the transport. */ - pjsip_transport_add_ref(tr); - - /* Mark transport as ready. */ - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL; - tsx->transport = tr; - - /* If there's a pending message to send, send it now. */ - if (tsx->has_unsent_msg) { - tsx_send_msg( tsx, tsx->last_tx ); - } - - /* Unlock transaction. */ - unlock_tsx(tsx, &lck); -} - -/* - * Callback from the resolver job. - */ -static void tsx_resolver_callback(pj_status_t status, - void *token, - const struct pjsip_server_addresses *addr) -{ - pjsip_transaction *tsx = token; - struct tsx_lock_data lck; - pjsip_transport *tp; + PJ_ASSERT_RETURN(tdata!=NULL && p_tsx!=NULL, PJ_EINVAL); - PJ_LOG(4, (tsx->obj_name, "resolver job complete, status=%d", status)); + /* Keep shortcut */ + msg = tdata->msg; - if (status != PJ_SUCCESS || addr->count == 0) { - lock_tsx(tsx, &lck); - tsx->status_code = PJSIP_SC_TSX_RESOLVE_ERROR; - tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_TRANSPORT_ERROR, (void*)status); - unlock_tsx(tsx, &lck); - return; + /* Make sure CSeq header is present. */ + cseq = pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL); + if (!cseq) { + pj_assert(!"CSeq header not present in outgoing message!"); + return PJSIP_EMISSINGHDR; } - /* Lock transaction. */ - lock_tsx(tsx, &lck); - - /* Copy server addresses. */ - pj_memcpy(&tsx->remote_addr, addr, sizeof(*addr)); - - /* Create/find the transport for the remote address. */ - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_CONNECTING; - status = pjsip_endpt_alloc_transport( tsx->endpt, addr->entry[0].type, - &addr->entry[0].addr, - addr->entry[0].addr_len, - &tp); - tsx_transport_callback(tp, tsx, status); - - /* Unlock transaction */ - unlock_tsx(tsx, &lck); - - /* There should be nothing to do after this point. - * Execution for the transaction will resume when the callback for the - * transport is called. - */ -} -/* - * Initialize the transaction as UAC transaction. - */ -PJ_DEF(pj_status_t) pjsip_tsx_init_uac( pjsip_transaction *tsx, - pjsip_tx_data *tdata) -{ - pjsip_msg *msg; - pjsip_cseq_hdr *cseq; - pjsip_via_hdr *via; - pj_status_t status; - struct tsx_lock_data lck; + /* Create transaction instance. */ + status = tsx_create( tsx_user, &tsx); + if (status != PJ_SUCCESS) + return status; - PJ_LOG(4,(tsx->obj_name, "initializing tsx as UAC (tdata=%p)", tdata)); /* Lock transaction. */ lock_tsx(tsx, &lck); - /* Keep shortcut */ - msg = tdata->msg; - /* Role is UAC. */ tsx->role = PJSIP_ROLE_UAC; /* Save method. */ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method); + /* Save CSeq. */ + tsx->cseq = cseq->cseq; + /* Generate Via header if it doesn't exist. */ via = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL); if (via == NULL) { @@ -1103,6 +961,7 @@ PJ_DEF(pj_status_t) pjsip_tsx_init_uac( pjsip_transaction *tsx, pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*) via); } + /* Generate branch parameter if it doesn't exist. */ if (via->branch_param.slen == 0) { pj_str_t tmp; via->branch_param.ptr = pj_pool_alloc(tsx->pool, PJSIP_MAX_BRANCH_LEN); @@ -1115,96 +974,85 @@ PJ_DEF(pj_status_t) pjsip_tsx_init_uac( pjsip_transaction *tsx, /* Save branch parameter. */ tsx->branch = via->branch_param; + } else { /* Copy branch parameter. */ pj_strdup(tsx->pool, &tsx->branch, &via->branch_param); } - - /* Generate transaction key. */ - status = create_tsx_key_3261( tsx->pool, &tsx->transaction_key, - PJSIP_ROLE_UAC, &tsx->method, - &via->branch_param); - if (status != PJ_SUCCESS) { - unlock_tsx(tsx, &lck); - return status; - } + /* Generate transaction key. */ + create_tsx_key_3261( tsx->pool, &tsx->transaction_key, + PJSIP_ROLE_UAC, &tsx->method, + &via->branch_param); PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, tsx->transaction_key.ptr)); - /* Save CSeq. */ - cseq = pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL); - if (!cseq) { - pj_assert(!"CSeq header not present in outgoing message!"); - unlock_tsx(tsx, &lck); - return PJSIP_EMISSINGHDR; - } - tsx->cseq = cseq->cseq; - - /* Begin with State_Null. * Manually set-up the state becase we don't want to call the callback. */ tsx->state = PJSIP_TSX_STATE_NULL; - tsx->state_handler = &pjsip_tsx_on_state_null; + tsx->state_handler = &tsx_on_state_null; - /* Get destination name from the message. */ - status = tsx_process_route(tsx, tdata, &tsx->dest_name); - if (status != PJ_SUCCESS) { - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL; - tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; - tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_TRANSPORT_ERROR, (void*)status); - unlock_tsx(tsx, &lck); - return status; - } + /* Save the message. */ + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tsx->last_tx); - /* Resolve destination. - * This will start asynchronous resolver job, and when it finishes, - * the callback will be called. - */ - PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d", - tsx->dest_name.addr.host.slen, - tsx->dest_name.addr.host.ptr, - tsx->dest_name.addr.port)); - - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING; - pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name, - tsx, &tsx_resolver_callback); - - /* There should be nothing to do after this point. - * Execution for the transaction will resume when the resolver callback is - * called. - */ - /* Unlock transaction and return. - * If transaction has been destroyed WITHIN the current thread, the - * unlock_tsx() function will return -1. - */ - return unlock_tsx(tsx, &lck); + /* Register transaction to hash table. */ + mod_tsx_layer_register_tsx(tsx); + + + /* Unlock transaction and return. */ + unlock_tsx(tsx, &lck); + + *p_tsx = tsx; + return PJ_SUCCESS; } /* - * Initialize the transaction as UAS transaction. + * Create, initialize, and register UAS transaction. */ -PJ_DEF(pj_status_t) pjsip_tsx_init_uas( pjsip_transaction *tsx, - pjsip_rx_data *rdata) +PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user, + pjsip_rx_data *rdata, + pjsip_transaction **p_tsx) { - pjsip_msg *msg = rdata->msg_info.msg; + pjsip_transaction *tsx; + pjsip_msg *msg; pj_str_t *branch; pjsip_cseq_hdr *cseq; pj_status_t status; struct tsx_lock_data lck; - PJ_LOG(4,(tsx->obj_name, "initializing tsx as UAS (rdata=%p)", rdata)); - - /* Lock transaction. */ - lock_tsx(tsx, &lck); + PJ_ASSERT_RETURN(rdata!=NULL && p_tsx!=NULL, PJ_EINVAL); /* Keep shortcut to message */ msg = rdata->msg_info.msg; + + /* Make sure this is a request message. */ + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); + + /* Make sure CSeq header is present. */ + cseq = rdata->msg_info.cseq; + if (!cseq) + return PJSIP_EMISSINGHDR; + + /* Make sure Via header is present. */ + if (rdata->msg_info.via == NULL) + return PJSIP_EMISSINGHDR; + + + /* + * Create transaction instance. + */ + status = tsx_create( tsx_user, &tsx); + if (status != PJ_SUCCESS) + return status; + + + /* Lock transaction. */ + lock_tsx(tsx, &lck); /* Role is UAS */ tsx->role = PJSIP_ROLE_UAS; @@ -1212,13 +1060,16 @@ PJ_DEF(pj_status_t) pjsip_tsx_init_uas( pjsip_transaction *tsx, /* Save method. */ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method); + /* Save CSeq */ + tsx->cseq = cseq->cseq; + /* Get transaction key either from branch for RFC3261 message, or * create transaction key. */ status = pjsip_tsx_create_key(tsx->pool, &tsx->transaction_key, PJSIP_ROLE_UAS, &tsx->method, rdata); if (status != PJ_SUCCESS) { - unlock_tsx(tsx, &lck); + tsx_destroy(tsx); return status; } @@ -1229,223 +1080,77 @@ PJ_DEF(pj_status_t) pjsip_tsx_init_uas( pjsip_transaction *tsx, PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, tsx->transaction_key.ptr)); - /* Save CSeq */ - cseq = rdata->msg_info.cseq; - tsx->cseq = cseq->cseq; - /* Begin with state NULL + /* Begin with state TRYING. * Manually set-up the state becase we don't want to call the callback. */ - tsx->state = PJSIP_TSX_STATE_NULL; - tsx->state_handler = &pjsip_tsx_on_state_null; - - /* Get the transport to send the response. - * According to section 18.2.2 of RFC3261, if the transport is reliable - * then the response must be sent using that transport. - */ - /* In addition, RFC 3581 says, if Via has "rport" parameter specified, - * then return the response using the same transport. - */ - if (PJSIP_TRANSPORT_IS_RELIABLE(rdata->tp_info.transport) || - rdata->msg_info.via->rport_param >= 0) - { - tsx->transport = rdata->tp_info.transport; - pjsip_transport_add_ref(tsx->transport); - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL; - - tsx->current_addr = 0; - tsx->remote_addr.count = 1; - tsx->remote_addr.entry[0].type = tsx->transport->key.type; - pj_memcpy(&tsx->remote_addr.entry[0].addr, - &rdata->pkt_info.src_addr, rdata->pkt_info.src_addr_len); - - } else { - pj_status_t status; - - status = pjsip_get_response_addr(tsx->pool, rdata->tp_info.transport, - rdata->msg_info.via, &tsx->dest_name); - if (status != PJ_SUCCESS) { - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL; - tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; - tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_TRANSPORT_ERROR, (void*)status); - unlock_tsx(tsx, &lck); - return status; - } + tsx->state = PJSIP_TSX_STATE_TRYING; + tsx->state_handler = &tsx_on_state_trying; - /* Resolve destination. - * This will start asynchronous resolver job, and when it finishes, - * the callback will be called. - */ - PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d", - tsx->dest_name.addr.host.slen, - tsx->dest_name.addr.host.ptr, - tsx->dest_name.addr.port)); - - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING; - pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name, - tsx, &tsx_resolver_callback); + /* Get response address. */ + status = pjsip_get_response_addr( tsx->pool, rdata, &tsx->res_addr ); + if (status != PJ_SUCCESS) { + tsx_destroy(tsx); + return status; } - - /* There should be nothing to do after this point. - * Execution for the transaction will resume when the resolver callback is - * called. - */ - /* Unlock transaction and return. - * If transaction has been destroyed WITHIN the current thread, the - * unlock_tsx() function will return -1. + /* If it's decided that we should use current transport, keep the + * transport. */ - return unlock_tsx(tsx, &lck); -} - -/* - * Callback when timer expires. - */ -static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry) -{ - pjsip_event event; - pjsip_transaction *tsx = entry->user_data; - struct tsx_lock_data lck; - - PJ_UNUSED_ARG(theap); + if (tsx->res_addr.transport) { + tsx->transport = tsx->res_addr.transport; + pjsip_transport_add_ref(tsx->transport); + pj_memcpy(&tsx->addr, &tsx->res_addr.addr, tsx->res_addr.addr_len); + tsx->addr_len = tsx->res_addr.addr_len; + } - PJ_LOG(5,(tsx->obj_name, "got timer event (%s timer)", - (entry->id==TSX_TIMER_RETRANSMISSION ? "Retransmit" : "Timeout"))); + /* Register the transaction. */ + mod_tsx_layer_register_tsx(tsx); - if (entry->id == TSX_TIMER_RETRANSMISSION) { - PJSIP_EVENT_INIT_TIMER(event, &tsx->retransmit_timer); - } else { - PJSIP_EVENT_INIT_TIMER(event, &tsx->timeout_timer); - } - /* Dispatch event to transaction. */ - lock_tsx(tsx, &lck); - (*tsx->state_handler)(tsx, &event); + /* Unlock transaction and return. */ unlock_tsx(tsx, &lck); + + *p_tsx = tsx; + return PJ_SUCCESS; } + /* - * Transmit ACK message for 2xx/INVITE with this transaction. The ACK for - * non-2xx/INVITE is automatically sent by the transaction. - * This operation is only valid if the transaction is configured to handle ACK - * (tsx->handle_ack is non-zero). If this attribute is not set, then the - * transaction will comply with RFC-3261, i.e. it will set itself to - * TERMINATED state when it receives 2xx/INVITE. + * Forcely terminate transaction. */ -PJ_DEF(void) pjsip_tsx_on_tx_ack( pjsip_transaction *tsx, pjsip_tx_data *tdata) +PJ_DEF(pj_status_t) pjsip_tsx_terminate( pjsip_transaction *tsx, int code ) { - pjsip_msg *msg; - pjsip_host_info dest_addr; - pjsip_via_hdr *via; struct tsx_lock_data lck; - pj_status_t status = PJ_SUCCESS; - - /* Lock tsx. */ - lock_tsx(tsx, &lck); - - pj_assert(tsx->handle_ack != 0); - - msg = tdata->msg; - - /* Generate branch parameter if it doesn't exist. */ - via = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL); - if (via == NULL) { - via = pjsip_via_hdr_create(tdata->pool); - pjsip_msg_add_hdr(msg, (pjsip_hdr*) via); - } - - if (via->branch_param.slen == 0) { - via->branch_param = tsx->branch; - } else { - pj_assert( pj_strcmp(&via->branch_param, &tsx->branch) == 0 ); - } - - /* Get destination name from the message. */ - status = tsx_process_route(tsx, tdata, &dest_addr); - if (status != 0){ - goto on_error; - } - - /* Compare message's destination name with transaction's destination name. - * If NOT equal, then we'll have to resolve the destination. - */ - if (dest_addr.type == tsx->dest_name.type && - dest_addr.flag == tsx->dest_name.flag && - dest_addr.addr.port == tsx->dest_name.addr.port && - pj_stricmp(&dest_addr.addr.host, &tsx->dest_name.addr.host) == 0) - { - /* Equal destination. We can use current transport. */ - pjsip_tsx_on_tx_msg(tsx, tdata); - unlock_tsx(tsx, &lck); - return; - - } - - /* New destination; we'll have to resolve host and create new transport. */ - pj_memcpy(&tsx->dest_name, &dest_addr, sizeof(dest_addr)); - pj_strdup(tsx->pool, &tsx->dest_name.addr.host, &dest_addr.addr.host); - - PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d", - tsx->dest_name.addr.host.slen, - tsx->dest_name.addr.host.ptr, - tsx->dest_name.addr.port)); - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING; - pjsip_transport_dec_ref(tsx->transport); - tsx->transport = NULL; - - /* Put the message in queue. */ - pjsip_tsx_on_tx_msg(tsx, tdata); - - /* This is a bug! - * We shouldn't change transaction's state before actually sending the - * message. Otherwise transaction will terminate before message is sent, - * and timeout timer will be scheduled. - */ - PJ_TODO(TSX_DONT_CHANGE_STATE_BEFORE_SENDING_ACK) - - /* - * This will start asynchronous resolver job, and when it finishes, - * the callback will be called. - */ - - tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING; - pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name, - tsx, &tsx_resolver_callback); + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(code >= 200, PJ_EINVAL); + lock_tsx(tsx, &lck); + tsx->status_code = code; + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_USER, NULL); unlock_tsx(tsx, &lck); - /* There should be nothing to do after this point. - * Execution for the transaction will resume when the resolver callback is - * called. - */ - return; - -on_error: - /* Failure condition. - * Send TERMINATED event. - */ - tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; - - tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_TRANSPORT_ERROR, (void*)status); - - unlock_tsx(tsx, &lck); + return PJ_SUCCESS; } /* * This function is called by TU to send a message. */ -PJ_DEF(void) pjsip_tsx_on_tx_msg( pjsip_transaction *tsx, - pjsip_tx_data *tdata ) +PJ_DEF(pj_status_t) pjsip_tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata ) { pjsip_event event; struct tsx_lock_data lck; pj_status_t status; + if (tdata == NULL) + tdata = tsx->last_tx; + + PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP); + PJ_LOG(5,(tsx->obj_name, "Request to transmit msg on state %s (tdata=%p)", state_str[tsx->state], tdata)); @@ -1455,14 +1160,16 @@ PJ_DEF(void) pjsip_tsx_on_tx_msg( pjsip_transaction *tsx, lock_tsx(tsx, &lck); status = (*tsx->state_handler)(tsx, &event); unlock_tsx(tsx, &lck); + + return status; } + /* * This function is called by endpoint when incoming message for the * transaction is received. */ -PJ_DEF(void) pjsip_tsx_on_rx_msg( pjsip_transaction *tsx, - pjsip_rx_data *rdata) +static void tsx_on_rx_msg( pjsip_transaction *tsx, pjsip_rx_data *rdata) { pjsip_event event; struct tsx_lock_data lck; @@ -1471,6 +1178,10 @@ PJ_DEF(void) pjsip_tsx_on_rx_msg( pjsip_transaction *tsx, PJ_LOG(5,(tsx->obj_name, "Incoming msg on state %s (rdata=%p)", state_str[tsx->state], rdata)); + /* Put the transaction in the rdata's mod_data. */ + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx; + + /* Init event. */ PJSIP_EVENT_INIT_RX_MSG(event, tsx, rdata); /* Dispatch to transaction. */ @@ -1479,162 +1190,282 @@ PJ_DEF(void) pjsip_tsx_on_rx_msg( pjsip_transaction *tsx, unlock_tsx(tsx, &lck); } -/* - * Forcely terminate transaction. - */ -PJ_DEF(void) pjsip_tsx_terminate( pjsip_transaction *tsx, int code ) + +/* Callback called by send message framework */ +static void send_msg_callback( pjsip_send_state *send_state, + pj_ssize_t sent, pj_bool_t *cont ) { + pjsip_transaction *tsx = send_state->token; struct tsx_lock_data lck; lock_tsx(tsx, &lck); - tsx->status_code = code; - tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_USER, NULL); + + if (sent > 0) { + /* Successfully sent! */ + pj_assert(send_state->cur_transport != NULL); + + if (tsx->transport != send_state->cur_transport) { + if (tsx->transport) { + pjsip_transport_dec_ref(tsx->transport); + tsx->transport = NULL; + } + tsx->transport = send_state->cur_transport; + pjsip_transport_add_ref(tsx->transport); + } + + /* Clear pending transport flag. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + + /* Pending destroy? */ + if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) { + tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED, + PJSIP_EVENT_UNKNOWN, NULL ); + unlock_tsx(tsx, &lck); + return; + } + + /* Need to transmit a message? */ + if (tsx->transport_flag & TSX_HAS_PENDING_SEND) { + tsx->transport_flag &= ~(TSX_HAS_PENDING_SEND); + tsx_send_msg(tsx, tsx->last_tx); + } + + /* Need to reschedule retransmission? */ + if (tsx->transport_flag & TSX_HAS_PENDING_RESCHED) { + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + tsx_resched_retransmission(tsx); + } + + } else { + /* Failed to send! */ + pj_assert(sent != 0); + + /* If transaction is using the same transport as the failed one, + * release the transport. + */ + if (send_state->cur_transport==tsx->transport && + tsx->transport != NULL) + { + pjsip_transport_dec_ref(tsx->transport); + tsx->transport = NULL; + } + + if (!*cont) { + PJ_LOG(4,(tsx->obj_name, "Failed to send message! status=%d", + -sent)); + + /* Clear pending transport flag. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + + /* Terminate transaction. */ + tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata ); + } + } unlock_tsx(tsx, &lck); } -/* - * Transport send completion callback. - */ -static void tsx_on_send_complete(void *token, pjsip_tx_data *tdata, - pj_ssize_t bytes_sent) + +/* Transport callback. */ +static void transport_callback(void *token, pjsip_tx_data *tdata, + pj_ssize_t sent) { - PJ_UNUSED_ARG(token); - PJ_UNUSED_ARG(tdata); + if (sent < 0) { + pjsip_transaction *tsx = token; + struct tsx_lock_data lck; - if (bytes_sent <= 0) { - PJ_TODO(HANDLE_TRANSPORT_ERROR); - } + PJ_LOG(4,(tsx->obj_name, "Failed to send message! status=%d", + -sent)); + + lock_tsx(tsx, &lck); + + /* Dereference transport. */ + pjsip_transport_dec_ref(tsx->transport); + tsx->transport = NULL; + + /* Terminate transaction. */ + tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, tdata ); + + unlock_tsx(tsx, &lck); + } } /* * Send message to the transport. - * If transport is not yet available, then do nothing. The message will be - * transmitted when transport connection completion callback is called. */ static pj_status_t tsx_send_msg( pjsip_transaction *tsx, pjsip_tx_data *tdata) { - pj_status_t status = PJ_SUCCESS; + pj_status_t status = PJ_EBUG; - PJ_LOG(5,(tsx->obj_name, "sending msg (tdata=%p)", tdata)); + PJ_ASSERT_RETURN(tsx && tdata, PJ_EINVAL); - if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) { - pjsip_event before_tx_event; - - pj_assert(tsx->transport != NULL); + /* Send later if transport is still pending. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_SEND; + return PJ_SUCCESS; + } - /* Make sure Via transport info is filled up properly for - * requests. + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + /* If we have the transport, send the message using that transport. + * Otherwise perform full transport resolution. */ - if (tdata->msg->type == PJSIP_REQUEST_MSG) { - pjsip_via_hdr *via = (pjsip_via_hdr*) - pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); - - /* For request message, set "rport" parameter by default. */ - if (tdata->msg->type == PJSIP_REQUEST_MSG) - via->rport_param = 0; - - /* Don't update Via sent-by on retransmission. */ - if (via->sent_by.host.slen == 0) { - pj_strdup2(tdata->pool, &via->transport, - tsx->transport->type_name); - pj_strdup(tdata->pool, &via->sent_by.host, - &tsx->transport->local_name.host); - via->sent_by.port = tsx->transport->local_name.port; + if (tsx->transport) { + status = pjsip_transport_send( tsx->transport, tdata, &tsx->addr, + tsx->addr_len, tsx, + &transport_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + if (status != PJ_SUCCESS) { + /* On error, release transport to force using full transport + * resolution procedure. + */ + if (tsx->transport) { + pjsip_transport_dec_ref(tsx->transport); + tsx->transport = NULL; + } + tsx->addr_len = 0; } } - - /* Notify everybody we're about to send message. */ - PJSIP_EVENT_INIT_PRE_TX_MSG(before_tx_event, tsx, tdata, - tsx->retransmit_count); - pjsip_endpt_send_tsx_event( tsx->endpt, &before_tx_event ); - - tsx->has_unsent_msg = 0; - status = pjsip_transport_send(tsx->transport, tdata, - &tsx->remote_addr.entry[tsx->current_addr].addr, - tsx->remote_addr.entry[tsx->current_addr].addr_len, - tsx, &tsx_on_send_complete); - if (status != PJ_SUCCESS && status != PJ_EPENDING) { - PJ_TODO(HANDLE_TRANSPORT_ERROR); - goto on_error; + + if (!tsx->transport) { + tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT; + status = pjsip_endpt_send_request_stateless(tsx->endpt, tdata, tsx, + &send_msg_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; } + } else { - tsx->has_unsent_msg = 1; - } + /* If we have the transport, send the message using that transport. + * Otherwise perform full transport resolution. + */ + if (tsx->transport) { + status = pjsip_transport_send( tsx->transport, tdata, + &tsx->addr, tsx->addr_len, + tsx, &transport_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + if (status != PJ_SUCCESS) { + if (tsx->transport) { + pjsip_transport_dec_ref(tsx->transport); + tsx->transport = NULL; + } + tsx->addr_len = 0; + tsx->res_addr.transport = NULL; + tsx->res_addr.addr_len = 0; + } - return 0; + } + + if (!tsx->transport) { + tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT; + status = pjsip_endpt_send_response( tsx->endpt, &tsx->res_addr, + tdata, tsx, + &send_msg_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + } + } -on_error: - tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR; - tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, - PJSIP_EVENT_TRANSPORT_ERROR, (void*)status); return status; } + +/* + * Retransmit last message sent. + */ +static void tsx_resched_retransmission( pjsip_transaction *tsx ) +{ + pj_time_val timeout; + int msec_time; + + pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0); + + msec_time = (1 << (tsx->retransmit_count)) * PJSIP_T1_TIMEOUT; + + if (msec_time>PJSIP_T2_TIMEOUT && tsx->method.id!=PJSIP_INVITE_METHOD) + msec_time = PJSIP_T2_TIMEOUT; + + timeout.sec = msec_time / 1000; + timeout.msec = msec_time % 1000; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer, + &timeout); +} + /* * Retransmit last message sent. */ -static pj_status_t pjsip_tsx_retransmit( pjsip_transaction *tsx, - int should_restart_timer) +static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched) { pj_status_t status; - PJ_LOG(4,(tsx->obj_name, "retransmiting (tdata=%p, count=%d, restart?=%d)", - tsx->last_tx, tsx->retransmit_count, should_restart_timer)); + PJ_ASSERT_RETURN(tsx->last_tx!=NULL, PJ_EBUG); - pj_assert(tsx->last_tx != NULL); + PJ_LOG(4,(tsx->obj_name, "retransmiting (tdata=%p, count=%d, restart?=%d)", + tsx->last_tx, tsx->retransmit_count, resched)); ++tsx->retransmit_count; status = tsx_send_msg( tsx, tsx->last_tx); - if (status != PJ_SUCCESS) { + if (status != PJ_SUCCESS) return status; - } /* Restart timer T1. */ - if (should_restart_timer) { - pj_time_val timeout; - int msec_time = (1 << (tsx->retransmit_count)) * PJSIP_T1_TIMEOUT; - - if (tsx->method.id!=PJSIP_INVITE_METHOD && msec_time>PJSIP_T2_TIMEOUT) - msec_time = PJSIP_T2_TIMEOUT; - - timeout.sec = msec_time / 1000; - timeout.msec = msec_time % 1000; - pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer, - &timeout); + if (resched) { + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx_resched_retransmission(tsx); + } } return PJ_SUCCESS; } + /* * Handler for events in state Null. */ -static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx, - pjsip_event *event ) +static pj_status_t tsx_on_state_null( pjsip_transaction *tsx, + pjsip_event *event ) { pj_status_t status; - pj_assert( tsx->state == PJSIP_TSX_STATE_NULL); - pj_assert( tsx->last_tx == NULL ); - pj_assert( tsx->has_unsent_msg == 0); + pj_assert(tsx->state == PJSIP_TSX_STATE_NULL); if (tsx->role == PJSIP_ROLE_UAS) { - /* Set state to Trying. */ - pj_assert(event->type == PJSIP_EVENT_RX_MSG); - tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING, - PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + /* UAS doesn't have STATE_NULL. + * State has moved from NULL after transaction is initialized. + */ + pj_assert(!"Bug bug bug!!"); + return PJ_EBUG; } else { - pjsip_tx_data *tdata = event->body.tx_msg.tdata; + pjsip_tx_data *tdata; + + /* Must be transmit event. */ + PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG, PJ_EBUG); + + /* Get the txdata */ + tdata = event->body.tx_msg.tdata; /* Save the message for retransmission. */ - tsx->last_tx = tdata; - pjsip_tx_data_add_ref(tdata); + if (tsx->last_tx && tsx->last_tx != tdata) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = NULL; + } + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tdata); + } /* Send the message. */ status = tsx_send_msg( tsx, tdata); @@ -1651,12 +1482,14 @@ static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx, /* Start Timer A (or timer E) for retransmission only if unreliable * transport is being used. */ - if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL && - PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) - { - pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer, - &t1_timer_val); + if (!PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)) { tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer, + &t1_timer_val); + } } /* Move state. */ @@ -1667,12 +1500,13 @@ static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx, return PJ_SUCCESS; } + /* * State Calling is for UAC after it sends request but before any responses * is received. */ -static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx, - pjsip_event *event ) +static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx, + pjsip_event *event ) { pj_assert(tsx->state == PJSIP_TSX_STATE_CALLING); pj_assert(tsx->role == PJSIP_ROLE_UAC); @@ -1683,7 +1517,7 @@ static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx, pj_status_t status; /* Retransmit the request. */ - status = pjsip_tsx_retransmit( tsx, 1 ); + status = tsx_retransmit( tsx, 1 ); if (status != PJ_SUCCESS) { return status; } @@ -1705,10 +1539,18 @@ static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx, PJSIP_EVENT_TIMER, &tsx->timeout_timer); /* Transaction is destroyed */ - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } else if (event->type == PJSIP_EVENT_RX_MSG) { - int code; + pjsip_msg *msg; + //int code; + + /* Get message instance */ + msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Better be a response message. */ + if (msg->type != PJSIP_RESPONSE_MSG) + return PJSIP_ENOTRESPONSEMSG; /* Cancel retransmission timer A. */ if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) @@ -1722,31 +1564,33 @@ static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx, * the final response. */ /* Keep last_tx for authorization. */ - code = event->body.rx_msg.rdata->msg_info.msg->line.status.code; - if (tsx->method.id != PJSIP_INVITE_METHOD && code!=401 && code!=407) { - pjsip_tx_data_dec_ref(tsx->last_tx); - tsx->last_tx = NULL; - } + //blp: always keep last_tx until transaction is destroyed + //code = msg->line.status.code; + //if (tsx->method.id != PJSIP_INVITE_METHOD && code!=401 && code!=407) { + // pjsip_tx_data_dec_ref(tsx->last_tx); + // tsx->last_tx = NULL; + //} /* Processing is similar to state Proceeding. */ - pjsip_tsx_on_state_proceeding_uac( tsx, event); + tsx_on_state_proceeding_uac( tsx, event); } else { - pj_assert(0); + pj_assert(!"Unexpected event"); return PJ_EBUG; } return PJ_SUCCESS; } + /* * State Trying is for UAS after it received request but before any responses * is sent. * Note: this is different than RFC3261, which can use Trying state for * non-INVITE client transaction (bug in RFC?). */ -static pj_status_t pjsip_tsx_on_state_trying( pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx, + pjsip_event *event) { pj_status_t status; @@ -1761,7 +1605,6 @@ static pj_status_t pjsip_tsx_on_state_trying( pjsip_transaction *tsx, * this happens, just ignore the event (we couldn't retransmit last * response because we haven't sent any!). */ - //pj_assert(event->type == PJSIP_EVENT_TX_MSG); if (event->type != PJSIP_EVENT_TX_MSG) { return PJ_SUCCESS; } @@ -1769,23 +1612,26 @@ static pj_status_t pjsip_tsx_on_state_trying( pjsip_transaction *tsx, /* The rest of the processing of the event is exactly the same as in * "Proceeding" state. */ - status = pjsip_tsx_on_state_proceeding_uas( tsx, event); + status = tsx_on_state_proceeding_uas( tsx, event); /* Inform the TU of the state transision if state is still State_Trying */ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TRYING) { + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata); + } return status; } + /* * Handler for events in Proceeding for UAS * This state happens after the TU sends provisional response. */ -static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, + pjsip_event *event) { pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING || tsx->state == PJSIP_TSX_STATE_TRYING); @@ -1798,10 +1644,16 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, pj_status_t status; - /* Send last response. */ - status = pjsip_tsx_retransmit( tsx, 0 ); - if (status != PJ_SUCCESS) { - return status; + /* Must have last response sent. */ + PJ_ASSERT_RETURN(tsx->last_tx != NULL, PJ_EBUG); + + /* Send last response */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_SEND; + } else { + status = tsx_send_msg(tsx, tsx->last_tx); + if (status != PJ_SUCCESS) + return status; } } else if (event->type == PJSIP_EVENT_TX_MSG ) { @@ -1815,10 +1667,7 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, pjsip_msg *msg = tdata->msg; /* This can only be a response message. */ - pj_assert(msg->type == PJSIP_RESPONSE_MSG); - - /* Status code must be higher than last sent. */ - pj_assert(msg->line.status.code >= tsx->status_code); + PJ_ASSERT_RETURN(msg->type==PJSIP_RESPONSE_MSG, PJSIP_ENOTRESPONSEMSG); /* Update last status */ tsx->status_code = msg->line.status.code; @@ -1847,12 +1696,13 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, tsx->last_tx = tdata; pjsip_tx_data_add_ref( tdata ); } + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, PJSIP_EVENT_TX_MSG, tdata ); } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { - if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_ack==0) { + if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_200resp==0) { /* 2xx class message is not saved, because retransmission * is handled by TU. @@ -1861,16 +1711,20 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, PJSIP_EVENT_TX_MSG, tdata ); /* Transaction is destroyed. */ - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } else { pj_time_val timeout; if (tsx->method.id == PJSIP_INVITE_METHOD) { tsx->retransmit_count = 0; - pjsip_endpt_schedule_timer( tsx->endpt, - &tsx->retransmit_timer, - &t1_timer_val); + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + pjsip_endpt_schedule_timer( tsx->endpt, + &tsx->retransmit_timer, + &t1_timer_val); + } } /* Save last response sent for retransmission when request @@ -1884,7 +1738,7 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, /* Start timer J at 64*T1 for unreliable transport or zero for * reliable transport. */ - if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) { + if (!PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)) { timeout = timeout_timer_val; } else { timeout.sec = timeout.msec = 0; @@ -1915,14 +1769,18 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, /* For INVITE, if unreliable transport is used, retransmission * timer G will be scheduled (retransmission). */ - if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) { + if (!PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)) { pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, NULL); if (cseq->method.id == PJSIP_INVITE_METHOD) { tsx->retransmit_count = 0; - pjsip_endpt_schedule_timer(tsx->endpt, - &tsx->retransmit_timer, - &t1_timer_val); + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + pjsip_endpt_schedule_timer(tsx->endpt, + &tsx->retransmit_timer, + &t1_timer_val); + } } } @@ -1937,14 +1795,18 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, } else if (event->type == PJSIP_EVENT_TIMER && event->body.timer.entry == &tsx->retransmit_timer) { + /* Retransmission timer elapsed. */ pj_status_t status; + /* Must not be triggered while transport is pending. */ + pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0); + /* Must have last response to retransmit. */ pj_assert(tsx->last_tx != NULL); /* Retransmit the last response. */ - status = pjsip_tsx_retransmit( tsx, 1 ); + status = tsx_retransmit( tsx, 1 ); if (status != PJ_SUCCESS) { return status; } @@ -1953,7 +1815,7 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, event->body.timer.entry == &tsx->timeout_timer) { /* Timeout timer. should not happen? */ - pj_assert(0); + pj_assert(!"Should not happen(?)"); tsx->status_code = PJSIP_SC_TSX_TIMEOUT; @@ -1963,26 +1825,29 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx, return PJ_EBUG; } else { - pj_assert(0); + pj_assert(!"Unexpected event"); return PJ_EBUG; } return PJ_SUCCESS; } + /* * Handler for events in Proceeding for UAC * This state happens after provisional response(s) has been received from * UAS. */ -static pj_status_t pjsip_tsx_on_state_proceeding_uac(pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_proceeding_uac(pjsip_transaction *tsx, + pjsip_event *event) { pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING || tsx->state == PJSIP_TSX_STATE_CALLING); if (event->type != PJSIP_EVENT_TIMER) { + pjsip_msg *msg; + /* Must be incoming response, because we should not retransmit * request once response has been received. */ @@ -1991,7 +1856,15 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uac(pjsip_transaction *tsx, return PJ_EINVALIDOP; } - tsx->status_code = event->body.rx_msg.rdata->msg_info.msg->line.status.code; + msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Must be a response message. */ + if (msg->type != PJSIP_RESPONSE_MSG) { + pj_assert(!"Expecting response message!"); + return PJSIP_ENOTRESPONSEMSG; + } + + tsx->status_code = msg->line.status.code; } else { tsx->status_code = PJSIP_SC_TSX_TIMEOUT; } @@ -2010,10 +1883,10 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uac(pjsip_transaction *tsx, /* For INVITE, the state moves to Terminated state (because ACK is * handled in TU). For non-INVITE, state moves to Completed. */ - if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_ack == 0) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } else { pj_time_val timeout; @@ -2046,8 +1919,19 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uac(pjsip_transaction *tsx, /* Generate and send ACK for INVITE. */ if (tsx->method.id == PJSIP_INVITE_METHOD) { - pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx, - event->body.rx_msg.rdata ); + pjsip_tx_data *ack; + + status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx, + event->body.rx_msg.rdata, + &ack); + if (status != PJ_SUCCESS) + return status; + + if (ack != tsx->last_tx) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = ack; + } + status = tsx_send_msg( tsx, tsx->last_tx); if (status != PJ_SUCCESS) { return status; @@ -2055,7 +1939,7 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uac(pjsip_transaction *tsx, } /* Start Timer D with TD/T4 timer if unreliable transport is used. */ - if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport) == 0) { + if (!PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)) { if (tsx->method.id == PJSIP_INVITE_METHOD) { timeout = td_timer_val; } else { @@ -2072,30 +1956,34 @@ static pj_status_t pjsip_tsx_on_state_proceeding_uac(pjsip_transaction *tsx, } else { // Shouldn't happen because there's no timer for this state. - pj_assert(0); + pj_assert(!"Unexpected event"); return PJ_EBUG; } return PJ_SUCCESS; } + /* * Handler for events in Completed state for UAS */ -static pj_status_t pjsip_tsx_on_state_completed_uas( pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx, + pjsip_event *event) { pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED); if (event->type == PJSIP_EVENT_RX_MSG) { pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; - pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, NULL ); + + /* This must be a request message retransmission. */ + if (msg->type != PJSIP_REQUEST_MSG) + return PJSIP_ENOTREQUESTMSG; /* On receive request retransmission, retransmit last response. */ - if (cseq->method.id != PJSIP_ACK_METHOD) { + if (msg->line.req.method.id != PJSIP_ACK_METHOD) { pj_status_t status; - status = pjsip_tsx_retransmit( tsx, 0 ); + status = tsx_retransmit( tsx, 0 ); if (status != PJ_SUCCESS) { return status; } @@ -2122,7 +2010,7 @@ static pj_status_t pjsip_tsx_on_state_completed_uas( pjsip_transaction *tsx, /* Retransmit message. */ pj_status_t status; - status = pjsip_tsx_retransmit( tsx, 1 ); + status = tsx_retransmit( tsx, 1 ); if (status != PJ_SUCCESS) { return status; } @@ -2139,29 +2027,32 @@ static pj_status_t pjsip_tsx_on_state_completed_uas( pjsip_transaction *tsx, tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_TIMER, &tsx->timeout_timer ); - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } else { /* Transaction terminated, it can now be deleted. */ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_TIMER, &tsx->timeout_timer ); - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } } } else { /* Ignore request to transmit. */ - pj_assert(event->body.tx_msg.tdata == tsx->last_tx); + PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG && + event->body.tx_msg.tdata == tsx->last_tx, + PJ_EINVALIDOP); } return PJ_SUCCESS; } + /* * Handler for events in Completed state for UAC transaction. */ -static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx, + pjsip_event *event) { pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED); @@ -2174,7 +2065,7 @@ static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx, PJSIP_EVENT_TIMER, event->body.timer.entry ); /* Transaction has been destroyed. */ - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } else if (event->type == PJSIP_EVENT_RX_MSG) { if (tsx->method.id == PJSIP_INVITE_METHOD) { @@ -2188,7 +2079,7 @@ static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx, { pj_status_t status; - status = pjsip_tsx_retransmit( tsx, 0 ); + status = tsx_retransmit( tsx, 0 ); if (status != PJ_SUCCESS) { return status; } @@ -2199,43 +2090,21 @@ static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx, } else { /* Just drop the response. */ } - } else if (tsx->method.id == PJSIP_INVITE_METHOD && - event->type == PJSIP_EVENT_TX_MSG && - event->body.tx_msg.tdata->msg->line.req.method.id==PJSIP_ACK_METHOD) { - - pj_status_t status; - - /* Set last transmitted message. */ - if (tsx->last_tx != event->body.tx_msg.tdata) { - pjsip_tx_data_dec_ref( tsx->last_tx ); - tsx->last_tx = event->body.tx_msg.tdata; - pjsip_tx_data_add_ref( tsx->last_tx ); - } - - /* No state changed, but notify app. - * Must notify now, so app has chance to put SDP in outgoing ACK msg. - */ - tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, - PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata ); - - /* Send msg */ - status = tsx_send_msg(tsx, event->body.tx_msg.tdata); - if (status != PJ_SUCCESS) - return status; } else { - pj_assert(0); - return PJ_EBUG; + pj_assert(!"Unexpected event"); + return PJ_EINVALIDOP; } return PJ_SUCCESS; } + /* * Handler for events in state Confirmed. */ -static pj_status_t pjsip_tsx_on_state_confirmed( pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx, + pjsip_event *event) { pj_assert(tsx->state == PJSIP_TSX_STATE_CONFIRMED); @@ -2246,20 +2115,15 @@ static pj_status_t pjsip_tsx_on_state_confirmed( pjsip_transaction *tsx, /* Absorb any ACK received. */ if (event->type == PJSIP_EVENT_RX_MSG) { - pjsip_method_e method_id = - event->body.rx_msg.rdata->msg_info.msg->line.req.method.id; + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; - /* Must be a request message. */ - pj_assert(event->body.rx_msg.rdata->msg_info.msg->type == PJSIP_REQUEST_MSG); + /* Only expecting request message. */ + if (msg->type != PJSIP_REQUEST_MSG) + return PJSIP_ENOTREQUESTMSG; /* Must be an ACK request or a late INVITE retransmission. */ - pj_assert(method_id == PJSIP_ACK_METHOD || - method_id == PJSIP_INVITE_METHOD); - - /* Just so that compiler won't complain about unused vars when - * building release code. - */ - PJ_UNUSED_ARG(method_id); + pj_assert(msg->line.req.method.id == PJSIP_ACK_METHOD || + msg->line.req.method.id == PJSIP_INVITE_METHOD); } else if (event->type == PJSIP_EVENT_TIMER) { /* Must be from timeout_timer_. */ @@ -2270,25 +2134,25 @@ static pj_status_t pjsip_tsx_on_state_confirmed( pjsip_transaction *tsx, PJSIP_EVENT_TIMER, &tsx->timeout_timer ); /* Transaction has been destroyed. */ - return PJSIP_ETSXDESTROYED; + //return PJSIP_ETSXDESTROYED; } else { - pj_assert(0); + pj_assert(!"Unexpected event"); return PJ_EBUG; } return PJ_SUCCESS; } + /* * Handler for events in state Terminated. */ -static pj_status_t pjsip_tsx_on_state_terminated( pjsip_transaction *tsx, - pjsip_event *event) +static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx, + pjsip_event *event) { pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED); - - PJ_UNUSED_ARG(event); + pj_assert(event->type == PJSIP_EVENT_TIMER); /* Destroy this transaction */ tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED, @@ -2298,11 +2162,16 @@ static pj_status_t pjsip_tsx_on_state_terminated( pjsip_transaction *tsx, } -static pj_status_t pjsip_tsx_on_state_destroyed(pjsip_transaction *tsx, - pjsip_event *event) +/* + * Handler for events in state Destroyed. + * Shouldn't happen! + */ +static pj_status_t tsx_on_state_destroyed(pjsip_transaction *tsx, + pjsip_event *event) { PJ_UNUSED_ARG(tsx); PJ_UNUSED_ARG(event); - return PJ_SUCCESS; + pj_assert(!"Not expecting any events!!"); + return PJ_EBUG; } diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 50ff6be3..4823feb9 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -70,7 +70,9 @@ const struct { PJSIP_TRANSPORT_UDP, 5060, {"UDP", 3}, PJSIP_TRANSPORT_DATAGRAM}, { PJSIP_TRANSPORT_TCP, 5060, {"TCP", 3}, PJSIP_TRANSPORT_RELIABLE}, { PJSIP_TRANSPORT_TLS, 5061, {"TLS", 3}, PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE}, - { PJSIP_TRANSPORT_SCTP, 5060, {"SCTP", 4}, PJSIP_TRANSPORT_RELIABLE} + { PJSIP_TRANSPORT_SCTP, 5060, {"SCTP", 4}, PJSIP_TRANSPORT_RELIABLE}, + { PJSIP_TRANSPORT_LOOP, 15060, {"LOOP", 4}, PJSIP_TRANSPORT_RELIABLE}, + { PJSIP_TRANSPORT_LOOP_DGRAM, 15060, {"LOOP-DGRAM", 10}, PJSIP_TRANSPORT_DATAGRAM}, }; @@ -194,14 +196,14 @@ PJ_DEF(pj_status_t) pjsip_tx_data_create( pjsip_tpmgr *mgr, status = pj_atomic_create(tdata->pool, 0, &tdata->ref_cnt); if (status != PJ_SUCCESS) { - pjsip_endpt_destroy_pool( mgr->endpt, tdata->pool ); + pjsip_endpt_release_pool( mgr->endpt, tdata->pool ); return status; } //status = pj_lock_create_simple_mutex(pool, "tdta%p", &tdata->lock); status = pj_lock_create_null_mutex(pool, "tdta%p", &tdata->lock); if (status != PJ_SUCCESS) { - pjsip_endpt_destroy_pool( mgr->endpt, tdata->pool ); + pjsip_endpt_release_pool( mgr->endpt, tdata->pool ); return status; } @@ -238,7 +240,7 @@ PJ_DEF(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ) #endif pj_atomic_destroy( tdata->ref_cnt ); pj_lock_destroy( tdata->lock ); - pjsip_endpt_destroy_pool( tdata->mgr->endpt, tdata->pool ); + pjsip_endpt_release_pool( tdata->mgr->endpt, tdata->pool ); return PJSIP_EBUFDESTROYED; } else { return PJ_SUCCESS; @@ -715,16 +717,10 @@ PJ_DEF(pj_ssize_t) pjsip_tpmgr_receive_packet( pjsip_tpmgr *mgr, goto finish_process_fragment; } - /* If message is received from address that's different from sent-by, - * MUST add received parameter to the via. - */ - if (pj_strcmp2(&rdata->msg_info.via->sent_by.host, - rdata->pkt_info.src_name) != 0) - { - pj_strdup2(rdata->tp_info.pool, - &rdata->msg_info.via->recvd_param, - rdata->pkt_info.src_name); - } + /* Always add received parameter to the via. */ + pj_strdup2(rdata->tp_info.pool, + &rdata->msg_info.via->recvd_param, + rdata->pkt_info.src_name); /* RFC 3581: * If message contains "rport" param, put the received port there. diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c new file mode 100644 index 00000000..38f44cea --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_loop.c @@ -0,0 +1,423 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define FAIL_IMMEDIATE 1 +#define FAIL_CALLBACK 2 + +#define ADDR_LOOP "128.0.0.1" +#define ADDR_LOOP_DGRAM "129.0.0.1" + + +/** This structure describes incoming packet. */ +struct recv_list +{ + PJ_DECL_LIST_MEMBER(struct recv_list); + pjsip_rx_data rdata; +}; + +/** This structure is used to keep delayed send failure. */ +struct send_list +{ + PJ_DECL_LIST_MEMBER(struct send_list); + pj_time_val sent_time; + pj_ssize_t sent; + pjsip_tx_data *tdata; + void *token; + void (*callback)(pjsip_transport*, void*, pj_ssize_t); +}; + +/** This structure describes the loop transport. */ +struct loop_transport +{ + pjsip_transport base; + pj_pool_t *pool; + pj_thread_t *thread; + pj_bool_t thread_quit_flag; + pj_bool_t discard; + int fail_mode; + unsigned delay; + struct recv_list recv_list; + struct send_list send_list; +}; + + +/* Helper function to create "incoming" packet */ +struct recv_list *create_incoming_packet( struct loop_transport *loop, + pjsip_tx_data *tdata ) +{ + pj_pool_t *pool; + struct recv_list *pkt; + + pool = pjsip_endpt_create_pool(loop->base.endpt, "rdata", + PJSIP_POOL_RDATA_LEN, PJSIP_POOL_RDATA_INC); + if (!pool) + return NULL; + + pkt = pj_pool_zalloc(pool, sizeof(struct recv_list)); + + /* Initialize rdata. */ + pkt->rdata.tp_info.pool = pool; + pkt->rdata.tp_info.transport = &loop->base; + + /* Copy the packet. */ + pj_memcpy(pkt->rdata.pkt_info.packet, tdata->buf.start, + tdata->buf.cur - tdata->buf.start); + pkt->rdata.pkt_info.len = tdata->buf.cur - tdata->buf.start; + + /* "Source address" info. */ + pkt->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in); + if (loop->base.key.type == PJSIP_TRANSPORT_LOOP) + pj_native_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP); + else + pj_native_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP_DGRAM); + pkt->rdata.pkt_info.src_port = loop->base.local_name.port; + + /* When do we need to "deliver" this packet. */ + pj_gettimeofday(&pkt->rdata.pkt_info.timestamp); + pkt->rdata.pkt_info.timestamp.msec += loop->delay; + pj_time_val_normalize(&pkt->rdata.pkt_info.timestamp); + + /* Done. */ + + return pkt; +} + + +/* Helper function to add pending notification callback. */ +static pj_status_t add_notification( struct loop_transport *loop, + pjsip_tx_data *tdata, + pj_ssize_t sent, + void *token, + void (*callback)(pjsip_transport*, + void*, pj_ssize_t)) +{ + struct send_list *sent_status; + + pjsip_tx_data_add_ref(tdata); + pj_lock_acquire(tdata->lock); + sent_status = pj_pool_alloc(tdata->pool, sizeof(struct send_list)); + pj_lock_release(tdata->lock); + + sent_status->sent = sent; + sent_status->tdata = tdata; + sent_status->token = token; + sent_status->callback = callback; + + pj_gettimeofday(&sent_status->sent_time); + sent_status->sent_time.msec += loop->delay; + pj_time_val_normalize(&sent_status->sent_time); + + pj_lock_acquire(loop->base.lock); + pj_list_push_back(&loop->send_list, sent_status); + pj_lock_release(loop->base.lock); + + return PJ_SUCCESS; +} + +/* Handler for sending outgoing message; called by transport manager. */ +static pj_status_t loop_send_msg( pjsip_transport *tp, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + void (*cb)(pjsip_transport *transport, + void *token, + pj_ssize_t sent_bytes)) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + struct recv_list *recv_pkt; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + PJ_UNUSED_ARG(rem_addr); + PJ_UNUSED_ARG(addr_len); + + + /* Need to send failure immediately? */ + if (loop->fail_mode == FAIL_IMMEDIATE) { + return PJ_STATUS_FROM_OS(OSERR_ECONNRESET); + + /* Need to send failure later? */ + } else if (loop->fail_mode == FAIL_CALLBACK) { + + add_notification(loop, tdata, -PJ_STATUS_FROM_OS(OSERR_ECONNRESET), + token, cb); + + return PJ_EPENDING; + } + + /* Discard any packets? */ + if (loop->discard) + return PJ_SUCCESS; + + /* Create rdata for the "incoming" packet. */ + recv_pkt = create_incoming_packet(loop, tdata); + if (!recv_pkt) + return PJ_ENOMEM; + + /* If delay is not configured, deliver this packet now! */ + if (loop->delay == 0) { + pj_ssize_t size_eaten; + + size_eaten = pjsip_tpmgr_receive_packet( loop->base.tpmgr, + &recv_pkt->rdata); + pj_assert(size_eaten == recv_pkt->rdata.pkt_info.len); + + pjsip_endpt_release_pool(loop->base.endpt, + recv_pkt->rdata.tp_info.pool); + return PJ_SUCCESS; + + } else { + /* Otherwise if delay is configured, add the "packet" to the + * receive list to be processed by worker thread, and add + * pending notification for calling the callback. + */ + add_notification(loop, tdata, tdata->buf.cur - tdata->buf.start, + token, cb); + + pj_lock_acquire(loop->base.lock); + pj_list_push_back(&loop->recv_list, recv_pkt); + pj_lock_release(loop->base.lock); + return PJ_EPENDING; + } +} + +/* Handler to destroy the transport; called by transport manager */ +static pj_status_t loop_destroy(pjsip_transport *tp) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + loop->thread_quit_flag = 1; + /* Unlock transport mutex before joining thread. */ + pj_lock_release(tp->lock); + pj_thread_join(loop->thread); + pj_thread_destroy(loop->thread); + + pj_lock_destroy(loop->base.lock); + pj_atomic_destroy(loop->base.ref_cnt); + pjsip_endpt_release_pool(loop->base.endpt, loop->base.pool); + + return PJ_SUCCESS; +} + +/* Worker thread for loop transport. */ +static int loop_thread(void *arg) +{ + struct loop_transport *loop = arg; + + while (!loop->thread_quit_flag) { + pj_time_val now; + + pj_thread_sleep(10); + pj_gettimeofday(&now); + + pj_lock_acquire(loop->base.lock); + + /* Process pending send notification. */ + while (!pj_list_empty(&loop->send_list)) { + struct send_list *node = loop->send_list.next; + + /* Break when next node time is greater than now. */ + if (PJ_TIME_VAL_GTE(node->sent_time, now)) + break; + + /* Notify callback. */ + if (node->callback) { + (*node->callback)(&loop->base, node->token, node->sent); + } + + /* Delete this from the list. */ + pj_list_erase(node); + + /* Decrement tdata reference counter. */ + pjsip_tx_data_dec_ref(node->tdata); + } + + /* Process "incoming" packets. */ + while (!pj_list_empty(&loop->recv_list)) { + struct recv_list *node = loop->recv_list.next; + pj_ssize_t size_eaten; + + /* Break when next node time is greater than now. */ + if (PJ_TIME_VAL_GTE(node->rdata.pkt_info.timestamp, now)) + break; + + /* Notify transport manager about the "incoming packet" */ + size_eaten = pjsip_tpmgr_receive_packet(loop->base.tpmgr, + &node->rdata); + + /* Must "eat" all the packets. */ + pj_assert(size_eaten == node->rdata.pkt_info.len); + + /* Delete this from the list. */ + pj_list_erase(node); + + /* Done. */ + pjsip_endpt_release_pool(loop->base.endpt, + node->rdata.tp_info.pool); + } + + pj_lock_release(loop->base.lock); + } + + return 0; +} + + +/* Start loop transport. */ +PJ_DEF(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt, + pjsip_transport **transport) +{ + pj_pool_t *pool; + struct loop_transport *loop; + pj_status_t status; + + /* Create pool. */ + pool = pjsip_endpt_create_pool(endpt, "loop", 4000, 4000); + if (!pool) + return PJ_ENOMEM; + + /* Create the loop structure. */ + loop = pj_pool_zalloc(pool, sizeof(struct loop_transport)); + + /* Initialize transport properties. */ + pj_sprintf(loop->base.obj_name, "loop%p", loop); + loop->base.pool = pool; + status = pj_atomic_create(pool, 0, &loop->base.ref_cnt); + if (status != PJ_SUCCESS) + goto on_error; + status = pj_lock_create_recursive_mutex(pool, "loop", &loop->base.lock); + if (status != PJ_SUCCESS) + goto on_error; + loop->base.key.type = PJSIP_TRANSPORT_LOOP_DGRAM; + loop->base.key.rem_addr.sa_family = PJ_AF_INET; + loop->base.type_name = "LOOP-DGRAM"; + loop->base.info = "LOOP-DGRAM"; + loop->base.flag = PJSIP_TRANSPORT_DATAGRAM; + loop->base.local_name.host = pj_str(ADDR_LOOP_DGRAM); + loop->base.local_name.port = + pjsip_transport_get_default_port_for_type(loop->base.key.type); + loop->base.addr_len = sizeof(pj_sockaddr_in); + loop->base.endpt = endpt; + loop->base.tpmgr = pjsip_endpt_get_tpmgr(endpt); + loop->base.send_msg = &loop_send_msg; + loop->base.destroy = &loop_destroy; + + pj_list_init(&loop->recv_list); + pj_list_init(&loop->send_list); + + /* Create worker thread. */ + status = pj_thread_create(pool, "loop", &loop_thread, loop, 0, + PJ_THREAD_SUSPENDED, &loop->thread); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register to transport manager. */ + status = pjsip_transport_register( loop->base.tpmgr, &loop->base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start the thread. */ + status = pj_thread_resume(loop->thread); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Done. + */ + + *transport = &loop->base; + return PJ_SUCCESS; + +on_error: + if (loop->base.lock) + pj_lock_destroy(loop->base.lock); + if (loop->thread) + pj_thread_destroy(loop->thread); + if (loop->base.ref_cnt) + pj_atomic_destroy(loop->base.ref_cnt); + pjsip_endpt_release_pool(endpt, loop->pool); + return status; +} + + +PJ_DEF(pj_status_t) pjsip_loop_set_discard( pjsip_transport *tp, + pj_bool_t discard, + pj_bool_t *prev_value ) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->discard; + loop->discard = discard; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_loop_set_failure( pjsip_transport *tp, + int fail_flag, + int *prev_value ) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->fail_mode; + loop->fail_mode = fail_flag; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_loop_set_delay( pjsip_transport *tp, + unsigned delay, + unsigned *prev_value) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->delay; + loop->delay = delay; + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsip/sip_transport_udp.c b/pjsip/src/pjsip/sip_transport_udp.c index f61b58c4..f925d8cd 100644 --- a/pjsip/src/pjsip/sip_transport_udp.c +++ b/pjsip/src/pjsip/sip_transport_udp.c @@ -1,4 +1,4 @@ -/* $Id: $ */ +/* $Id$ */ /* * Copyright (C) 2003-2006 Benny Prijono * @@ -192,6 +192,7 @@ static pj_status_t udp_send_msg( pjsip_transport *transport, { struct udp_transport *tp = (struct udp_transport*)transport; pj_ssize_t size; + pj_status_t status; PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); @@ -203,9 +204,14 @@ static pj_status_t udp_send_msg( pjsip_transport *transport, /* Send to ioqueue! */ size = tdata->buf.cur - tdata->buf.start; - return pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key, - tdata->buf.start, &size, 0, - rem_addr, addr_len); + status = pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key, + tdata->buf.start, &size, 0, + rem_addr, addr_len); + + if (status != PJ_EPENDING) + tdata->op_key.tdata = NULL; + + return status; } /* @@ -244,7 +250,7 @@ static pj_status_t udp_destroy( pjsip_transport *transport ) pj_lock_destroy(tp->base.lock); /* Destroy pool. */ - pjsip_endpt_destroy_pool(tp->base.endpt, tp->base.pool); + pjsip_endpt_release_pool(tp->base.endpt, tp->base.pool); return PJ_SUCCESS; } @@ -419,7 +425,8 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt, } /* Done. */ - *p_transport = &tp->base; + if (p_transport) + *p_transport = &tp->base; return PJ_SUCCESS; on_error: diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 532d6fb5..0760e322 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -176,8 +176,7 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, pj_strdup_with_null(tdata->pool, &tmp, param_target); target = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 0); if (target == NULL) { - PJ_LOG(4,(THIS_FILE, "Error creating request: invalid target %s", - tmp.ptr)); + status = PJSIP_EINVALIDREQURI; goto on_error; } @@ -187,8 +186,7 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, from->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (from->uri == NULL) { - PJ_LOG(4,(THIS_FILE, "Error creating request: invalid 'From' URI '%s'", - tmp.ptr)); + status = PJSIP_EINVALIDHDR; goto on_error; } pj_create_unique_string(tdata->pool, &from->tag); @@ -199,8 +197,7 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, to->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (to->uri == NULL) { - PJ_LOG(4,(THIS_FILE, "Error creating request: invalid 'To' URI '%s'", - tmp.ptr)); + status = PJSIP_EINVALIDHDR; goto on_error; } @@ -211,9 +208,7 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, contact->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (contact->uri == NULL) { - PJ_LOG(4,(THIS_FILE, - "Error creating request: invalid 'Contact' URI '%s'", - tmp.ptr)); + status = PJSIP_EINVALIDHDR; goto on_error; } } else { @@ -1169,7 +1164,7 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, /* Callback will be called later. */ return PJ_SUCCESS; } else { - send_response_transport_cb(send_state, tdata, -status); + pjsip_transport_dec_ref(send_state->cur_transport); return status; } } else { @@ -1179,6 +1174,57 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, } } +/* + * Send response + */ +PJ_DEF(pj_status_t) pjsip_endpt_respond_stateless( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *hdr_list, + const pjsip_msg_body *body) +{ + pj_status_t status; + pjsip_response_addr res_addr; + pjsip_tx_data *tdata; + + /* Create response message */ + status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text, + &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add the message headers, if any */ + if (hdr_list) { + const pjsip_hdr *hdr = hdr_list->next; + while (hdr != hdr_list) { + pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_clone(tdata->pool, hdr) ); + hdr = hdr->next; + } + } + + /* Add the message body, if any. */ + if (body) { + tdata->msg->body = pj_pool_alloc(tdata->pool, sizeof(pjsip_msg_body)); + status = pjsip_msg_body_clone( tdata->pool, tdata->msg->body, body ); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + } + + /* Get where to send request. */ + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr ); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + + /* Send! */ + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL ); + + return status; +} /* * Get the event string from the event ID. diff --git a/pjsip/src/pjsip/sip_util_proxy.c b/pjsip/src/pjsip/sip_util_proxy.c new file mode 100644 index 00000000..043877bf --- /dev/null +++ b/pjsip/src/pjsip/sip_util_proxy.c @@ -0,0 +1,66 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + const pjsip_uri *uri, + const pj_str_t *branch, + unsigned options, + pjsip_tx_data **tdata) +{ + PJ_UNUSED_ARG(endpt); + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(uri); + PJ_UNUSED_ARG(branch); + PJ_UNUSED_ARG(options); + PJ_UNUSED_ARG(tdata); + + pj_assert(!"Not implemented yet"); + return PJ_EBUG; +} + + +PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + unsigned options, + pjsip_tx_data **tdata) +{ + PJ_UNUSED_ARG(endpt); + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(options); + PJ_UNUSED_ARG(tdata); + + pj_assert(!"Not implemented yet"); + return PJ_EBUG; +} + + +PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata ) +{ + pj_str_t empty_str = { NULL, 0 }; + + PJ_UNUSED_ARG(rdata); + pj_assert(!"Not implemented yet"); + return empty_str; +} + + diff --git a/pjsip/src/test-pjsip/msg_test.c b/pjsip/src/test-pjsip/msg_test.c index e3702713..11a69b43 100644 --- a/pjsip/src/test-pjsip/msg_test.c +++ b/pjsip/src/test-pjsip/msg_test.c @@ -47,7 +47,7 @@ struct test_msg { /* 'Normal' message with all headers. */ "INVITE sip:user@foo SIP/2.0\n" - "From: Hi I'm Joe ;tag=1234578901234567890\r" + "From: Hi I'm Joe ;tag=123457890123456\r" "To: Fellow User \r\n" "Call-ID: 12345678901234567890@bar\r\n" "Content-Length: 0\r\n" @@ -57,15 +57,16 @@ struct test_msg "Content-Type: text/html ; charset=ISO-8859-4\r" "Route: ,\r\n" " \r" - "Record-Route: ,\r\n" + "Record-Route: ,\r\n" /* multiple routes+folding*/ " \n" - "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1\n" - "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" + "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" + "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" /* folding. */ " ;received=192.0.2.1\r\n" "Via: SIP/2.0/UDP 10.2.1.1, SIP/2.0/TCP 192.168.1.1\n" "Organization: \r" "Max-Forwards: 70\n" - "X-Header: \r\n" + "X-Header: \r\n" /* empty header */ + "P-Associated-URI:\r\n" /* empty header without space */ "\r\n", &create_msg0, PJ_SUCCESS @@ -351,10 +352,10 @@ static pjsip_msg *create_msg0(pj_pool_t *pool) pj_strdup2(pool, &url->user, "user"); pj_strdup2(pool, &url->host, "foo"); - /* "From: Hi I'm Joe ;tag=1234578901234567890\r" */ + /* "From: Hi I'm Joe ;tag=123457890123456\r" */ fromto = pjsip_from_hdr_create(pool); pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto); - pj_strdup2(pool, &fromto->tag, "1234578901234567890"); + pj_strdup2(pool, &fromto->tag, "123457890123456"); name_addr = pjsip_name_addr_create(pool); fromto->uri = (pjsip_uri*)name_addr; pj_strdup2(pool, &name_addr->display, "Hi I'm Joe"); @@ -462,12 +463,12 @@ static pjsip_msg *create_msg0(pj_pool_t *pool) pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com"); url->lr_param = 1; - /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1\n" */ + /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" */ via = pjsip_via_hdr_create(pool); pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); pj_strdup2(pool, &via->transport, "SCTP"); pj_strdup2(pool, &via->sent_by.host, "bigbox3.site3.atlanta.com"); - pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c2312983.1"); + pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c230"); /* "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" " ;received=192.0.2.1\r\n" */ @@ -518,6 +519,15 @@ static pjsip_msg *create_msg0(pj_pool_t *pool) str.slen = 0; generic->hvalue = str; + /* P-Associated-URI:\r\n */ + str.ptr = "P-Associated-URI"; + str.slen = 16; + generic = pjsip_generic_string_hdr_create(pool, &str); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = NULL; + str.slen = 0; + generic->hvalue = str; + return msg; } @@ -677,7 +687,7 @@ int msg_test(void) for (i=0; i + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include +#include + +static int datagram_loop_test() +{ + pjsip_transport *loop, *tp; + pj_str_t s; + int i, log_level; + pj_sockaddr_in addr; + pj_status_t status; + + PJ_LOG(3,("", "testing datagram loop transport")); + + /* Create loop transport. */ + status = pjsip_loop_start(endpt, &loop); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create datagram loop transport", + status); + return -10; + } + + /* Create dummy address. */ + pj_sockaddr_in_init(&addr, pj_cstr(&s, "130.0.0.1"), TEST_UDP_PORT); + + /* Test acquire transport. */ + status = pjsip_endpt_acquire_transport( endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &addr, sizeof(addr), &tp); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to acquire transport", status); + return -20; + } + + /* Check that this is the right transport. */ + if (tp != loop) { + return -30; + } + + /* Test basic transport attributes */ + status = generic_transport_test(loop); + if (status != PJ_SUCCESS) + return status; + + /* Basic transport's send/receive loopback test. */ + for (i=0; i<2; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram"); + if (status != 0) + return status; + } + + /* For multithreaded round-trip test to work, delay must be set + * (otherwise functions will be called recursively until no memory is + * left in the system) + */ + + /* Put delay. */ + pjsip_loop_set_delay(loop, 1, NULL); + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, tp, + "sip:bob@130.0.0.1;transport=loop-dgram"); + if (status != 0) + return status; + + + /* Next test will test without delay. + * This will stress-test the system. + */ + PJ_LOG(3,(""," performing another multithreaded round-trip test...")); + + /* Remove delay. */ + pjsip_loop_set_delay(loop, 0, NULL); + + /* Ignore errors. */ + log_level = pj_log_get_level(); + pj_log_set_level(2); + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, tp, + "sip:bob@130.0.0.1;transport=loop-dgram"); + if (status != 0) + return status; + + /* Restore log level. */ + pj_log_set_level(log_level); + + /* Check that reference counter is one. */ + if (pj_atomic_get(loop->ref_cnt) != 1) { + return -30; + } + + /* Decrement reference. */ + pjsip_transport_dec_ref(loop); + + return 0; +} + +int transport_loop_test(void) +{ + int status; + + status = datagram_loop_test(); + if (status != 0) + return status; + + return 0; +} diff --git a/pjsip/src/test-pjsip/transport_test.c b/pjsip/src/test-pjsip/transport_test.c index 0d54d42d..76b6bb9e 100644 --- a/pjsip/src/test-pjsip/transport_test.c +++ b/pjsip/src/test-pjsip/transport_test.c @@ -75,7 +75,6 @@ int generic_transport_test(pjsip_transport *tp) * before we continue with more complicated tests. */ #define FROM_HDR "Bob " -#define TO_HDR "Alice " #define CONTACT_HDR "Bob " #define CALL_ID_HDR "SendRecv-Test" #define CSEQ_VALUE 100 @@ -108,7 +107,7 @@ static pjsip_module my_module = NULL, /* unload() */ &my_on_rx_request, /* on_rx_request() */ &my_on_rx_response, /* on_rx_response() */ - NULL, /* tsx_handler() */ + NULL, /* on_tsx_state() */ }; @@ -188,10 +187,9 @@ static void send_msg_callback(pjsip_send_state *stateless_data, /* Test that we receive loopback message. */ int transport_send_recv_test( pjsip_transport_type_e tp_type, pjsip_transport *ref_tp, - const pj_sockaddr_in *rem_addr ) + char *target_url ) { pj_status_t status; - char target_buf[80]; pj_str_t target, from, to, contact, call_id, body; pjsip_method method; pjsip_tx_data *tdata; @@ -209,11 +207,9 @@ int transport_send_recv_test( pjsip_transport_type_e tp_type, } /* Create a request message. */ - pj_sprintf(target_buf, "sip:%s:%d", pj_inet_ntoa(rem_addr->sin_addr), - pj_ntohs(rem_addr->sin_port)); - target = pj_str(target_buf); + target = pj_str(target_url); from = pj_str(FROM_HDR); - to = pj_str(TO_HDR); + to = pj_str(target_url); contact = pj_str(CONTACT_HDR); call_id = pj_str(CALL_ID_HDR); body = pj_str(BODY); @@ -333,7 +329,7 @@ static struct pj_str_t call_id; } rt_test_data[16]; -static char rt_target_uri[32]; +static char rt_target_uri[64]; static pj_bool_t rt_stop; static pj_str_t rt_call_id; @@ -349,15 +345,18 @@ static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata) status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); if (status != PJ_SUCCESS) { + app_perror(" error creating response", status); return PJ_TRUE; } status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); if (status != PJ_SUCCESS) { + app_perror(" error in get response address", status); pjsip_tx_data_dec_ref(tdata); return PJ_TRUE; } status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL); if (status != PJ_SUCCESS) { + app_perror(" error sending response", status); pjsip_tx_data_dec_ref(tdata); return PJ_TRUE; } @@ -376,7 +375,7 @@ static pj_status_t rt_send_request(int thread_id) /* Create a request message. */ target = pj_str(rt_target_uri); from = pj_str(FROM_HDR); - to = pj_str(TO_HDR); + to = pj_str(rt_target_uri); contact = pj_str(CONTACT_HDR); call_id = rt_test_data[thread_id].call_id; @@ -430,7 +429,7 @@ static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata) static int rt_thread(void *arg) { - int thread_id = (int)arg; + int i, thread_id = (int)arg; pj_time_val poll_delay = { 0, 10 }; /* Sleep to allow main threads to run. */ @@ -443,12 +442,17 @@ static int rt_thread(void *arg) while (!rt_stop) { pjsip_endpt_handle_events(endpt, &poll_delay); } + + /* Exhaust responses. */ + for (i=0; i<100; ++i) + pjsip_endpt_handle_events(endpt, &poll_delay); + return 0; } int transport_rt_test( pjsip_transport_type_e tp_type, pjsip_transport *ref_tp, - const pj_sockaddr_in *rem_addr ) + char *target_url ) { enum { THREADS = 4, INTERVAL = 10 }; int i; @@ -483,8 +487,7 @@ int transport_rt_test( pjsip_transport_type_e tp_type, return -610; /* Initialize static test data. */ - pj_sprintf(rt_target_uri, "sip:%s:%d", pj_inet_ntoa(rem_addr->sin_addr), - pj_ntohs(rem_addr->sin_port)); + pj_native_strcpy(rt_target_uri, target_url); rt_call_id = pj_str("RT-Call-Id/"); rt_stop = PJ_FALSE; @@ -551,7 +554,7 @@ int transport_rt_test( pjsip_transport_type_e tp_type, PJ_LOG(3,("", " no message was lost")); PJ_LOG(3,("", " average round-trip=%d usec", usec_rt)); - pjsip_endpt_destroy_pool(endpt, pool); + pjsip_endpt_release_pool(endpt, pool); if (is_reliable && (total_sent != total_recv)) { PJ_LOG(3,("", " error: %d messages lost", total_sent-total_recv)); diff --git a/pjsip/src/test-pjsip/transport_udp_test.c b/pjsip/src/test-pjsip/transport_udp_test.c index 1af74ed0..0fdbddae 100644 --- a/pjsip/src/test-pjsip/transport_udp_test.c +++ b/pjsip/src/test-pjsip/transport_udp_test.c @@ -76,13 +76,15 @@ int transport_udp_test(void) /* Basic transport's send/receive loopback test. */ pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "127.0.0.1"), TEST_UDP_PORT); for (i=0; i + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include +#include + +/***************************************************************************** + ** + ** UAC basic retransmission and timeout test. + ** + ** This will test the retransmission of the UAC transaction. Remote will not + ** answer the transaction, so the transaction should fail. + ** + ***************************************************************************** + */ + +static char *CALL_ID1 = "UAC-Tsx-Basic-Test1"; +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e); +static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata); + +/* UAC transaction user module. */ +static pjsip_module tsx_user = +{ + NULL, NULL, /* prev and next */ + { "Tsx-User", 8}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* User data. */ + 0, /* Number of methods supported (=0). */ + { 0 }, /* Array of methods (none) */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + &tsx_user_on_tsx_state, /* on_tsx_state() */ +}; + +/* Module to receive the loop-backed request. */ +static pjsip_module msg_receiver = +{ + NULL, NULL, /* prev and next */ + { "Test", 4}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* User data. */ + 0, /* Number of methods supported (=0). */ + { 0 }, /* Array of methods (none) */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &msg_receiver_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* Static vars. */ +static int recv_count; +static pj_time_val recv_last; +static pj_bool_t test_complete; + +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED && test_complete==0) + test_complete = 1; +} + +#define DIFF(a,b) ((amsg_info.call_id, CALL_ID1) == 0) { + /* + * The CALL_ID1 test performs the verifications for transaction + * retransmission mechanism. It will not answer the incoming request + * with any response. + */ + pjsip_msg *msg = rdata->msg_info.msg; + + PJ_LOG(4,("", " received request")); + + /* Only wants to take INVITE or OPTIONS method. */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD && + msg->line.req.method.id != PJSIP_OPTIONS_METHOD) + { + PJ_LOG(3,("", " error: received unexpected method %.*s", + msg->line.req.method.name.slen, + msg->line.req.method.name.ptr)); + test_complete = -600; + return PJ_TRUE; + } + + if (recv_count == 0) { + recv_count++; + pj_gettimeofday(&recv_last); + } else { + pj_time_val now; + unsigned msec_expected, msec_elapsed; + + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, recv_last); + msec_elapsed = now.sec*1000 + now.msec; + + ++recv_count; + msec_expected = (1<<(recv_count-2))*PJSIP_T1_TIMEOUT; + + if (msg->line.req.method.id != PJSIP_INVITE_METHOD) { + if (msec_expected > PJSIP_T2_TIMEOUT) + msec_expected = PJSIP_T2_TIMEOUT; + } + + if (DIFF(msec_expected, msec_elapsed) > 100) { + PJ_LOG(3,(""," error: expecting %d-th retransmission in %d " + "ms, received in %d ms", + recv_count-1, msec_expected, msec_elapsed)); + test_complete = -610; + } + + if (recv_count > 7) { + PJ_LOG(3,("", " error: too many messages (%d) received", + recv_count)); + test_complete = -620; + } + + pj_gettimeofday(&recv_last); + } + return PJ_TRUE; + } + return PJ_FALSE; +} + +/***************************************************************************** + ** + ** UAC basic retransmission and timeout test. + ** + ** This will test the retransmission of the UAC transaction. Remote will not + ** answer the transaction, so the transaction should fail. The Call-ID + ** CALL_ID1 will be used for this test. + ** + ***************************************************************************** + */ +static int tsx_uac_retransmit_test(const pjsip_method *method) +{ + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + char buf[80]; + pj_str_t target, from, call_id, tsx_key; + pj_time_val timeout; + pj_status_t status; + + PJ_LOG(3,("", " basic uac retransmission and timeout test")); + + pj_sprintf(buf, "sip:alice@127.0.0.1:%d", TEST_UDP_PORT); + target = pj_str(buf); + from = pj_str("sip:bob@127.0.0.1"); + call_id = pj_str(CALL_ID1); + + /* Create request. */ + status = pjsip_endpt_create_request( endpt, method, &target, + &from, &target, NULL, &call_id, -1, + NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create request", status); + return -500; + } + + /* Add additional reference to tdata to prevent transaction from + * deleting it. + */ + pjsip_tx_data_add_ref(tdata); + + /* Create transaction. */ + status = pjsip_tsx_create_uac( &tsx_user, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create UAC transaction", status); + return -510; + } + + /* Get transaction key. */ + pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key); + + /* Send the message. */ + status = pjsip_tsx_send_msg(tsx, NULL); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to send request", status); + return -520; + } + + /* Set test completion time. */ + pj_gettimeofday(&timeout); + timeout.sec += 33; + + /* Wait until test complete. */ + while (!test_complete) { + pj_time_val now; + + pjsip_endpt_handle_events(endpt, NULL); + + pj_gettimeofday(&now); + if (now.sec > timeout.sec) { + PJ_LOG(3,("", " Error: test has timed out")); + return -530; + } + } + + if (status < 0) + return status; + + /* Make sure transaction has been destroyed. */ + if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) { + PJ_LOG(3,("", " Error: transaction has not been destroyed")); + return -540; + } + + /* Check tdata reference counter. */ + if (pj_atomic_get(tdata->ref_cnt) != 1) { + PJ_LOG(3,("", " Error: tdata reference counter is %d", + pj_atomic_get(tdata->ref_cnt))); + return -550; + } + + /* Destroy txdata */ + pjsip_tx_data_dec_ref(tdata); + + return PJ_SUCCESS; +} + +/***************************************************************************** + ** + ** UAC Transaction Test. + ** + ***************************************************************************** + */ +int tsx_uac_test(void) +{ + pj_sockaddr_in addr; + pj_str_t tmp; + pjsip_transport *tp; + pj_status_t status; + + pj_sockaddr_in_init(&addr, pj_cstr(&tmp, "127.0.0.1"), TEST_UDP_PORT); + + /* Start UDP transport if necessary. */ + if (pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, &addr, + sizeof(addr), &tp) != PJ_SUCCESS) + { + addr.sin_addr.s_addr = 0; + status = pjsip_udp_transport_start( endpt, &addr, NULL, 1, NULL); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to start UDP transport", status); + return -10; + } + } else { + pjsip_transport_dec_ref(tp); + } + + /* Start transaction layer module. */ + status = pjsip_tsx_layer_init(endpt); + if (status != PJ_SUCCESS) { + app_perror(" Error initializing transaction module", status); + return -20; + } + + /* Register modules. */ + status = pjsip_endpt_register_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -30; + } + status = pjsip_endpt_register_module(endpt, &msg_receiver); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -30; + } + + /* Basic retransmit and timeout test for INVITE. */ + status = tsx_uac_retransmit_test(&pjsip_invite_method); + if (status != 0) + return status; + + /* Basic retransmit and timeout test for non-INVITE. */ + status = tsx_uac_retransmit_test(&pjsip_options_method); + if (status != 0) + return status; + + return 0; +} diff --git a/pjsip/src/test-pjsip/uri_test.c b/pjsip/src/test-pjsip/uri_test.c index 647397a2..de9604bf 100644 --- a/pjsip/src/test-pjsip/uri_test.c +++ b/pjsip/src/test-pjsip/uri_test.c @@ -804,7 +804,7 @@ int uri_test() goto on_return; } } - pjsip_endpt_destroy_pool(endpt, pool); + pjsip_endpt_release_pool(endpt, pool); PJ_LOG(3,("", " benchmarking...")); parse_len = print_len = cmp_len = 0; @@ -818,11 +818,11 @@ int uri_test() if (status != PJ_SUCCESS) { PJ_LOG(3,("uri_test", " error %d when testing entry %d", status, i)); - pjsip_endpt_destroy_pool(endpt, pool); + pjsip_endpt_release_pool(endpt, pool); goto on_return; } } - pjsip_endpt_destroy_pool(endpt, pool); + pjsip_endpt_release_pool(endpt, pool); } kbytes = parse_len; -- cgit v1.2.3