diff options
author | Benny Prijono <bennylp@teluu.com> | 2005-12-30 23:50:15 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2005-12-30 23:50:15 +0000 |
commit | 944562492d0c16b9e44ec4e1cc97657846d82cd0 (patch) | |
tree | 6e6c2e65e95e479384faeeaab4f525fa91cc7f27 | |
parent | 9b86a2145e18cb843e69167b10f3c7414c11c634 (diff) |
Basic module, transport, and sending messages
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@106 74dad513-b988-da41-8d7b-12977e46ad98
33 files changed, 3189 insertions, 1044 deletions
diff --git a/pjlib/include/pj/assert.h b/pjlib/include/pj/assert.h index 5a842477..6814e4cd 100644 --- a/pjlib/include/pj/assert.h +++ b/pjlib/include/pj/assert.h @@ -67,6 +67,26 @@ # define PJ_ASSERT_RETURN(expr,retval) pj_assert(expr) #endif +/** + * @hideinitializer + * If #PJ_ENABLE_EXTRA_CHECK is declared and non-zero, then + * #PJ_ASSERT_ON_FAIL macro will evaluate the expression in @a expr during + * run-time. If the expression yields false, assertion will be triggered + * and @a exec_on_fail will be executed. + * + * If #PJ_ENABLE_EXTRA_CHECK is not declared or is zero, then no run-time + * checking will be performed. The macro simply evaluates to pj_assert(expr). + */ +#if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK != 0 +# define PJ_ASSERT_ON_FAIL(expr,exec_on_fail) \ + do { \ + pj_assert(expr); \ + if (!(expr)) exec_on_fail; \ + } while (0) +#else +# define PJ_ASSERT_ON_FAIL(expr,exec_on_fail) pj_assert(expr) +#endif + /** @} */ #endif /* __PJ_ASSERT_H__ */ diff --git a/pjlib/include/pj/os.h b/pjlib/include/pj/os.h index 2917a2ab..8c04364d 100644 --- a/pjlib/include/pj/os.h +++ b/pjlib/include/pj/os.h @@ -507,6 +507,75 @@ PJ_DECL(pj_status_t) pj_mutex_destroy(pj_mutex_t *mutex); /////////////////////////////////////////////////////////////////////////////// /** + * @defgroup PJ_RW_MUTEX Reader/Writer Mutex + * @ingroup PJ_OS + * @{ + * Reader/writer mutex is a classic synchronization object where multiple + * readers can acquire the mutex, but only a single writer can acquire the + * mutex. + */ +typedef struct pj_rwmutex_t pj_rwmutex_t; + +/** + * Create reader/writer mutex. + * + * @param pool Pool to allocate memory for the mutex. + * @param name Name to be assigned to the mutex. + * @param mutex Pointer to receive the newly created mutex. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, + pj_rwmutex_t **mutex); + +/** + * Lock the mutex for reading. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex); + +/** + * Lock the mutex for writing. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex); + +/** + * Release read lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex); + +/** + * Release write lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex); + +/** + * Destroy reader/writer mutex. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex); + + +/** + * @} + */ + + +/////////////////////////////////////////////////////////////////////////////// +/** * @defgroup PJ_CRIT_SEC Critical sections. * @ingroup PJ_OS * @{ diff --git a/pjlib/include/pj/sock.h b/pjlib/include/pj/sock.h index 02d88c1e..89883495 100644 --- a/pjlib/include/pj/sock.h +++ b/pjlib/include/pj/sock.h @@ -176,13 +176,13 @@ typedef struct pj_in_addr /** * This structure describes Internet socket address. */ -typedef struct pj_sockaddr_in +struct pj_sockaddr_in { pj_uint16_t sin_family; /**< Address family. */ pj_uint16_t sin_port; /**< Transport layer port number. */ pj_in_addr sin_addr; /**< IP address. */ char sin_zero[8]; /**< Padding. */ -} pj_sockaddr_in; +}; /** diff --git a/pjlib/include/pj/types.h b/pjlib/include/pj/types.h index 62e2c0f4..0099255f 100644 --- a/pjlib/include/pj/types.h +++ b/pjlib/include/pj/types.h @@ -218,6 +218,9 @@ typedef long pj_sock_t; /** Generic socket address. */ typedef void pj_sockaddr_t; +/** Forward declaration. */ +typedef struct pj_sockaddr_in pj_sockaddr_in; + /** Color type. */ typedef unsigned int pj_color_t; diff --git a/pjlib/src/pj/os_core_win32.c b/pjlib/src/pj/os_core_win32.c index 756b88a8..eac778f6 100644 --- a/pjlib/src/pj/os_core_win32.c +++ b/pjlib/src/pj/os_core_win32.c @@ -859,6 +859,123 @@ PJ_DEF(pj_bool_t) pj_mutex_is_locked(pj_mutex_t *mutex) #endif /////////////////////////////////////////////////////////////////////////////// + +struct pj_rwmutex_t +{ + pj_mutex_t *read_lock, *write_lock; + int reader_count; +}; + +/* + * Create reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, + pj_rwmutex_t **p_mutex) +{ + pj_status_t status; + pj_rwmutex_t *rwmutex; + + PJ_ASSERT_RETURN(pool && p_mutex, PJ_EINVAL); + + *p_mutex = NULL; + rwmutex = pj_pool_alloc(pool, sizeof(struct pj_rwmutex_t)); + + status = pj_mutex_create_simple(pool, name, &rwmutex ->read_lock); + if (status != PJ_SUCCESS) + return status; + + status = pj_mutex_create_recursive(pool, name, &rwmutex->write_lock); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(rwmutex->read_lock); + return status; + } + + rwmutex->reader_count = 0; + *p_mutex = rwmutex; + return PJ_SUCCESS; +} + +/* + * Lock the mutex for reading. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + status = pj_mutex_lock(mutex->read_lock); + if (status != PJ_SUCCESS) + return status; + + mutex->reader_count++; + if (mutex->reader_count == 1) + pj_mutex_lock(mutex->write_lock); + + status = pj_mutex_unlock(mutex->read_lock); + return status; +} + +/* + * Lock the mutex for writing. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + return pj_mutex_lock(mutex->write_lock); +} + +/* + * Release read lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + status = pj_mutex_lock(mutex->read_lock); + if (status != PJ_SUCCESS) + return status; + + pj_assert(mutex->reader_count >= 1); + + --mutex->reader_count; + if (mutex->reader_count == 0) + pj_mutex_unlock(mutex->write_lock); + + status = pj_mutex_unlock(mutex->read_lock); + return status; +} + +/* + * Release write lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + return pj_mutex_unlock(mutex->write_lock); +} + + +/* + * Destroy reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + pj_mutex_destroy(mutex->read_lock); + pj_mutex_destroy(mutex->write_lock); + return PJ_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// /* * pj_enter_critical_section() */ diff --git a/pjlib/src/pj/sock_bsd.c b/pjlib/src/pj/sock_bsd.c index 5a096936..3ded3a54 100644 --- a/pjlib/src/pj/sock_bsd.c +++ b/pjlib/src/pj/sock_bsd.c @@ -187,7 +187,7 @@ PJ_DEF(pj_status_t) pj_sockaddr_in_set_str_addr( pj_sockaddr_in *addr, { PJ_CHECK_STACK(); - PJ_ASSERT_RETURN(str_addr && str_addr->slen < PJ_MAX_HOSTNAME, + PJ_ASSERT_RETURN(!str_addr || str_addr->slen < PJ_MAX_HOSTNAME, (addr->sin_addr.s_addr=PJ_INADDR_NONE, PJ_EINVAL)); addr->sin_family = AF_INET; @@ -224,8 +224,7 @@ PJ_DEF(pj_status_t) pj_sockaddr_in_init( pj_sockaddr_in *addr, const pj_str_t *str_addr, pj_uint16_t port) { - PJ_ASSERT_RETURN(addr && str_addr, - (addr->sin_addr.s_addr=PJ_INADDR_NONE, PJ_EINVAL)); + PJ_ASSERT_RETURN(addr, (addr->sin_addr.s_addr=PJ_INADDR_NONE, PJ_EINVAL)); addr->sin_family = PJ_AF_INET; pj_sockaddr_in_set_port(addr, port); diff --git a/pjsip/build/pjsip_core.dsp b/pjsip/build/pjsip_core.dsp index 8893fa00..264799c7 100644 --- a/pjsip/build/pjsip_core.dsp +++ b/pjsip/build/pjsip_core.dsp @@ -124,6 +124,7 @@ SOURCE=..\src\pjsip\sip_tel_uri.c # Begin Source File
SOURCE=..\src\pjsip\sip_transaction.c
+# PROP Exclude_From_Build 1
# End Source File
# Begin Source File
@@ -141,6 +142,11 @@ SOURCE=..\src\pjsip\sip_uri.c SOURCE=..\src\pjsip\sip_util.c
# End Source File
+# Begin Source File
+
+SOURCE=..\src\pjsip\sip_util_statefull.c
+# PROP Exclude_From_Build 1
+# End Source File
# End Group
# Begin Group "Header Files"
diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp index 2ca46668..1c8ad9f2 100644 --- a/pjsip/build/test_pjsip.dsp +++ b/pjsip/build/test_pjsip.dsp @@ -101,6 +101,18 @@ SOURCE="..\src\test-pjsip\test.c" # End Source File
# Begin Source File
+SOURCE="..\src\test-pjsip\transport_test.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\test-pjsip\transport_udp_test.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\test-pjsip\txdata_test.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\test-pjsip\uri_test.c"
# End Source File
# End Group
diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index 8254d508..d9d03a97 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -115,6 +115,34 @@ PJ_DECL(const pj_str_t*) pjsip_endpt_name(const pjsip_endpoint *endpt); PJ_DECL(void) pjsip_endpt_handle_events( pjsip_endpoint *endpt, const pj_time_val *max_timeout); + +/** + * Schedule timer to endpoint's timer heap. Application must poll the endpoint + * periodically (by calling #pjsip_endpt_handle_events) to ensure that the + * timer events are handled in timely manner. When the timeout for the timer + * has elapsed, the callback specified in the entry argument will be called. + * This function, like all other endpoint functions, is thread safe. + * + * @param endpt The endpoint. + * @param entry The timer entry. + * @param delay The relative delay of the timer. + * @return PJ_OK (zero) if successfull. + */ +PJ_DECL(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt, + pj_timer_entry *entry, + const pj_time_val *delay ); + +/** + * Cancel the previously registered timer. + * This function, like all other endpoint functions, is thread safe. + * + * @param endpt The endpoint. + * @param entry The timer entry previously registered. + */ +PJ_DECL(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, + pj_timer_entry *entry ); + + /** * Dump endpoint status to the log. This will print the status to the log * with log level 3. @@ -126,6 +154,35 @@ PJ_DECL(void) pjsip_endpt_handle_events( pjsip_endpoint *endpt, */ PJ_DECL(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail ); + +/** + * Register new module to the endpoint. + * The endpoint will then call the load and start function in the module to + * properly initialize the module, and assign a unique module ID for the + * module. + * + * @param endpt The endpoint. + * @param module The module to be registered. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt, + pjsip_module *module ); + +/** + * Unregister a module from the endpoint. + * The endpoint will then call the stop and unload function in the module to + * properly shutdown the module. + * + * @param endpt The endpoint. + * @param module The module to be registered. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt, + pjsip_module *module ); + + /** * Create pool from the endpoint. All SIP components should allocate their * memory pool by calling this function, to make sure that the pools are @@ -156,32 +213,6 @@ PJ_DECL(void) pjsip_endpt_destroy_pool( pjsip_endpoint *endpt, pj_pool_t *pool ); /** - * Schedule timer to endpoint's timer heap. Application must poll the endpoint - * periodically (by calling #pjsip_endpt_handle_events) to ensure that the - * timer events are handled in timely manner. When the timeout for the timer - * has elapsed, the callback specified in the entry argument will be called. - * This function, like all other endpoint functions, is thread safe. - * - * @param endpt The endpoint. - * @param entry The timer entry. - * @param delay The relative delay of the timer. - * @return PJ_OK (zero) if successfull. - */ -PJ_DECL(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt, - pj_timer_entry *entry, - const pj_time_val *delay ); - -/** - * Cancel the previously registered timer. - * This function, like all other endpoint functions, is thread safe. - * - * @param endpt The endpoint. - * @param entry The timer entry previously registered. - */ -PJ_DECL(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, - pj_timer_entry *entry ); - -/** * Create a new transaction. After creating the transaction, application MUST * initialize the transaction as either UAC or UAS (by calling * #pjsip_tsx_init_uac or #pjsip_tsx_init_uas), then must register the @@ -278,11 +309,12 @@ PJ_DECL(pj_ioqueue_t*) pjsip_endpt_get_ioqueue(pjsip_endpoint *endpt); * * @see pjsip_transport_get */ -PJ_DECL(pj_status_t) pjsip_endpt_alloc_transport( pjsip_endpoint *endpt, - pjsip_transport_type_e type, - const pj_sockaddr *remote, - int addr_len, - pjsip_transport **p_transport); +PJ_DECL(pj_status_t) +pjsip_endpt_acquire_transport( pjsip_endpoint *endpt, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + pjsip_transport **p_transport); /** * Get additional headers to be put in outgoing request message. diff --git a/pjsip/include/pjsip/sip_errno.h b/pjsip/include/pjsip/sip_errno.h index eefda673..4d50c75d 100644 --- a/pjsip/include/pjsip/sip_errno.h +++ b/pjsip/include/pjsip/sip_errno.h @@ -93,7 +93,7 @@ PJ_DECL(pj_str_t) pjsip_strerror( pj_status_t status, char *buffer, ***********************************************************/ /** * @hideinitializer - * Invalid message (syntax error) + * General invalid message error (e.g. syntax error) */ #define PJSIP_EINVALIDMSG (PJSIP_ERRNO_START_PJSIP + 20) /* 171020 */ /** @@ -113,19 +113,45 @@ PJ_DECL(pj_str_t) pjsip_strerror( pj_status_t status, char *buffer, #define PJSIP_EPARTIALMSG (PJSIP_ERRNO_START_PJSIP + 23) /* 171023 */ /** * @hideinitializer + * Missing Request-URI. + */ +#define PJSIP_EMISSINGREQURI (PJSIP_ERRNO_START_PJSIP + 24) /* 171024 */ +/** + * @hideinitializer * Missing required header(s). */ -#define PJSIP_EMISSINGHDR (PJSIP_ERRNO_START_PJSIP + 24) /* 171024 */ +#define PJSIP_EMISSINGHDR (PJSIP_ERRNO_START_PJSIP + 25) /* 171025 */ +/** + * @hideinitializer + * Missing message body. + */ +#define PJSIP_EMISSINGBODY (PJSIP_ERRNO_START_PJSIP + 26) /* 171026 */ /** * @hideinitializer * Invalid Via header in response (sent-by, etc). */ -#define PJSIP_EINVALIDVIA (PJSIP_ERRNO_START_PJSIP + 25) /* 171025 */ +#define PJSIP_EINVALIDVIA (PJSIP_ERRNO_START_PJSIP + 27) /* 171027 */ /** * @hideinitializer * Multiple Via headers in response. */ -#define PJSIP_EMULTIPLEVIA (PJSIP_ERRNO_START_PJSIP + 26) /* 171026 */ +#define PJSIP_EMULTIPLEVIA (PJSIP_ERRNO_START_PJSIP + 28) /* 171028 */ +/** + * @hideinitializer + * Invalid request URI. + */ +#define PJSIP_EINVALIDREQURI (PJSIP_ERRNO_START_PJSIP + 29) /* 171029 */ +/** + * @hideinitializer + * Expecting request message. + */ +#define PJSIP_ENOTREQUESTMSG (PJSIP_ERRNO_START_PJSIP + 30) /* 171030 */ +/** + * @hideinitializer + * Expecting response message. + */ +#define PJSIP_ENOTRESPONSEMSG (PJSIP_ERRNO_START_PJSIP + 31) /* 171031 */ + /************************************************************ * TRANSPORT ERRORS @@ -144,7 +170,13 @@ PJ_DECL(pj_str_t) pjsip_strerror( pj_status_t status, char *buffer, * @hideinitializer * Rx buffer overflow. See also PJSIP_EMSGTOOLONG. */ -#define PJSIP_ERXOVERFLOW (PJSIP_ERRNO_START_PJSIP + 42)/* 171042 */ +#define PJSIP_ERXOVERFLOW (PJSIP_ERRNO_START_PJSIP + 42) /* 171042 */ +/** + * @hideinitializer + * This is not really an error, it just informs application that + * transmit data has been deleted on return of pjsip_tx_data_dec_ref(). + */ +#define PJSIP_EBUFDESTROYED (PJSIP_ERRNO_START_PJSIP + 43) /* 171043 */ /************************************************************ diff --git a/pjsip/include/pjsip/sip_module.h b/pjsip/include/pjsip/sip_module.h index 345be88a..0f188194 100644 --- a/pjsip/include/pjsip/sip_module.h +++ b/pjsip/include/pjsip/sip_module.h @@ -41,28 +41,31 @@ PJ_BEGIN_DECL */ struct pjsip_module { + /** To allow chaining of modules in the endpoint. */ + PJ_DECL_LIST_MEMBER(struct pjsip_module); + /** * Module name. */ pj_str_t name; /** - * Flag to indicate the type of interfaces supported by the module. + * Module ID. */ - pj_uint32_t flag; + int id; /** * Integer number to identify module initialization and start order with * regard to other modules. Higher number will make the module gets * initialized later. */ - pj_uint32_t priority; + int priority; /** * Opaque data which can be used by a module to identify a resource within * the module itself. */ - void *mod_data; + void *user_data; /** * Number of methods supported by this module. @@ -78,22 +81,24 @@ struct pjsip_module * Pointer to function to be called to initialize the module. * * @param endpt The endpoint instance. - * @param mod The module. - * @param id The unique module ID assigned to this module. - * - * @return Module should return zero when initialization succeed. + * @return Module should return PJ_SUCCESS to indicate success. */ - pj_status_t (*init_module)(pjsip_endpoint *endpt, - struct pjsip_module *mod, pj_uint32_t id); + pj_status_t (*load)(pjsip_endpoint *endpt); /** * Pointer to function to be called to start the module. * - * @param mod The module. - * * @return Module should return zero to indicate success. */ - pj_status_t (*start_module)(struct pjsip_module *mod); + pj_status_t (*start)(void); + + /** + * Pointer to function to be called to deinitialize the module before + * it is unloaded. + * + * @return Module should return PJ_SUCCESS to indicate success. + */ + pj_status_t (*stop)(void); /** * Pointer to function to be called to deinitialize the module before @@ -101,34 +106,55 @@ struct pjsip_module * * @param mod The module. * - * @return Module should return zero to indicate success. + * @return Module should return PJ_SUCCESS to indicate success. */ - pj_status_t (*deinit_module)(struct pjsip_module *mod); + pj_status_t (*unload)(void); /** - * Pointer to function to receive transaction related events. - * If the module doesn't wish to receive such notification, this member - * must be set to NULL. + * Called to process incoming request. * - * @param mod The module. - * @param event The transaction event. + * @param rdata The incoming message. + * + * @return Module should return PJ_TRUE if it handles the request, + * or otherwise it should return PJ_FALSE to allow other + * modules to handle the request. */ - void (*tsx_handler)(struct pjsip_module *mod, pjsip_event *event); + pj_bool_t (*on_rx_request)(pjsip_rx_data *rdata); + + /** + * Called to processed incoming response. + * + * @param rdata The incoming message. + * + * @return Module should return PJ_TRUE if it handles the + * response, or otherwise it should return PJ_FALSE to + * allow other modules to handle the response. + */ + pj_bool_t (*on_rx_response)(pjsip_rx_data *rdata); + + /** + * Called when this module is acting as transaction user for the specified + * transaction, when the transaction's state has changed. + * + * @param tsx The transaction. + * @param event The event which has caused the transaction state + * to change. + */ + void (*on_tsx_state)(pjsip_transaction *tsx, pjsip_event *event); + }; /** - * Prototype of function to register static modules (eg modules that are - * linked staticly with the application). This function must be implemented - * by any applications that use PJSIP library. - * - * @param count [input/output] On input, it contains the maximum number of - * elements in the array. On output, the function fills with - * the number of modules to be registered. - * @param modules [output] array of pointer to modules to be registered. + * Module priority guidelines. */ -pj_status_t register_static_modules( pj_size_t *count, - pjsip_module **modules ); +enum pjsip_module_priority +{ + PJSIP_MOD_PRIORITY_TSX_LAYER = 4, + PJSIP_MOD_PRIORITY_UA_PROXY_LAYER = 16, + PJSIP_MOD_PRIORITY_APPLICATION = 32, +}; + /** * @} diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index 836ce37b..e1866048 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -52,27 +52,14 @@ PJ_BEGIN_DECL */ typedef enum pjsip_method_e { - /** INVITE method, for establishing dialogs. */ - PJSIP_INVITE_METHOD, + PJSIP_INVITE_METHOD, /**< INVITE method, for establishing dialogs. */ + PJSIP_CANCEL_METHOD, /**< CANCEL method, for cancelling request. */ + PJSIP_ACK_METHOD, /**< ACK method. */ + PJSIP_BYE_METHOD, /**< BYE method, for terminating dialog. */ + PJSIP_REGISTER_METHOD, /**< REGISTER method. */ + PJSIP_OPTIONS_METHOD, /**< OPTIONS method. */ - /** CANCEL method, for cancelling request. */ - PJSIP_CANCEL_METHOD, - - /** ACK method, for acknowledging final response to INVITE. */ - PJSIP_ACK_METHOD, - - /** BYE method, for terminating dialog. */ - PJSIP_BYE_METHOD, - - /** REGISTER method. */ - PJSIP_REGISTER_METHOD, - - /** OPTIONS method, for querying remote capabilities. */ - PJSIP_OPTIONS_METHOD, - - /** Other method, which means that the method name itself will be stored - elsewhere. */ - PJSIP_OTHER_METHOD, + PJSIP_OTHER_METHOD, /**< Other method. */ } pjsip_method_e; @@ -93,6 +80,17 @@ typedef struct pjsip_method } pjsip_method; +/* + * For convenience, standard method structures are defined in the library. + */ +extern const pjsip_method pjsip_invite_method; /**< INVITE structure. */ +extern const pjsip_method pjsip_cancel_method; /**< CANCEL structure. */ +extern const pjsip_method pjsip_ack_method; /**< ACK structure. */ +extern const pjsip_method pjsip_bye_method; /**< BYE structure. */ +extern const pjsip_method pjsip_register_method; /**< REGISTER structure.*/ +extern const pjsip_method pjsip_options_method; /**< OPTIONS structure. */ + + /** * Initialize the method structure from a string. * This function will check whether the method is a known method then set @@ -638,8 +636,8 @@ PJ_DECL(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type); * @return The header field, or NULL if no header with the specified * type is found. */ -PJ_DECL(void*) pjsip_msg_find_hdr( pjsip_msg *msg, - pjsip_hdr_e type, void *start); +PJ_DECL(void*) pjsip_msg_find_hdr( const pjsip_msg *msg, + pjsip_hdr_e type, const void *start); /** * Find a header in the message by its name. @@ -654,8 +652,9 @@ PJ_DECL(void*) pjsip_msg_find_hdr( pjsip_msg *msg, * @return The header field, or NULL if no header with the specified * type is found. */ -PJ_DECL(void*) pjsip_msg_find_hdr_by_name( pjsip_msg *msg, - const pj_str_t *name, void *start); +PJ_DECL(void*) pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, + const pj_str_t *name, + const void *start); /** * Find and remove a header in the message. @@ -702,7 +701,8 @@ PJ_IDECL(void) pjsip_msg_insert_first_hdr( pjsip_msg *msg, pjsip_hdr *hdr ); * @return The length of the printed characters (in bytes), or NEGATIVE * value if the message is too large for the specified buffer. */ -PJ_DECL(pj_ssize_t) pjsip_msg_print(pjsip_msg *msg, char *buf, pj_size_t size); +PJ_DECL(pj_ssize_t) pjsip_msg_print(const pjsip_msg *msg, + char *buf, pj_size_t size); /** * @} diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h index 14175dc7..6e6ba428 100644 --- a/pjsip/include/pjsip/sip_transport.h +++ b/pjsip/include/pjsip/sip_transport.h @@ -367,16 +367,21 @@ PJ_DECL(void) pjsip_tx_data_add_ref( pjsip_tx_data *tdata ); /** * Decrement reference counter of the transmit buffer. - * When the transmit buffer is no longer used, it will be destroyed. + * When the transmit buffer is no longer used, it will be destroyed and + * caller is informed with PJSIP_EBUFDESTROYED return status. * * @param tdata The transmit buffer data. + * @return This function will always succeeded eventhough the return + * status is non-zero. A status PJSIP_EBUFDESTROYED will be + * returned to inform that buffer is destroyed. */ -PJ_DECL(void) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ); +PJ_DECL(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ); /** * Check if transmit data buffer contains a valid message. * * @param tdata The transmit buffer. + * @return Non-zero (PJ_TRUE) if buffer contains a valid message. */ PJ_DECL(pj_bool_t) pjsip_tx_data_is_valid( pjsip_tx_data *tdata ); @@ -542,11 +547,14 @@ struct pjsip_tpfactory /** * Create new outbound connection. + * Note that the factory is responsible for both creating the + * transport and registering it to the transport manager. */ pj_status_t (*create_transport)(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt, const pj_sockaddr *rem_addr, + int addr_len, pjsip_transport **transport); /* @@ -621,11 +629,11 @@ PJ_DECL(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr); * Find transport to be used to send message to remote destination. If no * suitable transport is found, a new one will be created. */ -PJ_DECL(pj_status_t) pjsip_tpmgr_alloc_transport( pjsip_tpmgr *mgr, - pjsip_transport_type_e type, - const pj_sockaddr_t *remote, - int addr_len, - pjsip_transport **p_transport ); +PJ_DECL(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + pjsip_transport **tp); /** diff --git a/pjsip/include/pjsip/sip_transport_udp.h b/pjsip/include/pjsip/sip_transport_udp.h index d3fd5d30..886f18c6 100644 --- a/pjsip/include/pjsip/sip_transport_udp.h +++ b/pjsip/include/pjsip/sip_transport_udp.h @@ -21,7 +21,7 @@ #include <pjsip/sip_transport.h> -PJ_DECL +PJ_BEGIN_DECL /** * Start UDP transport. diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h index f55cd7e9..ea0fcf3f 100644 --- a/pjsip/include/pjsip/sip_util.h +++ b/pjsip/include/pjsip/sip_util.h @@ -20,6 +20,7 @@ #define __PJSIP_SIP_MISC_H__ #include <pjsip/sip_msg.h> +#include <pjsip/sip_resolve.h> PJ_BEGIN_DECL @@ -34,6 +35,15 @@ PJ_BEGIN_DECL * request outside a dialog, such as OPTIONS, MESSAGE, etc. To create a request * inside a dialog, application should use #pjsip_dlg_create_request. * + * This function adds the following headers in the request: + * - From, To, Call-ID, and CSeq, + * - Contact header, if contact is specified. + * - A blank Via header. + * - Additional request headers (such as Max-Forwards) which are copied + * from endpoint configuration. + * + * In addition, the function adds a unique tag in the From header. + * * Once a transmit data is created, the reference counter is initialized to 1. * * @param endpt Endpoint instance. @@ -62,7 +72,16 @@ PJ_DECL(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, /** * Create an independent request message from the specified headers. This - * function will shallow clone the headers and put them in the request. + * function will clone the headers and put them in the request. + * + * This function adds the following headers in the request: + * - From, To, Call-ID, and CSeq, + * - Contact header, if contact is specified. + * - A blank Via header. + * - Additional request headers (such as Max-Forwards) which are copied + * from endpoint configuration. + * + * In addition, the function adds a unique tag in the From header. * * Once a transmit data is created, the reference counter is initialized to 1. * @@ -92,31 +111,6 @@ pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt, pjsip_tx_data **p_tdata); /** - * Send outgoing request and initiate UAC transaction for the request. - * This is an auxiliary function to be used by application to send arbitrary - * requests outside a dialog. To send a request within a dialog, application - * should use #pjsip_dlg_send_msg instead. - * - * @param endpt The endpoint instance. - * @param tdata The transmit data to be sent. - * @param timeout Optional timeout for final response to be received, or -1 - * if the transaction should not have a timeout restriction. - * @param token Optional token to be associated with the transaction, and - * to be passed to the callback. - * @param cb Optional callback to be called when the transaction has - * received a final response. The callback will be called with - * the previously registered token and the event that triggers - * the completion of the transaction. - * - * @return PJ_SUCCESS, or the appropriate error code. - */ -PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, - pjsip_tx_data *tdata, - int timeout, - void *token, - void (*cb)(void*,pjsip_event*)); - -/** * Construct a minimal response message for the received request. This function * will construct all the Via, Record-Route, Call-ID, From, To, CSeq, and * Call-ID headers from the request. @@ -125,14 +119,16 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, * * @param endpt The endpoint. * @param rdata The request receive data. - * @param code Status code to be put in the response. + * @param st_code Status code to be put in the response. + * @param st_text Optional status text, or NULL to get the default text. * @param p_tdata Pointer to receive the transmit data. * * @return PJ_SUCCESS, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, const pjsip_rx_data *rdata, - int code, + int st_code, + const pj_str_t *st_text, pjsip_tx_data **p_tdata); /** @@ -143,13 +139,16 @@ PJ_DECL(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, * this one. * * @param endpt The endpoint. - * @param tdata On input, this contains the original INVITE request, and on - * output, it contains the ACK message. - * @param rdata The final response message. + * @param tdata This contains the original INVITE request + * @param rdata The final response. + * @param ack The ACK request created. + * + * @return PJ_SUCCESS, or the appropriate error code. */ -PJ_DECL(void) pjsip_endpt_create_ack( pjsip_endpoint *endpt, - pjsip_tx_data *tdata, - const pjsip_rx_data *rdata ); +PJ_DECL(pj_status_t) pjsip_endpt_create_ack( pjsip_endpoint *endpt, + const pjsip_tx_data *tdata, + const pjsip_rx_data *rdata, + pjsip_tx_data **ack); /** @@ -162,25 +161,197 @@ PJ_DECL(void) pjsip_endpt_create_ack( pjsip_endpoint *endpt, * @return PJ_SUCCESS, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt, - pjsip_tx_data *tdata, + const pjsip_tx_data *tdata, pjsip_tx_data **p_tdata); /** - * Get the address parameters (host, port, flag, TTL, etc) to send the - * response. + * Find which destination to be used to send the request message, based + * on the request URI and Route headers in the message. The procedure + * used here follows the guidelines on sending the request in RFC 3261 + * chapter 8.1.2. + * + * This function may modify the message (request line and Route headers), + * if the Route information specifies strict routing and the request + * URI in the message is different than the calculated target URI. In that + * case, the target URI will be put as the request URI of the request and + * current request URI will be put as the last entry of the Route headers. + * + * @param tdata The transmit data containing the request message. + * @param dest_info On return, it contains information about destination + * host to contact, along with the preferable transport + * type, if any. Caller will then normally proceed with + * resolving this host with server resolution procedure + * described in RFC 3263. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_get_request_addr( pjsip_tx_data *tdata, + pjsip_host_info *dest_info ); + + +/** + * This structure holds the state of outgoing stateless request. + */ +typedef struct pjsip_send_state +{ + /** Application token, which was specified when the function + * #pjsip_endpt_send_request_stateless() is called. + */ + void *token; + + /** Endpoint instance. + */ + pjsip_endpoint *endpt; + + /** Transmit data buffer being sent. + */ + pjsip_tx_data *tdata; + + /** Server addresses resolved. + */ + pjsip_server_addresses addr; + + /** Current server address being tried. + */ + unsigned cur_addr; + + /** Current transport being used. + */ + pjsip_transport *cur_transport; + + /** The application callback which was specified when the function + * #pjsip_endpt_send_request_stateless() was called. + */ + void (*app_cb)(struct pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont); +} pjsip_send_state; + +/** + * Send outgoing request statelessly The function will take care of which + * destination and transport to use based on the information in the message, + * taking care of URI in the request line and Route header. + * + * This function is different than #pjsip_transport_send() in that this + * function adds/modify the Via header as necessary. + * + * @param endpt The endpoint instance. + * @param tdata The transmit data to be sent. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pjsip_endpt_send_request_stateless( pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + void *token, + void (*cb)(pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont)); + +/** + * This structure describes destination information to send response. + * It is initialized by calling #pjsip_get_response_addr(). + * + * If the response message should be sent using transport from which + * the request was received, then transport, addr, and addr_len fields + * are initialized. + * + * The dst_host field is also initialized. It should be used when server + * fails to send the response using the transport from which the request + * was received, or when the transport is NULL, which means server + * must send the response to this address (this situation occurs when + * maddr parameter is set, or when rport param is not set in the request). + */ +typedef struct pjsip_response_addr +{ + pjsip_transport *transport; /**< Immediate transport to be used. */ + pj_sockaddr addr; /**< Immediate address to send to. */ + int addr_len; /**< Address length. */ + pjsip_host_info dst_host; /**< Destination host to contact. */ +} pjsip_response_addr; + +/** + * Determine which address (and transport) to use to send response message + * based on the received request. This function follows the specification + * in section 18.2.2 of RFC 3261 and RFC 3581 for calculating the destination + * address and transport. + * + * The information about destination to send the response will be returned + * in res_addr argument. Please see #pjsip_response_addr for more info. * * @param pool The pool. - * @param tr The transport where the request was received. - * @param via The top-most Via header of the request. - * @param addr The send address concluded from the calculation. + * @param rdata The incoming request received by the server. + * @param res_addr On return, it will be initialized with information about + * destination address and transport to send the response. * * @return zero (PJ_OK) if successfull. */ PJ_DECL(pj_status_t) pjsip_get_response_addr(pj_pool_t *pool, - const pjsip_transport *tr, - const pjsip_via_hdr *via, - pjsip_host_info *addr); + pjsip_rx_data *rdata, + pjsip_response_addr *res_addr); + +/** + * Send response in tdata statelessly. The function will take care of which + * response destination and transport to use based on the information in the + * Via header (such as the presence of rport, symmetric transport, etc.). + * + * This function will create a new ephemeral transport if no existing + * transports can be used to send the message to the destination. The ephemeral + * transport will be destroyed after some period if it is not used to send any + * more messages. + * + * The behavior of this function complies with section 18.2.2 of RFC 3261 + * and RFC 3581. + * + * @param endpt The endpoint instance. + * @param res_addr The information about the address and transport to send + * the response to. Application can get this information + * by calling #pjsip_get_response_addr(). + * @param tdata The response message to be sent. + * @param token Token to be passed back when the callback is called. + * @param cb Optional callback to notify the transmission status + * to application, and to inform whether next address or + * transport will be tried. + * + * @return PJ_SUCCESS if response has been successfully created and + * sent to transport layer, or a non-zero error code. + * However, even when it returns PJ_SUCCESS, there is no + * guarantee that the response has been successfully sent. + */ +PJ_DECL(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, + pjsip_response_addr *res_addr, + pjsip_tx_data *tdata, + void *token, + void (*cb)(pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont)); + +/** + * Send outgoing request and initiate UAC transaction for the request. + * This is an auxiliary function to be used by application to send arbitrary + * requests outside a dialog. To send a request within a dialog, application + * should use #pjsip_dlg_send_msg instead. + * + * @param endpt The endpoint instance. + * @param tdata The transmit data to be sent. + * @param timeout Optional timeout for final response to be received, or -1 + * if the transaction should not have a timeout restriction. + * @param token Optional token to be associated with the transaction, and + * to be passed to the callback. + * @param cb Optional callback to be called when the transaction has + * received a final response. The callback will be called with + * the previously registered token and the event that triggers + * the completion of the transaction. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + int timeout, + void *token, + void (*cb)(void*,pjsip_event*)); + /** * @} diff --git a/pjsip/include/pjsip_core.h b/pjsip/include/pjsip_core.h index 8ae286a5..9f3c7e9b 100644 --- a/pjsip/include/pjsip_core.h +++ b/pjsip/include/pjsip_core.h @@ -31,6 +31,7 @@ #include <pjsip/sip_tel_uri.h> #include <pjsip/sip_transaction.h> #include <pjsip/sip_transport.h> +#include <pjsip/sip_transport_udp.h> #include <pjsip/sip_uri.h> #include <pjsip/sip_util.h> diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index b3fa2f8c..61741cb1 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -56,12 +56,6 @@ struct pjsip_endpoint /** Name. */ pj_str_t name; - /** Transaction table. */ - pj_hash_table_t *tsx_table; - - /** Mutex for transaction table. */ - pj_mutex_t *tsx_table_mutex; - /** Timer heap. */ pj_timer_heap_t *timer_heap; @@ -74,12 +68,15 @@ struct pjsip_endpoint /** DNS Resolver. */ pjsip_resolver_t *resolver; - /** Number of modules registered. */ - pj_uint32_t mod_count; + /** Modules lock. */ + pj_rwmutex_t *mod_mutex; /** Modules. */ pjsip_module *modules[PJSIP_MAX_MODULE]; + /** Module list, sorted by priority. */ + pjsip_module module_list; + /** Number of supported methods. */ unsigned method_cnt; @@ -127,167 +124,119 @@ static void pool_callback( pj_pool_t *pool, pj_size_t size ) /* - * Initialize modules. + * Register new module to the endpoint. + * The endpoint will then call the load and start function in the module to + * properly initialize the module, and assign a unique module ID for the + * module. */ -static pj_status_t init_modules( pjsip_endpoint *endpt ) +PJ_DEF(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt, + pjsip_module *mod ) { - pj_status_t status; - unsigned i; - //pj_str_t str_COMMA = { ", ", 2 }; - extern pjsip_module aux_tsx_module; - - PJ_LOG(5, (THIS_FILE, "init_modules()")); - - /* Load static modules. */ - endpt->mod_count = PJSIP_MAX_MODULE; - status = register_static_modules( &endpt->mod_count, endpt->modules ); - if (status != 0) { - return status; - } + pj_status_t status = PJ_SUCCESS; + pjsip_module *m; + int i; - /* Add mini aux module. */ - endpt->modules[endpt->mod_count++] = &aux_tsx_module; + pj_rwmutex_lock_write(endpt->mod_mutex); - /* Load dynamic modules. */ - // Not supported yet! + /* Make sure that this module has not been registered. */ + PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == NULL, + {status = PJ_EEXISTS; goto on_return;}); - /* Sort modules on the priority. */ - for (i=endpt->mod_count-1; i>0; --i) { - pj_uint32_t max = 0; - unsigned j; - for (j=1; j<=i; ++j) { - if (endpt->modules[j]->priority > endpt->modules[max]->priority) - max = j; - } - if (max != i) { - pjsip_module *temp = endpt->modules[max]; - endpt->modules[max] = endpt->modules[i]; - endpt->modules[i] = temp; - } + /* Find unused ID for this module. */ + for (i=0; i<PJ_ARRAY_SIZE(endpt->modules); ++i) { + if (endpt->modules[i] == NULL) + break; + } + if (i == PJ_ARRAY_SIZE(endpt->modules)) { + pj_assert(!"Too many modules registered!"); + status = PJ_ETOOMANY; + goto on_return; } - /* Initialize each module. */ - for (i=0; i < endpt->mod_count; ++i) { - int j; - - pjsip_module *mod = endpt->modules[i]; - if (mod->init_module) { - status = mod->init_module(endpt, mod, i); - if (status != 0) { - return status; - } - } + /* Assign the ID. */ + mod->id = i; - /* Collect all supported methods from modules. */ - for (j=0; j<mod->method_cnt; ++j) { - unsigned k; - for (k=0; k<endpt->method_cnt; ++k) { - if (pjsip_method_cmp(mod->methods[j], endpt->methods[k]) == 0) - break; - } - if (k == endpt->method_cnt) { - if (endpt->method_cnt < MAX_METHODS) { - endpt->methods[endpt->method_cnt++] = mod->methods[j]; - } else { - PJ_LOG(1,(THIS_FILE, "Too many methods")); - return -1; - } - } - } + /* Try to load the module. */ + if (mod->load) { + status = (*mod->load)(endpt); + if (status != PJ_SUCCESS) + goto on_return; } - /* Create Allow header. */ - endpt->allow_hdr = pjsip_allow_hdr_create( endpt->pool ); - endpt->allow_hdr->count = endpt->method_cnt; - for (i=0; i<endpt->method_cnt; ++i) { - endpt->allow_hdr->values[i] = endpt->methods[i]->name; + /* Try to start the module. */ + if (mod->start) { + status = (*mod->start)(); + if (status != PJ_SUCCESS) + goto on_return; } - /* Start each module. */ - for (i=0; i < endpt->mod_count; ++i) { - pjsip_module *mod = endpt->modules[i]; - if (mod->start_module) { - status = mod->start_module(mod); - if (status != 0) { - return status; - } - } + /* Save the module. */ + endpt->modules[i] = mod; + + /* Put in the module list, sorted by priority. */ + m = endpt->module_list.next; + while (m != &endpt->module_list) { + if (m->priority > mod->priority) + break; + m = m->next; } + pj_list_insert_before(m, mod); /* Done. */ - return 0; + PJ_TODO(BUILD_ALLOW_HEADER_BASED_ON_MODULES_SUPPORTED_METHODS); + +on_return: + pj_rwmutex_unlock_write(endpt->mod_mutex); + return status; } /* - * Unregister the transaction from the hash table, and destroy the resources - * from the transaction. + * Unregister a module from the endpoint. + * The endpoint will then call the stop and unload function in the module to + * properly shutdown the module. */ -PJ_DEF(void) pjsip_endpt_destroy_tsx( pjsip_endpoint *endpt, - pjsip_transaction *tsx) +PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt, + pjsip_module *mod ) { - 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 ); + pj_status_t status; - /* Unregister from the hash table. */ - pj_hash_set( NULL, endpt->tsx_table, tsx->transaction_key.ptr, - tsx->transaction_key.slen, NULL); + pj_rwmutex_lock_write(endpt->mod_mutex); - /* Unlock endpoint mutex. */ - pj_mutex_unlock( endpt->tsx_table_mutex ); + /* Make sure the module exists in the list. */ + PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == mod, + {status = PJ_ENOTFOUND;goto on_return;} ); - /* Destroy transaction mutex. */ - pj_mutex_destroy( tsx->mutex ); + /* Make sure the module exists in the array. */ + PJ_ASSERT_ON_FAIL( mod->id>=0 && mod->id<PJ_ARRAY_SIZE(endpt->modules) && + endpt->modules[mod->id] == mod, + {status = PJ_ENOTFOUND; goto on_return;}); - /* Release the pool for the transaction. */ - pj_pool_release(tsx->pool); + /* Try to stop the module. */ + if (mod->stop) { + status = (*mod->stop)(); + if (status != PJ_SUCCESS) goto on_return; + } - PJ_LOG(4, (THIS_FILE, "tsx%p destroyed", tsx)); -} + /* Try to unload the module. */ + if (mod->unload) { + status = (*mod->unload)(); + if (status != PJ_SUCCESS) goto on_return; + } + /* Remove module from array. */ + endpt->modules[mod->id] = NULL; -/* - * Receive transaction events from transactions and dispatch them to the - * modules. - */ -static void endpt_do_event( pjsip_endpoint *endpt, pjsip_event *evt) -{ - unsigned i; + /* Remove module from list. */ + pj_list_erase(mod); - /* Dispatch event to modules. */ - for (i=0; i<endpt->mod_count; ++i) { - pjsip_module *mod = endpt->modules[i]; - if (mod && mod->tsx_handler) { - mod->tsx_handler( mod, evt ); - } - } + /* Done. */ + status = PJ_SUCCESS; - /* Destroy transaction if it is terminated. */ - if (evt->type == PJSIP_EVENT_TSX_STATE && - evt->body.tsx_state.tsx->state == PJSIP_TSX_STATE_DESTROYED) - { - /* No need to lock mutex. Mutex is locked inside the destroy function */ - pjsip_endpt_destroy_tsx( endpt, evt->body.tsx_state.tsx ); - } -} + PJ_TODO(REMOVE_METHODS_FROM_ALLOW_HEADER_WHEN_MODULE_IS_UNREGISTERED); -/* - * Receive transaction events from transactions and put in the event queue - * to be processed later. - */ -void pjsip_endpt_send_tsx_event( pjsip_endpoint *endpt, pjsip_event *evt ) -{ - // Need to protect this with try/catch? - endpt_do_event(endpt, evt); +on_return: + pj_rwmutex_unlock_write(endpt->mod_mutex); + return status; } /* @@ -375,10 +324,18 @@ PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf, return PJ_ENOMEM; /* Create endpoint. */ - endpt = pj_pool_calloc(pool, 1, sizeof(*endpt)); + endpt = pj_pool_zalloc(pool, sizeof(*endpt)); endpt->pool = pool; endpt->pf = pf; + /* Init modules list. */ + pj_list_init(&endpt->module_list); + + /* Create R/W mutex for module manipulation. */ + status = pj_rwmutex_create(endpt->pool, "ept%p", &endpt->mod_mutex); + if (status != PJ_SUCCESS) + goto on_error; + /* Init parser. */ init_sip_parser(); @@ -399,20 +356,6 @@ PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf, goto on_error; } - /* Create mutex for the transaction table. */ - status = pj_mutex_create_recursive( endpt->pool, "mtbl%p", - &endpt->tsx_table_mutex); - if (status != PJ_SUCCESS) { - goto on_error; - } - - /* 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 timer heap to manage all timers within this endpoint. */ status = pj_timer_heap_create( endpt->pool, PJSIP_MAX_TIMER_COUNT, &endpt->timer_heap); @@ -452,13 +395,6 @@ PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf, goto on_error; } - /* 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); - /* Initialize request headers. */ pj_list_init(&endpt->req_hdr); @@ -470,13 +406,6 @@ PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf, mf_hdr->ivalue = PJSIP_MAX_FORWARDS_VALUE; pj_list_insert_before( &endpt->req_hdr, mf_hdr); - /* Load and init modules. */ - status = init_modules(endpt); - if (status != PJ_SUCCESS) { - PJ_LOG(4, (THIS_FILE, "pjsip_endpt_init(): error in init_modules()")); - return status; - } - /* Done. */ *p_endpt = endpt; return status; @@ -490,9 +419,9 @@ on_error: pj_mutex_destroy(endpt->mutex); endpt->mutex = NULL; } - if (endpt->tsx_table_mutex) { - pj_mutex_destroy(endpt->tsx_table_mutex); - endpt->tsx_table_mutex = NULL; + if (endpt->mod_mutex) { + pj_rwmutex_destroy(endpt->mod_mutex); + endpt->mod_mutex = NULL; } pj_pool_release( endpt->pool ); @@ -513,9 +442,6 @@ PJ_DEF(void) pjsip_endpt_destroy(pjsip_endpoint *endpt) /* Delete endpoint mutex. */ pj_mutex_destroy(endpt->mutex); - /* Delete transaction table mutex. */ - pj_mutex_destroy(endpt->tsx_table_mutex); - /* Finally destroy pool. */ pj_pool_release(endpt->pool); } @@ -624,93 +550,6 @@ PJ_DEF(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, } /* - * 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. */ @@ -719,19 +558,17 @@ static void endpt_transport_callback( pjsip_endpoint *endpt, pjsip_rx_data *rdata ) { 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" + "Error processing packet from %s:%d, packet:--\n" "%s\n" - "-- end of packet. Error", - src_addr, port, rdata->msg_info.msg_buf)); + "-- end of packet.", + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + rdata->msg_info.msg_buf)); return; } @@ -740,7 +577,7 @@ static void endpt_transport_callback( pjsip_endpoint *endpt, * Ref: RFC3261 Section 18.1.2 Receiving Response */ if (msg->type == PJSIP_RESPONSE_MSG) { - const pj_str_t *addr_addr; + const pj_str_t *local_addr; int port = rdata->msg_info.via->sent_by.port; pj_bool_t mismatch = PJ_FALSE; if (port == 0) { @@ -748,8 +585,8 @@ static void endpt_transport_callback( pjsip_endpoint *endpt, 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) + local_addr = &rdata->tp_info.transport->local_name.host; + if (pj_strcmp(&rdata->msg_info.via->sent_by.host, local_addr) != 0) mismatch = PJ_TRUE; else if (port != rdata->tp_info.transport->local_name.port) { /* Port or address mismatch, we should discard response */ @@ -759,186 +596,71 @@ static void endpt_transport_callback( pjsip_endpoint *endpt, * 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) + 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)); + PJ_LOG(4,(THIS_FILE, "Response %p from %s has mismatch port in " + "sent-by but the rport parameter is " + "correct", + rdata, rdata->pkt_info.src_name)); } } if (mismatch) { - pjsip_event e; - - PJSIP_EVENT_INIT_DISCARD_MSG(e, rdata, PJSIP_EINVALIDVIA); - endpt_do_event( endpt, &e ); + PJ_TODO(ENDPT_REPORT_WHEN_DROPPING_MESSAGE); + PJ_LOG(4,(THIS_FILE, "Dropping response from %s:%d because sent-by" + " is mismatch", + rdata->pkt_info.src_name, + rdata->pkt_info.src_port)); 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; - } + /* Distribute to modules. */ + pj_rwmutex_lock_read(endpt->mod_mutex); - /* Initialize transaction as UAS. */ - pjsip_tsx_init_uas( tsx, rdata ); + if (msg->type == PJSIP_REQUEST_MSG) { + pjsip_module *mod; + pj_bool_t handled = PJ_FALSE; - /* Register transaction, mutex is locked there. */ - pjsip_endpt_register_tsx( endpt, tsx ); - - a_new_transaction_just_been_created = PJ_TRUE; + mod = endpt->module_list.next; + while (mod != &endpt->module_list) { + if (mod->on_rx_request) + handled = (*mod->on_rx_request)(rdata); + if (handled) + break; + mod = mod->next; } - } - /* 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 ); - } + /* No module is able to handle the request. */ + if (!handled) { + PJ_TODO(ENDPT_RESPOND_UNHANDLED_REQUEST); + PJ_LOG(4,(THIS_FILE, "Request from %s:%d was dropped/unhandled by" + " any modules")); + } - /* - * 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; i<PJSIP_MAX_MODULE; ++i) { - if (tsx->module_data[i] != NULL) { + } else { + pjsip_module *mod; + pj_bool_t handled = PJ_FALSE; + + mod = endpt->module_list.next; + while (mod != &endpt->module_list) { + if (mod->on_rx_response) + handled = (*mod->on_rx_response)(rdata); + if (handled) break; - } + mod = mod->next; } - 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); + if (!handled) { + PJ_LOG(4,(THIS_FILE, "Response from %s:%d was dropped/unhandled by" + " any modules")); } } + + pj_rwmutex_unlock_read(endpt->mod_mutex); } /* @@ -983,15 +705,15 @@ PJ_DEF(pj_ioqueue_t*) pjsip_endpt_get_ioqueue(pjsip_endpoint *endpt) /* * Find/create transport. */ -PJ_DEF(pj_status_t) pjsip_endpt_alloc_transport( pjsip_endpoint *endpt, +PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt, pjsip_transport_type_e type, - const pj_sockaddr *remote, + const pj_sockaddr_t *remote, int addr_len, - pjsip_transport **p_transport) + pjsip_transport **transport) { - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_alloc_transport()")); - return pjsip_tpmgr_alloc_transport( endpt->transport_mgr, type, - remote, addr_len, p_transport); + PJ_LOG(5, (THIS_FILE, "pjsip_endpt_acquire_transport()")); + return pjsip_tpmgr_acquire_transport(endpt->transport_mgr, type, + remote, addr_len, transport); } @@ -1049,8 +771,6 @@ PJ_DEF(void) pjsip_endpt_log_error( pjsip_endpoint *endpt, PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail ) { #if PJ_LOG_MAX_LEVEL >= 3 - unsigned count; - PJ_LOG(5, (THIS_FILE, "pjsip_endpt_dump()")); /* Lock mutex. */ @@ -1066,53 +786,6 @@ PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail ) pj_pool_get_capacity(endpt->pool), pj_pool_get_used_size(endpt->pool))); - /* 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); - } - } - /* Transports. */ pjsip_tpmgr_dump_transports( endpt->transport_mgr ); diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c index 999156c6..f3c7319b 100644 --- a/pjsip/src/pjsip/sip_errno.c +++ b/pjsip/src/pjsip/sip_errno.c @@ -39,14 +39,20 @@ static const struct { PJSIP_EINVALIDSCHEME, "Invalid URI scheme" }, { PJSIP_EMSGTOOLONG, "Message too long" }, { PJSIP_EPARTIALMSG, "Partial message" }, + { PJSIP_EMISSINGREQURI, "Missing Request-URI" }, { PJSIP_EMISSINGHDR, "Missing required header(s)" }, + { PJSIP_EMISSINGBODY, "Missing message body" }, { PJSIP_EINVALIDVIA, "Invalid Via header" }, { PJSIP_EMULTIPLEVIA, "Multiple Via headers in response" }, + { PJSIP_EINVALIDREQURI, "Invalid Request URI" }, + { PJSIP_ENOTREQUESTMSG, "Expecting request message"}, + { PJSIP_ENOTRESPONSEMSG, "Expecting response message"}, /* Transport errors */ { PJSIP_EUNSUPTRANSPORT, "Unsupported transport"}, { PJSIP_EPENDINGTX, "Transmit buffer already pending"}, { PJSIP_ERXOVERFLOW, "Rx buffer overflow"}, + { PJSIP_EBUFDESTROYED, "Buffer destroyed"}, /* Transaction errors */ { PJSIP_ETSXDESTROYED, "Transaction has been destroyed"}, diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 2dc83dda..1d5ffe06 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -21,6 +21,7 @@ #include <pjsip/print_util.h> #include <pj/string.h> #include <pj/pool.h> +#include <pj/assert.h> /* * Include inline definitions here if functions are NOT inlined. @@ -29,14 +30,22 @@ # include <pjsip/sip_msg_i.h> #endif -static const pj_str_t method_names[] = -{ - { "INVITE", 6 }, - { "CANCEL", 6 }, - { "ACK", 3 }, - { "BYE", 3 }, - { "REGISTER", 8 }, - { "OPTIONS", 7 }, +const pjsip_method + pjsip_invite_method = { PJSIP_INVITE_METHOD, { "INVITE",6 } }, + pjsip_cancel_method = { PJSIP_CANCEL_METHOD, { "CANCEL",6 } }, + pjsip_ack_method = { PJSIP_ACK_METHOD, { "ACK",3} }, + pjsip_bye_method = { PJSIP_BYE_METHOD, { "BYE",3} }, + pjsip_register_method = { PJSIP_REGISTER_METHOD, { "REGISTER",8} }, + pjsip_options_method = { PJSIP_OPTIONS_METHOD, { "OPTIONS",7} }; + +static const pj_str_t *method_names[] = +{ + &pjsip_invite_method.name, + &pjsip_cancel_method.name, + &pjsip_ack_method.name, + &pjsip_bye_method.name, + &pjsip_register_method.name, + &pjsip_options_method.name }; const pj_str_t pjsip_hdr_names[] = @@ -178,8 +187,9 @@ PJ_DEF(void) pjsip_method_init( pjsip_method *m, PJ_DEF(void) pjsip_method_set( pjsip_method *m, pjsip_method_e me ) { + pj_assert(me < PJSIP_OTHER_METHOD); m->id = me; - m->name = method_names[me]; + m->name = *method_names[me]; } PJ_DEF(void) pjsip_method_init_np(pjsip_method *m, @@ -187,9 +197,9 @@ PJ_DEF(void) pjsip_method_init_np(pjsip_method *m, { int i; for (i=0; i<PJ_ARRAY_SIZE(method_names); ++i) { - if (pj_stricmp(str, &method_names[i])==0) { + if (pj_stricmp(str, method_names[i])==0) { m->id = (pjsip_method_e)i; - m->name = method_names[i]; + m->name = *method_names[i]; return; } } @@ -235,25 +245,26 @@ PJ_DEF(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type) return msg; } -PJ_DEF(void*) pjsip_msg_find_hdr( pjsip_msg *msg, - pjsip_hdr_e hdr_type, void *start) +PJ_DEF(void*) pjsip_msg_find_hdr( const pjsip_msg *msg, + pjsip_hdr_e hdr_type, const void *start) { - pjsip_hdr *hdr=start, *end=&msg->hdr; + const pjsip_hdr *hdr=start, *end=&msg->hdr; if (hdr == NULL) { hdr = msg->hdr.next; } for (; hdr!=end; hdr = hdr->next) { if (hdr->type == hdr_type) - return hdr; + return (void*)hdr; } return NULL; } -PJ_DEF(void*) pjsip_msg_find_hdr_by_name( pjsip_msg *msg, - const pj_str_t *name, void *start) +PJ_DEF(void*) pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, + const pj_str_t *name, + const void *start) { - pjsip_hdr *hdr=start, *end=&msg->hdr; + const pjsip_hdr *hdr=start, *end=&msg->hdr; if (hdr == NULL) { hdr = msg->hdr.next; @@ -261,10 +272,10 @@ PJ_DEF(void*) pjsip_msg_find_hdr_by_name( pjsip_msg *msg, for (; hdr!=end; hdr = hdr->next) { if (hdr->type < PJSIP_H_OTHER) { if (pj_stricmp(&pjsip_hdr_names[hdr->type], name) == 0) - return hdr; + return (void*)hdr; } else { if (pj_stricmp(&hdr->name, name) == 0) - return hdr; + return (void*)hdr; } } return NULL; @@ -280,7 +291,8 @@ PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, return hdr; } -PJ_DEF(pj_ssize_t) pjsip_msg_print( pjsip_msg *msg, char *buf, pj_size_t size) +PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, + char *buf, pj_size_t size) { char *p=buf, *end=buf+size; int len; @@ -1349,7 +1361,7 @@ PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_create( pj_pool_t *pool ) { pjsip_via_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr)); init_hdr(hdr, PJSIP_H_VIA, &via_hdr_vptr); - hdr->sent_by.port = 5060; + //hdr->sent_by.port = 5060; hdr->ttl_param = -1; hdr->rport_param = -1; pj_list_init(&hdr->other_param); @@ -1383,9 +1395,11 @@ static int pjsip_via_hdr_print( pjsip_via_hdr *hdr, *buf++ = ' '; pj_memcpy(buf, hdr->sent_by.host.ptr, hdr->sent_by.host.slen); buf += hdr->sent_by.host.slen; - *buf++ = ':'; - printed = pj_utoa(hdr->sent_by.port, buf); - buf += printed; + if (hdr->sent_by.port != 0) { + *buf++ = ':'; + printed = pj_utoa(hdr->sent_by.port, buf); + buf += printed; + } if (hdr->ttl_param >= 0) { size = endbuf-buf; diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c index 7b163578..e7306846 100644 --- a/pjsip/src/pjsip/sip_parser.c +++ b/pjsip/src/pjsip/sip_parser.c @@ -1782,8 +1782,6 @@ static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx ) pj_scan_get_char(scanner); pj_scan_get(scanner, &pjsip_DIGIT_SPEC, &digit); hdr->sent_by.port = pj_strtoul(&digit); - } else { - hdr->sent_by.port = 5060; } int_parse_via_param(hdr, scanner, ctx->pool); diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index bb9eee9d..1497fbd6 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -30,6 +30,396 @@ #include <pj/pool.h> #include <pj/assert.h> +#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 ) +{ + 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; i<PJSIP_MAX_MODULE; ++i) { + if (tsx->module_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); + } + } + + + +#endif // XXX JUNK + /* Thread Local Storage ID for transaction lock (initialized by endpoint) */ long pjsip_tsx_lock_tls_id; @@ -154,6 +544,45 @@ 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. @@ -500,9 +929,7 @@ static pj_status_t tsx_process_route( pjsip_transaction *tsx, pjsip_tx_data *tdata, pjsip_host_info *send_addr ) { - const pjsip_uri *new_request_uri, *target_uri; - const pjsip_name_addr *topmost_route_uri; - pjsip_route_hdr *first_route_hdr, *last_route_hdr; + pjsip_route_hdr *route_hdr; pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG); @@ -510,130 +937,20 @@ static pj_status_t tsx_process_route( pjsip_transaction *tsx, * have any "Route" headers but the endpoint has, then copy the "Route" * headers from the endpoint first. */ - last_route_hdr = first_route_hdr = - pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); - if (first_route_hdr) { - topmost_route_uri = &first_route_hdr->name_addr; - while (last_route_hdr->next != (void*)&tdata->msg->hdr) { - pjsip_route_hdr *hdr; - hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, - last_route_hdr->next); - if (!hdr) - break; - last_route_hdr = hdr; - } - } else { + route_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (!route_hdr) { const pjsip_route_hdr *hdr_list; - hdr_list = (pjsip_route_hdr*)pjsip_endpt_get_routing(tsx->endpt); - if (hdr_list->next != hdr_list) { - const pjsip_route_hdr *hdr = (pjsip_route_hdr*)hdr_list->next; - first_route_hdr = NULL; - topmost_route_uri = &hdr->name_addr; - do { - last_route_hdr = pjsip_hdr_shallow_clone(tdata->pool, hdr); - if (first_route_hdr == NULL) - first_route_hdr = last_route_hdr; - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)last_route_hdr); - hdr = hdr->next; - } while (hdr != hdr_list); - } else { - topmost_route_uri = NULL; + 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; } } - /* If Route headers exist, and the first element indicates loose-route, - * the URI is taken from the Request-URI, and we keep all existing Route - * headers intact. - * If Route headers exist, and the first element DOESN'T indicate loose - * route, the URI is taken from the first Route header, and remove the - * first Route header from the message. - * Otherwise if there's no Route headers, the URI is taken from the - * Request-URI. - */ - if (topmost_route_uri) { - pj_bool_t has_lr_param; - - if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) || - PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri)) - { - const pjsip_url *url = pjsip_uri_get_uri((void*)topmost_route_uri); - has_lr_param = url->lr_param; - } else { - has_lr_param = 0; - } - - if (has_lr_param) { - new_request_uri = tdata->msg->line.req.uri; - /* We shouldn't need to delete topmost Route if it has lr param. - * But seems like it breaks some proxy implementation, so we - * delete it anyway. - */ - /* - pj_list_erase(first_route_hdr); - if (first_route_hdr == last_route_hdr) - last_route_hdr = NULL; - */ - } else { - new_request_uri = pjsip_uri_get_uri((void*)topmost_route_uri); - pj_list_erase(first_route_hdr); - if (first_route_hdr == last_route_hdr) - last_route_hdr = NULL; - } - - target_uri = (pjsip_uri*)topmost_route_uri; - - } else { - target_uri = new_request_uri = tdata->msg->line.req.uri; - } - - /* The target URI must be a SIP/SIPS URL so we can resolve it's address. - * Otherwise we're in trouble (i.e. there's no host part in tel: URL). - */ - pj_memset(send_addr, 0, sizeof(*send_addr)); - - if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) { - pjsip_uri *uri = (pjsip_uri*) target_uri; - const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri); - send_addr->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE); - pj_strdup(tdata->pool, &send_addr->addr.host, &url->host); - send_addr->addr.port = url->port; - send_addr->type = - pjsip_transport_get_type_from_name(&url->transport_param); - - } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) { - pjsip_uri *uri = (pjsip_uri*) target_uri; - const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri); - pj_strdup(tdata->pool, &send_addr->addr.host, &url->host); - send_addr->addr.port = url->port; - send_addr->type = - pjsip_transport_get_type_from_name(&url->transport_param); -#if PJ_HAS_TCP - if (send_addr->type == PJSIP_TRANSPORT_TCP || - send_addr->type == PJSIP_TRANSPORT_SCTP) - { - send_addr->flag |= PJSIP_TRANSPORT_RELIABLE; - } -#endif - } else { - pj_assert(!"Unsupported URI scheme!"); - return PJSIP_EINVALIDSCHEME; - } - - /* If target URI is different than request URI, replace - * request URI add put the original URI in the last Route header. - */ - if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) { - pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool); - route->name_addr.uri = tdata->msg->line.req.uri; - if (last_route_hdr) - pj_list_insert_after(last_route_hdr, route); - else - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route); - tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri; - } - - /* Success. */ - return PJ_SUCCESS; + return pjsip_get_request_addr(tdata, send_addr); } diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 7299060c..50ff6be3 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -43,6 +43,9 @@ struct pjsip_tpmgr pj_lock_t *lock; pjsip_endpoint *endpt; pjsip_tpfactory factory_list; +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_atomic_t *tdata_counter; +#endif void (*msg_cb)(pjsip_endpoint*, pj_status_t, pjsip_rx_data*); }; @@ -204,6 +207,10 @@ PJ_DEF(pj_status_t) pjsip_tx_data_create( pjsip_tpmgr *mgr, pj_ioqueue_op_key_init(&tdata->op_key.key, sizeof(tdata->op_key)); +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_atomic_inc( tdata->mgr->tdata_counter ); +#endif + *p_tdata = tdata; return PJ_SUCCESS; } @@ -221,14 +228,20 @@ PJ_DEF(void) pjsip_tx_data_add_ref( pjsip_tx_data *tdata ) * Decrease transport data reference, destroy it when the reference count * reaches zero. */ -PJ_DEF(void) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ) +PJ_DEF(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ) { pj_assert( pj_atomic_get(tdata->ref_cnt) > 0); if (pj_atomic_dec_and_get(tdata->ref_cnt) <= 0) { PJ_LOG(5,(tdata->obj_name, "destroying txdata")); +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_atomic_dec( tdata->mgr->tdata_counter ); +#endif pj_atomic_destroy( tdata->ref_cnt ); pj_lock_destroy( tdata->lock ); pjsip_endpt_destroy_pool( tdata->mgr->endpt, tdata->pool ); + return PJSIP_EBUFDESTROYED; + } else { + return PJ_SUCCESS; } } @@ -452,7 +465,7 @@ PJ_DEF(pj_status_t) pjsip_transport_unregister( pjsip_tpmgr *mgr, /* * Unregister timer, if any. */ - pj_assert(tp->idle_timer.id == PJ_FALSE); + //pj_assert(tp->idle_timer.id == PJ_FALSE); if (tp->idle_timer.id != PJ_FALSE) { pjsip_endpt_cancel_timer(mgr->endpt, &tp->idle_timer); tp->idle_timer.id = PJ_FALSE; @@ -566,6 +579,12 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_create( pj_pool_t *pool, if (status != PJ_SUCCESS) return status; +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + status = pj_atomic_create(pool, 0, &mgr->tdata_counter); + if (status != PJ_SUCCESS) + return status; +#endif + *p_mgr = mgr; return PJ_SUCCESS; } @@ -582,6 +601,10 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr ) PJ_LOG(5, (THIS_FILE, "pjsip_tpmgr_destroy()")); +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_assert(pj_atomic_get(mgr->tdata_counter) == 0); +#endif + pj_lock_acquire(mgr->lock); itr = pj_hash_first(mgr->table, &itr_val); @@ -600,6 +623,7 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr ) } pj_lock_release(mgr->lock); + pj_lock_destroy(mgr->lock); return PJ_SUCCESS; } @@ -741,16 +765,16 @@ finish_process_fragment: /* - * pjsip_tpmgr_alloc_transport() + * pjsip_tpmgr_acquire_transport() * * Get transport suitable to communicate to remote. Create a new one * if necessary. */ -PJ_DEF(pj_status_t) pjsip_tpmgr_alloc_transport( pjsip_tpmgr *mgr, - pjsip_transport_type_e type, - const pj_sockaddr_t *remote, - int addr_len, - pjsip_transport **p_transport) +PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + pjsip_transport **tp) { struct transport_key { @@ -798,7 +822,7 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_alloc_transport( pjsip_tpmgr *mgr, */ pjsip_transport_add_ref(transport); pj_lock_release(mgr->lock); - *p_transport = transport; + *tp = transport; return PJ_SUCCESS; } @@ -821,8 +845,12 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_alloc_transport( pjsip_tpmgr *mgr, /* Request factory to create transport. */ status = factory->create_transport(factory, mgr, mgr->endpt, - remote, p_transport); - + remote, addr_len, tp); + if (status == PJ_SUCCESS) { + PJ_ASSERT_ON_FAIL(tp!=NULL, + {pj_lock_release(mgr->lock); return PJ_EBUG;}); + pjsip_transport_add_ref(*tp); + } pj_lock_release(mgr->lock); return status; } diff --git a/pjsip/src/pjsip/sip_transport_udp.c b/pjsip/src/pjsip/sip_transport_udp.c index bd6d7ffb..f61b58c4 100644 --- a/pjsip/src/pjsip/sip_transport_udp.c +++ b/pjsip/src/pjsip/sip_transport_udp.c @@ -21,6 +21,7 @@ #include <pjsip/sip_errno.h> #include <pj/pool.h> #include <pj/sock.h> +#include <pj/addr_resolv.h> #include <pj/os.h> #include <pj/lock.h> #include <pj/string.h> @@ -267,6 +268,9 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt, unsigned i; pj_status_t status; + PJ_ASSERT_RETURN(endpt && sock!=PJ_INVALID_SOCKET && a_name && async_cnt>0, + PJ_EINVAL); + /* Create pool. */ pool = pjsip_endpt_create_pool(endpt, "udp%p", PJSIP_POOL_LEN_TRANSPORT, PJSIP_POOL_INC_TRANSPORT); @@ -327,7 +331,7 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt, pj_sprintf(tp->base.info, "udp %s:%d [published as %s:%d]", pj_inet_ntoa(((pj_sockaddr_in*)&tp->base.local_addr)->sin_addr), pj_ntohs(((pj_sockaddr_in*)&tp->base.local_addr)->sin_port), - tp->base.local_name.host, + tp->base.local_name.host.ptr, tp->base.local_name.port); /* Set endpoint. */ @@ -367,6 +371,8 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt, /* Create rdata and put it in the array. */ tp->rdata_cnt = 0; + tp->rdata = pj_pool_calloc(tp->base.pool, async_cnt, + sizeof(pjsip_rx_data*)); for (i=0; i<async_cnt; ++i) { pj_pool_t *rdata_pool = pjsip_endpt_create_pool(endpt, "rtd%p", PJSIP_POOL_RDATA_LEN, @@ -382,6 +388,7 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt, /* Init tp_info part. */ tp->rdata[i]->tp_info.pool = rdata_pool; tp->rdata[i]->tp_info.transport = &tp->base; + tp->rdata[i]->tp_info.op_key.rdata = tp->rdata[i]; pj_ioqueue_op_key_init(&tp->rdata[i]->tp_info.op_key.op_key, sizeof(pj_ioqueue_op_key_t)); @@ -436,6 +443,8 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_start( pjsip_endpoint *endpt, char addr_buf[16]; pjsip_host_port bound_name; + PJ_ASSERT_RETURN(local != NULL, PJ_EINVAL); + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock); if (status != PJ_SUCCESS) return status; @@ -447,10 +456,31 @@ PJ_DEF(pj_status_t) pjsip_udp_transport_start( pjsip_endpoint *endpt, } if (a_name == NULL) { + /* Address name is not specified. + * Build a name based on bound address. + */ a_name = &bound_name; bound_name.host.ptr = addr_buf; - pj_strcpy2(&bound_name.host, pj_inet_ntoa(local->sin_addr)); bound_name.port = pj_ntohs(local->sin_port); + + /* If bound address specifies "0.0.0.0", get the IP address + * of local hostname. + */ + if (local->sin_addr.s_addr == PJ_INADDR_ANY) { + pj_hostent he; + const pj_str_t *hostname = pj_gethostname(); + status = pj_gethostbyname(hostname, &he); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + pj_strcpy2(&bound_name.host, + pj_inet_ntoa(*(pj_in_addr*)he.h_addr)); + } else { + /* Otherwise use bound address. */ + pj_strcpy2(&bound_name.host, pj_inet_ntoa(local->sin_addr)); + } + } return pjsip_udp_transport_attach( endpt, sock, a_name, async_cnt, diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index c227eff7..532d6fb5 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -23,6 +23,7 @@ #include <pjsip/sip_event.h> #include <pjsip/sip_transaction.h> #include <pjsip/sip_module.h> +#include <pjsip/sip_errno.h> #include <pj/log.h> #include <pj/string.h> #include <pj/guid.h> @@ -51,101 +52,6 @@ static const char *event_str[] = static pj_str_t str_TEXT = { "text", 4}, str_PLAIN = { "plain", 5 }; -static int aux_mod_id; - -struct aux_tsx_data -{ - void *token; - void (*cb)(void*,pjsip_event*); -}; - -static pj_status_t aux_tsx_init( pjsip_endpoint *endpt, - struct pjsip_module *mod, pj_uint32_t id ) -{ - PJ_UNUSED_ARG(endpt); - PJ_UNUSED_ARG(mod); - - aux_mod_id = id; - return 0; -} - -static void aux_tsx_handler( struct pjsip_module *mod, pjsip_event *event ) -{ - pjsip_transaction *tsx; - struct aux_tsx_data *tsx_data; - - PJ_UNUSED_ARG(mod); - - if (event->type != PJSIP_EVENT_TSX_STATE) - return; - - pj_assert(event->body.tsx_state.tsx != NULL); - tsx = event->body.tsx_state.tsx; - if (tsx == NULL) - return; - if (tsx->module_data[aux_mod_id] == NULL) - return; - if (tsx->status_code < 200) - return; - - /* Call the callback, if any, and prevent the callback to be called again - * by clearing the transaction's module_data. - */ - tsx_data = tsx->module_data[aux_mod_id]; - tsx->module_data[aux_mod_id] = NULL; - - if (tsx_data->cb) { - (*tsx_data->cb)(tsx_data->token, event); - } -} - -pjsip_module aux_tsx_module = -{ - { "Aux-Tsx", 7}, /* Name. */ - 0, /* Flag */ - 128, /* Priority */ - NULL, /* Arbitrary data. */ - 0, /* Number of methods supported (none). */ - { 0 }, /* Array of methods (none) */ - &aux_tsx_init, /* init_module() */ - NULL, /* start_module() */ - NULL, /* deinit_module() */ - &aux_tsx_handler, /* tsx_handler() */ -}; - -PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, - pjsip_tx_data *tdata, - int timeout, - void *token, - void (*cb)(void*,pjsip_event*)) -{ - pjsip_transaction *tsx; - struct aux_tsx_data *tsx_data; - pj_status_t status; - - status = pjsip_endpt_create_tsx(endpt, &tsx); - if (!tsx) { - pjsip_tx_data_dec_ref(tdata); - return -1; - } - - tsx_data = pj_pool_alloc(tsx->pool, sizeof(struct aux_tsx_data)); - tsx_data->token = token; - tsx_data->cb = cb; - tsx->module_data[aux_mod_id] = tsx_data; - - if (pjsip_tsx_init_uac(tsx, tdata) != 0) { - pjsip_endpt_destroy_tsx(endpt, tsx); - pjsip_tx_data_dec_ref(tdata); - return -1; - } - - pjsip_endpt_register_tsx(endpt, tsx); - pjsip_tx_data_invalidate_msg(tdata); - pjsip_tsx_on_tx_msg(tsx, tdata); - pjsip_tx_data_dec_ref(tdata); - return 0; -} /* * Initialize transmit data (msg) with the headers and optional body. @@ -169,6 +75,7 @@ static void init_request_throw( pjsip_endpoint *endpt, { pjsip_msg *msg; pjsip_msg_body *body; + pjsip_via_hdr *via; const pjsip_hdr *endpt_hdr; /* Create the message. */ @@ -205,6 +112,11 @@ static void init_request_throw( pjsip_endpoint *endpt, /* Add CSeq header. */ pjsip_msg_add_hdr(msg, (void*)param_cseq); + /* Add a blank Via header. */ + via = pjsip_via_hdr_create(tdata->pool); + via->rport_param = 0; + pjsip_msg_insert_first_hdr(msg, (void*)via); + /* Create message body. */ if (param_text) { body = pj_pool_calloc(tdata->pool, 1, sizeof(pjsip_msg_body)); @@ -216,6 +128,13 @@ static void init_request_throw( pjsip_endpoint *endpt, body->print_body = &pjsip_print_text_body; msg->body = body; } + + PJ_LOG(4,(THIS_FILE, "Request %s (CSeq=%d/%.*s) created.", + tdata->obj_name, + param_cseq->cseq, + param_cseq->method.name.slen, + param_cseq->method.name.ptr)); + } /* @@ -328,12 +247,6 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, } PJ_END - PJ_LOG(4,(THIS_FILE, "Request %s (%d %.*s) created.", - tdata->obj_name, - cseq->cseq, - cseq->method.name.slen, - cseq->method.name.ptr)); - *p_tdata = tdata; return PJ_SUCCESS; @@ -366,23 +279,30 @@ pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt, PJ_LOG(5,(THIS_FILE, "Entering pjsip_endpt_create_request_from_hdr()")); + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && method && param_target && param_from && + param_to && p_tdata, PJ_EINVAL); + + /* Create new transmit data. */ status = pjsip_endpt_create_tdata(endpt, &tdata); if (status != PJ_SUCCESS) return status; + /* Set initial reference counter to 1. */ pjsip_tx_data_add_ref(tdata); PJ_TRY { + /* Duplicate target URI and headers. */ target = pjsip_uri_clone(tdata->pool, param_target); - from = pjsip_hdr_shallow_clone(tdata->pool, param_from); + from = pjsip_hdr_clone(tdata->pool, param_from); pjsip_fromto_set_from(from); - to = pjsip_hdr_shallow_clone(tdata->pool, param_to); + to = pjsip_hdr_clone(tdata->pool, param_to); pjsip_fromto_set_to(to); if (param_contact) - contact = pjsip_hdr_shallow_clone(tdata->pool, param_contact); + contact = pjsip_hdr_clone(tdata->pool, param_contact); else contact = NULL; - call_id = pjsip_hdr_shallow_clone(tdata->pool, param_call_id); + call_id = pjsip_hdr_clone(tdata->pool, param_call_id); cseq = pjsip_cseq_hdr_create(tdata->pool); if (param_cseq >= 0) cseq->cseq = param_cseq; @@ -390,6 +310,7 @@ pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt, cseq->cseq = pj_rand() % 0xFFFF; pjsip_method_copy(tdata->pool, &cseq->method, method); + /* Copy headers to the request. */ init_request_throw(endpt, tdata, &cseq->method, target, from, to, contact, call_id, cseq, param_text); } @@ -399,12 +320,6 @@ pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt, } PJ_END; - PJ_LOG(4,(THIS_FILE, "Request %s (%d %.*s) created.", - tdata->obj_name, - cseq->cseq, - cseq->method.name.slen, - cseq->method.name.ptr)); - *p_tdata = tdata; return PJ_SUCCESS; @@ -418,7 +333,8 @@ on_error: */ PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, const pjsip_rx_data *rdata, - int code, + int st_code, + const pj_str_t *st_text, pjsip_tx_data **p_tdata) { pjsip_tx_data *tdata; @@ -434,19 +350,25 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, /* Log this action. */ PJ_LOG(5,(THIS_FILE, "pjsip_endpt_create_response(rdata=%p, code=%d)", - rdata, code)); + rdata, st_code)); /* Create a new transmit buffer. */ status = pjsip_endpt_create_tdata( endpt, &tdata); if (status != PJ_SUCCESS) return status; + /* Set initial reference count to 1. */ + pjsip_tx_data_add_ref(tdata); + /* Create new response message. */ tdata->msg = msg = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG); /* Set status code and reason text. */ - msg->line.status.code = code; - msg->line.status.reason = *pjsip_get_status_text(code); + msg->line.status.code = st_code; + if (st_text) + pj_strdup(tdata->pool, &msg->line.status.reason, st_text); + else + msg->line.status.reason = *pjsip_get_status_text(st_code); /* Set TX data attributes. */ tdata->rx_timestamp = rdata->pkt_info.timestamp; @@ -500,81 +422,95 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, * RFC3261). Note that the generation of ACK for 2xx response is different, * and one must not use this function to generate such ACK. */ -PJ_DEF(void) pjsip_endpt_create_ack(pjsip_endpoint *endpt, - pjsip_tx_data *tdata, - const pjsip_rx_data *rdata ) +PJ_DEF(pj_status_t) pjsip_endpt_create_ack( pjsip_endpoint *endpt, + const pjsip_tx_data *tdata, + const pjsip_rx_data *rdata, + pjsip_tx_data **ack_tdata) { - pjsip_msg *ack_msg, *invite_msg; + pjsip_tx_data *ack = NULL; + const pjsip_msg *invite_msg; + const pjsip_from_hdr *from_hdr; + const pjsip_to_hdr *to_hdr; + const pjsip_cid_hdr *cid_hdr; + const pjsip_cseq_hdr *cseq_hdr; + const pjsip_hdr *hdr; pjsip_to_hdr *to; - pjsip_from_hdr *from; - pjsip_cseq_hdr *cseq; - pjsip_hdr *hdr; + pj_status_t status; - /* Make compiler happy. */ - PJ_UNUSED_ARG(endpt); + /* Log this action. */ + PJ_LOG(5,(THIS_FILE, "pjsip_endpt_create_ack(rdata=%p)", rdata)); /* rdata must be a final response. */ pj_assert(rdata->msg_info.msg->type==PJSIP_RESPONSE_MSG && rdata->msg_info.msg->line.status.code >= 300); - /* Log this action. */ - PJ_LOG(5,(THIS_FILE, "pjsip_endpt_create_ack(rdata=%p)", rdata)); - - /* Create new request message. */ - ack_msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG); - pjsip_method_set( &ack_msg->line.req.method, PJSIP_ACK_METHOD ); + /* Initialize return value to NULL. */ + *ack_tdata = NULL; /* The original INVITE message. */ invite_msg = tdata->msg; - /* Copy Request-Uri from the original INVITE. */ - ack_msg->line.req.uri = invite_msg->line.req.uri; - - /* Copy Call-ID from the original INVITE */ - hdr = pjsip_msg_find_remove_hdr( invite_msg, PJSIP_H_CALL_ID, NULL); - pjsip_msg_add_hdr( ack_msg, hdr ); + /* Get the headers from original INVITE request. */ +# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL) - /* Copy From header from the original INVITE. */ - from = (pjsip_from_hdr*)pjsip_msg_find_remove_hdr(invite_msg, - PJSIP_H_FROM, NULL); - pjsip_msg_add_hdr( ack_msg, (pjsip_hdr*)from ); + from_hdr = (const pjsip_from_hdr*) FIND_HDR(invite_msg, FROM); + PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr); - /* Copy To header from the original INVITE. */ - to = (pjsip_to_hdr*)pjsip_msg_find_remove_hdr( invite_msg, - PJSIP_H_TO, NULL); - pj_strdup(tdata->pool, &to->tag, &rdata->msg_info.to->tag); - pjsip_msg_add_hdr( ack_msg, (pjsip_hdr*)to ); + to_hdr = (const pjsip_to_hdr*) FIND_HDR(invite_msg, TO); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); - /* Must contain single Via, just as the original INVITE. */ - hdr = pjsip_msg_find_remove_hdr( invite_msg, PJSIP_H_VIA, NULL); - pjsip_msg_insert_first_hdr( ack_msg, hdr ); + cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(invite_msg, CALL_ID); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); - /* Must have the same CSeq value as the original INVITE, but method - * changed to ACK - */ - cseq = (pjsip_cseq_hdr*) pjsip_msg_find_remove_hdr( invite_msg, - PJSIP_H_CSEQ, NULL); - pjsip_method_set( &cseq->method, PJSIP_ACK_METHOD ); - pjsip_msg_add_hdr( ack_msg, (pjsip_hdr*) cseq ); + cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(invite_msg, CSEQ); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + +# undef FIND_HDR + + /* Create new request message from the headers. */ + status = pjsip_endpt_create_request_from_hdr(endpt, + &pjsip_ack_method, + tdata->msg->line.req.uri, + from_hdr, to_hdr, + NULL, cid_hdr, + cseq_hdr->cseq, NULL, + &ack); + + if (status != PJ_SUCCESS) + return status; + + /* Update tag in To header with the one from the response (if any). */ + to = (pjsip_to_hdr*) pjsip_msg_find_hdr(ack->msg, PJSIP_H_TO, NULL); + pj_strdup(ack->pool, &to->tag, &rdata->msg_info.to->tag); + + /* Must contain single Via, just as the original INVITE. */ + hdr = pjsip_msg_find_hdr( invite_msg, PJSIP_H_VIA, NULL); + if (hdr) { + pjsip_msg_insert_first_hdr( ack->msg, pjsip_hdr_clone(ack->pool,hdr) ); + } /* If the original INVITE has Route headers, those header fields MUST * appear in the ACK. */ - hdr = pjsip_msg_find_remove_hdr( invite_msg, PJSIP_H_ROUTE, NULL); + hdr = pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, NULL); while (hdr != NULL) { - pjsip_msg_add_hdr( ack_msg, hdr ); - hdr = pjsip_msg_find_remove_hdr( invite_msg, PJSIP_H_ROUTE, NULL); + pjsip_msg_add_hdr( ack->msg, pjsip_hdr_clone(ack->pool, hdr) ); + hdr = hdr->next; + if (hdr == &invite_msg->hdr) + break; + hdr = pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, hdr); } - /* Set the message in the "tdata" to point to the ACK message. */ - tdata->msg = ack_msg; - - /* Reset transmit packet buffer, to force 're-printing' of message. */ - tdata->buf.cur = tdata->buf.start; - /* We're done. * "tdata" parameter now contains the ACK message. */ + *ack_tdata = ack; + return PJ_SUCCESS; + +on_missing_hdr: + if (ack) + pjsip_tx_data_dec_ref(ack); + return PJSIP_EMISSINGHDR; } @@ -583,82 +519,74 @@ PJ_DEF(void) pjsip_endpt_create_ack(pjsip_endpoint *endpt, * chapter 9.1 of RFC3261. */ PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt, - pjsip_tx_data *req_tdata, + const pjsip_tx_data *req_tdata, pjsip_tx_data **p_tdata) { - pjsip_msg *req_msg; /* the original request. */ - pjsip_tx_data *cancel_tdata; - pjsip_msg *cancel_msg; - pjsip_hdr *hdr; - pjsip_cseq_hdr *req_cseq, *cseq; - pjsip_uri *req_uri; + pjsip_tx_data *cancel_tdata = NULL; + const pjsip_from_hdr *from_hdr; + const pjsip_to_hdr *to_hdr; + const pjsip_cid_hdr *cid_hdr; + const pjsip_cseq_hdr *cseq_hdr; + const pjsip_hdr *hdr; pj_status_t status; /* Log this action. */ PJ_LOG(5,(THIS_FILE, "pjsip_endpt_create_cancel(tdata=%p)", req_tdata)); - /* Get the original request. */ - req_msg = req_tdata->msg; - /* The transmit buffer must INVITE request. */ - PJ_ASSERT_RETURN(req_msg->type == PJSIP_REQUEST_MSG && - req_msg->line.req.method.id == PJSIP_INVITE_METHOD, + PJ_ASSERT_RETURN(req_tdata->msg->type == PJSIP_REQUEST_MSG && + req_tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD, PJ_EINVAL); - /* Create new transmit buffer. */ - status = pjsip_endpt_create_tdata( endpt, &cancel_tdata); - if (status != PJ_SUCCESS) { - return status; - } + /* Get the headers from original INVITE request. */ +# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL) - /* Create CANCEL request message. */ - cancel_msg = pjsip_msg_create(cancel_tdata->pool, PJSIP_REQUEST_MSG); - cancel_tdata->msg = cancel_msg; + from_hdr = (const pjsip_from_hdr*) FIND_HDR(req_tdata->msg, FROM); + PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr); - /* Request-URI, Call-ID, From, To, and the numeric part of the CSeq are - * copied from the original request. - */ - /* Set request line. */ - pjsip_method_set(&cancel_msg->line.req.method, PJSIP_CANCEL_METHOD); - req_uri = req_msg->line.req.uri; - cancel_msg->line.req.uri = pjsip_uri_clone(cancel_tdata->pool, req_uri); + to_hdr = (const pjsip_to_hdr*) FIND_HDR(req_tdata->msg, TO); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); - /* Copy Call-ID */ - hdr = pjsip_msg_find_hdr(req_msg, PJSIP_H_CALL_ID, NULL); - pjsip_msg_add_hdr(cancel_msg, pjsip_hdr_clone(cancel_tdata->pool, hdr)); + cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(req_tdata->msg, CALL_ID); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); - /* Copy From header. */ - hdr = pjsip_msg_find_hdr(req_msg, PJSIP_H_FROM, NULL); - pjsip_msg_add_hdr(cancel_msg, pjsip_hdr_clone(cancel_tdata->pool, hdr)); + cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(req_tdata->msg, CSEQ); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); - /* Copy To header. */ - hdr = pjsip_msg_find_hdr(req_msg, PJSIP_H_TO, NULL); - pjsip_msg_add_hdr(cancel_msg, pjsip_hdr_clone(cancel_tdata->pool, hdr)); +# undef FIND_HDR - /* Create new CSeq with equal number, but method set to CANCEL. */ - req_cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(req_msg, PJSIP_H_CSEQ, NULL); - cseq = pjsip_cseq_hdr_create(cancel_tdata->pool); - cseq->cseq = req_cseq->cseq; - pjsip_method_set(&cseq->method, PJSIP_CANCEL_METHOD); - pjsip_msg_add_hdr(cancel_msg, (pjsip_hdr*)cseq); + /* Create new request message from the headers. */ + status = pjsip_endpt_create_request_from_hdr(endpt, + &pjsip_cancel_method, + req_tdata->msg->line.req.uri, + from_hdr, to_hdr, + NULL, cid_hdr, + cseq_hdr->cseq, NULL, + &cancel_tdata); + + if (status != PJ_SUCCESS) + return status; /* Must only have single Via which matches the top-most Via in the * request being cancelled. */ - hdr = pjsip_msg_find_hdr(req_msg, PJSIP_H_VIA, NULL); - pjsip_msg_insert_first_hdr(cancel_msg, - pjsip_hdr_clone(cancel_tdata->pool, hdr)); + hdr = pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_VIA, NULL); + if (hdr) { + pjsip_msg_insert_first_hdr(cancel_tdata->msg, + pjsip_hdr_clone(cancel_tdata->pool, hdr)); + } /* If the original request has Route header, the CANCEL request must also * has exactly the same. * Copy "Route" header from the request. */ - hdr = pjsip_msg_find_hdr(req_msg, PJSIP_H_ROUTE, NULL); + hdr = pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_ROUTE, NULL); while (hdr != NULL) { - pjsip_msg_add_hdr(cancel_msg, pjsip_hdr_clone(cancel_tdata->pool, hdr)); + pjsip_msg_add_hdr(cancel_tdata->msg, + pjsip_hdr_clone(cancel_tdata->pool, hdr)); hdr = hdr->next; - if (hdr != &cancel_msg->hdr) - hdr = pjsip_msg_find_hdr(req_msg, PJSIP_H_ROUTE, hdr); + if (hdr != &cancel_tdata->msg->hdr) + hdr = pjsip_msg_find_hdr(cancel_tdata->msg, PJSIP_H_ROUTE, hdr); else break; } @@ -668,51 +596,591 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt, */ *p_tdata = cancel_tdata; return PJ_SUCCESS; + +on_missing_hdr: + if (cancel_tdata) + pjsip_tx_data_dec_ref(cancel_tdata); + return PJSIP_EMISSINGHDR; } -/* Get the address parameters (host, port, flag, TTL, etc) to send the - * response. + +/* + * Find which destination to be used to send the request message, based + * on the request URI and Route headers in the message. The procedure + * used here follows the guidelines on sending the request in RFC 3261 + * chapter 8.1.2. */ -PJ_DEF(pj_status_t) pjsip_get_response_addr(pj_pool_t *pool, - const pjsip_transport *req_transport, - const pjsip_via_hdr *via, - pjsip_host_info *send_addr) +PJ_DEF(pj_status_t) pjsip_get_request_addr( pjsip_tx_data *tdata, + pjsip_host_info *dest_info ) { - /* Determine the destination address (section 18.2.2): - * - for TCP, SCTP, or TLS, send the response using the transport where - * the request was received. - * - if maddr parameter is present, send to this address using the port - * in sent-by or 5060. If multicast is used, the TTL in the Via must - * be used, or 1 if ttl parameter is not present. - * - otherwise if received parameter is present, set to this address. - * - otherwise send to the address in sent-by. + const pjsip_uri *new_request_uri, *target_uri; + const pjsip_name_addr *topmost_route_uri; + pjsip_route_hdr *first_route_hdr, *last_route_hdr; + + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL); + + /* 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. */ - send_addr->flag = req_transport->flag; - send_addr->type = req_transport->key.type; + last_route_hdr = first_route_hdr = + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (first_route_hdr) { + topmost_route_uri = &first_route_hdr->name_addr; + while (last_route_hdr->next != (void*)&tdata->msg->hdr) { + pjsip_route_hdr *hdr; + hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, + last_route_hdr->next); + if (!hdr) + break; + last_route_hdr = hdr; + } + } else { + topmost_route_uri = NULL; + } - if (PJSIP_TRANSPORT_IS_RELIABLE(req_transport)) { - pj_strdup( pool, &send_addr->addr.host, - &req_transport->remote_name.host); - send_addr->addr.port = req_transport->remote_name.port; + /* If Route headers exist, and the first element indicates loose-route, + * the URI is taken from the Request-URI, and we keep all existing Route + * headers intact. + * If Route headers exist, and the first element DOESN'T indicate loose + * route, the URI is taken from the first Route header, and remove the + * first Route header from the message. + * Otherwise if there's no Route headers, the URI is taken from the + * Request-URI. + */ + if (topmost_route_uri) { + pj_bool_t has_lr_param; + + if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) || + PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri)) + { + const pjsip_url *url = pjsip_uri_get_uri((void*)topmost_route_uri); + has_lr_param = url->lr_param; + } else { + has_lr_param = 0; + } + if (has_lr_param) { + new_request_uri = tdata->msg->line.req.uri; + /* We shouldn't need to delete topmost Route if it has lr param. + * But seems like it breaks some proxy implementation, so we + * delete it anyway. + */ + /* + pj_list_erase(first_route_hdr); + if (first_route_hdr == last_route_hdr) + last_route_hdr = NULL; + */ + } else { + new_request_uri = pjsip_uri_get_uri((void*)topmost_route_uri); + pj_list_erase(first_route_hdr); + if (first_route_hdr == last_route_hdr) + last_route_hdr = NULL; + } + + target_uri = (pjsip_uri*)topmost_route_uri; + + } else { + target_uri = new_request_uri = tdata->msg->line.req.uri; + } + + /* The target URI must be a SIP/SIPS URL so we can resolve it's address. + * Otherwise we're in trouble (i.e. there's no host part in tel: URL). + */ + pj_memset(dest_info, 0, sizeof(*dest_info)); + + if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) { + pjsip_uri *uri = (pjsip_uri*) target_uri; + const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri); + dest_info->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE); + pj_strdup(tdata->pool, &dest_info->addr.host, &url->host); + dest_info->addr.port = url->port; + dest_info->type = + pjsip_transport_get_type_from_name(&url->transport_param); + + } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) { + pjsip_uri *uri = (pjsip_uri*) target_uri; + const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri); + pj_strdup(tdata->pool, &dest_info->addr.host, &url->host); + dest_info->addr.port = url->port; + dest_info->type = + pjsip_transport_get_type_from_name(&url->transport_param); +#if PJ_HAS_TCP + if (dest_info->type == PJSIP_TRANSPORT_TCP || + dest_info->type == PJSIP_TRANSPORT_SCTP) + { + dest_info->flag |= PJSIP_TRANSPORT_RELIABLE; + } +#endif } else { - /* Set the host part */ - if (via->maddr_param.slen) { - pj_strdup(pool, &send_addr->addr.host, &via->maddr_param); - } else if (via->recvd_param.slen) { - pj_strdup(pool, &send_addr->addr.host, &via->recvd_param); + pj_assert(!"Unsupported URI scheme!"); + PJ_TODO(SUPPORT_REQUEST_ADDR_RESOLUTION_FOR_TEL_URI); + return PJSIP_EINVALIDSCHEME; + } + + /* If target URI is different than request URI, replace + * request URI add put the original URI in the last Route header. + */ + if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) { + pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool); + route->name_addr.uri = tdata->msg->line.req.uri; + if (last_route_hdr) + pj_list_insert_after(last_route_hdr, route); + else + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route); + tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri; + } + + /* Success. */ + return PJ_SUCCESS; +} + + +/* Transport callback for sending stateless request. + * This is one of the most bizzare function in pjsip, so + * good luck if you happen to debug this function!! + */ +static void stateless_send_transport_cb( void *token, + pjsip_tx_data *tdata, + pj_ssize_t sent ) +{ + pjsip_send_state *stateless_data = token; + + PJ_UNUSED_ARG(tdata); + pj_assert(tdata == stateless_data->tdata); + + for (;;) { + pj_status_t status; + pj_bool_t cont; + + pj_sockaddr_t *cur_addr; + pjsip_transport_type_e cur_addr_type; + int cur_addr_len; + + pjsip_via_hdr *via; + + if (sent == -PJ_EPENDING) { + /* This is the initial process. + * When the process started, this function will be called by + * stateless_send_resolver_callback() with sent argument set to + * -PJ_EPENDING. + */ + cont = PJ_TRUE; + } else { + /* There are two conditions here: + * (1) Message is sent (i.e. sent > 0), + * (2) Failure (i.e. sent <= 0) + */ + cont = (sent > 0) ? PJ_FALSE : + (stateless_data->cur_addr<stateless_data->addr.count-1); + if (stateless_data->app_cb) { + (*stateless_data->app_cb)(stateless_data, sent, &cont); + } else { + /* Doesn't have application callback. + * Terminate the process. + */ + cont = PJ_FALSE; + } + } + + /* Finished with this transport. */ + if (stateless_data->cur_transport) { + pjsip_transport_dec_ref(stateless_data->cur_transport); + stateless_data->cur_transport = NULL; + } + + /* Done if application doesn't want to continue. */ + if (sent > 0 || !cont) { + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Try next address, if any, and only when this is not the + * first invocation. + */ + if (sent != -PJ_EPENDING) { + stateless_data->cur_addr++; + } + + /* Have next address? */ + if (stateless_data->cur_addr >= stateless_data->addr.count) { + /* This only happens when a rather buggy application has + * sent 'cont' to PJ_TRUE when the initial value was PJ_FALSE. + * In this case just stop the processing; we don't need to + * call the callback again as application has been informed + * before. + */ + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Keep current server address information handy. */ + cur_addr = &stateless_data->addr.entry[stateless_data->cur_addr].addr; + cur_addr_type = stateless_data->addr.entry[stateless_data->cur_addr].type; + cur_addr_len = stateless_data->addr.entry[stateless_data->cur_addr].addr_len; + + /* Acquire transport. */ + status = pjsip_endpt_acquire_transport( stateless_data->endpt, + cur_addr_type, + cur_addr, + cur_addr_len, + &stateless_data->cur_transport); + if (status != PJ_SUCCESS) { + sent = -status; + continue; + } + + /* Modify Via header. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr( tdata->msg, + PJSIP_H_VIA, NULL); + if (!via) { + /* Shouldn't happen if request was created with PJSIP API! + * But we handle the case anyway for robustness. + */ + pj_assert(!"Via header not found!"); + via = pjsip_via_hdr_create(tdata->pool); + pjsip_msg_insert_first_hdr(tdata->msg, (pjsip_hdr*)via); + } + + if (via->branch_param.slen == 0) { + pj_str_t tmp; + via->branch_param.ptr = pj_pool_alloc(tdata->pool, + PJSIP_MAX_BRANCH_LEN); + via->branch_param.slen = PJSIP_MAX_BRANCH_LEN; + pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN); + tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN; + pj_generate_unique_string(&tmp); + } + + via->transport = pj_str(stateless_data->cur_transport->type_name); + via->sent_by = stateless_data->cur_transport->local_name; + via->rport_param = 0; + + /* Send message using this transport. */ + status = pjsip_transport_send( stateless_data->cur_transport, + tdata, + cur_addr, + cur_addr_len, + stateless_data, + &stateless_send_transport_cb); + if (status == PJ_SUCCESS) { + /* Recursively call this function. */ + sent = tdata->buf.cur - tdata->buf.start; + stateless_send_transport_cb( stateless_data, tdata, sent ); + return; + } else if (status == PJ_EPENDING) { + /* This callback will be called later. */ + return; } else { - pj_strdup(pool, &send_addr->addr.host, &via->sent_by.host); + /* Recursively call this function. */ + sent = -status; + stateless_send_transport_cb( stateless_data, tdata, sent ); + return; + } + } + +} + +/* Resolver callback for sending stateless request. */ +static void +stateless_send_resolver_callback( pj_status_t status, + void *token, + const struct pjsip_server_addresses *addr) +{ + pjsip_send_state *stateless_data = token; + + /* Fail on server resolution. */ + if (status != PJ_SUCCESS) { + if (stateless_data->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*stateless_data->app_cb)(stateless_data, -status, &cont); + } + pjsip_tx_data_dec_ref(stateless_data->tdata); + return; + } + + /* Copy server addresses */ + pj_memcpy( &stateless_data->addr, addr, sizeof(pjsip_server_addresses)); + + /* Process the addresses. */ + stateless_send_transport_cb( stateless_data, stateless_data->tdata, + -PJ_EPENDING); +} + +/* + * Send stateless request. + * The sending process consists of several stages: + * - determine which host to contact (#pjsip_get_request_addr). + * - resolve the host (#pjsip_endpt_resolve) + * - establish transport (#pjsip_endpt_acquire_transport) + * - send the message (#pjsip_transport_send) + */ +PJ_DEF(pj_status_t) +pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + void *token, + void (*cb)(pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont)) +{ + pjsip_host_info dest_info; + pjsip_send_state *stateless_data; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt && tdata, PJ_EINVAL); + + /* Get destination name to contact. */ + status = pjsip_get_request_addr(tdata, &dest_info); + if (status != PJ_SUCCESS) + return status; + + /* Keep stateless data. */ + stateless_data = pj_pool_zalloc(tdata->pool, sizeof(pjsip_send_state)); + stateless_data->token = token; + stateless_data->endpt = endpt; + stateless_data->tdata = tdata; + stateless_data->app_cb = cb; + + /* Resolve destination host. + * The processing then resumed when the resolving callback is called. + */ + pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data, + &stateless_send_resolver_callback); + return PJ_SUCCESS; +} + +/* + * Determine which address (and transport) to use to send response message + * based on the received request. This function follows the specification + * in section 18.2.2 of RFC 3261 and RFC 3581 for calculating the destination + * address and transport. + */ +PJ_DEF(pj_status_t) pjsip_get_response_addr( pj_pool_t *pool, + pjsip_rx_data *rdata, + pjsip_response_addr *res_addr ) +{ + pjsip_transport *src_transport = rdata->tp_info.transport; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL); + + /* All requests must have "received" parameter. + * This must always be done in transport layer. + */ + pj_assert(rdata->msg_info.via->recvd_param.slen != 0); + + /* Do the calculation based on RFC 3261 Section 18.2.2 and RFC 3581 */ + + if (PJSIP_TRANSPORT_IS_RELIABLE(src_transport)) { + /* For reliable protocol such as TCP or SCTP, or TLS over those, the + * response MUST be sent using the existing connection to the source + * of the original request that created the transaction, if that + * connection is still open. + * If that connection is no longer open, the server SHOULD open a + * connection to the IP address in the received parameter, if present, + * using the port in the sent-by value, or the default port for that + * transport, if no port is specified. + * If that connection attempt fails, the server SHOULD use the + * procedures in [4] for servers in order to determine the IP address + * and port to open the connection and send the response to. + */ + res_addr->transport = rdata->tp_info.transport; + pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr, + rdata->pkt_info.src_addr_len); + res_addr->addr_len = rdata->pkt_info.src_addr_len; + res_addr->dst_host.type = src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->recvd_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) { + res_addr->dst_host.addr.port = + pjsip_transport_get_default_port_for_type(res_addr->dst_host.type); + } + + } else if (rdata->msg_info.via->maddr_param.slen) { + /* Otherwise, if the Via header field value contains a maddr parameter, + * the response MUST be forwarded to the address listed there, using + * the port indicated in sent-by, or port 5060 if none is present. + * If the address is a multicast address, the response SHOULD be sent + * using the TTL indicated in the ttl parameter, or with a TTL of 1 if + * that parameter is not present. + */ + res_addr->transport = NULL; + res_addr->dst_host.type = src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->maddr_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) + res_addr->dst_host.addr.port = 5060; + + } else if (rdata->msg_info.via->rport_param >= 0) { + /* There is both a "received" parameter and an "rport" parameter, + * the response MUST be sent to the IP address listed in the "received" + * parameter, and the port in the "rport" parameter. + * The response MUST be sent from the same address and port that the + * corresponding request was received on. + */ + res_addr->transport = rdata->tp_info.transport; + pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr, + rdata->pkt_info.src_addr_len); + res_addr->addr_len = rdata->pkt_info.src_addr_len; + res_addr->dst_host.type = src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->recvd_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) { + res_addr->dst_host.addr.port = + pjsip_transport_get_default_port_for_type(res_addr->dst_host.type); } - /* Set the port */ - send_addr->addr.port = via->sent_by.port; + } else { + res_addr->transport = NULL; + res_addr->dst_host.type = src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->recvd_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) { + res_addr->dst_host.addr.port = + pjsip_transport_get_default_port_for_type(res_addr->dst_host.type); + } } return PJ_SUCCESS; } /* + * Callback called by transport during send_response. + */ +static void send_response_transport_cb(void *token, pjsip_tx_data *tdata, + pj_ssize_t sent) +{ + pjsip_send_state *send_state = token; + pj_bool_t cont = PJ_FALSE; + + /* Call callback, if any. */ + if (send_state->app_cb) + (*send_state->app_cb)(send_state, sent, &cont); + + /* Decrement transport reference counter. */ + pjsip_transport_dec_ref(send_state->cur_transport); + + /* Decrement transmit data ref counter. */ + pjsip_tx_data_dec_ref(tdata); +} + +/* + * Resolver calback during send_response. + */ +static void send_response_resolver_cb( pj_status_t status, void *token, + const pjsip_server_addresses *addr ) +{ + pjsip_send_state *send_state = token; + + if (status != PJ_SUCCESS) { + if (send_state->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*send_state->app_cb)(send_state, -status, &cont); + } + pjsip_tx_data_dec_ref(send_state->tdata); + return; + } + + /* Only handle the first address resolved. */ + + /* Acquire transport. */ + status = pjsip_endpt_acquire_transport( send_state->endpt, + addr->entry[0].type, + &addr->entry[0].addr, + addr->entry[0].addr_len, + &send_state->cur_transport); + if (status != PJ_SUCCESS) { + if (send_state->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*send_state->app_cb)(send_state, -status, &cont); + } + pjsip_tx_data_dec_ref(send_state->tdata); + return; + } + + /* Send response using the transoprt. */ + status = pjsip_transport_send( send_state->cur_transport, + send_state->tdata, + &addr->entry[0].addr, + addr->entry[0].addr_len, + send_state, + &send_response_transport_cb); + if (status == PJ_SUCCESS) { + pj_ssize_t sent = send_state->tdata->buf.cur - + send_state->tdata->buf.start; + send_response_transport_cb(send_state, send_state->tdata, sent); + + } else if (status == PJ_EPENDING) { + /* Transport callback will be called later. */ + } else { + send_response_transport_cb(send_state, send_state->tdata, -status); + } +} + +/* + * Send response. + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, + pjsip_response_addr *res_addr, + pjsip_tx_data *tdata, + void *token, + void (*cb)(pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont)) +{ + /* Determine which transports and addresses to send the response, + * based on Section 18.2.2 of RFC 3261. + */ + pjsip_send_state *send_state; + pj_status_t status; + + /* Create structure to keep the sending state. */ + send_state = pj_pool_zalloc(tdata->pool, sizeof(pjsip_send_state)); + send_state->endpt = endpt; + send_state->tdata = tdata; + send_state->token = token; + send_state->app_cb = cb; + + if (res_addr->transport != NULL) { + send_state->cur_transport = res_addr->transport; + pjsip_transport_add_ref(send_state->cur_transport); + + status = pjsip_transport_send( send_state->cur_transport, tdata, + &res_addr->addr, + res_addr->addr_len, + send_state, + &send_response_transport_cb ); + if (status == PJ_SUCCESS) { + pj_ssize_t sent = tdata->buf.cur - tdata->buf.start; + send_response_transport_cb(send_state, tdata, sent); + return PJ_SUCCESS; + } else if (status == PJ_EPENDING) { + /* Callback will be called later. */ + return PJ_SUCCESS; + } else { + send_response_transport_cb(send_state, tdata, -status); + return status; + } + } else { + pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, + send_state, &send_response_resolver_cb); + return PJ_SUCCESS; + } +} + + +/* * Get the event string from the event ID. */ PJ_DEF(const char *) pjsip_event_str(pjsip_event_id_e e) diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c new file mode 100644 index 00000000..41efcddc --- /dev/null +++ b/pjsip/src/pjsip/sip_util_statefull.c @@ -0,0 +1,108 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * 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 <pjsip/sip_util.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_event.h> +#include <pj/pool.h> + +struct aux_tsx_data +{ + void *token; + void (*cb)(void*,pjsip_event*); +}; + +static void aux_tsx_handler( pjsip_transaction *tsx, pjsip_event *event ); + +pjsip_module aux_tsx_module = +{ + NULL, NULL, /* prev and next */ + { "Aux-Tsx", 7}, /* 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() */ + &aux_tsx_handler, /* tsx_handler() */ +}; + +static void aux_tsx_handler( pjsip_transaction *tsx, pjsip_event *event ) +{ + struct aux_tsx_data *tsx_data; + + if (event->type != PJSIP_EVENT_TSX_STATE) + return; + if (tsx->module_data[aux_tsx_module.id] == NULL) + return; + if (tsx->status_code < 200) + return; + + /* Call the callback, if any, and prevent the callback to be called again + * by clearing the transaction's module_data. + */ + tsx_data = tsx->module_data[aux_tsx_module.id]; + tsx->module_data[aux_tsx_module.id] = NULL; + + if (tsx_data->cb) { + (*tsx_data->cb)(tsx_data->token, event); + } +} + + +PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + int timeout, + void *token, + void (*cb)(void*,pjsip_event*)) +{ + pjsip_transaction *tsx; + struct aux_tsx_data *tsx_data; + pj_status_t status; + + status = pjsip_endpt_create_tsx(endpt, &tsx); + if (!tsx) { + pjsip_tx_data_dec_ref(tdata); + return -1; + } + + tsx_data = pj_pool_alloc(tsx->pool, sizeof(struct aux_tsx_data)); + tsx_data->token = token; + tsx_data->cb = cb; + tsx->module_data[aux_tsx_module.id] = tsx_data; + + if (pjsip_tsx_init_uac(tsx, tdata) != 0) { + pjsip_endpt_destroy_tsx(endpt, tsx); + pjsip_tx_data_dec_ref(tdata); + return -1; + } + + pjsip_endpt_register_tsx(endpt, tsx); + pjsip_tx_data_invalidate_msg(tdata); + pjsip_tsx_on_tx_msg(tsx, tdata); + pjsip_tx_data_dec_ref(tdata); + return 0; +} + diff --git a/pjsip/src/test-pjsip/main.c b/pjsip/src/test-pjsip/main.c index 3e5270b1..3c5f0800 100644 --- a/pjsip/src/test-pjsip/main.c +++ b/pjsip/src/test-pjsip/main.c @@ -17,8 +17,17 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "test.h" +#include <stdio.h> -int main(void) +int main(int argc, char *argv[]) { - return test_main(); + int retval = test_main(); + + if (argc != 1) { + char s[10]; + printf("<Press ENTER to quit>\n"); + fgets(s, sizeof(s), stdin); + } + + return retval; } diff --git a/pjsip/src/test-pjsip/msg_test.c b/pjsip/src/test-pjsip/msg_test.c index 1f6affdd..e3702713 100644 --- a/pjsip/src/test-pjsip/msg_test.c +++ b/pjsip/src/test-pjsip/msg_test.c @@ -664,7 +664,7 @@ static pjsip_msg *create_msg1(pj_pool_t *pool) /*****************************************************************************/ -pj_status_t msg_test(void) +int msg_test(void) { pj_status_t status; pj_pool_t *pool; diff --git a/pjsip/src/test-pjsip/test.c b/pjsip/src/test-pjsip/test.c index 92e0c33a..a8f339c8 100644 --- a/pjsip/src/test-pjsip/test.c +++ b/pjsip/src/test-pjsip/test.c @@ -83,6 +83,8 @@ int test_main(void) DO_TEST(uri_test()); DO_TEST(msg_test()); + DO_TEST(txdata_test()); + DO_TEST(transport_udp_test()); on_return: diff --git a/pjsip/src/test-pjsip/test.h b/pjsip/src/test-pjsip/test.h index aebf6022..686dea07 100644 --- a/pjsip/src/test-pjsip/test.h +++ b/pjsip/src/test-pjsip/test.h @@ -23,10 +23,27 @@ extern pjsip_endpoint *endpt; -pj_status_t uri_test(void); -pj_status_t msg_test(void); +#define TEST_UDP_PORT 15060 +/* The tests */ +int uri_test(void); +int msg_test(void); +int txdata_test(void); +int transport_udp_test(void); + +/* Transport test helpers (transport_test.c). */ +int generic_transport_test(pjsip_transport *tp); +int transport_send_recv_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + const pj_sockaddr_in *rem_addr ); +int transport_rt_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + const pj_sockaddr_in *rem_addr ); + +/* Test main entry */ int test_main(void); + +/* Test utilities. */ void app_perror(const char *msg, pj_status_t status); diff --git a/pjsip/src/test-pjsip/transport_test.c b/pjsip/src/test-pjsip/transport_test.c new file mode 100644 index 00000000..0d54d42d --- /dev/null +++ b/pjsip/src/test-pjsip/transport_test.c @@ -0,0 +1,561 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * 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 <pjsip_core.h> +#include <pjlib.h> + +/////////////////////////////////////////////////////////////////////////////// +/* + * Generic testing for transport, to make sure that basic + * attributes have been initialized properly. + */ +int generic_transport_test(pjsip_transport *tp) +{ + PJ_LOG(3,("", " structure test...")); + + /* Check that local address name is valid. */ + { + struct pj_in_addr addr; + + /* Note: inet_aton() returns non-zero if addr is valid! */ + if (pj_inet_aton(&tp->local_name.host, &addr) != 0) { + if (addr.s_addr==PJ_INADDR_ANY || addr.s_addr==PJ_INADDR_NONE) { + PJ_LOG(3,("", " Error: invalid address name")); + return -420; + } + } else { + /* It's okay. local_name.host may be a hostname instead of + * IP address. + */ + } + } + + /* Check that port is valid. */ + if (tp->local_name.port <= 0) { + return -430; + } + + /* Check length of address (for now we only check against sockaddr_in). */ + if (tp->addr_len != sizeof(pj_sockaddr_in)) + return -440; + + /* Check type. */ + if (tp->key.type == PJSIP_TRANSPORT_UNSPECIFIED) + return -450; + + /* That's it. */ + return PJ_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Send/receive test. + * + * This test sends a request to loopback address; as soon as request is + * received, response will be sent, and time is recorded. + * + * The main purpose is to test that the basic transport functionalities works, + * before we continue with more complicated tests. + */ +#define FROM_HDR "Bob <sip:bob@example.com>" +#define TO_HDR "Alice <sip:alice@example.com>" +#define CONTACT_HDR "Bob <sip:bob@127.0.0.1>" +#define CALL_ID_HDR "SendRecv-Test" +#define CSEQ_VALUE 100 +#define BODY "Hello World!" + +static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata); + +/* Flag to indicate message has been received + * (or failed to send) + */ +#define NO_STATUS -2 +static int send_status = NO_STATUS; +static int recv_status = NO_STATUS; +static pj_timestamp my_send_time, my_recv_time; + +/* Module to receive messages for this test. */ +static pjsip_module my_module = +{ + NULL, NULL, /* prev and next */ + { "Transport-Test", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* User data. */ + 0, /* Number of methods supported (=0). */ + { 0 }, /* Array of methods (none) */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &my_on_rx_request, /* on_rx_request() */ + &my_on_rx_response, /* on_rx_response() */ + NULL, /* tsx_handler() */ +}; + + +static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata) +{ + /* Check that this is our request. */ + if (pj_strcmp2(&rdata->msg_info.call_id, CALL_ID_HDR) == 0) { + /* It is! */ + /* Send response. */ + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pj_status_t status; + + PJ_LOG(4,("test", "Received %d bytes request: --begin-\n" + "%s\n" + "--end--", + rdata->msg_info.len, + rdata->msg_info.msg_buf)); + + + status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) { + recv_status = status; + return PJ_TRUE; + } + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + recv_status = 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) { + recv_status = status; + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + return PJ_TRUE; + } + + /* Not ours. */ + return PJ_FALSE; +} + +static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata) +{ + if (pj_strcmp2(&rdata->msg_info.call_id, CALL_ID_HDR) == 0) { + PJ_LOG(4,("test", "Received %d bytes response: --begin-\n" + "%s\n" + "--end--", + rdata->msg_info.len, + rdata->msg_info.msg_buf)); + + pj_get_timestamp(&my_recv_time); + recv_status = PJ_SUCCESS; + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* Transport callback. */ +static void send_msg_callback(pjsip_send_state *stateless_data, + pj_ssize_t sent, pj_bool_t *cont) +{ + if (sent < 1) { + /* Obtain the error code. */ + send_status = -sent; + } else { + send_status = PJ_SUCCESS; + } + + /* Don't want to continue. */ + *cont = PJ_FALSE; +} + + +/* 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 ) +{ + 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; + pj_time_val timeout; + + PJ_LOG(3,("", " single message round-trip test...")); + + /* Register out test module to receive the message (if necessary). */ + if (my_module.id == -1) { + status = pjsip_endpt_register_module( endpt, &my_module ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to register module", status); + return -500; + } + } + + /* 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); + from = pj_str(FROM_HDR); + to = pj_str(TO_HDR); + contact = pj_str(CONTACT_HDR); + call_id = pj_str(CALL_ID_HDR); + body = pj_str(BODY); + + pjsip_method_set(&method, PJSIP_OPTIONS_METHOD); + status = pjsip_endpt_create_request( endpt, &method, &target, &from, &to, + &contact, &call_id, CSEQ_VALUE, + &body, &tdata ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -510; + } + + /* Reset statuses */ + send_status = recv_status = NO_STATUS; + + /* Start time. */ + pj_get_timestamp(&my_send_time); + + /* Send the message (statelessly). */ + status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, + &send_msg_callback); + if (status != PJ_SUCCESS) { + /* Immediate error! */ + pjsip_tx_data_dec_ref(tdata); + send_status = status; + } + + /* Set the timeout (1 second from now) */ + pj_gettimeofday(&timeout); + timeout.sec += 1; + + /* Loop handling events until we get status */ + do { + pj_time_val now; + pj_time_val poll_interval = { 0, 10 }; + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, timeout)) { + PJ_LOG(3,("", " error: timeout in send/recv test")); + status = -540; + goto on_return; + } + + if (send_status!=NO_STATUS && send_status!=PJ_SUCCESS) { + app_perror(" error sending message", send_status); + status = -550; + goto on_return; + } + + if (recv_status!=NO_STATUS && recv_status!=PJ_SUCCESS) { + app_perror(" error receiving message", recv_status); + status = -560; + goto on_return; + } + + if (send_status!=NO_STATUS && recv_status!=NO_STATUS) { + /* Success! */ + break; + } + + pjsip_endpt_handle_events(endpt, &poll_interval); + + } while (1); + + if (status == PJ_SUCCESS) { + unsigned usec_rt; + usec_rt = pj_elapsed_usec(&my_send_time, &my_recv_time); + PJ_LOG(3,("", " round-trip = %d usec", usec_rt)); + } + + status = PJ_SUCCESS; + +on_return: + return status; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Multithreaded round-trip test + * + * This test will spawn multiple threads, each of them send a request. As soon + * as request is received, response will be sent, and time is recorded. + * + * The main purpose of this test is to ensure there's no crash when multiple + * threads are sending/receiving messages. + * + */ +static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata); + +static pjsip_module rt_module = +{ + NULL, NULL, /* prev and next */ + { "Transport-RT-Test", 17}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* User data. */ + 0, /* Number of methods supported (=0). */ + { 0 }, /* Array of methods (none) */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &rt_on_rx_request, /* on_rx_request() */ + &rt_on_rx_response, /* on_rx_response() */ + NULL, /* tsx_handler() */ +}; + +static struct +{ + pj_thread_t *thread; + pj_timestamp send_time; + pj_timestamp total_rt_time; + int sent_request_count, recv_response_count; + pj_str_t call_id; +} rt_test_data[16]; + +static char rt_target_uri[32]; +static pj_bool_t rt_stop; +static pj_str_t rt_call_id; + +static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata) +{ + if (!pj_strncmp(&rdata->msg_info.call_id, &rt_call_id, rt_call_id.slen)) { + char *pos = pj_strchr(&rdata->msg_info.call_id, '/'); + int thread_id = (*pos - '0'); + + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) { + return PJ_TRUE; + } + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + return PJ_TRUE; + + } + return PJ_FALSE; +} + +static pj_status_t rt_send_request(int thread_id) +{ + pj_status_t status; + pj_str_t target, from, to, contact, call_id; + pjsip_tx_data *tdata; + + /* Create a request message. */ + target = pj_str(rt_target_uri); + from = pj_str(FROM_HDR); + to = pj_str(TO_HDR); + contact = pj_str(CONTACT_HDR); + call_id = rt_test_data[thread_id].call_id; + + status = pjsip_endpt_create_request( endpt, &pjsip_options_method, + &target, &from, &to, + &contact, &call_id, -1, + NULL, &tdata ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -610; + } + + /* Start time. */ + pj_get_timestamp(&rt_test_data[thread_id].send_time); + + /* Send the message (statelessly). */ + status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + /* Immediate error! */ + app_perror(" error: send request", status); + pjsip_tx_data_dec_ref(tdata); + return -620; + } + + /* Update counter. */ + rt_test_data[thread_id].sent_request_count++; + + return PJ_SUCCESS; +} + +static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata) +{ + if (!pj_strncmp(&rdata->msg_info.call_id, &rt_call_id, rt_call_id.slen)) { + char *pos = pj_strchr(&rdata->msg_info.call_id, '/')+1; + int thread_id = (*pos - '0'); + pj_timestamp recv_time; + + /* Update counter and end-time. */ + rt_test_data[thread_id].recv_response_count++; + pj_get_timestamp(&recv_time); + + pj_sub_timestamp(&recv_time, &rt_test_data[thread_id].send_time); + pj_add_timestamp(&rt_test_data[thread_id].total_rt_time, &recv_time); + + if (!rt_stop) + rt_send_request(thread_id); + return PJ_TRUE; + } + return PJ_FALSE; +} + +static int rt_thread(void *arg) +{ + int thread_id = (int)arg; + pj_time_val poll_delay = { 0, 10 }; + + /* Sleep to allow main threads to run. */ + pj_thread_sleep(10); + + /* Send the first request. */ + if (rt_send_request(thread_id) != PJ_SUCCESS) + return -1; + + while (!rt_stop) { + 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 ) +{ + enum { THREADS = 4, INTERVAL = 10 }; + int i; + pj_status_t status; + pj_pool_t *pool; + pj_bool_t is_reliable; + + pj_timestamp zero_time, total_time; + unsigned usec_rt; + unsigned total_sent; + unsigned total_recv; + + + PJ_LOG(3,("", " multithreaded round-trip test (%d threads)...", + THREADS)); + PJ_LOG(3,("", " this will take approx %d seconds, please wait..", INTERVAL)); + + is_reliable = (pjsip_transport_get_flag_from_type(tp_type) & PJSIP_TRANSPORT_RELIABLE); + + /* Register module (if not yet registered) */ + if (rt_module.id == -1) { + status = pjsip_endpt_register_module( endpt, &rt_module ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to register module", status); + return -600; + } + } + + /* Create pool for this test. */ + pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000); + if (!pool) + 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)); + rt_call_id = pj_str("RT-Call-Id/"); + rt_stop = PJ_FALSE; + + /* Initialize thread data. */ + for (i=0; i<THREADS; ++i) { + char buf[1]; + pj_str_t str_id = { buf, 1 }; + + pj_memset(&rt_test_data[i], 0, sizeof(rt_test_data[i])); + + /* Generate Call-ID for each thread. */ + rt_test_data[i].call_id.ptr = pj_pool_alloc(pool, rt_call_id.slen+1); + pj_strcpy(&rt_test_data[i].call_id, &rt_call_id); + buf[0] = '0' + i; + pj_strcat(&rt_test_data[i].call_id, &str_id); + + /* Create thread, suspended. */ + status = pj_thread_create(pool, "rttest%p", &rt_thread, (void*)i, 0, + PJ_THREAD_SUSPENDED, &rt_test_data[i].thread); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create thread", status); + return -620; + } + } + + /* Start threads! */ + for (i=0; i<THREADS; ++i) { + pj_thread_resume(rt_test_data[i].thread); + } + + /* Sleep for some time. */ + pj_thread_sleep(INTERVAL * 1000); + + /* Signal thread to stop. */ + rt_stop = PJ_TRUE; + + /* Wait threads to complete. */ + for (i=0; i<THREADS; ++i) { + pj_thread_join(rt_test_data[i].thread); + pj_thread_destroy(rt_test_data[i].thread); + } + + /* Gather statistics. */ + pj_memset(&total_time, 0, sizeof(total_time)); + pj_memset(&zero_time, 0, sizeof(zero_time)); + usec_rt = total_sent = total_recv = 0; + for (i=0; i<THREADS; ++i) { + total_sent += rt_test_data[i].sent_request_count; + total_recv += rt_test_data[i].recv_response_count; + pj_add_timestamp(&total_time, &rt_test_data[i].total_rt_time); + } + + /* Display statistics. */ + if (total_recv) + total_time.u64 = total_time.u64/total_recv; + else + total_time.u64 = 0; + usec_rt = pj_elapsed_usec(&zero_time, &total_time); + PJ_LOG(3,("", " done.")); + PJ_LOG(3,("", " total %d messages sent", total_sent)); + if (total_sent-total_recv) + PJ_LOG(2,("", " total %d messages LOST", total_sent-total_recv)); + else + PJ_LOG(3,("", " no message was lost")); + PJ_LOG(3,("", " average round-trip=%d usec", usec_rt)); + + pjsip_endpt_destroy_pool(endpt, pool); + + if (is_reliable && (total_sent != total_recv)) { + PJ_LOG(3,("", " error: %d messages lost", total_sent-total_recv)); + return -650; + } + return 0; +} diff --git a/pjsip/src/test-pjsip/transport_udp_test.c b/pjsip/src/test-pjsip/transport_udp_test.c new file mode 100644 index 00000000..1af74ed0 --- /dev/null +++ b/pjsip/src/test-pjsip/transport_udp_test.c @@ -0,0 +1,98 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * 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 <pjsip_core.h> +#include <pjlib.h> + + +/* + * UDP transport test. + */ +int transport_udp_test(void) +{ + enum { SEND_RECV_LOOP = 2 }; + pjsip_transport *udp_tp, *tp; + pj_sockaddr_in addr, rem_addr; + pj_str_t s; + pj_status_t status; + int i; + + pj_sockaddr_in_init(&addr, NULL, TEST_UDP_PORT); + + /* Start UDP transport. */ + status = pjsip_udp_transport_start( endpt, &addr, NULL, 1, &udp_tp); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to start UDP transport", status); + return -10; + } + + /* UDP transport must have initial reference counter set to 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -20; + + /* Test basic transport attributes */ + status = generic_transport_test(udp_tp); + if (status != PJ_SUCCESS) + return status; + + /* Test that transport manager is returning the correct + * transport. + */ + pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80); + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, + &rem_addr, sizeof(rem_addr), + &tp); + if (status != PJ_SUCCESS) + return -50; + if (tp != udp_tp) + return -60; + + /* pjsip_endpt_acquire_transport() adds reference, so we need + * to decrement it. + */ + pjsip_transport_dec_ref(tp); + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -70; + + /* 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<SEND_RECV_LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_UDP, tp, &rem_addr); + if (status != 0) + return status; + } + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_UDP, tp, &rem_addr); + if (status != 0) + return status; + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -80; + + /* Destroy this transport. */ + pjsip_transport_dec_ref(udp_tp); + + /* Done */ + return 0; +} diff --git a/pjsip/src/test-pjsip/txdata_test.c b/pjsip/src/test-pjsip/txdata_test.c new file mode 100644 index 00000000..08cad024 --- /dev/null +++ b/pjsip/src/test-pjsip/txdata_test.c @@ -0,0 +1,320 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * 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 <pjsip_core.h> +#include <pjlib.h> + +#define HFIND(msg,h,H) ((pjsip_##h##_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_##H, NULL)) + +/* + * This tests various core message creation functions. + */ +int txdata_test(void) +{ + pj_status_t status; + pj_str_t target, from, to, contact, body; + pjsip_rx_data dummy_rdata; + pjsip_tx_data *invite, *invite2, *cancel, *response, *ack; + + /* Create INVITE request. */ + target = pj_str("tel:+1"); + from = pj_str("tel:+0"); + to = pj_str("tel:+1"); + contact = pj_str("Bob <sip:+0@example.com;user=phone>"); + body = pj_str("Hello world!"); + + status = pjsip_endpt_create_request( endpt, &pjsip_invite_method, &target, + &from, &to, &contact, NULL, 10, &body, + &invite); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -10; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(invite) != 0) { + PJ_LOG(3,("", " error: buffer must be invalid")); + return -14; + } + /* Reference counter must be set to 1. */ + if (pj_atomic_get(invite->ref_cnt) != 1) { + PJ_LOG(3,("", " error: invalid reference counter")); + return -15; + } + /* Check message type. */ + if (invite->msg->type != PJSIP_REQUEST_MSG) + return -16; + /* Check method. */ + if (invite->msg->line.req.method.id != PJSIP_INVITE_METHOD) + return -17; + + /* Check that mandatory headers are present. */ + if (HFIND(invite->msg, from, FROM) == 0) + return -20; + if (HFIND(invite->msg, to, TO) == 0) + return -21; + if (HFIND(invite->msg, contact, CONTACT) == 0) + return -22; + if (HFIND(invite->msg, cid, CALL_ID) == 0) + return -23; + if (HFIND(invite->msg, cseq, CSEQ) == 0) + return -24; + do { + pjsip_via_hdr *via = HFIND(invite->msg, via, VIA); + if (via == NULL) + return -25; + /* Branch param must be empty. */ + if (via->branch_param.slen != 0) + return -26; + } while (0); + if (invite->msg->body == NULL) + return -28; + + /* Create another INVITE request from first request. */ + status = pjsip_endpt_create_request_from_hdr( endpt, &pjsip_invite_method, + invite->msg->line.req.uri, + HFIND(invite->msg,from,FROM), + HFIND(invite->msg,to,TO), + HFIND(invite->msg,contact,CONTACT), + HFIND(invite->msg,cid,CALL_ID), + 10, &body, &invite2); + if (status != PJ_SUCCESS) { + app_perror(" error: create second request failed", status); + return -30; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(invite2) != 0) { + PJ_LOG(3,("", " error: buffer must be invalid")); + return -34; + } + /* Reference counter must be set to 1. */ + if (pj_atomic_get(invite2->ref_cnt) != 1) { + PJ_LOG(3,("", " error: invalid reference counter")); + return -35; + } + /* Check message type. */ + if (invite2->msg->type != PJSIP_REQUEST_MSG) + return -36; + /* Check method. */ + if (invite2->msg->line.req.method.id != PJSIP_INVITE_METHOD) + return -37; + + /* Check that mandatory headers are again present. */ + if (HFIND(invite2->msg, from, FROM) == 0) + return -40; + if (HFIND(invite2->msg, to, TO) == 0) + return -41; + if (HFIND(invite2->msg, contact, CONTACT) == 0) + return -42; + if (HFIND(invite2->msg, cid, CALL_ID) == 0) + return -43; + if (HFIND(invite2->msg, cseq, CSEQ) == 0) + return -44; + if (HFIND(invite2->msg, via, VIA) == 0) + return -45; + /* + if (HFIND(invite2->msg, ctype, CONTENT_TYPE) == 0) + return -46; + if (HFIND(invite2->msg, clen, CONTENT_LENGTH) == 0) + return -47; + */ + if (invite2->msg->body == NULL) + return -48; + + /* Done checking invite2. We can delete this. */ + if (pjsip_tx_data_dec_ref(invite2) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,("", " error: request buffer not destroyed!")); + return -49; + } + + /* Initialize dummy rdata (to simulate receiving a request) + * We should never do this in real application, as there are many + * many more fields need to be initialized!! + */ + dummy_rdata.msg_info.call_id = (HFIND(invite->msg, cid, CALL_ID))->id; + dummy_rdata.msg_info.clen = NULL; + dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ); + dummy_rdata.msg_info.ctype = NULL; + dummy_rdata.msg_info.from = HFIND(invite->msg, from, FROM); + dummy_rdata.msg_info.max_fwd = NULL; + dummy_rdata.msg_info.msg = invite->msg; + dummy_rdata.msg_info.record_route = NULL; + dummy_rdata.msg_info.require = NULL; + dummy_rdata.msg_info.route = NULL; + dummy_rdata.msg_info.to = HFIND(invite->msg, to, TO); + dummy_rdata.msg_info.via = HFIND(invite->msg, via, VIA); + + /* Create a response message for the request. */ + status = pjsip_endpt_create_response( endpt, &dummy_rdata, 301, NULL, + &response); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + return -50; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(response) != 0) { + PJ_LOG(3,("", " error: buffer must be invalid")); + return -54; + } + /* Check reference counter. */ + if (pj_atomic_get(response->ref_cnt) != 1) { + PJ_LOG(3,("", " error: invalid ref count in response")); + return -55; + } + /* Check message type. */ + if (response->msg->type != PJSIP_RESPONSE_MSG) + return -56; + /* Check correct status is set. */ + if (response->msg->line.status.code != 301) + return -57; + + /* Check that mandatory headers are again present. */ + if (HFIND(response->msg, from, FROM) == 0) + return -60; + if (HFIND(response->msg, to, TO) == 0) + return -61; + /* + if (HFIND(response->msg, contact, CONTACT) == 0) + return -62; + */ + if (HFIND(response->msg, cid, CALL_ID) == 0) + return -63; + if (HFIND(response->msg, cseq, CSEQ) == 0) + return -64; + if (HFIND(response->msg, via, VIA) == 0) + return -65; + + /* This response message will be used later when creating ACK */ + + /* Create CANCEL request for the original request. */ + status = pjsip_endpt_create_cancel( endpt, invite, &cancel); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create CANCEL request", status); + return -80; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(cancel) != 0) { + PJ_LOG(3,("", " error: buffer must be invalid")); + return -84; + } + /* Check reference counter. */ + if (pj_atomic_get(cancel->ref_cnt) != 1) { + PJ_LOG(3,("", " error: invalid ref count in CANCEL request")); + return -85; + } + /* Check message type. */ + if (cancel->msg->type != PJSIP_REQUEST_MSG) + return -86; + /* Check method. */ + if (cancel->msg->line.req.method.id != PJSIP_CANCEL_METHOD) + return -87; + + /* Check that mandatory headers are again present. */ + if (HFIND(cancel->msg, from, FROM) == 0) + return -90; + if (HFIND(cancel->msg, to, TO) == 0) + return -91; + /* + if (HFIND(cancel->msg, contact, CONTACT) == 0) + return -92; + */ + if (HFIND(cancel->msg, cid, CALL_ID) == 0) + return -93; + if (HFIND(cancel->msg, cseq, CSEQ) == 0) + return -94; + if (HFIND(cancel->msg, via, VIA) == 0) + return -95; + + /* Done checking CANCEL request. */ + if (pjsip_tx_data_dec_ref(cancel) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,("", " error: response buffer not destroyed!")); + return -99; + } + + /* Modify dummy_rdata to simulate receiving response. */ + pj_memset(&dummy_rdata, 0, sizeof(dummy_rdata)); + dummy_rdata.msg_info.msg = response->msg; + dummy_rdata.msg_info.to = HFIND(response->msg, to, TO); + + /* Create ACK request */ + status = pjsip_endpt_create_ack( endpt, invite, &dummy_rdata, &ack ); + if (status != PJ_SUCCESS) { + PJ_LOG(3,("", " error: unable to create ACK")); + return -100; + } + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(ack) != 0) { + PJ_LOG(3,("", " error: buffer must be invalid")); + return -104; + } + /* Check reference counter. */ + if (pj_atomic_get(ack->ref_cnt) != 1) { + PJ_LOG(3,("", " error: invalid ref count in ACK request")); + return -105; + } + /* Check message type. */ + if (ack->msg->type != PJSIP_REQUEST_MSG) + return -106; + /* Check method. */ + if (ack->msg->line.req.method.id != PJSIP_ACK_METHOD) + return -107; + /* Check Request-URI is present. */ + if (ack->msg->line.req.uri == NULL) + return -108; + + /* Check that mandatory headers are again present. */ + if (HFIND(ack->msg, from, FROM) == 0) + return -110; + if (HFIND(ack->msg, to, TO) == 0) + return -111; + if (HFIND(ack->msg, cid, CALL_ID) == 0) + return -112; + if (HFIND(ack->msg, cseq, CSEQ) == 0) + return -113; + if (HFIND(ack->msg, via, VIA) == 0) + return -114; + if (ack->msg->body != NULL) + return -115; + + /* Done checking invite message. */ + if (pjsip_tx_data_dec_ref(invite) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,("", " error: response buffer not destroyed!")); + return -120; + } + + /* Done checking response message. */ + if (pjsip_tx_data_dec_ref(response) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,("", " error: response buffer not destroyed!")); + return -130; + } + + /* Done checking ack message. */ + if (pjsip_tx_data_dec_ref(ack) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,("", " error: response buffer not destroyed!")); + return -140; + } + + /* Done. */ + return 0; +} + diff --git a/pjsip/src/test-pjsip/uri_test.c b/pjsip/src/test-pjsip/uri_test.c index 29a16dd5..647397a2 100644 --- a/pjsip/src/test-pjsip/uri_test.c +++ b/pjsip/src/test-pjsip/uri_test.c @@ -783,7 +783,7 @@ on_return: return status; } -pj_status_t uri_test() +int uri_test() { unsigned i, loop; pj_pool_t *pool; |