blob: 7ae3188965651f5f81b822da3938f63765e7be3b [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $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#include <pjsua-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23
24#define THIS_FILE "pjsua_im.h"
25
26
27/* Declare MESSAGE method */
28/* We put PJSIP_MESSAGE_METHOD as the enum here, so that when
29 * somebody add that method into pjsip_method_e in sip_msg.h we
30 * will get an error here.
31 */
32enum
33{
34 PJSIP_MESSAGE_METHOD = PJSIP_OTHER_METHOD
35};
36
37const pjsip_method pjsip_message_method =
38{
39 (pjsip_method_e) PJSIP_MESSAGE_METHOD,
40 { "MESSAGE", 7 }
41};
42
43
44/* Proto */
45static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata);
46
47
48/* The module instance. */
49static pjsip_module mod_pjsua_im =
50{
51 NULL, NULL, /* prev, next. */
52 { "mod-pjsua-im", 12 }, /* Name. */
53 -1, /* Id */
54 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
55 NULL, /* load() */
56 NULL, /* start() */
57 NULL, /* stop() */
58 NULL, /* unload() */
59 &im_on_rx_request, /* on_rx_request() */
60 NULL, /* on_rx_response() */
61 NULL, /* on_tx_request. */
62 NULL, /* on_tx_response() */
63 NULL, /* on_tsx_state() */
64
65};
66
67
68/* MIME constants. */
69static const pj_str_t STR_MIME_APP = { "application", 11 };
70static const pj_str_t STR_MIME_ISCOMPOSING = { "im-iscomposing+xml", 18 };
71static const pj_str_t STR_MIME_TEXT = { "text", 4 };
72static const pj_str_t STR_MIME_PLAIN = { "plain", 5 };
73
74
75/* Check if content type is acceptable */
76#if 0
77static pj_bool_t acceptable_message(const pjsip_media_type *mime)
78{
79 return (pj_stricmp(&mime->type, &STR_MIME_TEXT)==0 &&
80 pj_stricmp(&mime->subtype, &STR_MIME_PLAIN)==0)
81 ||
82 (pj_stricmp(&mime->type, &STR_MIME_APP)==0 &&
83 pj_stricmp(&mime->subtype, &STR_MIME_ISCOMPOSING)==0);
84}
85#endif
86
87/**
88 * Create Accept header for MESSAGE.
89 */
90pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool)
91{
92 /* Create Accept header. */
93 pjsip_accept_hdr *accept;
94
95 accept = pjsip_accept_hdr_create(pool);
96 accept->values[0] = pj_str("text/plain");
97 accept->values[1] = pj_str("application/im-iscomposing+xml");
98 accept->count = 2;
99
100 return accept;
101}
102
103/**
104 * Private: check if we can accept the message.
105 */
106pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata,
107 pjsip_accept_hdr **p_accept_hdr)
108{
109 /* Some UA sends text/html, so this check will break */
110#if 0
111 pjsip_ctype_hdr *ctype;
112 pjsip_msg *msg;
113
114 msg = rdata->msg_info.msg;
115
116 /* Request MUST have message body, with Content-Type equal to
117 * "text/plain".
118 */
119 ctype = (pjsip_ctype_hdr*)
120 pjsip_msg_find_hdr(msg, PJSIP_H_CONTENT_TYPE, NULL);
121 if (msg->body == NULL || ctype == NULL ||
122 !acceptable_message(&ctype->media))
123 {
124 /* Create Accept header. */
125 if (p_accept_hdr)
126 *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool);
127
128 return PJ_FALSE;
129 }
130#elif 0
131 pjsip_msg *msg;
132
133 msg = rdata->msg_info.msg;
134 if (msg->body == NULL) {
135 /* Create Accept header. */
136 if (p_accept_hdr)
137 *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool);
138
139 return PJ_FALSE;
140 }
141#else
142 /* Ticket #693: allow incoming MESSAGE without message body */
143 PJ_UNUSED_ARG(rdata);
144 PJ_UNUSED_ARG(p_accept_hdr);
145#endif
146
147 return PJ_TRUE;
148}
149
150/**
151 * Private: process pager message.
152 * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing().
153 */
154void pjsua_im_process_pager(int call_id, const pj_str_t *from,
155 const pj_str_t *to, pjsip_rx_data *rdata)
156{
157 pjsip_contact_hdr *contact_hdr;
158 pj_str_t contact;
159 pjsip_msg_body *body = rdata->msg_info.msg->body;
160
161#if 0
162 /* Ticket #693: allow incoming MESSAGE without message body */
163 /* Body MUST have been checked before */
164 pj_assert(body != NULL);
165#endif
166
167
168 /* Build remote contact */
169 contact_hdr = (pjsip_contact_hdr*)
170 pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
171 NULL);
172 if (contact_hdr && contact_hdr->uri) {
173 contact.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool,
174 PJSIP_MAX_URL_SIZE);
175 contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
176 contact_hdr->uri, contact.ptr,
177 PJSIP_MAX_URL_SIZE);
178 } else {
179 contact.slen = 0;
180 }
181
182 if (body && pj_stricmp(&body->content_type.type, &STR_MIME_APP)==0 &&
183 pj_stricmp(&body->content_type.subtype, &STR_MIME_ISCOMPOSING)==0)
184 {
185 /* Expecting typing indication */
186 pj_status_t status;
187 pj_bool_t is_typing;
188
189 status = pjsip_iscomposing_parse(rdata->tp_info.pool, (char*)body->data,
190 body->len, &is_typing, NULL, NULL,
191 NULL );
192 if (status != PJ_SUCCESS) {
193 pjsua_perror(THIS_FILE, "Invalid MESSAGE body", status);
194 return;
195 }
196
197 if (pjsua_var.ua_cfg.cb.on_typing) {
198 (*pjsua_var.ua_cfg.cb.on_typing)(call_id, from, to, &contact,
199 is_typing);
200 }
201
202 if (pjsua_var.ua_cfg.cb.on_typing2) {
203 pjsua_acc_id acc_id;
204
205 if (call_id == PJSUA_INVALID_ID) {
206 acc_id = pjsua_acc_find_for_incoming(rdata);
207 } else {
208 pjsua_call *call = &pjsua_var.calls[call_id];
209 acc_id = call->acc_id;
210 }
211
212
213 (*pjsua_var.ua_cfg.cb.on_typing2)(call_id, from, to, &contact,
214 is_typing, rdata, acc_id);
215 }
216
217 } else {
218 pj_str_t mime_type;
219 char buf[256];
220 pjsip_media_type *m;
221 pj_str_t text_body;
222
223 /* Save text body */
224 if (body) {
225 text_body.ptr = (char*)rdata->msg_info.msg->body->data;
226 text_body.slen = rdata->msg_info.msg->body->len;
227
228 /* Get mime type */
229 m = &rdata->msg_info.msg->body->content_type;
230 mime_type.ptr = buf;
231 mime_type.slen = pj_ansi_snprintf(buf, sizeof(buf),
232 "%.*s/%.*s",
233 (int)m->type.slen,
234 m->type.ptr,
235 (int)m->subtype.slen,
236 m->subtype.ptr);
237 if (mime_type.slen < 1)
238 mime_type.slen = 0;
239
240
241 } else {
242 text_body.ptr = mime_type.ptr = "";
243 text_body.slen = mime_type.slen = 0;
244 }
245
246 if (pjsua_var.ua_cfg.cb.on_pager) {
247 (*pjsua_var.ua_cfg.cb.on_pager)(call_id, from, to, &contact,
248 &mime_type, &text_body);
249 }
250
251 if (pjsua_var.ua_cfg.cb.on_pager2) {
252 pjsua_acc_id acc_id;
253
254 if (call_id == PJSUA_INVALID_ID) {
255 acc_id = pjsua_acc_find_for_incoming(rdata);
256 } else {
257 pjsua_call *call = &pjsua_var.calls[call_id];
258 acc_id = call->acc_id;
259 }
260
261 (*pjsua_var.ua_cfg.cb.on_pager2)(call_id, from, to, &contact,
262 &mime_type, &text_body, rdata,
263 acc_id);
264 }
265 }
266}
267
268
269/*
270 * Handler to receive incoming MESSAGE
271 */
272static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata)
273{
274 pj_str_t from, to;
275 pjsip_accept_hdr *accept_hdr;
276 pjsip_msg *msg;
277 pj_status_t status;
278
279 msg = rdata->msg_info.msg;
280
281 /* Only want to handle MESSAGE requests. */
282 if (pjsip_method_cmp(&msg->line.req.method, &pjsip_message_method) != 0) {
283 return PJ_FALSE;
284 }
285
286
287 /* Should not have any transaction attached to rdata. */
288 PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_FALSE);
289
290 /* Should not have any dialog attached to rdata. */
291 PJ_ASSERT_RETURN(pjsip_rdata_get_dlg(rdata)==NULL, PJ_FALSE);
292
293 /* Check if we can accept the message. */
294 if (!pjsua_im_accept_pager(rdata, &accept_hdr)) {
295 pjsip_hdr hdr_list;
296
297 pj_list_init(&hdr_list);
298 pj_list_push_back(&hdr_list, accept_hdr);
299
300 pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
301 PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL,
302 &hdr_list, NULL);
303 return PJ_TRUE;
304 }
305
306 /* Respond with 200 first, so that remote doesn't retransmit in case
307 * the UI takes too long to process the message.
308 */
309 status = pjsip_endpt_respond( pjsua_var.endpt, NULL, rdata, 200, NULL,
310 NULL, NULL, NULL);
311
312 /* For the source URI, we use Contact header if present, since
313 * Contact header contains the port number information. If this is
314 * not available, then use From header.
315 */
316 from.ptr = (char*)pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE);
317 from.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
318 rdata->msg_info.from->uri,
319 from.ptr, PJSIP_MAX_URL_SIZE);
320
321 if (from.slen < 1)
322 from = pj_str("<--URI is too long-->");
323
324 /* Build the To text. */
325 to.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE);
326 to.slen = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR,
327 rdata->msg_info.to->uri,
328 to.ptr, PJSIP_MAX_URL_SIZE);
329 if (to.slen < 1)
330 to = pj_str("<--URI is too long-->");
331
332 /* Process pager. */
333 pjsua_im_process_pager(-1, &from, &to, rdata);
334
335 /* Done. */
336 return PJ_TRUE;
337}
338
339
340/* Outgoing IM callback. */
341static void im_callback(void *token, pjsip_event *e)
342{
343 pjsua_im_data *im_data = (pjsua_im_data*) token;
344
345 if (e->type == PJSIP_EVENT_TSX_STATE) {
346
347 pjsip_transaction *tsx = e->body.tsx_state.tsx;
348
349 /* Ignore provisional response, if any */
350 if (tsx->status_code < 200)
351 return;
352
353
354 /* Handle authentication challenges */
355 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
356 (tsx->status_code == 401 || tsx->status_code == 407))
357 {
358 pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
359 pjsip_tx_data *tdata;
360 pjsip_auth_clt_sess auth;
361 pj_status_t status;
362
363 PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
364
365 /* Create temporary authentication session */
366 pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
367
368 pjsip_auth_clt_set_credentials(&auth,
369 pjsua_var.acc[im_data->acc_id].cred_cnt,
370 pjsua_var.acc[im_data->acc_id].cred);
371
372 pjsip_auth_clt_set_prefs(&auth,
373 &pjsua_var.acc[im_data->acc_id].cfg.auth_pref);
374
375 status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
376 &tdata);
377 if (status == PJ_SUCCESS) {
378 pjsua_im_data *im_data2;
379
380 /* Must duplicate im_data */
381 im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
382
383 /* Increment CSeq */
384 PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++;
385
386 /* Re-send request */
387 status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
388 im_data2, &im_callback);
389 if (status == PJ_SUCCESS) {
390 /* Done */
391 return;
392 }
393 }
394 }
395
396 if (tsx->status_code/100 == 2) {
397 PJ_LOG(4,(THIS_FILE,
398 "Message \'%s\' delivered successfully",
399 im_data->body.ptr));
400 } else {
401 PJ_LOG(3,(THIS_FILE,
402 "Failed to deliver message \'%s\': %d/%.*s",
403 im_data->body.ptr,
404 tsx->status_code,
405 (int)tsx->status_text.slen,
406 tsx->status_text.ptr));
407 }
408
409 if (pjsua_var.ua_cfg.cb.on_pager_status) {
410 pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id,
411 &im_data->to,
412 &im_data->body,
413 im_data->user_data,
414 (pjsip_status_code)
415 tsx->status_code,
416 &tsx->status_text);
417 }
418
419 if (pjsua_var.ua_cfg.cb.on_pager_status2) {
420 pjsip_rx_data *rdata;
421
422 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
423 rdata = e->body.tsx_state.src.rdata;
424 else
425 rdata = NULL;
426
427 pjsua_var.ua_cfg.cb.on_pager_status2(im_data->call_id,
428 &im_data->to,
429 &im_data->body,
430 im_data->user_data,
431 (pjsip_status_code)
432 tsx->status_code,
433 &tsx->status_text,
434 tsx->last_tx,
435 rdata, im_data->acc_id);
436 }
437 }
438}
439
440
441/* Outgoing typing indication callback.
442 * (used to reauthenticate request)
443 */
444static void typing_callback(void *token, pjsip_event *e)
445{
446 pjsua_im_data *im_data = (pjsua_im_data*) token;
447
448 if (e->type == PJSIP_EVENT_TSX_STATE) {
449
450 pjsip_transaction *tsx = e->body.tsx_state.tsx;
451
452 /* Ignore provisional response, if any */
453 if (tsx->status_code < 200)
454 return;
455
456 /* Handle authentication challenges */
457 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
458 (tsx->status_code == 401 || tsx->status_code == 407))
459 {
460 pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
461 pjsip_tx_data *tdata;
462 pjsip_auth_clt_sess auth;
463 pj_status_t status;
464
465 PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
466
467 /* Create temporary authentication session */
468 pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
469
470 pjsip_auth_clt_set_credentials(&auth,
471 pjsua_var.acc[im_data->acc_id].cred_cnt,
472 pjsua_var.acc[im_data->acc_id].cred);
473
474 pjsip_auth_clt_set_prefs(&auth,
475 &pjsua_var.acc[im_data->acc_id].cfg.auth_pref);
476
477 status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
478 &tdata);
479 if (status == PJ_SUCCESS) {
480 pjsua_im_data *im_data2;
481
482 /* Must duplicate im_data */
483 im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
484
485 /* Increment CSeq */
486 PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++;
487
488 /* Re-send request */
489 status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
490 im_data2, &typing_callback);
491 if (status == PJ_SUCCESS) {
492 /* Done */
493 return;
494 }
495 }
496 }
497
498 }
499}
500
501
502/*
503 * Send instant messaging outside dialog, using the specified account for
504 * route set and authentication.
505 */
506PJ_DEF(pj_status_t) pjsua_im_send( pjsua_acc_id acc_id,
507 const pj_str_t *to,
508 const pj_str_t *mime_type,
509 const pj_str_t *content,
510 const pjsua_msg_data *msg_data,
511 void *user_data)
512{
513 pjsip_tx_data *tdata;
514 const pj_str_t mime_text_plain = pj_str("text/plain");
515 pjsip_media_type media_type;
516 pjsua_im_data *im_data;
517 pjsua_acc *acc;
518 pj_status_t status;
519
520 /* To and message body must be specified. */
521 PJ_ASSERT_RETURN(to && content, PJ_EINVAL);
522
523 acc = &pjsua_var.acc[acc_id];
524
525 /* Create request. */
526 status = pjsip_endpt_create_request(pjsua_var.endpt,
527 &pjsip_message_method,
528 (msg_data && msg_data->target_uri.slen?
529 &msg_data->target_uri: to),
530 &acc->cfg.id,
531 to, NULL, NULL, -1, NULL, &tdata);
532 if (status != PJ_SUCCESS) {
533 pjsua_perror(THIS_FILE, "Unable to create request", status);
534 return status;
535 }
536
537 /* If account is locked to specific transport, then set transport to
538 * the request.
539 */
540 if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
541 pjsip_tpselector tp_sel;
542
543 pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
544 pjsip_tx_data_set_transport(tdata, &tp_sel);
545 }
546
547 /* Add accept header. */
548 pjsip_msg_add_hdr( tdata->msg,
549 (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
550
551 /* Create suitable Contact header unless a Contact header has been
552 * set in the account.
553 */
554 /* Ticket #1632: According to RFC 3428:
555 * MESSAGE requests do not initiate dialogs.
556 * User Agents MUST NOT insert Contact header fields into MESSAGE requests
557 */
558 /*
559 if (acc->contact.slen) {
560 contact = acc->contact;
561 } else {
562 status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to);
563 if (status != PJ_SUCCESS) {
564 pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
565 pjsip_tx_data_dec_ref(tdata);
566 return status;
567 }
568 }
569
570 pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
571 pjsip_generic_string_hdr_create(tdata->pool,
572 &STR_CONTACT, &contact));
573 */
574
575 /* Create IM data to keep message details and give it back to
576 * application on the callback
577 */
578 im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
579 im_data->acc_id = acc_id;
580 im_data->call_id = PJSUA_INVALID_ID;
581 pj_strdup_with_null(tdata->pool, &im_data->to, to);
582 pj_strdup_with_null(tdata->pool, &im_data->body, content);
583 im_data->user_data = user_data;
584
585
586 /* Set default media type if none is specified */
587 if (mime_type == NULL) {
588 mime_type = &mime_text_plain;
589 }
590
591 /* Parse MIME type */
592 pjsua_parse_media_type(tdata->pool, mime_type, &media_type);
593
594 /* Add message body */
595 tdata->msg->body = pjsip_msg_body_create( tdata->pool, &media_type.type,
596 &media_type.subtype,
597 &im_data->body);
598 if (tdata->msg->body == NULL) {
599 pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
600 pjsip_tx_data_dec_ref(tdata);
601 return PJ_ENOMEM;
602 }
603
604 /* Add additional headers etc. */
605 pjsua_process_msg_data(tdata, msg_data);
606
607 /* Add route set */
608 pjsua_set_msg_route_set(tdata, &acc->route_set);
609
610 /* If via_addr is set, use this address for the Via header. */
611 if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
612 tdata->via_addr = acc->via_addr;
613 tdata->via_tp = acc->via_tp;
614 }
615
616 /* Send request (statefully) */
617 status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
618 im_data, &im_callback);
619 if (status != PJ_SUCCESS) {
620 pjsua_perror(THIS_FILE, "Unable to send request", status);
621 return status;
622 }
623
624 return PJ_SUCCESS;
625}
626
627
628/*
629 * Send typing indication outside dialog.
630 */
631PJ_DEF(pj_status_t) pjsua_im_typing( pjsua_acc_id acc_id,
632 const pj_str_t *to,
633 pj_bool_t is_typing,
634 const pjsua_msg_data *msg_data)
635{
636 pjsua_im_data *im_data;
637 pjsip_tx_data *tdata;
638 pjsua_acc *acc;
639 pj_status_t status;
640
641 acc = &pjsua_var.acc[acc_id];
642
643 /* Create request. */
644 status = pjsip_endpt_create_request( pjsua_var.endpt, &pjsip_message_method,
645 to, &acc->cfg.id,
646 to, NULL, NULL, -1, NULL, &tdata);
647 if (status != PJ_SUCCESS) {
648 pjsua_perror(THIS_FILE, "Unable to create request", status);
649 return status;
650 }
651
652
653 /* If account is locked to specific transport, then set transport to
654 * the request.
655 */
656 if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
657 pjsip_tpselector tp_sel;
658
659 pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
660 pjsip_tx_data_set_transport(tdata, &tp_sel);
661 }
662
663 /* Add accept header. */
664 pjsip_msg_add_hdr( tdata->msg,
665 (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
666
667
668 /* Create suitable Contact header unless a Contact header has been
669 * set in the account.
670 */
671 /* Ticket #1632: According to RFC 3428:
672 * MESSAGE requests do not initiate dialogs.
673 * User Agents MUST NOT insert Contact header fields into MESSAGE requests
674 */
675 /*
676 if (acc->contact.slen) {
677 contact = acc->contact;
678 } else {
679 status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to);
680 if (status != PJ_SUCCESS) {
681 pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
682 pjsip_tx_data_dec_ref(tdata);
683 return status;
684 }
685 }
686
687 pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
688 pjsip_generic_string_hdr_create(tdata->pool,
689 &STR_CONTACT, &contact));
690 */
691
692 /* Create "application/im-iscomposing+xml" msg body. */
693 tdata->msg->body = pjsip_iscomposing_create_body( tdata->pool, is_typing,
694 NULL, NULL, -1);
695
696 /* Add additional headers etc. */
697 pjsua_process_msg_data(tdata, msg_data);
698
699 /* Add route set */
700 pjsua_set_msg_route_set(tdata, &acc->route_set);
701
702 /* If via_addr is set, use this address for the Via header. */
703 if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
704 tdata->via_addr = acc->via_addr;
705 tdata->via_tp = acc->via_tp;
706 }
707
708 /* Create data to reauthenticate */
709 im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
710 im_data->acc_id = acc_id;
711
712 /* Send request (statefully) */
713 status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
714 im_data, &typing_callback);
715 if (status != PJ_SUCCESS) {
716 pjsua_perror(THIS_FILE, "Unable to send request", status);
717 return status;
718 }
719
720 return PJ_SUCCESS;
721}
722
723
724/*
725 * Init pjsua IM module.
726 */
727pj_status_t pjsua_im_init(void)
728{
729 const pj_str_t msg_tag = { "MESSAGE", 7 };
730 const pj_str_t STR_MIME_TEXT_PLAIN = { "text/plain", 10 };
731 const pj_str_t STR_MIME_APP_ISCOMPOSING =
732 { "application/im-iscomposing+xml", 30 };
733 pj_status_t status;
734
735 /* Register module */
736 status = pjsip_endpt_register_module(pjsua_var.endpt, &mod_pjsua_im);
737 if (status != PJ_SUCCESS)
738 return status;
739
740 /* Register support for MESSAGE method. */
741 pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ALLOW,
742 NULL, 1, &msg_tag);
743
744 /* Register support for "application/im-iscomposing+xml" content */
745 pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT,
746 NULL, 1, &STR_MIME_APP_ISCOMPOSING);
747
748 /* Register support for "text/plain" content */
749 pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT,
750 NULL, 1, &STR_MIME_TEXT_PLAIN);
751
752 return PJ_SUCCESS;
753}
754