blob: e4d1e1c8ba9d3f227e9306468e0a9a72a2b17e09 [file] [log] [blame]
Benny Prijonoe0312a72005-11-18 00:16:43 +00001/* $Id$ */
Benny Prijonoe7224612005-11-13 19:40:44 +00002/*
Benny Prijonoe0312a72005-11-18 00:16:43 +00003 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
Benny Prijonoe7224612005-11-13 19:40:44 +00004 *
Benny Prijonoe0312a72005-11-18 00:16:43 +00005 * 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 Prijonoe7224612005-11-13 19:40:44 +00009 *
Benny Prijonoe0312a72005-11-18 00:16:43 +000010 * This program is distributed in the hope that it will be useful,
Benny Prijonoe7224612005-11-13 19:40:44 +000011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Benny Prijonoe0312a72005-11-18 00:16:43 +000012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
Benny Prijonoe7224612005-11-13 19:40:44 +000014 *
Benny Prijonoe0312a72005-11-18 00:16:43 +000015 * 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 Prijonoe7224612005-11-13 19:40:44 +000018 */
Benny Prijonoe7224612005-11-13 19:40:44 +000019#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. */
44static 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
120enum { AUTO_ANSWER, AUTO_HANGUP };
121
122/* This is the data that will be 'attached' on per dialog basis. */
123struct 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 */
143static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
144 pjsip_event *event );
145static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx,
146 pjsip_tx_data *tdata, int retransmission);
147static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx,
148 pjsip_tx_data *tdata);
149static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx,
150 pjsip_rx_data *rdata);
151static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx,
152 pjsip_rx_data *rdata);
153static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx,
154 pjsip_tx_data *tdata);
155static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx,
156 pjsip_event *event);
157static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event);
158static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event);
159static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event);
160static void dlg_on_terminated (pjsip_dlg *dlg);
161static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event);
162
163/* The callback structure that will be registered to UA layer. */
164struct 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
186static 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
210static 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
225static 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
247static 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
286static 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 */
294static 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. */
305static 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. */
335static 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 */
344static 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 */
393static 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 */
407static 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. */
420static 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. */
430static 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 */
439static 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
452static 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. */
517static 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
609send_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 */
641static 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. */
667static 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. */
749static 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 */
798static 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. */
839static 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. */
853static 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. */
915static 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
1039on_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
1047static 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. */
1059static 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, &regc_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. */
1168static 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. */
1181static 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 */
1239static 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 */
1274static 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
1330static 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
1377static 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
1450static 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
1484static 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. */
1498static 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. */
1516static 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. */
1530static 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. */
1543static 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 */
1556int 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
1761on_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 */
1814pj_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}