/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #define THIS_FILE "mwi.c" /* * MWI module (mod-mdi) */ static struct pjsip_module mod_mwi = { NULL, NULL, /* prev, next. */ { "mod-mwi", 7 }, /* Name. */ -1, /* Id */ PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */ NULL, /* load() */ NULL, /* start() */ NULL, /* stop() */ NULL, /* unload() */ NULL, /* on_rx_request() */ NULL, /* on_rx_response() */ NULL, /* on_tx_request. */ NULL, /* on_tx_response() */ NULL, /* on_tsx_state() */ }; /* * This structure describe an mwi agent (both client and server) */ typedef struct pjsip_mwi { pjsip_evsub *sub; /**< Event subscribtion record. */ pjsip_dialog *dlg; /**< The dialog. */ pjsip_evsub_user user_cb; /**< The user callback. */ /* These are for server subscriptions */ pj_pool_t *body_pool; /**< Pool to save message body */ pjsip_media_type mime_type; /**< MIME type of last msg body */ pj_str_t body; /**< Last sent message body */ } pjsip_mwi; /* * Forward decl for evsub callbacks. */ static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event); static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body); static void mwi_on_evsub_rx_notify( pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body); static void mwi_on_evsub_client_refresh(pjsip_evsub *sub); static void mwi_on_evsub_server_timeout(pjsip_evsub *sub); /* * Event subscription callback for mwi. */ static pjsip_evsub_user mwi_user = { &mwi_on_evsub_state, &mwi_on_evsub_tsx_state, &mwi_on_evsub_rx_refresh, &mwi_on_evsub_rx_notify, &mwi_on_evsub_client_refresh, &mwi_on_evsub_server_timeout, }; /* * Some static constants. */ static const pj_str_t STR_EVENT = { "Event", 5 }; static const pj_str_t STR_MWI = { "message-summary", 15 }; static const pj_str_t STR_APP_SIMPLE_SMS = { "application/simple-message-summary", 34}; /* * Init mwi module. */ PJ_DEF(pj_status_t) pjsip_mwi_init_module( pjsip_endpoint *endpt, pjsip_module *mod_evsub) { pj_status_t status; pj_str_t accept[1]; /* Check arguments. */ PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL); /* Must have not been registered */ PJ_ASSERT_RETURN(mod_mwi.id == -1, PJ_EINVALIDOP); /* Register to endpoint */ status = pjsip_endpt_register_module(endpt, &mod_mwi); if (status != PJ_SUCCESS) return status; accept[0] = STR_APP_SIMPLE_SMS; /* Register event package to event module. */ status = pjsip_evsub_register_pkg( &mod_mwi, &STR_MWI, PJSIP_MWI_DEFAULT_EXPIRES, PJ_ARRAY_SIZE(accept), accept); if (status != PJ_SUCCESS) { pjsip_endpt_unregister_module(endpt, &mod_mwi); return status; } return PJ_SUCCESS; } /* * Get mwi module instance. */ PJ_DEF(pjsip_module*) pjsip_mwi_instance(void) { return &mod_mwi; } /* * Create client subscription. */ PJ_DEF(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg, const pjsip_evsub_user *user_cb, unsigned options, pjsip_evsub **p_evsub ) { pj_status_t status; pjsip_mwi *mwi; pjsip_evsub *sub; PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); PJ_UNUSED_ARG(options); pjsip_dlg_inc_lock(dlg); /* Create event subscription */ status = pjsip_evsub_create_uac( dlg, &mwi_user, &STR_MWI, options, &sub); if (status != PJ_SUCCESS) goto on_return; /* Create mwi */ mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi); mwi->dlg = dlg; mwi->sub = sub; if (user_cb) pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user)); /* Attach to evsub */ pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi); *p_evsub = sub; on_return: pjsip_dlg_dec_lock(dlg); return status; } /* * Create server subscription. */ PJ_DEF(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg, const pjsip_evsub_user *user_cb, pjsip_rx_data *rdata, pjsip_evsub **p_evsub ) { pjsip_accept_hdr *accept; pjsip_event_hdr *event; pjsip_evsub *sub; pjsip_mwi *mwi; char obj_name[PJ_MAX_OBJ_NAME]; pj_status_t status; /* Check arguments */ PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); /* Must be request message */ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); /* Check that request is SUBSCRIBE */ PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_subscribe_method)==0, PJSIP_SIMPLE_ENOTSUBSCRIBE); /* Check that Event header contains "mwi" */ event = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL); if (!event) { return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); } if (pj_stricmp(&event->event_type, &STR_MWI) != 0) { return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT); } /* Check that request contains compatible Accept header. */ accept = (pjsip_accept_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); if (accept) { unsigned i; for (i=0; icount; ++i) { if (pj_stricmp(&accept->values[i], &STR_APP_SIMPLE_SMS)==0) { break; } } if (i==accept->count) { /* Nothing is acceptable */ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); } } else { /* No Accept header. * Assume client supports "application/simple-message-summary" */ } /* Lock dialog */ pjsip_dlg_inc_lock(dlg); /* Create server subscription */ status = pjsip_evsub_create_uas( dlg, &mwi_user, rdata, 0, &sub); if (status != PJ_SUCCESS) goto on_return; /* Create server mwi subscription */ mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi); mwi->dlg = dlg; mwi->sub = sub; if (user_cb) pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user)); pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "mwibd%p", dlg->pool); mwi->body_pool = pj_pool_create(dlg->pool->factory, obj_name, 512, 512, NULL); /* Attach to evsub */ pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi); /* Done: */ *p_evsub = sub; on_return: pjsip_dlg_dec_lock(dlg); return status; } /* * Forcefully terminate mwi. */ PJ_DEF(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub, pj_bool_t notify ) { return pjsip_evsub_terminate(sub, notify); } /* * Create SUBSCRIBE */ PJ_DEF(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub, pj_uint32_t expires, pjsip_tx_data **p_tdata) { return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, p_tdata); } /* * Accept incoming subscription. */ PJ_DEF(pj_status_t) pjsip_mwi_accept( pjsip_evsub *sub, pjsip_rx_data *rdata, int st_code, const pjsip_hdr *hdr_list ) { return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); } /* * Create message body and attach it to the (NOTIFY) request. */ static pj_status_t mwi_create_msg_body( pjsip_mwi *mwi, pjsip_tx_data *tdata) { pjsip_msg_body *body; pj_str_t dup_text; PJ_ASSERT_RETURN(mwi->mime_type.type.slen && mwi->body.slen, PJ_EINVALIDOP); /* Clone the message body and mime type */ pj_strdup(tdata->pool, &dup_text, &mwi->body); /* Create the message body */ body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body); pjsip_media_type_cp(tdata->pool, &body->content_type, &mwi->mime_type); body->data = dup_text.ptr; body->len = (unsigned)dup_text.slen; body->print_body = &pjsip_print_text_body; body->clone_data = &pjsip_clone_text_data; /* Attach to tdata */ tdata->msg->body = body; return PJ_SUCCESS; } /* * Create NOTIFY */ PJ_DEF(pj_status_t) pjsip_mwi_notify( pjsip_evsub *sub, pjsip_evsub_state state, const pj_str_t *state_str, const pj_str_t *reason, const pjsip_media_type *mime_type, const pj_str_t *body, pjsip_tx_data **p_tdata) { pjsip_mwi *mwi; pjsip_tx_data *tdata; pj_status_t status; /* Check arguments. */ PJ_ASSERT_RETURN(sub && mime_type && body && p_tdata, PJ_EINVAL); /* Get the mwi object. */ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP); /* Lock object. */ pjsip_dlg_inc_lock(mwi->dlg); /* Create the NOTIFY request. */ status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata); if (status != PJ_SUCCESS) goto on_return; /* Update the cached message body */ if (mime_type || body) pj_pool_reset(mwi->body_pool); if (mime_type) pjsip_media_type_cp(mwi->body_pool, &mwi->mime_type, mime_type); if (body) pj_strdup(mwi->body_pool, &mwi->body, body); /* Create message body */ status = mwi_create_msg_body( mwi, tdata ); if (status != PJ_SUCCESS) goto on_return; /* Done. */ *p_tdata = tdata; on_return: pjsip_dlg_dec_lock(mwi->dlg); return status; } /* * Create NOTIFY that reflect current state. */ PJ_DEF(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub, pjsip_tx_data **p_tdata ) { pjsip_mwi *mwi; pjsip_tx_data *tdata; pj_status_t status; /* Check arguments. */ PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL); /* Get the mwi object. */ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP); /* Lock object. */ pjsip_dlg_inc_lock(mwi->dlg); /* Create the NOTIFY request. */ status = pjsip_evsub_current_notify( sub, &tdata); if (status != PJ_SUCCESS) goto on_return; /* Create message body to reflect the mwi status. */ status = mwi_create_msg_body( mwi, tdata ); if (status != PJ_SUCCESS) goto on_return; /* Done. */ *p_tdata = tdata; on_return: pjsip_dlg_dec_lock(mwi->dlg); return status; } /* * Send request. */ PJ_DEF(pj_status_t) pjsip_mwi_send_request( pjsip_evsub *sub, pjsip_tx_data *tdata ) { return pjsip_evsub_send_request(sub, tdata); } /* * This callback is called by event subscription when subscription * state has changed. */ static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) { pjsip_mwi *mwi; mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); if (mwi->user_cb.on_evsub_state) (*mwi->user_cb.on_evsub_state)(sub, event); if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { if (mwi->body_pool) { pj_pool_release(mwi->body_pool); mwi->body_pool = NULL; } } } /* * Called when transaction state has changed. */ static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) { pjsip_mwi *mwi; mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); if (mwi->user_cb.on_tsx_state) (*mwi->user_cb.on_tsx_state)(sub, tsx, event); } /* * Called when SUBSCRIBE is received. */ static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) { pjsip_mwi *mwi; mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); if (mwi->user_cb.on_rx_refresh) { (*mwi->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text, res_hdr, p_body); } else { /* Implementors MUST send NOTIFY if it implements on_rx_refresh */ pjsip_tx_data *tdata; pj_str_t timeout = { "timeout", 7}; pj_status_t status; if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) { status = pjsip_mwi_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, &timeout, NULL, NULL, &tdata); } else { status = pjsip_mwi_current_notify(sub, &tdata); } if (status == PJ_SUCCESS) pjsip_mwi_send_request(sub, tdata); } } /* * Called when NOTIFY is received. */ static void mwi_on_evsub_rx_notify( pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) { pjsip_mwi *mwi; mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); /* Just notify application. */ if (mwi->user_cb.on_rx_notify) { (*mwi->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, res_hdr, p_body); } } /* * Called when it's time to send SUBSCRIBE. */ static void mwi_on_evsub_client_refresh(pjsip_evsub *sub) { pjsip_mwi *mwi; mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); if (mwi->user_cb.on_client_refresh) { (*mwi->user_cb.on_client_refresh)(sub); } else { pj_status_t status; pjsip_tx_data *tdata; status = pjsip_mwi_initiate(sub, PJSIP_EXPIRES_NOT_SPECIFIED, &tdata); if (status == PJ_SUCCESS) pjsip_mwi_send_request(sub, tdata); } } /* * Called when no refresh is received after the interval. */ static void mwi_on_evsub_server_timeout(pjsip_evsub *sub) { pjsip_mwi *mwi; mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); if (mwi->user_cb.on_server_timeout) { (*mwi->user_cb.on_server_timeout)(sub); } else { pj_status_t status; pjsip_tx_data *tdata; pj_str_t reason = { "timeout", 7 }; status = pjsip_mwi_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, &reason, NULL, NULL, &tdata); if (status == PJ_SUCCESS) pjsip_mwi_send_request(sub, tdata); } }