Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 1 | /* $Id$ */
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 2 | /*
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 3 | * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 4 | *
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 5 | * This program is free software; you can redistribute it and/or modify
|
| 6 | * it under the terms of the GNU General Public License as published by
|
| 7 | * the Free Software Foundation; either version 2 of the License, or
|
| 8 | * (at your option) any later version.
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 9 | *
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 10 | * This program is distributed in the hope that it will be useful,
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 13 | * GNU General Public License for more details.
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 14 | *
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 15 | * You should have received a copy of the GNU General Public License
|
| 16 | * along with this program; if not, write to the Free Software
|
| 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 18 | */
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 19 | #include <pjlib.h>
|
| 20 | #include <pjsip_core.h>
|
| 21 | #include <pjsip_ua.h>
|
| 22 | #include <pjsip_simple.h>
|
| 23 | #include <pjmedia.h>
|
| 24 | #include <ctype.h>
|
| 25 | #include <stdlib.h>
|
| 26 | #include <pj/stun.h>
|
| 27 |
|
| 28 | #define START_PORT 5060
|
| 29 | #define MAX_BUDDIES 32
|
| 30 | #define THIS_FILE "main.c"
|
| 31 | #define MAX_PRESENTITY 32
|
| 32 | #define PRESENCE_TIMEOUT 60
|
| 33 |
|
| 34 | /* By default we'll have one worker thread, except when threading
|
| 35 | * is disabled.
|
| 36 | */
|
| 37 | #if PJ_HAS_THREADS
|
| 38 | # define WORKER_COUNT 1
|
| 39 | #else
|
| 40 | # define WORKER_COUNT 0
|
| 41 | #endif
|
| 42 |
|
| 43 | /* Global variable. */
|
| 44 | static struct
|
| 45 | {
|
| 46 | /* Control. */
|
| 47 | pj_pool_factory *pf;
|
| 48 | pjsip_endpoint *endpt;
|
| 49 | pj_pool_t *pool;
|
| 50 | pjsip_user_agent *user_agent;
|
| 51 | int worker_cnt;
|
| 52 | int worker_quit_flag;
|
| 53 |
|
| 54 | /* User info. */
|
| 55 | char user_id[64];
|
| 56 | pj_str_t local_uri;
|
| 57 | pj_str_t contact;
|
| 58 | pj_str_t real_contact;
|
| 59 |
|
| 60 | /* Dialog. */
|
| 61 | pjsip_dlg *cur_dlg;
|
| 62 |
|
| 63 | /* Authentication. */
|
| 64 | int cred_count;
|
| 65 | pjsip_cred_info cred_info[4];
|
| 66 |
|
| 67 | /* Media stack. */
|
| 68 | pj_bool_t null_audio;
|
| 69 | pj_med_mgr_t *mmgr;
|
| 70 |
|
| 71 | /* Misc. */
|
| 72 | int app_log_level;
|
| 73 | char *log_filename;
|
| 74 | FILE *log_file;
|
| 75 |
|
| 76 | /* Proxy URLs */
|
| 77 | pj_str_t proxy;
|
| 78 | pj_str_t outbound_proxy;
|
| 79 |
|
| 80 | /* UA auto options. */
|
| 81 | int auto_answer; /* -1 to disable. */
|
| 82 | int auto_hangup; /* -1 to disable */
|
| 83 |
|
| 84 | /* Registration. */
|
| 85 | pj_str_t registrar_uri;
|
| 86 | pjsip_regc *regc;
|
| 87 | pj_int32_t reg_timeout;
|
| 88 | pj_timer_entry regc_timer;
|
| 89 |
|
| 90 | /* STUN */
|
| 91 | pj_str_t stun_srv1;
|
| 92 | int stun_port1;
|
| 93 | pj_str_t stun_srv2;
|
| 94 | int stun_port2;
|
| 95 |
|
| 96 | /* UDP sockets and their public address. */
|
| 97 | int sip_port;
|
| 98 | pj_sock_t sip_sock;
|
| 99 | pj_sockaddr_in sip_sock_name;
|
| 100 | pj_sock_t rtp_sock;
|
| 101 | pj_sockaddr_in rtp_sock_name;
|
| 102 | pj_sock_t rtcp_sock;
|
| 103 | pj_sockaddr_in rtcp_sock_name;
|
| 104 |
|
| 105 | /* SIMPLE */
|
| 106 | pj_bool_t hide_status;
|
| 107 | pj_bool_t offer_x_ms_msg;
|
| 108 | int im_counter;
|
| 109 | int buddy_cnt;
|
| 110 | pj_str_t buddy[MAX_BUDDIES];
|
| 111 | pj_bool_t buddy_status[MAX_BUDDIES];
|
| 112 | pj_bool_t no_presence;
|
| 113 | pjsip_presentity *buddy_pres[MAX_BUDDIES];
|
| 114 |
|
| 115 | int pres_cnt;
|
| 116 | pjsip_presentity *pres[MAX_PRESENTITY];
|
| 117 |
|
| 118 | } global;
|
| 119 |
|
| 120 | enum { AUTO_ANSWER, AUTO_HANGUP };
|
| 121 |
|
| 122 | /* This is the data that will be 'attached' on per dialog basis. */
|
| 123 | struct dialog_data
|
| 124 | {
|
| 125 | /* Media session. */
|
| 126 | pj_media_session_t *msession;
|
| 127 |
|
| 128 | /* x-ms-chat session. */
|
| 129 | pj_bool_t x_ms_msg_session;
|
| 130 |
|
| 131 | /* Cached SDP body, updated when media session changed. */
|
| 132 | pjsip_msg_body *body;
|
| 133 |
|
| 134 | /* Timer. */
|
| 135 | pj_bool_t has_auto_timer;
|
| 136 | pj_timer_entry auto_timer;
|
| 137 | };
|
| 138 |
|
| 139 | /*
|
| 140 | * These are the callbacks to be registered to dialog to receive notifications
|
| 141 | * about various events in the dialog.
|
| 142 | */
|
| 143 | static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
|
| 144 | pjsip_event *event );
|
| 145 | static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 146 | pjsip_tx_data *tdata, int retransmission);
|
| 147 | static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 148 | pjsip_tx_data *tdata);
|
| 149 | static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 150 | pjsip_rx_data *rdata);
|
| 151 | static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 152 | pjsip_rx_data *rdata);
|
| 153 | static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 154 | pjsip_tx_data *tdata);
|
| 155 | static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 156 | pjsip_event *event);
|
| 157 | static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event);
|
| 158 | static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event);
|
| 159 | static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event);
|
| 160 | static void dlg_on_terminated (pjsip_dlg *dlg);
|
| 161 | static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event);
|
| 162 |
|
| 163 | /* The callback structure that will be registered to UA layer. */
|
| 164 | struct pjsip_dlg_callback dlg_callback = {
|
| 165 | &dlg_on_all_events,
|
| 166 | &dlg_on_before_tx,
|
| 167 | &dlg_on_tx_msg,
|
| 168 | &dlg_on_rx_msg,
|
| 169 | &dlg_on_incoming,
|
| 170 | &dlg_on_calling,
|
| 171 | &dlg_on_provisional,
|
| 172 | &dlg_on_connecting,
|
| 173 | &dlg_on_established,
|
| 174 | &dlg_on_disconnected,
|
| 175 | &dlg_on_terminated,
|
| 176 | &dlg_on_mid_call_evt
|
| 177 | };
|
| 178 |
|
| 179 |
|
| 180 | /*
|
| 181 | * Auxiliary things are put in misc.c, so that this main.c file is more
|
| 182 | * readable.
|
| 183 | */
|
| 184 | #include "misc.c"
|
| 185 |
|
| 186 | static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap,
|
| 187 | struct pj_timer_entry *entry)
|
| 188 | {
|
| 189 | pjsip_dlg *dlg = entry->user_data;
|
| 190 | struct dialog_data *dlg_data = dlg->user_data;
|
| 191 |
|
| 192 | PJ_UNUSED_ARG(timer_heap)
|
| 193 |
|
| 194 | dlg_data->has_auto_timer = 0;
|
| 195 |
|
| 196 | if (entry->id == AUTO_ANSWER) {
|
| 197 | pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200);
|
| 198 | if (tdata) {
|
| 199 | struct dialog_data *dlg_data = global.cur_dlg->user_data;
|
| 200 | tdata->msg->body = dlg_data->body;
|
| 201 | pjsip_dlg_send_msg(dlg, tdata);
|
| 202 | }
|
| 203 | } else {
|
| 204 | pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500);
|
| 205 | if (tdata)
|
| 206 | pjsip_dlg_send_msg(dlg, tdata);
|
| 207 | }
|
| 208 | }
|
| 209 |
|
| 210 | static void update_registration(pjsip_regc *regc, int renew)
|
| 211 | {
|
| 212 | pjsip_tx_data *tdata;
|
| 213 |
|
| 214 | PJ_LOG(3,(THIS_FILE, "Performing SIP registration..."));
|
| 215 |
|
| 216 | if (renew) {
|
| 217 | tdata = pjsip_regc_register(regc, 1);
|
| 218 | } else {
|
| 219 | tdata = pjsip_regc_unregister(regc);
|
| 220 | }
|
| 221 |
|
| 222 | pjsip_regc_send( regc, tdata );
|
| 223 | }
|
| 224 |
|
| 225 | static void regc_cb(struct pjsip_regc_cbparam *param)
|
| 226 | {
|
| 227 | /*
|
| 228 | * Print registration status.
|
| 229 | */
|
| 230 | if (param->code < 0 || param->code >= 300) {
|
| 231 | PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)",
|
| 232 | param->code, pjsip_get_status_text(param->code)->ptr));
|
| 233 | global.regc = NULL;
|
| 234 |
|
| 235 | } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
|
| 236 | PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), "
|
| 237 | "will re-register in %d seconds",
|
| 238 | param->code,
|
| 239 | pjsip_get_status_text(param->code)->ptr,
|
| 240 | param->expiration));
|
| 241 |
|
| 242 | } else {
|
| 243 | PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code));
|
| 244 | }
|
| 245 | }
|
| 246 |
|
| 247 | static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata,
|
| 248 | int *timeout)
|
| 249 | {
|
| 250 | int state;
|
| 251 | int i;
|
| 252 | char url[PJSIP_MAX_URL_SIZE];
|
| 253 | int urllen;
|
| 254 |
|
| 255 | PJ_UNUSED_ARG(rdata)
|
| 256 |
|
| 257 | if (*timeout > 0) {
|
| 258 | state = PJSIP_EVENT_SUB_STATE_ACTIVE;
|
| 259 | if (*timeout > 300)
|
| 260 | *timeout = 300;
|
| 261 | } else {
|
| 262 | state = PJSIP_EVENT_SUB_STATE_TERMINATED;
|
| 263 | }
|
| 264 |
|
| 265 | urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1);
|
| 266 | if (urllen < 1) {
|
| 267 | pj_native_strcpy(url, "<unknown>");
|
| 268 | } else {
|
| 269 | url[urllen] = '\0';
|
| 270 | }
|
| 271 | PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s",
|
| 272 | url,
|
| 273 | (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated")));
|
| 274 |
|
| 275 | for (i=0; i<global.pres_cnt; ++i)
|
| 276 | if (global.pres[i] == pres)
|
| 277 | break;
|
| 278 | if (i == global.pres_cnt)
|
| 279 | global.pres[global.pres_cnt++] = pres;
|
| 280 |
|
| 281 | pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info );
|
| 282 | pjsip_presence_notify(pres, state, !global.hide_status);
|
| 283 |
|
| 284 | }
|
| 285 |
|
| 286 | static void pres_on_received_refresh(pjsip_presentity *pres, pjsip_rx_data *rdata)
|
| 287 | {
|
| 288 | pres_on_received_request(pres, rdata, &pres->sub->default_interval);
|
| 289 | }
|
| 290 |
|
| 291 | /* This is called by presence framework when we receives presence update
|
| 292 | * of a resource (buddy).
|
| 293 | */
|
| 294 | static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open)
|
| 295 | {
|
| 296 | int buddy_index = (int)pres->user_data;
|
| 297 |
|
| 298 | global.buddy_status[buddy_index] = is_open;
|
| 299 | PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s",
|
| 300 | global.buddy[buddy_index].ptr,
|
| 301 | (is_open ? "Online" : "Offline")));
|
| 302 | }
|
| 303 |
|
| 304 | /* This is called when the subscription is terminated. */
|
| 305 | static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason)
|
| 306 | {
|
| 307 | if (pres->sub->role == PJSIP_ROLE_UAC) {
|
| 308 | int buddy_index = (int)pres->user_data;
|
| 309 | PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)",
|
| 310 | global.buddy[buddy_index].ptr,
|
| 311 | reason->slen, reason->ptr));
|
| 312 | global.buddy_pres[buddy_index] = NULL;
|
| 313 | global.buddy_status[buddy_index] = 0;
|
| 314 | } else {
|
| 315 | int i;
|
| 316 | PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)",
|
| 317 | reason->slen, reason->ptr));
|
| 318 | pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1);
|
| 319 | for (i=0; i<global.pres_cnt; ++i) {
|
| 320 | if (global.pres[i] == pres) {
|
| 321 | int j;
|
| 322 | global.pres[i] = NULL;
|
| 323 | for (j=i+1; j<global.pres_cnt; ++j)
|
| 324 | global.pres[j-1] = global.pres[j];
|
| 325 | global.pres_cnt--;
|
| 326 | break;
|
| 327 | }
|
| 328 | }
|
| 329 | }
|
| 330 | pjsip_presence_destroy(pres);
|
| 331 | }
|
| 332 |
|
| 333 |
|
| 334 | /* Callback attached to SIP body to print the body to message buffer. */
|
| 335 | static int print_msg_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size)
|
| 336 | {
|
| 337 | pjsip_msg_body *body = msg_body;
|
| 338 | return pjsdp_print ((pjsdp_session_desc*)body->data, buf, size);
|
| 339 | }
|
| 340 |
|
| 341 | /* When media session has changed, call this function to update the cached body
|
| 342 | * information in the dialog.
|
| 343 | */
|
| 344 | static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg)
|
| 345 | {
|
| 346 | struct dialog_data *dlg_data = dlg->user_data;
|
| 347 | pjsdp_session_desc *sdp;
|
| 348 |
|
| 349 | sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg);
|
| 350 | if (!sdp) {
|
| 351 | dlg_data->body = NULL;
|
| 352 | return NULL;
|
| 353 | }
|
| 354 |
|
| 355 | /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new
|
| 356 | * "m=" line in the SDP.
|
| 357 | */
|
| 358 | if (dlg_data->x_ms_msg_session >= 0 &&
|
| 359 | dlg_data->x_ms_msg_session >= (int)sdp->media_count)
|
| 360 | {
|
| 361 | pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m));
|
| 362 | sdp->media[sdp->media_count] = m;
|
| 363 | dlg_data->x_ms_msg_session = sdp->media_count++;
|
| 364 | }
|
| 365 |
|
| 366 | /*
|
| 367 | * For "x-ms-message" line, remove all attributes and connection line etc.
|
| 368 | */
|
| 369 | if (dlg_data->x_ms_msg_session >= 0) {
|
| 370 | pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session];
|
| 371 | if (m) {
|
| 372 | m->desc.media = pj_str("x-ms-message");
|
| 373 | m->desc.port = 5060;
|
| 374 | m->desc.transport = pj_str("sip");
|
| 375 | m->desc.fmt_count = 1;
|
| 376 | m->desc.fmt[0] = pj_str("null");
|
| 377 | m->attr_count = 0;
|
| 378 | m->conn = NULL;
|
| 379 | }
|
| 380 | }
|
| 381 |
|
| 382 | dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body));
|
| 383 | dlg_data->body->content_type.type = pj_str("application");
|
| 384 | dlg_data->body->content_type.subtype = pj_str("sdp");
|
| 385 | dlg_data->body->len = 0; /* ignored */
|
| 386 | dlg_data->body->print_body = &print_msg_body;
|
| 387 |
|
| 388 | dlg_data->body->data = sdp;
|
| 389 | return dlg_data->body;
|
| 390 | }
|
| 391 |
|
| 392 | /* This callback will be called on every occurence of events in dialogs */
|
| 393 | static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
|
| 394 | pjsip_event *event )
|
| 395 | {
|
| 396 | PJ_UNUSED_ARG(dlg_evt)
|
| 397 | PJ_UNUSED_ARG(event)
|
| 398 |
|
| 399 | PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg));
|
| 400 | }
|
| 401 |
|
| 402 | /* This callback is called before each outgoing msg is sent (including
|
| 403 | * retransmission). Application can override this notification if it wants
|
| 404 | * to modify the message before transmission or if it wants to do something
|
| 405 | * else for each transmission.
|
| 406 | */
|
| 407 | static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 408 | pjsip_tx_data *tdata, int ret_cnt)
|
| 409 | {
|
| 410 | PJ_UNUSED_ARG(tsx)
|
| 411 | PJ_UNUSED_ARG(tdata)
|
| 412 |
|
| 413 | if (ret_cnt > 0) {
|
| 414 | PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)",
|
| 415 | dlg->obj_name, ret_cnt));
|
| 416 | }
|
| 417 | }
|
| 418 |
|
| 419 | /* This callback is called after a message is sent. */
|
| 420 | static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 421 | pjsip_tx_data *tdata)
|
| 422 | {
|
| 423 | PJ_UNUSED_ARG(tsx)
|
| 424 | PJ_UNUSED_ARG(tdata)
|
| 425 |
|
| 426 | PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg));
|
| 427 | }
|
| 428 |
|
| 429 | /* This callback is called on receipt of incoming message. */
|
| 430 | static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 431 | pjsip_rx_data *rdata)
|
| 432 | {
|
| 433 | PJ_UNUSED_ARG(tsx)
|
| 434 | PJ_UNUSED_ARG(rdata)
|
| 435 | PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg));
|
| 436 | }
|
| 437 |
|
| 438 | /* This callback is called after dialog has sent INVITE */
|
| 439 | static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 440 | pjsip_tx_data *tdata)
|
| 441 | {
|
| 442 | PJ_UNUSED_ARG(tsx)
|
| 443 | PJ_UNUSED_ARG(tdata)
|
| 444 |
|
| 445 | pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG &&
|
| 446 | tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD &&
|
| 447 | tsx->method.id == PJSIP_INVITE_METHOD);
|
| 448 |
|
| 449 | PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name));
|
| 450 | }
|
| 451 |
|
| 452 | static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp)
|
| 453 | {
|
| 454 | struct dialog_data *dlg_data = dlg->user_data;
|
| 455 | pj_bool_t sdp_x_ms_msg_index = -1;
|
| 456 | int i;
|
| 457 | int mcnt;
|
| 458 | const pj_media_stream_info *mi[PJSDP_MAX_MEDIA];
|
| 459 | int has_active;
|
| 460 | pj_media_sock_info sock_info;
|
| 461 |
|
| 462 | /* Find "m=x-ms-message" line in the SDP. */
|
| 463 | for (i=0; i<(int)sdp->media_count; ++i) {
|
| 464 | if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0)
|
| 465 | sdp_x_ms_msg_index = i;
|
| 466 | }
|
| 467 |
|
| 468 | /*
|
| 469 | * Create media session.
|
| 470 | */
|
| 471 | pj_memset(&sock_info, 0, sizeof(sock_info));
|
| 472 | sock_info.rtp_sock = global.rtp_sock;
|
| 473 | sock_info.rtcp_sock = global.rtcp_sock;
|
| 474 | pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in));
|
| 475 |
|
| 476 | dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info);
|
| 477 |
|
| 478 | /* A session will always be created, unless there is memory
|
| 479 | * alloc problem.
|
| 480 | */
|
| 481 | pj_assert(dlg_data->msession);
|
| 482 |
|
| 483 | /* See if we can take the offer by checking that we have at least
|
| 484 | * one media stream active.
|
| 485 | */
|
| 486 | mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi);
|
| 487 | for (i=0, has_active=0; i<mcnt; ++i) {
|
| 488 | if (mi[i]->fmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) {
|
| 489 | has_active = 1;
|
| 490 | break;
|
| 491 | }
|
| 492 | }
|
| 493 |
|
| 494 | if (!has_active && sdp_x_ms_msg_index==-1) {
|
| 495 | pjsip_tx_data *tdata;
|
| 496 |
|
| 497 | /* Unable to accept remote's SDP.
|
| 498 | * Answer with 488 (Not Acceptable Here)
|
| 499 | */
|
| 500 | /* Create 488 response. */
|
| 501 | tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE);
|
| 502 |
|
| 503 | /* Send response. */
|
| 504 | if (tdata)
|
| 505 | pjsip_dlg_send_msg(dlg, tdata);
|
| 506 | return;
|
| 507 | }
|
| 508 |
|
| 509 | dlg_data->x_ms_msg_session = sdp_x_ms_msg_index;
|
| 510 |
|
| 511 | /* Create msg body to be used later in 2xx/response */
|
| 512 | create_msg_body(dlg, 0);
|
| 513 |
|
| 514 | }
|
| 515 |
|
| 516 | /* This callback is called after an INVITE is received. */
|
| 517 | static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 518 | pjsip_rx_data *rdata)
|
| 519 | {
|
| 520 | struct dialog_data *dlg_data;
|
| 521 | pjsip_msg *msg;
|
| 522 | pjsip_tx_data *tdata;
|
| 523 | char buf[128];
|
| 524 | int len;
|
| 525 |
|
| 526 | PJ_UNUSED_ARG(tsx)
|
| 527 |
|
| 528 | pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG &&
|
| 529 | rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD &&
|
| 530 | tsx->method.id == PJSIP_INVITE_METHOD);
|
| 531 |
|
| 532 | /*
|
| 533 | * Notify user!
|
| 534 | */
|
| 535 | PJ_LOG(3, (THIS_FILE, ""));
|
| 536 | PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name));
|
| 537 | PJ_LOG(3, (THIS_FILE, ""));
|
| 538 | len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR,
|
| 539 | (pjsip_name_addr*)dlg->remote.info->uri,
|
| 540 | buf, sizeof(buf)-1);
|
| 541 | if (len > 0) {
|
| 542 | buf[len] = '\0';
|
| 543 | PJ_LOG(3,(THIS_FILE, "From:\t%s", buf));
|
| 544 | }
|
| 545 | len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR,
|
| 546 | (pjsip_name_addr*)dlg->local.info->uri,
|
| 547 | buf, sizeof(buf)-1);
|
| 548 | if (len > 0) {
|
| 549 | buf[len] = '\0';
|
| 550 | PJ_LOG(3,(THIS_FILE, "To:\t%s", buf));
|
| 551 | }
|
| 552 | PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name));
|
| 553 | PJ_LOG(3, (THIS_FILE, ""));
|
| 554 |
|
| 555 | /*
|
| 556 | * Process incoming dialog.
|
| 557 | */
|
| 558 |
|
| 559 | dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data));
|
| 560 | dlg->user_data = dlg_data;
|
| 561 |
|
| 562 | /* Update contact. */
|
| 563 | pjsip_dlg_set_contact(dlg, &global.contact);
|
| 564 |
|
| 565 | /* Initialize credentials. */
|
| 566 | pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info);
|
| 567 |
|
| 568 | /* Create media session if the request has "application/sdp" body. */
|
| 569 | msg = rdata->msg;
|
| 570 | if (msg->body &&
|
| 571 | pj_stricmp2(&msg->body->content_type.type, "application")==0 &&
|
| 572 | pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0)
|
| 573 | {
|
| 574 | pjsdp_session_desc *sdp;
|
| 575 |
|
| 576 | /* Parse SDP body, and instantiate media session based on remote's SDP.
|
| 577 | * Then create our SDP body from the session.
|
| 578 | */
|
| 579 | sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool);
|
| 580 | if (!sdp)
|
| 581 | goto send_answer;
|
| 582 |
|
| 583 | create_session_from_sdp(dlg, sdp);
|
| 584 |
|
| 585 | } else if (msg->body) {
|
| 586 | /* The request has a message body other than "application/sdp" */
|
| 587 | pjsip_accept_hdr *accept;
|
| 588 |
|
| 589 | /* Create response. */
|
| 590 | tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
|
| 591 |
|
| 592 | /* Add "Accept" header. */
|
| 593 | accept = pjsip_accept_hdr_create(tdata->pool);
|
| 594 | accept->values[0] = pj_str("application/sdp");
|
| 595 | accept->count = 1;
|
| 596 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept);
|
| 597 |
|
| 598 | /* Send response. */
|
| 599 | pjsip_dlg_send_msg(dlg, tdata);
|
| 600 | return;
|
| 601 |
|
| 602 | } else {
|
| 603 | /* The request has no message body. We can take this request, but
|
| 604 | * no media session will be activated.
|
| 605 | */
|
| 606 | /* Nothing to do here. */
|
| 607 | }
|
| 608 |
|
| 609 | send_answer:
|
| 610 | /* Immediately answer with 100 (or 180? */
|
| 611 | tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING );
|
| 612 | pjsip_dlg_send_msg(dlg, tdata);
|
| 613 |
|
| 614 | /* Set current dialog to this dialog if we don't currently have
|
| 615 | * current dialog.
|
| 616 | */
|
| 617 | if (global.cur_dlg == NULL) {
|
| 618 | global.cur_dlg = dlg;
|
| 619 | }
|
| 620 |
|
| 621 | /* Auto-answer if option is specified. */
|
| 622 | if (global.auto_answer >= 0) {
|
| 623 | pj_time_val delay = { 0, 0};
|
| 624 | struct dialog_data *dlg_data = dlg->user_data;
|
| 625 |
|
| 626 | PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds",
|
| 627 | global.auto_answer));
|
| 628 |
|
| 629 | delay.sec = global.auto_answer;
|
| 630 | dlg_data->auto_timer.user_data = dlg;
|
| 631 | dlg_data->auto_timer.id = AUTO_ANSWER;
|
| 632 | dlg_data->auto_timer.cb = &dlg_auto_timer_callback;
|
| 633 | dlg_data->has_auto_timer = 1;
|
| 634 | pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay);
|
| 635 | }
|
| 636 | }
|
| 637 |
|
| 638 | /* This callback is called when dialog has sent/received a provisional response
|
| 639 | * to INVITE.
|
| 640 | */
|
| 641 | static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx,
|
| 642 | pjsip_event *event)
|
| 643 | {
|
| 644 | const char *action;
|
| 645 |
|
| 646 | pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
|
| 647 | event->src.tdata->msg->type == PJSIP_RESPONSE_MSG &&
|
| 648 | event->src.tdata->msg->line.status.code/100 == 1 &&
|
| 649 | tsx->method.id == PJSIP_INVITE_METHOD)
|
| 650 | ||
|
| 651 | (event->src_type == PJSIP_EVENT_RX_MSG &&
|
| 652 | event->src.rdata->msg->type == PJSIP_RESPONSE_MSG &&
|
| 653 | event->src.rdata->msg->line.status.code/100 == 1 &&
|
| 654 | tsx->method.id == PJSIP_INVITE_METHOD));
|
| 655 |
|
| 656 | if (event->src_type == PJSIP_EVENT_TX_MSG)
|
| 657 | action = "Sending";
|
| 658 | else
|
| 659 | action = "Received";
|
| 660 |
|
| 661 | PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)",
|
| 662 | dlg->obj_name, action, tsx->status_code,
|
| 663 | pjsip_get_status_text(tsx->status_code)->ptr));
|
| 664 | }
|
| 665 |
|
| 666 | /* This callback is called when 200 response to INVITE is sent/received. */
|
| 667 | static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event)
|
| 668 | {
|
| 669 | struct dialog_data *dlg_data = dlg->user_data;
|
| 670 | const char *action;
|
| 671 |
|
| 672 | pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
|
| 673 | event->src.tdata->msg->type == PJSIP_RESPONSE_MSG &&
|
| 674 | event->src.tdata->msg->line.status.code/100 == 2)
|
| 675 | ||
|
| 676 | (event->src_type == PJSIP_EVENT_RX_MSG &&
|
| 677 | event->src.rdata->msg->type == PJSIP_RESPONSE_MSG &&
|
| 678 | event->src.rdata->msg->line.status.code/100 == 2));
|
| 679 |
|
| 680 | if (event->src_type == PJSIP_EVENT_RX_MSG)
|
| 681 | action = "Received";
|
| 682 | else
|
| 683 | action = "Sending";
|
| 684 |
|
| 685 | PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action));
|
| 686 |
|
| 687 | if (event->src_type == PJSIP_EVENT_RX_MSG) {
|
| 688 | /* On receipt of 2xx response, negotiate our media capability
|
| 689 | * and start media.
|
| 690 | */
|
| 691 | pjsip_msg *msg = event->src.rdata->msg;
|
| 692 | pjsip_msg_body *body;
|
| 693 | pjsdp_session_desc *sdp;
|
| 694 |
|
| 695 | /* Get SDP from message. */
|
| 696 |
|
| 697 | /* Ignore if no SDP body is present. */
|
| 698 | body = msg->body;
|
| 699 | if (!body) {
|
| 700 | PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!",
|
| 701 | dlg->obj_name));
|
| 702 | return;
|
| 703 | }
|
| 704 |
|
| 705 | if (pj_stricmp2(&body->content_type.type, "application") != 0 &&
|
| 706 | pj_stricmp2(&body->content_type.subtype, "sdp") != 0)
|
| 707 | {
|
| 708 | PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!",
|
| 709 | dlg->obj_name));
|
| 710 | return;
|
| 711 | }
|
| 712 |
|
| 713 | /* Got what seems to be a SDP content. Parse it. */
|
| 714 | sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool);
|
| 715 | if (!sdp) {
|
| 716 | PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!",
|
| 717 | dlg->obj_name));
|
| 718 | return;
|
| 719 | }
|
| 720 |
|
| 721 | /* Negotiate media session with remote's media capability. */
|
| 722 | if (pj_media_session_update (dlg_data->msession, sdp) != 0) {
|
| 723 | PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!",
|
| 724 | dlg->obj_name));
|
| 725 | return;
|
| 726 | }
|
| 727 |
|
| 728 | /* Update the saved SDP body because media session has changed.
|
| 729 | * Also set ack flag to '1', because we only want to send one format/
|
| 730 | * codec for each media streams.
|
| 731 | */
|
| 732 | create_msg_body(dlg, 1);
|
| 733 |
|
| 734 | /* Activate media. */
|
| 735 | pj_media_session_activate (dlg_data->msession);
|
| 736 |
|
| 737 | } else {
|
| 738 | pjsip_msg *msg = event->src.tdata->msg;
|
| 739 |
|
| 740 | if (msg->body) {
|
| 741 | /* On transmission of 2xx response, start media session. */
|
| 742 | pj_media_session_activate (dlg_data->msession);
|
| 743 | }
|
| 744 | }
|
| 745 |
|
| 746 | }
|
| 747 |
|
| 748 | /* This callback is called when ACK to initial INVITE is sent/received. */
|
| 749 | static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event)
|
| 750 | {
|
| 751 | const char *action;
|
| 752 |
|
| 753 | pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
|
| 754 | event->src.tdata->msg->type == PJSIP_REQUEST_MSG &&
|
| 755 | event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD)
|
| 756 | ||
|
| 757 | (event->src_type == PJSIP_EVENT_RX_MSG &&
|
| 758 | event->src.rdata->msg->type == PJSIP_REQUEST_MSG &&
|
| 759 | event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD));
|
| 760 |
|
| 761 | if (event->src_type == PJSIP_EVENT_RX_MSG)
|
| 762 | action = "Received";
|
| 763 | else
|
| 764 | action = "Sending";
|
| 765 |
|
| 766 | PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED",
|
| 767 | dlg->obj_name, action));
|
| 768 |
|
| 769 | /* Attach SDP body for outgoing ACK. */
|
| 770 | if (event->src_type == PJSIP_EVENT_TX_MSG &&
|
| 771 | event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD)
|
| 772 | {
|
| 773 | struct dialog_data *dlg_data = dlg->user_data;
|
| 774 | event->src.tdata->msg->body = dlg_data->body;
|
| 775 | }
|
| 776 |
|
| 777 | /* Auto-hangup if option is specified. */
|
| 778 | if (global.auto_hangup >= 0) {
|
| 779 | pj_time_val delay = { 0, 0};
|
| 780 | struct dialog_data *dlg_data = dlg->user_data;
|
| 781 |
|
| 782 | PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds",
|
| 783 | global.auto_hangup));
|
| 784 |
|
| 785 | delay.sec = global.auto_hangup;
|
| 786 | dlg_data->auto_timer.user_data = dlg;
|
| 787 | dlg_data->auto_timer.id = AUTO_HANGUP;
|
| 788 | dlg_data->auto_timer.cb = &dlg_auto_timer_callback;
|
| 789 | dlg_data->has_auto_timer = 1;
|
| 790 | pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay);
|
| 791 | }
|
| 792 | }
|
| 793 |
|
| 794 |
|
| 795 | /* This callback is called when dialog is disconnected (because of final
|
| 796 | * response, BYE, or timer).
|
| 797 | */
|
| 798 | static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event)
|
| 799 | {
|
| 800 | struct dialog_data *dlg_data = dlg->user_data;
|
| 801 | int status_code;
|
| 802 | const pj_str_t *reason;
|
| 803 |
|
| 804 | PJ_UNUSED_ARG(event)
|
| 805 |
|
| 806 | /* Cancel auto-answer/auto-hangup timer. */
|
| 807 | if (dlg_data->has_auto_timer) {
|
| 808 | pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer);
|
| 809 | dlg_data->has_auto_timer = 0;
|
| 810 | }
|
| 811 |
|
| 812 | if (dlg->invite_tsx)
|
| 813 | status_code = dlg->invite_tsx->status_code;
|
| 814 | else
|
| 815 | status_code = 200;
|
| 816 |
|
| 817 | if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) {
|
| 818 | if (event->src_type == PJSIP_EVENT_RX_MSG)
|
| 819 | reason = &event->src.rdata->msg->line.status.reason;
|
| 820 | else if (event->src_type == PJSIP_EVENT_TX_MSG)
|
| 821 | reason = &event->src.tdata->msg->line.status.reason;
|
| 822 | else
|
| 823 | reason = pjsip_get_status_text(event->obj.tsx->status_code);
|
| 824 | } else {
|
| 825 | reason = &event->obj.tsx->method.name;
|
| 826 | }
|
| 827 |
|
| 828 | PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)",
|
| 829 | dlg->obj_name, status_code,
|
| 830 | reason->slen, reason->ptr));
|
| 831 |
|
| 832 | if (dlg_data->msession) {
|
| 833 | pj_media_session_destroy (dlg_data->msession);
|
| 834 | dlg_data->msession = NULL;
|
| 835 | }
|
| 836 | }
|
| 837 |
|
| 838 | /* This callback is called when dialog is about to be destroyed. */
|
| 839 | static void dlg_on_terminated(pjsip_dlg *dlg)
|
| 840 | {
|
| 841 | PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name));
|
| 842 |
|
| 843 | /* If current dialog is equal to this dialog, update it. */
|
| 844 | if (global.cur_dlg == dlg) {
|
| 845 | global.cur_dlg = global.cur_dlg->next;
|
| 846 | if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
|
| 847 | global.cur_dlg = NULL;
|
| 848 | }
|
| 849 | }
|
| 850 | }
|
| 851 |
|
| 852 | /* This callback is called for any requests when dialog is established. */
|
| 853 | static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event)
|
| 854 | {
|
| 855 | pjsip_transaction *tsx = event->obj.tsx;
|
| 856 |
|
| 857 | if (event->src_type == PJSIP_EVENT_RX_MSG &&
|
| 858 | event->src.rdata->msg->type == PJSIP_REQUEST_MSG)
|
| 859 | {
|
| 860 | if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) {
|
| 861 | /* Re-invitation. */
|
| 862 | pjsip_tx_data *tdata;
|
| 863 |
|
| 864 | PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)",
|
| 865 | dlg->obj_name));
|
| 866 | tdata = pjsip_dlg_answer(dlg, 200);
|
| 867 | if (tdata) {
|
| 868 | struct dialog_data *dlg_data = dlg->user_data;
|
| 869 | tdata->msg->body = dlg_data->body;
|
| 870 | pjsip_dlg_send_msg(dlg, tdata);
|
| 871 | }
|
| 872 | } else {
|
| 873 | /* Don't worry, endpoint will answer with 500 or whetever. */
|
| 874 | }
|
| 875 |
|
| 876 | } else if (tsx->status_code/100 == 2) {
|
| 877 | PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)",
|
| 878 | dlg->obj_name,
|
| 879 | tsx->method.name.slen, tsx->method.name.ptr,
|
| 880 | tsx->status_code,
|
| 881 | pjsip_get_status_text(tsx->status_code)->ptr));
|
| 882 |
|
| 883 |
|
| 884 | } else if (tsx->status_code >= 300) {
|
| 885 | pj_bool_t report_failure = PJ_TRUE;
|
| 886 |
|
| 887 | /* Check for authentication failures. */
|
| 888 | if (tsx->status_code==401 || tsx->status_code==407) {
|
| 889 | pjsip_tx_data *tdata;
|
| 890 | tdata = pjsip_auth_reinit_req( global.endpt,
|
| 891 | dlg->pool, &dlg->auth_sess,
|
| 892 | dlg->cred_count, dlg->cred_info,
|
| 893 | tsx->last_tx, event->src.rdata );
|
| 894 | if (tdata) {
|
| 895 | int rc;
|
| 896 | rc = pjsip_dlg_send_msg( dlg, tdata);
|
| 897 | report_failure = (rc != 0);
|
| 898 | }
|
| 899 | }
|
| 900 | if (report_failure) {
|
| 901 | const pj_str_t *reason;
|
| 902 | if (event->src_type == PJSIP_EVENT_RX_MSG) {
|
| 903 | reason = &event->src.rdata->msg->line.status.reason;
|
| 904 | } else {
|
| 905 | reason = pjsip_get_status_text(tsx->status_code);
|
| 906 | }
|
| 907 | PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)",
|
| 908 | dlg->obj_name, tsx->status_code,
|
| 909 | reason->slen, reason->ptr));
|
| 910 | }
|
| 911 | }
|
| 912 | }
|
| 913 |
|
| 914 | /* Initialize sockets and optionally get the public address via STUN. */
|
| 915 | static pj_status_t init_sockets()
|
| 916 | {
|
| 917 | enum {
|
| 918 | RTP_START_PORT = 4000,
|
| 919 | RTP_RANDOM_START = 2,
|
| 920 | RTP_RETRY = 10
|
| 921 | };
|
| 922 | enum {
|
| 923 | SIP_SOCK,
|
| 924 | RTP_SOCK,
|
| 925 | RTCP_SOCK,
|
| 926 | };
|
| 927 | int i;
|
| 928 | int rtp_port;
|
| 929 | pj_sock_t sock[3];
|
| 930 | pj_sockaddr_in mapped_addr[3];
|
| 931 |
|
| 932 | for (i=0; i<3; ++i)
|
| 933 | sock[i] = PJ_INVALID_SOCKET;
|
| 934 |
|
| 935 | /* Create and bind SIP UDP socket. */
|
| 936 | sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
|
| 937 | if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) {
|
| 938 | PJ_LOG(2,(THIS_FILE, "Unable to create socket"));
|
| 939 | goto on_error;
|
| 940 | }
|
| 941 | if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) {
|
| 942 | PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port"));
|
| 943 | goto on_error;
|
| 944 | }
|
| 945 |
|
| 946 | /* Initialize start of RTP port to try. */
|
| 947 | rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2;
|
| 948 |
|
| 949 | /* Loop retry to bind RTP and RTCP sockets. */
|
| 950 | for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
|
| 951 |
|
| 952 | /* Create and bind RTP socket. */
|
| 953 | sock[RTP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
|
| 954 | if (sock[RTP_SOCK] == PJ_INVALID_SOCKET)
|
| 955 | goto on_error;
|
| 956 | if (pj_sock_bind_in(sock[RTP_SOCK], 0, (pj_uint16_t)rtp_port) != 0) {
|
| 957 | pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
|
| 958 | continue;
|
| 959 | }
|
| 960 |
|
| 961 | /* Create and bind RTCP socket. */
|
| 962 | sock[RTCP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
|
| 963 | if (sock[RTCP_SOCK] == PJ_INVALID_SOCKET)
|
| 964 | goto on_error;
|
| 965 | if (pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)) != 0) {
|
| 966 | pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
|
| 967 | pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
|
| 968 | continue;
|
| 969 | }
|
| 970 |
|
| 971 | /*
|
| 972 | * If we're configured to use STUN, then find out the mapped address,
|
| 973 | * and make sure that the mapped RTCP port is adjacent with the RTP.
|
| 974 | */
|
| 975 | if (global.stun_port1 == 0) {
|
| 976 | pj_str_t hostname;
|
| 977 | pj_sockaddr_in addr;
|
| 978 |
|
| 979 | /* Get local IP address. */
|
| 980 | char hostname_buf[PJ_MAX_HOSTNAME];
|
| 981 | if (gethostname(hostname_buf, sizeof(hostname_buf)))
|
| 982 | goto on_error;
|
| 983 | hostname = pj_str(hostname_buf);
|
| 984 |
|
| 985 | pj_memset( &addr, 0, sizeof(addr));
|
| 986 | addr.sin_family = PJ_AF_INET;
|
| 987 | if (pj_sockaddr_set_str_addr( &addr, &hostname) != PJ_SUCCESS)
|
| 988 | goto on_error;
|
| 989 |
|
| 990 | for (i=0; i<3; ++i)
|
| 991 | pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
|
| 992 |
|
| 993 | mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)global.sip_port);
|
| 994 | mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port);
|
| 995 | mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
|
| 996 | break;
|
| 997 | } else {
|
| 998 | pj_status_t rc;
|
| 999 | rc = pj_stun_get_mapped_addr( global.pf, 3, sock,
|
| 1000 | &global.stun_srv1, global.stun_port1,
|
| 1001 | &global.stun_srv2, global.stun_port2,
|
| 1002 | mapped_addr);
|
| 1003 | if (rc != 0) {
|
| 1004 | PJ_LOG(3,(THIS_FILE, "Error: %s", pj_stun_get_err_msg(rc)));
|
| 1005 | goto on_error;
|
| 1006 | }
|
| 1007 |
|
| 1008 | if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1)
|
| 1009 | break;
|
| 1010 |
|
| 1011 | pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
|
| 1012 | pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
|
| 1013 | }
|
| 1014 | }
|
| 1015 |
|
| 1016 | if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) {
|
| 1017 | PJ_LOG(2,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination"));
|
| 1018 | goto on_error;
|
| 1019 | }
|
| 1020 |
|
| 1021 | global.sip_sock = sock[SIP_SOCK];
|
| 1022 | pj_memcpy(&global.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in));
|
| 1023 | global.rtp_sock = sock[RTP_SOCK];
|
| 1024 | pj_memcpy(&global.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in));
|
| 1025 | global.rtcp_sock = sock[RTCP_SOCK];
|
| 1026 | pj_memcpy(&global.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in));
|
| 1027 |
|
| 1028 | PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d",
|
| 1029 | pj_inet_ntoa(global.sip_sock_name.sin_addr),
|
| 1030 | pj_ntohs(global.sip_sock_name.sin_port)));
|
| 1031 | PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
|
| 1032 | pj_inet_ntoa(global.rtp_sock_name.sin_addr),
|
| 1033 | pj_ntohs(global.rtp_sock_name.sin_port)));
|
| 1034 | PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d",
|
| 1035 | pj_inet_ntoa(global.rtcp_sock_name.sin_addr),
|
| 1036 | pj_ntohs(global.rtcp_sock_name.sin_port)));
|
| 1037 | return 0;
|
| 1038 |
|
| 1039 | on_error:
|
| 1040 | for (i=0; i<3; ++i) {
|
| 1041 | if (sock[i] != PJ_INVALID_SOCKET)
|
| 1042 | pj_sock_close(sock[i]);
|
| 1043 | }
|
| 1044 | return -1;
|
| 1045 | }
|
| 1046 |
|
| 1047 | static void log_function(int level, const char *buffer, int len)
|
| 1048 | {
|
| 1049 | /* Write to both stdout and file. */
|
| 1050 | if (level <= global.app_log_level)
|
| 1051 | pj_log_to_stdout(level, buffer, len);
|
| 1052 | if (global.log_file) {
|
| 1053 | fwrite(buffer, len, 1, global.log_file);
|
| 1054 | fflush(global.log_file);
|
| 1055 | }
|
| 1056 | }
|
| 1057 |
|
| 1058 | /* Initialize stack. */
|
| 1059 | static pj_status_t init_stack()
|
| 1060 | {
|
| 1061 | pj_status_t status;
|
| 1062 | pj_sockaddr_in bind_addr;
|
| 1063 | pj_sockaddr_in bind_name;
|
| 1064 | const char *local_addr;
|
| 1065 | static char local_uri[128];
|
| 1066 |
|
| 1067 | /* Optionally set logging file. */
|
| 1068 | if (global.log_filename) {
|
| 1069 | global.log_file = fopen(global.log_filename, "wt");
|
| 1070 | }
|
| 1071 |
|
| 1072 | /* Initialize endpoint. This will also call initialization to all the
|
| 1073 | * modules.
|
| 1074 | */
|
| 1075 | global.endpt = pjsip_endpt_create(global.pf);
|
| 1076 | if (global.endpt == NULL) {
|
| 1077 | return -1;
|
| 1078 | }
|
| 1079 |
|
| 1080 | /* Set dialog callback. */
|
| 1081 | pjsip_ua_set_dialog_callback(global.user_agent, &dlg_callback);
|
| 1082 |
|
| 1083 | /* Init listener's bound address and port. */
|
| 1084 | pj_sockaddr_init2(&bind_addr, "0.0.0.0", global.sip_port);
|
| 1085 | pj_sockaddr_init(&bind_name, pj_gethostname(), global.sip_port);
|
| 1086 |
|
| 1087 | /* Add UDP transport listener. */
|
| 1088 | status = pjsip_endpt_create_udp_listener( global.endpt, global.sip_sock,
|
| 1089 | &global.sip_sock_name);
|
| 1090 | if (status != 0)
|
| 1091 | return -1;
|
| 1092 |
|
| 1093 | local_addr = pj_inet_ntoa(global.sip_sock_name.sin_addr);
|
| 1094 |
|
| 1095 | #if PJ_HAS_TCP
|
| 1096 | /* Add TCP transport listener. */
|
| 1097 | status = pjsip_endpt_create_listener( global.endpt, PJSIP_TRANSPORT_TCP,
|
| 1098 | &bind_addr, &bind_name);
|
| 1099 | if (status != 0)
|
| 1100 | return -1;
|
| 1101 | #endif
|
| 1102 |
|
| 1103 | /* Determine user_id to be put in Contact */
|
| 1104 | if (global.local_uri.slen) {
|
| 1105 | pj_pool_t *pool = pj_pool_create(global.pf, "parser", 1024, 0, NULL);
|
| 1106 | pjsip_uri *uri;
|
| 1107 |
|
| 1108 | uri = pjsip_parse_uri(pool, global.local_uri.ptr, global.local_uri.slen, 0);
|
| 1109 | if (uri) {
|
| 1110 | if (pj_stricmp2(pjsip_uri_get_scheme(uri), "sip")==0) {
|
| 1111 | pjsip_url *url = (pjsip_url*)pjsip_uri_get_uri(uri);
|
| 1112 | if (url->user.slen)
|
| 1113 | strncpy(global.user_id, url->user.ptr, url->user.slen);
|
| 1114 | }
|
| 1115 | }
|
| 1116 | pj_pool_release(pool);
|
| 1117 | }
|
| 1118 |
|
| 1119 | if (global.user_id[0]=='\0') {
|
| 1120 | pj_native_strcpy(global.user_id, "user");
|
| 1121 | }
|
| 1122 |
|
| 1123 | /* build contact */
|
| 1124 | global.real_contact.ptr = local_uri;
|
| 1125 | global.real_contact.slen =
|
| 1126 | sprintf(local_uri, "<sip:%s@%s:%d>", global.user_id, local_addr, global.sip_port);
|
| 1127 |
|
| 1128 | if (global.contact.slen == 0)
|
| 1129 | global.contact = global.real_contact;
|
| 1130 |
|
| 1131 | /* initialize local_uri with contact if it's not specified in cmdline */
|
| 1132 | if (global.local_uri.slen == 0)
|
| 1133 | global.local_uri = global.contact;
|
| 1134 |
|
| 1135 | /* Init proxy. */
|
| 1136 | if (global.proxy.slen || global.outbound_proxy.slen) {
|
| 1137 | int count = 0;
|
| 1138 | pj_str_t proxy_url[2];
|
| 1139 |
|
| 1140 | if (global.outbound_proxy.slen) {
|
| 1141 | proxy_url[count++] = global.outbound_proxy;
|
| 1142 | }
|
| 1143 | if (global.proxy.slen) {
|
| 1144 | proxy_url[count++] = global.proxy;
|
| 1145 | }
|
| 1146 |
|
| 1147 | if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) {
|
| 1148 | PJ_LOG(2,(THIS_FILE, "Error setting proxy address!"));
|
| 1149 | return -1;
|
| 1150 | }
|
| 1151 | }
|
| 1152 |
|
| 1153 | /* initialize SIP registration if registrar is configured */
|
| 1154 | if (global.registrar_uri.slen) {
|
| 1155 | global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb);
|
| 1156 | pjsip_regc_init( global.regc, &global.registrar_uri,
|
| 1157 | &global.local_uri,
|
| 1158 | &global.local_uri,
|
| 1159 | 1, &global.contact,
|
| 1160 | global.reg_timeout);
|
| 1161 | pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info );
|
| 1162 | }
|
| 1163 |
|
| 1164 | return PJ_SUCCESS;
|
| 1165 | }
|
| 1166 |
|
| 1167 | /* Worker thread function, only used when threading is enabled. */
|
| 1168 | static void *PJ_THREAD_FUNC worker_thread(void *unused)
|
| 1169 | {
|
| 1170 | PJ_UNUSED_ARG(unused)
|
| 1171 |
|
| 1172 | while (!global.worker_quit_flag) {
|
| 1173 | pj_time_val timeout = { 0, 10 };
|
| 1174 | pjsip_endpt_handle_events (global.endpt, &timeout);
|
| 1175 | }
|
| 1176 | return NULL;
|
| 1177 | }
|
| 1178 |
|
| 1179 |
|
| 1180 | /* Make call to the specified URI. */
|
| 1181 | static pjsip_dlg *make_call(pj_str_t *remote_uri)
|
| 1182 | {
|
| 1183 | pjsip_dlg *dlg;
|
| 1184 | pj_str_t local = global.contact;
|
| 1185 | pj_str_t remote = *remote_uri;
|
| 1186 | struct dialog_data *dlg_data;
|
| 1187 | pjsip_tx_data *tdata;
|
| 1188 | pj_media_sock_info sock_info;
|
| 1189 |
|
| 1190 | /* Create new dialog instance. */
|
| 1191 | dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC);
|
| 1192 |
|
| 1193 | /* Attach our own user data. */
|
| 1194 | dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data));
|
| 1195 | dlg->user_data = dlg_data;
|
| 1196 |
|
| 1197 | /* Create media session. */
|
| 1198 | pj_memset(&sock_info, 0, sizeof(sock_info));
|
| 1199 | sock_info.rtp_sock = global.rtp_sock;
|
| 1200 | sock_info.rtcp_sock = global.rtcp_sock;
|
| 1201 | pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in));
|
| 1202 |
|
| 1203 | dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info);
|
| 1204 | dlg_data->x_ms_msg_session = -1;
|
| 1205 |
|
| 1206 | if (global.offer_x_ms_msg) {
|
| 1207 | const pj_media_stream_info *minfo[32];
|
| 1208 | unsigned cnt;
|
| 1209 |
|
| 1210 | cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo);
|
| 1211 | if (cnt > 0)
|
| 1212 | dlg_data->x_ms_msg_session = cnt;
|
| 1213 | }
|
| 1214 |
|
| 1215 | /* Initialize dialog with local and remote URI. */
|
| 1216 | if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) {
|
| 1217 | pjsip_ua_destroy_dialog(dlg);
|
| 1218 | return NULL;
|
| 1219 | }
|
| 1220 |
|
| 1221 | /* Initialize credentials. */
|
| 1222 | pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info);
|
| 1223 |
|
| 1224 | /* Send INVITE! */
|
| 1225 | tdata = pjsip_dlg_invite(dlg);
|
| 1226 | tdata->msg->body = create_msg_body (dlg, 0);
|
| 1227 |
|
| 1228 | if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) {
|
| 1229 | pjsip_ua_destroy_dialog(dlg);
|
| 1230 | return NULL;
|
| 1231 | }
|
| 1232 |
|
| 1233 | return dlg;
|
| 1234 | }
|
| 1235 |
|
| 1236 | /*
|
| 1237 | * Callback to receive incoming IM message.
|
| 1238 | */
|
| 1239 | static int on_incoming_im_msg(pjsip_rx_data *rdata)
|
| 1240 | {
|
| 1241 | pjsip_msg *msg = rdata->msg;
|
| 1242 | pjsip_msg_body *body = msg->body;
|
| 1243 | int len;
|
| 1244 | char to[128], from[128];
|
| 1245 |
|
| 1246 |
|
| 1247 | len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
|
| 1248 | rdata->from->uri, from, sizeof(from));
|
| 1249 | if (len > 0) from[len] = '\0';
|
| 1250 | else pj_native_strcpy(from, "<URL too long..>");
|
| 1251 |
|
| 1252 | len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
|
| 1253 | rdata->to->uri, to, sizeof(to));
|
| 1254 | if (len > 0) to[len] = '\0';
|
| 1255 | else pj_native_strcpy(to, "<URL too long..>");
|
| 1256 |
|
| 1257 | PJ_LOG(3,(THIS_FILE, "Incoming instant message:"));
|
| 1258 |
|
| 1259 | printf("----- BEGIN INSTANT MESSAGE ----->\n");
|
| 1260 | printf("From:\t%s\n", from);
|
| 1261 | printf("To:\t%s\n", to);
|
| 1262 | printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : ""));
|
| 1263 | printf("<------ END INSTANT MESSAGE ------\n");
|
| 1264 |
|
| 1265 | fflush(stdout);
|
| 1266 |
|
| 1267 | /* Must answer with final response. */
|
| 1268 | return 200;
|
| 1269 | }
|
| 1270 |
|
| 1271 | /*
|
| 1272 | * Input URL.
|
| 1273 | */
|
| 1274 | static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection)
|
| 1275 | {
|
| 1276 | int i;
|
| 1277 |
|
| 1278 | *selection = -1;
|
| 1279 |
|
| 1280 | printf("\nBuddy list:\n");
|
| 1281 | printf("---------------------------------------\n");
|
| 1282 | for (i=0; i<global.buddy_cnt; ++i) {
|
| 1283 | printf(" %d\t%s <%s>\n", i+1, global.buddy[i].ptr,
|
| 1284 | (global.buddy_status[i]?"Online":"Offline"));
|
| 1285 | }
|
| 1286 | printf("-------------------------------------\n");
|
| 1287 |
|
| 1288 | printf("Choices\n"
|
| 1289 | "\t0 For current dialog.\n"
|
| 1290 | "\t[1-%02d] Select from buddy list\n"
|
| 1291 | "\tURL An URL\n"
|
| 1292 | , global.buddy_cnt);
|
| 1293 | printf("Input: ");
|
| 1294 |
|
| 1295 | fflush(stdout);
|
| 1296 | fgets(buf, len, stdin);
|
| 1297 | buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */
|
| 1298 |
|
| 1299 | while (isspace(*buf)) ++buf;
|
| 1300 |
|
| 1301 | if (!*buf || *buf=='\n' || *buf=='\r')
|
| 1302 | return NULL;
|
| 1303 |
|
| 1304 | i = atoi(buf);
|
| 1305 |
|
| 1306 | if (i == 0) {
|
| 1307 | if (isdigit(*buf)) {
|
| 1308 | *selection = 0;
|
| 1309 | *out = pj_str("0");
|
| 1310 | return out;
|
| 1311 | } else {
|
| 1312 | if (verify_sip_url(buf) != 0) {
|
| 1313 | puts("Invalid URL specified!");
|
| 1314 | return NULL;
|
| 1315 | }
|
| 1316 | *out = pj_str(buf);
|
| 1317 | return out;
|
| 1318 | }
|
| 1319 | } else if (i > global.buddy_cnt || i < 0) {
|
| 1320 | printf("Error: invalid selection!\n");
|
| 1321 | return NULL;
|
| 1322 | } else {
|
| 1323 | *out = global.buddy[i-1];
|
| 1324 | *selection = i;
|
| 1325 | return out;
|
| 1326 | }
|
| 1327 | }
|
| 1328 |
|
| 1329 |
|
| 1330 | static void generic_request_callback( void *token, pjsip_event *event )
|
| 1331 | {
|
| 1332 | pjsip_transaction *tsx = event->obj.tsx;
|
| 1333 |
|
| 1334 | PJ_UNUSED_ARG(token)
|
| 1335 |
|
| 1336 | if (tsx->status_code/100 == 2) {
|
| 1337 | PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)",
|
| 1338 | event->obj.tsx->method.name.slen,
|
| 1339 | event->obj.tsx->method.name.ptr,
|
| 1340 | tsx->status_code,
|
| 1341 | pjsip_get_status_text(tsx->status_code)->ptr));
|
| 1342 | } else if (tsx->status_code==401 || tsx->status_code==407) {
|
| 1343 | pjsip_tx_data *tdata;
|
| 1344 | tdata = pjsip_auth_reinit_req( global.endpt,
|
| 1345 | global.pool, NULL, global.cred_count, global.cred_info,
|
| 1346 | tsx->last_tx, event->src.rdata);
|
| 1347 | if (tdata) {
|
| 1348 | int rc;
|
| 1349 | pjsip_cseq_hdr *cseq;
|
| 1350 | cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
|
| 1351 | cseq->cseq++;
|
| 1352 | rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL,
|
| 1353 | &generic_request_callback);
|
| 1354 | if (rc == 0)
|
| 1355 | return;
|
| 1356 | }
|
| 1357 | PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)",
|
| 1358 | event->obj.tsx->method.name.slen,
|
| 1359 | event->obj.tsx->method.name.ptr,
|
| 1360 | event->obj.tsx->status_code,
|
| 1361 | pjsip_get_status_text(event->obj.tsx->status_code)->ptr));
|
| 1362 | } else {
|
| 1363 | const pj_str_t *reason;
|
| 1364 | if (event->src_type == PJSIP_EVENT_RX_MSG)
|
| 1365 | reason = &event->src.rdata->msg->line.status.reason;
|
| 1366 | else
|
| 1367 | reason = pjsip_get_status_text(tsx->status_code);
|
| 1368 | PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)",
|
| 1369 | event->obj.tsx->method.name.slen,
|
| 1370 | event->obj.tsx->method.name.ptr,
|
| 1371 | event->obj.tsx->status_code,
|
| 1372 | reason->slen, reason->ptr));
|
| 1373 | }
|
| 1374 | }
|
| 1375 |
|
| 1376 |
|
| 1377 | static void ui_send_im_message()
|
| 1378 | {
|
| 1379 | char line[100];
|
| 1380 | char text_buf[100];
|
| 1381 | pj_str_t str;
|
| 1382 | pj_str_t text_msg;
|
| 1383 | int selection, rc;
|
| 1384 | pjsip_tx_data *tdata;
|
| 1385 |
|
| 1386 | if (ui_input_url(&str, line, sizeof(line), &selection) == NULL)
|
| 1387 | return;
|
| 1388 |
|
| 1389 |
|
| 1390 | printf("Enter text to send (empty to cancel): "); fflush(stdout);
|
| 1391 | fgets(text_buf, sizeof(text_buf), stdin);
|
| 1392 | text_buf[strlen(text_buf)-1] = '\0';
|
| 1393 | if (!*text_buf)
|
| 1394 | return;
|
| 1395 |
|
| 1396 | text_msg = pj_str(text_buf);
|
| 1397 |
|
| 1398 | if (selection==0) {
|
| 1399 | pjsip_method message_method;
|
| 1400 | pj_str_t str_MESSAGE = { "MESSAGE", 7 };
|
| 1401 |
|
| 1402 | /* Send IM to current dialog. */
|
| 1403 | if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
|
| 1404 | printf("No current dialog or dialog state is not ESTABLISHED!\n");
|
| 1405 | return;
|
| 1406 | }
|
| 1407 |
|
| 1408 | pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE);
|
| 1409 | tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 );
|
| 1410 |
|
| 1411 | if (tdata) {
|
| 1412 | /* Create message body for the text. */
|
| 1413 | pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body));
|
| 1414 | body->content_type.type = pj_str("text");
|
| 1415 | body->content_type.subtype = pj_str("plain");
|
| 1416 | body->data = pj_pool_alloc(tdata->pool, text_msg.slen);
|
| 1417 | pj_memcpy(body->data, text_msg.ptr, text_msg.slen);
|
| 1418 | body->len = text_msg.slen;
|
| 1419 | body->print_body = &pjsip_print_text_body;
|
| 1420 |
|
| 1421 | /* Assign body to message, and send the message! */
|
| 1422 | tdata->msg->body = body;
|
| 1423 | pjsip_dlg_send_msg( global.cur_dlg, tdata );
|
| 1424 | }
|
| 1425 |
|
| 1426 | } else {
|
| 1427 | /* Send IM to buddy list. */
|
| 1428 | pjsip_method message;
|
| 1429 | static pj_str_t MESSAGE = { "MESSAGE", 7 };
|
| 1430 | pjsip_method_init_np(&message, &MESSAGE);
|
| 1431 | tdata = pjsip_endpt_create_request(global.endpt, &message,
|
| 1432 | &str,
|
| 1433 | &global.real_contact,
|
| 1434 | &str, &global.real_contact, NULL, -1,
|
| 1435 | &text_msg);
|
| 1436 | if (!tdata) {
|
| 1437 | puts("Error creating request");
|
| 1438 | return;
|
| 1439 | }
|
| 1440 | rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback);
|
| 1441 | if (rc == 0) {
|
| 1442 | printf("Sending IM message %d\n", global.im_counter);
|
| 1443 | ++global.im_counter;
|
| 1444 | } else {
|
| 1445 | printf("Error: unable to send IM message!\n");
|
| 1446 | }
|
| 1447 | }
|
| 1448 | }
|
| 1449 |
|
| 1450 | static void ui_send_options()
|
| 1451 | {
|
| 1452 | char line[100];
|
| 1453 | pj_str_t str;
|
| 1454 | int selection, rc;
|
| 1455 | pjsip_tx_data *tdata;
|
| 1456 | pjsip_method options;
|
| 1457 |
|
| 1458 | if (ui_input_url(&str, line, sizeof(line), &selection) == NULL)
|
| 1459 | return;
|
| 1460 |
|
| 1461 | pjsip_method_set( &options, PJSIP_OPTIONS_METHOD );
|
| 1462 |
|
| 1463 | if (selection == 0) {
|
| 1464 | /* Send OPTIONS to current dialog. */
|
| 1465 | tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1);
|
| 1466 | if (tdata)
|
| 1467 | pjsip_dlg_send_msg( global.cur_dlg, tdata );
|
| 1468 | } else {
|
| 1469 | /* Send OPTIONS to arbitrary party. */
|
| 1470 | tdata = pjsip_endpt_create_request( global.endpt, &options,
|
| 1471 | &str,
|
| 1472 | &global.local_uri, &str,
|
| 1473 | &global.real_contact,
|
| 1474 | NULL, -1, NULL);
|
| 1475 | if (tdata) {
|
| 1476 | rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL,
|
| 1477 | &generic_request_callback);
|
| 1478 | if (rc != 0)
|
| 1479 | PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!"));
|
| 1480 | }
|
| 1481 | }
|
| 1482 | }
|
| 1483 |
|
| 1484 | static void init_presence()
|
| 1485 | {
|
| 1486 | const pjsip_presence_cb pres_cb = {
|
| 1487 | NULL,
|
| 1488 | &pres_on_received_request,
|
| 1489 | &pres_on_received_refresh,
|
| 1490 | &pres_on_received_update,
|
| 1491 | &pres_on_terminated
|
| 1492 | };
|
| 1493 |
|
| 1494 | pjsip_presence_init(&pres_cb);
|
| 1495 | }
|
| 1496 |
|
| 1497 | /* Subscribe presence information for all buddies. */
|
| 1498 | static void subscribe_buddies_presence()
|
| 1499 | {
|
| 1500 | int i;
|
| 1501 | for (i=0; i<global.buddy_cnt; ++i) {
|
| 1502 | pjsip_presentity *pres;
|
| 1503 | if (global.buddy_pres[i])
|
| 1504 | continue;
|
| 1505 | pres = pjsip_presence_create( global.endpt, &global.local_uri,
|
| 1506 | &global.buddy[i], PRESENCE_TIMEOUT, (void*)i);
|
| 1507 | if (pres) {
|
| 1508 | pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info );
|
| 1509 | pjsip_presence_subscribe( pres );
|
| 1510 | }
|
| 1511 | global.buddy_pres[i] = pres;
|
| 1512 | }
|
| 1513 | }
|
| 1514 |
|
| 1515 | /* Unsubscribe presence information for all buddies. */
|
| 1516 | static void unsubscribe_buddies_presence()
|
| 1517 | {
|
| 1518 | int i;
|
| 1519 | for (i=0; i<global.buddy_cnt; ++i) {
|
| 1520 | pjsip_presentity *pres = global.buddy_pres[i];
|
| 1521 | if (pres) {
|
| 1522 | pjsip_presence_unsubscribe(pres);
|
| 1523 | pjsip_presence_destroy(pres);
|
| 1524 | global.buddy_pres[i] = NULL;
|
| 1525 | }
|
| 1526 | }
|
| 1527 | }
|
| 1528 |
|
| 1529 | /* Unsubscribe presence. */
|
| 1530 | static void unsubscribe_presence()
|
| 1531 | {
|
| 1532 | int i;
|
| 1533 |
|
| 1534 | unsubscribe_buddies_presence();
|
| 1535 | for (i=0; i<global.pres_cnt; ++i) {
|
| 1536 | pjsip_presentity *pres = global.pres[i];
|
| 1537 | pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 0);
|
| 1538 | pjsip_presence_destroy( pres );
|
| 1539 | }
|
| 1540 | }
|
| 1541 |
|
| 1542 | /* Advertise online status to subscribers. */
|
| 1543 | static void update_im_status()
|
| 1544 | {
|
| 1545 | int i;
|
| 1546 | for (i=0; i<global.pres_cnt; ++i) {
|
| 1547 | pjsip_presentity *pres = global.pres[i];
|
| 1548 | pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_ACTIVE,
|
| 1549 | !global.hide_status);
|
| 1550 | }
|
| 1551 | }
|
| 1552 |
|
| 1553 | /*
|
| 1554 | * Main program.
|
| 1555 | */
|
| 1556 | int main(int argc, char *argv[])
|
| 1557 | {
|
| 1558 | /* set to WORKER_COUNT+1 to avoid zero size warning
|
| 1559 | * when threading is disabled. */
|
| 1560 | pj_thread_t *thread[WORKER_COUNT+1];
|
| 1561 | pj_caching_pool cp;
|
| 1562 | int i;
|
| 1563 |
|
| 1564 | global.sip_port = 5060;
|
| 1565 | global.auto_answer = -1;
|
| 1566 | global.auto_hangup = -1;
|
| 1567 | global.app_log_level = 3;
|
| 1568 |
|
| 1569 | pj_log_set_level(4);
|
| 1570 | pj_log_set_log_func(&log_function);
|
| 1571 |
|
| 1572 | /* Init PJLIB */
|
| 1573 | if (pj_init() != PJ_SUCCESS)
|
| 1574 | return 1;
|
| 1575 |
|
| 1576 | /* Init caching pool. */
|
| 1577 | pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
|
| 1578 | global.pf = &cp.factory;
|
| 1579 |
|
| 1580 | /* Create memory pool for application. */
|
| 1581 | global.pool = pj_pool_create(global.pf, "main", 1024, 0, NULL);
|
| 1582 |
|
| 1583 | /* Parse command line arguments. */
|
| 1584 | if (parse_args(global.pool, argc, argv) != PJ_SUCCESS) {
|
| 1585 | pj_caching_pool_destroy(&cp);
|
| 1586 | return 1;
|
| 1587 | }
|
| 1588 |
|
| 1589 | /* Init sockets */
|
| 1590 | if (init_sockets() != 0) {
|
| 1591 | pj_caching_pool_destroy(&cp);
|
| 1592 | return 1;
|
| 1593 | }
|
| 1594 |
|
| 1595 | /* Initialize stack. */
|
| 1596 | if (init_stack() != PJ_SUCCESS) {
|
| 1597 | pj_caching_pool_destroy(&cp);
|
| 1598 | return 1;
|
| 1599 | }
|
| 1600 |
|
| 1601 | /* Set callback to receive incoming IM */
|
| 1602 | pjsip_messaging_set_incoming_callback( &on_incoming_im_msg );
|
| 1603 |
|
| 1604 | /* Set default worker count (can be zero) */
|
| 1605 | global.worker_cnt = WORKER_COUNT;
|
| 1606 |
|
| 1607 | /* Create user worker thread(s), only when threading is enabled. */
|
| 1608 | for (i=0; i<global.worker_cnt; ++i) {
|
| 1609 | thread[i] = pj_thread_create( global.pool, "sip%p",
|
| 1610 | &worker_thread,
|
| 1611 | NULL, 0, NULL, 0);
|
| 1612 | if (thread == NULL) {
|
| 1613 | global.worker_quit_flag = 1;
|
| 1614 | for (--i; i>=0; --i) {
|
| 1615 | pj_thread_join(thread[i]);
|
| 1616 | pj_thread_destroy(thread[i]);
|
| 1617 | }
|
| 1618 | pj_caching_pool_destroy(&cp);
|
| 1619 | return 1;
|
| 1620 | }
|
| 1621 | }
|
| 1622 |
|
| 1623 | printf("Worker thread count: %d\n", global.worker_cnt);
|
| 1624 |
|
| 1625 | /* Perform registration, if required. */
|
| 1626 | if (global.regc) {
|
| 1627 | update_registration(global.regc, 1);
|
| 1628 | }
|
| 1629 |
|
| 1630 | /* Initialize media manager. */
|
| 1631 | global.mmgr = pj_med_mgr_create(global.pf);
|
| 1632 |
|
| 1633 | /* Init presence. */
|
| 1634 | init_presence();
|
| 1635 |
|
| 1636 | /* Subscribe presence information of all buddies. */
|
| 1637 | if (!global.no_presence)
|
| 1638 | subscribe_buddies_presence();
|
| 1639 |
|
| 1640 | /* Initializatio completes, loop waiting for commands. */
|
| 1641 | for (;!global.worker_quit_flag;) {
|
| 1642 | pj_str_t str;
|
| 1643 | char line[128];
|
| 1644 |
|
| 1645 | #if WORKER_COUNT==0
|
| 1646 | /* If worker thread does not exist, main thread must poll for evetns.
|
| 1647 | * But this won't work very well since main thread is blocked by
|
| 1648 | * fgets(). So keep pressing the ENTER key to get the events!
|
| 1649 | */
|
| 1650 | pj_time_val timeout = { 0, 100 };
|
| 1651 | pjsip_endpt_handle_events(global.endpt, &timeout);
|
| 1652 | puts("Keep pressing ENTER key to get the events!");
|
| 1653 | #endif
|
| 1654 |
|
| 1655 | printf("\nCurrent dialog: ");
|
| 1656 | print_dialog(global.cur_dlg);
|
| 1657 | puts("");
|
| 1658 |
|
| 1659 | keystroke_help();
|
| 1660 |
|
| 1661 | fgets(line, sizeof(line), stdin);
|
| 1662 |
|
| 1663 | switch (*line) {
|
| 1664 | case 'm':
|
| 1665 | puts("Make outgoing call");
|
| 1666 | if (ui_input_url(&str, line, sizeof(line), &i) != NULL) {
|
| 1667 | pjsip_dlg *dlg = make_call(&str);
|
| 1668 | if (global.cur_dlg == NULL) {
|
| 1669 | global.cur_dlg = dlg;
|
| 1670 | }
|
| 1671 | }
|
| 1672 | break;
|
| 1673 | case 'i':
|
| 1674 | puts("Send Instant Messaging");
|
| 1675 | ui_send_im_message();
|
| 1676 | break;
|
| 1677 | case 'o':
|
| 1678 | puts("Send OPTIONS");
|
| 1679 | ui_send_options();
|
| 1680 | break;
|
| 1681 | case 'a':
|
| 1682 | if (global.cur_dlg) {
|
| 1683 | unsigned code;
|
| 1684 | pjsip_tx_data *tdata;
|
| 1685 | struct dialog_data *dlg_data = global.cur_dlg->user_data;
|
| 1686 |
|
| 1687 | printf("Answer with status code (1xx-6xx): ");
|
| 1688 | fflush(stdout);
|
| 1689 | fgets(line, sizeof(line), stdin);
|
| 1690 | str = pj_str(line);
|
| 1691 | str.slen -= 1;
|
| 1692 |
|
| 1693 | code = pj_strtoul(&str);
|
| 1694 | tdata = pjsip_dlg_answer(global.cur_dlg, code);
|
| 1695 | if (tdata) {
|
| 1696 | if (code/100 == 2) {
|
| 1697 | tdata->msg->body = dlg_data->body;
|
| 1698 | }
|
| 1699 | pjsip_dlg_send_msg(global.cur_dlg, tdata);
|
| 1700 |
|
| 1701 | }
|
| 1702 | } else {
|
| 1703 | puts("No current dialog");
|
| 1704 | }
|
| 1705 | break;
|
| 1706 | case 'h':
|
| 1707 | if (global.cur_dlg) {
|
| 1708 | pjsip_tx_data *tdata;
|
| 1709 | tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE);
|
| 1710 | if (tdata) {
|
| 1711 | pjsip_dlg_send_msg(global.cur_dlg, tdata);
|
| 1712 | }
|
| 1713 | } else {
|
| 1714 | puts("No current dialog");
|
| 1715 | }
|
| 1716 | break;
|
| 1717 | case ']':
|
| 1718 | if (global.cur_dlg) {
|
| 1719 | global.cur_dlg = global.cur_dlg->next;
|
| 1720 | if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
|
| 1721 | global.cur_dlg = global.cur_dlg->next;
|
| 1722 | }
|
| 1723 | } else {
|
| 1724 | puts("No current dialog");
|
| 1725 | }
|
| 1726 | break;
|
| 1727 | case '[':
|
| 1728 | if (global.cur_dlg) {
|
| 1729 | global.cur_dlg = global.cur_dlg->prev;
|
| 1730 | if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
|
| 1731 | global.cur_dlg = global.cur_dlg->prev;
|
| 1732 | }
|
| 1733 | } else {
|
| 1734 | puts("No current dialog");
|
| 1735 | }
|
| 1736 | break;
|
| 1737 | case 'd':
|
| 1738 | pjsip_endpt_dump(global.endpt, *(line+1)=='1');
|
| 1739 | pjsip_ua_dump(global.user_agent);
|
| 1740 | break;
|
| 1741 | case 's':
|
| 1742 | if (*(line+1) == 'u')
|
| 1743 | subscribe_buddies_presence();
|
| 1744 | break;
|
| 1745 | case 'u':
|
| 1746 | if (*(line+1) == 's')
|
| 1747 | unsubscribe_presence();
|
| 1748 | break;
|
| 1749 | case 't':
|
| 1750 | global.hide_status = !global.hide_status;
|
| 1751 | update_im_status();
|
| 1752 | break;
|
| 1753 | case 'q':
|
| 1754 | goto on_exit;
|
| 1755 | case 'l':
|
| 1756 | print_all_dialogs();
|
| 1757 | break;
|
| 1758 | }
|
| 1759 | }
|
| 1760 |
|
| 1761 | on_exit:
|
| 1762 | /* Unregister, if required. */
|
| 1763 | if (global.regc) {
|
| 1764 | update_registration(global.regc, 0);
|
| 1765 | }
|
| 1766 |
|
| 1767 | /* Unsubscribe presence. */
|
| 1768 | unsubscribe_presence();
|
| 1769 |
|
| 1770 | /* Allow one second to get all events. */
|
| 1771 | if (1) {
|
| 1772 | pj_time_val end_time;
|
| 1773 |
|
| 1774 | pj_gettimeofday(&end_time);
|
| 1775 | end_time.sec++;
|
| 1776 |
|
| 1777 | PJ_LOG(3,(THIS_FILE, "Shutting down.."));
|
| 1778 | for (;;) {
|
| 1779 | pj_time_val timeout = { 0, 20 }, now;
|
| 1780 | pjsip_endpt_handle_events (global.endpt, &timeout);
|
| 1781 | pj_gettimeofday(&now);
|
| 1782 | PJ_TIME_VAL_SUB(now, end_time);
|
| 1783 | if (now.sec >= 1)
|
| 1784 | break;
|
| 1785 | }
|
| 1786 | }
|
| 1787 |
|
| 1788 | global.worker_quit_flag = 1;
|
| 1789 |
|
| 1790 | pj_med_mgr_destroy(global.mmgr);
|
| 1791 |
|
| 1792 | /* Wait all threads to quit. */
|
| 1793 | for (i=0; i<global.worker_cnt; ++i) {
|
| 1794 | pj_thread_join(thread[i]);
|
| 1795 | pj_thread_destroy(thread[i]);
|
| 1796 | }
|
| 1797 |
|
| 1798 | /* Destroy endpoint. */
|
| 1799 | pjsip_endpt_destroy(global.endpt);
|
| 1800 |
|
| 1801 | /* Destroy caching pool. */
|
| 1802 | pj_caching_pool_destroy(&cp);
|
| 1803 |
|
| 1804 | /* Close log file, if any. */
|
| 1805 | if (global.log_file)
|
| 1806 | fclose(global.log_file);
|
| 1807 |
|
| 1808 | return 0;
|
| 1809 | }
|
| 1810 |
|
| 1811 | /*
|
| 1812 | * Register static modules to the endpoint.
|
| 1813 | */
|
| 1814 | pj_status_t register_static_modules( pj_size_t *count,
|
| 1815 | pjsip_module **modules )
|
| 1816 | {
|
| 1817 | /* Reset count. */
|
| 1818 | *count = 0;
|
| 1819 |
|
| 1820 | /* Register user agent module. */
|
| 1821 | modules[(*count)++] = pjsip_ua_get_module();
|
| 1822 | global.user_agent = modules[0]->mod_data;
|
| 1823 | modules[(*count)++] = pjsip_messaging_get_module();
|
| 1824 | modules[(*count)++] = pjsip_event_sub_get_module();
|
| 1825 |
|
| 1826 | return PJ_SUCCESS;
|
| 1827 | }
|