diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-02-07 12:34:11 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-02-07 12:34:11 +0000 |
commit | 9cded7c4883dc2eccfddf1b74b8175ac179d5703 (patch) | |
tree | 2d2172ce3a662c0d1ff779c11477d7b3c96d1cbf /pjsip | |
parent | 493e580f04c988e607a4fd21f369f55fa6685cb9 (diff) |
Initial implementation of invite session abstraction, and updated pjsua for the new framework
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@139 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/build/pjsip_ua.dsp | 12 | ||||
-rw-r--r-- | pjsip/build/pjsua.dsp | 13 | ||||
-rw-r--r-- | pjsip/include/pjsip-ua/sip_inv.h | 507 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_dialog.h | 1 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_event.h | 79 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_msg.h | 57 | ||||
-rw-r--r-- | pjsip/include/pjsip_ua.h | 5 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_inv.c | 1327 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_dialog.c | 64 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_endpoint.c | 9 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_msg.c | 34 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transaction.c | 102 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_ua_layer.c | 36 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_util.c | 4 | ||||
-rw-r--r-- | pjsip/src/pjsua/getopt.c | 2 | ||||
-rw-r--r-- | pjsip/src/pjsua/main.c | 1871 | ||||
-rw-r--r-- | pjsip/src/pjsua/pjsua.c | 648 | ||||
-rw-r--r-- | pjsip/src/pjsua/pjsua.h | 136 |
18 files changed, 3051 insertions, 1856 deletions
diff --git a/pjsip/build/pjsip_ua.dsp b/pjsip/build/pjsip_ua.dsp index c5986076..669a805c 100644 --- a/pjsip/build/pjsip_ua.dsp +++ b/pjsip/build/pjsip_ua.dsp @@ -41,7 +41,7 @@ RSC=rc.exe # PROP Intermediate_Dir ".\output\pjsip_ua_vc6_Release"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
-# ADD CPP /nologo /MD /W4 /Zi /O2 /I "../src" /I "../../pjlib/src" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FR /FD /c
+# ADD CPP /nologo /MD /W4 /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
@@ -65,7 +65,7 @@ LIB32=link.exe -lib # PROP Intermediate_Dir ".\output\pjsip_ua_vc6_Debug"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../src" /I "../../pjlib/src" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
@@ -87,6 +87,10 @@ LIB32=link.exe -lib # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
+SOURCE="..\src\pjsip-ua\sip_inv.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\pjsip-ua\sip_reg.c"
!IF "$(CFG)" == "pjsip_ua - Win32 Release"
@@ -104,6 +108,10 @@ SOURCE="..\src\pjsip-ua\sip_reg.c" # PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
+SOURCE="..\include\pjsip-ua\sip_inv.h"
+# End Source File
+# Begin Source File
+
SOURCE="..\include\pjsip-ua\sip_regc.h"
# End Source File
# End Group
diff --git a/pjsip/build/pjsua.dsp b/pjsip/build/pjsua.dsp index e355d9ae..7b79266f 100644 --- a/pjsip/build/pjsua.dsp +++ b/pjsip/build/pjsua.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../src" /I "../../pjlib/src" /I "../../pjmedia/src" /I "../../pjsdp/src" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FR /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
@@ -68,7 +68,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../src" /I "../../pjlib/src" /I "../../pjmedia/src" /I "../../pjsdp/src" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
@@ -95,6 +95,15 @@ SOURCE=..\src\pjsua\getopt.c # Begin Source File
SOURCE=..\src\pjsua\main.c
+
+!IF "$(CFG)" == "pjsua - Win32 Release"
+
+!ELSEIF "$(CFG)" == "pjsua - Win32 Debug"
+
+# PROP Exclude_From_Build 1
+
+!ENDIF
+
# End Source File
# Begin Source File
diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h new file mode 100644 index 00000000..26a38972 --- /dev/null +++ b/pjsip/include/pjsip-ua/sip_inv.h @@ -0,0 +1,507 @@ +/* $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 + */ +#ifndef __SIP_INVITE_SESSION_H__ +#define __SIP_INVITE_SESSION_H__ + +#include <pjsip/sip_dialog.h> +#include <pjmedia/sdp_neg.h> + +typedef enum pjsip_inv_state pjsip_inv_state; +typedef struct pjsip_inv_session pjsip_inv_session; +typedef struct pjsip_inv_callback pjsip_inv_callback; + +/** + * This enumeration describes invite session state. + */ +enum pjsip_inv_state +{ + PJSIP_INV_STATE_NULL, /**< Before INVITE is sent or received */ + PJSIP_INV_STATE_CALLING, /**< After INVITE is sent */ + PJSIP_INV_STATE_INCOMING, /**< After INVITE is received. */ + PJSIP_INV_STATE_EARLY, /**< After response with To tag. */ + PJSIP_INV_STATE_CONNECTING, /**< After 2xx is sent/received. */ + PJSIP_INV_STATE_CONFIRMED, /**< After ACK is sent/received. */ + PJSIP_INV_STATE_DISCONNECTED, /**< Session is terminated. */ + PJSIP_INV_STATE_TERMINATED, /**< Session will be destroyed soon. */ +}; + +/** + * This structure contains callbacks to be registered by application to + * receieve notifications from the framework about various events in + * the invite session. + */ +struct pjsip_inv_callback +{ + /** + * This callback is called when the invite sesion state has changed. + * Application should inspect the session state (inv_sess->state) to get + * the current state of the session. + * + * This callback is mandatory. + * + * @param inv The invite session. + * @param e The event which has caused the invite session's + * state to change. + */ + void (*on_state_changed)(pjsip_inv_session *inv, pjsip_event *e); + + + /** + * This callback is called when the invite usage module has created + * a new dialog and invite because of forked outgoing request. + * + * This callback is mandatory. + * + * @param inv The new invite session. + * @param e The event which has caused the dialog to fork. + * The type of this event can be either + * PJSIP_EVENT_RX_MSG or PJSIP_EVENT_RX_200_MSG. + */ + void (*on_new_session)(pjsip_inv_session *inv, pjsip_event *e); + + /** + * This callback is called whenever any transactions within the session + * has changed their state. Application MAY implement this callback, + * e.g. to monitor the progress of an outgoing request. + * + * This callback is optional. + * + * @param inv The invite session. + * @param tsx The transaction, which state has changed. + * @param e The event which has caused the transation state's + * to change. + */ + void (*on_tsx_state_changed)(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e); + + /** + * This callback is called when the invite session has received + * new offer from peer. Application can inspect the remote offer + * by calling negotiator's pjmedia_sdp_neg_get_neg_remote(), and + * optionally specify a modified answer. + * + * This callback is optional. When it's not specified, the default + * behavior is nothing. After calling this callback, the negotiator + * will negotiate remote offer with session's initial capability. + * + * @param inv The invite session. + */ + void (*on_rx_offer)(pjsip_inv_session *inv); + + /** + * This callback is called after SDP offer/answer session has completed. + * The status argument specifies the status of the offer/answer, + * as returned by pjmedia_sdp_neg_negotiate(). + * + * This callback is optional (from the point of view of the framework), + * but all useful applications normally need to implement this callback. + * + * @param inv The invite session. + * @param status The negotiation status. + */ + void (*on_media_update)(pjsip_inv_session *inv_ses, + pj_status_t status); + +}; + + +/** + * This enumeration shows various options that can be applied to a session. + * The bitmask combination of these options need to be specified when + * creating a session. After the dialog is established (including early), + * the options member of #pjsip_inv_session shows which capabilities are + * common in both endpoints. + */ +enum pjsip_inv_option +{ + /** + * Indicate support for reliable provisional response extension + */ + PJSIP_INV_SUPPORT_100REL = 1, + + /** + * Indicate support for session timer extension. + */ + PJSIP_INV_SUPPORT_TIMER = 2, + + /** + * Indicate support for UPDATE method. This is automatically implied + * when creating outgoing dialog. After the dialog is established, + * the options member of #pjsip_inv_session shows whether peer supports + * this method as well. + */ + PJSIP_INV_SUPPORT_UPDATE = 4, + + /** + * Require reliable provisional response extension. + */ + PJSIP_INV_REQUIRE_100REL = 32, + + /** + * Require session timer extension. + */ + PJSIP_INV_REQUIRE_TIMER = 64, + +}; + + +/** + * This structure describes the invite session. + */ +struct pjsip_inv_session +{ + pj_pool_t *pool; + pjsip_inv_state state; + pjsip_dialog *dlg; + pjsip_role_e role; + unsigned options; + pjmedia_sdp_neg *neg; + pjsip_transaction *invite_tsx; + void *mod_data[PJSIP_MAX_MODULE]; +}; + + +/** + * Initialize the invite usage module and register it to the endpoint. + * The callback argument contains pointer to functions to be called on + * occurences of events in invite sessions. + * + * @param endpt The endpoint instance. + * @param app_module Application module. + * @param callback Callback structure. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsip_inv_usage_init(pjsip_endpoint *endpt, + pjsip_module *app_module, + const pjsip_inv_callback *cb); + +/** + * Get the INVITE usage module instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pjsip_module*) pjsip_inv_usage_instance(void); + + + +/** + * Create UAC invite session for the specified dialog in dlg. + * + * @param dlg The dialog which will be used by this invite session. + * @param local_sdp If application has determined its media capability, + * it can specify the SDP here. Otherwise it can leave + * this to NULL, to let remote UAS specifies an offer. + * @param options The options argument is bitmask combination of SIP + * features in pjsip_inv_options enumeration. + * @param p_inv On successful return, the invite session will be put + * in this argument. + * + * @return The function will return PJ_SUCCESS if it can create + * the session. Otherwise the appropriate error status + * will be returned on failure. + */ +PJ_DECL(pj_status_t) pjsip_inv_create_uac(pjsip_dialog *dlg, + const pjmedia_sdp_session *local_sdp, + unsigned options, + pjsip_inv_session **p_inv); + + +/** + * Application SHOULD call this function upon receiving the initial INVITE + * request in rdata before creating the invite session (or even dialog), + * to verify that the invite session can handle the INVITE request. + * This function verifies that local endpoint is capable to handle required + * SIP extensions in the request (i.e. Require header) and also the media, + * if media description is present in the request. + * + * @param rdata The incoming INVITE request. + * + * @param options Upon calling this function, the options argument + * MUST contain the desired SIP extensions to be + * applied to the session. Upon return, this argument + * will contain the SIP extension that will be applied + * to the session, after considering the Supported, + * Require, and Allow headers in the request. + * + * @param sdp If local media capability has been determined, + * and if application wishes to verify that it can + * handle the media offer in the incoming INVITE + * request, it SHOULD specify its local media capability + * in this argument. + * If it is not specified, media verification will not + * be performed by this function. + * + * @param dlg If tdata is not NULL, application needs to specify + * how to create the response. Either dlg or endpt + * argument MUST be specified, with dlg argument takes + * precedence when both are specified. + * + * If a dialog has been created prior to calling this + * function, then it MUST be specified in dlg argument. + * Otherwise application MUST specify the endpt argument + * (this is useful e.g. when application wants to send + * the response statelessly). + * + * @param endpt If tdata is not NULL, application needs to specify + * how to create the response. Either dlg or endpt + * argument MUST be specified, with dlg argument takes + * precedence when both are specified. + * + * @param tdata If this argument is not NULL, this function will + * create the appropriate non-2xx final response message + * when the verification fails. + * + * @return If everything has been negotiated successfully, + * the function will return PJ_SUCCESS. Otherwise it + * will return the reason of the failure as the return + * code. + * + * This function is capable to create the appropriate + * response message when the verification has failed. + * If tdata is specified, then a non-2xx final response + * will be created and put in this argument upon return, + * when the verification has failed. + * + * If a dialog has been created prior to calling this + * function, then it MUST be specified in dlg argument. + * Otherwise application MUST specify the endpt argument + * (this is useful e.g. when application wants to send + * the response statelessly). + */ +PJ_DECL(pj_status_t) pjsip_inv_verify_request( pjsip_rx_data *rdata, + unsigned *options, + const pjmedia_sdp_session *sdp, + pjsip_dialog *dlg, + pjsip_endpoint *endpt, + pjsip_tx_data **tdata); + + +/** + * Create UAS invite session for the specified dialog in dlg. Application + * SHOULD call the verification function before calling this function, + * to ensure that it can create the session successfully. + * + * @param dlg The dialog to be used. + * @param rdata Application MUST specify the received INVITE request + * in rdata. The invite session needs to inspect the + * received request to see if the request contains + * features that it supports. + * @param sdp If application has determined its media capability, + * it can specify this capability in this argument. + * If SDP is received in the initial INVITE, the UAS + * capability specified in this argument doesn't have to + * match the received offer; the SDP negotiator is able + * to rearrange the media lines in the answer so that it + * matches the offer. + * @param options The options argument is bitmask combination of SIP + * features in pjsip_inv_options enumeration. + * @param p_inv Pointer to receive the newly created invite session. + * + * @return On successful, the invite session will be put in + * p_inv argument and the function will return PJ_SUCCESS. + * Otherwise the appropriate error status will be returned + * on failure. + */ +PJ_DECL(pj_status_t) pjsip_inv_create_uas(pjsip_dialog *dlg, + pjsip_rx_data *rdata, + const pjmedia_sdp_session *local_sdp, + unsigned options, + pjsip_inv_session **p_inv); + + +/** + * Create the initial INVITE request for this session. This function can only + * be called for UAC session. If local media capability is specified when + * the invite session was created, then this function will put an SDP offer + * in the outgoing INVITE request. Otherwise the outgoing request will not + * contain SDP body. + * + * @param inv The UAC invite session. + * @param p_tdata The initial INVITE request will be put in this + * argument if it can be created successfully. + * + * @return PJ_SUCCESS if the INVITE request can be created. + */ +PJ_DECL(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv, + pjsip_tx_data **p_tdata ); + + +/** + * Create a response message to the initial INVITE request. This function + * can only be called for the initial INVITE request, as subsequent + * re-INVITE request will be answered automatically. + * + * @param inv The UAS invite session. + * @param st_code The st_code contains the status code to be sent, + * which may be a provisional or final response. + * @param st_text If custom status text is desired, application can + * specify the text in st_text; otherwise if this + * argument is NULL, default status text will be used. + * @param local_sdp If application has specified its media capability + * during creation of UAS invite session, the local_sdp + * argument MUST be NULL. This is because application + * can not perform more than one SDP offer/answer session + * in a single INVITE transaction. + * If application has not specified its media capability + * during creation of UAS invite session, it MAY or MUST + * specify its capability in local_sdp argument, + * depending whether st_code indicates a 2xx final + * response. + * @param p_tdata Pointer to receive the response message created by + * this function. + * + * @return PJ_SUCCESS if response message was created + * successfully. + */ +PJ_DECL(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, + int st_code, + const pj_str_t *st_text, + const pjmedia_sdp_session *local_sdp, + pjsip_tx_data **p_tdata ); + + +/** + * Create a SIP message to initiate invite session termination. Depending on + * the state of the session, this function may return CANCEL request, + * a non-2xx final response, or a BYE request. If the session has not answered + * the incoming INVITE, this function creates the non-2xx final response with + * the specified status code in st_code and optional status text in st_text. + * + * @param inv The invite session. + * @param st_code Status code to be used for terminating the session. + * @param st_text Optional status text. + * @param p_tdata Pointer to receive the message to be created. + * + * @return PJ_SUCCESS if termination message can be created. + */ +PJ_DECL(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv, + int st_code, + const pj_str_t *st_text, + pjsip_tx_data **p_tdata ); + + + +/** + * Create a re-INVITE request. + * + * @param inv The invite session. + * @param new_contact If application wants to update its local contact and + * inform peer to perform target refresh with a new + * contact, it can specify the new contact in this + * argument; otherwise this argument must be NULL. + * @param new_offer Application MAY initiate a new SDP offer/answer + * session in the request when there is no pending + * answer to be sent or received. It can detect this + * condition by observing the state of the SDP + * negotiator of the invite session. If new offer + * should be sent to remote, the offer must be specified + * in this argument, otherwise it must be NULL. + * @param p_tdata Pointer to receive the re-INVITE request message to + * be created. + * + * @return PJ_SUCCESS if a re-INVITE request with the specified + * characteristics (e.g. to contain new offer) can be + * created. + */ +PJ_DECL(pj_status_t) pjsip_inv_reinvite(pjsip_inv_session *inv, + const pj_str_t *new_contact, + const pjmedia_sdp_session *new_offer, + pjsip_tx_data **p_tdata ); + + + +/** + * Create an UPDATE request. + * + * @param inv The invite session. + * @param new_contact If application wants to update its local contact + * and inform peer to perform target refresh with a new + * contact, it can specify the new contact in this + * argument; otherwise this argument must be NULL. + * @param new_offer Application MAY initiate a new SDP offer/answer + * session in the request when there is no pending answer + * to be sent or received. It can detect this condition + * by observing the state of the SDP negotiator of the + * invite session. If new offer should be sent to remote, + * the offer must be specified in this argument; otherwise + * this argument must be NULL. + * @param p_tdata Pointer to receive the UPDATE request message to + * be created. + * + * @return PJ_SUCCESS if a UPDATE request with the specified + * characteristics (e.g. to contain new offer) can be + * created. + */ +PJ_DECL(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, + const pj_str_t *new_contact, + const pjmedia_sdp_session *new_offer, + pjsip_tx_data **p_tdata ); + + +/** + * Send request or response message in tdata. + * + * @param inv The invite session. + * @param tdata The message to be sent. + * @param token The token is an arbitrary application data that + * will be put in the transaction's mod_data array, + * at application module's index. Application can inspect + * this value when the framework reports the completion + * of the transaction that sends this message. + * + * @return PJ_SUCCESS if transaction can be initiated + * successfully to send this message. Note that the + * actual final state of the transaction itself will + * be reported later, in on_tsx_state_changed() + * callback. + */ +PJ_DECL(pj_status_t) pjsip_inv_send_msg(pjsip_inv_session *inv, + pjsip_tx_data *tdata, + void *token ); + + + +/** + * Get the invite session for the dialog, if any. + * + * @param dlg The dialog which invite session is being queried. + * + * @return The invite session instance which has been + * associated with this dialog, or NULL. + */ +PJ_DECL(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg); + +/** + * Get the invite session instance associated with transaction tsx, if any. + * + * @param tsx The transaction, which invite session is being + * queried. + * + * @return The invite session instance which has been + * associated with this transaction, or NULL. + */ +PJ_DECL(pjsip_inv_session*) pjsip_tsx_get_inv_session(pjsip_transaction *tsx); + + + + + +#endif /* __SIP_INVITE_SESSION_H__ */ diff --git a/pjsip/include/pjsip/sip_dialog.h b/pjsip/include/pjsip/sip_dialog.h index fde16ad9..6a5418b8 100644 --- a/pjsip/include/pjsip/sip_dialog.h +++ b/pjsip/include/pjsip/sip_dialog.h @@ -61,6 +61,7 @@ struct pjsip_dialog pj_pool_t *pool; /**< Dialog's pool. */ pj_mutex_t *mutex; /**< Dialog's mutex. */ pjsip_user_agent *ua; /**< User agent instance. */ + pjsip_endpoint *endpt; /**< Endpoint instance. */ /* The dialog set. */ void *dlg_set; diff --git a/pjsip/include/pjsip/sip_event.h b/pjsip/include/pjsip/sip_event.h index 03260a5f..268a60e7 100644 --- a/pjsip/include/pjsip/sip_event.h +++ b/pjsip/include/pjsip/sip_event.h @@ -57,21 +57,9 @@ typedef enum pjsip_event_id_e /** Transaction state changed event. */ PJSIP_EVENT_TSX_STATE, - /** 2xx response received event. */ - PJSIP_EVENT_RX_200_MSG, - - /** ACK request received event. */ - PJSIP_EVENT_RX_ACK_MSG, - - /** Message discarded event. */ - PJSIP_EVENT_DISCARD_MSG, - /** Indicates that the event was triggered by user action. */ PJSIP_EVENT_USER, - /** On before transmitting message. */ - PJSIP_EVENT_PRE_TX_MSG, - } pjsip_event_id_e; @@ -140,14 +128,6 @@ struct pjsip_event } tx_msg; - /** Pre-transmission event. */ - struct - { - pjsip_tx_data *tdata; /**< Msg to be transmitted. */ - pjsip_transaction *tsx; /**< The transaction. */ - int retcnt;/**< Retransmission count. */ - } pre_tx_msg; - /** Transmission error event. */ struct { @@ -162,25 +142,6 @@ struct pjsip_event pjsip_transaction *tsx; /**< The transaction. */ } rx_msg; - /** Receipt of 200/INVITE response. */ - struct - { - pjsip_rx_data *rdata; /**< The 200 response msg. */ - } rx_200_msg; - - /** Receipt of ACK message. */ - struct - { - pjsip_rx_data *rdata; /**< The ack message. */ - } rx_ack_msg; - - /** Notification that endpoint has discarded a message. */ - struct - { - pjsip_rx_data *rdata; /**< The discarded message. */ - pj_status_t reason;/**< The reason. */ - } discard_msg; - /** User event. */ struct { @@ -245,34 +206,6 @@ struct pjsip_event } while (0) /** - * Init rx 200/INVITE event. - */ -#define PJSIP_EVENT_INIT_RX_200_MSG(event,prdata) \ - do { \ - (event).type = PJSIP_EVENT_RX_200_MSG; \ - (event).body.rx_200_msg.rdata = prdata; \ - } while (0) - -/** - * Init rx ack msg event. - */ -#define PJSIP_EVENT_INIT_RX_ACK_MSG(event,prdata) \ - do { \ - (event).type = PJSIP_EVENT_RX_ACK_MSG; \ - (event).body.rx_ack_msg.rdata = prdata; \ - } while (0) - -/** - * Init discard msg event. - */ -#define PJSIP_EVENT_INIT_DISCARD_MSG(event,prdata,preason) \ - do { \ - (event).type = PJSIP_EVENT_DISCARD_MSG; \ - (event).body.discard_msg.rdata = prdata; \ - (event).body.discard_msg.reason = preason; \ - } while (0) - -/** * Init user event. */ #define PJSIP_EVENT_INIT_USER(event,u1,u2,u3,u4) \ @@ -285,18 +218,6 @@ struct pjsip_event } while (0) /** - * Init pre tx msg event. - */ -#define PJSIP_EVENT_INIT_PRE_TX_MSG(event,ptsx,ptdata,pretcnt) \ - do { \ - (event).type = PJSIP_EVENT_PRE_TX_MSG; \ - (event).body.pre_tx_msg.tsx = ptsx; \ - (event).body.pre_tx_msg.tdata = ptdata; \ - (event).body.pre_tx_msg.retcnt = pretcnt; \ - } while (0) - - -/** * Get the event string from the event ID. * @param e the event ID. * @notes defined in sip_util.c diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index cebaf3f0..e67d015d 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -1821,6 +1821,58 @@ PJ_DECL(pjsip_via_hdr*) pjsip_via_hdr_init( pj_pool_t *pool, * @} */ + +/////////////////////////////////////////////////////////////////////////////// +/** + * @defgroup PJSIP_MSG_HDR_WARNING Header Field: Warning + * @brief Warning header field. + * @ingroup PJSIP_MSG + * @{ + */ +/** + * SIP Warning header. + * In this version, Warning header is just a typedef for generic string + * header. + */ +typedef pjsip_generic_string_hdr pjsip_warning_hdr; + +/** + * Create a warning header with the specified contents. + * + * @param pool Pool to allocate memory from. + * @param code Warning code, 300-399. + * @param host The host portion of the Warning header. + * @param text The warning text, which MUST not be quoted with + * double quote. + * + * @return The Warning header field. + */ +PJ_DECL(pjsip_warning_hdr*) pjsip_warning_hdr_create( pj_pool_t *pool, + int code, + const pj_str_t *host, + const pj_str_t *text); + +/** + * Create a warning header and initialize the contents from the error + * message for the specified status code. The warning code will be + * set to 399. + * + * @param pool Pool to allocate memory from. + * @param host The host portion of the Warning header. + * @param status The error status code, which error text will be + * put in as the Warning text. + * + * @return The Warning header field. + */ +PJ_DECL(pjsip_warning_hdr*) +pjsip_warning_hdr_create_from_status( pj_pool_t *pool, + const pj_str_t *host, + pj_status_t status); + +/** + * @} + */ + /** * @bug Once a header is put in the message, the header CAN NOT be put in * other list. Solution: @@ -1953,11 +2005,6 @@ typedef pjsip_generic_string_hdr pjsip_user_agent_hdr; /** Create User-Agent header. */ #define pjsip_user_agent_hdr_create pjsip_generic_string_hdr_create -/** Warning header. */ -typedef pjsip_generic_string_hdr pjsip_warning_hdr; - -/** Create Warning header. */ -#define pjsip_warning_hdr_create pjsip_generic_string_hdr_create /** * @} diff --git a/pjsip/include/pjsip_ua.h b/pjsip/include/pjsip_ua.h index e2c640e9..c32ada4f 100644 --- a/pjsip/include/pjsip_ua.h +++ b/pjsip/include/pjsip_ua.h @@ -16,13 +16,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - #ifndef __PJSIP_UA_H__ #define __PJSIP_UA_H__ -#include <pjsip_mod_ua/sip_dialog.h> -#include <pjsip_mod_ua/sip_reg.h> -#include <pjsip_mod_ua/sip_ua.h> +#include <pjsip-ua/sip_inv.h> #endif /* __PJSIP_UA_H__ */ diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c new file mode 100644 index 00000000..f9eac982 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -0,0 +1,1327 @@ +/* $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-ua/sip_inv.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_transaction.h> +#include <pjmedia/sdp.h> +#include <pjmedia/sdp_neg.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/assert.h> + +#define THIS_FILE "sip_invite_session.c" + +/* + * Static prototypes. + */ +static pj_status_t mod_inv_load(pjsip_endpoint *endpt); +static pj_status_t mod_inv_unload(void); +static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata); +static void mod_inv_on_tsx_state(pjsip_transaction*, pjsip_event*); + +static void inv_on_state_null( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_calling( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_incoming( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_early( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_connecting( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_confirmed( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_disconnected( pjsip_inv_session *s, pjsip_event *e); +static void inv_on_state_terminated( pjsip_inv_session *s, pjsip_event *e); + +static void (*inv_state_handler[])( pjsip_inv_session *s, pjsip_event *e) = +{ + &inv_on_state_null, + &inv_on_state_calling, + &inv_on_state_incoming, + &inv_on_state_early, + &inv_on_state_connecting, + &inv_on_state_confirmed, + &inv_on_state_disconnected, + &inv_on_state_terminated, +}; + +static struct mod_inv +{ + pjsip_module mod; + pjsip_endpoint *endpt; + pjsip_inv_callback cb; + pjsip_module *app_user; +} mod_inv = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-invite", 10 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* User data. */ + &mod_inv_load, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + &mod_inv_unload, /* unload() */ + &mod_inv_on_rx_request, /* on_rx_request() */ + &mod_inv_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &mod_inv_on_tsx_state, /* on_tsx_state() */ + } +}; + + + +static pj_status_t mod_inv_load(pjsip_endpoint *endpt) +{ + pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}}; + + /* Register supported methods: INVITE, ACK, BYE, CANCEL */ + pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL, + PJ_ARRAY_SIZE(allowed), allowed); + + return PJ_SUCCESS; +} + +static pj_status_t mod_inv_unload(void) +{ + /* Should remove capability here */ + return PJ_SUCCESS; +} + +static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata) +{ + /* Ignore requests outside dialog */ + if (pjsip_rdata_get_dlg(rdata) == NULL) + return PJ_FALSE; + + /* Ignore all. */ + return PJ_FALSE; +} + +static pj_status_t send_ack(pjsip_inv_session *inv, pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_dlg_create_request(inv->dlg, &pjsip_ack_method, + rdata->msg_info.cseq->cseq, &tdata); + if (status != PJ_SUCCESS) { + /* Better luck next time */ + pj_assert(!"Unable to create ACK!"); + return status; + } + + status = pjsip_dlg_send_request(inv->dlg, tdata, NULL); + if (status != PJ_SUCCESS) { + /* Better luck next time */ + pj_assert(!"Unable to send ACK!"); + return status; + } + + return PJ_SUCCESS; +} + +static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata) +{ + pjsip_dialog *dlg; + pjsip_inv_session *inv; + pjsip_msg *msg = rdata->msg_info.msg; + + dlg = pjsip_rdata_get_dlg(rdata); + + /* Ignore responses outside dialog */ + if (dlg == NULL) + return PJ_FALSE; + + /* Ignore responses not belonging to invite session */ + inv = pjsip_dlg_get_inv_session(dlg); + if (inv == NULL) + return PJ_FALSE; + + /* This MAY be retransmission of 2xx response to INVITE. + * If it is, we need to send ACK. + */ + if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 && + rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) { + + send_ack(inv, rdata); + return PJ_TRUE; + + } + + /* No other processing needs to be done here. */ + return PJ_FALSE; +} + +static void mod_inv_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + pjsip_dialog *dlg; + pjsip_inv_session *inv; + + dlg = pjsip_tsx_get_dlg(tsx); + if (dlg == NULL) + return; + + inv = pjsip_dlg_get_inv_session(dlg); + if (inv == NULL) + return; + + /* Call state handler for the invite session. */ + (*inv_state_handler[inv->state])(inv, e); + + /* Call on_tsx_state */ + if (mod_inv.cb.on_tsx_state_changed) + (*mod_inv.cb.on_tsx_state_changed)(inv, tsx, e); + + /* Clear invite transaction when tsx is terminated. */ + if (tsx->state==PJSIP_TSX_STATE_TERMINATED && tsx == inv->invite_tsx) + inv->invite_tsx = NULL; +} + +PJ_DEF(pj_status_t) pjsip_inv_usage_init( pjsip_endpoint *endpt, + pjsip_module *app_module, + const pjsip_inv_callback *cb) +{ + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && app_module && cb, PJ_EINVAL); + + /* Some callbacks are mandatory */ + PJ_ASSERT_RETURN(cb->on_state_changed && cb->on_new_session, PJ_EINVAL); + + /* Check if module already registered. */ + PJ_ASSERT_RETURN(mod_inv.mod.id == -1, PJ_EINVALIDOP); + + /* Copy param. */ + pj_memcpy(&mod_inv.cb, cb, sizeof(pjsip_inv_callback)); + + mod_inv.endpt = endpt; + mod_inv.app_user = app_module; + + /* Register the module. */ + status = pjsip_endpt_register_module(endpt, &mod_inv.mod); + + return status; +} + +PJ_DEF(pjsip_module*) pjsip_inv_usage_instance(void) +{ + return &mod_inv.mod; +} + + +PJ_DEF(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg) +{ + return dlg->mod_data[mod_inv.mod.id]; +} + +/* + * Create UAC session. + */ +PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg, + const pjmedia_sdp_session *local_sdp, + unsigned options, + pjsip_inv_session **p_inv) +{ + pjsip_inv_session *inv; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(dlg && p_inv, PJ_EINVAL); + + /* Normalize options */ + if (options & PJSIP_INV_REQUIRE_100REL) + options |= PJSIP_INV_SUPPORT_100REL; + + if (options & PJSIP_INV_REQUIRE_TIMER) + options |= PJSIP_INV_SUPPORT_TIMER; + + /* Create the session */ + inv = pj_pool_zalloc(dlg->pool, sizeof(pjsip_inv_session)); + PJ_ASSERT_RETURN(inv != NULL, PJ_ENOMEM); + + inv->pool = dlg->pool; + inv->role = PJSIP_ROLE_UAC; + inv->state = PJSIP_INV_STATE_NULL; + inv->dlg = dlg; + inv->options = options; + + /* Create negotiator if local_sdp is specified. */ + if (local_sdp) { + status = pjmedia_sdp_neg_create_w_local_offer(dlg->pool, local_sdp, + &inv->neg); + if (status != PJ_SUCCESS) + return status; + } + + /* Register invite as dialog usage. */ + status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv); + if (status != PJ_SUCCESS) + return status; + + /* Increment dialog session */ + pjsip_dlg_inc_session(dlg); + + /* Done */ + *p_inv = inv; + return PJ_SUCCESS; +} + +/* + * Verify incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsip_inv_verify_request(pjsip_rx_data *rdata, + unsigned *options, + const pjmedia_sdp_session *l_sdp, + pjsip_dialog *dlg, + pjsip_endpoint *endpt, + pjsip_tx_data **p_tdata) +{ + pjsip_msg *msg; + pjsip_allow_hdr *allow; + pjsip_supported_hdr *sup_hdr; + pjsip_require_hdr *req_hdr; + int code = 200; + unsigned rem_option = 0; + pj_status_t status = PJ_SUCCESS; + pjsip_hdr res_hdr_list; + + /* Init return arguments. */ + if (p_tdata) *p_tdata = NULL; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(rdata != NULL && options != NULL, PJ_EINVAL); + + /* Normalize options */ + if (*options & PJSIP_INV_REQUIRE_100REL) + *options |= PJSIP_INV_SUPPORT_100REL; + + if (*options & PJSIP_INV_REQUIRE_TIMER) + *options |= PJSIP_INV_SUPPORT_TIMER; + + /* Get the message in rdata */ + msg = rdata->msg_info.msg; + + /* Must be INVITE request. */ + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG && + msg->line.req.method.id == PJSIP_INVITE_METHOD, + PJ_EINVAL); + + /* If tdata is specified, then either dlg or endpt must be specified */ + PJ_ASSERT_RETURN((!p_tdata) || (endpt || dlg), PJ_EINVAL); + + /* Get the endpoint */ + endpt = endpt ? endpt : dlg->endpt; + + /* Init response header list */ + pj_list_init(&res_hdr_list); + + /* Check the request body, see if it's something that we support + * (i.e. SDP). + */ + if (msg->body) { + pjsip_msg_body *body = msg->body; + pj_str_t str_application = {"application", 11}; + pj_str_t str_sdp = { "sdp", 3 }; + pjmedia_sdp_session *sdp; + + /* Check content type. */ + if (pj_stricmp(&body->content_type.type, &str_application) != 0 || + pj_stricmp(&body->content_type.subtype, &str_sdp) != 0) + { + /* Not "application/sdp" */ + code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + /* Add Accept header to response */ + pjsip_accept_hdr *acc; + + acc = pjsip_accept_hdr_create(rdata->tp_info.pool); + PJ_ASSERT_RETURN(acc, PJ_ENOMEM); + acc->values[acc->count++] = pj_str("application/sdp"); + pj_list_push_back(&res_hdr_list, acc); + } + + goto on_return; + } + + /* Parse and validate SDP */ + status = pjmedia_sdp_parse(rdata->tp_info.pool, body->data, body->len, + &sdp); + if (status == PJ_SUCCESS) + status = pjmedia_sdp_validate(sdp); + + if (status != PJ_SUCCESS) { + /* Unparseable or invalid SDP */ + code = PJSIP_SC_BAD_REQUEST; + + if (p_tdata) { + /* Add Warning header. */ + pjsip_warning_hdr *w; + + w = pjsip_warning_hdr_create_from_status(rdata->tp_info.pool, + pjsip_endpt_name(endpt), + status); + PJ_ASSERT_RETURN(w, PJ_ENOMEM); + + pj_list_push_back(&res_hdr_list, w); + } + + goto on_return; + } + + /* Negotiate with local SDP */ + if (l_sdp) { + pjmedia_sdp_neg *neg; + + /* Local SDP must be valid! */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(l_sdp))==PJ_SUCCESS, + status); + + /* Create SDP negotiator */ + status = pjmedia_sdp_neg_create_w_remote_offer( + rdata->tp_info.pool, l_sdp, sdp, &neg); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Negotiate SDP */ + status = pjmedia_sdp_neg_negotiate(rdata->tp_info.pool, neg, 0); + if (status != PJ_SUCCESS) { + + /* Incompatible media */ + code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + pjsip_accept_hdr *acc; + pjsip_warning_hdr *w; + + /* Add Warning header. */ + w = pjsip_warning_hdr_create_from_status( + rdata->tp_info.pool, + pjsip_endpt_name(endpt), status); + PJ_ASSERT_RETURN(w, PJ_ENOMEM); + + pj_list_push_back(&res_hdr_list, w); + + /* Add Accept header to response */ + acc = pjsip_accept_hdr_create(rdata->tp_info.pool); + PJ_ASSERT_RETURN(acc, PJ_ENOMEM); + acc->values[acc->count++] = pj_str("application/sdp"); + pj_list_push_back(&res_hdr_list, acc); + + } + + goto on_return; + } + } + } + + /* Check supported methods, see if peer supports UPDATE. + * We just assume that peer supports standard INVITE, ACK, CANCEL, and BYE + * implicitly by sending this INVITE. + */ + allow = pjsip_msg_find_hdr(msg, PJSIP_H_ALLOW, NULL); + if (allow) { + unsigned i; + const pj_str_t STR_UPDATE = { "UPDATE", 6 }; + + for (i=0; i<allow->count; ++i) { + if (pj_stricmp(&allow->values[i], &STR_UPDATE)==0) + break; + } + + if (i != allow->count) { + /* UPDATE is present in Allow */ + rem_option |= PJSIP_INV_SUPPORT_UPDATE; + } + + } + + /* Check Supported header */ + sup_hdr = pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL); + if (sup_hdr) { + unsigned i; + pj_str_t STR_100REL = { "100rel", 6}; + pj_str_t STR_TIMER = { "timer", 5 }; + + for (i=0; i<sup_hdr->count; ++i) { + if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0) + rem_option |= PJSIP_INV_SUPPORT_100REL; + else if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0) + rem_option |= PJSIP_INV_SUPPORT_TIMER; + } + } + + /* Check Require header */ + req_hdr = pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); + if (req_hdr) { + unsigned i; + pj_str_t STR_100REL = { "100rel", 6}; + pj_str_t STR_TIMER = { "timer", 5 }; + unsigned unsupp_cnt = 0; + pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT]; + + for (i=0; i<req_hdr->count; ++i) { + if ((*options & PJSIP_INV_SUPPORT_100REL) && + pj_stricmp(&req_hdr->values[i], &STR_100REL)==0) + { + rem_option |= PJSIP_INV_REQUIRE_100REL; + + } else if ((*options && PJSIP_INV_SUPPORT_TIMER) && + pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0) + { + rem_option |= PJSIP_INV_REQUIRE_TIMER; + + } else { + /* Unknown/unsupported extension tag! */ + unsupp_tags[unsupp_cnt++] = req_hdr->values[i]; + } + } + + /* Check if there are required tags that we don't support */ + if (unsupp_cnt) { + + code = PJSIP_SC_BAD_EXTENSION; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + pjsip_unsupported_hdr *unsupp_hdr; + const pjsip_hdr *h; + + /* Add Unsupported header. */ + unsupp_hdr = pjsip_unsupported_hdr_create(rdata->tp_info.pool); + PJ_ASSERT_RETURN(unsupp_hdr != NULL, PJ_ENOMEM); + + unsupp_hdr->count = unsupp_cnt; + for (i=0; i<unsupp_cnt; ++i) + unsupp_hdr->values[i] = unsupp_tags[i]; + + pj_list_push_back(&res_hdr_list, unsupp_hdr); + + /* Add Supported header. */ + h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, + NULL); + pj_assert(h); + if (h) { + sup_hdr = pjsip_hdr_clone(rdata->tp_info.pool, h); + pj_list_push_back(&res_hdr_list, sup_hdr); + } + } + + goto on_return; + } + } + + /* Check if there are local requirements that are not supported + * by peer. + */ + if ( ((*options & PJSIP_INV_REQUIRE_100REL)!=0 && + (rem_option & PJSIP_INV_SUPPORT_100REL)==0) || + ((*options & PJSIP_INV_REQUIRE_TIMER)!=0 && + (rem_option & PJSIP_INV_SUPPORT_TIMER)==0)) + { + code = PJSIP_SC_EXTENSION_REQUIRED; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + const pjsip_hdr *h; + + /* Add Require header. */ + req_hdr = pjsip_require_hdr_create(rdata->tp_info.pool); + PJ_ASSERT_RETURN(req_hdr != NULL, PJ_ENOMEM); + + if (*options & PJSIP_INV_REQUIRE_100REL) + req_hdr->values[req_hdr->count++] = pj_str("100rel"); + + if (*options & PJSIP_INV_REQUIRE_TIMER) + req_hdr->values[req_hdr->count++] = pj_str("timer"); + + pj_list_push_back(&res_hdr_list, req_hdr); + + /* Add Supported header. */ + h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, + NULL); + pj_assert(h); + if (h) { + sup_hdr = pjsip_hdr_clone(rdata->tp_info.pool, h); + pj_list_push_back(&res_hdr_list, sup_hdr); + } + + } + + goto on_return; + } + +on_return: + + /* Create response if necessary */ + if (code != 200 && p_tdata) { + pjsip_tx_data *tdata; + const pjsip_hdr *h; + + if (dlg) { + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, + &tdata); + } else { + status = pjsip_endpt_create_response(endpt, rdata, code, NULL, + &tdata); + } + + if (status != PJ_SUCCESS) + return status; + + /* Add response headers. */ + h = res_hdr_list.next; + while (h != &res_hdr_list) { + pjsip_hdr *cloned; + + cloned = pjsip_hdr_clone(tdata->pool, h); + PJ_ASSERT_RETURN(cloned, PJ_ENOMEM); + + pjsip_msg_add_hdr(tdata->msg, cloned); + + h = h->next; + } + + *p_tdata = tdata; + } + + return status; +} + +/* + * Create UAS session. + */ +PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg, + pjsip_rx_data *rdata, + const pjmedia_sdp_session *local_sdp, + unsigned options, + pjsip_inv_session **p_inv) +{ + pjsip_inv_session *inv; + pjsip_msg *msg; + pjmedia_sdp_session *rem_sdp = NULL; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(dlg && rdata && p_inv, PJ_EINVAL); + + /* Dialog MUST have been initialised. */ + PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) != NULL, PJ_EINVALIDOP); + + msg = rdata->msg_info.msg; + + /* rdata MUST contain INVITE request */ + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG && + msg->line.req.method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + /* Normalize options */ + if (options & PJSIP_INV_REQUIRE_100REL) + options |= PJSIP_INV_SUPPORT_100REL; + + if (options & PJSIP_INV_REQUIRE_TIMER) + options |= PJSIP_INV_SUPPORT_TIMER; + + /* Create the session */ + inv = pj_pool_zalloc(dlg->pool, sizeof(pjsip_inv_session)); + PJ_ASSERT_RETURN(inv != NULL, PJ_ENOMEM); + + inv->pool = dlg->pool; + inv->role = PJSIP_ROLE_UAS; + inv->state = PJSIP_INV_STATE_NULL; + inv->dlg = dlg; + inv->options = options; + + /* Parse SDP in message body, if present. */ + if (msg->body) { + pjsip_msg_body *body = msg->body; + + /* Parse and validate SDP */ + status = pjmedia_sdp_parse(inv->pool, body->data, body->len, + &rem_sdp); + if (status == PJ_SUCCESS) + status = pjmedia_sdp_validate(rem_sdp); + + if (status != PJ_SUCCESS) + return status; + } + + /* Create negotiator. */ + if (rem_sdp) { + status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, local_sdp, + rem_sdp, &inv->neg); + + } else if (local_sdp) { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, local_sdp, + &inv->neg); + } else { + pj_assert(!"UAS dialog without remote and local offer is not supported!"); + PJ_TODO(IMPLEMENT_DELAYED_UAS_OFFER); + status = PJ_ENOTSUP; + } + + if (status != PJ_SUCCESS) + return status; + + /* Register invite as dialog usage. */ + status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv); + if (status != PJ_SUCCESS) + return status; + + /* Increment session in the dialog. */ + pjsip_dlg_inc_session(dlg); + + /* Save the invite transaction. */ + inv->invite_tsx = pjsip_rdata_get_tsx(rdata); + inv->invite_tsx->mod_data[mod_inv.mod.id] = inv; + + /* Done */ + *p_inv = inv; + return PJ_SUCCESS; +} + +static void *clone_sdp(pj_pool_t *pool, const void *data, unsigned len) +{ + PJ_UNUSED_ARG(len); + return pjmedia_sdp_session_clone(pool, data); +} + +static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len) +{ + return pjmedia_sdp_print(body->data, buf, len); +} + +static pjsip_msg_body *create_sdp_body(pj_pool_t *pool, + const pjmedia_sdp_session *c_sdp) +{ + pjsip_msg_body *body; + + + body = pj_pool_zalloc(pool, sizeof(pjsip_msg_body)); + PJ_ASSERT_RETURN(body != NULL, NULL); + + body->content_type.type = pj_str("application"); + body->content_type.subtype = pj_str("sdp"); + body->data = pjmedia_sdp_session_clone(pool, c_sdp); + body->len = 0; + body->clone_data = &clone_sdp; + body->print_body = &print_sdp; + + return body; +} + +/* + * Create initial INVITE request. + */ +PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *tdata; + const pjsip_hdr *hdr; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* State MUST be NULL. */ + PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL, PJ_EINVAL); + + /* Create the INVITE request. */ + status = pjsip_dlg_create_request(inv->dlg, &pjsip_invite_method, -1, + &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add SDP, if any. */ + if (inv->neg && + pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + const pjmedia_sdp_session *offer; + + status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); + if (status != PJ_SUCCESS) + return status; + + tdata->msg->body = create_sdp_body(tdata->pool, offer); + } + + /* Add Allow header. */ + hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_ALLOW, NULL); + if (hdr) { + pjsip_msg_add_hdr(tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + } + + /* Add Supported header */ + hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_SUPPORTED, NULL); + if (hdr) { + pjsip_msg_add_hdr(tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + } + + /* Add Require header. */ + PJ_TODO(INVITE_ADD_REQUIRE_HEADER); + + /* Done. */ + *p_tdata = tdata; + + return PJ_SUCCESS; +} + + +/* + * Answer initial INVITE. + */ +PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, + int st_code, + const pj_str_t *st_text, + const pjmedia_sdp_session *local_sdp, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *last_res; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Must have INVITE transaction. */ + PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG); + + /* INVITE transaction MUST have transmitted a response (e.g. 100) */ + PJ_ASSERT_RETURN(inv->invite_tsx->last_tx, PJ_EINVALIDOP); + + /* If local_sdp is specified, then we MUST NOT have answered the + * offer before. + */ + PJ_ASSERT_RETURN(!local_sdp || + (pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER), + PJ_EINVALIDOP); + if (local_sdp) { + status = pjmedia_sdp_neg_set_local_answer(inv->pool, inv->neg, + local_sdp); + if (status != PJ_SUCCESS) + return status; + } + + last_res = inv->invite_tsx->last_tx; + + /* Modify last response. */ + status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text); + if (status != PJ_SUCCESS) + return status; + + /* Include SDP for 18x and 2xx response. */ + if (st_code/10 == 18 || st_code/10 == 20) { + pjmedia_sdp_session *local; + + status = pjmedia_sdp_neg_get_neg_local(inv->neg, &local); + if (status == PJ_SUCCESS) + last_res->msg->body = create_sdp_body(last_res->pool, local); + ; + } + + /* Do we need to increment tdata's reference counter? */ + PJ_TODO(INV_ANSWER_MAY_HAVE_TO_INCREMENT_REF_COUNTER); + + *p_tdata = last_res; + + return PJ_SUCCESS; +} + + +/* + * End session. + */ +PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv, + int st_code, + const pj_str_t *st_text, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Create appropriate message. */ + switch (inv->state) { + case PJSIP_INV_STATE_CALLING: + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_INCOMING: + + if (inv->role == PJSIP_ROLE_UAC) { + + /* For UAC when session has not been confirmed, create CANCEL. */ + + /* MUST have the original UAC INVITE transaction. */ + PJ_ASSERT_RETURN(inv->invite_tsx != NULL, PJ_EBUG); + + /* But CANCEL should only be called when we have received a + * provisional response. If we haven't received any responses, + * just destroy the transaction. + */ + if (inv->invite_tsx->status_code < 100) { + + pjsip_tsx_terminate(inv->invite_tsx, 487); + + return PJSIP_ETSXDESTROYED; + } + + /* The CSeq here assumes that the dialog is started with an + * INVITE session. This may not be correct; dialog can be + * started as SUBSCRIBE session. + * So fix this! + */ + status = pjsip_endpt_create_cancel(inv->dlg->endpt, + inv->invite_tsx->last_tx, + &tdata); + + } else { + + /* For UAS, send a final response. */ + tdata = inv->invite_tsx->last_tx; + PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP); + + status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code, + st_text); + } + break; + + case PJSIP_INV_STATE_CONNECTING: + case PJSIP_INV_STATE_CONFIRMED: + /* For established dialog, send BYE */ + status = pjsip_dlg_create_request(inv->dlg, &pjsip_bye_method, -1, + &tdata); + break; + + case PJSIP_INV_STATE_DISCONNECTED: + case PJSIP_INV_STATE_TERMINATED: + /* No need to do anything. */ + PJ_TODO(RETURN_A_PROPER_STATUS_CODE_HERE); + return PJ_EINVALIDOP; + + default: + pj_assert("!Invalid operation!"); + return PJ_EINVALIDOP; + } + + if (status != PJ_SUCCESS) + return status; + + + /* Done */ + + *p_tdata = tdata; + + return PJ_SUCCESS; +} + + +/* + * Create re-INVITE. + */ +PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv, + const pj_str_t *new_contact, + const pjmedia_sdp_session *new_offer, + pjsip_tx_data **p_tdata ) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(new_contact); + PJ_UNUSED_ARG(new_offer); + PJ_UNUSED_ARG(p_tdata); + + PJ_TODO(CREATE_REINVITE_REQUEST); + return PJ_ENOTSUP; +} + +/* + * Create UPDATE. + */ +PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, + const pj_str_t *new_contact, + const pjmedia_sdp_session *new_offer, + pjsip_tx_data **p_tdata ) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(new_contact); + PJ_UNUSED_ARG(new_offer); + PJ_UNUSED_ARG(p_tdata); + + PJ_TODO(CREATE_UPDATE_REQUEST); + return PJ_ENOTSUP; +} + +/* + * Send a request or response message. + */ +PJ_DEF(pj_status_t) pjsip_inv_send_msg( pjsip_inv_session *inv, + pjsip_tx_data *tdata, + void *token ) +{ + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL); + + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + pjsip_transaction *tsx; + + status = pjsip_dlg_send_request(inv->dlg, tdata, &tsx); + if (status != PJ_SUCCESS) + return status; + + tsx->mod_data[mod_inv.mod.id] = inv; + tsx->mod_data[mod_inv.app_user->id] = token; + + } else { + pjsip_cseq_hdr *cseq; + + /* Can only do this to send response to original INVITE + * request. + */ + PJ_ASSERT_RETURN((cseq=pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL)) != NULL && + (cseq->cseq == inv->invite_tsx->cseq), + PJ_EINVALIDOP); + + status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); + if (status != PJ_SUCCESS) + return status; + } + + /* Done (?) */ + return PJ_SUCCESS; +} + + +void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state, + pjsip_event *e) +{ + inv->state = state; + if (mod_inv.cb.on_state_changed) + (*mod_inv.cb.on_state_changed)(inv, e); + + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) + pjsip_dlg_dec_session(inv->dlg); +} + +static void inv_on_state_null( pjsip_inv_session *s, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + if (dlg->role == PJSIP_ROLE_UAC) { + + /* Keep the initial INVITE transaction. */ + if (s->invite_tsx == NULL) + s->invite_tsx = tsx; + + switch (tsx->state) { + case PJSIP_TSX_STATE_CALLING: + inv_set_state(s, PJSIP_INV_STATE_CALLING, e); + break; + default: + pj_assert(!"Unexpected state"); + break; + } + + } else { + switch (tsx->state) { + case PJSIP_TSX_STATE_TRYING: + inv_set_state(s, PJSIP_INV_STATE_INCOMING, e); + break; + default: + pj_assert(!"Unexpected state"); + } + } + + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +static void inv_on_state_calling( pjsip_inv_session *s, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + switch (tsx->state) { + + case PJSIP_TSX_STATE_PROCEEDING: + if (dlg->remote.info->tag.slen) { + inv_set_state(s, PJSIP_INV_STATE_EARLY, e); + } else { + /* Ignore 100 (Trying) response, as it doesn't change + * session state. It only ceases retransmissions. + */ + } + break; + + case PJSIP_TSX_STATE_COMPLETED: + if (tsx->status_code/100 == 2) { + + /* This should not happen. + * When transaction receives 2xx, it should be terminated + */ + pj_assert(0); + inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e); + + } else { + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + } + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* INVITE transaction can be terminated either because UAC + * transaction received 2xx response or because of transport + * error. + */ + if (tsx->status_code/100 == 2) { + /* This must be receipt of 2xx response */ + + /* Set state to CONNECTING */ + inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e); + + /* Send ACK */ + pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + + send_ack(s, e->body.tsx_state.src.rdata); + + } else { + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e); + } + break; + + default: + pj_assert(!"Unexpected state"); + } + + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +static void inv_on_state_incoming( pjsip_inv_session *s, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + switch (tsx->state) { + case PJSIP_TSX_STATE_PROCEEDING: + if (tsx->status_code > 100) + inv_set_state(s, PJSIP_INV_STATE_EARLY, e); + break; + case PJSIP_TSX_STATE_COMPLETED: + if (tsx->status_code/100 == 2) + inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e); + else + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + break; + case PJSIP_TSX_STATE_TERMINATED: + /* This happens on transport error */ + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e); + break; + default: + pj_assert(!"Unexpected state"); + } + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +static void inv_on_state_early( pjsip_inv_session *s, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + switch (tsx->state) { + + case PJSIP_TSX_STATE_PROCEEDING: + /* Send/received another provisional response. */ + inv_set_state(s, PJSIP_INV_STATE_EARLY, e); + break; + + case PJSIP_TSX_STATE_COMPLETED: + if (tsx->status_code/100 == 2) + inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e); + else + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* INVITE transaction can be terminated either because UAC + * transaction received 2xx response or because of transport + * error. + */ + if (tsx->status_code/100 == 2) { + + /* This must be receipt of 2xx response */ + + /* Set state to CONNECTING */ + inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e); + + /* if UAC, send ACK and move state to confirmed. */ + if (tsx->role == PJSIP_ROLE_UAC) { + pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + + send_ack(s, e->body.tsx_state.src.rdata); + + inv_set_state(s, PJSIP_INV_STATE_CONFIRMED, e); + } + + } else { + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e); + } + break; + + default: + pj_assert(!"Unexpected state"); + } + + } else if (tsx->method.id == PJSIP_CANCEL_METHOD) { + + /* Handle incoming CANCEL request. */ + if (tsx->role == PJSIP_ROLE_UAS && s->role == PJSIP_ROLE_UAS && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + + pj_status_t status; + pjsip_tx_data *tdata; + + /* Respond CANCEL with 200 (OK) */ + status = pjsip_dlg_create_response(dlg, e->body.tsx_state.src.rdata, + 200, NULL, &tdata); + if (status == PJ_SUCCESS) { + pjsip_dlg_send_response(dlg, tsx, tdata); + } + + /* Respond the original UAS transaction with 487 (Request + * Terminated) response. + */ + pj_assert(s->invite_tsx && s->invite_tsx->last_tx); + tdata = s->invite_tsx->last_tx; + + status = pjsip_dlg_modify_response(dlg, tdata, 487, NULL); + if (status == PJ_SUCCESS) { + pjsip_dlg_send_response(dlg, s->invite_tsx, tdata); + } + } + + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +static void inv_on_state_connecting( pjsip_inv_session *s, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + switch (tsx->state) { + + case PJSIP_TSX_STATE_CONFIRMED: + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* INVITE transaction can be terminated either because UAC + * transaction received 2xx response or because of transport + * error. + */ + if (tsx->status_code/100 != 2) { + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e); + } + break; + + case PJSIP_TSX_STATE_DESTROYED: + /* Do nothing. */ + break; + + default: + pj_assert(!"Unexpected state"); + } + + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +static void inv_on_state_confirmed( pjsip_inv_session *s, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_BYE_METHOD) { + inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e); + + } else if (tsx->method.id == PJSIP_INVITE_METHOD) { + + switch (tsx->state) { + case PJSIP_TSX_STATE_TERMINATED: + case PJSIP_TSX_STATE_DESTROYED: + break; + default: + pj_assert(!"Unexpected state"); + break; + } + + + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +static void inv_on_state_disconnected( pjsip_inv_session *s, pjsip_event *e) +{ + PJ_UNUSED_ARG(s); + PJ_UNUSED_ARG(e); +} + +static void inv_on_state_terminated( pjsip_inv_session *s, pjsip_event *e) +{ + PJ_UNUSED_ARG(s); + PJ_UNUSED_ARG(e); +} + diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c index a9a4010a..b4ea16d5 100644 --- a/pjsip/src/pjsip/sip_dialog.c +++ b/pjsip/src/pjsip/sip_dialog.c @@ -37,6 +37,7 @@ #define THIS_FILE "sip_dialog.c" +long pjsip_dlg_lock_tls_id; PJ_DEF(pj_bool_t) pjsip_method_creates_dialog(const pjsip_method *m) { @@ -72,6 +73,7 @@ static pj_status_t create_dialog( pjsip_user_agent *ua, dlg->pool = pool; pj_sprintf(dlg->obj_name, "dlg%p", dlg); dlg->ua = ua; + dlg->endpt = endpt; status = pj_mutex_create_recursive(pool, "dlg%p", &dlg->mutex); if (status != PJ_SUCCESS) @@ -92,7 +94,7 @@ static void destroy_dialog( pjsip_dialog *dlg ) { if (dlg->mutex) pj_mutex_destroy(dlg->mutex); - pjsip_endpt_release_pool(pjsip_ua_get_endpt(dlg->ua), dlg->pool); + pjsip_endpt_release_pool(dlg->endpt, dlg->pool); } @@ -144,7 +146,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, /* Randomize local CSeq. */ dlg->local.first_cseq = pj_rand() % 0x7FFFFFFFL; - dlg->local.cseq = dlg->local.cseq; + dlg->local.cseq = dlg->local.first_cseq; /* Init local contact. */ dlg->local.contact = pjsip_contact_hdr_create(dlg->pool); @@ -183,7 +185,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, pj_list_init(&dlg->route_set); /* Init client authentication session. */ - status = pjsip_auth_clt_init(&dlg->auth_sess, pjsip_ua_get_endpt(ua), + status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt, dlg->pool, 0); if (status != PJ_SUCCESS) goto on_error; @@ -342,7 +344,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua, } /* Init client authentication session. */ - status = pjsip_auth_clt_init(&dlg->auth_sess, pjsip_ua_get_endpt(ua), + status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt, dlg->pool, 0); if (status != PJ_SUCCESS) goto on_error; @@ -506,9 +508,12 @@ static pj_status_t unregister_and_destroy_dialog( pjsip_dialog *dlg ) return status; } + /* Log */ + PJ_LOG(5,(dlg->obj_name, "Dialog destroyed")); + /* Destroy this dialog. */ pj_mutex_destroy(dlg->mutex); - pjsip_endpt_release_pool(pjsip_ua_get_endpt(dlg->ua), dlg->pool); + pjsip_endpt_release_pool(dlg->endpt, dlg->pool); return PJ_SUCCESS; } @@ -629,6 +634,9 @@ PJ_DEF(pj_status_t) pjsip_dlg_add_usage( pjsip_dialog *dlg, /* Set module data. */ dlg->mod_data[mod->id] = mod_data; + /* Increment count. */ + ++dlg->usage_cnt; + pj_mutex_unlock(dlg->mutex); return PJ_SUCCESS; @@ -663,7 +671,7 @@ static pj_status_t dlg_create_request_throw( pjsip_dialog *dlg, * Create the request by cloning from the headers in the * dialog. */ - status = pjsip_endpt_create_request_from_hdr(pjsip_ua_get_endpt(dlg->ua), + status = pjsip_endpt_create_request_from_hdr(dlg->endpt, method, dlg->target, dlg->local.info, @@ -809,15 +817,17 @@ PJ_DEF(pj_status_t) pjsip_dlg_send_request( pjsip_dialog *dlg, goto on_error; } - *p_tsx = tsx; + if (p_tsx) + *p_tsx = tsx; } else { - status = pjsip_endpt_send_request_stateless( - pjsip_ua_get_endpt(dlg->ua), tdata, NULL, NULL); + status = pjsip_endpt_send_request_stateless(dlg->endpt, tdata, + NULL, NULL); if (status != PJ_SUCCESS) goto on_error; - *p_tsx = NULL; + if (p_tsx) + *p_tsx = NULL; } /* Unlock dialog. */ @@ -832,7 +842,8 @@ on_error: /* Whatever happen delete the message. */ pjsip_tx_data_dec_ref( tdata ); - *p_tsx = NULL; + if (p_tsx) + *p_tsx = NULL; return status; } @@ -852,7 +863,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_response( pjsip_dialog *dlg, int st_class; /* Create generic response. */ - status = pjsip_endpt_create_response(pjsip_ua_get_endpt(dlg->ua), + status = pjsip_endpt_create_response(dlg->endpt, rdata, st_code, st_text, &tdata); if (status != PJ_SUCCESS) return status; @@ -914,7 +925,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_response( pjsip_dialog *dlg, /* Add Allow header in 2xx and 405 response. */ if (st_class==2 || st_code==405) { const pjsip_hdr *c_hdr; - c_hdr = pjsip_endpt_get_capability(pjsip_ua_get_endpt(dlg->ua), + c_hdr = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ALLOW, NULL); if (c_hdr) { pjsip_hdr *hdr = pjsip_hdr_clone(tdata->pool, c_hdr); @@ -925,7 +936,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_response( pjsip_dialog *dlg, /* Add Supported header in 2xx response. */ if (st_class==2) { const pjsip_hdr *c_hdr; - c_hdr = pjsip_endpt_get_capability(pjsip_ua_get_endpt(dlg->ua), + c_hdr = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_SUPPORTED, NULL); if (c_hdr) { pjsip_hdr *hdr = pjsip_hdr_clone(tdata->pool, c_hdr); @@ -1056,7 +1067,7 @@ void pjsip_dlg_on_rx_request( pjsip_dialog *dlg, pjsip_rx_data *rdata ) * Respond statelessly with 500 (Internal Server Error) */ pj_mutex_unlock(dlg->mutex); - pjsip_endpt_respond_stateless(pjsip_ua_get_endpt(dlg->ua), + pjsip_endpt_respond_stateless(dlg->endpt, rdata, 500, NULL, NULL, NULL); return; } @@ -1094,7 +1105,7 @@ void pjsip_dlg_on_rx_request( pjsip_dialog *dlg, pjsip_rx_data *rdata ) "%s is unhandled by dialog usages. " "Dialog will response with 500 (Internal Server Error)", pjsip_rx_data_get_info(rdata))); - status = pjsip_endpt_create_response(pjsip_ua_get_endpt(dlg->ua), + status = pjsip_endpt_create_response(dlg->endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, &tdata); @@ -1125,6 +1136,9 @@ void pjsip_dlg_on_rx_response( pjsip_dialog *dlg, pjsip_rx_data *rdata ) /* Lock the dialog. */ pj_mutex_lock(dlg->mutex); + /* Check that rdata already has dialog in mod_data. */ + pj_assert(pjsip_rdata_get_dlg(rdata) == dlg); + /* Update the remote tag, if none is specified yet. */ if (dlg->remote.info->tag.slen == 0 && rdata->msg_info.to->tag.slen != 0) { @@ -1133,8 +1147,19 @@ void pjsip_dlg_on_rx_response( pjsip_dialog *dlg, pjsip_rx_data *rdata ) /* No need to update remote's tag_hval since its never used. */ } - /* Check that rdata already has dialog in mod_data. */ - pj_assert(pjsip_rdata_get_dlg(rdata) == dlg); + /* Update remote target when receiving certain response messages. */ + if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) && + rdata->msg_info.msg->line.status.code/100 == 2) + { + pjsip_contact_hdr *contact; + + contact = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (contact) { + dlg->remote.contact = pjsip_hdr_clone(dlg->pool, contact); + dlg->target = dlg->remote.contact->uri; + } + } /* Pass to dialog usages. */ for (i=0; i<dlg->usage_cnt; ++i) { @@ -1185,6 +1210,9 @@ void pjsip_dlg_on_tsx_state( pjsip_dialog *dlg, if (tsx->state == PJSIP_TSX_STATE_TERMINATED && dlg->tsx_count == 0 && dlg->sess_count == 0) { + /* Unregister this dialog from the transaction. */ + tsx->mod_data[dlg->ua->id] = NULL; + /* Time to destroy dialog. */ unregister_and_destroy_dialog(dlg); diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index 3d109e4c..53561030 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -311,6 +311,10 @@ PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt, default: return PJ_EINVAL; } + + if (hdr) { + pj_list_push_back(&endpt->cap_hdr, hdr); + } } /* Add the tags to the header. */ @@ -755,6 +759,11 @@ static void endpt_on_rx_msg( pjsip_endpoint *endpt, } pj_rwmutex_unlock_read(endpt->mod_mutex); + + /* Must clear mod_data before returning rdata to transport, since + * rdata may be reused. + */ + pj_memset(&rdata->endpt_info, 0, sizeof(rdata->endpt_info)); } /* diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 23e97f77..bee67215 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -1702,6 +1702,40 @@ static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool, /////////////////////////////////////////////////////////////////////////////// /* + * Warning header. + */ +PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create( pj_pool_t *pool, + int code, + const pj_str_t *host, + const pj_str_t *text) +{ + const pj_str_t str_warning = { "Warning", 7 }; + pj_str_t hvalue; + + hvalue.ptr = pj_pool_alloc(pool, 10 + /* code */ + host->slen + 2 + /* host */ + text->slen + 2); /* text */ + hvalue.slen = pj_sprintf(hvalue.ptr, "%u %.*s \"%.*s\"", + code, host->slen, host->ptr, + text->slen, text->ptr); + + return pjsip_generic_string_hdr_create(pool, &str_warning, &hvalue); +} + +PJ_DEF(pjsip_warning_hdr*) +pjsip_warning_hdr_create_from_status( pj_pool_t *pool, + const pj_str_t *host, + pj_status_t status) +{ + char errbuf[PJ_ERR_MSG_SIZE]; + pj_str_t text; + + text = pj_strerror(status, errbuf, sizeof(errbuf)); + return pjsip_warning_hdr_create(pool, 399, host, &text); +} + +/////////////////////////////////////////////////////////////////////////////// +/* * Message body manipulations. */ PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index cf07ddf0..20d90c7b 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -32,6 +32,13 @@ #define THIS_FILE "sip_transaction.c" +#if 0 +#define TSX_TRACE_(expr) PJ_LOG(3,expr) +#else +#define TSX_TRACE_(expr) +#endif + + /***************************************************************************** ** ** Declarations and static variable definitions section. @@ -504,20 +511,26 @@ static pj_status_t mod_tsx_layer_register_tsx( pjsip_transaction *tsx) /* Lock hash table mutex. */ pj_mutex_lock(mod_tsx_layer.mutex); - /* Check if no transaction with the same key exists. */ - PJ_ASSERT_ON_FAIL(pj_hash_get( mod_tsx_layer.htable, - &tsx->transaction_key.ptr, - tsx->transaction_key.slen, - &tsx->hashed_key) == NULL, - { - pj_mutex_unlock(mod_tsx_layer.mutex); - return PJ_EEXISTS; - } - ); + /* Check if no transaction with the same key exists. + * Do not use PJ_ASSERT_RETURN since it evaluates the expression + * twice! + */ + pj_assert(pj_hash_get( mod_tsx_layer.htable, + &tsx->transaction_key.ptr, + tsx->transaction_key.slen, + NULL) == NULL); + + TSX_TRACE_((THIS_FILE, + "Transaction %p registered with hkey=0x%p and key=%.*s", + tsx, tsx->hashed_key, tsx->transaction_key.slen, + tsx->transaction_key.ptr)); /* Register the transaction to the hash table. */ + //pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, + // tsx->transaction_key.slen, tsx->hashed_key, tsx); + PJ_TODO(USE_PRECALCULATED_HASHED_VALUE); pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, - tsx->transaction_key.slen, tsx->hashed_key, tsx); + tsx->transaction_key.slen, 0, tsx); /* Unlock mutex. */ pj_mutex_unlock(mod_tsx_layer.mutex); @@ -538,8 +551,16 @@ static void mod_tsx_layer_unregister_tsx( pjsip_transaction *tsx) pj_mutex_lock(mod_tsx_layer.mutex); /* Register the transaction to the hash table. */ + //pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, + // tsx->transaction_key.slen, tsx->hashed_key, NULL); + PJ_TODO(USE_PRECALCULATED_HASHED_VALUE); pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, - tsx->transaction_key.slen, tsx->hashed_key, NULL); + tsx->transaction_key.slen, 0, NULL); + + TSX_TRACE_((THIS_FILE, + "Transaction %p unregistered, hkey=0x%p and key=%.*s", + tsx, tsx->hashed_key, tsx->transaction_key.slen, + tsx->transaction_key.ptr)); /* Unlock mutex. */ pj_mutex_unlock(mod_tsx_layer.mutex); @@ -553,11 +574,15 @@ PJ_DEF(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key, pj_bool_t lock ) { pjsip_transaction *tsx; + pj_uint32_t hval = 0; pj_mutex_lock(mod_tsx_layer.mutex); - tsx = pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, NULL ); + tsx = pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, &hval ); pj_mutex_unlock(mod_tsx_layer.mutex); + TSX_TRACE_((THIS_FILE, + "Finding tsx with hkey=0x%p and key=%.*s: found %p", + hval, key->slen, key->ptr, tsx)); /* Race condition! * Transaction may gets deleted before we have chance to lock it. @@ -641,6 +666,7 @@ static pj_status_t mod_tsx_layer_unload(void) static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata) { pj_str_t key; + pj_uint32_t hval = 0; pjsip_transaction *tsx; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS, @@ -649,7 +675,13 @@ static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata) /* Find transaction. */ pj_mutex_lock( mod_tsx_layer.mutex ); - tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, NULL ); + tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval ); + + + TSX_TRACE_((THIS_FILE, + "Finding tsx for request, hkey=0x%p and key=%.*s, found %p", + hval, key.slen, key.ptr, tsx)); + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { /* Transaction not found. @@ -682,6 +714,7 @@ static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata) static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata) { pj_str_t key; + pj_uint32_t hval = 0; pjsip_transaction *tsx; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, @@ -690,7 +723,13 @@ static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata) /* Find transaction. */ pj_mutex_lock( mod_tsx_layer.mutex ); - tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, NULL ); + tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval ); + + + TSX_TRACE_((THIS_FILE, + "Finding tsx for response, hkey=0x%p and key=%.*s, found %p", + hval, key.slen, key.ptr, tsx)); + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { /* Transaction not found. @@ -918,7 +957,28 @@ static void tsx_set_state( pjsip_transaction *tsx, tsx->state_handler = tsx_state_handler_uas[state]; } - /* Inform TU */ + /* Before informing TU about state changed, inform TU about + * rx event. + */ + if (event_src_type==PJSIP_EVENT_RX_MSG && tsx->tsx_user) { + pjsip_rx_data *rdata = event_src; + + pj_assert(rdata != NULL); + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG && + tsx->tsx_user->on_rx_request) + { + (*tsx->tsx_user->on_rx_request)(rdata); + + } else if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG && + tsx->tsx_user->on_rx_response) + { + (*tsx->tsx_user->on_rx_response)(rdata); + } + + } + + /* Inform TU about state changed. */ if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) { pjsip_event e; PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src, @@ -1044,8 +1104,13 @@ PJ_DEF(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user, &via->branch_param); /* Calculate hashed key value. */ + PJ_TODO(OPTIMIZE_TSX_BY_PRECALCULATING_HASHED_KEY_VALUE); + /* + blp: somehow this yields different hashed value!! + tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr, tsx->transaction_key.slen); + */ PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, tsx->transaction_key.ptr)); @@ -1158,8 +1223,13 @@ PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user, } /* Calculate hashed key value. */ + PJ_TODO(OPTIMIZE_TSX_BY_PRECALCULATING_HASHED_KEY_VALUE); + /* + blp: somehow this yields different hashed value!! + tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr, tsx->transaction_key.slen); + */ /* Duplicate branch parameter for transaction. */ branch = &rdata->msg_info.via->branch_param; diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c index 90885847..c01e78ee 100644 --- a/pjsip/src/pjsip/sip_ua_layer.c +++ b/pjsip/src/pjsip/sip_ua_layer.c @@ -168,8 +168,11 @@ static void mod_ua_on_tsx_state( pjsip_transaction *tsx, pjsip_event *e) /* Get the dialog where this transaction belongs. */ dlg = tsx->mod_data[mod_ua.mod.id]; - /* Must have the dialog instance! */ - PJ_ASSERT_ON_FAIL(dlg != NULL, return); + /* If dialog instance has gone, it could mean that the dialog + * may has been destroyed. + */ + if (dlg == NULL) + return; /* Hand over the event to the dialog. */ pjsip_dlg_on_tsx_state(dlg, tsx, e); @@ -208,6 +211,17 @@ PJ_DEF(pjsip_user_agent*) pjsip_ua_instance(void) /* + * Get the endpoint where this UA is currently registered. + */ +PJ_DEF(pjsip_endpoint*) pjsip_ua_get_endpt(pjsip_user_agent *ua) +{ + PJ_UNUSED_ARG(ua); + pj_assert(ua == &mod_ua.mod); + return mod_ua.endpt; +} + + +/* * Destroy the user agent layer. */ PJ_DEF(pj_status_t) pjsip_ua_destroy(void) @@ -460,17 +474,27 @@ static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata) pj_str_t *from_tag; pjsip_dialog *dlg; + /* Optimized path: bail out early if request doesn't have To tag */ + if (rdata->msg_info.to->tag.slen == 0) + return PJ_FALSE; + /* Lock user agent before looking up the dialog hash table. */ pj_mutex_lock(mod_ua.mutex); /* Lookup the dialog set, based on the To tag header. */ dlg_set = find_dlg_set_for_msg(rdata); - /* Bail out if dialog is not found. */ + /* If dialog is not found, respond with 481 (Call/Transaction + * Does Not Exist). + */ if (dlg_set == NULL) { - /* Not ours. */ + /* Unable to find dialog. */ pj_mutex_unlock(mod_ua.mutex); - return PJ_FALSE; + + /* Respond with 481 . */ + pjsip_endpt_respond_stateless( mod_ua.endpt, rdata, 481, NULL, NULL, + NULL ); + return PJ_TRUE; } /* Dialog set has been found. @@ -530,7 +554,7 @@ static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata) /* Check if transaction is present. */ tsx = pjsip_rdata_get_tsx(rdata); - if (!tsx) { + if (tsx) { /* Check if dialog is present in the transaction. */ dlg = pjsip_tsx_get_dlg(tsx); if (!dlg) diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 8684f50a..ba068675 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -43,11 +43,7 @@ static const char *event_str[] = "RX_MSG", "TRANSPORT_ERROR", "TSX_STATE", - "RX_2XX_RESPONSE", - "RX_ACK", - "DISCARD_MSG", "USER", - "BEFORE_TX", }; static pj_str_t str_TEXT = { "text", 4}, diff --git a/pjsip/src/pjsua/getopt.c b/pjsip/src/pjsua/getopt.c index b522c579..eae96f1b 100644 --- a/pjsip/src/pjsua/getopt.c +++ b/pjsip/src/pjsua/getopt.c @@ -36,6 +36,8 @@ #include "config.h" #endif +#include <pj/string.h> + #ifndef HAVE_GETOPT_LONG /* getopt_long and getopt_long_only entry points for GNU getopt. diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c index 00cb40de..c921794e 100644 --- a/pjsip/src/pjsua/main.c +++ b/pjsip/src/pjsua/main.c @@ -16,1812 +16,243 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjlib.h> -#include <pjsip_core.h> -#include <pjsip_ua.h> -#include <pjsip_simple.h> -#include <pjmedia.h> -#include <ctype.h> -#include <stdlib.h> -#include <pj/stun.h> +#include "pjsua.h" -#define START_PORT 5060 -#define MAX_BUDDIES 32 -#define THIS_FILE "main.c" -#define MAX_PRESENTITY 32 -#define PRESENCE_TIMEOUT 60 +/* For debugging, disable threading. */ +//#define NO_WORKER_THREAD -/* By default we'll have one worker thread, except when threading - * is disabled. - */ -#if PJ_HAS_THREADS -# define WORKER_COUNT 1 -#else -# define WORKER_COUNT 0 +#ifdef NO_WORKER_THREAD +#include <conio.h> #endif -/* Global variable. */ -static struct -{ - /* Control. */ - pj_pool_factory *pf; - pjsip_endpoint *endpt; - pj_pool_t *pool; - pjsip_user_agent *user_agent; - int worker_cnt; - int worker_quit_flag; - - /* User info. */ - char user_id[64]; - pj_str_t local_uri; - pj_str_t contact; - pj_str_t real_contact; - - /* Dialog. */ - pjsip_dlg *cur_dlg; - - /* Authentication. */ - int cred_count; - pjsip_cred_info cred_info[4]; - - /* Media stack. */ - pj_bool_t null_audio; - pj_med_mgr_t *mmgr; - - /* Misc. */ - int app_log_level; - char *log_filename; - FILE *log_file; - - /* Proxy URLs */ - pj_str_t proxy; - pj_str_t outbound_proxy; - - /* UA auto options. */ - int auto_answer; /* -1 to disable. */ - int auto_hangup; /* -1 to disable */ - - /* Registration. */ - pj_str_t registrar_uri; - pjsip_regc *regc; - pj_int32_t reg_timeout; - pj_timer_entry regc_timer; - - /* STUN */ - pj_str_t stun_srv1; - int stun_port1; - pj_str_t stun_srv2; - int stun_port2; - - /* UDP sockets and their public address. */ - int sip_port; - pj_sock_t sip_sock; - pj_sockaddr_in sip_sock_name; - pj_sock_t rtp_sock; - pj_sockaddr_in rtp_sock_name; - pj_sock_t rtcp_sock; - pj_sockaddr_in rtcp_sock_name; - - /* SIMPLE */ - pj_bool_t hide_status; - pj_bool_t offer_x_ms_msg; - int im_counter; - int buddy_cnt; - pj_str_t buddy[MAX_BUDDIES]; - pj_bool_t buddy_status[MAX_BUDDIES]; - pj_bool_t no_presence; - pjsip_presentity *buddy_pres[MAX_BUDDIES]; - - int pres_cnt; - pjsip_presentity *pres[MAX_PRESENTITY]; - -} global; +#define THIS_FILE "main.c" -enum { AUTO_ANSWER, AUTO_HANGUP }; - -/* This is the data that will be 'attached' on per dialog basis. */ -struct dialog_data -{ - /* Media session. */ - pj_media_session_t *msession; - - /* x-ms-chat session. */ - pj_bool_t x_ms_msg_session; - - /* Cached SDP body, updated when media session changed. */ - pjsip_msg_body *body; - - /* Timer. */ - pj_bool_t has_auto_timer; - pj_timer_entry auto_timer; -}; +static pjsip_inv_session *inv_session; /* - * These are the callbacks to be registered to dialog to receive notifications - * about various events in the dialog. + * Notify UI when invite state has changed. */ -static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, - pjsip_event *event ); -static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata, int retransmission); -static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata); -static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata); -static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata); -static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata); -static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_event *event); -static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event); -static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event); -static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event); -static void dlg_on_terminated (pjsip_dlg *dlg); -static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event); - -/* The callback structure that will be registered to UA layer. */ -struct pjsip_dlg_callback dlg_callback = { - &dlg_on_all_events, - &dlg_on_before_tx, - &dlg_on_tx_msg, - &dlg_on_rx_msg, - &dlg_on_incoming, - &dlg_on_calling, - &dlg_on_provisional, - &dlg_on_connecting, - &dlg_on_established, - &dlg_on_disconnected, - &dlg_on_terminated, - &dlg_on_mid_call_evt -}; - - -/* - * Auxiliary things are put in misc.c, so that this main.c file is more - * readable. - */ -#include "misc.c" - -static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap, - struct pj_timer_entry *entry) -{ - pjsip_dlg *dlg = entry->user_data; - struct dialog_data *dlg_data = dlg->user_data; - - PJ_UNUSED_ARG(timer_heap) - - dlg_data->has_auto_timer = 0; - - if (entry->id == AUTO_ANSWER) { - pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200); - if (tdata) { - struct dialog_data *dlg_data = global.cur_dlg->user_data; - tdata->msg->body = dlg_data->body; - pjsip_dlg_send_msg(dlg, tdata); - } - } else { - pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500); - if (tdata) - pjsip_dlg_send_msg(dlg, tdata); - } -} - -static void update_registration(pjsip_regc *regc, int renew) -{ - pjsip_tx_data *tdata; - - PJ_LOG(3,(THIS_FILE, "Performing SIP registration...")); - - if (renew) { - tdata = pjsip_regc_register(regc, 1); - } else { - tdata = pjsip_regc_unregister(regc); - } - - pjsip_regc_send( regc, tdata ); -} - -static void regc_cb(struct pjsip_regc_cbparam *param) +void ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) { - /* - * Print registration status. - */ - if (param->code < 0 || param->code >= 300) { - PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", - param->code, pjsip_get_status_text(param->code)->ptr)); - global.regc = NULL; - - } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { - PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), " - "will re-register in %d seconds", - param->code, - pjsip_get_status_text(param->code)->ptr, - param->expiration)); - - } else { - PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); - } -} - -static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata, - int *timeout) -{ - int state; - int i; - char url[PJSIP_MAX_URL_SIZE]; - int urllen; - - PJ_UNUSED_ARG(rdata) - - if (*timeout > 0) { - state = PJSIP_EVENT_SUB_STATE_ACTIVE; - if (*timeout > 300) - *timeout = 300; - } else { - state = PJSIP_EVENT_SUB_STATE_TERMINATED; - } - - urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1); - if (urllen < 1) { - pj_native_strcpy(url, "<unknown>"); - } else { - url[urllen] = '\0'; - } - PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s", - url, - (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated"))); - - for (i=0; i<global.pres_cnt; ++i) - if (global.pres[i] == pres) - break; - if (i == global.pres_cnt) - global.pres[global.pres_cnt++] = pres; - - pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info ); - pjsip_presence_notify(pres, state, !global.hide_status); - -} - -static void pres_on_received_refresh(pjsip_presentity *pres, pjsip_rx_data *rdata) -{ - pres_on_received_request(pres, rdata, &pres->sub->default_interval); -} - -/* This is called by presence framework when we receives presence update - * of a resource (buddy). - */ -static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open) -{ - int buddy_index = (int)pres->user_data; - - global.buddy_status[buddy_index] = is_open; - PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s", - global.buddy[buddy_index].ptr, - (is_open ? "Online" : "Offline"))); -} - -/* This is called when the subscription is terminated. */ -static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason) -{ - if (pres->sub->role == PJSIP_ROLE_UAC) { - int buddy_index = (int)pres->user_data; - PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)", - global.buddy[buddy_index].ptr, - reason->slen, reason->ptr)); - global.buddy_pres[buddy_index] = NULL; - global.buddy_status[buddy_index] = 0; - } else { - int i; - PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)", - reason->slen, reason->ptr)); - pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1); - for (i=0; i<global.pres_cnt; ++i) { - if (global.pres[i] == pres) { - int j; - global.pres[i] = NULL; - for (j=i+1; j<global.pres_cnt; ++j) - global.pres[j-1] = global.pres[j]; - global.pres_cnt--; - break; - } - } - } - pjsip_presence_destroy(pres); -} - - -/* Callback attached to SIP body to print the body to message buffer. */ -static int print_msg_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) -{ - pjsip_msg_body *body = msg_body; - return pjsdp_print ((pjsdp_session_desc*)body->data, buf, size); -} - -/* When media session has changed, call this function to update the cached body - * information in the dialog. - */ -static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg) -{ - struct dialog_data *dlg_data = dlg->user_data; - pjsdp_session_desc *sdp; - - sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg); - if (!sdp) { - dlg_data->body = NULL; - return NULL; - } - - /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new - * "m=" line in the SDP. - */ - if (dlg_data->x_ms_msg_session >= 0 && - dlg_data->x_ms_msg_session >= (int)sdp->media_count) + const char *state_names[] = { - pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m)); - sdp->media[sdp->media_count] = m; - dlg_data->x_ms_msg_session = sdp->media_count++; - } - - /* - * For "x-ms-message" line, remove all attributes and connection line etc. - */ - if (dlg_data->x_ms_msg_session >= 0) { - pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session]; - if (m) { - m->desc.media = pj_str("x-ms-message"); - m->desc.port = 5060; - m->desc.transport = pj_str("sip"); - m->desc.fmt_count = 1; - m->desc.fmt[0] = pj_str("null"); - m->attr_count = 0; - m->conn = NULL; - } - } - - dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body)); - dlg_data->body->content_type.type = pj_str("application"); - dlg_data->body->content_type.subtype = pj_str("sdp"); - dlg_data->body->len = 0; /* ignored */ - dlg_data->body->print_body = &print_msg_body; - - dlg_data->body->data = sdp; - return dlg_data->body; -} - -/* This callback will be called on every occurence of events in dialogs */ -static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, - pjsip_event *event ) -{ - PJ_UNUSED_ARG(dlg_evt) - PJ_UNUSED_ARG(event) - - PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg)); -} - -/* This callback is called before each outgoing msg is sent (including - * retransmission). Application can override this notification if it wants - * to modify the message before transmission or if it wants to do something - * else for each transmission. - */ -static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata, int ret_cnt) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(tdata) - - if (ret_cnt > 0) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)", - dlg->obj_name, ret_cnt)); - } -} - -/* This callback is called after a message is sent. */ -static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(tdata) - - PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); -} - -/* This callback is called on receipt of incoming message. */ -static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(rdata) - PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); -} - -/* This callback is called after dialog has sent INVITE */ -static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(tdata) - - pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG && - tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && - tsx->method.id == PJSIP_INVITE_METHOD); - - PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name)); -} - -static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp) -{ - struct dialog_data *dlg_data = dlg->user_data; - pj_bool_t sdp_x_ms_msg_index = -1; - int i; - int mcnt; - const pj_media_stream_info *mi[PJSDP_MAX_MEDIA]; - int has_active; - pj_media_sock_info sock_info; - - /* Find "m=x-ms-message" line in the SDP. */ - for (i=0; i<(int)sdp->media_count; ++i) { - if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0) - sdp_x_ms_msg_index = i; - } - - /* - * Create media session. - */ - pj_memset(&sock_info, 0, sizeof(sock_info)); - sock_info.rtp_sock = global.rtp_sock; - sock_info.rtcp_sock = global.rtcp_sock; - pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); - - dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info); - - /* A session will always be created, unless there is memory - * alloc problem. - */ - pj_assert(dlg_data->msession); - - /* See if we can take the offer by checking that we have at least - * one media stream active. - */ - mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi); - for (i=0, has_active=0; i<mcnt; ++i) { - if (mi[i]->fmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) { - has_active = 1; - break; - } - } - - if (!has_active && sdp_x_ms_msg_index==-1) { - pjsip_tx_data *tdata; - - /* Unable to accept remote's SDP. - * Answer with 488 (Not Acceptable Here) - */ - /* Create 488 response. */ - tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE); - - /* Send response. */ - if (tdata) - pjsip_dlg_send_msg(dlg, tdata); - return; - } - - dlg_data->x_ms_msg_session = sdp_x_ms_msg_index; - - /* Create msg body to be used later in 2xx/response */ - create_msg_body(dlg, 0); - -} - -/* This callback is called after an INVITE is received. */ -static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata) -{ - struct dialog_data *dlg_data; - pjsip_msg *msg; - pjsip_tx_data *tdata; - char buf[128]; - int len; - - PJ_UNUSED_ARG(tsx) - - pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG && - rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && - tsx->method.id == PJSIP_INVITE_METHOD); - - /* - * Notify user! - */ - PJ_LOG(3, (THIS_FILE, "")); - PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name)); - PJ_LOG(3, (THIS_FILE, "")); - len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, - (pjsip_name_addr*)dlg->remote.info->uri, - buf, sizeof(buf)-1); - if (len > 0) { - buf[len] = '\0'; - PJ_LOG(3,(THIS_FILE, "From:\t%s", buf)); - } - len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, - (pjsip_name_addr*)dlg->local.info->uri, - buf, sizeof(buf)-1); - if (len > 0) { - buf[len] = '\0'; - PJ_LOG(3,(THIS_FILE, "To:\t%s", buf)); - } - PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name)); - PJ_LOG(3, (THIS_FILE, "")); - - /* - * Process incoming dialog. - */ - - dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); - dlg->user_data = dlg_data; + "NULL", + "CALLING", + "INCOMING", + "EARLY", + "CONNECTING", + "CONFIRMED", + "DISCONNECTED", + "TERMINATED", + }; - /* Update contact. */ - pjsip_dlg_set_contact(dlg, &global.contact); + PJ_UNUSED_ARG(e); - /* Initialize credentials. */ - pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); + PJ_LOG(3,(THIS_FILE, "INVITE session state changed to %s", state_names[inv->state])); - /* Create media session if the request has "application/sdp" body. */ - msg = rdata->msg; - if (msg->body && - pj_stricmp2(&msg->body->content_type.type, "application")==0 && - pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0) + if (inv->state == PJSIP_INV_STATE_DISCONNECTED || + inv->state == PJSIP_INV_STATE_TERMINATED) { - pjsdp_session_desc *sdp; - - /* Parse SDP body, and instantiate media session based on remote's SDP. - * Then create our SDP body from the session. - */ - sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool); - if (!sdp) - goto send_answer; - - create_session_from_sdp(dlg, sdp); - - } else if (msg->body) { - /* The request has a message body other than "application/sdp" */ - pjsip_accept_hdr *accept; - - /* Create response. */ - tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - - /* Add "Accept" header. */ - accept = pjsip_accept_hdr_create(tdata->pool); - accept->values[0] = pj_str("application/sdp"); - accept->count = 1; - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept); - - /* Send response. */ - pjsip_dlg_send_msg(dlg, tdata); - return; + if (inv == inv_session) + inv_session = NULL; } else { - /* The request has no message body. We can take this request, but - * no media session will be activated. - */ - /* Nothing to do here. */ - } - -send_answer: - /* Immediately answer with 100 (or 180? */ - tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING ); - pjsip_dlg_send_msg(dlg, tdata); - - /* Set current dialog to this dialog if we don't currently have - * current dialog. - */ - if (global.cur_dlg == NULL) { - global.cur_dlg = dlg; - } - - /* Auto-answer if option is specified. */ - if (global.auto_answer >= 0) { - pj_time_val delay = { 0, 0}; - struct dialog_data *dlg_data = dlg->user_data; - - PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds", - global.auto_answer)); - - delay.sec = global.auto_answer; - dlg_data->auto_timer.user_data = dlg; - dlg_data->auto_timer.id = AUTO_ANSWER; - dlg_data->auto_timer.cb = &dlg_auto_timer_callback; - dlg_data->has_auto_timer = 1; - pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); - } -} - -/* This callback is called when dialog has sent/received a provisional response - * to INVITE. - */ -static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_event *event) -{ - const char *action; - - pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.tdata->msg->line.status.code/100 == 1 && - tsx->method.id == PJSIP_INVITE_METHOD) - || - (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.rdata->msg->line.status.code/100 == 1 && - tsx->method.id == PJSIP_INVITE_METHOD)); - - if (event->src_type == PJSIP_EVENT_TX_MSG) - action = "Sending"; - else - action = "Received"; - - PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)", - dlg->obj_name, action, tsx->status_code, - pjsip_get_status_text(tsx->status_code)->ptr)); -} - -/* This callback is called when 200 response to INVITE is sent/received. */ -static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event) -{ - struct dialog_data *dlg_data = dlg->user_data; - const char *action; - - pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.tdata->msg->line.status.code/100 == 2) - || - (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.rdata->msg->line.status.code/100 == 2)); - - if (event->src_type == PJSIP_EVENT_RX_MSG) - action = "Received"; - else - action = "Sending"; - PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action)); + inv_session = inv; - if (event->src_type == PJSIP_EVENT_RX_MSG) { - /* On receipt of 2xx response, negotiate our media capability - * and start media. - */ - pjsip_msg *msg = event->src.rdata->msg; - pjsip_msg_body *body; - pjsdp_session_desc *sdp; - - /* Get SDP from message. */ - - /* Ignore if no SDP body is present. */ - body = msg->body; - if (!body) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!", - dlg->obj_name)); - return; - } - - if (pj_stricmp2(&body->content_type.type, "application") != 0 && - pj_stricmp2(&body->content_type.subtype, "sdp") != 0) - { - PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!", - dlg->obj_name)); - return; - } - - /* Got what seems to be a SDP content. Parse it. */ - sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool); - if (!sdp) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!", - dlg->obj_name)); - return; - } - - /* Negotiate media session with remote's media capability. */ - if (pj_media_session_update (dlg_data->msession, sdp) != 0) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!", - dlg->obj_name)); - return; - } - - /* Update the saved SDP body because media session has changed. - * Also set ack flag to '1', because we only want to send one format/ - * codec for each media streams. - */ - create_msg_body(dlg, 1); - - /* Activate media. */ - pj_media_session_activate (dlg_data->msession); - - } else { - pjsip_msg *msg = event->src.tdata->msg; - - if (msg->body) { - /* On transmission of 2xx response, start media session. */ - pj_media_session_activate (dlg_data->msession); - } } - } -/* This callback is called when ACK to initial INVITE is sent/received. */ -static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event) +static void ui_help(void) { - const char *action; - - pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->type == PJSIP_REQUEST_MSG && - event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) - || - (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_REQUEST_MSG && - event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD)); - - if (event->src_type == PJSIP_EVENT_RX_MSG) - action = "Received"; - else - action = "Sending"; - - PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED", - dlg->obj_name, action)); - - /* Attach SDP body for outgoing ACK. */ - if (event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) - { - struct dialog_data *dlg_data = dlg->user_data; - event->src.tdata->msg->body = dlg_data->body; - } - - /* Auto-hangup if option is specified. */ - if (global.auto_hangup >= 0) { - pj_time_val delay = { 0, 0}; - struct dialog_data *dlg_data = dlg->user_data; - - PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds", - global.auto_hangup)); - - delay.sec = global.auto_hangup; - dlg_data->auto_timer.user_data = dlg; - dlg_data->auto_timer.id = AUTO_HANGUP; - dlg_data->auto_timer.cb = &dlg_auto_timer_callback; - dlg_data->has_auto_timer = 1; - pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); - } + puts(""); + puts("Console keys:"); + puts(" m Make a call"); + puts(" h Hangup current call"); + puts(" q Quit"); + puts(""); } - -/* This callback is called when dialog is disconnected (because of final - * response, BYE, or timer). - */ -static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event) +static void ui_console_main(void) { - struct dialog_data *dlg_data = dlg->user_data; - int status_code; - const pj_str_t *reason; - - PJ_UNUSED_ARG(event) - - /* Cancel auto-answer/auto-hangup timer. */ - if (dlg_data->has_auto_timer) { - pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer); - dlg_data->has_auto_timer = 0; - } - - if (dlg->invite_tsx) - status_code = dlg->invite_tsx->status_code; - else - status_code = 200; - - if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) { - if (event->src_type == PJSIP_EVENT_RX_MSG) - reason = &event->src.rdata->msg->line.status.reason; - else if (event->src_type == PJSIP_EVENT_TX_MSG) - reason = &event->src.tdata->msg->line.status.reason; - else - reason = pjsip_get_status_text(event->obj.tsx->status_code); - } else { - reason = &event->obj.tsx->method.name; - } + char keyin[10]; + char buf[128]; + char *p; + pjsip_inv_session *inv; - PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)", - dlg->obj_name, status_code, - reason->slen, reason->ptr)); + //ui_help(); - if (dlg_data->msession) { - pj_media_session_destroy (dlg_data->msession); - dlg_data->msession = NULL; - } -} + for (;;) { -/* This callback is called when dialog is about to be destroyed. */ -static void dlg_on_terminated(pjsip_dlg *dlg) -{ - PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name)); - - /* If current dialog is equal to this dialog, update it. */ - if (global.cur_dlg == dlg) { - global.cur_dlg = global.cur_dlg->next; - if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { - global.cur_dlg = NULL; - } - } -} +#ifdef NO_WORKER_THREAD + pj_time_val timeout = { 0, 10 }; + pjsip_endpt_handle_events (pjsua.endpt, &timeout); -/* This callback is called for any requests when dialog is established. */ -static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event) -{ - pjsip_transaction *tsx = event->obj.tsx; + if (kbhit()) + fgets(keyin, sizeof(keyin), stdin); +#else + ui_help(); + fgets(keyin, sizeof(keyin), stdin); +#endif - if (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_REQUEST_MSG) - { - if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) { - /* Re-invitation. */ - pjsip_tx_data *tdata; + switch (keyin[0]) { - PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)", - dlg->obj_name)); - tdata = pjsip_dlg_answer(dlg, 200); - if (tdata) { - struct dialog_data *dlg_data = dlg->user_data; - tdata->msg->body = dlg_data->body; - pjsip_dlg_send_msg(dlg, tdata); + case 'm': + if (inv_session != NULL) { + puts("Can not make call while another one is in progress"); + continue; } - } else { - /* Don't worry, endpoint will answer with 500 or whetever. */ - } - } else if (tsx->status_code/100 == 2) { - PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)", - dlg->obj_name, - tsx->method.name.slen, tsx->method.name.ptr, - tsx->status_code, - pjsip_get_status_text(tsx->status_code)->ptr)); +#if 0 + printf("Enter URL to call: "); + fgets(buf, sizeof(buf), stdin); - - } else if (tsx->status_code >= 300) { - pj_bool_t report_failure = PJ_TRUE; - - /* Check for authentication failures. */ - if (tsx->status_code==401 || tsx->status_code==407) { - pjsip_tx_data *tdata; - tdata = pjsip_auth_reinit_req( global.endpt, - dlg->pool, &dlg->auth_sess, - dlg->cred_count, dlg->cred_info, - tsx->last_tx, event->src.rdata ); - if (tdata) { - int rc; - rc = pjsip_dlg_send_msg( dlg, tdata); - report_failure = (rc != 0); + if (buf[0]=='\r' || buf[0]=='\n') { + /* Cancelled. */ + puts("<cancelled>"); + continue; } - } - if (report_failure) { - const pj_str_t *reason; - if (event->src_type == PJSIP_EVENT_RX_MSG) { - reason = &event->src.rdata->msg->line.status.reason; - } else { - reason = pjsip_get_status_text(tsx->status_code); - } - PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)", - dlg->obj_name, tsx->status_code, - reason->slen, reason->ptr)); - } - } -} -/* Initialize sockets and optionally get the public address via STUN. */ -static pj_status_t init_sockets() -{ - enum { - RTP_START_PORT = 4000, - RTP_RANDOM_START = 2, - RTP_RETRY = 10 - }; - enum { - SIP_SOCK, - RTP_SOCK, - RTCP_SOCK, - }; - int i; - int rtp_port; - pj_sock_t sock[3]; - pj_sockaddr_in mapped_addr[3]; - - for (i=0; i<3; ++i) - sock[i] = PJ_INVALID_SOCKET; - - /* Create and bind SIP UDP socket. */ - sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); - if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) { - PJ_LOG(2,(THIS_FILE, "Unable to create socket")); - goto on_error; - } - if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) { - PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port")); - goto on_error; - } - - /* Initialize start of RTP port to try. */ - rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2; - - /* Loop retry to bind RTP and RTCP sockets. */ - for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) { - - /* Create and bind RTP socket. */ - sock[RTP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); - if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) - goto on_error; - if (pj_sock_bind_in(sock[RTP_SOCK], 0, (pj_uint16_t)rtp_port) != 0) { - pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; - continue; - } - - /* Create and bind RTCP socket. */ - sock[RTCP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); - if (sock[RTCP_SOCK] == PJ_INVALID_SOCKET) - goto on_error; - if (pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)) != 0) { - pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; - pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; - continue; - } - - /* - * If we're configured to use STUN, then find out the mapped address, - * and make sure that the mapped RTCP port is adjacent with the RTP. - */ - if (global.stun_port1 == 0) { - pj_str_t hostname; - pj_sockaddr_in addr; - - /* Get local IP address. */ - char hostname_buf[PJ_MAX_HOSTNAME]; - if (gethostname(hostname_buf, sizeof(hostname_buf))) - goto on_error; - hostname = pj_str(hostname_buf); - - pj_memset( &addr, 0, sizeof(addr)); - addr.sin_family = PJ_AF_INET; - if (pj_sockaddr_set_str_addr( &addr, &hostname) != PJ_SUCCESS) - goto on_error; - - for (i=0; i<3; ++i) - pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); - - mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)global.sip_port); - mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port); - mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1)); - break; - } else { - pj_status_t rc; - rc = pj_stun_get_mapped_addr( global.pf, 3, sock, - &global.stun_srv1, global.stun_port1, - &global.stun_srv2, global.stun_port2, - mapped_addr); - if (rc != 0) { - PJ_LOG(3,(THIS_FILE, "Error: %s", pj_stun_get_err_msg(rc))); - goto on_error; + /* Remove trailing newlines. */ + for (p=buf; ; ++p) { + if (*p=='\r' || *p=='\n') *p='\0'; + else if (!*p) break; } + /* Make call! : */ - if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1) - break; - - pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; - pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; - } - } - - if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) { - PJ_LOG(2,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination")); - goto on_error; - } - - global.sip_sock = sock[SIP_SOCK]; - pj_memcpy(&global.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in)); - global.rtp_sock = sock[RTP_SOCK]; - pj_memcpy(&global.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in)); - global.rtcp_sock = sock[RTCP_SOCK]; - pj_memcpy(&global.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in)); - - PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", - pj_inet_ntoa(global.sip_sock_name.sin_addr), - pj_ntohs(global.sip_sock_name.sin_port))); - PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", - pj_inet_ntoa(global.rtp_sock_name.sin_addr), - pj_ntohs(global.rtp_sock_name.sin_port))); - PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d", - pj_inet_ntoa(global.rtcp_sock_name.sin_addr), - pj_ntohs(global.rtcp_sock_name.sin_port))); - return 0; - -on_error: - for (i=0; i<3; ++i) { - if (sock[i] != PJ_INVALID_SOCKET) - pj_sock_close(sock[i]); - } - return -1; -} - -static void log_function(int level, const char *buffer, int len) -{ - /* Write to both stdout and file. */ - if (level <= global.app_log_level) - pj_log_to_stdout(level, buffer, len); - if (global.log_file) { - fwrite(buffer, len, 1, global.log_file); - fflush(global.log_file); - } -} - -/* Initialize stack. */ -static pj_status_t init_stack() -{ - pj_status_t status; - pj_sockaddr_in bind_addr; - pj_sockaddr_in bind_name; - const char *local_addr; - static char local_uri[128]; - - /* Optionally set logging file. */ - if (global.log_filename) { - global.log_file = fopen(global.log_filename, "wt"); - } - - /* Initialize endpoint. This will also call initialization to all the - * modules. - */ - global.endpt = pjsip_endpt_create(global.pf); - if (global.endpt == NULL) { - return -1; - } - - /* Set dialog callback. */ - pjsip_ua_set_dialog_callback(global.user_agent, &dlg_callback); - - /* Init listener's bound address and port. */ - pj_sockaddr_init2(&bind_addr, "0.0.0.0", global.sip_port); - pj_sockaddr_init(&bind_name, pj_gethostname(), global.sip_port); - - /* Add UDP transport listener. */ - status = pjsip_endpt_create_udp_listener( global.endpt, global.sip_sock, - &global.sip_sock_name); - if (status != 0) - return -1; - - local_addr = pj_inet_ntoa(global.sip_sock_name.sin_addr); - -#if PJ_HAS_TCP - /* Add TCP transport listener. */ - status = pjsip_endpt_create_listener( global.endpt, PJSIP_TRANSPORT_TCP, - &bind_addr, &bind_name); - if (status != 0) - return -1; + pjsua_invite(buf, &inv); #endif - /* Determine user_id to be put in Contact */ - if (global.local_uri.slen) { - pj_pool_t *pool = pj_pool_create(global.pf, "parser", 1024, 0, NULL); - pjsip_uri *uri; - - uri = pjsip_parse_uri(pool, global.local_uri.ptr, global.local_uri.slen, 0); - if (uri) { - if (pj_stricmp2(pjsip_uri_get_scheme(uri), "sip")==0) { - pjsip_sip_uri *url = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); - if (url->user.slen) - strncpy(global.user_id, url->user.ptr, url->user.slen); - } - } - pj_pool_release(pool); - } - - if (global.user_id[0]=='\0') { - pj_native_strcpy(global.user_id, "user"); - } - - /* build contact */ - global.real_contact.ptr = local_uri; - global.real_contact.slen = - sprintf(local_uri, "<sip:%s@%s:%d>", global.user_id, local_addr, global.sip_port); - - if (global.contact.slen == 0) - global.contact = global.real_contact; - - /* initialize local_uri with contact if it's not specified in cmdline */ - if (global.local_uri.slen == 0) - global.local_uri = global.contact; - - /* Init proxy. */ - if (global.proxy.slen || global.outbound_proxy.slen) { - int count = 0; - pj_str_t proxy_url[2]; - - if (global.outbound_proxy.slen) { - proxy_url[count++] = global.outbound_proxy; - } - if (global.proxy.slen) { - proxy_url[count++] = global.proxy; - } - - if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) { - PJ_LOG(2,(THIS_FILE, "Error setting proxy address!")); - return -1; - } - } - - /* initialize SIP registration if registrar is configured */ - if (global.registrar_uri.slen) { - global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb); - pjsip_regc_init( global.regc, &global.registrar_uri, - &global.local_uri, - &global.local_uri, - 1, &global.contact, - global.reg_timeout); - pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info ); - } - - return PJ_SUCCESS; -} - -/* Worker thread function, only used when threading is enabled. */ -static void *PJ_THREAD_FUNC worker_thread(void *unused) -{ - PJ_UNUSED_ARG(unused) - - while (!global.worker_quit_flag) { - pj_time_val timeout = { 0, 10 }; - pjsip_endpt_handle_events (global.endpt, &timeout); - } - return NULL; -} - - -/* Make call to the specified URI. */ -static pjsip_dlg *make_call(pj_str_t *remote_uri) -{ - pjsip_dlg *dlg; - pj_str_t local = global.contact; - pj_str_t remote = *remote_uri; - struct dialog_data *dlg_data; - pjsip_tx_data *tdata; - pj_media_sock_info sock_info; - - /* Create new dialog instance. */ - dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC); - - /* Attach our own user data. */ - dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); - dlg->user_data = dlg_data; - - /* Create media session. */ - pj_memset(&sock_info, 0, sizeof(sock_info)); - sock_info.rtp_sock = global.rtp_sock; - sock_info.rtcp_sock = global.rtcp_sock; - pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); - - dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info); - dlg_data->x_ms_msg_session = -1; - - if (global.offer_x_ms_msg) { - const pj_media_stream_info *minfo[32]; - unsigned cnt; - - cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo); - if (cnt > 0) - dlg_data->x_ms_msg_session = cnt; - } - - /* Initialize dialog with local and remote URI. */ - if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) { - pjsip_ua_destroy_dialog(dlg); - return NULL; - } - - /* Initialize credentials. */ - pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); - - /* Send INVITE! */ - tdata = pjsip_dlg_invite(dlg); - tdata->msg->body = create_msg_body (dlg, 0); - - if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) { - pjsip_ua_destroy_dialog(dlg); - return NULL; - } - - return dlg; -} - -/* - * Callback to receive incoming IM message. - */ -static int on_incoming_im_msg(pjsip_rx_data *rdata) -{ - pjsip_msg *msg = rdata->msg; - pjsip_msg_body *body = msg->body; - int len; - char to[128], from[128]; - - - len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, - rdata->from->uri, from, sizeof(from)); - if (len > 0) from[len] = '\0'; - else pj_native_strcpy(from, "<URL too long..>"); - - len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, - rdata->to->uri, to, sizeof(to)); - if (len > 0) to[len] = '\0'; - else pj_native_strcpy(to, "<URL too long..>"); - - PJ_LOG(3,(THIS_FILE, "Incoming instant message:")); - - printf("----- BEGIN INSTANT MESSAGE ----->\n"); - printf("From:\t%s\n", from); - printf("To:\t%s\n", to); - printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : "")); - printf("<------ END INSTANT MESSAGE ------\n"); - - fflush(stdout); - - /* Must answer with final response. */ - return 200; -} - -/* - * Input URL. - */ -static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection) -{ - int i; - - *selection = -1; - - printf("\nBuddy list:\n"); - printf("---------------------------------------\n"); - for (i=0; i<global.buddy_cnt; ++i) { - printf(" %d\t%s <%s>\n", i+1, global.buddy[i].ptr, - (global.buddy_status[i]?"Online":"Offline")); - } - printf("-------------------------------------\n"); + pjsua_invite("sip:localhost:5061", &inv); + break; - printf("Choices\n" - "\t0 For current dialog.\n" - "\t[1-%02d] Select from buddy list\n" - "\tURL An URL\n" - , global.buddy_cnt); - printf("Input: "); - fflush(stdout); - fgets(buf, len, stdin); - buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */ + case 'h': - while (isspace(*buf)) ++buf; + if (inv_session == NULL) { + puts("No current call"); + continue; - if (!*buf || *buf=='\n' || *buf=='\r') - return NULL; + } else { + pj_status_t status; + pjsip_tx_data *tdata; - i = atoi(buf); + status = pjsip_inv_end_session(inv_session, PJSIP_SC_DECLINE, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror("Failed to create end session message", status); + continue; + } - if (i == 0) { - if (isdigit(*buf)) { - *selection = 0; - *out = pj_str("0"); - return out; - } else { - if (verify_sip_url(buf) != 0) { - puts("Invalid URL specified!"); - return NULL; + status = pjsip_inv_send_msg(inv_session, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror("Failed to send end session message", status); + continue; + } } - *out = pj_str(buf); - return out; - } - } else if (i > global.buddy_cnt || i < 0) { - printf("Error: invalid selection!\n"); - return NULL; - } else { - *out = global.buddy[i-1]; - *selection = i; - return out; - } -} - - -static void generic_request_callback( void *token, pjsip_event *event ) -{ - pjsip_transaction *tsx = event->obj.tsx; - - PJ_UNUSED_ARG(token) - - if (tsx->status_code/100 == 2) { - PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)", - event->obj.tsx->method.name.slen, - event->obj.tsx->method.name.ptr, - tsx->status_code, - pjsip_get_status_text(tsx->status_code)->ptr)); - } else if (tsx->status_code==401 || tsx->status_code==407) { - pjsip_tx_data *tdata; - tdata = pjsip_auth_reinit_req( global.endpt, - global.pool, NULL, global.cred_count, global.cred_info, - tsx->last_tx, event->src.rdata); - if (tdata) { - int rc; - pjsip_cseq_hdr *cseq; - cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); - cseq->cseq++; - rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, - &generic_request_callback); - if (rc == 0) - return; - } - PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)", - event->obj.tsx->method.name.slen, - event->obj.tsx->method.name.ptr, - event->obj.tsx->status_code, - pjsip_get_status_text(event->obj.tsx->status_code)->ptr)); - } else { - const pj_str_t *reason; - if (event->src_type == PJSIP_EVENT_RX_MSG) - reason = &event->src.rdata->msg->line.status.reason; - else - reason = pjsip_get_status_text(tsx->status_code); - PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)", - event->obj.tsx->method.name.slen, - event->obj.tsx->method.name.ptr, - event->obj.tsx->status_code, - reason->slen, reason->ptr)); - } -} - - -static void ui_send_im_message() -{ - char line[100]; - char text_buf[100]; - pj_str_t str; - pj_str_t text_msg; - int selection, rc; - pjsip_tx_data *tdata; - - if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) - return; - - - printf("Enter text to send (empty to cancel): "); fflush(stdout); - fgets(text_buf, sizeof(text_buf), stdin); - text_buf[strlen(text_buf)-1] = '\0'; - if (!*text_buf) - return; - - text_msg = pj_str(text_buf); - - if (selection==0) { - pjsip_method message_method; - pj_str_t str_MESSAGE = { "MESSAGE", 7 }; - - /* Send IM to current dialog. */ - if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { - printf("No current dialog or dialog state is not ESTABLISHED!\n"); - return; - } - - pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE); - tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 ); - if (tdata) { - /* Create message body for the text. */ - pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body)); - body->content_type.type = pj_str("text"); - body->content_type.subtype = pj_str("plain"); - body->data = pj_pool_alloc(tdata->pool, text_msg.slen); - pj_memcpy(body->data, text_msg.ptr, text_msg.slen); - body->len = text_msg.slen; - body->print_body = &pjsip_print_text_body; - - /* Assign body to message, and send the message! */ - tdata->msg->body = body; - pjsip_dlg_send_msg( global.cur_dlg, tdata ); - } + break; - } else { - /* Send IM to buddy list. */ - pjsip_method message; - static pj_str_t MESSAGE = { "MESSAGE", 7 }; - pjsip_method_init_np(&message, &MESSAGE); - tdata = pjsip_endpt_create_request(global.endpt, &message, - &str, - &global.real_contact, - &str, &global.real_contact, NULL, -1, - &text_msg); - if (!tdata) { - puts("Error creating request"); - return; - } - rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback); - if (rc == 0) { - printf("Sending IM message %d\n", global.im_counter); - ++global.im_counter; - } else { - printf("Error: unable to send IM message!\n"); + case 'q': + goto on_exit; } } -} - -static void ui_send_options() -{ - char line[100]; - pj_str_t str; - int selection, rc; - pjsip_tx_data *tdata; - pjsip_method options; - if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) - return; - - pjsip_method_set( &options, PJSIP_OPTIONS_METHOD ); - - if (selection == 0) { - /* Send OPTIONS to current dialog. */ - tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1); - if (tdata) - pjsip_dlg_send_msg( global.cur_dlg, tdata ); - } else { - /* Send OPTIONS to arbitrary party. */ - tdata = pjsip_endpt_create_request( global.endpt, &options, - &str, - &global.local_uri, &str, - &global.real_contact, - NULL, -1, NULL); - if (tdata) { - rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, - &generic_request_callback); - if (rc != 0) - PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!")); - } - } +on_exit: + ; } -static void init_presence() +static pj_bool_t console_on_rx_msg(pjsip_rx_data *rdata) { - const pjsip_presence_cb pres_cb = { - NULL, - &pres_on_received_request, - &pres_on_received_refresh, - &pres_on_received_update, - &pres_on_terminated - }; - - pjsip_presence_init(&pres_cb); + PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n" + "%s\n" + "--end msg--", + rdata->msg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + rdata->msg_info.msg_buf)); + + /* Must return false for logger! */ + return PJ_FALSE; } -/* Subscribe presence information for all buddies. */ -static void subscribe_buddies_presence() +static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata) { - int i; - for (i=0; i<global.buddy_cnt; ++i) { - pjsip_presentity *pres; - if (global.buddy_pres[i]) - continue; - pres = pjsip_presence_create( global.endpt, &global.local_uri, - &global.buddy[i], PRESENCE_TIMEOUT, (void*)i); - if (pres) { - pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info ); - pjsip_presence_subscribe( pres ); - } - global.buddy_pres[i] = pres; - } -} + PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" + "%s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + tdata->buf.start)); -/* Unsubscribe presence information for all buddies. */ -static void unsubscribe_buddies_presence() -{ - int i; - for (i=0; i<global.buddy_cnt; ++i) { - pjsip_presentity *pres = global.buddy_pres[i]; - if (pres) { - pjsip_presence_unsubscribe(pres); - pjsip_presence_destroy(pres); - global.buddy_pres[i] = NULL; - } - } + return PJ_SUCCESS; } -/* Unsubscribe presence. */ -static void unsubscribe_presence() +static pjsip_module console_msg_logger = { - int i; + NULL, NULL, /* prev, next. */ + { "mod-console-msg-logger", 22 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* User data. */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &console_on_rx_msg, /* on_rx_request() */ + &console_on_rx_msg, /* on_rx_response() */ + &console_on_tx_msg, /* on_tx_request. */ + &console_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ - unsubscribe_buddies_presence(); - for (i=0; i<global.pres_cnt; ++i) { - pjsip_presentity *pres = global.pres[i]; - pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 0); - pjsip_presence_destroy( pres ); - } -} +}; -/* Advertise online status to subscribers. */ -static void update_im_status() -{ - int i; - for (i=0; i<global.pres_cnt; ++i) { - pjsip_presentity *pres = global.pres[i]; - pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_ACTIVE, - !global.hide_status); - } -} -/* - * Main program. - */ -int main(int argc, char *argv[]) +int main() { - /* set to WORKER_COUNT+1 to avoid zero size warning - * when threading is disabled. */ - pj_thread_t *thread[WORKER_COUNT+1]; - pj_caching_pool cp; - int i; - - global.sip_port = 5060; - global.auto_answer = -1; - global.auto_hangup = -1; - global.app_log_level = 3; - - pj_log_set_level(4); - pj_log_set_log_func(&log_function); - - /* Init PJLIB */ - if (pj_init() != PJ_SUCCESS) - return 1; - - /* Init caching pool. */ - pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); - global.pf = &cp.factory; - - /* Create memory pool for application. */ - global.pool = pj_pool_create(global.pf, "main", 1024, 0, NULL); - - /* Parse command line arguments. */ - if (parse_args(global.pool, argc, argv) != PJ_SUCCESS) { - pj_caching_pool_destroy(&cp); - return 1; - } - - /* Init sockets */ - if (init_sockets() != 0) { - pj_caching_pool_destroy(&cp); - return 1; - } - - /* Initialize stack. */ - if (init_stack() != PJ_SUCCESS) { - pj_caching_pool_destroy(&cp); - return 1; - } - - /* Set callback to receive incoming IM */ - pjsip_messaging_set_incoming_callback( &on_incoming_im_msg ); - - /* Set default worker count (can be zero) */ - global.worker_cnt = WORKER_COUNT; + /* Init default settings. */ - /* Create user worker thread(s), only when threading is enabled. */ - for (i=0; i<global.worker_cnt; ++i) { - thread[i] = pj_thread_create( global.pool, "sip%p", - &worker_thread, - NULL, 0, NULL, 0); - if (thread == NULL) { - global.worker_quit_flag = 1; - for (--i; i>=0; --i) { - pj_thread_join(thread[i]); - pj_thread_destroy(thread[i]); - } - pj_caching_pool_destroy(&cp); - return 1; - } - } - - printf("Worker thread count: %d\n", global.worker_cnt); - - /* Perform registration, if required. */ - if (global.regc) { - update_registration(global.regc, 1); - } - - /* Initialize media manager. */ - global.mmgr = pj_med_mgr_create(global.pf); - - /* Init presence. */ - init_presence(); + pjsua_default(); - /* Subscribe presence information of all buddies. */ - if (!global.no_presence) - subscribe_buddies_presence(); - /* Initializatio completes, loop waiting for commands. */ - for (;!global.worker_quit_flag;) { - pj_str_t str; - char line[128]; - -#if WORKER_COUNT==0 - /* If worker thread does not exist, main thread must poll for evetns. - * But this won't work very well since main thread is blocked by - * fgets(). So keep pressing the ENTER key to get the events! - */ - pj_time_val timeout = { 0, 100 }; - pjsip_endpt_handle_events(global.endpt, &timeout); - puts("Keep pressing ENTER key to get the events!"); +#ifdef NO_WORKER_THREAD + pjsua.thread_cnt = 0; #endif - printf("\nCurrent dialog: "); - print_dialog(global.cur_dlg); - puts(""); - - keystroke_help(); - fgets(line, sizeof(line), stdin); - - switch (*line) { - case 'm': - puts("Make outgoing call"); - if (ui_input_url(&str, line, sizeof(line), &i) != NULL) { - pjsip_dlg *dlg = make_call(&str); - if (global.cur_dlg == NULL) { - global.cur_dlg = dlg; - } - } - break; - case 'i': - puts("Send Instant Messaging"); - ui_send_im_message(); - break; - case 'o': - puts("Send OPTIONS"); - ui_send_options(); - break; - case 'a': - if (global.cur_dlg) { - unsigned code; - pjsip_tx_data *tdata; - struct dialog_data *dlg_data = global.cur_dlg->user_data; - - printf("Answer with status code (1xx-6xx): "); - fflush(stdout); - fgets(line, sizeof(line), stdin); - str = pj_str(line); - str.slen -= 1; + /* Initialize pjsua. + * This will start worker thread, client registration, etc. + */ - code = pj_strtoul(&str); - tdata = pjsip_dlg_answer(global.cur_dlg, code); - if (tdata) { - if (code/100 == 2) { - tdata->msg->body = dlg_data->body; - } - pjsip_dlg_send_msg(global.cur_dlg, tdata); + if (pjsua_init() != PJ_SUCCESS) + return 1; - } - } else { - puts("No current dialog"); - } - break; - case 'h': - if (global.cur_dlg) { - pjsip_tx_data *tdata; - tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE); - if (tdata) { - pjsip_dlg_send_msg(global.cur_dlg, tdata); - } - } else { - puts("No current dialog"); - } - break; - case ']': - if (global.cur_dlg) { - global.cur_dlg = global.cur_dlg->next; - if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { - global.cur_dlg = global.cur_dlg->next; - } - } else { - puts("No current dialog"); - } - break; - case '[': - if (global.cur_dlg) { - global.cur_dlg = global.cur_dlg->prev; - if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { - global.cur_dlg = global.cur_dlg->prev; - } - } else { - puts("No current dialog"); - } - break; - case 'd': - pjsip_endpt_dump(global.endpt, *(line+1)=='1'); - pjsip_ua_dump(global.user_agent); - break; - case 's': - if (*(line+1) == 'u') - subscribe_buddies_presence(); - break; - case 'u': - if (*(line+1) == 's') - unsubscribe_presence(); - break; - case 't': - global.hide_status = !global.hide_status; - update_im_status(); - break; - case 'q': - goto on_exit; - case 'l': - print_all_dialogs(); - break; - } - } + /* Register message logger to print incoming and outgoing + * messages. + */ -on_exit: - /* Unregister, if required. */ - if (global.regc) { - update_registration(global.regc, 0); - } + pjsip_endpt_register_module(pjsua.endpt, &console_msg_logger); - /* Unsubscribe presence. */ - unsubscribe_presence(); - /* Allow one second to get all events. */ - if (1) { - pj_time_val end_time; + /* Sleep for a while, let any messages get printed to console: */ - pj_gettimeofday(&end_time); - end_time.sec++; + pj_thread_sleep(500); - PJ_LOG(3,(THIS_FILE, "Shutting down..")); - for (;;) { - pj_time_val timeout = { 0, 20 }, now; - pjsip_endpt_handle_events (global.endpt, &timeout); - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, end_time); - if (now.sec >= 1) - break; - } - } - global.worker_quit_flag = 1; + /* Start UI console main loop: */ - pj_med_mgr_destroy(global.mmgr); + ui_console_main(); - /* Wait all threads to quit. */ - for (i=0; i<global.worker_cnt; ++i) { - pj_thread_join(thread[i]); - pj_thread_destroy(thread[i]); - } - /* Destroy endpoint. */ - pjsip_endpt_destroy(global.endpt); + /* Destroy pjsua: */ - /* Destroy caching pool. */ - pj_caching_pool_destroy(&cp); + pjsua_destroy(); - /* Close log file, if any. */ - if (global.log_file) - fclose(global.log_file); + /* Exit... */ return 0; } -/* - * Register static modules to the endpoint. - */ -pj_status_t register_static_modules( pj_size_t *count, - pjsip_module **modules ) -{ - /* Reset count. */ - *count = 0; - - /* Register user agent module. */ - modules[(*count)++] = pjsip_ua_get_module(); - global.user_agent = modules[0]->mod_data; - modules[(*count)++] = pjsip_messaging_get_module(); - modules[(*count)++] = pjsip_event_sub_get_module(); - - return PJ_SUCCESS; -} diff --git a/pjsip/src/pjsua/pjsua.c b/pjsip/src/pjsua/pjsua.c new file mode 100644 index 00000000..8e9cbde6 --- /dev/null +++ b/pjsip/src/pjsua/pjsua.c @@ -0,0 +1,648 @@ +/* $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 "pjsua.h" + +struct pjsua pjsua; + +#define THIS_FILE "pjsua.c" + + +#define PJSUA_LOCAL_URI "<sip:bennylp@192.168.0.7>" +#define PJSUA_CONTACT_URI "<sip:bennylp@192.168.0.7>" + +static char *PJSUA_DUMMY_SDP_OFFER = + "v=0\r\n" + "o=offer 2890844526 2890844526 IN IP4 127.0.0.1\r\n" + "s= \r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n"; + +static char *PJSUA_DUMMY_SDP_ANSWER = + "v=0\r\n" + "o=answer 2890844730 2890844730 IN IP4 127.0.0.1\r\n" + "s= \r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n"; + +/* + * Init default application parameters. + */ +void pjsua_default(void) +{ + + /* Normally need another thread for console application, because main + * thread will be blocked in fgets(). + */ + pjsua.thread_cnt = 1; + + + /* Default transport settings: */ + + pjsua.sip_port = 5060; + + + /* Default logging settings: */ + + pjsua.log_level = 5; + pjsua.app_log_level = 4; + pjsua.log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE; + + /* Default: do not use STUN: */ + + pjsua.stun_port1 = pjsua.stun_port2 = 0; +} + + +/* + * Display error message for the specified error code. + */ +void pjsua_perror(const char *title, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg)); +} + + +/* + * Handler for receiving incoming requests. + * + * This handler serves multiple purposes: + * - it receives requests outside dialogs. + * - it receives requests inside dialogs, when the requests are + * unhandled by other dialog usages. Example of these + * requests are: MESSAGE. + */ +static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata) +{ + PJ_UNUSED_ARG(rdata); + PJ_TODO(IMPLEMENT_UAS); + return PJ_FALSE; +} + + +/* + * Handler for receiving incoming responses. + * + * This handler serves multiple purposes: + * - it receives strayed responses (i.e. outside any dialog and + * outside any transactions). + * - it receives responses coming to a transaction, when pjsua + * module is set as transaction user for the transaction. + * - it receives responses inside a dialog, when these responses + * are unhandled by other dialog usages. + */ +static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata) +{ + PJ_UNUSED_ARG(rdata); + PJ_TODO(IMPLEMENT_UAS); + return PJ_FALSE; +} + + +/* + * This callback receives notification from invite session when the + * session state has changed. + */ +static void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) +{ + ui_inv_on_state_changed(inv, e); +} + + +/* + * This callback is called by invite session framework when UAC session + * has forked. + */ +static void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); + + PJ_TODO(HANDLE_FORKED_DIALOG); +} + +/* + * Initialize sockets and optionally get the public address via STUN. + */ +static pj_status_t init_sockets() +{ + enum { + RTP_START_PORT = 4000, + RTP_RANDOM_START = 2, + RTP_RETRY = 10 + }; + enum { + SIP_SOCK, + RTP_SOCK, + RTCP_SOCK, + }; + int i; + pj_uint16_t rtp_port; + pj_sock_t sock[3]; + pj_sockaddr_in mapped_addr[3]; + pj_status_t status; + + for (i=0; i<3; ++i) + sock[i] = PJ_INVALID_SOCKET; + + /* Create and bind SIP UDP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[SIP_SOCK]); + if (status != PJ_SUCCESS) { + pjsua_perror("socket() error", status); + goto on_error; + } + + status = pj_sock_bind_in(sock[SIP_SOCK], 0, pjsua.sip_port); + if (status != PJ_SUCCESS) { + pjsua_perror("bind() error", status); + goto on_error; + } + + /* Initialize start of RTP port to try. */ + rtp_port = (pj_uint16_t)(RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2); + + /* Loop retry to bind RTP and RTCP sockets. */ + for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) { + + /* Create and bind RTP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTP_SOCK]); + if (status != PJ_SUCCESS) { + pjsua_perror("socket() error", status); + goto on_error; + } + + status = pj_sock_bind_in(sock[RTP_SOCK], 0, rtp_port); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[RTP_SOCK]); + sock[RTP_SOCK] = PJ_INVALID_SOCKET; + continue; + } + + /* Create and bind RTCP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTCP_SOCK]); + if (status != PJ_SUCCESS) { + pjsua_perror("socket() error", status); + goto on_error; + } + + status = pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[RTP_SOCK]); + sock[RTP_SOCK] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[RTCP_SOCK]); + sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + continue; + } + + /* + * If we're configured to use STUN, then find out the mapped address, + * and make sure that the mapped RTCP port is adjacent with the RTP. + */ + if (pjsua.stun_port1 == 0) { + const pj_str_t *hostname; + pj_sockaddr_in addr; + + /* Get local IP address. */ + hostname = pj_gethostname(); + + pj_memset( &addr, 0, sizeof(addr)); + addr.sin_family = PJ_AF_INET; + status = pj_sockaddr_in_set_str_addr( &addr, hostname); + if (status != PJ_SUCCESS) { + pjsua_perror("Unresolvable local hostname", status); + goto on_error; + } + + for (i=0; i<3; ++i) + pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); + + mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)pjsua.sip_port); + mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port); + mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1)); + break; + } else { + status = pj_stun_get_mapped_addr( &pjsua.cp.factory, 3, sock, + &pjsua.stun_srv1, pjsua.stun_port1, + &pjsua.stun_srv2, pjsua.stun_port2, + mapped_addr); + if (status != PJ_SUCCESS) { + pjsua_perror("STUN error", status); + goto on_error; + } + + if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1) + break; + + pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; + pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + } + } + + if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) { + PJ_LOG(1,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination")); + goto on_error; + } + + pjsua.sip_sock = sock[SIP_SOCK]; + pj_memcpy(&pjsua.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in)); + pjsua.rtp_sock = sock[RTP_SOCK]; + pj_memcpy(&pjsua.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in)); + pjsua.rtcp_sock = sock[RTCP_SOCK]; + pj_memcpy(&pjsua.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in)); + + PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", + pj_inet_ntoa(pjsua.sip_sock_name.sin_addr), + pj_ntohs(pjsua.sip_sock_name.sin_port))); + PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", + pj_inet_ntoa(pjsua.rtp_sock_name.sin_addr), + pj_ntohs(pjsua.rtp_sock_name.sin_port))); + PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d", + pj_inet_ntoa(pjsua.rtcp_sock_name.sin_addr), + pj_ntohs(pjsua.rtcp_sock_name.sin_port))); + + return PJ_SUCCESS; + +on_error: + for (i=0; i<3; ++i) { + if (sock[i] != PJ_INVALID_SOCKET) + pj_sock_close(sock[i]); + } + return status; +} + + + +/* + * Initialize stack. + */ +static pj_status_t init_stack(void) +{ + pj_status_t status; + + /* Create global endpoint: */ + + { + const pj_str_t *hostname; + const char *endpt_name; + + /* Endpoint MUST be assigned a globally unique name. + * The name will be used as the hostname in Warning header. + */ + + /* For this implementation, we'll use hostname for simplicity */ + hostname = pj_gethostname(); + endpt_name = hostname->ptr; + + /* Create the endpoint: */ + + status = pjsip_endpt_create(&pjsua.cp.factory, endpt_name, + &pjsua.endpt); + if (status != PJ_SUCCESS) { + pjsua_perror("Unable to create SIP endpoint", status); + return status; + } + } + + + /* Initialize transaction layer: */ + + status = pjsip_tsx_layer_init(pjsua.endpt); + if (status != PJ_SUCCESS) { + pjsua_perror("Transaction layer initialization error", status); + goto on_error; + } + + /* Initialize UA layer module: */ + + status = pjsip_ua_init( pjsua.endpt, NULL ); + if (status != PJ_SUCCESS) { + pjsua_perror("UA layer initialization error", status); + goto on_error; + } + + /* Initialize and register pjsua's application module: */ + + { + pjsip_module my_mod = + { + NULL, NULL, /* prev, next. */ + { "mod-pjsua", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* User data. */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &mod_pjsua_on_rx_request, /* on_rx_request() */ + &mod_pjsua_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }; + + pjsua.mod = my_mod; + + status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod); + if (status != PJ_SUCCESS) { + pjsua_perror("Unable to register pjsua module", status); + goto on_error; + } + } + + /* Initialize invite session module: */ + + { + + /* Initialize invite session callback. */ + pjsip_inv_callback inv_cb; + + pj_memset(&inv_cb, 0, sizeof(inv_cb)); + inv_cb.on_state_changed = &pjsua_inv_on_state_changed; + inv_cb.on_new_session = &pjsua_inv_on_new_session; + + /* Initialize invite session module: */ + status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb); + if (status != PJ_SUCCESS) { + pjsua_perror("Invite usage initialization error", status); + goto on_error; + } + + } + + + /* Add UDP transport: */ + + { + /* Init the published name for the transport. + * Depending whether STUN is used, this may be the STUN mapped + * address, or socket's bound address. + */ + pjsip_host_port addr_name; + + addr_name.host.ptr = pj_inet_ntoa(pjsua.sip_sock_name.sin_addr); + addr_name.host.slen = pj_native_strlen(addr_name.host.ptr); + addr_name.port = pj_ntohs(pjsua.sip_sock_name.sin_port); + + /* Create UDP transport from previously created UDP socket: */ + + status = pjsip_udp_transport_attach( pjsua.endpt, pjsua.sip_sock, + &addr_name, 1, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror("Unable to start UDP transport", status); + goto on_error; + } + } + + /* Initialize local user info and contact: */ + + { + pj_strdup2(pjsua.pool, &pjsua.local_uri, PJSUA_LOCAL_URI); + pj_strdup2(pjsua.pool, &pjsua.contact_uri, PJSUA_CONTACT_URI); + } + + /* Initialize global route_set: */ + + PJ_TODO(INIT_GLOBAL_ROUTE_SET); + + + /* Start registration: */ + + PJ_TODO(START_REGISTRATION); + + /* Done? */ + + return PJ_SUCCESS; + + +on_error: + pjsip_endpt_destroy(pjsua.endpt); + pjsua.endpt = NULL; + return status; +} + + +static int PJ_THREAD_FUNC pjsua_worker_thread(void *arg) +{ + PJ_UNUSED_ARG(arg); + + while (!pjsua.quit_flag) { + pj_time_val timeout = { 0, 10 }; + pjsip_endpt_handle_events (pjsua.endpt, &timeout); + } + + return 0; +} + +/* + * Initialize pjsua application. + * This will start the registration process, if registration is configured. + */ +pj_status_t pjsua_init(void) +{ + int i; /* Must be signed */ + pj_status_t status; + + /* Init PJLIB logging: */ + + pj_log_set_level(pjsua.log_level); + pj_log_set_decor(pjsua.log_decor); + + + /* Init PJLIB: */ + + status = pj_init(); + if (status != PJ_SUCCESS) { + pjsua_perror("pj_init() error", status); + return status; + } + + /* Init memory pool: */ + + /* Init caching pool. */ + pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0); + + /* Create memory pool for application. */ + pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL); + + + /* Init sockets (STUN etc): */ + + status = init_sockets(); + if (status != PJ_SUCCESS) { + pj_caching_pool_destroy(&pjsua.cp); + pjsua_perror("init_sockets() has returned error", status); + return status; + } + + + /* Init PJSIP and all the modules: */ + + status = init_stack(); + if (status != PJ_SUCCESS) { + pj_caching_pool_destroy(&pjsua.cp); + pjsua_perror("Stack initialization has returned error", status); + return status; + } + + /* Create worker thread(s), if required: */ + + for (i=0; i<pjsua.thread_cnt; ++i) { + status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_worker_thread, + NULL, 0, 0, &pjsua.threads[i]); + if (status != PJ_SUCCESS) { + pjsua.quit_flag = 1; + for (--i; i>=0; --i) { + pj_thread_join(pjsua.threads[i]); + pj_thread_destroy(pjsua.threads[i]); + } + pj_caching_pool_destroy(&pjsua.cp); + return status; + } + } + + /* Done. */ + return PJ_SUCCESS; +} + + +/* + * Destroy pjsua. + */ +pj_status_t pjsua_destroy(void) +{ + int i; + + /* Signal threads to quit: */ + + pjsua.quit_flag = 1; + + /* Wait worker threads to quit: */ + + for (i=0; i<pjsua.thread_cnt; ++i) { + + pj_thread_join(pjsua.threads[i]); + pj_thread_destroy(pjsua.threads[i]); + } + + /* Destroy endpoint. */ + pjsip_endpt_destroy(pjsua.endpt); + pjsua.endpt = NULL; + + /* Destroy caching pool. */ + pj_caching_pool_destroy(&pjsua.cp); + + + /* Done. */ + + return PJ_SUCCESS; +} + + +/** + * Make outgoing call. + */ +pj_status_t pjsua_invite(const char *cstr_dest_uri, + pjsip_inv_session **p_inv) +{ + pj_str_t dest_uri; + pjsip_dialog *dlg; + pjmedia_sdp_session *offer; + pjsip_inv_session *inv; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Convert cstr_dest_uri to dest_uri */ + + dest_uri = pj_str((char*)cstr_dest_uri); + + /* Create outgoing dialog: */ + + status = pjsip_dlg_create_uac( pjsip_ua_instance(), &pjsua.local_uri, + &pjsua.contact_uri, &dest_uri, &dest_uri, + &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror("Dialog creation failed", status); + return status; + } + + /* Create dummy SDP for offer: */ + + status = pjmedia_sdp_parse(dlg->pool, PJSUA_DUMMY_SDP_OFFER, + pj_native_strlen(PJSUA_DUMMY_SDP_OFFER), + &offer); + if (status != PJ_SUCCESS) { + pjsua_perror("Dummy SDP offer parsing failed", status); + goto on_error; + } + + /* Create the INVITE session: */ + + status = pjsip_inv_create_uac( dlg, offer, 0, &inv); + if (status != PJ_SUCCESS) { + pjsua_perror("Invite session creation failed", status); + goto on_error; + } + + + /* Set credentials: */ + + PJ_TODO(SET_DIALOG_CREDENTIALS); + + + /* Create initial INVITE: */ + + status = pjsip_inv_invite(inv, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror("Unable to create initial INVITE request", status); + goto on_error; + } + + + /* Send initial INVITE: */ + + status = pjsip_inv_send_msg(inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror("Unable to send initial INVITE request", status); + goto on_error; + } + + + /* Done. */ + + *p_inv = inv; + + return PJ_SUCCESS; + + +on_error: + + PJ_TODO(DESTROY_DIALOG_ON_FAIL); + return status; +} + diff --git a/pjsip/src/pjsua/pjsua.h b/pjsip/src/pjsua/pjsua.h new file mode 100644 index 00000000..20e300f3 --- /dev/null +++ b/pjsip/src/pjsua/pjsua.h @@ -0,0 +1,136 @@ +/* $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 + */ +#ifndef __PJSUA_H__ +#define __PJSUA_H__ + +/* Include all PJSIP core headers. */ +#include <pjsip.h> + +/* Include all PJMEDIA headers. */ +#include <pjmedia.h> + +/* Include all PJSIP-UA headers */ +#include <pjsip_ua.h> + +/* Include all PJLIB-UTIL headers. */ +#include <pjlib-util.h> + +/* Include all PJLIB headers. */ +#include <pjlib.h> + + +/* PJSUA application variables. */ +extern struct pjsua +{ + /* Control: */ + + pj_caching_pool cp; /**< Global pool factory. */ + pjsip_endpoint *endpt; /**< Global endpoint. */ + pj_pool_t *pool; /**< pjsua's private pool. */ + pjsip_module mod; /**< pjsua's PJSIP module. */ + + + /* User info: */ + pj_str_t local_uri; /**< Uri in From: header. */ + pj_str_t contact_uri; /**< Uri in Contact: header. */ + + /* Threading: */ + + int thread_cnt; /**< Thread count. */ + pj_thread_t *threads[8]; /**< Thread instances. */ + pj_bool_t quit_flag; /**< To signal thread to quit. */ + + /* Transport (UDP): */ + + pj_uint16_t sip_port; /**< SIP signaling port. */ + pj_sock_t sip_sock; /**< SIP UDP socket. */ + pj_sockaddr_in sip_sock_name; /**< Public/STUN UDP socket addr. */ + pj_sock_t rtp_sock; /**< RTP socket. */ + pj_sockaddr_in rtp_sock_name; /**< Public/STUN UDP socket addr. */ + pj_sock_t rtcp_sock; /**< RTCP socket. */ + pj_sockaddr_in rtcp_sock_name;/**< Public/STUN UDP socket addr. */ + + + + /* STUN: */ + + pj_str_t stun_srv1; + int stun_port1; + pj_str_t stun_srv2; + int stun_port2; + + + /* Misc: */ + + int log_level; /**< Logging verbosity. */ + int app_log_level; /**< stdout log verbosity. */ + unsigned log_decor; /**< Log decoration. */ + +} pjsua; + + +/***************************************************************************** + * PJSUA API. + */ + +/** + * Initialize pjsua settings with default parameters. + */ +void pjsua_default(void); + + +/** + * Display error message for the specified error code. + */ +void pjsua_perror(const char *title, pj_status_t status); + + +/** + * Initialize pjsua application. + * This will start the registration process, if registration is configured. + */ +pj_status_t pjsua_init(void); + + +/** + * Destroy pjsua. + */ +pj_status_t pjsua_destroy(void); + + +/** + * Make outgoing call. + */ +pj_status_t pjsua_invite(const char *cstr_dest_uri, + pjsip_inv_session **p_inv); + + +/***************************************************************************** + * User Interface API. + * The UI API specifies functions that will be called by pjsua upon + * occurence of various events. + */ + +/** + * Notify UI when invite state has changed. + */ +void ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e); + + +#endif /* __PJSUA_H__ */ |