Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / pjsip-apps / src / samples / sipecho.c
1 /* $Id$ */
2 /* 
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
19  */
20
21
22 /**
23  * sipecho.c
24  *
25  * - Accepts incoming calls and echoes back SDP and any media.
26  * - Specify URI in cmdline argument to make call
27  * - Accepts registration too!
28  */
29
30 /* Include all headers. */
31 #include <pjsip.h>
32 #include <pjmedia/sdp.h>
33 #include <pjsip_ua.h>
34 #include <pjlib-util.h>
35 #include <pjlib.h>
36
37 /* For logging purpose. */
38 #define THIS_FILE   "sipecho.c"
39
40 #include "util.h"
41
42
43 /* Settings */
44 #define AF              pj_AF_INET() /* Change to pj_AF_INET6() for IPv6.
45                                       * PJ_HAS_IPV6 must be enabled and
46                                       * your system must support IPv6.  */
47 #define SIP_PORT        5060         /* Listening SIP port              */
48 #define MAX_CALLS       8
49
50 typedef struct call_t
51 {
52     pjsip_inv_session   *inv;
53 } call_t;
54
55 static struct app_t
56 {
57     pj_caching_pool      cp;
58     pj_pool_t           *pool;
59
60     pjsip_endpoint      *sip_endpt;
61     //pjmedia_endpt     *med_endpt;
62
63     call_t               call[MAX_CALLS];
64
65     pj_bool_t            quit;
66     pj_thread_t         *worker_thread;
67
68     pj_bool_t            enable_msg_logging;
69 } app;
70
71 /*
72  * Prototypes:
73  */
74
75 static void call_on_media_update(pjsip_inv_session *inv, pj_status_t status);
76 static void call_on_state_changed(pjsip_inv_session *inv, pjsip_event *e);
77 static void call_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer);
78 static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
79 static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
80
81
82 /* This is a PJSIP module to be registered by application to handle
83  * incoming requests outside any dialogs/transactions. The main purpose
84  * here is to handle incoming INVITE request message, where we will
85  * create a dialog and INVITE session for it.
86  */
87 static pjsip_module mod_sipecho =
88 {
89     NULL, NULL,                     /* prev, next.              */
90     { "mod-sipecho", 11 },          /* Name.                    */
91     -1,                             /* Id                       */
92     PJSIP_MOD_PRIORITY_APPLICATION, /* Priority                 */
93     NULL,                           /* load()                   */
94     NULL,                           /* start()                  */
95     NULL,                           /* stop()                   */
96     NULL,                           /* unload()                 */
97     &on_rx_request,                 /* on_rx_request()          */
98     NULL,                           /* on_rx_response()         */
99     NULL,                           /* on_tx_request.           */
100     NULL,                           /* on_tx_response()         */
101     NULL,                           /* on_tsx_state()           */
102 };
103
104 /* Notification on incoming messages */
105 static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
106 {
107     if (!app.enable_msg_logging)
108         return PJ_FALSE;
109
110     PJ_LOG(3,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
111                          "%.*s\n"
112                          "--end msg--",
113                          rdata->msg_info.len,
114                          pjsip_rx_data_get_info(rdata),
115                          rdata->tp_info.transport->type_name,
116                          rdata->pkt_info.src_name,
117                          rdata->pkt_info.src_port,
118                          (int)rdata->msg_info.len,
119                          rdata->msg_info.msg_buf));
120     return PJ_FALSE;
121 }
122
123 /* Notification on outgoing messages */
124 static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
125 {
126     if (!app.enable_msg_logging)
127         return PJ_SUCCESS;
128
129     PJ_LOG(3,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
130                          "%.*s\n"
131                          "--end msg--",
132                          (tdata->buf.cur - tdata->buf.start),
133                          pjsip_tx_data_get_info(tdata),
134                          tdata->tp_info.transport->type_name,
135                          tdata->tp_info.dst_name,
136                          tdata->tp_info.dst_port,
137                          (int)(tdata->buf.cur - tdata->buf.start),
138                          tdata->buf.start));
139     return PJ_SUCCESS;
140 }
141
142 /* The module instance. */
143 static pjsip_module msg_logger =
144 {
145     NULL, NULL,                         /* prev, next.          */
146     { "mod-msg-log", 13 },              /* Name.                */
147     -1,                                 /* Id                   */
148     PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority            */
149     NULL,                               /* load()               */
150     NULL,                               /* start()              */
151     NULL,                               /* stop()               */
152     NULL,                               /* unload()             */
153     &logging_on_rx_msg,                 /* on_rx_request()      */
154     &logging_on_rx_msg,                 /* on_rx_response()     */
155     &logging_on_tx_msg,                 /* on_tx_request.       */
156     &logging_on_tx_msg,                 /* on_tx_response()     */
157     NULL,                               /* on_tsx_state()       */
158
159 };
160
161 static int worker_proc(void *arg)
162 {
163     PJ_UNUSED_ARG(arg);
164
165     while (!app.quit) {
166         pj_time_val interval = { 0, 20 };
167         pjsip_endpt_handle_events(app.sip_endpt, &interval);
168     }
169
170     return 0;
171 }
172
173 static void hangup_all(void)
174 {
175     unsigned i;
176     for (i=0; i<MAX_CALLS; ++i) {
177         call_t *call = &app.call[i];
178
179         if (call->inv && call->inv->state <= PJSIP_INV_STATE_CONFIRMED) {
180             pj_status_t status;
181             pjsip_tx_data *tdata;
182
183             status = pjsip_inv_end_session(call->inv, PJSIP_SC_BUSY_HERE, NULL, &tdata);
184             if (status==PJ_SUCCESS && tdata)
185                 pjsip_inv_send_msg(call->inv, tdata);
186         }
187     }
188 }
189
190 static void destroy_stack(void)
191 {
192     enum { WAIT_CLEAR = 5000, WAIT_INTERVAL = 500 };
193     unsigned i;
194
195     PJ_LOG(3,(THIS_FILE, "Shutting down.."));
196
197     /* Wait until all clear */
198     hangup_all();
199     for (i=0; i<WAIT_CLEAR/WAIT_INTERVAL; ++i) {
200         unsigned j;
201
202         for (j=0; j<MAX_CALLS; ++j) {
203             call_t *call = &app.call[j];
204             if (call->inv && call->inv->state <= PJSIP_INV_STATE_CONFIRMED)
205                 break;
206         }
207
208         if (j==MAX_CALLS)
209             return;
210
211         pj_thread_sleep(WAIT_INTERVAL);
212     }
213
214     app.quit = PJ_TRUE;
215     if (app.worker_thread) {
216         pj_thread_join(app.worker_thread);
217         app.worker_thread = NULL;
218     }
219
220     //if (app.med_endpt)
221         //pjmedia_endpt_destroy(app.med_endpt);
222
223     if (app.sip_endpt)
224         pjsip_endpt_destroy(app.sip_endpt);
225
226     if (app.pool)
227         pj_pool_release(app.pool);
228
229     dump_pool_usage(THIS_FILE, &app.cp);
230     pj_caching_pool_destroy(&app.cp);
231 }
232
233 #define CHECK_STATUS()  do { if (status != PJ_SUCCESS) return status; } while (0)
234
235 static pj_status_t init_stack()
236 {
237     pj_sockaddr addr;
238     pjsip_inv_callback inv_cb;
239     pj_status_t status;
240
241     pj_log_set_level(5);
242
243     status = pj_init();
244     CHECK_STATUS();
245
246     pj_log_set_level(3);
247
248     status = pjlib_util_init();
249     CHECK_STATUS();
250
251     pj_caching_pool_init(&app.cp, NULL, 0);
252     app.pool = pj_pool_create( &app.cp.factory, "sipecho", 512, 512, 0);
253
254     status = pjsip_endpt_create(&app.cp.factory, NULL, &app.sip_endpt);
255     CHECK_STATUS();
256
257     pj_log_set_level(4);
258     pj_sockaddr_init(AF, &addr, NULL, (pj_uint16_t)SIP_PORT);
259     if (AF == pj_AF_INET()) {
260         status = pjsip_udp_transport_start( app.sip_endpt, &addr.ipv4, NULL,
261                                             1, NULL);
262     } else if (AF == pj_AF_INET6()) {
263         status = pjsip_udp_transport_start6(app.sip_endpt, &addr.ipv6, NULL,
264                                             1, NULL);
265     } else {
266         status = PJ_EAFNOTSUP;
267     }
268
269     pj_log_set_level(3);
270     CHECK_STATUS();
271
272     status = pjsip_tsx_layer_init_module(app.sip_endpt) ||
273              pjsip_ua_init_module( app.sip_endpt, NULL );
274     CHECK_STATUS();
275
276     pj_bzero(&inv_cb, sizeof(inv_cb));
277     inv_cb.on_state_changed = &call_on_state_changed;
278     inv_cb.on_new_session = &call_on_forked;
279     inv_cb.on_media_update = &call_on_media_update;
280     inv_cb.on_rx_offer = &call_on_rx_offer;
281
282     status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb) ||
283              pjsip_100rel_init_module(app.sip_endpt) ||
284              pjsip_endpt_register_module( app.sip_endpt, &mod_sipecho) ||
285              pjsip_endpt_register_module( app.sip_endpt, &msg_logger) ||
286              //pjmedia_endpt_create(&app.cp.factory,
287                 //                pjsip_endpt_get_ioqueue(app.sip_endpt),
288                 //                0, &app.med_endpt) ||
289              pj_thread_create(app.pool, "sipecho", &worker_proc, NULL, 0, 0,
290                               &app.worker_thread);
291     CHECK_STATUS();
292
293     return PJ_SUCCESS;
294 }
295
296 static void destroy_call(call_t *call)
297 {
298     call->inv = NULL;
299 }
300
301 static pjmedia_sdp_attr * find_remove_sdp_attrs(unsigned *cnt,
302                                                 pjmedia_sdp_attr *attr[],
303                                                 unsigned cnt_attr_to_remove,
304                                                 const char* attr_to_remove[])
305 {
306     pjmedia_sdp_attr *found_attr = NULL;
307     int i;
308
309     for (i=0; i<(int)*cnt; ++i) {
310         unsigned j;
311         for (j=0; j<cnt_attr_to_remove; ++j) {
312             if (pj_strcmp2(&attr[i]->name, attr_to_remove[j])==0) {
313                 if (!found_attr) found_attr = attr[i];
314                 pj_array_erase(attr, sizeof(attr[0]), *cnt, i);
315                 --(*cnt);
316                 --i;
317                 break;
318             }
319         }
320     }
321
322     return found_attr;
323 }
324
325 static pjmedia_sdp_session *create_answer(int call_num, pj_pool_t *pool,
326                                           const pjmedia_sdp_session *offer)
327 {
328     const char* dir_attrs[] = { "sendrecv", "sendonly", "recvonly", "inactive" };
329     const char *ice_attrs[] = {"ice-pwd", "ice-ufrag", "candidate"};
330     pjmedia_sdp_session *answer = pjmedia_sdp_session_clone(pool, offer);
331     pjmedia_sdp_attr *sess_dir_attr = NULL;
332     unsigned mi;
333
334     PJ_LOG(3,(THIS_FILE, "Call %d: creating answer:", call_num));
335
336     answer->name = pj_str("sipecho");
337     sess_dir_attr = find_remove_sdp_attrs(&answer->attr_count, answer->attr,
338                                           PJ_ARRAY_SIZE(dir_attrs),
339                                           dir_attrs);
340
341     for (mi=0; mi<answer->media_count; ++mi) {
342         pjmedia_sdp_media *m = answer->media[mi];
343         pjmedia_sdp_attr *m_dir_attr;
344         pjmedia_sdp_attr *dir_attr;
345         const char *our_dir = NULL;
346         pjmedia_sdp_conn *c;
347
348         /* Match direction */
349         m_dir_attr = find_remove_sdp_attrs(&m->attr_count, m->attr,
350                                            PJ_ARRAY_SIZE(dir_attrs),
351                                            dir_attrs);
352         dir_attr = m_dir_attr ? m_dir_attr : sess_dir_attr;
353
354         if (dir_attr) {
355             if (pj_strcmp2(&dir_attr->name, "sendonly")==0)
356                 our_dir = "recvonly";
357             else if (pj_strcmp2(&dir_attr->name, "inactive")==0)
358                 our_dir = "inactive";
359             else if (pj_strcmp2(&dir_attr->name, "recvonly")==0)
360                 our_dir = "inactive";
361
362             if (our_dir) {
363                 dir_attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
364                 dir_attr->name = pj_str((char*)our_dir);
365                 m->attr[m->attr_count++] = dir_attr;
366             }
367         }
368
369         /* Remove ICE attributes */
370         find_remove_sdp_attrs(&m->attr_count, m->attr, PJ_ARRAY_SIZE(ice_attrs), ice_attrs);
371
372         /* Done */
373         c = m->conn ? m->conn : answer->conn;
374         PJ_LOG(3,(THIS_FILE, "  Media %d, %.*s: %s <--> %.*s:%d",
375                   mi, (int)m->desc.media.slen, m->desc.media.ptr,
376                   (our_dir ? our_dir : "sendrecv"),
377                   (int)c->addr.slen, c->addr.ptr, m->desc.port));
378     }
379
380     return answer;
381 }
382
383 static void call_on_state_changed( pjsip_inv_session *inv, 
384                                    pjsip_event *e)
385 {
386     call_t *call = (call_t*)inv->mod_data[mod_sipecho.id];
387     if (!call)
388         return;
389
390     PJ_UNUSED_ARG(e);
391     if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
392         PJ_LOG(3,(THIS_FILE, "Call %d: DISCONNECTED [reason=%d (%s)]",
393                   call - app.call, inv->cause,
394                   pjsip_get_status_text(inv->cause)->ptr));
395         destroy_call(call);
396     } else {
397         PJ_LOG(3,(THIS_FILE, "Call %d: state changed to %s",
398                   call - app.call, pjsip_inv_state_name(inv->state)));
399     }
400 }
401
402 static void call_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer)
403 {
404     call_t *call = (call_t*) inv->mod_data[mod_sipecho.id];
405     pjsip_inv_set_sdp_answer(inv, create_answer(call - app.call, inv->pool_prov, offer));
406 }
407
408 static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
409 {
410     PJ_UNUSED_ARG(inv);
411     PJ_UNUSED_ARG(e);
412 }
413
414 static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
415 {
416     pj_sockaddr hostaddr;
417     char temp[80], hostip[PJ_INET6_ADDRSTRLEN];
418     pj_str_t local_uri;
419     pjsip_dialog *dlg;
420     pjsip_rdata_sdp_info *sdp_info;
421     pjmedia_sdp_session *answer = NULL;
422     pjsip_tx_data *tdata = NULL;
423     call_t *call = NULL;
424     unsigned i;
425     pj_status_t status;
426
427     PJ_LOG(3,(THIS_FILE, "RX %.*s from %s",
428               (int)rdata->msg_info.msg->line.req.method.name.slen,
429               rdata->msg_info.msg->line.req.method.name.ptr,
430               rdata->pkt_info.src_name));
431
432     if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD) {
433         /* Let me be a registrar! */
434         pjsip_hdr hdr_list, *h;
435         pjsip_msg *msg;
436         int expires = -1;
437
438         pj_list_init(&hdr_list);
439         msg = rdata->msg_info.msg;
440         h = (pjsip_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
441         if (h) {
442             expires = ((pjsip_expires_hdr*)h)->ivalue;
443             pj_list_push_back(&hdr_list, pjsip_hdr_clone(rdata->tp_info.pool, h));
444             PJ_LOG(3,(THIS_FILE, " Expires=%d", expires));
445         }
446         if (expires != 0) {
447             h = (pjsip_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
448             if (h)
449                 pj_list_push_back(&hdr_list, pjsip_hdr_clone(rdata->tp_info.pool, h));
450         }
451
452         pjsip_endpt_respond(app.sip_endpt, &mod_sipecho, rdata, 200, NULL,
453                             &hdr_list, NULL, NULL);
454         return PJ_TRUE;
455     }
456
457     if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
458         if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
459             pj_str_t reason = pj_str("Go away");
460             pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
461                                            400, &reason,
462                                            NULL, NULL);
463         }
464         return PJ_TRUE;
465     }
466
467     sdp_info = pjsip_rdata_get_sdp_info(rdata);
468     if (!sdp_info || !sdp_info->sdp) {
469         pj_str_t reason = pj_str("Require valid offer");
470         pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
471                                        400, &reason,
472                                        NULL, NULL);
473     }
474
475     for (i=0; i<MAX_CALLS; ++i) {
476         if (app.call[i].inv == NULL) {
477             call = &app.call[i];
478             break;
479         }
480     }
481
482     if (i==MAX_CALLS) {
483         pj_str_t reason = pj_str("We're full");
484         pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
485                                        PJSIP_SC_BUSY_HERE, &reason,
486                                        NULL, NULL);
487         return PJ_TRUE;
488     }
489
490     /* Generate Contact URI */
491     status = pj_gethostip(AF, &hostaddr);
492     if (status != PJ_SUCCESS) {
493         app_perror(THIS_FILE, "Unable to retrieve local host IP", status);
494         return PJ_TRUE;
495     }
496     pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);
497     pj_ansi_sprintf(temp, "<sip:sipecho@%s:%d>", hostip, SIP_PORT);
498     local_uri = pj_str(temp);
499
500     status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
501                                    &local_uri, &dlg);
502
503     if (status == PJ_SUCCESS)
504         answer = create_answer(call-app.call, dlg->pool, sdp_info->sdp);
505     if (status == PJ_SUCCESS)
506         status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &call->inv);
507     if (status == PJ_SUCCESS)
508         status = pjsip_inv_initial_answer(call->inv, rdata, 100,
509                                           NULL, NULL, &tdata);
510     if (status == PJ_SUCCESS)
511         status = pjsip_inv_send_msg(call->inv, tdata);
512
513     if (status == PJ_SUCCESS)
514         status = pjsip_inv_answer(call->inv, 180, NULL,
515                                   NULL, &tdata);
516     if (status == PJ_SUCCESS)
517         status = pjsip_inv_send_msg(call->inv, tdata);
518
519     if (status == PJ_SUCCESS)
520         status = pjsip_inv_answer(call->inv, 200, NULL,
521                                   NULL, &tdata);
522     if (status == PJ_SUCCESS)
523         status = pjsip_inv_send_msg(call->inv, tdata);
524
525     if (status != PJ_SUCCESS) {
526         pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
527                                        500, NULL, NULL, NULL);
528         destroy_call(call);
529     } else {
530         call->inv->mod_data[mod_sipecho.id] = call;
531     }
532
533     return PJ_TRUE;
534 }
535
536 static void call_on_media_update( pjsip_inv_session *inv,
537                                   pj_status_t status)
538 {
539     PJ_UNUSED_ARG(inv);
540     PJ_UNUSED_ARG(status);
541 }
542
543
544 /* main()
545  *
546  * If called with argument, treat argument as SIP URL to be called.
547  * Otherwise wait for incoming calls.
548  */
549 int main(int argc, char *argv[])
550 {
551     if (init_stack())
552         goto on_error;
553
554     /* If URL is specified, then make call immediately. */
555     if (argc > 1) {
556         pj_sockaddr hostaddr;
557         char hostip[PJ_INET6_ADDRSTRLEN+2];
558         char temp[80];
559         call_t *call;
560         pj_str_t dst_uri = pj_str(argv[1]);
561         pj_str_t local_uri;
562         pjsip_dialog *dlg;
563         pj_status_t status;
564         pjsip_tx_data *tdata;
565
566         if (pj_gethostip(AF, &hostaddr) != PJ_SUCCESS) {
567             PJ_LOG(1,(THIS_FILE, "Unable to retrieve local host IP"));
568             goto on_error;
569         }
570         pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);
571
572         pj_ansi_sprintf(temp, "<sip:sipecho@%s:%d>",
573                         hostip, SIP_PORT);
574         local_uri = pj_str(temp);
575
576         call = &app.call[0];
577
578         status = pjsip_dlg_create_uac( pjsip_ua_instance(),
579                                        &local_uri,  /* local URI */
580                                        &local_uri,  /* local Contact */
581                                        &dst_uri,    /* remote URI */
582                                        &dst_uri,    /* remote target */
583                                        &dlg);       /* dialog */
584         if (status != PJ_SUCCESS) {
585             app_perror(THIS_FILE, "Unable to create UAC dialog", status);
586             return 1;
587         }
588
589         status = pjsip_inv_create_uac( dlg, NULL, 0, &call->inv);
590         if (status != PJ_SUCCESS) goto on_error;
591
592         call->inv->mod_data[mod_sipecho.id] = call;
593
594         status = pjsip_inv_invite(call->inv, &tdata);
595         if (status != PJ_SUCCESS) goto on_error;
596
597         status = pjsip_inv_send_msg(call->inv, tdata);
598         if (status != PJ_SUCCESS) goto on_error;
599
600         puts("Press ENTER to quit...");
601     } else {
602         puts("Ready for incoming calls. Press ENTER to quit...");
603     }
604
605     for (;;) {
606         char s[10];
607
608         printf("\nMenu:\n"
609                "  h    Hangup all calls\n"
610                "  l    %s message logging\n"
611                "  q    Quit\n",
612                (app.enable_msg_logging? "Disable" : "Enable"));
613
614         if (fgets(s, sizeof(s), stdin) == NULL)
615             continue;
616
617         if (s[0]=='q')
618             break;
619         switch (s[0]) {
620         case 'l':
621             app.enable_msg_logging = !app.enable_msg_logging;
622             break;
623         case 'h':
624             hangup_all();
625             break;
626         }
627     }
628
629     destroy_stack();
630
631     puts("Bye bye..");
632     return 0;
633
634 on_error:
635     puts("An error has occurred. run a debugger..");
636     return 1;
637 }
638