Implemented major feature: call hold and transfer
git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@212 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
parent
9ffa6a8da3
commit
26ff906cfe
|
@ -93,6 +93,10 @@ SOURCE="..\src\pjsip-ua\sip_inv.c"
|
|||
|
||||
SOURCE="..\src\pjsip-ua\sip_reg.c"
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE="..\src\pjsip-ua\sip_xfer.c"
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Header Files"
|
||||
|
||||
|
@ -109,6 +113,10 @@ SOURCE="..\include\pjsip-ua\sip_inv.h"
|
|||
|
||||
SOURCE="..\include\pjsip-ua\sip_regc.h"
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE="..\include\pjsip-ua\sip_xfer.h"
|
||||
# End Source File
|
||||
# End Group
|
||||
# End Target
|
||||
# End Project
|
||||
|
|
|
@ -22,48 +22,54 @@
|
|||
|
||||
#include <pjsip/sip_errno.h>
|
||||
|
||||
/**
|
||||
* Start of error code relative to PJ_ERRNO_START_USER.
|
||||
*/
|
||||
#define PJSIP_SIMPLE_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2)
|
||||
|
||||
|
||||
/**
|
||||
* @hideinitializer
|
||||
* No event package with the specified name.
|
||||
*/
|
||||
#define PJSIP_SIMPLE_ENOPKG -1
|
||||
#define PJSIP_SIMPLE_ENOPKG (PJSIP_SIMPLE_ERRNO_START+1) /*270001*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Event package already exists.
|
||||
*/
|
||||
#define PJSIP_SIMPLE_EPKGEXISTS -1
|
||||
#define PJSIP_SIMPLE_EPKGEXISTS (PJSIP_SIMPLE_ERRNO_START+2) /*270002*/
|
||||
|
||||
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Expecting SUBSCRIBE request
|
||||
*/
|
||||
#define PJSIP_SIMPLE_ENOTSUBSCRIBE -1
|
||||
#define PJSIP_SIMPLE_ENOTSUBSCRIBE (PJSIP_SIMPLE_ERRNO_START+20) /*270020*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* No presence associated with subscription
|
||||
*/
|
||||
#define PJSIP_SIMPLE_ENOPRESENCE -1
|
||||
#define PJSIP_SIMPLE_ENOPRESENCE (PJSIP_SIMPLE_ERRNO_START+21) /*270021*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* No presence info in server subscription
|
||||
*/
|
||||
#define PJSIP_SIMPLE_ENOPRESENCEINFO -1
|
||||
#define PJSIP_SIMPLE_ENOPRESENCEINFO (PJSIP_SIMPLE_ERRNO_START+22) /*270022*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Bad Content-Type
|
||||
*/
|
||||
#define PJSIP_SIMPLE_EBADCONTENT -1
|
||||
#define PJSIP_SIMPLE_EBADCONTENT (PJSIP_SIMPLE_ERRNO_START+23) /*270023*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Bad PIDF Message
|
||||
*/
|
||||
#define PJSIP_SIMPLE_EBADPIDF -1
|
||||
#define PJSIP_SIMPLE_EBADPIDF (PJSIP_SIMPLE_ERRNO_START+24) /*270024*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Bad XPIDF Message
|
||||
*/
|
||||
#define PJSIP_SIMPLE_EBADXPIDF -1
|
||||
#define PJSIP_SIMPLE_EBADXPIDF (PJSIP_SIMPLE_ERRNO_START+25) /*270025*/
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -76,6 +76,19 @@ enum pjsip_evsub_state
|
|||
typedef enum pjsip_evsub_state pjsip_evsub_state;
|
||||
|
||||
|
||||
/**
|
||||
* Some options for the event subscription.
|
||||
*/
|
||||
enum
|
||||
{
|
||||
/**
|
||||
* If this flag is set, then outgoing request to create subscription
|
||||
* will not have id in the Event header (e.g. in REFER request). But if
|
||||
* there is an id in the incoming NOTIFY, that id will be used.
|
||||
*/
|
||||
PJSIP_EVSUB_NO_EVENT_ID = 1,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes callback that is registered by application or
|
||||
|
@ -244,6 +257,7 @@ PJ_DECL(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
|
|||
* @param dlg The underlying dialog to use.
|
||||
* @param user_cb Callback to receive event subscription notifications.
|
||||
* @param event Event name.
|
||||
* @param option Bitmask of options.
|
||||
* @param p_evsub Pointer to receive event subscription instance.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
|
@ -251,6 +265,7 @@ PJ_DECL(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
|
|||
PJ_DECL(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
const pj_str_t *event,
|
||||
unsigned option,
|
||||
pjsip_evsub **p_evsub);
|
||||
|
||||
/**
|
||||
|
@ -260,6 +275,7 @@ PJ_DECL(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
|
|||
* @param user_cb Callback to receive event subscription notifications.
|
||||
* @param rdata The incoming request that creates the event
|
||||
* subscription, such as SUBSCRIBE or REFER.
|
||||
* @param option Bitmask of options.
|
||||
* @param p_evsub Pointer to receive event subscription instance.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
|
@ -267,6 +283,7 @@ PJ_DECL(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
|
|||
PJ_DECL(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_rx_data *rdata,
|
||||
unsigned option,
|
||||
pjsip_evsub **p_evsub);
|
||||
|
||||
|
||||
|
@ -366,7 +383,10 @@ PJ_DECL(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub,
|
|||
|
||||
|
||||
/**
|
||||
* Send request message.
|
||||
* Send request message that was previously created with initiate(), notify(),
|
||||
* or current_notify(). Application may also send request created with other
|
||||
* functions, e.g. authentication. But the request MUST be either request
|
||||
* that creates/refresh subscription or NOTIFY request.
|
||||
*
|
||||
* @param sub The event subscription object.
|
||||
* @param tdata Request message to be send.
|
||||
|
|
|
@ -202,7 +202,10 @@ PJ_DECL(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub,
|
|||
|
||||
|
||||
/**
|
||||
* Send request.
|
||||
* Send request message that was previously created with initiate(), notify(),
|
||||
* or current_notify(). Application may also send request created with other
|
||||
* functions, e.g. authentication. But the request MUST be either request
|
||||
* that creates/refresh subscription or NOTIFY request.
|
||||
*
|
||||
* @param sub The subscription object.
|
||||
* @param tdata Request message to be sent.
|
||||
|
|
|
@ -97,16 +97,13 @@ struct pjsip_inv_callback
|
|||
/**
|
||||
* 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.
|
||||
* in "offer".
|
||||
*
|
||||
* @param inv The invite session.
|
||||
* @param offer Remote offer.
|
||||
*/
|
||||
void (*on_rx_offer)(pjsip_inv_session *inv);
|
||||
void (*on_rx_offer)(pjsip_inv_session *inv,
|
||||
const pjmedia_sdp_session *offer);
|
||||
|
||||
/**
|
||||
* This callback is called after SDP offer/answer session has completed.
|
||||
|
@ -121,7 +118,6 @@ struct pjsip_inv_callback
|
|||
*/
|
||||
void (*on_media_update)(pjsip_inv_session *inv_ses,
|
||||
pj_status_t status);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -387,23 +383,20 @@ PJ_DECL(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
|
|||
pjsip_tx_data **p_tdata );
|
||||
|
||||
|
||||
#if 0
|
||||
If we implement this, we need to send SDP answer automatically on
|
||||
subsequent request/response. Something like "has_answer" flag.
|
||||
|
||||
/**
|
||||
* Set local answer to respond to remote SDP offer. By calling this function,
|
||||
* application can start SDP negotiation immediately without having to
|
||||
* send any request or response message. This function is normally called
|
||||
* when on_rx_offer() callback is called.
|
||||
* Set local answer to respond to remote SDP offer, to be carried by
|
||||
* subsequent response (or request).
|
||||
*
|
||||
* @param inv The invite session.
|
||||
* @param sdp The SDP description which will be set as answer
|
||||
* to remote.
|
||||
*
|
||||
* @return PJ_SUCCESS if local answer can be accepted by
|
||||
* SDP negotiator.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv,
|
||||
const pjmedia_sdp_session *sdp );
|
||||
#endif
|
||||
PJ_DECL(pj_status_t) pjsip_inv_set_sdp_answer(pjsip_inv_session *inv,
|
||||
const pjmedia_sdp_session *sdp );
|
||||
|
||||
|
||||
/**
|
||||
* Create a SIP message to initiate invite session termination. Depending on
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/* $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 __PJSIP_XFER_H__
|
||||
#define __PJSIP_XFER_H__
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @file sip_xfer.h
|
||||
* @brief SIP Transfer (REFER, RFC 3515)
|
||||
*/
|
||||
#include <pjsip-simple/evsub.h>
|
||||
#include <pjsip/sip_msg.h>
|
||||
|
||||
|
||||
|
||||
PJ_BEGIN_DECL
|
||||
|
||||
|
||||
/**
|
||||
* Declaration for REFER method constant.
|
||||
*/
|
||||
extern const pjsip_method pjsip_refer_method;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the REFER subsystem.
|
||||
* This currently does very little (only register REFER as supported method).
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create transferer (sender of REFER request).
|
||||
*
|
||||
* @param dlg The underlying dialog to use.
|
||||
* @param user_cb Pointer to callbacks to receive presence subscription
|
||||
* events.
|
||||
* @param p_evsub Pointer to receive the presence subscription
|
||||
* session.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_evsub **p_evsub );
|
||||
|
||||
|
||||
/**
|
||||
* Create transferee (receiver of REFER request).
|
||||
*
|
||||
* @param dlg The underlying dialog to use.
|
||||
* @param user_cb Pointer to callbacks to receive presence subscription
|
||||
* events.
|
||||
* @param rdata The incoming SUBSCRIBE request that creates the event
|
||||
* subscription.
|
||||
* @param p_evsub Pointer to receive the presence subscription
|
||||
* session.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_rx_data *rdata,
|
||||
pjsip_evsub **p_evsub );
|
||||
|
||||
/**
|
||||
* Call this function to create request to initiate REFER subscription,
|
||||
* to refresh subscription, or to unsubscribe. For request other than
|
||||
* the initial REFER request, "refer_to_uri" argument may be NULL.
|
||||
*
|
||||
* @param sub Client subscription instance.
|
||||
* @param refer_to_uri URI to be put to the Refer-To header. This argument
|
||||
* may be NULL for subsequent REFER requests.
|
||||
* @param p_tdata Pointer to receive the request.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
|
||||
const pj_str_t *refer_to_uri,
|
||||
pjsip_tx_data **p_tdata);
|
||||
|
||||
|
||||
/**
|
||||
* Accept the incoming REFER request by sending 2xx response.
|
||||
*
|
||||
* @param sub Server subscription instance.
|
||||
* @param rdata The incoming subscription request message.
|
||||
* @param st_code Status code, which MUST be 2xx.
|
||||
* @param hdr_list Optional list of headers to be added in the response.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int st_code,
|
||||
const pjsip_hdr *hdr_list );
|
||||
|
||||
|
||||
/**
|
||||
* For notifier, create NOTIFY request to subscriber, and set the state
|
||||
* of the subscription.
|
||||
*
|
||||
* @param sub The server subscription (notifier) instance.
|
||||
* @param state New state to set.
|
||||
* @param xfer_st_code The call status code to be reported with the NOTIFY
|
||||
* request.
|
||||
* @param xfer_st_text Optional call status text to be reported with the
|
||||
* NOTIFY request. If the value is NULL, default
|
||||
* status text will be used.
|
||||
* @param p_tdata Pointer to receive the request.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub,
|
||||
pjsip_evsub_state state,
|
||||
int xfer_st_code,
|
||||
const pj_str_t *xfer_st_text,
|
||||
pjsip_tx_data **p_tdata);
|
||||
|
||||
|
||||
/**
|
||||
* Create NOTIFY request to reflect current subscription status. Application
|
||||
* can only call this function after it has sent NOTIFY before.
|
||||
* This will also re-send the last "message/sipfrag" body that was sent
|
||||
* in the previous NOTIFY.
|
||||
*
|
||||
* @param sub Server subscription object.
|
||||
* @param p_tdata Pointer to receive request.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
|
||||
pjsip_tx_data **p_tdata );
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send request message that was previously created with initiate(), notify(),
|
||||
* or current_notify(). Application may also send request created with other
|
||||
* functions, e.g. authentication. But the request MUST be either request
|
||||
* that creates/refresh subscription or NOTIFY request.
|
||||
*
|
||||
*
|
||||
* @param sub The event subscription object.
|
||||
* @param tdata Request message to be send.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
|
||||
pjsip_tx_data *tdata);
|
||||
|
||||
|
||||
|
||||
PJ_END_DECL
|
||||
|
||||
#endif /* __PJSIP_XFER_H__ */
|
||||
|
|
@ -41,6 +41,7 @@ PJ_BEGIN_DECL
|
|||
typedef struct pjsip_dlg_party
|
||||
{
|
||||
pjsip_fromto_hdr *info; /**< From/To header, inc tag. */
|
||||
pj_str_t info_str; /**< String rep of info header. */
|
||||
pj_uint32_t tag_hval; /**< Hashed value of the tag. */
|
||||
pjsip_contact_hdr *contact; /**< Contact header. */
|
||||
pj_int32_t first_cseq;/**< First CSeq seen. */
|
||||
|
@ -76,8 +77,9 @@ struct pjsip_dialog
|
|||
void *dlg_set;
|
||||
|
||||
/* Dialog's session properties. */
|
||||
enum pjsip_dialog_state state; /**< Dialog state. */
|
||||
pjsip_dialog_state state; /**< Dialog state. */
|
||||
pjsip_uri *target; /**< Current target. */
|
||||
pjsip_hdr inv_hdr; /**< Headers from hparam in dest URL */
|
||||
pjsip_dlg_party local; /**< Local party info. */
|
||||
pjsip_dlg_party remote; /**< Remote party info. */
|
||||
pjsip_role_e role; /**< Initial role. */
|
||||
|
|
|
@ -171,6 +171,11 @@ PJ_BEGIN_DECL
|
|||
* Missing message body.
|
||||
*/
|
||||
#define PJSIP_EMISSINGBODY (PJSIP_ERRNO_START_PJSIP + 54) /* 171054 */
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Invalid/unexpected method.
|
||||
*/
|
||||
#define PJSIP_EINVALIDMETHOD (PJSIP_ERRNO_START_PJSIP + 55) /* 171055 */
|
||||
|
||||
|
||||
/************************************************************
|
||||
|
@ -347,6 +352,17 @@ PJ_BEGIN_DECL
|
|||
* Missing From/To tag.
|
||||
*/
|
||||
#define PJSIP_EMISSINGTAG (PJSIP_ERRNO_START_PJSIP+120) /* 171120 */
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Expecting REFER method
|
||||
*/
|
||||
#define PJSIP_ENOTREFER (PJSIP_ERRNO_START_PJSIP+121) /* 171121 */
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Not associated with REFER subscription
|
||||
*/
|
||||
#define PJSIP_ENOREFERSESSION (PJSIP_ERRNO_START_PJSIP+122) /* 171122 */
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -583,6 +583,22 @@ struct pjsip_msg_body
|
|||
PJ_DECL(int) pjsip_print_text_body( pjsip_msg_body *msg_body,
|
||||
char *buf, pj_size_t size);
|
||||
|
||||
/**
|
||||
* General purpose function to clone textual data in a SIP body. Attach this
|
||||
* function as "clone_data" member of the SIP body only if the data type
|
||||
* is a text (i.e. C string, not pj_str_t), and the length indicates the
|
||||
* length of the text.
|
||||
*
|
||||
* @param pool Pool used to clone the data.
|
||||
* @param data Textual data.
|
||||
* @param len The length of the string.
|
||||
*
|
||||
* @return New text duplicated from the original text.
|
||||
*/
|
||||
PJ_DECL(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data,
|
||||
unsigned len);
|
||||
|
||||
|
||||
/**
|
||||
* Clone the message body in src_body to the dst_body. This will duplicate
|
||||
* the contents of the message body using the \a clone_data member of the
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include <pjsip-ua/sip_inv.h>
|
||||
#include <pjsip-ua/sip_regc.h>
|
||||
#include <pjsip-ua/sip_xfer.h>
|
||||
|
||||
|
||||
#endif /* __PJSIP_UA_H__ */
|
||||
|
|
|
@ -17,4 +17,83 @@
|
|||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjsip-simple/errno.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
/* PJSIP-SIMPLE's own error codes/messages
|
||||
* MUST KEEP THIS ARRAY SORTED!!
|
||||
* Message must be limited to 64 chars!
|
||||
*/
|
||||
static const struct
|
||||
{
|
||||
int code;
|
||||
const char *msg;
|
||||
} err_str[] =
|
||||
{
|
||||
{ PJSIP_SIMPLE_ENOPKG, "No SIP event package with the specified name" },
|
||||
{ PJSIP_SIMPLE_EPKGEXISTS, "SIP event package already exist" },
|
||||
|
||||
{ PJSIP_SIMPLE_ENOTSUBSCRIBE, "Expecting SUBSCRIBE request" },
|
||||
{ PJSIP_SIMPLE_ENOPRESENCE, "No presence associated with the subscription" },
|
||||
{ PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" },
|
||||
{ PJSIP_SIMPLE_EBADCONTENT, "Bad Content-Type for presence" },
|
||||
{ PJSIP_SIMPLE_EBADPIDF, "Bad PIDF content for presence" },
|
||||
{ PJSIP_SIMPLE_EBADXPIDF, "Bad XPIDF content for presence" },
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* pjsipsimple_strerror()
|
||||
*/
|
||||
PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
|
||||
char *buf, pj_size_t bufsize )
|
||||
{
|
||||
pj_str_t errstr;
|
||||
|
||||
if (statcode >= PJSIP_SIMPLE_ERRNO_START &&
|
||||
statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
|
||||
{
|
||||
/* Find the error in the table.
|
||||
* Use binary search!
|
||||
*/
|
||||
int first = 0;
|
||||
int n = PJ_ARRAY_SIZE(err_str);
|
||||
|
||||
while (n > 0) {
|
||||
int half = n/2;
|
||||
int mid = first + half;
|
||||
|
||||
if (err_str[mid].code < statcode) {
|
||||
first = mid+1;
|
||||
n -= (half+1);
|
||||
} else if (err_str[mid].code > statcode) {
|
||||
n = half;
|
||||
} else {
|
||||
first = mid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
|
||||
pj_str_t msg;
|
||||
|
||||
msg.ptr = (char*)err_str[first].msg;
|
||||
msg.slen = pj_ansi_strlen(err_str[first].msg);
|
||||
|
||||
errstr.ptr = buf;
|
||||
pj_strncpy_with_null(&errstr, &msg, bufsize);
|
||||
return errstr;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Error not found. */
|
||||
errstr.ptr = buf;
|
||||
errstr.slen = pj_snprintf(buf, bufsize,
|
||||
"Unknown error %d",
|
||||
statcode);
|
||||
|
||||
return errstr;
|
||||
}
|
||||
|
||||
|
|
|
@ -197,6 +197,7 @@ struct pjsip_evsub
|
|||
pjsip_endpoint *endpt; /**< Endpoint instance. */
|
||||
pjsip_dialog *dlg; /**< Underlying dialog. */
|
||||
struct evpkg *pkg; /**< The event package. */
|
||||
unsigned option; /**< Options. */
|
||||
pjsip_evsub_user user; /**< Callback. */
|
||||
pjsip_role_e role; /**< UAC=subscriber, UAS=notifier */
|
||||
pjsip_evsub_state state; /**< Subscription state. */
|
||||
|
@ -235,6 +236,7 @@ static const pj_str_t STR_ACTIVE = { "active", 6 };
|
|||
static const pj_str_t STR_PENDING = { "pending", 7 };
|
||||
static const pj_str_t STR_TIMEOUT = { "timeout", 7};
|
||||
|
||||
|
||||
/*
|
||||
* On unload module.
|
||||
*/
|
||||
|
@ -246,6 +248,12 @@ static pj_status_t mod_evsub_unload(void)
|
|||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/* Proto for pjsipsimple_strerror().
|
||||
* Defined in errno.c
|
||||
*/
|
||||
PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
|
||||
char *buf, pj_size_t bufsize );
|
||||
|
||||
/*
|
||||
* Init and register module.
|
||||
*/
|
||||
|
@ -257,6 +265,9 @@ PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt)
|
|||
{ "NOTIFY", 6}
|
||||
};
|
||||
|
||||
pj_register_strerror(PJSIP_SIMPLE_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
|
||||
&pjsipsimple_strerror);
|
||||
|
||||
PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP);
|
||||
|
||||
|
@ -618,6 +629,7 @@ static pj_status_t evsub_create( pjsip_dialog *dlg,
|
|||
pjsip_role_e role,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
const pj_str_t *event,
|
||||
unsigned option,
|
||||
pjsip_evsub **p_evsub )
|
||||
{
|
||||
pjsip_evsub *sub;
|
||||
|
@ -640,6 +652,7 @@ static pj_status_t evsub_create( pjsip_dialog *dlg,
|
|||
sub->dlg = dlg;
|
||||
sub->pkg = pkg;
|
||||
sub->role = role;
|
||||
sub->option = option;
|
||||
sub->state = PJSIP_EVSUB_STATE_NULL;
|
||||
sub->state_str = evsub_state_names[sub->state];
|
||||
sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires);
|
||||
|
@ -697,6 +710,7 @@ static pj_status_t evsub_create( pjsip_dialog *dlg,
|
|||
PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
const pj_str_t *event,
|
||||
unsigned option,
|
||||
pjsip_evsub **p_evsub)
|
||||
{
|
||||
pjsip_evsub *sub;
|
||||
|
@ -705,12 +719,16 @@ PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
|
|||
PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL);
|
||||
|
||||
pjsip_dlg_inc_lock(dlg);
|
||||
status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, &sub);
|
||||
status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Add unique Id to Event header */
|
||||
pj_create_unique_string(sub->pool, &sub->event->id_param);
|
||||
/* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID
|
||||
* is not specified.
|
||||
*/
|
||||
if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) {
|
||||
pj_create_unique_string(sub->pool, &sub->event->id_param);
|
||||
}
|
||||
|
||||
/* Increment dlg session. */
|
||||
pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod);
|
||||
|
@ -730,6 +748,7 @@ on_return:
|
|||
PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_rx_data *rdata,
|
||||
unsigned option,
|
||||
pjsip_evsub **p_evsub)
|
||||
{
|
||||
pjsip_evsub *sub;
|
||||
|
@ -757,8 +776,9 @@ PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
|
|||
/* Package MUST implement on_rx_refresh */
|
||||
PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP);
|
||||
|
||||
/* Request MUST have "Event" header: */
|
||||
|
||||
/* Request MUST have "Event" header. We need the Event header to get
|
||||
* the package name (don't want to add more arguments in the function).
|
||||
*/
|
||||
event_hdr = (pjsip_event_hdr*)
|
||||
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
|
||||
if (event_hdr == NULL) {
|
||||
|
@ -772,7 +792,7 @@ PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
|
|||
/* Create the session: */
|
||||
|
||||
status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb,
|
||||
&event_hdr->event_type, &sub);
|
||||
&event_hdr->event_type, option, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
|
@ -1147,6 +1167,7 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
switch (event->body.tsx_state.type) {
|
||||
case PJSIP_EVENT_RX_MSG:
|
||||
msg = event->body.tsx_state.src.rdata->msg_info.msg;
|
||||
|
@ -1163,7 +1184,11 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
|
|||
}
|
||||
|
||||
if (!msg) {
|
||||
pj_assert(!"First transaction event is not TX or RX!");
|
||||
//Note:
|
||||
// this transaction can be other transaction in the dialog.
|
||||
// The assertion below probably only valid for dialog that
|
||||
// only has one event subscription usage.
|
||||
//pj_assert(!"First transaction event is not TX or RX!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1187,12 +1212,43 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
|
|||
|
||||
while (dlgsub != dlgsub_head) {
|
||||
|
||||
/* Match event type and Id */
|
||||
if (pj_strcmp(&dlgsub->sub->event->id_param, &event_hdr->id_param)==0 &&
|
||||
pj_stricmp(&dlgsub->sub->event->event_type, &event_hdr->event_type)==0)
|
||||
if (pj_stricmp(&dlgsub->sub->event->event_type,
|
||||
&event_hdr->event_type)==0)
|
||||
{
|
||||
break;
|
||||
/* Event type matched.
|
||||
* Check if event ID matched too.
|
||||
*/
|
||||
if (pj_strcmp(&dlgsub->sub->event->id_param,
|
||||
&event_hdr->id_param)==0)
|
||||
{
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
/*
|
||||
* Otherwise if it is an UAC subscription, AND
|
||||
* PJSIP_EVSUB_NO_EVENT_ID flag is set, AND
|
||||
* the session's event id is NULL, AND
|
||||
* the incoming request is NOTIFY with event ID, then
|
||||
* we consider it as a match, and update the
|
||||
* session's event id.
|
||||
*/
|
||||
else if (dlgsub->sub->role == PJSIP_ROLE_UAC &&
|
||||
(dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 &&
|
||||
dlgsub->sub->event->id_param.slen==0 &&
|
||||
!pjsip_method_cmp(&tsx->method, &pjsip_notify_method))
|
||||
{
|
||||
/* Update session's event id. */
|
||||
pj_strdup(dlgsub->sub->pool,
|
||||
&dlgsub->sub->event->id_param,
|
||||
&event_hdr->id_param);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
dlgsub = dlgsub->next;
|
||||
}
|
||||
|
||||
|
@ -1200,6 +1256,8 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
|
|||
/* This could be incoming request to create new subscription */
|
||||
PJ_LOG(4,(THIS_FILE,
|
||||
"Subscription not found for %.*s, event=%.*s;id=%.*s",
|
||||
(int)tsx->method.name.slen,
|
||||
tsx->method.name.ptr,
|
||||
(int)event_hdr->event_type.slen,
|
||||
event_hdr->event_type.ptr,
|
||||
(int)event_hdr->id_param.slen,
|
||||
|
@ -1737,7 +1795,18 @@ static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
|
|||
/* Can't authenticate. Terminate session (?) */
|
||||
set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Terminate event usage if we receive 481, 408, and 7 class
|
||||
* responses.
|
||||
*/
|
||||
if (sub->state != PJSIP_EVSUB_STATE_TERMINATED &&
|
||||
(tsx->status_code==481 || tsx->status_code==408 ||
|
||||
tsx->status_code/100 == 7))
|
||||
{
|
||||
set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
|
|
@ -191,13 +191,14 @@ PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg,
|
|||
pjsip_dlg_inc_lock(dlg);
|
||||
|
||||
/* Create event subscription */
|
||||
status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, &sub);
|
||||
status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, 0, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Create presence */
|
||||
pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres));
|
||||
pres->dlg = dlg;
|
||||
pres->sub = sub;
|
||||
if (user_cb)
|
||||
pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
|
||||
|
||||
|
@ -297,7 +298,7 @@ PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg,
|
|||
|
||||
|
||||
/* Create server subscription */
|
||||
status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, &sub);
|
||||
status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
|
|
|
@ -200,10 +200,7 @@ static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
|
|||
/* On receipt ACK request, when state is CONNECTING,
|
||||
* move state to CONFIRMED.
|
||||
*/
|
||||
if (method->id == PJSIP_ACK_METHOD && inv &&
|
||||
inv->state != PJSIP_INV_STATE_CONFIRMED)
|
||||
{
|
||||
pjsip_event event;
|
||||
if (method->id == PJSIP_ACK_METHOD && inv) {
|
||||
|
||||
/* Terminate INVITE transaction, if it's still present. */
|
||||
if (inv->invite_tsx &&
|
||||
|
@ -214,8 +211,12 @@ static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
|
|||
inv->invite_tsx = NULL;
|
||||
}
|
||||
|
||||
PJSIP_EVENT_INIT_RX_MSG(event, rdata);
|
||||
inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event);
|
||||
if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
|
||||
pjsip_event event;
|
||||
|
||||
PJSIP_EVENT_INIT_RX_MSG(event, rdata);
|
||||
inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event);
|
||||
}
|
||||
}
|
||||
|
||||
return PJ_FALSE;
|
||||
|
@ -250,7 +251,9 @@ static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata)
|
|||
* 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) {
|
||||
rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
|
||||
inv->invite_tsx == NULL)
|
||||
{
|
||||
|
||||
inv_send_ack(inv, rdata);
|
||||
return PJ_TRUE;
|
||||
|
@ -865,13 +868,16 @@ PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
|
|||
{
|
||||
pjsip_tx_data *tdata;
|
||||
const pjsip_hdr *hdr;
|
||||
pj_bool_t has_sdp;
|
||||
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);
|
||||
/* State MUST be NULL or CONFIRMED. */
|
||||
PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL ||
|
||||
inv->state == PJSIP_INV_STATE_CONFIRMED,
|
||||
PJ_EINVALIDOP);
|
||||
|
||||
/* Create the INVITE request. */
|
||||
status = pjsip_dlg_create_request(inv->dlg, &pjsip_invite_method, -1,
|
||||
|
@ -879,10 +885,37 @@ PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
|
|||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
/* If this is the first INVITE, then copy the headers from inv_hdr.
|
||||
* These are the headers parsed from the request URI when the
|
||||
* dialog was created.
|
||||
*/
|
||||
if (inv->state == PJSIP_INV_STATE_NULL) {
|
||||
hdr = inv->dlg->inv_hdr.next;
|
||||
|
||||
while (hdr != &inv->dlg->inv_hdr) {
|
||||
pjsip_msg_add_hdr(tdata->msg,
|
||||
pjsip_hdr_shallow_clone(tdata->pool, hdr));
|
||||
hdr = hdr->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* See if we have SDP to send. */
|
||||
if (inv->neg) {
|
||||
pjmedia_sdp_neg_state neg_state;
|
||||
|
||||
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
|
||||
|
||||
has_sdp = (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
|
||||
(neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
|
||||
pjmedia_sdp_neg_has_local_answer(inv->neg)));
|
||||
|
||||
|
||||
} else {
|
||||
has_sdp = PJ_FALSE;
|
||||
}
|
||||
|
||||
/* Add SDP, if any. */
|
||||
if (inv->neg &&
|
||||
pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
|
||||
{
|
||||
if (has_sdp) {
|
||||
const pjmedia_sdp_session *offer;
|
||||
|
||||
status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
|
||||
|
@ -940,9 +973,9 @@ static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv )
|
|||
/*
|
||||
* Check in incoming message for SDP offer/answer.
|
||||
*/
|
||||
static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
||||
pjsip_transaction *tsx,
|
||||
pjsip_rx_data *rdata)
|
||||
static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
||||
pjsip_transaction *tsx,
|
||||
pjsip_rx_data *rdata)
|
||||
{
|
||||
struct tsx_inv_data *tsx_inv_data;
|
||||
static const pj_str_t str_application = { "application", 11 };
|
||||
|
@ -963,21 +996,21 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
|||
*/
|
||||
|
||||
if (tsx_inv_data->sdp_done)
|
||||
return;
|
||||
return PJ_SUCCESS;
|
||||
|
||||
/* Check if SDP is present in the message. */
|
||||
|
||||
msg = rdata->msg_info.msg;
|
||||
if (msg->body == NULL) {
|
||||
/* Message doesn't have body. */
|
||||
return;
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if (pj_stricmp(&msg->body->content_type.type, &str_application) ||
|
||||
pj_stricmp(&msg->body->content_type.subtype, &str_sdp))
|
||||
{
|
||||
/* Message body is not "application/sdp" */
|
||||
return;
|
||||
return PJMEDIA_SDP_EINSDP;
|
||||
}
|
||||
|
||||
/* Parse the SDP body. */
|
||||
|
@ -989,7 +1022,7 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
|||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(4,(THIS_FILE, "Error parsing SDP in %s: %s",
|
||||
pjsip_rx_data_get_info(rdata), errmsg));
|
||||
return;
|
||||
return PJMEDIA_SDP_EINSDP;
|
||||
}
|
||||
|
||||
/* The SDP can be an offer or answer, depending on negotiator's state */
|
||||
|
@ -1015,13 +1048,16 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
|||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(4,(THIS_FILE, "Error processing SDP offer in %s: %s",
|
||||
pjsip_rx_data_get_info(rdata), errmsg));
|
||||
return;
|
||||
return PJMEDIA_SDP_EINSDP;
|
||||
}
|
||||
|
||||
/* Inform application about remote offer. */
|
||||
|
||||
if (mod_inv.cb.on_rx_offer)
|
||||
(*mod_inv.cb.on_rx_offer)(inv);
|
||||
if (mod_inv.cb.on_rx_offer) {
|
||||
|
||||
(*mod_inv.cb.on_rx_offer)(inv, sdp);
|
||||
|
||||
}
|
||||
|
||||
} else if (pjmedia_sdp_neg_get_state(inv->neg) ==
|
||||
PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
|
||||
|
@ -1041,7 +1077,7 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
|||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(4,(THIS_FILE, "Error processing SDP answer in %s: %s",
|
||||
pjsip_rx_data_get_info(rdata), errmsg));
|
||||
return;
|
||||
return PJMEDIA_SDP_EINSDP;
|
||||
}
|
||||
|
||||
/* Negotiate SDP */
|
||||
|
@ -1059,12 +1095,68 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
|
|||
pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv->neg))));
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process INVITE answer, for both initial and subsequent re-INVITE
|
||||
*/
|
||||
static pj_status_t process_answer( pjsip_inv_session *inv,
|
||||
int st_code,
|
||||
pjsip_tx_data *tdata )
|
||||
{
|
||||
pj_status_t status;
|
||||
pjmedia_sdp_session *sdp = NULL;
|
||||
|
||||
/* Include SDP for 18x and 2xx response.
|
||||
* Also if SDP negotiator is ready, start negotiation.
|
||||
*/
|
||||
if (st_code/10 == 18 || st_code/10 == 20) {
|
||||
|
||||
pjmedia_sdp_neg_state neg_state;
|
||||
|
||||
neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
|
||||
PJMEDIA_SDP_NEG_STATE_NULL;
|
||||
|
||||
if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
|
||||
|
||||
status = pjmedia_sdp_neg_get_neg_local(inv->neg, &sdp);
|
||||
|
||||
} else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
|
||||
pjmedia_sdp_neg_has_local_answer(inv->neg) )
|
||||
{
|
||||
|
||||
status = inv_negotiate_sdp(inv);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Include SDP when it's available.
|
||||
* Subsequent response will include this SDP.
|
||||
*/
|
||||
if (sdp) {
|
||||
tdata->msg->body = create_sdp_body(tdata->pool, sdp);
|
||||
}
|
||||
|
||||
/* Remove message body if this is a non-2xx final response */
|
||||
if (st_code >= 300)
|
||||
tdata->msg->body = NULL;
|
||||
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Answer initial INVITE.
|
||||
* Answer initial INVITE
|
||||
* Re-INVITE will be answered automatically, and will not use this function.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
|
||||
int st_code,
|
||||
|
@ -1087,7 +1179,7 @@ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
|
|||
/* If local_sdp is specified, then we MUST NOT have answered the
|
||||
* offer before.
|
||||
*/
|
||||
if (local_sdp) {
|
||||
if (local_sdp && (st_code/100==1 || st_code/100==2)) {
|
||||
|
||||
if (inv->neg == NULL) {
|
||||
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, local_sdp,
|
||||
|
@ -1108,42 +1200,20 @@ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
|
|||
return status;
|
||||
}
|
||||
|
||||
last_res = inv->invite_tsx->last_tx;
|
||||
|
||||
|
||||
|
||||
/* Modify last response. */
|
||||
last_res = inv->invite_tsx->last_tx;
|
||||
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.
|
||||
* Also if SDP negotiator is ready, start negotiation.
|
||||
*/
|
||||
if (st_code/10 == 18 || st_code/10 == 20) {
|
||||
|
||||
pjmedia_sdp_neg_state neg_state;
|
||||
|
||||
neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
|
||||
PJMEDIA_SDP_NEG_STATE_NULL;
|
||||
|
||||
if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
|
||||
neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)
|
||||
{
|
||||
const 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);
|
||||
}
|
||||
|
||||
/* Start negotiation, if ready. */
|
||||
if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
|
||||
status = inv_negotiate_sdp(inv);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsip_tx_data_dec_ref(last_res);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Process SDP in answer */
|
||||
status = process_answer(inv, st_code, last_res);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
|
||||
*p_tdata = last_res;
|
||||
|
@ -1152,6 +1222,24 @@ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set SDP answer.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv,
|
||||
const pjmedia_sdp_session *sdp )
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL);
|
||||
|
||||
pjsip_dlg_inc_lock(inv->dlg);
|
||||
status = pjmedia_sdp_neg_set_local_answer( inv->pool, inv->neg, sdp);
|
||||
pjsip_dlg_dec_lock(inv->dlg);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* End session.
|
||||
*/
|
||||
|
@ -1205,8 +1293,9 @@ PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv,
|
|||
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);
|
||||
//status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code,
|
||||
// st_text);
|
||||
status = pjsip_inv_answer(inv, st_code, st_text, NULL, &tdata);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1247,13 +1336,82 @@ PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv,
|
|||
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_status_t status;
|
||||
pjsip_contact_hdr *contact_hdr = NULL;
|
||||
|
||||
PJ_TODO(CREATE_REINVITE_REQUEST);
|
||||
return PJ_ENOTSUP;
|
||||
/* Check arguments. */
|
||||
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
|
||||
|
||||
/* Must NOT have a pending INVITE transaction */
|
||||
PJ_ASSERT_RETURN(inv->invite_tsx==NULL, PJ_EINVALIDOP);
|
||||
|
||||
|
||||
pjsip_dlg_inc_lock(inv->dlg);
|
||||
|
||||
if (new_contact) {
|
||||
pj_str_t tmp;
|
||||
const pj_str_t STR_CONTACT = { "Contact", 7 };
|
||||
|
||||
pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact);
|
||||
contact_hdr = pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT,
|
||||
tmp.ptr, tmp.slen, NULL);
|
||||
if (!contact_hdr) {
|
||||
status = PJSIP_EINVALIDURI;
|
||||
goto on_return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (new_offer) {
|
||||
if (!inv->neg) {
|
||||
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, new_offer,
|
||||
&inv->neg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
} else switch (pjmedia_sdp_neg_get_state(inv->neg)) {
|
||||
|
||||
case PJMEDIA_SDP_NEG_STATE_NULL:
|
||||
pj_assert(!"Unexpected SDP neg state NULL");
|
||||
status = PJ_EBUG;
|
||||
goto on_return;
|
||||
|
||||
case PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER:
|
||||
PJ_LOG(4,(inv->obj_name,
|
||||
"pjsip_inv_reinvite: already have an offer, new "
|
||||
"offer is ignored"));
|
||||
break;
|
||||
|
||||
case PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER:
|
||||
status = pjmedia_sdp_neg_set_local_answer(inv->pool, inv->neg,
|
||||
new_offer);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
break;
|
||||
|
||||
case PJMEDIA_SDP_NEG_STATE_WAIT_NEGO:
|
||||
PJ_LOG(4,(inv->obj_name,
|
||||
"pjsip_inv_reinvite: SDP in WAIT_NEGO state, new "
|
||||
"offer is ignored"));
|
||||
break;
|
||||
|
||||
case PJMEDIA_SDP_NEG_STATE_DONE:
|
||||
status = pjmedia_sdp_neg_modify_local_offer(inv->pool,inv->neg,
|
||||
new_offer);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (contact_hdr)
|
||||
inv->dlg->local.contact = contact_hdr;
|
||||
|
||||
status = pjsip_inv_invite(inv, p_tdata);
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(inv->dlg);
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1908,6 +2066,124 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e)
|
|||
inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e );
|
||||
|
||||
}
|
||||
else if (tsx->method.id == PJSIP_INVITE_METHOD &&
|
||||
tsx->role == PJSIP_ROLE_UAS)
|
||||
{
|
||||
|
||||
/*
|
||||
* Handle incoming re-INVITE
|
||||
*/
|
||||
if (tsx->state == PJSIP_TSX_STATE_TRYING) {
|
||||
|
||||
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
/* Check if we have INVITE pending. */
|
||||
if (inv->invite_tsx && inv->invite_tsx!=tsx) {
|
||||
|
||||
/* Can not receive re-INVITE while another one is pending. */
|
||||
status = pjsip_dlg_create_response( inv->dlg, rdata, 500, NULL,
|
||||
&tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
status = pjsip_dlg_send_response( inv->dlg, tsx, tdata);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Save the invite transaction. */
|
||||
inv->invite_tsx = tsx;
|
||||
|
||||
/* Process SDP in incoming message. */
|
||||
status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata);
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
/* Not Acceptable */
|
||||
const pjsip_hdr *accept;
|
||||
|
||||
status = pjsip_dlg_create_response(inv->dlg, rdata,
|
||||
488, NULL, &tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
|
||||
accept = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ACCEPT,
|
||||
NULL);
|
||||
if (accept) {
|
||||
pjsip_msg_add_hdr(tdata->msg,
|
||||
pjsip_hdr_clone(tdata->pool, accept));
|
||||
}
|
||||
|
||||
status = pjsip_dlg_send_response(dlg, tsx, tdata);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create 2xx ANSWER */
|
||||
status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Process SDP in the answer */
|
||||
status = process_answer(inv, 200, tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
status = pjsip_inv_send_msg(inv, tdata, NULL);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (tsx->method.id == PJSIP_INVITE_METHOD &&
|
||||
tsx->role == PJSIP_ROLE_UAC)
|
||||
{
|
||||
/*
|
||||
* Handle outgoing re-INVITE
|
||||
*/
|
||||
if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
|
||||
tsx->status_code/100 == 2)
|
||||
{
|
||||
|
||||
/* Re-INVITE was accepted. */
|
||||
|
||||
/* Process SDP */
|
||||
inv_check_sdp_in_incoming_msg(inv, tsx,
|
||||
e->body.tsx_state.src.rdata);
|
||||
|
||||
/* Send ACK */
|
||||
inv_send_ack(inv, e->body.tsx_state.src.rdata);
|
||||
|
||||
} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
|
||||
(tsx->status_code==401 || tsx->status_code==407))
|
||||
{
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
/* Handle authentication challenge. */
|
||||
status = pjsip_auth_clt_reinit_req( &dlg->auth_sess,
|
||||
e->body.tsx_state.src.rdata,
|
||||
tsx->last_tx,
|
||||
&tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Send re-INVITE */
|
||||
status = pjsip_inv_send_msg( inv, tdata, NULL);
|
||||
|
||||
} else if (tsx->status_code==PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ||
|
||||
tsx->status_code==PJSIP_SC_REQUEST_TIMEOUT ||
|
||||
tsx->status_code >= 700)
|
||||
{
|
||||
/*
|
||||
* Handle responses that terminates dialog.
|
||||
*/
|
||||
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,614 @@
|
|||
/* $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_xfer.h>
|
||||
#include <pjsip-simple/evsub_msg.h>
|
||||
#include <pjsip/sip_dialog.h>
|
||||
#include <pjsip/sip_errno.h>
|
||||
#include <pjsip/sip_endpoint.h>
|
||||
#include <pjsip/sip_module.h>
|
||||
#include <pjsip/sip_transport.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
|
||||
/*
|
||||
* Refer module (mod-refer)
|
||||
*/
|
||||
static struct pjsip_module mod_xfer =
|
||||
{
|
||||
NULL, NULL, /* prev, next. */
|
||||
{ "mod-refer", 9 }, /* Name. */
|
||||
-1, /* Id */
|
||||
PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */
|
||||
NULL, /* User data. */
|
||||
NULL, /* load() */
|
||||
NULL, /* start() */
|
||||
NULL, /* stop() */
|
||||
NULL, /* unload() */
|
||||
NULL, /* on_rx_request() */
|
||||
NULL, /* on_rx_response() */
|
||||
NULL, /* on_tx_request. */
|
||||
NULL, /* on_tx_response() */
|
||||
NULL, /* on_tsx_state() */
|
||||
};
|
||||
|
||||
|
||||
/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in
|
||||
* sip_msg.h we can catch the error here.
|
||||
*/
|
||||
enum
|
||||
{
|
||||
PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD
|
||||
};
|
||||
|
||||
const pjsip_method pjsip_refer_method = {
|
||||
PJSIP_REFER_METHOD,
|
||||
{ "REFER", 5}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* String constants
|
||||
*/
|
||||
const pj_str_t STR_REFER = { "refer", 5 };
|
||||
const pj_str_t STR_MESSAGE = { "message", 7 };
|
||||
const pj_str_t STR_SIPFRAG = { "sipfrag", 7 };
|
||||
const pj_str_t STR_SIPFRAG_VERSION = {";version=2.0", 12 };
|
||||
|
||||
|
||||
/*
|
||||
* Transfer struct.
|
||||
*/
|
||||
struct pjsip_xfer
|
||||
{
|
||||
pjsip_evsub *sub; /**< Event subscribtion record. */
|
||||
pjsip_dialog *dlg; /**< The dialog. */
|
||||
pjsip_evsub_user user_cb; /**< The user callback. */
|
||||
pj_str_t refer_to_uri; /**< The full Refer-To URI. */
|
||||
int last_st_code; /**< st_code sent in last NOTIFY */
|
||||
pj_str_t last_st_text; /**< st_text sent in last NOTIFY */
|
||||
};
|
||||
|
||||
|
||||
typedef struct pjsip_xfer pjsip_xfer;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Forward decl for evsub callback.
|
||||
*/
|
||||
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
|
||||
static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
|
||||
pjsip_event *event);
|
||||
static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body);
|
||||
static void xfer_on_evsub_rx_notify( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body);
|
||||
static void xfer_on_evsub_client_refresh(pjsip_evsub *sub);
|
||||
static void xfer_on_evsub_server_timeout(pjsip_evsub *sub);
|
||||
|
||||
|
||||
/*
|
||||
* Event subscription callback for xference.
|
||||
*/
|
||||
static pjsip_evsub_user xfer_user =
|
||||
{
|
||||
&xfer_on_evsub_state,
|
||||
&xfer_on_evsub_tsx_state,
|
||||
&xfer_on_evsub_rx_refresh,
|
||||
&xfer_on_evsub_rx_notify,
|
||||
&xfer_on_evsub_client_refresh,
|
||||
&xfer_on_evsub_server_timeout,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the REFER subsystem.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt)
|
||||
{
|
||||
const pj_str_t accept = { "message/sipfrag;version=2.0", 27 };
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP);
|
||||
|
||||
status = pjsip_endpt_register_module(endpt, &mod_xfer);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW,
|
||||
NULL, 1, &pjsip_refer_method.name);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pjsip_evsub_register_pkg( &mod_xfer, &STR_REFER, 300, 1, &accept);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create transferer (sender of REFER request).
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_evsub **p_evsub )
|
||||
{
|
||||
pj_status_t status;
|
||||
pjsip_xfer *xfer;
|
||||
pjsip_evsub *sub;
|
||||
|
||||
PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
|
||||
|
||||
pjsip_dlg_inc_lock(dlg);
|
||||
|
||||
/* Create event subscription */
|
||||
status = pjsip_evsub_create_uac( dlg, &xfer_user, &STR_REFER,
|
||||
PJSIP_EVSUB_NO_EVENT_ID, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Create xfer session */
|
||||
xfer = pj_pool_zalloc(dlg->pool, sizeof(pjsip_xfer));
|
||||
xfer->dlg = dlg;
|
||||
xfer->sub = sub;
|
||||
if (user_cb)
|
||||
pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
|
||||
|
||||
/* Attach to evsub */
|
||||
pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
|
||||
|
||||
*p_evsub = sub;
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Create transferee (receiver of REFER request).
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_rx_data *rdata,
|
||||
pjsip_evsub **p_evsub )
|
||||
{
|
||||
pjsip_evsub *sub;
|
||||
pjsip_xfer *xfer;
|
||||
const pj_str_t STR_EVENT = {"Event", 5 };
|
||||
pjsip_event_hdr *event_hdr;
|
||||
pj_status_t status;
|
||||
|
||||
/* Check arguments */
|
||||
PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
|
||||
|
||||
/* Must be request message */
|
||||
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
|
||||
PJSIP_ENOTREQUESTMSG);
|
||||
|
||||
/* Check that request is REFER */
|
||||
PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
|
||||
&pjsip_refer_method)==0,
|
||||
PJSIP_ENOTREFER);
|
||||
|
||||
/* Lock dialog */
|
||||
pjsip_dlg_inc_lock(dlg);
|
||||
|
||||
/* The evsub framework expects an Event header in the request,
|
||||
* while a REFER request conveniently doesn't have one (pun intended!).
|
||||
* So create a dummy Event header.
|
||||
*/
|
||||
if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
|
||||
&STR_EVENT, NULL)==NULL)
|
||||
{
|
||||
event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool);
|
||||
event_hdr->event_type = STR_REFER;
|
||||
pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr);
|
||||
}
|
||||
|
||||
/* Create server subscription */
|
||||
status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata,
|
||||
PJSIP_EVSUB_NO_EVENT_ID, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Create server xfer subscription */
|
||||
xfer = pj_pool_zalloc(dlg->pool, sizeof(pjsip_xfer));
|
||||
xfer->dlg = dlg;
|
||||
xfer->sub = sub;
|
||||
if (user_cb)
|
||||
pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
|
||||
|
||||
/* Attach to evsub */
|
||||
pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
|
||||
|
||||
/* Done: */
|
||||
*p_evsub = sub;
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Call this function to create request to initiate REFER subscription.
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
|
||||
const pj_str_t *refer_to_uri,
|
||||
pjsip_tx_data **p_tdata)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
const pj_str_t refer_to = { "Refer-To", 8};
|
||||
pjsip_tx_data *tdata;
|
||||
pjsip_generic_string_hdr *hdr;
|
||||
pj_status_t status;
|
||||
|
||||
/* sub and p_tdata argument must be valid. */
|
||||
PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
|
||||
|
||||
|
||||
/* Get the xfer object. */
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
|
||||
|
||||
/* refer_to_uri argument MAY be NULL for subsequent REFER requests,
|
||||
* but it MUST be specified in the first REFER.
|
||||
*/
|
||||
PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL);
|
||||
|
||||
/* Lock dialog. */
|
||||
pjsip_dlg_inc_lock(xfer->dlg);
|
||||
|
||||
/* Create basic REFER request */
|
||||
status = pjsip_evsub_initiate(sub, &pjsip_refer_method, -1,
|
||||
&tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Save Refer-To URI. */
|
||||
if (refer_to_uri == NULL) {
|
||||
refer_to_uri = &xfer->refer_to_uri;
|
||||
} else {
|
||||
pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri);
|
||||
}
|
||||
|
||||
/* Create and add Refer-To header. */
|
||||
hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to,
|
||||
refer_to_uri);
|
||||
if (!hdr) {
|
||||
pjsip_tx_data_dec_ref(tdata);
|
||||
status = PJ_ENOMEM;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
|
||||
|
||||
|
||||
/* Done. */
|
||||
*p_tdata = tdata;
|
||||
|
||||
status = PJ_SUCCESS;
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(xfer->dlg);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Accept the incoming REFER request by sending 2xx response.
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int st_code,
|
||||
const pjsip_hdr *hdr_list )
|
||||
{
|
||||
/*
|
||||
* Don't need to add custom headers, so just call basic
|
||||
* evsub response.
|
||||
*/
|
||||
return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* For notifier, create NOTIFY request to subscriber, and set the state
|
||||
* of the subscription.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub,
|
||||
pjsip_evsub_state state,
|
||||
int xfer_st_code,
|
||||
const pj_str_t *xfer_st_text,
|
||||
pjsip_tx_data **p_tdata)
|
||||
{
|
||||
pjsip_tx_data *tdata;
|
||||
pjsip_xfer *xfer;
|
||||
const pj_str_t reason = { "noresource", 10 };
|
||||
char *body;
|
||||
int bodylen;
|
||||
pjsip_msg_body *msg_body;
|
||||
pj_status_t status;
|
||||
|
||||
|
||||
/* Check arguments. */
|
||||
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
|
||||
|
||||
/* Get the xfer object. */
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
|
||||
|
||||
|
||||
/* Lock object. */
|
||||
pjsip_dlg_inc_lock(xfer->dlg);
|
||||
|
||||
/* Create the NOTIFY request.
|
||||
* Note that reason is only used when state is TERMINATED, and
|
||||
* the defined termination reason for REFER is "noresource".
|
||||
*/
|
||||
status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
|
||||
/* Check status text */
|
||||
if (xfer_st_text==NULL || xfer_st_text->slen==0)
|
||||
xfer_st_text = pjsip_get_status_text(xfer_st_code);
|
||||
|
||||
/* Save st_code and st_text, for current_notify() */
|
||||
xfer->last_st_code = xfer_st_code;
|
||||
pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text);
|
||||
|
||||
/* Create sipfrag content. */
|
||||
body = pj_pool_alloc(tdata->pool, 128);
|
||||
bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s",
|
||||
xfer_st_code,
|
||||
(int)xfer_st_text->slen,
|
||||
xfer_st_text->ptr);
|
||||
PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128,
|
||||
{status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata);
|
||||
goto on_return; });
|
||||
|
||||
|
||||
/* Create SIP message body. */
|
||||
msg_body = pj_pool_zalloc(tdata->pool, sizeof(pjsip_msg_body));
|
||||
msg_body->content_type.type = STR_MESSAGE;
|
||||
msg_body->content_type.subtype = STR_SIPFRAG;
|
||||
msg_body->content_type.param = STR_SIPFRAG_VERSION;
|
||||
msg_body->data = body;
|
||||
msg_body->len = bodylen;
|
||||
msg_body->print_body = &pjsip_print_text_body;
|
||||
msg_body->clone_data = &pjsip_clone_text_data;
|
||||
|
||||
/* Attach sipfrag body. */
|
||||
tdata->msg->body = msg_body;
|
||||
|
||||
|
||||
/* Done. */
|
||||
*p_tdata = tdata;
|
||||
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(xfer->dlg);
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send current state and the last sipfrag body.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
|
||||
pjsip_tx_data **p_tdata )
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
pj_status_t status;
|
||||
|
||||
|
||||
/* Check arguments. */
|
||||
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
|
||||
|
||||
/* Get the xfer object. */
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
|
||||
|
||||
pjsip_dlg_inc_lock(xfer->dlg);
|
||||
|
||||
status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub),
|
||||
xfer->last_st_code, &xfer->last_st_text,
|
||||
p_tdata);
|
||||
|
||||
pjsip_dlg_dec_lock(xfer->dlg);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send request message.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
|
||||
pjsip_tx_data *tdata)
|
||||
{
|
||||
return pjsip_evsub_send_request(sub, tdata);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This callback is called by event subscription when subscription
|
||||
* state has changed.
|
||||
*/
|
||||
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_evsub_state)
|
||||
(*xfer->user_cb.on_evsub_state)(sub, event);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when transaction state has changed.
|
||||
*/
|
||||
static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
|
||||
pjsip_event *event)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_tsx_state)
|
||||
(*xfer->user_cb.on_tsx_state)(sub, tsx, event);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when REFER is received to refresh subscription.
|
||||
*/
|
||||
static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_rx_refresh) {
|
||||
(*xfer->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
|
||||
res_hdr, p_body);
|
||||
|
||||
} else {
|
||||
/* Implementors MUST send NOTIFY if it implements on_rx_refresh
|
||||
* (implementor == "us" from evsub point of view.
|
||||
*/
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
|
||||
status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
|
||||
xfer->last_st_code,
|
||||
&xfer->last_st_text,
|
||||
&tdata);
|
||||
} else {
|
||||
status = pjsip_xfer_current_notify(sub, &tdata);
|
||||
}
|
||||
|
||||
if (status == PJ_SUCCESS)
|
||||
pjsip_xfer_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called when NOTIFY is received.
|
||||
*/
|
||||
static void xfer_on_evsub_rx_notify( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_rx_notify)
|
||||
(*xfer->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
|
||||
res_hdr, p_body);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when it's time to send SUBSCRIBE.
|
||||
*/
|
||||
static void xfer_on_evsub_client_refresh(pjsip_evsub *sub)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_client_refresh) {
|
||||
(*xfer->user_cb.on_client_refresh)(sub);
|
||||
} else {
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_xfer_initiate(sub, NULL, &tdata);
|
||||
if (status == PJ_SUCCESS)
|
||||
pjsip_xfer_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called when no refresh is received after the interval.
|
||||
*/
|
||||
static void xfer_on_evsub_server_timeout(pjsip_evsub *sub)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_server_timeout) {
|
||||
(*xfer->user_cb.on_server_timeout)(sub);
|
||||
} else {
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
|
||||
xfer->last_st_code,
|
||||
&xfer->last_st_text, &tdata);
|
||||
if (status == PJ_SUCCESS)
|
||||
pjsip_xfer_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +78,8 @@ static pj_status_t create_dialog( pjsip_user_agent *ua,
|
|||
dlg->endpt = endpt;
|
||||
dlg->state = PJSIP_DIALOG_STATE_NULL;
|
||||
|
||||
pj_list_init(&dlg->inv_hdr);
|
||||
|
||||
status = pj_mutex_create_recursive(pool, "dlg%p", &dlg->mutex);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
@ -131,10 +133,31 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua,
|
|||
goto on_error;
|
||||
}
|
||||
|
||||
/* Put any header param in the target URI into INVITE header list. */
|
||||
if (PJSIP_URI_SCHEME_IS_SIP(dlg->target) ||
|
||||
PJSIP_URI_SCHEME_IS_SIPS(dlg->target))
|
||||
{
|
||||
pjsip_param *param;
|
||||
pjsip_sip_uri *uri = (pjsip_sip_uri*)pjsip_uri_get_uri(dlg->target);
|
||||
|
||||
param = uri->header_param.next;
|
||||
while (param != &uri->header_param) {
|
||||
pjsip_generic_string_hdr *req_hdr;
|
||||
|
||||
req_hdr = pjsip_generic_string_hdr_create(dlg->pool, ¶m->name,
|
||||
¶m->value);
|
||||
pj_list_push_back(&dlg->inv_hdr, req_hdr);
|
||||
|
||||
param = param->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Init local info. */
|
||||
dlg->local.info = pjsip_from_hdr_create(dlg->pool);
|
||||
pj_strdup_with_null(dlg->pool, &tmp, local_uri);
|
||||
dlg->local.info->uri = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0);
|
||||
pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri);
|
||||
dlg->local.info->uri = pjsip_parse_uri(dlg->pool,
|
||||
dlg->local.info_str.ptr,
|
||||
dlg->local.info_str.slen, 0);
|
||||
if (!dlg->local.info->uri) {
|
||||
status = PJSIP_EINVALIDURI;
|
||||
goto on_error;
|
||||
|
@ -164,8 +187,10 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua,
|
|||
|
||||
/* Init remote info. */
|
||||
dlg->remote.info = pjsip_to_hdr_create(dlg->pool);
|
||||
pj_strdup_with_null(dlg->pool, &tmp, remote_uri);
|
||||
dlg->remote.info->uri = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0);
|
||||
pj_strdup_with_null(dlg->pool, &dlg->remote.info_str, remote_uri);
|
||||
dlg->remote.info->uri = pjsip_parse_uri(dlg->pool,
|
||||
dlg->remote.info_str.ptr,
|
||||
dlg->remote.info_str.slen, 0);
|
||||
if (!dlg->remote.info->uri) {
|
||||
status = PJSIP_EINVALIDURI;
|
||||
goto on_error;
|
||||
|
@ -225,6 +250,9 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua,
|
|||
pjsip_hdr *contact_hdr;
|
||||
pjsip_rr_hdr *rr;
|
||||
pjsip_transaction *tsx = NULL;
|
||||
pj_str_t tmp;
|
||||
enum { TMP_LEN=128};
|
||||
pj_ssize_t len;
|
||||
pjsip_dialog *dlg;
|
||||
|
||||
/* Check arguments. */
|
||||
|
@ -249,6 +277,11 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua,
|
|||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
/* Temprary string for getting the string representation of
|
||||
* both local and remote URI.
|
||||
*/
|
||||
tmp.ptr = pj_pool_alloc(rdata->tp_info.pool, TMP_LEN);
|
||||
|
||||
/* Init local info from the To header. */
|
||||
dlg->local.info = pjsip_hdr_clone(dlg->pool, rdata->msg_info.to);
|
||||
pjsip_fromto_hdr_set_from(dlg->local.info);
|
||||
|
@ -256,10 +289,36 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua,
|
|||
/* Generate local tag. */
|
||||
pj_create_unique_string(dlg->pool, &dlg->local.info->tag);
|
||||
|
||||
|
||||
/* Print the local info. */
|
||||
len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
|
||||
dlg->local.info->uri, tmp.ptr, TMP_LEN);
|
||||
if (len < 1) {
|
||||
pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
|
||||
tmp.slen = pj_ansi_strlen(tmp.ptr);
|
||||
} else
|
||||
tmp.slen = len;
|
||||
|
||||
/* Save the local info. */
|
||||
pj_strdup(dlg->pool, &dlg->local.info_str, &tmp);
|
||||
|
||||
/* Calculate hash value of local tag. */
|
||||
dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr,
|
||||
dlg->local.info->tag.slen);
|
||||
|
||||
/* Print the local info. */
|
||||
len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
|
||||
dlg->local.info->uri, tmp.ptr, TMP_LEN);
|
||||
if (len < 1) {
|
||||
pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
|
||||
tmp.slen = pj_ansi_strlen(tmp.ptr);
|
||||
} else
|
||||
tmp.slen = len;
|
||||
|
||||
/* Save the local info. */
|
||||
pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp);
|
||||
|
||||
|
||||
/* Randomize local cseq */
|
||||
dlg->local.first_cseq = pj_rand() % 0x7FFFFFFFL;
|
||||
dlg->local.cseq = dlg->local.first_cseq;
|
||||
|
@ -1085,6 +1144,8 @@ PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg,
|
|||
pjsip_transaction *tsx,
|
||||
pjsip_tx_data *tdata)
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
/* Sanity check. */
|
||||
PJ_ASSERT_RETURN(dlg && tsx && tdata && tdata->msg, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
|
||||
|
@ -1107,7 +1168,14 @@ PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg,
|
|||
PJ_EINVALIDOP);
|
||||
#endif
|
||||
|
||||
return pjsip_tsx_send_msg(tsx, tdata);
|
||||
/* Must acquire dialog first, to prevent deadlock */
|
||||
pjsip_dlg_inc_lock(dlg);
|
||||
|
||||
status = pjsip_tsx_send_msg(tsx, tdata);
|
||||
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -325,6 +325,8 @@ PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt,
|
|||
htype==PJSIP_H_SUPPORTED,
|
||||
PJ_EINVAL);
|
||||
|
||||
PJ_UNUSED_ARG(mod);
|
||||
|
||||
/* Find the header. */
|
||||
hdr = (pjsip_generic_array_hdr*) pjsip_endpt_get_capability(endpt,
|
||||
htype, hname);
|
||||
|
|
|
@ -1746,6 +1746,18 @@ PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t
|
|||
return msg_body->len;
|
||||
}
|
||||
|
||||
PJ_DEF(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data,
|
||||
unsigned len)
|
||||
{
|
||||
char *newdata = "";
|
||||
|
||||
if (len) {
|
||||
newdata = pj_pool_alloc(pool, len);
|
||||
pj_memcpy(newdata, data, len);
|
||||
}
|
||||
return newdata;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pjsip_msg_body_clone( pj_pool_t *pool,
|
||||
pjsip_msg_body *dst_body,
|
||||
const pjsip_msg_body *src_body )
|
||||
|
|
|
@ -113,8 +113,8 @@ static void keystroke_help(void)
|
|||
pj_snprintf(reg_status, sizeof(reg_status),
|
||||
"%s (%.*s;expires=%d)",
|
||||
pjsip_get_status_text(pjsua.regc_last_code)->ptr,
|
||||
(int)info.server_uri.slen,
|
||||
info.server_uri.ptr,
|
||||
(int)info.client_uri.slen,
|
||||
info.client_uri.ptr,
|
||||
info.next_reg);
|
||||
|
||||
} else {
|
||||
|
@ -131,16 +131,16 @@ static void keystroke_help(void)
|
|||
puts("| Call Commands: | IM & Presence: | Misc: |");
|
||||
puts("| | | |");
|
||||
puts("| m Make new call | i Send IM | o Send OPTIONS |");
|
||||
puts("| a Answer call | s Subscribe presence | R (Re-)register |");
|
||||
puts("| h Hangup call | u Unsubscribe presence | r Unregister |");
|
||||
puts("| ] Select next dialog | t Toggle Online status | d Dump status |");
|
||||
puts("| a Answer call | s Subscribe presence | rr (Re-)register |");
|
||||
puts("| h Hangup call | u Unsubscribe presence | ru Unregister |");
|
||||
puts("| ] Select next dialog | t ToGgle Online status | d Dump status |");
|
||||
puts("| [ Select previous dialog | | |");
|
||||
puts("+-----------------------------------------------------------------------------+");
|
||||
puts("| Conference Command |");
|
||||
puts("| cl List ports |");
|
||||
puts("| cc Connect port |");
|
||||
puts("| cd Disconnect port |");
|
||||
puts("+-----------------------------------------------------------------------------+");
|
||||
puts("| +--------------------------+-------------------+");
|
||||
puts("| H Hold call | Conference Command | |");
|
||||
puts("| v re-inVite (release hold) | cl List ports | |");
|
||||
puts("| x Xfer call | cc Connect port | |");
|
||||
puts("| | cd Disconnect port | |");
|
||||
puts("+------------------------------+--------------------------+-------------------+");
|
||||
puts("| q QUIT |");
|
||||
puts("+=============================================================================+");
|
||||
printf(">>> ");
|
||||
|
@ -283,7 +283,6 @@ static void ui_console_main(void)
|
|||
{
|
||||
char menuin[10];
|
||||
char buf[128];
|
||||
pjsip_inv_session *inv;
|
||||
struct input_result result;
|
||||
|
||||
//keystroke_help();
|
||||
|
@ -305,9 +304,9 @@ static void ui_console_main(void)
|
|||
if (result.nb_result == -1)
|
||||
puts("You can't do that with make call!");
|
||||
else
|
||||
pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, &inv);
|
||||
pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, NULL);
|
||||
} else if (result.uri_result)
|
||||
pjsua_invite(result.uri_result, &inv);
|
||||
pjsua_invite(result.uri_result, NULL);
|
||||
|
||||
break;
|
||||
|
||||
|
@ -353,43 +352,100 @@ static void ui_console_main(void)
|
|||
continue;
|
||||
|
||||
} else {
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_inv_end_session(inv_session->inv,
|
||||
PJSIP_SC_DECLINE, NULL, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE,
|
||||
"Failed to create end session message",
|
||||
status);
|
||||
continue;
|
||||
}
|
||||
|
||||
status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE,
|
||||
"Failed to send end session message",
|
||||
status);
|
||||
continue;
|
||||
}
|
||||
pjsua_inv_hangup(inv_session, PJSIP_SC_DECLINE);
|
||||
}
|
||||
break;
|
||||
|
||||
case ']':
|
||||
inv_session = inv_session->next;
|
||||
if (inv_session == &pjsua.inv_list)
|
||||
inv_session = pjsua.inv_list.next;
|
||||
case '[':
|
||||
/*
|
||||
* Cycle next/prev dialog.
|
||||
*/
|
||||
if (menuin[0] == ']') {
|
||||
inv_session = inv_session->next;
|
||||
if (inv_session == &pjsua.inv_list)
|
||||
inv_session = pjsua.inv_list.next;
|
||||
|
||||
} else {
|
||||
inv_session = inv_session->prev;
|
||||
if (inv_session == &pjsua.inv_list)
|
||||
inv_session = pjsua.inv_list.prev;
|
||||
}
|
||||
|
||||
if (inv_session != &pjsua.inv_list) {
|
||||
char url[PJSIP_MAX_URL_SIZE];
|
||||
int len;
|
||||
|
||||
len = pjsip_uri_print(0, inv_session->inv->dlg->remote.info->uri,
|
||||
url, sizeof(url)-1);
|
||||
if (len < 1) {
|
||||
pj_ansi_strcpy(url, "<uri is too long>");
|
||||
} else {
|
||||
url[len] = '\0';
|
||||
}
|
||||
|
||||
PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url));
|
||||
|
||||
} else {
|
||||
PJ_LOG(3,(THIS_FILE,"No current dialog"));
|
||||
}
|
||||
break;
|
||||
|
||||
case '[':
|
||||
inv_session = inv_session->prev;
|
||||
if (inv_session == &pjsua.inv_list)
|
||||
inv_session = pjsua.inv_list.prev;
|
||||
case 'H':
|
||||
/*
|
||||
* Hold call.
|
||||
*/
|
||||
if (inv_session != &pjsua.inv_list) {
|
||||
|
||||
pjsua_inv_set_hold(inv_session);
|
||||
|
||||
} else {
|
||||
PJ_LOG(3,(THIS_FILE, "No current call"));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
/*
|
||||
* Send re-INVITE (to release hold, etc).
|
||||
*/
|
||||
if (inv_session != &pjsua.inv_list) {
|
||||
|
||||
pjsua_inv_reinvite(inv_session);
|
||||
|
||||
} else {
|
||||
PJ_LOG(3,(THIS_FILE, "No current call"));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
/*
|
||||
* Transfer call.
|
||||
*/
|
||||
if (inv_session == &pjsua.inv_list) {
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "No current call"));
|
||||
|
||||
} else {
|
||||
ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
|
||||
if (result.nb_result != NO_NB) {
|
||||
if (result.nb_result == -1)
|
||||
puts("You can't do that with transfer call!");
|
||||
else
|
||||
pjsua_inv_xfer_call( inv_session,
|
||||
pjsua.buddies[result.nb_result].uri.ptr);
|
||||
|
||||
} else if (result.uri_result) {
|
||||
pjsua_inv_xfer_call( inv_session, result.uri_result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
case 'u':
|
||||
ui_input_url("Subscribe presence of", buf, sizeof(buf), &result);
|
||||
/*
|
||||
* Subscribe/unsubscribe presence.
|
||||
*/
|
||||
ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
|
||||
if (result.nb_result != NO_NB) {
|
||||
if (result.nb_result == -1) {
|
||||
unsigned i;
|
||||
|
@ -402,19 +458,29 @@ static void ui_console_main(void)
|
|||
pjsua_pres_refresh();
|
||||
|
||||
} else if (result.uri_result) {
|
||||
puts("Sorry, can only subscribe to buddy's presence, not arbitrary URL (for now)");
|
||||
puts("Sorry, can only subscribe to buddy's presence, "
|
||||
"not arbitrary URL (for now)");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
pjsua_regc_update(PJ_TRUE);
|
||||
case 'r':
|
||||
switch (menuin[1]) {
|
||||
case 'r':
|
||||
/*
|
||||
* Re-Register.
|
||||
*/
|
||||
pjsua_regc_update(PJ_TRUE);
|
||||
break;
|
||||
case 'u':
|
||||
/*
|
||||
* Unregister
|
||||
*/
|
||||
pjsua_regc_update(PJ_FALSE);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
pjsua_regc_update(PJ_FALSE);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
pjsua.online_status = !pjsua.online_status;
|
||||
pjsua_pres_refresh();
|
||||
|
@ -430,16 +496,31 @@ static void ui_console_main(void)
|
|||
{
|
||||
char src_port[10], dst_port[10];
|
||||
pj_status_t status;
|
||||
const char *src_title, *dst_title;
|
||||
|
||||
if (!simple_input("Connect src port #:", src_port, sizeof(src_port)))
|
||||
conf_list();
|
||||
|
||||
src_title = (menuin[1]=='c'?
|
||||
"Connect src port #":
|
||||
"Disconnect src port #");
|
||||
dst_title = (menuin[1]=='c'?
|
||||
"To dst port #":
|
||||
"From dst port #");
|
||||
|
||||
if (!simple_input(src_title, src_port, sizeof(src_port)))
|
||||
break;
|
||||
if (!simple_input("To dst port #:", dst_port, sizeof(dst_port)))
|
||||
|
||||
if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
|
||||
break;
|
||||
|
||||
if (menuin[1]=='c') {
|
||||
status = pjmedia_conf_connect_port(pjsua.mconf, atoi(src_port), atoi(dst_port));
|
||||
status = pjmedia_conf_connect_port(pjsua.mconf,
|
||||
atoi(src_port),
|
||||
atoi(dst_port));
|
||||
} else {
|
||||
status = pjmedia_conf_disconnect_port(pjsua.mconf, atoi(src_port), atoi(dst_port));
|
||||
status = pjmedia_conf_disconnect_port(pjsua.mconf,
|
||||
atoi(src_port),
|
||||
atoi(dst_port));
|
||||
}
|
||||
if (status == PJ_SUCCESS) {
|
||||
puts("Success");
|
||||
|
|
|
@ -68,6 +68,8 @@ struct pjsua_inv_data
|
|||
pjmedia_session *session; /**< The media session. */
|
||||
unsigned conf_slot; /**< Slot # in conference bridge. */
|
||||
unsigned call_slot; /**< RTP media index in med_sock_use[] */
|
||||
pjsip_evsub *xfer_sub; /**< Xfer server subscription, if this
|
||||
call was triggered by xfer. */
|
||||
};
|
||||
|
||||
|
||||
|
@ -254,7 +256,7 @@ pj_status_t pjsua_destroy(void);
|
|||
* Make outgoing call.
|
||||
*/
|
||||
pj_status_t pjsua_invite(const char *cstr_dest_uri,
|
||||
pjsip_inv_session **p_inv);
|
||||
struct pjsua_inv_data **p_inv_data);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -263,6 +265,31 @@ pj_status_t pjsua_invite(const char *cstr_dest_uri,
|
|||
pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata);
|
||||
|
||||
|
||||
/**
|
||||
* Hangup call.
|
||||
*/
|
||||
void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code);
|
||||
|
||||
|
||||
/**
|
||||
* Put call on-hold.
|
||||
*/
|
||||
void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session);
|
||||
|
||||
|
||||
/**
|
||||
* Send re-INVITE (to release hold).
|
||||
*/
|
||||
void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session);
|
||||
|
||||
|
||||
/**
|
||||
* Transfer call.
|
||||
*/
|
||||
void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session,
|
||||
const char *dest);
|
||||
|
||||
|
||||
/**
|
||||
* Callback to be called by session when invite session's state has changed.
|
||||
*/
|
||||
|
@ -283,6 +310,19 @@ void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e);
|
|||
*/
|
||||
void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status);
|
||||
|
||||
/**
|
||||
* Callback called when invite session received new offer.
|
||||
*/
|
||||
void pjsua_inv_on_rx_offer( pjsip_inv_session *inv,
|
||||
const pjmedia_sdp_session *offer);
|
||||
|
||||
/**
|
||||
* Callback to receive transaction state inside invite session or dialog
|
||||
* (e.g. REFER, MESSAGE).
|
||||
*/
|
||||
void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv,
|
||||
pjsip_transaction *tsx,
|
||||
pjsip_event *e);
|
||||
|
||||
/**
|
||||
* Terminate all calls.
|
||||
|
|
|
@ -141,7 +141,7 @@ static pj_status_t init_sockets(pj_bool_t sip,
|
|||
enum {
|
||||
RTP_START_PORT = 4000,
|
||||
RTP_RANDOM_START = 2,
|
||||
RTP_RETRY = 10
|
||||
RTP_RETRY = 20
|
||||
};
|
||||
enum {
|
||||
SIP_SOCK,
|
||||
|
@ -392,6 +392,9 @@ static pj_status_t init_stack(void)
|
|||
inv_cb.on_state_changed = &pjsua_inv_on_state_changed;
|
||||
inv_cb.on_new_session = &pjsua_inv_on_new_session;
|
||||
inv_cb.on_media_update = &pjsua_inv_on_media_update;
|
||||
inv_cb.on_rx_offer = &pjsua_inv_on_rx_offer;
|
||||
inv_cb.on_tsx_state_changed = &pjsua_inv_on_tsx_state_changed;
|
||||
|
||||
|
||||
/* Initialize invite session module: */
|
||||
status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb);
|
||||
|
@ -479,6 +482,9 @@ pj_status_t pjsua_init(void)
|
|||
|
||||
pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
|
||||
|
||||
/* Init xfer/REFER module */
|
||||
|
||||
pjsip_xfer_init_module( pjsua.endpt );
|
||||
|
||||
/* Init pjsua presence handler: */
|
||||
|
||||
|
@ -751,6 +757,10 @@ pj_status_t pjsua_destroy(void)
|
|||
PJ_LOG(4,(THIS_FILE, "Shutting down..."));
|
||||
busy_sleep(1000);
|
||||
|
||||
/* Destroy conference bridge. */
|
||||
if (pjsua.mconf)
|
||||
pjmedia_conf_destroy(pjsua.mconf);
|
||||
|
||||
/* Shutdown pjmedia-codec: */
|
||||
pjmedia_codec_deinit();
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
* Make outgoing call.
|
||||
*/
|
||||
pj_status_t pjsua_invite(const char *cstr_dest_uri,
|
||||
pjsip_inv_session **p_inv)
|
||||
struct pjsua_inv_data **p_inv_data)
|
||||
{
|
||||
pj_str_t dest_uri;
|
||||
pjsip_dialog *dlg;
|
||||
|
@ -136,8 +136,8 @@ pj_status_t pjsua_invite(const char *cstr_dest_uri,
|
|||
|
||||
|
||||
/* Done. */
|
||||
|
||||
*p_inv = inv;
|
||||
if (p_inv_data)
|
||||
*p_inv_data = inv_data;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
|
||||
|
@ -158,127 +158,145 @@ pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata)
|
|||
pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
|
||||
pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
|
||||
pjsip_msg *msg = rdata->msg_info.msg;
|
||||
pjsip_tx_data *response = NULL;
|
||||
unsigned options = 0;
|
||||
pjsip_inv_session *inv;
|
||||
struct pjsua_inv_data *inv_data;
|
||||
pjmedia_sdp_session *answer;
|
||||
int med_sk_index;
|
||||
pj_status_t status;
|
||||
|
||||
/*
|
||||
* Handle incoming INVITE outside dialog.
|
||||
/* Don't want to handle anything but INVITE */
|
||||
if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
|
||||
return PJ_FALSE;
|
||||
|
||||
/* Don't want to handle anything that's already associated with
|
||||
* existing dialog or transaction.
|
||||
*/
|
||||
if (dlg == NULL && tsx == NULL &&
|
||||
msg->line.req.method.id == PJSIP_INVITE_METHOD)
|
||||
{
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *response = NULL;
|
||||
unsigned options = 0;
|
||||
if (dlg || tsx)
|
||||
return PJ_FALSE;
|
||||
|
||||
/* Verify that we can handle the request. */
|
||||
status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
|
||||
pjsua.endpt, &response);
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
/*
|
||||
* No we can't handle the incoming INVITE request.
|
||||
*/
|
||||
/* Verify that we can handle the request. */
|
||||
status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
|
||||
pjsua.endpt, &response);
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
if (response) {
|
||||
pjsip_response_addr res_addr;
|
||||
/*
|
||||
* No we can't handle the incoming INVITE request.
|
||||
*/
|
||||
|
||||
pjsip_get_response_addr(response->pool, rdata, &res_addr);
|
||||
pjsip_endpt_send_response(pjsua.endpt, &res_addr, response,
|
||||
NULL, NULL);
|
||||
if (response) {
|
||||
pjsip_response_addr res_addr;
|
||||
|
||||
} else {
|
||||
|
||||
/* Respond with 500 (Internal Server Error) */
|
||||
pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
|
||||
NULL, NULL);
|
||||
}
|
||||
pjsip_get_response_addr(response->pool, rdata, &res_addr);
|
||||
pjsip_endpt_send_response(pjsua.endpt, &res_addr, response,
|
||||
NULL, NULL);
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Yes we can handle the incoming INVITE request.
|
||||
*/
|
||||
pjsip_inv_session *inv;
|
||||
struct pjsua_inv_data *inv_data;
|
||||
pjmedia_sdp_session *answer;
|
||||
int med_sk_index;
|
||||
|
||||
|
||||
/* Find free socket. */
|
||||
for (med_sk_index=0; med_sk_index<PJSUA_MAX_CALLS; ++med_sk_index) {
|
||||
if (!pjsua.med_sock_use[med_sk_index])
|
||||
break;
|
||||
}
|
||||
|
||||
if (med_sk_index == PJSUA_MAX_CALLS) {
|
||||
PJ_LOG(3,(THIS_FILE, "Error: too many calls!"));
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
pjsua.med_sock_use[med_sk_index] = 1;
|
||||
|
||||
/* Get media capability from media endpoint: */
|
||||
|
||||
status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool,
|
||||
1, &pjsua.med_sock_info[med_sk_index],
|
||||
&answer );
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
|
||||
NULL, NULL);
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
/* Create dialog: */
|
||||
|
||||
status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
|
||||
&pjsua.contact_uri, &dlg);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* Create invite session: */
|
||||
|
||||
status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv);
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
status = pjsip_dlg_create_response( dlg, rdata, 500, NULL,
|
||||
&response);
|
||||
if (status == PJ_SUCCESS)
|
||||
status = pjsip_dlg_send_response(dlg,
|
||||
pjsip_rdata_get_tsx(rdata),
|
||||
response);
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
return PJ_TRUE;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Create and attach pjsua data to the dialog: */
|
||||
|
||||
inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data));
|
||||
inv_data->inv = inv;
|
||||
inv_data->call_slot = inv_data->call_slot = med_sk_index;
|
||||
dlg->mod_data[pjsua.mod.id] = inv_data;
|
||||
inv->mod_data[pjsua.mod.id] = inv_data;
|
||||
|
||||
pj_list_push_back(&pjsua.inv_list, inv_data);
|
||||
|
||||
|
||||
/* Answer with 100 (using the dialog, not invite): */
|
||||
|
||||
status = pjsip_dlg_create_response(dlg, rdata, 100, NULL, &response);
|
||||
if (status == PJ_SUCCESS)
|
||||
status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), response);
|
||||
/* Respond with 500 (Internal Server Error) */
|
||||
pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
/* This INVITE request has been handled. */
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Yes we can handle the incoming INVITE request.
|
||||
*/
|
||||
|
||||
/* Find free call slot. */
|
||||
for (med_sk_index=0; med_sk_index<PJSUA_MAX_CALLS; ++med_sk_index) {
|
||||
if (!pjsua.med_sock_use[med_sk_index])
|
||||
break;
|
||||
}
|
||||
|
||||
if (med_sk_index == PJSUA_MAX_CALLS) {
|
||||
pjsip_endpt_respond_stateless(pjsua.endpt, rdata,
|
||||
PJSIP_SC_BUSY_HERE, NULL,
|
||||
NULL, NULL);
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
return PJ_FALSE;
|
||||
|
||||
pjsua.med_sock_use[med_sk_index] = 1;
|
||||
|
||||
/* Get media capability from media endpoint: */
|
||||
|
||||
status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool,
|
||||
1, &pjsua.med_sock_info[med_sk_index],
|
||||
&answer );
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
|
||||
NULL, NULL);
|
||||
|
||||
/* Free call socket. */
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
/* Create dialog: */
|
||||
|
||||
status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
|
||||
&pjsua.contact_uri, &dlg);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
|
||||
NULL, NULL);
|
||||
|
||||
/* Free call socket. */
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* Create invite session: */
|
||||
|
||||
status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv);
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
pjsip_dlg_respond(dlg, rdata, 500, NULL);
|
||||
|
||||
/* Free call socket. */
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
|
||||
// TODO: Need to delete dialog
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* Create and attach pjsua data to the dialog: */
|
||||
|
||||
inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data));
|
||||
inv_data->inv = inv;
|
||||
inv_data->call_slot = inv_data->call_slot = med_sk_index;
|
||||
dlg->mod_data[pjsua.mod.id] = inv_data;
|
||||
inv->mod_data[pjsua.mod.id] = inv_data;
|
||||
|
||||
pj_list_push_back(&pjsua.inv_list, inv_data);
|
||||
|
||||
|
||||
/* Answer with 100 (using the dialog, not invite): */
|
||||
|
||||
status = pjsip_dlg_create_response(dlg, rdata, 100, NULL, &response);
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
pjsip_dlg_respond(dlg, rdata, 500, NULL);
|
||||
|
||||
/* Free call socket. */
|
||||
pjsua.med_sock_use[med_sk_index] = 0;
|
||||
|
||||
// TODO: Need to delete dialog
|
||||
|
||||
} else {
|
||||
status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
|
||||
response);
|
||||
}
|
||||
|
||||
/* This INVITE request has been handled. */
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
@ -288,12 +306,66 @@ pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata)
|
|||
*/
|
||||
void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
|
||||
{
|
||||
struct pjsua_inv_data *inv_data;
|
||||
|
||||
inv_data = inv->dlg->mod_data[pjsua.mod.id];
|
||||
|
||||
/* If this is an outgoing INVITE that was created because of
|
||||
* REFER/transfer, send NOTIFY to transferer.
|
||||
*/
|
||||
if (inv_data && inv_data->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE)
|
||||
{
|
||||
int st_code = -1;
|
||||
pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
|
||||
|
||||
|
||||
switch (inv->state) {
|
||||
case PJSIP_INV_STATE_NULL:
|
||||
case PJSIP_INV_STATE_CALLING:
|
||||
/* Do nothing */
|
||||
break;
|
||||
|
||||
case PJSIP_INV_STATE_EARLY:
|
||||
case PJSIP_INV_STATE_CONNECTING:
|
||||
st_code = e->body.tsx_state.tsx->status_code;
|
||||
ev_state = PJSIP_EVSUB_STATE_ACTIVE;
|
||||
break;
|
||||
|
||||
case PJSIP_INV_STATE_CONFIRMED:
|
||||
/* When state is confirmed, send the final 200/OK and terminate
|
||||
* subscription.
|
||||
*/
|
||||
st_code = e->body.tsx_state.tsx->status_code;
|
||||
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
|
||||
break;
|
||||
|
||||
case PJSIP_INV_STATE_DISCONNECTED:
|
||||
st_code = e->body.tsx_state.tsx->status_code;
|
||||
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (st_code != -1) {
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
status = pjsip_xfer_notify( inv_data->xfer_sub,
|
||||
ev_state, st_code,
|
||||
NULL, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
|
||||
} else {
|
||||
status = pjsip_xfer_send_request(inv_data->xfer_sub, tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Destroy media session when invite session is disconnected. */
|
||||
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
|
||||
struct pjsua_inv_data *inv_data;
|
||||
|
||||
inv_data = inv->dlg->mod_data[pjsua.mod.id];
|
||||
|
||||
pj_assert(inv_data != NULL);
|
||||
|
||||
|
@ -317,6 +389,158 @@ void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback called by event framework when the xfer subscription state
|
||||
* has changed.
|
||||
*/
|
||||
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
|
||||
{
|
||||
|
||||
PJ_UNUSED_ARG(event);
|
||||
|
||||
/*
|
||||
* We're only interested when subscription is terminated, to
|
||||
* clear the xfer_sub member of the inv_data.
|
||||
*/
|
||||
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
|
||||
struct pjsua_inv_data *inv_data;
|
||||
|
||||
inv_data = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
|
||||
if (!inv_data)
|
||||
return;
|
||||
|
||||
pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
|
||||
inv_data->xfer_sub = NULL;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Follow transfer (REFER) request.
|
||||
*/
|
||||
static void on_call_transfered( pjsip_inv_session *inv,
|
||||
pjsip_rx_data *rdata )
|
||||
{
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
struct pjsua_inv_data *inv_data;
|
||||
const pj_str_t str_refer_to = { "Refer-To", 8};
|
||||
pjsip_generic_string_hdr *refer_to;
|
||||
char *uri;
|
||||
struct pjsip_evsub_user xfer_cb;
|
||||
pjsip_evsub *sub;
|
||||
|
||||
/* Find the Refer-To header */
|
||||
refer_to = (pjsip_generic_string_hdr*)
|
||||
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL);
|
||||
|
||||
if (refer_to == NULL) {
|
||||
/* Invalid Request.
|
||||
* No Refer-To header!
|
||||
*/
|
||||
PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!"));
|
||||
pjsip_dlg_respond( inv->dlg, rdata, 400, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s",
|
||||
(int)inv->dlg->remote.info_str.slen,
|
||||
inv->dlg->remote.info_str.ptr,
|
||||
(int)refer_to->hvalue.slen,
|
||||
refer_to->hvalue.ptr));
|
||||
|
||||
/* Init callback */
|
||||
pj_memset(&xfer_cb, 0, sizeof(xfer_cb));
|
||||
xfer_cb.on_evsub_state = &xfer_on_evsub_state;
|
||||
|
||||
/* Create transferee event subscription */
|
||||
status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create xfer uas", status);
|
||||
pjsip_dlg_respond( inv->dlg, rdata, 500, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Accept the REFER request, send 200 (OK). */
|
||||
pjsip_xfer_accept(sub, rdata, 200, NULL);
|
||||
|
||||
/* Create initial NOTIFY request */
|
||||
status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE,
|
||||
100, NULL, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send initial NOTIFY request */
|
||||
status = pjsip_xfer_send_request( sub, tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We're cheating here.
|
||||
* We need to get a null terminated string from a pj_str_t.
|
||||
* So grab the pointer from the hvalue and NULL terminate it, knowing
|
||||
* that the NULL position will be occupied by a newline.
|
||||
*/
|
||||
uri = refer_to->hvalue.ptr;
|
||||
uri[refer_to->hvalue.slen] = '\0';
|
||||
|
||||
/* Now make the outgoing call. */
|
||||
status = pjsua_invite(uri, &inv_data);
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
/* Notify xferer about the error */
|
||||
status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
|
||||
500, NULL, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
status = pjsip_xfer_send_request(sub, tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Put the server subscription in inv_data.
|
||||
* Subsequent state changed in pjsua_inv_on_state_changed() will be
|
||||
* reported back to the server subscription.
|
||||
*/
|
||||
inv_data->xfer_sub = sub;
|
||||
|
||||
/* Put the invite_data in the subscription. */
|
||||
pjsip_evsub_set_mod_data(sub, pjsua.mod.id, inv_data);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This callback is called when transaction state has changed in INVITE
|
||||
* session. We use this to trap incoming REFER request.
|
||||
*/
|
||||
void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv,
|
||||
pjsip_transaction *tsx,
|
||||
pjsip_event *e)
|
||||
{
|
||||
if (tsx->role==PJSIP_ROLE_UAS &&
|
||||
tsx->state==PJSIP_TSX_STATE_TRYING &&
|
||||
pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0)
|
||||
{
|
||||
/*
|
||||
* Incoming REFER request.
|
||||
*/
|
||||
on_call_transfered(inv, e->body.tsx_state.src.rdata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This callback is called by invite session framework when UAC session
|
||||
* has forked.
|
||||
|
@ -330,6 +554,109 @@ void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create inactive SDP for call hold.
|
||||
*/
|
||||
static pj_status_t create_inactive_sdp(struct pjsua_inv_data *inv_session,
|
||||
pjmedia_sdp_session **p_answer)
|
||||
{
|
||||
pj_status_t status;
|
||||
pjmedia_sdp_conn *conn;
|
||||
pjmedia_sdp_attr *attr;
|
||||
pjmedia_sdp_session *sdp;
|
||||
|
||||
/* Create new offer */
|
||||
status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1,
|
||||
&pjsua.med_sock_info[inv_session->call_slot],
|
||||
&sdp);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Get SDP media connection line */
|
||||
conn = sdp->media[0]->conn;
|
||||
if (!conn)
|
||||
conn = sdp->conn;
|
||||
|
||||
/* Modify address */
|
||||
conn->addr = pj_str("0.0.0.0");
|
||||
|
||||
/* Remove existing directions attributes */
|
||||
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv");
|
||||
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly");
|
||||
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly");
|
||||
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive");
|
||||
|
||||
/* Add inactive attribute */
|
||||
attr = pjmedia_sdp_attr_create(pjsua.pool, "inactive", NULL);
|
||||
pjmedia_sdp_media_add_attr(sdp->media[0], attr);
|
||||
|
||||
*p_answer = sdp;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when session received new offer.
|
||||
*/
|
||||
void pjsua_inv_on_rx_offer( pjsip_inv_session *inv,
|
||||
const pjmedia_sdp_session *offer)
|
||||
{
|
||||
struct pjsua_inv_data *inv_data;
|
||||
pjmedia_sdp_conn *conn;
|
||||
pjmedia_sdp_session *answer;
|
||||
pj_bool_t is_remote_active;
|
||||
pj_status_t status;
|
||||
|
||||
inv_data = inv->dlg->mod_data[pjsua.mod.id];
|
||||
|
||||
/*
|
||||
* See if remote is offering active media (i.e. not on-hold)
|
||||
*/
|
||||
is_remote_active = PJ_TRUE;
|
||||
|
||||
conn = offer->media[0]->conn;
|
||||
if (!conn)
|
||||
conn = offer->conn;
|
||||
|
||||
if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 ||
|
||||
pj_strcmp2(&conn->addr, "0")==0)
|
||||
{
|
||||
is_remote_active = PJ_FALSE;
|
||||
|
||||
}
|
||||
else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL))
|
||||
{
|
||||
is_remote_active = PJ_FALSE;
|
||||
}
|
||||
|
||||
PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s",
|
||||
(is_remote_active ? "active" : "inactive")));
|
||||
|
||||
/* Supply candidate answer */
|
||||
if (is_remote_active) {
|
||||
status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1,
|
||||
&pjsua.med_sock_info[inv_data->call_slot],
|
||||
&answer);
|
||||
} else {
|
||||
status = create_inactive_sdp( inv_data, &answer );
|
||||
}
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
|
||||
return;
|
||||
}
|
||||
|
||||
status = pjsip_inv_set_sdp_answer(inv, answer);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to set answer", status);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback to be called when SDP offer/answer negotiation has just completed
|
||||
* in the session. This function will start/update media if negotiation
|
||||
|
@ -340,6 +667,9 @@ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status)
|
|||
struct pjsua_inv_data *inv_data;
|
||||
const pjmedia_sdp_session *local_sdp;
|
||||
const pjmedia_sdp_session *remote_sdp;
|
||||
pjmedia_port *media_port;
|
||||
pj_str_t port_name;
|
||||
char tmp[PJSIP_MAX_URL_SIZE];
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
|
||||
|
@ -380,48 +710,240 @@ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status)
|
|||
/* Create new media session.
|
||||
* The media session is active immediately.
|
||||
*/
|
||||
|
||||
if (!pjsua.null_audio) {
|
||||
pjmedia_port *media_port;
|
||||
pj_str_t port_name;
|
||||
char tmp[PJSIP_MAX_URL_SIZE];
|
||||
|
||||
status = pjmedia_session_create( pjsua.med_endpt, 1,
|
||||
&pjsua.med_sock_info[inv_data->call_slot],
|
||||
local_sdp, remote_sdp,
|
||||
&inv_data->session );
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create media session",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
|
||||
pjmedia_session_get_port(inv_data->session, 0, &media_port);
|
||||
|
||||
port_name.ptr = tmp;
|
||||
port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
|
||||
inv_data->inv->dlg->remote.info->uri,
|
||||
tmp, sizeof(tmp));
|
||||
if (port_name.slen < 1) {
|
||||
port_name = pj_str("call");
|
||||
}
|
||||
status = pjmedia_conf_add_port( pjsua.mconf, inv->pool,
|
||||
media_port,
|
||||
&port_name,
|
||||
&inv_data->conf_slot);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create conference slot",
|
||||
status);
|
||||
pjmedia_session_destroy(inv_data->session);
|
||||
inv_data->session = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot);
|
||||
pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0);
|
||||
|
||||
PJ_LOG(3,(THIS_FILE,"Media has been started successfully"));
|
||||
if (pjsua.null_audio)
|
||||
return;
|
||||
|
||||
status = pjmedia_session_create( pjsua.med_endpt, 1,
|
||||
&pjsua.med_sock_info[inv_data->call_slot],
|
||||
local_sdp, remote_sdp,
|
||||
&inv_data->session );
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create media session",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Get the port interface of the first stream in the session.
|
||||
* We need the port interface to add to the conference bridge.
|
||||
*/
|
||||
pjmedia_session_get_port(inv_data->session, 0, &media_port);
|
||||
|
||||
|
||||
/*
|
||||
* Add the call to conference bridge.
|
||||
*/
|
||||
port_name.ptr = tmp;
|
||||
port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
|
||||
inv_data->inv->dlg->remote.info->uri,
|
||||
tmp, sizeof(tmp));
|
||||
if (port_name.slen < 1) {
|
||||
port_name = pj_str("call");
|
||||
}
|
||||
status = pjmedia_conf_add_port( pjsua.mconf, inv->pool,
|
||||
media_port,
|
||||
&port_name,
|
||||
&inv_data->conf_slot);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create conference slot",
|
||||
status);
|
||||
pjmedia_session_destroy(inv_data->session);
|
||||
inv_data->session = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Connect new call to the sound device port (port zero) in the
|
||||
* main conference bridge.
|
||||
*/
|
||||
pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot);
|
||||
pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0);
|
||||
|
||||
/* Done. */
|
||||
{
|
||||
struct pjmedia_session_info sess_info;
|
||||
char info[80];
|
||||
int info_len = 0;
|
||||
unsigned i;
|
||||
|
||||
pjmedia_session_get_info(inv_data->session, &sess_info);
|
||||
for (i=0; i<sess_info.stream_cnt; ++i) {
|
||||
int len;
|
||||
const char *dir;
|
||||
pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
|
||||
|
||||
switch (strm_info->dir) {
|
||||
case PJMEDIA_DIR_NONE:
|
||||
dir = "inactive";
|
||||
break;
|
||||
case PJMEDIA_DIR_ENCODING:
|
||||
dir = "sendonly";
|
||||
break;
|
||||
case PJMEDIA_DIR_DECODING:
|
||||
dir = "recvonly";
|
||||
break;
|
||||
case PJMEDIA_DIR_ENCODING_DECODING:
|
||||
dir = "sendrecv";
|
||||
break;
|
||||
default:
|
||||
dir = "unknown";
|
||||
break;
|
||||
}
|
||||
len = pj_ansi_sprintf( info+info_len,
|
||||
", stream #%d: %.*s (%s)", i,
|
||||
(int)strm_info->fmt.encoding_name.slen,
|
||||
(int)strm_info->fmt.encoding_name.ptr,
|
||||
dir);
|
||||
if (len > 0)
|
||||
info_len += len;
|
||||
}
|
||||
PJ_LOG(3,(THIS_FILE,"Media started%s", info));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Hangup call.
|
||||
*/
|
||||
void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code)
|
||||
{
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_inv_end_session(inv_session->inv,
|
||||
code, NULL, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE,
|
||||
"Failed to create end session message",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
|
||||
status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE,
|
||||
"Failed to send end session message",
|
||||
status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Put call on-Hold.
|
||||
*/
|
||||
void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session)
|
||||
{
|
||||
pjmedia_sdp_session *sdp;
|
||||
pjsip_inv_session *inv = inv_session->inv;
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
|
||||
PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
|
||||
return;
|
||||
}
|
||||
|
||||
status = create_inactive_sdp(inv_session, &sdp);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Send re-INVITE with new offer */
|
||||
status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
|
||||
return;
|
||||
}
|
||||
|
||||
status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* re-INVITE.
|
||||
*/
|
||||
void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session)
|
||||
{
|
||||
pjmedia_sdp_session *sdp;
|
||||
pjsip_tx_data *tdata;
|
||||
pjsip_inv_session *inv = inv_session->inv;
|
||||
pj_status_t status;
|
||||
|
||||
|
||||
if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
|
||||
PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create SDP */
|
||||
status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1,
|
||||
&pjsua.med_sock_info[inv_session->call_slot],
|
||||
&sdp);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send re-INVITE with new offer */
|
||||
status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
|
||||
return;
|
||||
}
|
||||
|
||||
status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Transfer call.
|
||||
*/
|
||||
void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session,
|
||||
const char *dest)
|
||||
{
|
||||
pjsip_evsub *sub;
|
||||
pjsip_tx_data *tdata;
|
||||
pj_str_t tmp;
|
||||
pj_status_t status;
|
||||
|
||||
|
||||
/* Create xfer client subscription.
|
||||
* We're not interested in knowing the transfer result, so we
|
||||
* put NULL as the callback.
|
||||
*/
|
||||
status = pjsip_xfer_create_uac(inv_session->inv->dlg, NULL, &sub);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create xfer", status);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create REFER request.
|
||||
*/
|
||||
status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to create REFER request", status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send. */
|
||||
status = pjsip_xfer_send_request(sub, tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pjsua_perror(THIS_FILE, "Unable to send REFER request", status);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For simplicity (that's what this program is intended to be!),
|
||||
* leave the original invite session as it is. More advanced application
|
||||
* may want to hold the INVITE, or terminate the invite, or whatever.
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -445,3 +967,4 @@ void pjsua_inv_shutdown()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue