| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| * |
| * 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 <pjsip-ua/sip_100rel.h> |
| #include <pjsip/sip_endpoint.h> |
| #include <pjsip/sip_event.h> |
| #include <pjsip/sip_module.h> |
| #include <pjsip/sip_transaction.h> |
| #include <pj/assert.h> |
| #include <pj/ctype.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/pool.h> |
| #include <pj/rand.h> |
| #include <pj/string.h> |
| |
| #define THIS_FILE "sip_100rel.c" |
| |
| /* PRACK method */ |
| PJ_DEF_DATA(const pjsip_method) pjsip_prack_method = |
| { |
| PJSIP_OTHER_METHOD, |
| { "PRACK", 5 } |
| }; |
| |
| typedef struct dlg_data dlg_data; |
| |
| /* |
| * Static prototypes. |
| */ |
| static pj_status_t mod_100rel_load(pjsip_endpoint *endpt); |
| |
| static void on_retransmit(pj_timer_heap_t *timer_heap, |
| struct pj_timer_entry *entry); |
| |
| |
| const pj_str_t tag_100rel = { "100rel", 6 }; |
| const pj_str_t RSEQ = { "RSeq", 4 }; |
| const pj_str_t RACK = { "RAck", 4 }; |
| |
| |
| /* 100rel module */ |
| static struct mod_100rel |
| { |
| pjsip_module mod; |
| pjsip_endpoint *endpt; |
| } mod_100rel = |
| { |
| { |
| NULL, NULL, /* prev, next. */ |
| { "mod-100rel", 10 }, /* Name. */ |
| -1, /* Id */ |
| PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */ |
| &mod_100rel_load, /* 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() */ |
| } |
| |
| }; |
| |
| /* List of pending transmission (may include the final response as well) */ |
| typedef struct tx_data_list_t |
| { |
| PJ_DECL_LIST_MEMBER(struct tx_data_list_t); |
| pj_uint32_t rseq; |
| pjsip_tx_data *tdata; |
| } tx_data_list_t; |
| |
| |
| /* Below, UAS and UAC roles are of the INVITE transaction */ |
| |
| /* UAS state. */ |
| typedef struct uas_state_t |
| { |
| pj_int32_t cseq; |
| pj_uint32_t rseq; /* Initialized to -1 */ |
| tx_data_list_t tx_data_list; |
| unsigned retransmit_count; |
| pj_timer_entry retransmit_timer; |
| } uas_state_t; |
| |
| |
| /* UAC state */ |
| typedef struct uac_state_t |
| { |
| pj_str_t tag; /* To tag */ |
| pj_int32_t cseq; |
| pj_uint32_t rseq; /* Initialized to -1 */ |
| struct uac_state_t *next; /* next call leg */ |
| } uac_state_t; |
| |
| |
| /* State attached to each dialog. */ |
| struct dlg_data |
| { |
| pjsip_inv_session *inv; |
| uas_state_t *uas_state; |
| uac_state_t *uac_state_list; |
| }; |
| |
| |
| /***************************************************************************** |
| ** |
| ** Module |
| ** |
| ***************************************************************************** |
| */ |
| static pj_status_t mod_100rel_load(pjsip_endpoint *endpt) |
| { |
| mod_100rel.endpt = endpt; |
| pjsip_endpt_add_capability(endpt, &mod_100rel.mod, |
| PJSIP_H_ALLOW, NULL, |
| 1, &pjsip_prack_method.name); |
| pjsip_endpt_add_capability(endpt, &mod_100rel.mod, |
| PJSIP_H_SUPPORTED, NULL, |
| 1, &tag_100rel); |
| |
| return PJ_SUCCESS; |
| } |
| |
| static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg) |
| { |
| pjsip_require_hdr *hreq; |
| |
| hreq = (pjsip_require_hdr*) |
| pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); |
| |
| while (hreq) { |
| unsigned i; |
| for (i=0; i<hreq->count; ++i) { |
| if (!pj_stricmp(&hreq->values[i], &tag_100rel)) { |
| return hreq; |
| } |
| } |
| |
| if ((void*)hreq->next == (void*)&msg->hdr) |
| return NULL; |
| |
| hreq = (pjsip_require_hdr*) |
| pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next); |
| |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* |
| * Get PRACK method constant. |
| */ |
| PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void) |
| { |
| return &pjsip_prack_method; |
| } |
| |
| |
| /* |
| * init module |
| */ |
| PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt) |
| { |
| if (mod_100rel.mod.id != -1) |
| return PJ_SUCCESS; |
| |
| return pjsip_endpt_register_module(endpt, &mod_100rel.mod); |
| } |
| |
| |
| /* |
| * API: attach 100rel support in invite session. Called by |
| * sip_inv.c |
| */ |
| PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv) |
| { |
| dlg_data *dd; |
| |
| /* Check that 100rel module has been initialized */ |
| PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP); |
| |
| /* Create and attach as dialog usage */ |
| dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data); |
| dd->inv = inv; |
| pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd); |
| |
| PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached")); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Check if incoming response has reliable provisional response feature. |
| */ |
| PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata) |
| { |
| pjsip_msg *msg = rdata->msg_info.msg; |
| |
| PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE); |
| |
| return msg->line.status.code > 100 && msg->line.status.code < 200 && |
| rdata->msg_info.require != NULL && |
| find_req_hdr(msg) != NULL; |
| } |
| |
| |
| /* |
| * Create PRACK request for the incoming reliable provisional response. |
| */ |
| PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv, |
| pjsip_rx_data *rdata, |
| pjsip_tx_data **p_tdata) |
| { |
| dlg_data *dd; |
| uac_state_t *uac_state = NULL; |
| const pj_str_t *to_tag = &rdata->msg_info.to->tag; |
| pjsip_transaction *tsx; |
| pjsip_msg *msg; |
| pjsip_generic_string_hdr *rseq_hdr; |
| pjsip_generic_string_hdr *rack_hdr; |
| unsigned rseq; |
| pj_str_t rack; |
| char rack_buf[80]; |
| pjsip_tx_data *tdata; |
| pj_status_t status; |
| |
| *p_tdata = NULL; |
| |
| dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; |
| PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED); |
| |
| tsx = pjsip_rdata_get_tsx(rdata); |
| msg = rdata->msg_info.msg; |
| |
| /* Check our assumptions */ |
| pj_assert( tsx->role == PJSIP_ROLE_UAC && |
| tsx->method.id == PJSIP_INVITE_METHOD && |
| msg->line.status.code > 100 && |
| msg->line.status.code < 200); |
| |
| |
| /* Get the RSeq header */ |
| rseq_hdr = (pjsip_generic_string_hdr*) |
| pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL); |
| if (rseq_hdr == NULL) { |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "Ignoring 100rel response with no RSeq header")); |
| return PJSIP_EMISSINGHDR; |
| } |
| rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue); |
| |
| /* Find UAC state for the specified call leg */ |
| uac_state = dd->uac_state_list; |
| while (uac_state) { |
| if (pj_stricmp(&uac_state->tag, to_tag)==0) |
| break; |
| uac_state = uac_state->next; |
| } |
| |
| /* Create new UAC state if we don't have one */ |
| if (uac_state == NULL) { |
| uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, uac_state_t); |
| uac_state->cseq = rdata->msg_info.cseq->cseq; |
| uac_state->rseq = rseq - 1; |
| pj_strdup(dd->inv->dlg->pool, &uac_state->tag, to_tag); |
| uac_state->next = dd->uac_state_list; |
| dd->uac_state_list = uac_state; |
| } |
| |
| /* If this is from new INVITE transaction, reset UAC state. */ |
| if (rdata->msg_info.cseq->cseq != uac_state->cseq) { |
| uac_state->cseq = rdata->msg_info.cseq->cseq; |
| uac_state->rseq = rseq - 1; |
| } |
| |
| /* Ignore provisional response retransmission */ |
| if (rseq <= uac_state->rseq) { |
| /* This should have been handled before */ |
| return PJ_EIGNORED; |
| |
| /* Ignore provisional response with out-of-order RSeq */ |
| } else if (rseq != uac_state->rseq + 1) { |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "Ignoring 100rel response because RSeq jump " |
| "(expecting %u, got %u)", |
| uac_state->rseq+1, rseq)); |
| return PJ_EIGNORED; |
| } |
| |
| /* Update our RSeq */ |
| uac_state->rseq = rseq; |
| |
| /* Create PRACK */ |
| status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method, |
| -1, &tdata); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* If this response is a forked response from a different call-leg, |
| * update the req URI (https://trac.pjsip.org/repos/ticket/1364) |
| */ |
| if (pj_stricmp(&uac_state->tag, &dd->inv->dlg->remote.info->tag)) { |
| const pjsip_contact_hdr *mhdr; |
| |
| mhdr = (const pjsip_contact_hdr*) |
| pjsip_msg_find_hdr(rdata->msg_info.msg, |
| PJSIP_H_CONTACT, NULL); |
| if (!mhdr || !mhdr->uri) { |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "Ignoring 100rel response with no or " |
| "invalid Contact header")); |
| pjsip_tx_data_dec_ref(tdata); |
| return PJ_EIGNORED; |
| } |
| tdata->msg->line.req.uri = (pjsip_uri*) |
| pjsip_uri_clone(tdata->pool, mhdr->uri); |
| } |
| |
| /* Create RAck header */ |
| rack.ptr = rack_buf; |
| rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf), |
| "%u %u %.*s", |
| rseq, rdata->msg_info.cseq->cseq, |
| (int)tsx->method.name.slen, |
| tsx->method.name.ptr); |
| rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack); |
| pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr); |
| |
| /* Done */ |
| *p_tdata = tdata; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Send PRACK request. |
| */ |
| PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv, |
| pjsip_tx_data *tdata) |
| { |
| dlg_data *dd; |
| |
| dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; |
| PJ_ASSERT_ON_FAIL(dd != NULL, |
| {pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; }); |
| |
| return pjsip_dlg_send_request(inv->dlg, tdata, |
| mod_100rel.mod.id, (void*) dd); |
| |
| } |
| |
| |
| /* |
| * Notify 100rel module that the invite session has been disconnected. |
| */ |
| PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv) |
| { |
| dlg_data *dd; |
| |
| dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; |
| if (!dd) |
| return PJ_SUCCESS; |
| |
| /* Make sure we don't have pending transmission */ |
| if (dd->uas_state) { |
| pj_assert(!dd->uas_state->retransmit_timer.id); |
| pj_assert(pj_list_empty(&dd->uas_state->tx_data_list)); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| static void parse_rack(const pj_str_t *rack, |
| pj_uint32_t *p_rseq, pj_int32_t *p_seq, |
| pj_str_t *p_method) |
| { |
| const char *p = rack->ptr, *end = p + rack->slen; |
| pj_str_t token; |
| |
| token.ptr = (char*)p; |
| while (p < end && pj_isdigit(*p)) |
| ++p; |
| token.slen = p - token.ptr; |
| *p_rseq = pj_strtoul(&token); |
| |
| ++p; |
| token.ptr = (char*)p; |
| while (p < end && pj_isdigit(*p)) |
| ++p; |
| token.slen = p - token.ptr; |
| *p_seq = pj_strtoul(&token); |
| |
| ++p; |
| if (p < end) { |
| p_method->ptr = (char*)p; |
| p_method->slen = end - p; |
| } else { |
| p_method->ptr = NULL; |
| p_method->slen = 0; |
| } |
| } |
| |
| /* Clear all responses in the transmission list */ |
| static void clear_all_responses(dlg_data *dd) |
| { |
| tx_data_list_t *tl; |
| |
| tl = dd->uas_state->tx_data_list.next; |
| while (tl != &dd->uas_state->tx_data_list) { |
| pjsip_tx_data_dec_ref(tl->tdata); |
| tl = tl->next; |
| } |
| pj_list_init(&dd->uas_state->tx_data_list); |
| } |
| |
| |
| /* |
| * Handle incoming PRACK request. |
| */ |
| PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv, |
| pjsip_rx_data *rdata) |
| { |
| dlg_data *dd; |
| pjsip_transaction *tsx; |
| pjsip_msg *msg; |
| pjsip_generic_string_hdr *rack_hdr; |
| pjsip_tx_data *tdata; |
| pj_uint32_t rseq; |
| pj_int32_t cseq; |
| pj_str_t method; |
| pj_status_t status; |
| |
| tsx = pjsip_rdata_get_tsx(rdata); |
| pj_assert(tsx != NULL); |
| |
| msg = rdata->msg_info.msg; |
| |
| dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; |
| if (dd == NULL) { |
| /* UAC sends us PRACK while we didn't send reliable provisional |
| * response. Respond with 400 (?) |
| */ |
| const pj_str_t reason = pj_str("Unexpected PRACK"); |
| |
| status = pjsip_dlg_create_response(inv->dlg, rdata, 400, |
| &reason, &tdata); |
| if (status == PJ_SUCCESS) { |
| status = pjsip_dlg_send_response(inv->dlg, tsx, tdata); |
| } |
| return PJSIP_ENOTINITIALIZED; |
| } |
| |
| /* Always reply with 200/OK for PRACK */ |
| status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata); |
| if (status == PJ_SUCCESS) { |
| status = pjsip_dlg_send_response(inv->dlg, tsx, tdata); |
| } |
| |
| /* Ignore if we don't have pending transmission */ |
| if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) { |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "PRACK ignored - no pending response")); |
| return PJ_EIGNORED; |
| } |
| |
| /* Find RAck header */ |
| rack_hdr = (pjsip_generic_string_hdr*) |
| pjsip_msg_find_hdr_by_name(msg, &RACK, NULL); |
| if (!rack_hdr) { |
| /* RAck header not found */ |
| PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header")); |
| return PJSIP_EMISSINGHDR; |
| } |
| |
| /* Parse RAck header */ |
| parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method); |
| |
| |
| /* Match RAck against outgoing transmission */ |
| if (rseq == dd->uas_state->tx_data_list.next->rseq && |
| cseq == dd->uas_state->cseq) |
| { |
| /* |
| * Yes this PRACK matches outgoing transmission. |
| */ |
| tx_data_list_t *tl = dd->uas_state->tx_data_list.next; |
| |
| if (dd->uas_state->retransmit_timer.id) { |
| pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, |
| &dd->uas_state->retransmit_timer); |
| dd->uas_state->retransmit_timer.id = PJ_FALSE; |
| } |
| |
| /* Remove from the list */ |
| if (tl != &dd->uas_state->tx_data_list) { |
| pj_list_erase(tl); |
| |
| /* Destroy the response */ |
| pjsip_tx_data_dec_ref(tl->tdata); |
| } |
| |
| /* Schedule next packet */ |
| dd->uas_state->retransmit_count = 0; |
| if (!pj_list_empty(&dd->uas_state->tx_data_list)) { |
| on_retransmit(NULL, &dd->uas_state->retransmit_timer); |
| } |
| |
| } else { |
| /* No it doesn't match */ |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "Rx PRACK with no matching reliable response")); |
| return PJ_EIGNORED; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * This is retransmit timer callback, called initially to send the response, |
| * and subsequently when the retransmission time elapses. |
| */ |
| static void on_retransmit(pj_timer_heap_t *timer_heap, |
| struct pj_timer_entry *entry) |
| { |
| dlg_data *dd; |
| tx_data_list_t *tl; |
| pjsip_tx_data *tdata; |
| pj_bool_t final; |
| pj_time_val delay; |
| |
| PJ_UNUSED_ARG(timer_heap); |
| |
| dd = (dlg_data*) entry->user_data; |
| |
| entry->id = PJ_FALSE; |
| |
| ++dd->uas_state->retransmit_count; |
| if (dd->uas_state->retransmit_count >= 7) { |
| /* If a reliable provisional response is retransmitted for |
| 64*T1 seconds without reception of a corresponding PRACK, |
| the UAS SHOULD reject the original request with a 5xx |
| response. |
| */ |
| pj_str_t reason = pj_str("Reliable response timed out"); |
| pj_status_t status; |
| |
| /* Clear all pending responses */ |
| clear_all_responses(dd); |
| |
| /* Send 500 response */ |
| status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata); |
| if (status == PJ_SUCCESS) { |
| pjsip_dlg_send_response(dd->inv->dlg, |
| dd->inv->invite_tsx, |
| tdata); |
| } |
| return; |
| } |
| |
| pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list)); |
| tl = dd->uas_state->tx_data_list.next; |
| tdata = tl->tdata; |
| |
| pjsip_tx_data_add_ref(tdata); |
| final = tdata->msg->line.status.code >= 200; |
| |
| if (dd->uas_state->retransmit_count == 1) { |
| pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata); |
| } else { |
| pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata); |
| } |
| |
| if (final) { |
| /* This is final response, which will be retransmitted by |
| * UA layer. There's no more task to do, so clear the |
| * transmission list and bail out. |
| */ |
| clear_all_responses(dd); |
| return; |
| } |
| |
| /* Schedule next retransmission */ |
| if (dd->uas_state->retransmit_count < 6) { |
| delay.sec = 0; |
| delay.msec = (1 << dd->uas_state->retransmit_count) * |
| pjsip_cfg()->tsx.t1; |
| pj_time_val_normalize(&delay); |
| } else { |
| delay.sec = 1; |
| delay.msec = 500; |
| } |
| |
| |
| pjsip_endpt_schedule_timer(dd->inv->dlg->endpt, |
| &dd->uas_state->retransmit_timer, |
| &delay); |
| |
| entry->id = PJ_TRUE; |
| } |
| |
| |
| /* Clone response. */ |
| static pjsip_tx_data *clone_tdata(dlg_data *dd, |
| const pjsip_tx_data *src) |
| { |
| pjsip_tx_data *dst; |
| const pjsip_hdr *hsrc; |
| pjsip_msg *msg; |
| pj_status_t status; |
| |
| status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst); |
| if (status != PJ_SUCCESS) |
| return NULL; |
| |
| msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG); |
| dst->msg = msg; |
| pjsip_tx_data_add_ref(dst); |
| |
| /* Duplicate status line */ |
| msg->line.status.code = src->msg->line.status.code; |
| pj_strdup(dst->pool, &msg->line.status.reason, |
| &src->msg->line.status.reason); |
| |
| /* Duplicate all headers */ |
| hsrc = src->msg->hdr.next; |
| while (hsrc != &src->msg->hdr) { |
| pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc); |
| pjsip_msg_add_hdr(msg, h); |
| hsrc = hsrc->next; |
| } |
| |
| /* Duplicate message body */ |
| if (src->msg->body) |
| msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body); |
| |
| PJ_LOG(5,(dd->inv->dlg->obj_name, |
| "Reliable response %s created", |
| pjsip_tx_data_get_info(dst))); |
| |
| return dst; |
| } |
| |
| |
| /* Check if any pending response in transmission list has SDP */ |
| static pj_bool_t has_sdp(dlg_data *dd) |
| { |
| tx_data_list_t *tl; |
| |
| tl = dd->uas_state->tx_data_list.next; |
| while (tl != &dd->uas_state->tx_data_list) { |
| if (tl->tdata->msg->body) |
| return PJ_TRUE; |
| tl = tl->next; |
| } |
| |
| return PJ_FALSE; |
| } |
| |
| |
| /* Send response reliably */ |
| PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv, |
| pjsip_tx_data *tdata) |
| { |
| pjsip_cseq_hdr *cseq_hdr; |
| pjsip_generic_string_hdr *rseq_hdr; |
| pjsip_require_hdr *req_hdr; |
| int status_code; |
| dlg_data *dd; |
| pjsip_tx_data *old_tdata; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, |
| PJSIP_ENOTRESPONSEMSG); |
| |
| status_code = tdata->msg->line.status.code; |
| |
| /* 100 response doesn't need PRACK */ |
| if (status_code == 100) |
| return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); |
| |
| |
| /* Get the 100rel data attached to this dialog */ |
| dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; |
| PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP); |
| |
| |
| /* Clone tdata. |
| * We need to clone tdata because we may need to keep it in our |
| * retransmission list, while the original dialog may modify it |
| * if it wants to send another response. |
| */ |
| old_tdata = tdata; |
| tdata = clone_tdata(dd, old_tdata); |
| pjsip_tx_data_dec_ref(old_tdata); |
| |
| |
| /* Get CSeq header, and make sure this is INVITE response */ |
| cseq_hdr = (pjsip_cseq_hdr*) |
| pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); |
| PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG); |
| PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD, |
| PJ_EINVALIDOP); |
| |
| /* Remove existing Require header */ |
| req_hdr = find_req_hdr(tdata->msg); |
| if (req_hdr) { |
| pj_list_erase(req_hdr); |
| } |
| |
| /* Remove existing RSeq header */ |
| rseq_hdr = (pjsip_generic_string_hdr*) |
| pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL); |
| if (rseq_hdr) |
| pj_list_erase(rseq_hdr); |
| |
| /* Different treatment for provisional and final response */ |
| if (status_code/100 == 2) { |
| |
| /* RFC 3262 Section 3: UAS Behavior: |
| |
| The UAS MAY send a final response to the initial request |
| before having received PRACKs for all unacknowledged |
| reliable provisional responses, unless the final response |
| is 2xx and any of the unacknowledged reliable provisional |
| responses contained a session description. In that case, |
| it MUST NOT send a final response until those provisional |
| responses are acknowledged. |
| */ |
| |
| if (dd->uas_state && has_sdp(dd)) { |
| /* Yes we have transmitted 1xx with SDP reliably. |
| * In this case, must queue the 2xx response. |
| */ |
| tx_data_list_t *tl; |
| |
| tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); |
| tl->tdata = tdata; |
| tl->rseq = (pj_uint32_t)-1; |
| pj_list_push_back(&dd->uas_state->tx_data_list, tl); |
| |
| /* Will send later */ |
| status = PJ_SUCCESS; |
| |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "2xx response will be sent after PRACK")); |
| |
| } else if (dd->uas_state) { |
| /* |
| RFC 3262 Section 3: UAS Behavior: |
| |
| If the UAS does send a final response when reliable |
| responses are still unacknowledged, it SHOULD NOT |
| continue to retransmit the unacknowledged reliable |
| provisional responses, but it MUST be prepared to |
| process PRACK requests for those outstanding |
| responses. |
| */ |
| |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "No SDP sent so far, sending 2xx now")); |
| |
| /* Cancel the retransmit timer */ |
| if (dd->uas_state->retransmit_timer.id) { |
| pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, |
| &dd->uas_state->retransmit_timer); |
| dd->uas_state->retransmit_timer.id = PJ_FALSE; |
| } |
| |
| /* Clear all pending responses (drop 'em) */ |
| clear_all_responses(dd); |
| |
| /* And transmit the 2xx response */ |
| status=pjsip_dlg_send_response(inv->dlg, |
| inv->invite_tsx, tdata); |
| |
| } else { |
| /* We didn't send any reliable provisional response */ |
| |
| /* Transmit the 2xx response */ |
| status=pjsip_dlg_send_response(inv->dlg, |
| inv->invite_tsx, tdata); |
| } |
| |
| } else if (status_code >= 300) { |
| |
| /* |
| RFC 3262 Section 3: UAS Behavior: |
| |
| If the UAS does send a final response when reliable |
| responses are still unacknowledged, it SHOULD NOT |
| continue to retransmit the unacknowledged reliable |
| provisional responses, but it MUST be prepared to |
| process PRACK requests for those outstanding |
| responses. |
| */ |
| |
| /* Cancel the retransmit timer */ |
| if (dd->uas_state && dd->uas_state->retransmit_timer.id) { |
| pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, |
| &dd->uas_state->retransmit_timer); |
| dd->uas_state->retransmit_timer.id = PJ_FALSE; |
| |
| /* Clear all pending responses (drop 'em) */ |
| clear_all_responses(dd); |
| } |
| |
| /* And transmit the 2xx response */ |
| status=pjsip_dlg_send_response(inv->dlg, |
| inv->invite_tsx, tdata); |
| |
| } else { |
| /* |
| * This is provisional response. |
| */ |
| char rseq_str[32]; |
| pj_str_t rseq; |
| tx_data_list_t *tl; |
| |
| /* Create UAS state if we don't have one */ |
| if (dd->uas_state == NULL) { |
| dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool, |
| uas_state_t); |
| dd->uas_state->cseq = cseq_hdr->cseq; |
| dd->uas_state->rseq = pj_rand() % 0x7FFF; |
| pj_list_init(&dd->uas_state->tx_data_list); |
| dd->uas_state->retransmit_timer.user_data = dd; |
| dd->uas_state->retransmit_timer.cb = &on_retransmit; |
| } |
| |
| /* Check that CSeq match */ |
| PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq, |
| PJ_EINVALIDOP); |
| |
| /* Add Require header */ |
| req_hdr = pjsip_require_hdr_create(tdata->pool); |
| req_hdr->count = 1; |
| req_hdr->values[0] = tag_100rel; |
| pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr); |
| |
| /* Add RSeq header */ |
| pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u", |
| dd->uas_state->rseq); |
| rseq = pj_str(rseq_str); |
| rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool, |
| &RSEQ, &rseq); |
| pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr); |
| |
| /* Create list entry for this response */ |
| tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); |
| tl->tdata = tdata; |
| tl->rseq = dd->uas_state->rseq++; |
| |
| /* Add to queue if there's pending response, otherwise |
| * transmit immediately. |
| */ |
| if (!pj_list_empty(&dd->uas_state->tx_data_list)) { |
| |
| int code = tdata->msg->line.status.code; |
| |
| /* Will send later */ |
| pj_list_push_back(&dd->uas_state->tx_data_list, tl); |
| status = PJ_SUCCESS; |
| |
| PJ_LOG(4,(dd->inv->dlg->obj_name, |
| "Reliable %d response enqueued (%d pending)", |
| code, pj_list_size(&dd->uas_state->tx_data_list))); |
| |
| } else { |
| pj_list_push_back(&dd->uas_state->tx_data_list, tl); |
| |
| dd->uas_state->retransmit_count = 0; |
| on_retransmit(NULL, &dd->uas_state->retransmit_timer); |
| status = PJ_SUCCESS; |
| } |
| |
| } |
| |
| return status; |
| } |
| |
| |