blob: 09f9f8f38e15e8bcb90f386d996d52c1d7ee455c [file] [log] [blame]
/* $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, &regc_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;
}