diff options
Diffstat (limited to 'pjsip/src/pjsua/main.c')
-rw-r--r-- | pjsip/src/pjsua/main.c | 3654 |
1 files changed, 1827 insertions, 1827 deletions
diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c index e4d1e1c8..09f9f8f3 100644 --- a/pjsip/src/pjsua/main.c +++ b/pjsip/src/pjsua/main.c @@ -1,1827 +1,1827 @@ -/* $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 <pjlib.h>
-#include <pjsip_core.h>
-#include <pjsip_ua.h>
-#include <pjsip_simple.h>
-#include <pjmedia.h>
-#include <ctype.h>
-#include <stdlib.h>
-#include <pj/stun.h>
-
-#define START_PORT 5060
-#define MAX_BUDDIES 32
-#define THIS_FILE "main.c"
-#define MAX_PRESENTITY 32
-#define PRESENCE_TIMEOUT 60
-
-/* By default we'll have one worker thread, except when threading
- * is disabled.
- */
-#if PJ_HAS_THREADS
-# define WORKER_COUNT 1
-#else
-# define WORKER_COUNT 0
-#endif
-
-/* Global variable. */
-static struct
-{
- /* Control. */
- pj_pool_factory *pf;
- pjsip_endpoint *endpt;
- pj_pool_t *pool;
- pjsip_user_agent *user_agent;
- int worker_cnt;
- int worker_quit_flag;
-
- /* User info. */
- char user_id[64];
- pj_str_t local_uri;
- pj_str_t contact;
- pj_str_t real_contact;
-
- /* Dialog. */
- pjsip_dlg *cur_dlg;
-
- /* Authentication. */
- int cred_count;
- pjsip_cred_info cred_info[4];
-
- /* Media stack. */
- pj_bool_t null_audio;
- pj_med_mgr_t *mmgr;
-
- /* Misc. */
- int app_log_level;
- char *log_filename;
- FILE *log_file;
-
- /* Proxy URLs */
- pj_str_t proxy;
- pj_str_t outbound_proxy;
-
- /* UA auto options. */
- int auto_answer; /* -1 to disable. */
- int auto_hangup; /* -1 to disable */
-
- /* Registration. */
- pj_str_t registrar_uri;
- pjsip_regc *regc;
- pj_int32_t reg_timeout;
- pj_timer_entry regc_timer;
-
- /* STUN */
- pj_str_t stun_srv1;
- int stun_port1;
- pj_str_t stun_srv2;
- int stun_port2;
-
- /* UDP sockets and their public address. */
- int sip_port;
- pj_sock_t sip_sock;
- pj_sockaddr_in sip_sock_name;
- pj_sock_t rtp_sock;
- pj_sockaddr_in rtp_sock_name;
- pj_sock_t rtcp_sock;
- pj_sockaddr_in rtcp_sock_name;
-
- /* SIMPLE */
- pj_bool_t hide_status;
- pj_bool_t offer_x_ms_msg;
- int im_counter;
- int buddy_cnt;
- pj_str_t buddy[MAX_BUDDIES];
- pj_bool_t buddy_status[MAX_BUDDIES];
- pj_bool_t no_presence;
- pjsip_presentity *buddy_pres[MAX_BUDDIES];
-
- int pres_cnt;
- pjsip_presentity *pres[MAX_PRESENTITY];
-
-} global;
-
-enum { AUTO_ANSWER, AUTO_HANGUP };
-
-/* This is the data that will be 'attached' on per dialog basis. */
-struct dialog_data
-{
- /* Media session. */
- pj_media_session_t *msession;
-
- /* x-ms-chat session. */
- pj_bool_t x_ms_msg_session;
-
- /* Cached SDP body, updated when media session changed. */
- pjsip_msg_body *body;
-
- /* Timer. */
- pj_bool_t has_auto_timer;
- pj_timer_entry auto_timer;
-};
-
-/*
- * These are the callbacks to be registered to dialog to receive notifications
- * about various events in the dialog.
- */
-static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
- pjsip_event *event );
-static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_tx_data *tdata, int retransmission);
-static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_tx_data *tdata);
-static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_rx_data *rdata);
-static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_rx_data *rdata);
-static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_tx_data *tdata);
-static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_event *event);
-static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event);
-static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event);
-static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event);
-static void dlg_on_terminated (pjsip_dlg *dlg);
-static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event);
-
-/* The callback structure that will be registered to UA layer. */
-struct pjsip_dlg_callback dlg_callback = {
- &dlg_on_all_events,
- &dlg_on_before_tx,
- &dlg_on_tx_msg,
- &dlg_on_rx_msg,
- &dlg_on_incoming,
- &dlg_on_calling,
- &dlg_on_provisional,
- &dlg_on_connecting,
- &dlg_on_established,
- &dlg_on_disconnected,
- &dlg_on_terminated,
- &dlg_on_mid_call_evt
-};
-
-
-/*
- * Auxiliary things are put in misc.c, so that this main.c file is more
- * readable.
- */
-#include "misc.c"
-
-static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap,
- struct pj_timer_entry *entry)
-{
- pjsip_dlg *dlg = entry->user_data;
- struct dialog_data *dlg_data = dlg->user_data;
-
- PJ_UNUSED_ARG(timer_heap)
-
- dlg_data->has_auto_timer = 0;
-
- if (entry->id == AUTO_ANSWER) {
- pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200);
- if (tdata) {
- struct dialog_data *dlg_data = global.cur_dlg->user_data;
- tdata->msg->body = dlg_data->body;
- pjsip_dlg_send_msg(dlg, tdata);
- }
- } else {
- pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500);
- if (tdata)
- pjsip_dlg_send_msg(dlg, tdata);
- }
-}
-
-static void update_registration(pjsip_regc *regc, int renew)
-{
- pjsip_tx_data *tdata;
-
- PJ_LOG(3,(THIS_FILE, "Performing SIP registration..."));
-
- if (renew) {
- tdata = pjsip_regc_register(regc, 1);
- } else {
- tdata = pjsip_regc_unregister(regc);
- }
-
- pjsip_regc_send( regc, tdata );
-}
-
-static void regc_cb(struct pjsip_regc_cbparam *param)
-{
- /*
- * Print registration status.
- */
- if (param->code < 0 || param->code >= 300) {
- PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)",
- param->code, pjsip_get_status_text(param->code)->ptr));
- global.regc = NULL;
-
- } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
- PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), "
- "will re-register in %d seconds",
- param->code,
- pjsip_get_status_text(param->code)->ptr,
- param->expiration));
-
- } else {
- PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code));
- }
-}
-
-static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata,
- int *timeout)
-{
- int state;
- int i;
- char url[PJSIP_MAX_URL_SIZE];
- int urllen;
-
- PJ_UNUSED_ARG(rdata)
-
- if (*timeout > 0) {
- state = PJSIP_EVENT_SUB_STATE_ACTIVE;
- if (*timeout > 300)
- *timeout = 300;
- } else {
- state = PJSIP_EVENT_SUB_STATE_TERMINATED;
- }
-
- urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1);
- if (urllen < 1) {
- pj_native_strcpy(url, "<unknown>");
- } else {
- url[urllen] = '\0';
- }
- PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s",
- url,
- (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated")));
-
- for (i=0; i<global.pres_cnt; ++i)
- if (global.pres[i] == pres)
- break;
- if (i == global.pres_cnt)
- global.pres[global.pres_cnt++] = pres;
-
- pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info );
- pjsip_presence_notify(pres, state, !global.hide_status);
-
-}
-
-static void pres_on_received_refresh(pjsip_presentity *pres, pjsip_rx_data *rdata)
-{
- pres_on_received_request(pres, rdata, &pres->sub->default_interval);
-}
-
-/* This is called by presence framework when we receives presence update
- * of a resource (buddy).
- */
-static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open)
-{
- int buddy_index = (int)pres->user_data;
-
- global.buddy_status[buddy_index] = is_open;
- PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s",
- global.buddy[buddy_index].ptr,
- (is_open ? "Online" : "Offline")));
-}
-
-/* This is called when the subscription is terminated. */
-static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason)
-{
- if (pres->sub->role == PJSIP_ROLE_UAC) {
- int buddy_index = (int)pres->user_data;
- PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)",
- global.buddy[buddy_index].ptr,
- reason->slen, reason->ptr));
- global.buddy_pres[buddy_index] = NULL;
- global.buddy_status[buddy_index] = 0;
- } else {
- int i;
- PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)",
- reason->slen, reason->ptr));
- pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1);
- for (i=0; i<global.pres_cnt; ++i) {
- if (global.pres[i] == pres) {
- int j;
- global.pres[i] = NULL;
- for (j=i+1; j<global.pres_cnt; ++j)
- global.pres[j-1] = global.pres[j];
- global.pres_cnt--;
- break;
- }
- }
- }
- pjsip_presence_destroy(pres);
-}
-
-
-/* Callback attached to SIP body to print the body to message buffer. */
-static int print_msg_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size)
-{
- pjsip_msg_body *body = msg_body;
- return pjsdp_print ((pjsdp_session_desc*)body->data, buf, size);
-}
-
-/* When media session has changed, call this function to update the cached body
- * information in the dialog.
- */
-static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg)
-{
- struct dialog_data *dlg_data = dlg->user_data;
- pjsdp_session_desc *sdp;
-
- sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg);
- if (!sdp) {
- dlg_data->body = NULL;
- return NULL;
- }
-
- /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new
- * "m=" line in the SDP.
- */
- if (dlg_data->x_ms_msg_session >= 0 &&
- dlg_data->x_ms_msg_session >= (int)sdp->media_count)
- {
- pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m));
- sdp->media[sdp->media_count] = m;
- dlg_data->x_ms_msg_session = sdp->media_count++;
- }
-
- /*
- * For "x-ms-message" line, remove all attributes and connection line etc.
- */
- if (dlg_data->x_ms_msg_session >= 0) {
- pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session];
- if (m) {
- m->desc.media = pj_str("x-ms-message");
- m->desc.port = 5060;
- m->desc.transport = pj_str("sip");
- m->desc.fmt_count = 1;
- m->desc.fmt[0] = pj_str("null");
- m->attr_count = 0;
- m->conn = NULL;
- }
- }
-
- dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body));
- dlg_data->body->content_type.type = pj_str("application");
- dlg_data->body->content_type.subtype = pj_str("sdp");
- dlg_data->body->len = 0; /* ignored */
- dlg_data->body->print_body = &print_msg_body;
-
- dlg_data->body->data = sdp;
- return dlg_data->body;
-}
-
-/* This callback will be called on every occurence of events in dialogs */
-static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
- pjsip_event *event )
-{
- PJ_UNUSED_ARG(dlg_evt)
- PJ_UNUSED_ARG(event)
-
- PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg));
-}
-
-/* This callback is called before each outgoing msg is sent (including
- * retransmission). Application can override this notification if it wants
- * to modify the message before transmission or if it wants to do something
- * else for each transmission.
- */
-static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_tx_data *tdata, int ret_cnt)
-{
- PJ_UNUSED_ARG(tsx)
- PJ_UNUSED_ARG(tdata)
-
- if (ret_cnt > 0) {
- PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)",
- dlg->obj_name, ret_cnt));
- }
-}
-
-/* This callback is called after a message is sent. */
-static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_tx_data *tdata)
-{
- PJ_UNUSED_ARG(tsx)
- PJ_UNUSED_ARG(tdata)
-
- PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg));
-}
-
-/* This callback is called on receipt of incoming message. */
-static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_rx_data *rdata)
-{
- PJ_UNUSED_ARG(tsx)
- PJ_UNUSED_ARG(rdata)
- PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg));
-}
-
-/* This callback is called after dialog has sent INVITE */
-static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_tx_data *tdata)
-{
- PJ_UNUSED_ARG(tsx)
- PJ_UNUSED_ARG(tdata)
-
- pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG &&
- tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD &&
- tsx->method.id == PJSIP_INVITE_METHOD);
-
- PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name));
-}
-
-static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp)
-{
- struct dialog_data *dlg_data = dlg->user_data;
- pj_bool_t sdp_x_ms_msg_index = -1;
- int i;
- int mcnt;
- const pj_media_stream_info *mi[PJSDP_MAX_MEDIA];
- int has_active;
- pj_media_sock_info sock_info;
-
- /* Find "m=x-ms-message" line in the SDP. */
- for (i=0; i<(int)sdp->media_count; ++i) {
- if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0)
- sdp_x_ms_msg_index = i;
- }
-
- /*
- * Create media session.
- */
- pj_memset(&sock_info, 0, sizeof(sock_info));
- sock_info.rtp_sock = global.rtp_sock;
- sock_info.rtcp_sock = global.rtcp_sock;
- pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in));
-
- dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info);
-
- /* A session will always be created, unless there is memory
- * alloc problem.
- */
- pj_assert(dlg_data->msession);
-
- /* See if we can take the offer by checking that we have at least
- * one media stream active.
- */
- mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi);
- for (i=0, has_active=0; i<mcnt; ++i) {
- if (mi[i]->fmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) {
- has_active = 1;
- break;
- }
- }
-
- if (!has_active && sdp_x_ms_msg_index==-1) {
- pjsip_tx_data *tdata;
-
- /* Unable to accept remote's SDP.
- * Answer with 488 (Not Acceptable Here)
- */
- /* Create 488 response. */
- tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE);
-
- /* Send response. */
- if (tdata)
- pjsip_dlg_send_msg(dlg, tdata);
- return;
- }
-
- dlg_data->x_ms_msg_session = sdp_x_ms_msg_index;
-
- /* Create msg body to be used later in 2xx/response */
- create_msg_body(dlg, 0);
-
-}
-
-/* This callback is called after an INVITE is received. */
-static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_rx_data *rdata)
-{
- struct dialog_data *dlg_data;
- pjsip_msg *msg;
- pjsip_tx_data *tdata;
- char buf[128];
- int len;
-
- PJ_UNUSED_ARG(tsx)
-
- pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG &&
- rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD &&
- tsx->method.id == PJSIP_INVITE_METHOD);
-
- /*
- * Notify user!
- */
- PJ_LOG(3, (THIS_FILE, ""));
- PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name));
- PJ_LOG(3, (THIS_FILE, ""));
- len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR,
- (pjsip_name_addr*)dlg->remote.info->uri,
- buf, sizeof(buf)-1);
- if (len > 0) {
- buf[len] = '\0';
- PJ_LOG(3,(THIS_FILE, "From:\t%s", buf));
- }
- len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR,
- (pjsip_name_addr*)dlg->local.info->uri,
- buf, sizeof(buf)-1);
- if (len > 0) {
- buf[len] = '\0';
- PJ_LOG(3,(THIS_FILE, "To:\t%s", buf));
- }
- PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name));
- PJ_LOG(3, (THIS_FILE, ""));
-
- /*
- * Process incoming dialog.
- */
-
- dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data));
- dlg->user_data = dlg_data;
-
- /* Update contact. */
- pjsip_dlg_set_contact(dlg, &global.contact);
-
- /* Initialize credentials. */
- pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info);
-
- /* Create media session if the request has "application/sdp" body. */
- msg = rdata->msg;
- if (msg->body &&
- pj_stricmp2(&msg->body->content_type.type, "application")==0 &&
- pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0)
- {
- pjsdp_session_desc *sdp;
-
- /* Parse SDP body, and instantiate media session based on remote's SDP.
- * Then create our SDP body from the session.
- */
- sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool);
- if (!sdp)
- goto send_answer;
-
- create_session_from_sdp(dlg, sdp);
-
- } else if (msg->body) {
- /* The request has a message body other than "application/sdp" */
- pjsip_accept_hdr *accept;
-
- /* Create response. */
- tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
-
- /* Add "Accept" header. */
- accept = pjsip_accept_hdr_create(tdata->pool);
- accept->values[0] = pj_str("application/sdp");
- accept->count = 1;
- pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept);
-
- /* Send response. */
- pjsip_dlg_send_msg(dlg, tdata);
- return;
-
- } else {
- /* The request has no message body. We can take this request, but
- * no media session will be activated.
- */
- /* Nothing to do here. */
- }
-
-send_answer:
- /* Immediately answer with 100 (or 180? */
- tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING );
- pjsip_dlg_send_msg(dlg, tdata);
-
- /* Set current dialog to this dialog if we don't currently have
- * current dialog.
- */
- if (global.cur_dlg == NULL) {
- global.cur_dlg = dlg;
- }
-
- /* Auto-answer if option is specified. */
- if (global.auto_answer >= 0) {
- pj_time_val delay = { 0, 0};
- struct dialog_data *dlg_data = dlg->user_data;
-
- PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds",
- global.auto_answer));
-
- delay.sec = global.auto_answer;
- dlg_data->auto_timer.user_data = dlg;
- dlg_data->auto_timer.id = AUTO_ANSWER;
- dlg_data->auto_timer.cb = &dlg_auto_timer_callback;
- dlg_data->has_auto_timer = 1;
- pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay);
- }
-}
-
-/* This callback is called when dialog has sent/received a provisional response
- * to INVITE.
- */
-static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx,
- pjsip_event *event)
-{
- const char *action;
-
- pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
- event->src.tdata->msg->type == PJSIP_RESPONSE_MSG &&
- event->src.tdata->msg->line.status.code/100 == 1 &&
- tsx->method.id == PJSIP_INVITE_METHOD)
- ||
- (event->src_type == PJSIP_EVENT_RX_MSG &&
- event->src.rdata->msg->type == PJSIP_RESPONSE_MSG &&
- event->src.rdata->msg->line.status.code/100 == 1 &&
- tsx->method.id == PJSIP_INVITE_METHOD));
-
- if (event->src_type == PJSIP_EVENT_TX_MSG)
- action = "Sending";
- else
- action = "Received";
-
- PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)",
- dlg->obj_name, action, tsx->status_code,
- pjsip_get_status_text(tsx->status_code)->ptr));
-}
-
-/* This callback is called when 200 response to INVITE is sent/received. */
-static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event)
-{
- struct dialog_data *dlg_data = dlg->user_data;
- const char *action;
-
- pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
- event->src.tdata->msg->type == PJSIP_RESPONSE_MSG &&
- event->src.tdata->msg->line.status.code/100 == 2)
- ||
- (event->src_type == PJSIP_EVENT_RX_MSG &&
- event->src.rdata->msg->type == PJSIP_RESPONSE_MSG &&
- event->src.rdata->msg->line.status.code/100 == 2));
-
- if (event->src_type == PJSIP_EVENT_RX_MSG)
- action = "Received";
- else
- action = "Sending";
-
- PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action));
-
- if (event->src_type == PJSIP_EVENT_RX_MSG) {
- /* On receipt of 2xx response, negotiate our media capability
- * and start media.
- */
- pjsip_msg *msg = event->src.rdata->msg;
- pjsip_msg_body *body;
- pjsdp_session_desc *sdp;
-
- /* Get SDP from message. */
-
- /* Ignore if no SDP body is present. */
- body = msg->body;
- if (!body) {
- PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!",
- dlg->obj_name));
- return;
- }
-
- if (pj_stricmp2(&body->content_type.type, "application") != 0 &&
- pj_stricmp2(&body->content_type.subtype, "sdp") != 0)
- {
- PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!",
- dlg->obj_name));
- return;
- }
-
- /* Got what seems to be a SDP content. Parse it. */
- sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool);
- if (!sdp) {
- PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!",
- dlg->obj_name));
- return;
- }
-
- /* Negotiate media session with remote's media capability. */
- if (pj_media_session_update (dlg_data->msession, sdp) != 0) {
- PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!",
- dlg->obj_name));
- return;
- }
-
- /* Update the saved SDP body because media session has changed.
- * Also set ack flag to '1', because we only want to send one format/
- * codec for each media streams.
- */
- create_msg_body(dlg, 1);
-
- /* Activate media. */
- pj_media_session_activate (dlg_data->msession);
-
- } else {
- pjsip_msg *msg = event->src.tdata->msg;
-
- if (msg->body) {
- /* On transmission of 2xx response, start media session. */
- pj_media_session_activate (dlg_data->msession);
- }
- }
-
-}
-
-/* This callback is called when ACK to initial INVITE is sent/received. */
-static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event)
-{
- const char *action;
-
- pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
- event->src.tdata->msg->type == PJSIP_REQUEST_MSG &&
- event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD)
- ||
- (event->src_type == PJSIP_EVENT_RX_MSG &&
- event->src.rdata->msg->type == PJSIP_REQUEST_MSG &&
- event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD));
-
- if (event->src_type == PJSIP_EVENT_RX_MSG)
- action = "Received";
- else
- action = "Sending";
-
- PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED",
- dlg->obj_name, action));
-
- /* Attach SDP body for outgoing ACK. */
- if (event->src_type == PJSIP_EVENT_TX_MSG &&
- event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD)
- {
- struct dialog_data *dlg_data = dlg->user_data;
- event->src.tdata->msg->body = dlg_data->body;
- }
-
- /* Auto-hangup if option is specified. */
- if (global.auto_hangup >= 0) {
- pj_time_val delay = { 0, 0};
- struct dialog_data *dlg_data = dlg->user_data;
-
- PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds",
- global.auto_hangup));
-
- delay.sec = global.auto_hangup;
- dlg_data->auto_timer.user_data = dlg;
- dlg_data->auto_timer.id = AUTO_HANGUP;
- dlg_data->auto_timer.cb = &dlg_auto_timer_callback;
- dlg_data->has_auto_timer = 1;
- pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay);
- }
-}
-
-
-/* This callback is called when dialog is disconnected (because of final
- * response, BYE, or timer).
- */
-static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event)
-{
- struct dialog_data *dlg_data = dlg->user_data;
- int status_code;
- const pj_str_t *reason;
-
- PJ_UNUSED_ARG(event)
-
- /* Cancel auto-answer/auto-hangup timer. */
- if (dlg_data->has_auto_timer) {
- pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer);
- dlg_data->has_auto_timer = 0;
- }
-
- if (dlg->invite_tsx)
- status_code = dlg->invite_tsx->status_code;
- else
- status_code = 200;
-
- if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) {
- if (event->src_type == PJSIP_EVENT_RX_MSG)
- reason = &event->src.rdata->msg->line.status.reason;
- else if (event->src_type == PJSIP_EVENT_TX_MSG)
- reason = &event->src.tdata->msg->line.status.reason;
- else
- reason = pjsip_get_status_text(event->obj.tsx->status_code);
- } else {
- reason = &event->obj.tsx->method.name;
- }
-
- PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)",
- dlg->obj_name, status_code,
- reason->slen, reason->ptr));
-
- if (dlg_data->msession) {
- pj_media_session_destroy (dlg_data->msession);
- dlg_data->msession = NULL;
- }
-}
-
-/* This callback is called when dialog is about to be destroyed. */
-static void dlg_on_terminated(pjsip_dlg *dlg)
-{
- PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name));
-
- /* If current dialog is equal to this dialog, update it. */
- if (global.cur_dlg == dlg) {
- global.cur_dlg = global.cur_dlg->next;
- if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
- global.cur_dlg = NULL;
- }
- }
-}
-
-/* This callback is called for any requests when dialog is established. */
-static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event)
-{
- pjsip_transaction *tsx = event->obj.tsx;
-
- if (event->src_type == PJSIP_EVENT_RX_MSG &&
- event->src.rdata->msg->type == PJSIP_REQUEST_MSG)
- {
- if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) {
- /* Re-invitation. */
- pjsip_tx_data *tdata;
-
- PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)",
- dlg->obj_name));
- tdata = pjsip_dlg_answer(dlg, 200);
- if (tdata) {
- struct dialog_data *dlg_data = dlg->user_data;
- tdata->msg->body = dlg_data->body;
- pjsip_dlg_send_msg(dlg, tdata);
- }
- } else {
- /* Don't worry, endpoint will answer with 500 or whetever. */
- }
-
- } else if (tsx->status_code/100 == 2) {
- PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)",
- dlg->obj_name,
- tsx->method.name.slen, tsx->method.name.ptr,
- tsx->status_code,
- pjsip_get_status_text(tsx->status_code)->ptr));
-
-
- } else if (tsx->status_code >= 300) {
- pj_bool_t report_failure = PJ_TRUE;
-
- /* Check for authentication failures. */
- if (tsx->status_code==401 || tsx->status_code==407) {
- pjsip_tx_data *tdata;
- tdata = pjsip_auth_reinit_req( global.endpt,
- dlg->pool, &dlg->auth_sess,
- dlg->cred_count, dlg->cred_info,
- tsx->last_tx, event->src.rdata );
- if (tdata) {
- int rc;
- rc = pjsip_dlg_send_msg( dlg, tdata);
- report_failure = (rc != 0);
- }
- }
- if (report_failure) {
- const pj_str_t *reason;
- if (event->src_type == PJSIP_EVENT_RX_MSG) {
- reason = &event->src.rdata->msg->line.status.reason;
- } else {
- reason = pjsip_get_status_text(tsx->status_code);
- }
- PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)",
- dlg->obj_name, tsx->status_code,
- reason->slen, reason->ptr));
- }
- }
-}
-
-/* Initialize sockets and optionally get the public address via STUN. */
-static pj_status_t init_sockets()
-{
- enum {
- RTP_START_PORT = 4000,
- RTP_RANDOM_START = 2,
- RTP_RETRY = 10
- };
- enum {
- SIP_SOCK,
- RTP_SOCK,
- RTCP_SOCK,
- };
- int i;
- int rtp_port;
- pj_sock_t sock[3];
- pj_sockaddr_in mapped_addr[3];
-
- for (i=0; i<3; ++i)
- sock[i] = PJ_INVALID_SOCKET;
-
- /* Create and bind SIP UDP socket. */
- sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
- if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) {
- PJ_LOG(2,(THIS_FILE, "Unable to create socket"));
- goto on_error;
- }
- if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) {
- PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port"));
- goto on_error;
- }
-
- /* Initialize start of RTP port to try. */
- rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2;
-
- /* Loop retry to bind RTP and RTCP sockets. */
- for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
-
- /* Create and bind RTP socket. */
- sock[RTP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
- if (sock[RTP_SOCK] == PJ_INVALID_SOCKET)
- goto on_error;
- if (pj_sock_bind_in(sock[RTP_SOCK], 0, (pj_uint16_t)rtp_port) != 0) {
- pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
- continue;
- }
-
- /* Create and bind RTCP socket. */
- sock[RTCP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
- if (sock[RTCP_SOCK] == PJ_INVALID_SOCKET)
- goto on_error;
- if (pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)) != 0) {
- pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
- pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
- continue;
- }
-
- /*
- * If we're configured to use STUN, then find out the mapped address,
- * and make sure that the mapped RTCP port is adjacent with the RTP.
- */
- if (global.stun_port1 == 0) {
- pj_str_t hostname;
- pj_sockaddr_in addr;
-
- /* Get local IP address. */
- char hostname_buf[PJ_MAX_HOSTNAME];
- if (gethostname(hostname_buf, sizeof(hostname_buf)))
- goto on_error;
- hostname = pj_str(hostname_buf);
-
- pj_memset( &addr, 0, sizeof(addr));
- addr.sin_family = PJ_AF_INET;
- if (pj_sockaddr_set_str_addr( &addr, &hostname) != PJ_SUCCESS)
- goto on_error;
-
- for (i=0; i<3; ++i)
- pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
-
- mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)global.sip_port);
- mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port);
- mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
- break;
- } else {
- pj_status_t rc;
- rc = pj_stun_get_mapped_addr( global.pf, 3, sock,
- &global.stun_srv1, global.stun_port1,
- &global.stun_srv2, global.stun_port2,
- mapped_addr);
- if (rc != 0) {
- PJ_LOG(3,(THIS_FILE, "Error: %s", pj_stun_get_err_msg(rc)));
- goto on_error;
- }
-
- if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1)
- break;
-
- pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
- pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
- }
- }
-
- if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) {
- PJ_LOG(2,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination"));
- goto on_error;
- }
-
- global.sip_sock = sock[SIP_SOCK];
- pj_memcpy(&global.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in));
- global.rtp_sock = sock[RTP_SOCK];
- pj_memcpy(&global.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in));
- global.rtcp_sock = sock[RTCP_SOCK];
- pj_memcpy(&global.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in));
-
- PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d",
- pj_inet_ntoa(global.sip_sock_name.sin_addr),
- pj_ntohs(global.sip_sock_name.sin_port)));
- PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
- pj_inet_ntoa(global.rtp_sock_name.sin_addr),
- pj_ntohs(global.rtp_sock_name.sin_port)));
- PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d",
- pj_inet_ntoa(global.rtcp_sock_name.sin_addr),
- pj_ntohs(global.rtcp_sock_name.sin_port)));
- return 0;
-
-on_error:
- for (i=0; i<3; ++i) {
- if (sock[i] != PJ_INVALID_SOCKET)
- pj_sock_close(sock[i]);
- }
- return -1;
-}
-
-static void log_function(int level, const char *buffer, int len)
-{
- /* Write to both stdout and file. */
- if (level <= global.app_log_level)
- pj_log_to_stdout(level, buffer, len);
- if (global.log_file) {
- fwrite(buffer, len, 1, global.log_file);
- fflush(global.log_file);
- }
-}
-
-/* Initialize stack. */
-static pj_status_t init_stack()
-{
- pj_status_t status;
- pj_sockaddr_in bind_addr;
- pj_sockaddr_in bind_name;
- const char *local_addr;
- static char local_uri[128];
-
- /* Optionally set logging file. */
- if (global.log_filename) {
- global.log_file = fopen(global.log_filename, "wt");
- }
-
- /* Initialize endpoint. This will also call initialization to all the
- * modules.
- */
- global.endpt = pjsip_endpt_create(global.pf);
- if (global.endpt == NULL) {
- return -1;
- }
-
- /* Set dialog callback. */
- pjsip_ua_set_dialog_callback(global.user_agent, &dlg_callback);
-
- /* Init listener's bound address and port. */
- pj_sockaddr_init2(&bind_addr, "0.0.0.0", global.sip_port);
- pj_sockaddr_init(&bind_name, pj_gethostname(), global.sip_port);
-
- /* Add UDP transport listener. */
- status = pjsip_endpt_create_udp_listener( global.endpt, global.sip_sock,
- &global.sip_sock_name);
- if (status != 0)
- return -1;
-
- local_addr = pj_inet_ntoa(global.sip_sock_name.sin_addr);
-
-#if PJ_HAS_TCP
- /* Add TCP transport listener. */
- status = pjsip_endpt_create_listener( global.endpt, PJSIP_TRANSPORT_TCP,
- &bind_addr, &bind_name);
- if (status != 0)
- return -1;
-#endif
-
- /* Determine user_id to be put in Contact */
- if (global.local_uri.slen) {
- pj_pool_t *pool = pj_pool_create(global.pf, "parser", 1024, 0, NULL);
- pjsip_uri *uri;
-
- uri = pjsip_parse_uri(pool, global.local_uri.ptr, global.local_uri.slen, 0);
- if (uri) {
- if (pj_stricmp2(pjsip_uri_get_scheme(uri), "sip")==0) {
- pjsip_url *url = (pjsip_url*)pjsip_uri_get_uri(uri);
- if (url->user.slen)
- strncpy(global.user_id, url->user.ptr, url->user.slen);
- }
- }
- pj_pool_release(pool);
- }
-
- if (global.user_id[0]=='\0') {
- pj_native_strcpy(global.user_id, "user");
- }
-
- /* build contact */
- global.real_contact.ptr = local_uri;
- global.real_contact.slen =
- sprintf(local_uri, "<sip:%s@%s:%d>", global.user_id, local_addr, global.sip_port);
-
- if (global.contact.slen == 0)
- global.contact = global.real_contact;
-
- /* initialize local_uri with contact if it's not specified in cmdline */
- if (global.local_uri.slen == 0)
- global.local_uri = global.contact;
-
- /* Init proxy. */
- if (global.proxy.slen || global.outbound_proxy.slen) {
- int count = 0;
- pj_str_t proxy_url[2];
-
- if (global.outbound_proxy.slen) {
- proxy_url[count++] = global.outbound_proxy;
- }
- if (global.proxy.slen) {
- proxy_url[count++] = global.proxy;
- }
-
- if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) {
- PJ_LOG(2,(THIS_FILE, "Error setting proxy address!"));
- return -1;
- }
- }
-
- /* initialize SIP registration if registrar is configured */
- if (global.registrar_uri.slen) {
- global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb);
- pjsip_regc_init( global.regc, &global.registrar_uri,
- &global.local_uri,
- &global.local_uri,
- 1, &global.contact,
- global.reg_timeout);
- pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info );
- }
-
- return PJ_SUCCESS;
-}
-
-/* Worker thread function, only used when threading is enabled. */
-static void *PJ_THREAD_FUNC worker_thread(void *unused)
-{
- PJ_UNUSED_ARG(unused)
-
- while (!global.worker_quit_flag) {
- pj_time_val timeout = { 0, 10 };
- pjsip_endpt_handle_events (global.endpt, &timeout);
- }
- return NULL;
-}
-
-
-/* Make call to the specified URI. */
-static pjsip_dlg *make_call(pj_str_t *remote_uri)
-{
- pjsip_dlg *dlg;
- pj_str_t local = global.contact;
- pj_str_t remote = *remote_uri;
- struct dialog_data *dlg_data;
- pjsip_tx_data *tdata;
- pj_media_sock_info sock_info;
-
- /* Create new dialog instance. */
- dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC);
-
- /* Attach our own user data. */
- dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data));
- dlg->user_data = dlg_data;
-
- /* Create media session. */
- pj_memset(&sock_info, 0, sizeof(sock_info));
- sock_info.rtp_sock = global.rtp_sock;
- sock_info.rtcp_sock = global.rtcp_sock;
- pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in));
-
- dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info);
- dlg_data->x_ms_msg_session = -1;
-
- if (global.offer_x_ms_msg) {
- const pj_media_stream_info *minfo[32];
- unsigned cnt;
-
- cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo);
- if (cnt > 0)
- dlg_data->x_ms_msg_session = cnt;
- }
-
- /* Initialize dialog with local and remote URI. */
- if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) {
- pjsip_ua_destroy_dialog(dlg);
- return NULL;
- }
-
- /* Initialize credentials. */
- pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info);
-
- /* Send INVITE! */
- tdata = pjsip_dlg_invite(dlg);
- tdata->msg->body = create_msg_body (dlg, 0);
-
- if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) {
- pjsip_ua_destroy_dialog(dlg);
- return NULL;
- }
-
- return dlg;
-}
-
-/*
- * Callback to receive incoming IM message.
- */
-static int on_incoming_im_msg(pjsip_rx_data *rdata)
-{
- pjsip_msg *msg = rdata->msg;
- pjsip_msg_body *body = msg->body;
- int len;
- char to[128], from[128];
-
-
- len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
- rdata->from->uri, from, sizeof(from));
- if (len > 0) from[len] = '\0';
- else pj_native_strcpy(from, "<URL too long..>");
-
- len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
- rdata->to->uri, to, sizeof(to));
- if (len > 0) to[len] = '\0';
- else pj_native_strcpy(to, "<URL too long..>");
-
- PJ_LOG(3,(THIS_FILE, "Incoming instant message:"));
-
- printf("----- BEGIN INSTANT MESSAGE ----->\n");
- printf("From:\t%s\n", from);
- printf("To:\t%s\n", to);
- printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : ""));
- printf("<------ END INSTANT MESSAGE ------\n");
-
- fflush(stdout);
-
- /* Must answer with final response. */
- return 200;
-}
-
-/*
- * Input URL.
- */
-static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection)
-{
- int i;
-
- *selection = -1;
-
- printf("\nBuddy list:\n");
- printf("---------------------------------------\n");
- for (i=0; i<global.buddy_cnt; ++i) {
- printf(" %d\t%s <%s>\n", i+1, global.buddy[i].ptr,
- (global.buddy_status[i]?"Online":"Offline"));
- }
- printf("-------------------------------------\n");
-
- printf("Choices\n"
- "\t0 For current dialog.\n"
- "\t[1-%02d] Select from buddy list\n"
- "\tURL An URL\n"
- , global.buddy_cnt);
- printf("Input: ");
-
- fflush(stdout);
- fgets(buf, len, stdin);
- buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */
-
- while (isspace(*buf)) ++buf;
-
- if (!*buf || *buf=='\n' || *buf=='\r')
- return NULL;
-
- i = atoi(buf);
-
- if (i == 0) {
- if (isdigit(*buf)) {
- *selection = 0;
- *out = pj_str("0");
- return out;
- } else {
- if (verify_sip_url(buf) != 0) {
- puts("Invalid URL specified!");
- return NULL;
- }
- *out = pj_str(buf);
- return out;
- }
- } else if (i > global.buddy_cnt || i < 0) {
- printf("Error: invalid selection!\n");
- return NULL;
- } else {
- *out = global.buddy[i-1];
- *selection = i;
- return out;
- }
-}
-
-
-static void generic_request_callback( void *token, pjsip_event *event )
-{
- pjsip_transaction *tsx = event->obj.tsx;
-
- PJ_UNUSED_ARG(token)
-
- if (tsx->status_code/100 == 2) {
- PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)",
- event->obj.tsx->method.name.slen,
- event->obj.tsx->method.name.ptr,
- tsx->status_code,
- pjsip_get_status_text(tsx->status_code)->ptr));
- } else if (tsx->status_code==401 || tsx->status_code==407) {
- pjsip_tx_data *tdata;
- tdata = pjsip_auth_reinit_req( global.endpt,
- global.pool, NULL, global.cred_count, global.cred_info,
- tsx->last_tx, event->src.rdata);
- if (tdata) {
- int rc;
- pjsip_cseq_hdr *cseq;
- cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
- cseq->cseq++;
- rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL,
- &generic_request_callback);
- if (rc == 0)
- return;
- }
- PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)",
- event->obj.tsx->method.name.slen,
- event->obj.tsx->method.name.ptr,
- event->obj.tsx->status_code,
- pjsip_get_status_text(event->obj.tsx->status_code)->ptr));
- } else {
- const pj_str_t *reason;
- if (event->src_type == PJSIP_EVENT_RX_MSG)
- reason = &event->src.rdata->msg->line.status.reason;
- else
- reason = pjsip_get_status_text(tsx->status_code);
- PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)",
- event->obj.tsx->method.name.slen,
- event->obj.tsx->method.name.ptr,
- event->obj.tsx->status_code,
- reason->slen, reason->ptr));
- }
-}
-
-
-static void ui_send_im_message()
-{
- char line[100];
- char text_buf[100];
- pj_str_t str;
- pj_str_t text_msg;
- int selection, rc;
- pjsip_tx_data *tdata;
-
- if (ui_input_url(&str, line, sizeof(line), &selection) == NULL)
- return;
-
-
- printf("Enter text to send (empty to cancel): "); fflush(stdout);
- fgets(text_buf, sizeof(text_buf), stdin);
- text_buf[strlen(text_buf)-1] = '\0';
- if (!*text_buf)
- return;
-
- text_msg = pj_str(text_buf);
-
- if (selection==0) {
- pjsip_method message_method;
- pj_str_t str_MESSAGE = { "MESSAGE", 7 };
-
- /* Send IM to current dialog. */
- if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
- printf("No current dialog or dialog state is not ESTABLISHED!\n");
- return;
- }
-
- pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE);
- tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 );
-
- if (tdata) {
- /* Create message body for the text. */
- pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body));
- body->content_type.type = pj_str("text");
- body->content_type.subtype = pj_str("plain");
- body->data = pj_pool_alloc(tdata->pool, text_msg.slen);
- pj_memcpy(body->data, text_msg.ptr, text_msg.slen);
- body->len = text_msg.slen;
- body->print_body = &pjsip_print_text_body;
-
- /* Assign body to message, and send the message! */
- tdata->msg->body = body;
- pjsip_dlg_send_msg( global.cur_dlg, tdata );
- }
-
- } else {
- /* Send IM to buddy list. */
- pjsip_method message;
- static pj_str_t MESSAGE = { "MESSAGE", 7 };
- pjsip_method_init_np(&message, &MESSAGE);
- tdata = pjsip_endpt_create_request(global.endpt, &message,
- &str,
- &global.real_contact,
- &str, &global.real_contact, NULL, -1,
- &text_msg);
- if (!tdata) {
- puts("Error creating request");
- return;
- }
- rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback);
- if (rc == 0) {
- printf("Sending IM message %d\n", global.im_counter);
- ++global.im_counter;
- } else {
- printf("Error: unable to send IM message!\n");
- }
- }
-}
-
-static void ui_send_options()
-{
- char line[100];
- pj_str_t str;
- int selection, rc;
- pjsip_tx_data *tdata;
- pjsip_method options;
-
- if (ui_input_url(&str, line, sizeof(line), &selection) == NULL)
- return;
-
- pjsip_method_set( &options, PJSIP_OPTIONS_METHOD );
-
- if (selection == 0) {
- /* Send OPTIONS to current dialog. */
- tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1);
- if (tdata)
- pjsip_dlg_send_msg( global.cur_dlg, tdata );
- } else {
- /* Send OPTIONS to arbitrary party. */
- tdata = pjsip_endpt_create_request( global.endpt, &options,
- &str,
- &global.local_uri, &str,
- &global.real_contact,
- NULL, -1, NULL);
- if (tdata) {
- rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL,
- &generic_request_callback);
- if (rc != 0)
- PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!"));
- }
- }
-}
-
-static void init_presence()
-{
- const pjsip_presence_cb pres_cb = {
- NULL,
- &pres_on_received_request,
- &pres_on_received_refresh,
- &pres_on_received_update,
- &pres_on_terminated
- };
-
- pjsip_presence_init(&pres_cb);
-}
-
-/* Subscribe presence information for all buddies. */
-static void subscribe_buddies_presence()
-{
- int i;
- for (i=0; i<global.buddy_cnt; ++i) {
- pjsip_presentity *pres;
- if (global.buddy_pres[i])
- continue;
- pres = pjsip_presence_create( global.endpt, &global.local_uri,
- &global.buddy[i], PRESENCE_TIMEOUT, (void*)i);
- if (pres) {
- pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info );
- pjsip_presence_subscribe( pres );
- }
- global.buddy_pres[i] = pres;
- }
-}
-
-/* Unsubscribe presence information for all buddies. */
-static void unsubscribe_buddies_presence()
-{
- int i;
- for (i=0; i<global.buddy_cnt; ++i) {
- pjsip_presentity *pres = global.buddy_pres[i];
- if (pres) {
- pjsip_presence_unsubscribe(pres);
- pjsip_presence_destroy(pres);
- global.buddy_pres[i] = NULL;
- }
- }
-}
-
-/* Unsubscribe presence. */
-static void unsubscribe_presence()
-{
- int i;
-
- unsubscribe_buddies_presence();
- for (i=0; i<global.pres_cnt; ++i) {
- pjsip_presentity *pres = global.pres[i];
- pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 0);
- pjsip_presence_destroy( pres );
- }
-}
-
-/* Advertise online status to subscribers. */
-static void update_im_status()
-{
- int i;
- for (i=0; i<global.pres_cnt; ++i) {
- pjsip_presentity *pres = global.pres[i];
- pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_ACTIVE,
- !global.hide_status);
- }
-}
-
-/*
- * Main program.
- */
-int main(int argc, char *argv[])
-{
- /* set to WORKER_COUNT+1 to avoid zero size warning
- * when threading is disabled. */
- pj_thread_t *thread[WORKER_COUNT+1];
- pj_caching_pool cp;
- int i;
-
- global.sip_port = 5060;
- global.auto_answer = -1;
- global.auto_hangup = -1;
- global.app_log_level = 3;
-
- pj_log_set_level(4);
- pj_log_set_log_func(&log_function);
-
- /* Init PJLIB */
- if (pj_init() != PJ_SUCCESS)
- return 1;
-
- /* Init caching pool. */
- pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
- global.pf = &cp.factory;
-
- /* Create memory pool for application. */
- global.pool = pj_pool_create(global.pf, "main", 1024, 0, NULL);
-
- /* Parse command line arguments. */
- if (parse_args(global.pool, argc, argv) != PJ_SUCCESS) {
- pj_caching_pool_destroy(&cp);
- return 1;
- }
-
- /* Init sockets */
- if (init_sockets() != 0) {
- pj_caching_pool_destroy(&cp);
- return 1;
- }
-
- /* Initialize stack. */
- if (init_stack() != PJ_SUCCESS) {
- pj_caching_pool_destroy(&cp);
- return 1;
- }
-
- /* Set callback to receive incoming IM */
- pjsip_messaging_set_incoming_callback( &on_incoming_im_msg );
-
- /* Set default worker count (can be zero) */
- global.worker_cnt = WORKER_COUNT;
-
- /* Create user worker thread(s), only when threading is enabled. */
- for (i=0; i<global.worker_cnt; ++i) {
- thread[i] = pj_thread_create( global.pool, "sip%p",
- &worker_thread,
- NULL, 0, NULL, 0);
- if (thread == NULL) {
- global.worker_quit_flag = 1;
- for (--i; i>=0; --i) {
- pj_thread_join(thread[i]);
- pj_thread_destroy(thread[i]);
- }
- pj_caching_pool_destroy(&cp);
- return 1;
- }
- }
-
- printf("Worker thread count: %d\n", global.worker_cnt);
-
- /* Perform registration, if required. */
- if (global.regc) {
- update_registration(global.regc, 1);
- }
-
- /* Initialize media manager. */
- global.mmgr = pj_med_mgr_create(global.pf);
-
- /* Init presence. */
- init_presence();
-
- /* Subscribe presence information of all buddies. */
- if (!global.no_presence)
- subscribe_buddies_presence();
-
- /* Initializatio completes, loop waiting for commands. */
- for (;!global.worker_quit_flag;) {
- pj_str_t str;
- char line[128];
-
-#if WORKER_COUNT==0
- /* If worker thread does not exist, main thread must poll for evetns.
- * But this won't work very well since main thread is blocked by
- * fgets(). So keep pressing the ENTER key to get the events!
- */
- pj_time_val timeout = { 0, 100 };
- pjsip_endpt_handle_events(global.endpt, &timeout);
- puts("Keep pressing ENTER key to get the events!");
-#endif
-
- printf("\nCurrent dialog: ");
- print_dialog(global.cur_dlg);
- puts("");
-
- keystroke_help();
-
- fgets(line, sizeof(line), stdin);
-
- switch (*line) {
- case 'm':
- puts("Make outgoing call");
- if (ui_input_url(&str, line, sizeof(line), &i) != NULL) {
- pjsip_dlg *dlg = make_call(&str);
- if (global.cur_dlg == NULL) {
- global.cur_dlg = dlg;
- }
- }
- break;
- case 'i':
- puts("Send Instant Messaging");
- ui_send_im_message();
- break;
- case 'o':
- puts("Send OPTIONS");
- ui_send_options();
- break;
- case 'a':
- if (global.cur_dlg) {
- unsigned code;
- pjsip_tx_data *tdata;
- struct dialog_data *dlg_data = global.cur_dlg->user_data;
-
- printf("Answer with status code (1xx-6xx): ");
- fflush(stdout);
- fgets(line, sizeof(line), stdin);
- str = pj_str(line);
- str.slen -= 1;
-
- code = pj_strtoul(&str);
- tdata = pjsip_dlg_answer(global.cur_dlg, code);
- if (tdata) {
- if (code/100 == 2) {
- tdata->msg->body = dlg_data->body;
- }
- pjsip_dlg_send_msg(global.cur_dlg, tdata);
-
- }
- } else {
- puts("No current dialog");
- }
- break;
- case 'h':
- if (global.cur_dlg) {
- pjsip_tx_data *tdata;
- tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE);
- if (tdata) {
- pjsip_dlg_send_msg(global.cur_dlg, tdata);
- }
- } else {
- puts("No current dialog");
- }
- break;
- case ']':
- if (global.cur_dlg) {
- global.cur_dlg = global.cur_dlg->next;
- if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
- global.cur_dlg = global.cur_dlg->next;
- }
- } else {
- puts("No current dialog");
- }
- break;
- case '[':
- if (global.cur_dlg) {
- global.cur_dlg = global.cur_dlg->prev;
- if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
- global.cur_dlg = global.cur_dlg->prev;
- }
- } else {
- puts("No current dialog");
- }
- break;
- case 'd':
- pjsip_endpt_dump(global.endpt, *(line+1)=='1');
- pjsip_ua_dump(global.user_agent);
- break;
- case 's':
- if (*(line+1) == 'u')
- subscribe_buddies_presence();
- break;
- case 'u':
- if (*(line+1) == 's')
- unsubscribe_presence();
- break;
- case 't':
- global.hide_status = !global.hide_status;
- update_im_status();
- break;
- case 'q':
- goto on_exit;
- case 'l':
- print_all_dialogs();
- break;
- }
- }
-
-on_exit:
- /* Unregister, if required. */
- if (global.regc) {
- update_registration(global.regc, 0);
- }
-
- /* Unsubscribe presence. */
- unsubscribe_presence();
-
- /* Allow one second to get all events. */
- if (1) {
- pj_time_val end_time;
-
- pj_gettimeofday(&end_time);
- end_time.sec++;
-
- PJ_LOG(3,(THIS_FILE, "Shutting down.."));
- for (;;) {
- pj_time_val timeout = { 0, 20 }, now;
- pjsip_endpt_handle_events (global.endpt, &timeout);
- pj_gettimeofday(&now);
- PJ_TIME_VAL_SUB(now, end_time);
- if (now.sec >= 1)
- break;
- }
- }
-
- global.worker_quit_flag = 1;
-
- pj_med_mgr_destroy(global.mmgr);
-
- /* Wait all threads to quit. */
- for (i=0; i<global.worker_cnt; ++i) {
- pj_thread_join(thread[i]);
- pj_thread_destroy(thread[i]);
- }
-
- /* Destroy endpoint. */
- pjsip_endpt_destroy(global.endpt);
-
- /* Destroy caching pool. */
- pj_caching_pool_destroy(&cp);
-
- /* Close log file, if any. */
- if (global.log_file)
- fclose(global.log_file);
-
- return 0;
-}
-
-/*
- * Register static modules to the endpoint.
- */
-pj_status_t register_static_modules( pj_size_t *count,
- pjsip_module **modules )
-{
- /* Reset count. */
- *count = 0;
-
- /* Register user agent module. */
- modules[(*count)++] = pjsip_ua_get_module();
- global.user_agent = modules[0]->mod_data;
- modules[(*count)++] = pjsip_messaging_get_module();
- modules[(*count)++] = pjsip_event_sub_get_module();
-
- return PJ_SUCCESS;
-}
+/* $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 <pjlib.h> +#include <pjsip_core.h> +#include <pjsip_ua.h> +#include <pjsip_simple.h> +#include <pjmedia.h> +#include <ctype.h> +#include <stdlib.h> +#include <pj/stun.h> + +#define START_PORT 5060 +#define MAX_BUDDIES 32 +#define THIS_FILE "main.c" +#define MAX_PRESENTITY 32 +#define PRESENCE_TIMEOUT 60 + +/* By default we'll have one worker thread, except when threading + * is disabled. + */ +#if PJ_HAS_THREADS +# define WORKER_COUNT 1 +#else +# define WORKER_COUNT 0 +#endif + +/* Global variable. */ +static struct +{ + /* Control. */ + pj_pool_factory *pf; + pjsip_endpoint *endpt; + pj_pool_t *pool; + pjsip_user_agent *user_agent; + int worker_cnt; + int worker_quit_flag; + + /* User info. */ + char user_id[64]; + pj_str_t local_uri; + pj_str_t contact; + pj_str_t real_contact; + + /* Dialog. */ + pjsip_dlg *cur_dlg; + + /* Authentication. */ + int cred_count; + pjsip_cred_info cred_info[4]; + + /* Media stack. */ + pj_bool_t null_audio; + pj_med_mgr_t *mmgr; + + /* Misc. */ + int app_log_level; + char *log_filename; + FILE *log_file; + + /* Proxy URLs */ + pj_str_t proxy; + pj_str_t outbound_proxy; + + /* UA auto options. */ + int auto_answer; /* -1 to disable. */ + int auto_hangup; /* -1 to disable */ + + /* Registration. */ + pj_str_t registrar_uri; + pjsip_regc *regc; + pj_int32_t reg_timeout; + pj_timer_entry regc_timer; + + /* STUN */ + pj_str_t stun_srv1; + int stun_port1; + pj_str_t stun_srv2; + int stun_port2; + + /* UDP sockets and their public address. */ + int sip_port; + pj_sock_t sip_sock; + pj_sockaddr_in sip_sock_name; + pj_sock_t rtp_sock; + pj_sockaddr_in rtp_sock_name; + pj_sock_t rtcp_sock; + pj_sockaddr_in rtcp_sock_name; + + /* SIMPLE */ + pj_bool_t hide_status; + pj_bool_t offer_x_ms_msg; + int im_counter; + int buddy_cnt; + pj_str_t buddy[MAX_BUDDIES]; + pj_bool_t buddy_status[MAX_BUDDIES]; + pj_bool_t no_presence; + pjsip_presentity *buddy_pres[MAX_BUDDIES]; + + int pres_cnt; + pjsip_presentity *pres[MAX_PRESENTITY]; + +} global; + +enum { AUTO_ANSWER, AUTO_HANGUP }; + +/* This is the data that will be 'attached' on per dialog basis. */ +struct dialog_data +{ + /* Media session. */ + pj_media_session_t *msession; + + /* x-ms-chat session. */ + pj_bool_t x_ms_msg_session; + + /* Cached SDP body, updated when media session changed. */ + pjsip_msg_body *body; + + /* Timer. */ + pj_bool_t has_auto_timer; + pj_timer_entry auto_timer; +}; + +/* + * These are the callbacks to be registered to dialog to receive notifications + * about various events in the dialog. + */ +static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, + pjsip_event *event ); +static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata, int retransmission); +static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata); +static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata); +static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_event *event); +static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event); +static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event); +static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event); +static void dlg_on_terminated (pjsip_dlg *dlg); +static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event); + +/* The callback structure that will be registered to UA layer. */ +struct pjsip_dlg_callback dlg_callback = { + &dlg_on_all_events, + &dlg_on_before_tx, + &dlg_on_tx_msg, + &dlg_on_rx_msg, + &dlg_on_incoming, + &dlg_on_calling, + &dlg_on_provisional, + &dlg_on_connecting, + &dlg_on_established, + &dlg_on_disconnected, + &dlg_on_terminated, + &dlg_on_mid_call_evt +}; + + +/* + * Auxiliary things are put in misc.c, so that this main.c file is more + * readable. + */ +#include "misc.c" + +static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_dlg *dlg = entry->user_data; + struct dialog_data *dlg_data = dlg->user_data; + + PJ_UNUSED_ARG(timer_heap) + + dlg_data->has_auto_timer = 0; + + if (entry->id == AUTO_ANSWER) { + pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200); + if (tdata) { + struct dialog_data *dlg_data = global.cur_dlg->user_data; + tdata->msg->body = dlg_data->body; + pjsip_dlg_send_msg(dlg, tdata); + } + } else { + pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500); + if (tdata) + pjsip_dlg_send_msg(dlg, tdata); + } +} + +static void update_registration(pjsip_regc *regc, int renew) +{ + pjsip_tx_data *tdata; + + PJ_LOG(3,(THIS_FILE, "Performing SIP registration...")); + + if (renew) { + tdata = pjsip_regc_register(regc, 1); + } else { + tdata = pjsip_regc_unregister(regc); + } + + pjsip_regc_send( regc, tdata ); +} + +static void regc_cb(struct pjsip_regc_cbparam *param) +{ + /* + * Print registration status. + */ + if (param->code < 0 || param->code >= 300) { + PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", + param->code, pjsip_get_status_text(param->code)->ptr)); + global.regc = NULL; + + } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { + PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), " + "will re-register in %d seconds", + param->code, + pjsip_get_status_text(param->code)->ptr, + param->expiration)); + + } else { + PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); + } +} + +static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata, + int *timeout) +{ + int state; + int i; + char url[PJSIP_MAX_URL_SIZE]; + int urllen; + + PJ_UNUSED_ARG(rdata) + + if (*timeout > 0) { + state = PJSIP_EVENT_SUB_STATE_ACTIVE; + if (*timeout > 300) + *timeout = 300; + } else { + state = PJSIP_EVENT_SUB_STATE_TERMINATED; + } + + urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1); + if (urllen < 1) { + pj_native_strcpy(url, "<unknown>"); + } else { + url[urllen] = '\0'; + } + PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s", + url, + (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated"))); + + for (i=0; i<global.pres_cnt; ++i) + if (global.pres[i] == pres) + break; + if (i == global.pres_cnt) + global.pres[global.pres_cnt++] = pres; + + pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info ); + pjsip_presence_notify(pres, state, !global.hide_status); + +} + +static void pres_on_received_refresh(pjsip_presentity *pres, pjsip_rx_data *rdata) +{ + pres_on_received_request(pres, rdata, &pres->sub->default_interval); +} + +/* This is called by presence framework when we receives presence update + * of a resource (buddy). + */ +static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open) +{ + int buddy_index = (int)pres->user_data; + + global.buddy_status[buddy_index] = is_open; + PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s", + global.buddy[buddy_index].ptr, + (is_open ? "Online" : "Offline"))); +} + +/* This is called when the subscription is terminated. */ +static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason) +{ + if (pres->sub->role == PJSIP_ROLE_UAC) { + int buddy_index = (int)pres->user_data; + PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)", + global.buddy[buddy_index].ptr, + reason->slen, reason->ptr)); + global.buddy_pres[buddy_index] = NULL; + global.buddy_status[buddy_index] = 0; + } else { + int i; + PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)", + reason->slen, reason->ptr)); + pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1); + for (i=0; i<global.pres_cnt; ++i) { + if (global.pres[i] == pres) { + int j; + global.pres[i] = NULL; + for (j=i+1; j<global.pres_cnt; ++j) + global.pres[j-1] = global.pres[j]; + global.pres_cnt--; + break; + } + } + } + pjsip_presence_destroy(pres); +} + + +/* Callback attached to SIP body to print the body to message buffer. */ +static int print_msg_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) +{ + pjsip_msg_body *body = msg_body; + return pjsdp_print ((pjsdp_session_desc*)body->data, buf, size); +} + +/* When media session has changed, call this function to update the cached body + * information in the dialog. + */ +static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg) +{ + struct dialog_data *dlg_data = dlg->user_data; + pjsdp_session_desc *sdp; + + sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg); + if (!sdp) { + dlg_data->body = NULL; + return NULL; + } + + /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new + * "m=" line in the SDP. + */ + if (dlg_data->x_ms_msg_session >= 0 && + dlg_data->x_ms_msg_session >= (int)sdp->media_count) + { + pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m)); + sdp->media[sdp->media_count] = m; + dlg_data->x_ms_msg_session = sdp->media_count++; + } + + /* + * For "x-ms-message" line, remove all attributes and connection line etc. + */ + if (dlg_data->x_ms_msg_session >= 0) { + pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session]; + if (m) { + m->desc.media = pj_str("x-ms-message"); + m->desc.port = 5060; + m->desc.transport = pj_str("sip"); + m->desc.fmt_count = 1; + m->desc.fmt[0] = pj_str("null"); + m->attr_count = 0; + m->conn = NULL; + } + } + + dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body)); + dlg_data->body->content_type.type = pj_str("application"); + dlg_data->body->content_type.subtype = pj_str("sdp"); + dlg_data->body->len = 0; /* ignored */ + dlg_data->body->print_body = &print_msg_body; + + dlg_data->body->data = sdp; + return dlg_data->body; +} + +/* This callback will be called on every occurence of events in dialogs */ +static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, + pjsip_event *event ) +{ + PJ_UNUSED_ARG(dlg_evt) + PJ_UNUSED_ARG(event) + + PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg)); +} + +/* This callback is called before each outgoing msg is sent (including + * retransmission). Application can override this notification if it wants + * to modify the message before transmission or if it wants to do something + * else for each transmission. + */ +static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata, int ret_cnt) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(tdata) + + if (ret_cnt > 0) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)", + dlg->obj_name, ret_cnt)); + } +} + +/* This callback is called after a message is sent. */ +static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(tdata) + + PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); +} + +/* This callback is called on receipt of incoming message. */ +static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(rdata) + PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); +} + +/* This callback is called after dialog has sent INVITE */ +static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(tdata) + + pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG && + tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && + tsx->method.id == PJSIP_INVITE_METHOD); + + PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name)); +} + +static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp) +{ + struct dialog_data *dlg_data = dlg->user_data; + pj_bool_t sdp_x_ms_msg_index = -1; + int i; + int mcnt; + const pj_media_stream_info *mi[PJSDP_MAX_MEDIA]; + int has_active; + pj_media_sock_info sock_info; + + /* Find "m=x-ms-message" line in the SDP. */ + for (i=0; i<(int)sdp->media_count; ++i) { + if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0) + sdp_x_ms_msg_index = i; + } + + /* + * Create media session. + */ + pj_memset(&sock_info, 0, sizeof(sock_info)); + sock_info.rtp_sock = global.rtp_sock; + sock_info.rtcp_sock = global.rtcp_sock; + pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); + + dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info); + + /* A session will always be created, unless there is memory + * alloc problem. + */ + pj_assert(dlg_data->msession); + + /* See if we can take the offer by checking that we have at least + * one media stream active. + */ + mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi); + for (i=0, has_active=0; i<mcnt; ++i) { + if (mi[i]->fmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) { + has_active = 1; + break; + } + } + + if (!has_active && sdp_x_ms_msg_index==-1) { + pjsip_tx_data *tdata; + + /* Unable to accept remote's SDP. + * Answer with 488 (Not Acceptable Here) + */ + /* Create 488 response. */ + tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE); + + /* Send response. */ + if (tdata) + pjsip_dlg_send_msg(dlg, tdata); + return; + } + + dlg_data->x_ms_msg_session = sdp_x_ms_msg_index; + + /* Create msg body to be used later in 2xx/response */ + create_msg_body(dlg, 0); + +} + +/* This callback is called after an INVITE is received. */ +static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + struct dialog_data *dlg_data; + pjsip_msg *msg; + pjsip_tx_data *tdata; + char buf[128]; + int len; + + PJ_UNUSED_ARG(tsx) + + pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG && + rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && + tsx->method.id == PJSIP_INVITE_METHOD); + + /* + * Notify user! + */ + PJ_LOG(3, (THIS_FILE, "")); + PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name)); + PJ_LOG(3, (THIS_FILE, "")); + len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, + (pjsip_name_addr*)dlg->remote.info->uri, + buf, sizeof(buf)-1); + if (len > 0) { + buf[len] = '\0'; + PJ_LOG(3,(THIS_FILE, "From:\t%s", buf)); + } + len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, + (pjsip_name_addr*)dlg->local.info->uri, + buf, sizeof(buf)-1); + if (len > 0) { + buf[len] = '\0'; + PJ_LOG(3,(THIS_FILE, "To:\t%s", buf)); + } + PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name)); + PJ_LOG(3, (THIS_FILE, "")); + + /* + * Process incoming dialog. + */ + + dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); + dlg->user_data = dlg_data; + + /* Update contact. */ + pjsip_dlg_set_contact(dlg, &global.contact); + + /* Initialize credentials. */ + pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); + + /* Create media session if the request has "application/sdp" body. */ + msg = rdata->msg; + if (msg->body && + pj_stricmp2(&msg->body->content_type.type, "application")==0 && + pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0) + { + pjsdp_session_desc *sdp; + + /* Parse SDP body, and instantiate media session based on remote's SDP. + * Then create our SDP body from the session. + */ + sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool); + if (!sdp) + goto send_answer; + + create_session_from_sdp(dlg, sdp); + + } else if (msg->body) { + /* The request has a message body other than "application/sdp" */ + pjsip_accept_hdr *accept; + + /* Create response. */ + tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + + /* Add "Accept" header. */ + accept = pjsip_accept_hdr_create(tdata->pool); + accept->values[0] = pj_str("application/sdp"); + accept->count = 1; + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept); + + /* Send response. */ + pjsip_dlg_send_msg(dlg, tdata); + return; + + } else { + /* The request has no message body. We can take this request, but + * no media session will be activated. + */ + /* Nothing to do here. */ + } + +send_answer: + /* Immediately answer with 100 (or 180? */ + tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING ); + pjsip_dlg_send_msg(dlg, tdata); + + /* Set current dialog to this dialog if we don't currently have + * current dialog. + */ + if (global.cur_dlg == NULL) { + global.cur_dlg = dlg; + } + + /* Auto-answer if option is specified. */ + if (global.auto_answer >= 0) { + pj_time_val delay = { 0, 0}; + struct dialog_data *dlg_data = dlg->user_data; + + PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds", + global.auto_answer)); + + delay.sec = global.auto_answer; + dlg_data->auto_timer.user_data = dlg; + dlg_data->auto_timer.id = AUTO_ANSWER; + dlg_data->auto_timer.cb = &dlg_auto_timer_callback; + dlg_data->has_auto_timer = 1; + pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); + } +} + +/* This callback is called when dialog has sent/received a provisional response + * to INVITE. + */ +static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_event *event) +{ + const char *action; + + pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.tdata->msg->line.status.code/100 == 1 && + tsx->method.id == PJSIP_INVITE_METHOD) + || + (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.rdata->msg->line.status.code/100 == 1 && + tsx->method.id == PJSIP_INVITE_METHOD)); + + if (event->src_type == PJSIP_EVENT_TX_MSG) + action = "Sending"; + else + action = "Received"; + + PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)", + dlg->obj_name, action, tsx->status_code, + pjsip_get_status_text(tsx->status_code)->ptr)); +} + +/* This callback is called when 200 response to INVITE is sent/received. */ +static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event) +{ + struct dialog_data *dlg_data = dlg->user_data; + const char *action; + + pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.tdata->msg->line.status.code/100 == 2) + || + (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.rdata->msg->line.status.code/100 == 2)); + + if (event->src_type == PJSIP_EVENT_RX_MSG) + action = "Received"; + else + action = "Sending"; + + PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action)); + + if (event->src_type == PJSIP_EVENT_RX_MSG) { + /* On receipt of 2xx response, negotiate our media capability + * and start media. + */ + pjsip_msg *msg = event->src.rdata->msg; + pjsip_msg_body *body; + pjsdp_session_desc *sdp; + + /* Get SDP from message. */ + + /* Ignore if no SDP body is present. */ + body = msg->body; + if (!body) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!", + dlg->obj_name)); + return; + } + + if (pj_stricmp2(&body->content_type.type, "application") != 0 && + pj_stricmp2(&body->content_type.subtype, "sdp") != 0) + { + PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!", + dlg->obj_name)); + return; + } + + /* Got what seems to be a SDP content. Parse it. */ + sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool); + if (!sdp) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!", + dlg->obj_name)); + return; + } + + /* Negotiate media session with remote's media capability. */ + if (pj_media_session_update (dlg_data->msession, sdp) != 0) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!", + dlg->obj_name)); + return; + } + + /* Update the saved SDP body because media session has changed. + * Also set ack flag to '1', because we only want to send one format/ + * codec for each media streams. + */ + create_msg_body(dlg, 1); + + /* Activate media. */ + pj_media_session_activate (dlg_data->msession); + + } else { + pjsip_msg *msg = event->src.tdata->msg; + + if (msg->body) { + /* On transmission of 2xx response, start media session. */ + pj_media_session_activate (dlg_data->msession); + } + } + +} + +/* This callback is called when ACK to initial INVITE is sent/received. */ +static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event) +{ + const char *action; + + pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->type == PJSIP_REQUEST_MSG && + event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) + || + (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_REQUEST_MSG && + event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD)); + + if (event->src_type == PJSIP_EVENT_RX_MSG) + action = "Received"; + else + action = "Sending"; + + PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED", + dlg->obj_name, action)); + + /* Attach SDP body for outgoing ACK. */ + if (event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) + { + struct dialog_data *dlg_data = dlg->user_data; + event->src.tdata->msg->body = dlg_data->body; + } + + /* Auto-hangup if option is specified. */ + if (global.auto_hangup >= 0) { + pj_time_val delay = { 0, 0}; + struct dialog_data *dlg_data = dlg->user_data; + + PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds", + global.auto_hangup)); + + delay.sec = global.auto_hangup; + dlg_data->auto_timer.user_data = dlg; + dlg_data->auto_timer.id = AUTO_HANGUP; + dlg_data->auto_timer.cb = &dlg_auto_timer_callback; + dlg_data->has_auto_timer = 1; + pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); + } +} + + +/* This callback is called when dialog is disconnected (because of final + * response, BYE, or timer). + */ +static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event) +{ + struct dialog_data *dlg_data = dlg->user_data; + int status_code; + const pj_str_t *reason; + + PJ_UNUSED_ARG(event) + + /* Cancel auto-answer/auto-hangup timer. */ + if (dlg_data->has_auto_timer) { + pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer); + dlg_data->has_auto_timer = 0; + } + + if (dlg->invite_tsx) + status_code = dlg->invite_tsx->status_code; + else + status_code = 200; + + if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) { + if (event->src_type == PJSIP_EVENT_RX_MSG) + reason = &event->src.rdata->msg->line.status.reason; + else if (event->src_type == PJSIP_EVENT_TX_MSG) + reason = &event->src.tdata->msg->line.status.reason; + else + reason = pjsip_get_status_text(event->obj.tsx->status_code); + } else { + reason = &event->obj.tsx->method.name; + } + + PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)", + dlg->obj_name, status_code, + reason->slen, reason->ptr)); + + if (dlg_data->msession) { + pj_media_session_destroy (dlg_data->msession); + dlg_data->msession = NULL; + } +} + +/* This callback is called when dialog is about to be destroyed. */ +static void dlg_on_terminated(pjsip_dlg *dlg) +{ + PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name)); + + /* If current dialog is equal to this dialog, update it. */ + if (global.cur_dlg == dlg) { + global.cur_dlg = global.cur_dlg->next; + if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { + global.cur_dlg = NULL; + } + } +} + +/* This callback is called for any requests when dialog is established. */ +static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event) +{ + pjsip_transaction *tsx = event->obj.tsx; + + if (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_REQUEST_MSG) + { + if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) { + /* Re-invitation. */ + pjsip_tx_data *tdata; + + PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)", + dlg->obj_name)); + tdata = pjsip_dlg_answer(dlg, 200); + if (tdata) { + struct dialog_data *dlg_data = dlg->user_data; + tdata->msg->body = dlg_data->body; + pjsip_dlg_send_msg(dlg, tdata); + } + } else { + /* Don't worry, endpoint will answer with 500 or whetever. */ + } + + } else if (tsx->status_code/100 == 2) { + PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)", + dlg->obj_name, + tsx->method.name.slen, tsx->method.name.ptr, + tsx->status_code, + pjsip_get_status_text(tsx->status_code)->ptr)); + + + } else if (tsx->status_code >= 300) { + pj_bool_t report_failure = PJ_TRUE; + + /* Check for authentication failures. */ + if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + tdata = pjsip_auth_reinit_req( global.endpt, + dlg->pool, &dlg->auth_sess, + dlg->cred_count, dlg->cred_info, + tsx->last_tx, event->src.rdata ); + if (tdata) { + int rc; + rc = pjsip_dlg_send_msg( dlg, tdata); + report_failure = (rc != 0); + } + } + if (report_failure) { + const pj_str_t *reason; + if (event->src_type == PJSIP_EVENT_RX_MSG) { + reason = &event->src.rdata->msg->line.status.reason; + } else { + reason = pjsip_get_status_text(tsx->status_code); + } + PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)", + dlg->obj_name, tsx->status_code, + reason->slen, reason->ptr)); + } + } +} + +/* Initialize sockets and optionally get the public address via STUN. */ +static pj_status_t init_sockets() +{ + enum { + RTP_START_PORT = 4000, + RTP_RANDOM_START = 2, + RTP_RETRY = 10 + }; + enum { + SIP_SOCK, + RTP_SOCK, + RTCP_SOCK, + }; + int i; + int rtp_port; + pj_sock_t sock[3]; + pj_sockaddr_in mapped_addr[3]; + + for (i=0; i<3; ++i) + sock[i] = PJ_INVALID_SOCKET; + + /* Create and bind SIP UDP socket. */ + sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); + if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) { + PJ_LOG(2,(THIS_FILE, "Unable to create socket")); + goto on_error; + } + if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) { + PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port")); + goto on_error; + } + + /* Initialize start of RTP port to try. */ + rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2; + + /* Loop retry to bind RTP and RTCP sockets. */ + for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) { + + /* Create and bind RTP socket. */ + sock[RTP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); + if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) + goto on_error; + if (pj_sock_bind_in(sock[RTP_SOCK], 0, (pj_uint16_t)rtp_port) != 0) { + pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; + continue; + } + + /* Create and bind RTCP socket. */ + sock[RTCP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); + if (sock[RTCP_SOCK] == PJ_INVALID_SOCKET) + goto on_error; + if (pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)) != 0) { + pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; + pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + continue; + } + + /* + * If we're configured to use STUN, then find out the mapped address, + * and make sure that the mapped RTCP port is adjacent with the RTP. + */ + if (global.stun_port1 == 0) { + pj_str_t hostname; + pj_sockaddr_in addr; + + /* Get local IP address. */ + char hostname_buf[PJ_MAX_HOSTNAME]; + if (gethostname(hostname_buf, sizeof(hostname_buf))) + goto on_error; + hostname = pj_str(hostname_buf); + + pj_memset( &addr, 0, sizeof(addr)); + addr.sin_family = PJ_AF_INET; + if (pj_sockaddr_set_str_addr( &addr, &hostname) != PJ_SUCCESS) + goto on_error; + + for (i=0; i<3; ++i) + pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); + + mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)global.sip_port); + mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port); + mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1)); + break; + } else { + pj_status_t rc; + rc = pj_stun_get_mapped_addr( global.pf, 3, sock, + &global.stun_srv1, global.stun_port1, + &global.stun_srv2, global.stun_port2, + mapped_addr); + if (rc != 0) { + PJ_LOG(3,(THIS_FILE, "Error: %s", pj_stun_get_err_msg(rc))); + goto on_error; + } + + if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1) + break; + + pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; + pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + } + } + + if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) { + PJ_LOG(2,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination")); + goto on_error; + } + + global.sip_sock = sock[SIP_SOCK]; + pj_memcpy(&global.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in)); + global.rtp_sock = sock[RTP_SOCK]; + pj_memcpy(&global.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in)); + global.rtcp_sock = sock[RTCP_SOCK]; + pj_memcpy(&global.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in)); + + PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", + pj_inet_ntoa(global.sip_sock_name.sin_addr), + pj_ntohs(global.sip_sock_name.sin_port))); + PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", + pj_inet_ntoa(global.rtp_sock_name.sin_addr), + pj_ntohs(global.rtp_sock_name.sin_port))); + PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d", + pj_inet_ntoa(global.rtcp_sock_name.sin_addr), + pj_ntohs(global.rtcp_sock_name.sin_port))); + return 0; + +on_error: + for (i=0; i<3; ++i) { + if (sock[i] != PJ_INVALID_SOCKET) + pj_sock_close(sock[i]); + } + return -1; +} + +static void log_function(int level, const char *buffer, int len) +{ + /* Write to both stdout and file. */ + if (level <= global.app_log_level) + pj_log_to_stdout(level, buffer, len); + if (global.log_file) { + fwrite(buffer, len, 1, global.log_file); + fflush(global.log_file); + } +} + +/* Initialize stack. */ +static pj_status_t init_stack() +{ + pj_status_t status; + pj_sockaddr_in bind_addr; + pj_sockaddr_in bind_name; + const char *local_addr; + static char local_uri[128]; + + /* Optionally set logging file. */ + if (global.log_filename) { + global.log_file = fopen(global.log_filename, "wt"); + } + + /* Initialize endpoint. This will also call initialization to all the + * modules. + */ + global.endpt = pjsip_endpt_create(global.pf); + if (global.endpt == NULL) { + return -1; + } + + /* Set dialog callback. */ + pjsip_ua_set_dialog_callback(global.user_agent, &dlg_callback); + + /* Init listener's bound address and port. */ + pj_sockaddr_init2(&bind_addr, "0.0.0.0", global.sip_port); + pj_sockaddr_init(&bind_name, pj_gethostname(), global.sip_port); + + /* Add UDP transport listener. */ + status = pjsip_endpt_create_udp_listener( global.endpt, global.sip_sock, + &global.sip_sock_name); + if (status != 0) + return -1; + + local_addr = pj_inet_ntoa(global.sip_sock_name.sin_addr); + +#if PJ_HAS_TCP + /* Add TCP transport listener. */ + status = pjsip_endpt_create_listener( global.endpt, PJSIP_TRANSPORT_TCP, + &bind_addr, &bind_name); + if (status != 0) + return -1; +#endif + + /* Determine user_id to be put in Contact */ + if (global.local_uri.slen) { + pj_pool_t *pool = pj_pool_create(global.pf, "parser", 1024, 0, NULL); + pjsip_uri *uri; + + uri = pjsip_parse_uri(pool, global.local_uri.ptr, global.local_uri.slen, 0); + if (uri) { + if (pj_stricmp2(pjsip_uri_get_scheme(uri), "sip")==0) { + pjsip_url *url = (pjsip_url*)pjsip_uri_get_uri(uri); + if (url->user.slen) + strncpy(global.user_id, url->user.ptr, url->user.slen); + } + } + pj_pool_release(pool); + } + + if (global.user_id[0]=='\0') { + pj_native_strcpy(global.user_id, "user"); + } + + /* build contact */ + global.real_contact.ptr = local_uri; + global.real_contact.slen = + sprintf(local_uri, "<sip:%s@%s:%d>", global.user_id, local_addr, global.sip_port); + + if (global.contact.slen == 0) + global.contact = global.real_contact; + + /* initialize local_uri with contact if it's not specified in cmdline */ + if (global.local_uri.slen == 0) + global.local_uri = global.contact; + + /* Init proxy. */ + if (global.proxy.slen || global.outbound_proxy.slen) { + int count = 0; + pj_str_t proxy_url[2]; + + if (global.outbound_proxy.slen) { + proxy_url[count++] = global.outbound_proxy; + } + if (global.proxy.slen) { + proxy_url[count++] = global.proxy; + } + + if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) { + PJ_LOG(2,(THIS_FILE, "Error setting proxy address!")); + return -1; + } + } + + /* initialize SIP registration if registrar is configured */ + if (global.registrar_uri.slen) { + global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb); + pjsip_regc_init( global.regc, &global.registrar_uri, + &global.local_uri, + &global.local_uri, + 1, &global.contact, + global.reg_timeout); + pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info ); + } + + return PJ_SUCCESS; +} + +/* Worker thread function, only used when threading is enabled. */ +static void *PJ_THREAD_FUNC worker_thread(void *unused) +{ + PJ_UNUSED_ARG(unused) + + while (!global.worker_quit_flag) { + pj_time_val timeout = { 0, 10 }; + pjsip_endpt_handle_events (global.endpt, &timeout); + } + return NULL; +} + + +/* Make call to the specified URI. */ +static pjsip_dlg *make_call(pj_str_t *remote_uri) +{ + pjsip_dlg *dlg; + pj_str_t local = global.contact; + pj_str_t remote = *remote_uri; + struct dialog_data *dlg_data; + pjsip_tx_data *tdata; + pj_media_sock_info sock_info; + + /* Create new dialog instance. */ + dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC); + + /* Attach our own user data. */ + dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); + dlg->user_data = dlg_data; + + /* Create media session. */ + pj_memset(&sock_info, 0, sizeof(sock_info)); + sock_info.rtp_sock = global.rtp_sock; + sock_info.rtcp_sock = global.rtcp_sock; + pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); + + dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info); + dlg_data->x_ms_msg_session = -1; + + if (global.offer_x_ms_msg) { + const pj_media_stream_info *minfo[32]; + unsigned cnt; + + cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo); + if (cnt > 0) + dlg_data->x_ms_msg_session = cnt; + } + + /* Initialize dialog with local and remote URI. */ + if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) { + pjsip_ua_destroy_dialog(dlg); + return NULL; + } + + /* Initialize credentials. */ + pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); + + /* Send INVITE! */ + tdata = pjsip_dlg_invite(dlg); + tdata->msg->body = create_msg_body (dlg, 0); + + if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) { + pjsip_ua_destroy_dialog(dlg); + return NULL; + } + + return dlg; +} + +/* + * Callback to receive incoming IM message. + */ +static int on_incoming_im_msg(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg; + pjsip_msg_body *body = msg->body; + int len; + char to[128], from[128]; + + + len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + rdata->from->uri, from, sizeof(from)); + if (len > 0) from[len] = '\0'; + else pj_native_strcpy(from, "<URL too long..>"); + + len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + rdata->to->uri, to, sizeof(to)); + if (len > 0) to[len] = '\0'; + else pj_native_strcpy(to, "<URL too long..>"); + + PJ_LOG(3,(THIS_FILE, "Incoming instant message:")); + + printf("----- BEGIN INSTANT MESSAGE ----->\n"); + printf("From:\t%s\n", from); + printf("To:\t%s\n", to); + printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : "")); + printf("<------ END INSTANT MESSAGE ------\n"); + + fflush(stdout); + + /* Must answer with final response. */ + return 200; +} + +/* + * Input URL. + */ +static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection) +{ + int i; + + *selection = -1; + + printf("\nBuddy list:\n"); + printf("---------------------------------------\n"); + for (i=0; i<global.buddy_cnt; ++i) { + printf(" %d\t%s <%s>\n", i+1, global.buddy[i].ptr, + (global.buddy_status[i]?"Online":"Offline")); + } + printf("-------------------------------------\n"); + + printf("Choices\n" + "\t0 For current dialog.\n" + "\t[1-%02d] Select from buddy list\n" + "\tURL An URL\n" + , global.buddy_cnt); + printf("Input: "); + + fflush(stdout); + fgets(buf, len, stdin); + buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */ + + while (isspace(*buf)) ++buf; + + if (!*buf || *buf=='\n' || *buf=='\r') + return NULL; + + i = atoi(buf); + + if (i == 0) { + if (isdigit(*buf)) { + *selection = 0; + *out = pj_str("0"); + return out; + } else { + if (verify_sip_url(buf) != 0) { + puts("Invalid URL specified!"); + return NULL; + } + *out = pj_str(buf); + return out; + } + } else if (i > global.buddy_cnt || i < 0) { + printf("Error: invalid selection!\n"); + return NULL; + } else { + *out = global.buddy[i-1]; + *selection = i; + return out; + } +} + + +static void generic_request_callback( void *token, pjsip_event *event ) +{ + pjsip_transaction *tsx = event->obj.tsx; + + PJ_UNUSED_ARG(token) + + if (tsx->status_code/100 == 2) { + PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)", + event->obj.tsx->method.name.slen, + event->obj.tsx->method.name.ptr, + tsx->status_code, + pjsip_get_status_text(tsx->status_code)->ptr)); + } else if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + tdata = pjsip_auth_reinit_req( global.endpt, + global.pool, NULL, global.cred_count, global.cred_info, + tsx->last_tx, event->src.rdata); + if (tdata) { + int rc; + pjsip_cseq_hdr *cseq; + cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); + cseq->cseq++; + rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, + &generic_request_callback); + if (rc == 0) + return; + } + PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)", + event->obj.tsx->method.name.slen, + event->obj.tsx->method.name.ptr, + event->obj.tsx->status_code, + pjsip_get_status_text(event->obj.tsx->status_code)->ptr)); + } else { + const pj_str_t *reason; + if (event->src_type == PJSIP_EVENT_RX_MSG) + reason = &event->src.rdata->msg->line.status.reason; + else + reason = pjsip_get_status_text(tsx->status_code); + PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)", + event->obj.tsx->method.name.slen, + event->obj.tsx->method.name.ptr, + event->obj.tsx->status_code, + reason->slen, reason->ptr)); + } +} + + +static void ui_send_im_message() +{ + char line[100]; + char text_buf[100]; + pj_str_t str; + pj_str_t text_msg; + int selection, rc; + pjsip_tx_data *tdata; + + if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) + return; + + + printf("Enter text to send (empty to cancel): "); fflush(stdout); + fgets(text_buf, sizeof(text_buf), stdin); + text_buf[strlen(text_buf)-1] = '\0'; + if (!*text_buf) + return; + + text_msg = pj_str(text_buf); + + if (selection==0) { + pjsip_method message_method; + pj_str_t str_MESSAGE = { "MESSAGE", 7 }; + + /* Send IM to current dialog. */ + if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { + printf("No current dialog or dialog state is not ESTABLISHED!\n"); + return; + } + + pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE); + tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 ); + + if (tdata) { + /* Create message body for the text. */ + pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body)); + body->content_type.type = pj_str("text"); + body->content_type.subtype = pj_str("plain"); + body->data = pj_pool_alloc(tdata->pool, text_msg.slen); + pj_memcpy(body->data, text_msg.ptr, text_msg.slen); + body->len = text_msg.slen; + body->print_body = &pjsip_print_text_body; + + /* Assign body to message, and send the message! */ + tdata->msg->body = body; + pjsip_dlg_send_msg( global.cur_dlg, tdata ); + } + + } else { + /* Send IM to buddy list. */ + pjsip_method message; + static pj_str_t MESSAGE = { "MESSAGE", 7 }; + pjsip_method_init_np(&message, &MESSAGE); + tdata = pjsip_endpt_create_request(global.endpt, &message, + &str, + &global.real_contact, + &str, &global.real_contact, NULL, -1, + &text_msg); + if (!tdata) { + puts("Error creating request"); + return; + } + rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback); + if (rc == 0) { + printf("Sending IM message %d\n", global.im_counter); + ++global.im_counter; + } else { + printf("Error: unable to send IM message!\n"); + } + } +} + +static void ui_send_options() +{ + char line[100]; + pj_str_t str; + int selection, rc; + pjsip_tx_data *tdata; + pjsip_method options; + + if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) + return; + + pjsip_method_set( &options, PJSIP_OPTIONS_METHOD ); + + if (selection == 0) { + /* Send OPTIONS to current dialog. */ + tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1); + if (tdata) + pjsip_dlg_send_msg( global.cur_dlg, tdata ); + } else { + /* Send OPTIONS to arbitrary party. */ + tdata = pjsip_endpt_create_request( global.endpt, &options, + &str, + &global.local_uri, &str, + &global.real_contact, + NULL, -1, NULL); + if (tdata) { + rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, + &generic_request_callback); + if (rc != 0) + PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!")); + } + } +} + +static void init_presence() +{ + const pjsip_presence_cb pres_cb = { + NULL, + &pres_on_received_request, + &pres_on_received_refresh, + &pres_on_received_update, + &pres_on_terminated + }; + + pjsip_presence_init(&pres_cb); +} + +/* Subscribe presence information for all buddies. */ +static void subscribe_buddies_presence() +{ + int i; + for (i=0; i<global.buddy_cnt; ++i) { + pjsip_presentity *pres; + if (global.buddy_pres[i]) + continue; + pres = pjsip_presence_create( global.endpt, &global.local_uri, + &global.buddy[i], PRESENCE_TIMEOUT, (void*)i); + if (pres) { + pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info ); + pjsip_presence_subscribe( pres ); + } + global.buddy_pres[i] = pres; + } +} + +/* Unsubscribe presence information for all buddies. */ +static void unsubscribe_buddies_presence() +{ + int i; + for (i=0; i<global.buddy_cnt; ++i) { + pjsip_presentity *pres = global.buddy_pres[i]; + if (pres) { + pjsip_presence_unsubscribe(pres); + pjsip_presence_destroy(pres); + global.buddy_pres[i] = NULL; + } + } +} + +/* Unsubscribe presence. */ +static void unsubscribe_presence() +{ + int i; + + unsubscribe_buddies_presence(); + for (i=0; i<global.pres_cnt; ++i) { + pjsip_presentity *pres = global.pres[i]; + pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 0); + pjsip_presence_destroy( pres ); + } +} + +/* Advertise online status to subscribers. */ +static void update_im_status() +{ + int i; + for (i=0; i<global.pres_cnt; ++i) { + pjsip_presentity *pres = global.pres[i]; + pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_ACTIVE, + !global.hide_status); + } +} + +/* + * Main program. + */ +int main(int argc, char *argv[]) +{ + /* set to WORKER_COUNT+1 to avoid zero size warning + * when threading is disabled. */ + pj_thread_t *thread[WORKER_COUNT+1]; + pj_caching_pool cp; + int i; + + global.sip_port = 5060; + global.auto_answer = -1; + global.auto_hangup = -1; + global.app_log_level = 3; + + pj_log_set_level(4); + pj_log_set_log_func(&log_function); + + /* Init PJLIB */ + if (pj_init() != PJ_SUCCESS) + return 1; + + /* Init caching pool. */ + pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); + global.pf = &cp.factory; + + /* Create memory pool for application. */ + global.pool = pj_pool_create(global.pf, "main", 1024, 0, NULL); + + /* Parse command line arguments. */ + if (parse_args(global.pool, argc, argv) != PJ_SUCCESS) { + pj_caching_pool_destroy(&cp); + return 1; + } + + /* Init sockets */ + if (init_sockets() != 0) { + pj_caching_pool_destroy(&cp); + return 1; + } + + /* Initialize stack. */ + if (init_stack() != PJ_SUCCESS) { + pj_caching_pool_destroy(&cp); + return 1; + } + + /* Set callback to receive incoming IM */ + pjsip_messaging_set_incoming_callback( &on_incoming_im_msg ); + + /* Set default worker count (can be zero) */ + global.worker_cnt = WORKER_COUNT; + + /* Create user worker thread(s), only when threading is enabled. */ + for (i=0; i<global.worker_cnt; ++i) { + thread[i] = pj_thread_create( global.pool, "sip%p", + &worker_thread, + NULL, 0, NULL, 0); + if (thread == NULL) { + global.worker_quit_flag = 1; + for (--i; i>=0; --i) { + pj_thread_join(thread[i]); + pj_thread_destroy(thread[i]); + } + pj_caching_pool_destroy(&cp); + return 1; + } + } + + printf("Worker thread count: %d\n", global.worker_cnt); + + /* Perform registration, if required. */ + if (global.regc) { + update_registration(global.regc, 1); + } + + /* Initialize media manager. */ + global.mmgr = pj_med_mgr_create(global.pf); + + /* Init presence. */ + init_presence(); + + /* Subscribe presence information of all buddies. */ + if (!global.no_presence) + subscribe_buddies_presence(); + + /* Initializatio completes, loop waiting for commands. */ + for (;!global.worker_quit_flag;) { + pj_str_t str; + char line[128]; + +#if WORKER_COUNT==0 + /* If worker thread does not exist, main thread must poll for evetns. + * But this won't work very well since main thread is blocked by + * fgets(). So keep pressing the ENTER key to get the events! + */ + pj_time_val timeout = { 0, 100 }; + pjsip_endpt_handle_events(global.endpt, &timeout); + puts("Keep pressing ENTER key to get the events!"); +#endif + + printf("\nCurrent dialog: "); + print_dialog(global.cur_dlg); + puts(""); + + keystroke_help(); + + fgets(line, sizeof(line), stdin); + + switch (*line) { + case 'm': + puts("Make outgoing call"); + if (ui_input_url(&str, line, sizeof(line), &i) != NULL) { + pjsip_dlg *dlg = make_call(&str); + if (global.cur_dlg == NULL) { + global.cur_dlg = dlg; + } + } + break; + case 'i': + puts("Send Instant Messaging"); + ui_send_im_message(); + break; + case 'o': + puts("Send OPTIONS"); + ui_send_options(); + break; + case 'a': + if (global.cur_dlg) { + unsigned code; + pjsip_tx_data *tdata; + struct dialog_data *dlg_data = global.cur_dlg->user_data; + + printf("Answer with status code (1xx-6xx): "); + fflush(stdout); + fgets(line, sizeof(line), stdin); + str = pj_str(line); + str.slen -= 1; + + code = pj_strtoul(&str); + tdata = pjsip_dlg_answer(global.cur_dlg, code); + if (tdata) { + if (code/100 == 2) { + tdata->msg->body = dlg_data->body; + } + pjsip_dlg_send_msg(global.cur_dlg, tdata); + + } + } else { + puts("No current dialog"); + } + break; + case 'h': + if (global.cur_dlg) { + pjsip_tx_data *tdata; + tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE); + if (tdata) { + pjsip_dlg_send_msg(global.cur_dlg, tdata); + } + } else { + puts("No current dialog"); + } + break; + case ']': + if (global.cur_dlg) { + global.cur_dlg = global.cur_dlg->next; + if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { + global.cur_dlg = global.cur_dlg->next; + } + } else { + puts("No current dialog"); + } + break; + case '[': + if (global.cur_dlg) { + global.cur_dlg = global.cur_dlg->prev; + if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { + global.cur_dlg = global.cur_dlg->prev; + } + } else { + puts("No current dialog"); + } + break; + case 'd': + pjsip_endpt_dump(global.endpt, *(line+1)=='1'); + pjsip_ua_dump(global.user_agent); + break; + case 's': + if (*(line+1) == 'u') + subscribe_buddies_presence(); + break; + case 'u': + if (*(line+1) == 's') + unsubscribe_presence(); + break; + case 't': + global.hide_status = !global.hide_status; + update_im_status(); + break; + case 'q': + goto on_exit; + case 'l': + print_all_dialogs(); + break; + } + } + +on_exit: + /* Unregister, if required. */ + if (global.regc) { + update_registration(global.regc, 0); + } + + /* Unsubscribe presence. */ + unsubscribe_presence(); + + /* Allow one second to get all events. */ + if (1) { + pj_time_val end_time; + + pj_gettimeofday(&end_time); + end_time.sec++; + + PJ_LOG(3,(THIS_FILE, "Shutting down..")); + for (;;) { + pj_time_val timeout = { 0, 20 }, now; + pjsip_endpt_handle_events (global.endpt, &timeout); + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, end_time); + if (now.sec >= 1) + break; + } + } + + global.worker_quit_flag = 1; + + pj_med_mgr_destroy(global.mmgr); + + /* Wait all threads to quit. */ + for (i=0; i<global.worker_cnt; ++i) { + pj_thread_join(thread[i]); + pj_thread_destroy(thread[i]); + } + + /* Destroy endpoint. */ + pjsip_endpt_destroy(global.endpt); + + /* Destroy caching pool. */ + pj_caching_pool_destroy(&cp); + + /* Close log file, if any. */ + if (global.log_file) + fclose(global.log_file); + + return 0; +} + +/* + * Register static modules to the endpoint. + */ +pj_status_t register_static_modules( pj_size_t *count, + pjsip_module **modules ) +{ + /* Reset count. */ + *count = 0; + + /* Register user agent module. */ + modules[(*count)++] = pjsip_ua_get_module(); + global.user_agent = modules[0]->mod_data; + modules[(*count)++] = pjsip_messaging_get_module(); + modules[(*count)++] = pjsip_event_sub_get_module(); + + return PJ_SUCCESS; +} |