void ICSCFSproutletTsx::on_tx_response(pjsip_msg* rsp) { if (_acr != NULL) { // Pass the transmitted response to the ACR to update the accounting // information. _acr->tx_response(rsp); } pjsip_status_code rsp_status = (pjsip_status_code)rsp->line.status.code; // Check if this is a terminating INVITE. If it is then check whether we need // to update our session establishment stats. We consider the session to be // set up as soon as we receive a final response OR a 180 Ringing (per TS // 32.409). if (!_originating && (_req_type == PJSIP_INVITE_METHOD) && !_session_set_up && ((rsp_status == PJSIP_SC_RINGING) || !PJSIP_IS_STATUS_IN_CLASS(rsp_status, 100))) { // Session is now set up. _session_set_up = true; _icscf->_session_establishment_tbl->increment_attempts(); _icscf->_session_establishment_network_tbl->increment_attempts(); if ((rsp_status == PJSIP_SC_RINGING) || PJSIP_IS_STATUS_IN_CLASS(rsp_status, 200)) { // Session has been set up successfully. TRC_DEBUG("Session successful"); _icscf->_session_establishment_tbl->increment_successes(); _icscf->_session_establishment_network_tbl->increment_successes(); } else if ((rsp_status == PJSIP_SC_BUSY_HERE) || (rsp_status == PJSIP_SC_BUSY_EVERYWHERE) || (rsp_status == PJSIP_SC_NOT_FOUND) || (rsp_status == PJSIP_SC_ADDRESS_INCOMPLETE)) { // Session failed, but should be counted as successful from a network // perspective. TRC_DEBUG("Session failed but network successful"); _icscf->_session_establishment_tbl->increment_failures(); _icscf->_session_establishment_network_tbl->increment_successes(); } else { // Session establishment failed. TRC_DEBUG("Session failed"); _icscf->_session_establishment_tbl->increment_failures(); _icscf->_session_establishment_network_tbl->increment_failures(); } } }
/*! * \internal * \brief Adds a path header to an outgoing 2XX response * * \param endpoint The endpoint to which the INVITE response is to be sent * \param contact The contact to which the INVITE response is to be sent * \param tdata The outbound INVITE response */ static void path_outgoing_response(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, pjsip_tx_data *tdata) { struct pjsip_status_line status = tdata->msg->line.status; pj_str_t path_dup; pjsip_generic_string_hdr *path_hdr; pjsip_contact_hdr *contact_hdr; RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup); pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); const pj_str_t REGISTER_METHOD = {"REGISTER", 8}; if (!endpoint || !pj_stristr(®ISTER_METHOD, &cseq->method.name) || !PJSIP_IS_STATUS_IN_CLASS(status.code, 200)) { return; } contact_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); if (!contact_hdr) { return; } aor = find_aor(endpoint, contact_hdr->uri); if (!aor || !aor->support_path || add_supported(tdata) || path_get_string(tdata->pool, contact, &path_dup)) { return; } path_hdr = pjsip_generic_string_hdr_create(tdata->pool, &PATH_NAME, &path_dup); if (!path_hdr) { return; } pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)path_hdr); }
/*! * \internal * \brief Adds a diversion header to an outgoing 3XX response * * \param session The session on which the INVITE response is to be sent * \param tdata The outbound INVITE response */ static void diversion_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata) { struct pjsip_status_line status = tdata->msg->line.status; /* add to 302 and 181 */ if (PJSIP_IS_STATUS_IN_CLASS(status.code, 300) || (status.code == 181)) { get_redirecting_add_diversion(session, tdata); } }
static void send_register_cb(void* token, pjsip_event *event) { ThirdPartyRegData* tsxdata = (ThirdPartyRegData*)token; pjsip_transaction* tsx = event->body.tsx_state.tsx; if ((tsxdata->default_handling == SESSION_TERMINATED) && ((tsx->status_code == 408) || (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 500)))) { std::string error_msg = "Third-party REGISTER transaction failed with code " + std::to_string(tsx->status_code); TRC_INFO(error_msg.c_str()); SAS::Event event(tsxdata->trail, SASEvent::REGISTER_AS_FAILED, 0); event.add_var_param(error_msg); SAS::report_event(event); third_party_register_failed(tsxdata->public_id, tsxdata->trail); } // printf("Expiry: %d, Is_initial_registration: %d\n", tsxdata->expires, tsxdata->is_initial_registration); if (tsx->status_code == 200) { if (tsxdata->expires == 0) { third_party_reg_stats_tables->de_reg_tbl->increment_successes(); } else if (tsxdata->is_initial_registration) { third_party_reg_stats_tables->init_reg_tbl->increment_successes(); } else { third_party_reg_stats_tables->re_reg_tbl->increment_successes(); } } else // Count all failed registration attempts, not just ones that result in user // being unsubscribed. { if (tsxdata->expires == 0) { third_party_reg_stats_tables->de_reg_tbl->increment_failures(); } else if (tsxdata->is_initial_registration) { third_party_reg_stats_tables->init_reg_tbl->increment_failures(); } else { third_party_reg_stats_tables->re_reg_tbl->increment_failures(); } } delete tsxdata; tsxdata = NULL; }
static void send_register_cb(void* token, pjsip_event *event) { ThirdPartyRegData* tsxdata = (ThirdPartyRegData*)token; pjsip_transaction* tsx = event->body.tsx_state.tsx; if ((tsxdata->default_handling == SESSION_TERMINATED) && ((tsx->status_code == 408) || (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 500)))) { std::string error_msg = "Third-party REGISTER transaction failed with code " + std::to_string(tsx->status_code); LOG_INFO(error_msg.c_str()); SAS::Event event(tsxdata->trail, SASEvent::REGISTER_AS_FAILED, 0); event.add_var_param(error_msg); SAS::report_event(event); third_party_register_failed(tsxdata->public_id, tsxdata->trail); } delete tsxdata; }
void SessionExpiresHelper::process_response(pjsip_msg* rsp, pj_pool_t* pool, SAS::TrailId trail) { // Session expires is only allowed on INVITE and UPDATE methods. pjsip_method* method = &PJSIP_MSG_CSEQ_HDR(rsp)->method; if ((pjsip_method_cmp(method, pjsip_get_invite_method()) != 0) && (pjsip_method_cmp(method, &METHOD_UPDATE) != 0)) { return; } // We only need to process successful final responses. if (!PJSIP_IS_STATUS_IN_CLASS(rsp->line.status.code, 200)) { return; } pjsip_session_expires_hdr* se_hdr = (pjsip_session_expires_hdr*) pjsip_msg_find_hdr_by_name(rsp, &STR_SESSION_EXPIRES, NULL); if (se_hdr == NULL) { // There is no session-expires header. This means we are most downstream // device that supports session timers, and in particular the UAS does not // support them. // // If the UAC does not support session timers, there's nothing more we can // do - session timers will not be used for this dialog. // // If the UAC *does* support session timers, re-add a session-expires header // that instructs the UAC to be the refresher. if (_uac_supports_timer) { se_hdr = pjsip_session_expires_hdr_create(pool); pjsip_msg_add_hdr(rsp, (pjsip_hdr*)se_hdr); se_hdr->expires = _se_on_req; se_hdr->refresher = SESSION_REFRESHER_UAC; // Also update (or add) the require header to force the UAC to do session // refreshes. pjsip_require_hdr* require_hdr = (pjsip_require_hdr*) pjsip_msg_find_hdr(rsp, PJSIP_H_REQUIRE, NULL); if (require_hdr == NULL) { require_hdr = (pjsip_require_hdr*)pjsip_require_hdr_create(pool); pjsip_msg_add_hdr(rsp, (pjsip_hdr*)require_hdr); } pj_strdup(pool, &require_hdr->values[require_hdr->count], &STR_TIMER); require_hdr->count++; } } if (_initial_request) { if (se_hdr == NULL) { SAS::Event event(trail, SASEvent::SESS_TIMER_NO_UA_SUPPORT, 0); SAS::report_event(event); } else if (se_hdr->expires > _target_se) { SAS::Event event(trail, SASEvent::SESS_TIMER_INTERVAL_TOO_LONG, 0); event.add_static_param(_target_se); event.add_static_param(se_hdr->expires); SAS::report_event(event); } } }
void ICSCFSproutletRegTsx::on_rx_response(pjsip_msg* rsp, int fork_id) { if (_acr != NULL) { // Pass the received response to the ACR. // @TODO - timestamp from response??? _acr->rx_response(rsp); } // Check if this response is one that we are allowed to retry the HSS lookup // for. See TS 24.229 - section 5.3.1.3. // // Note we support service restoration, so integrity-protected settings in // Authorization header are immaterial). pjsip_status_code rsp_status = (pjsip_status_code)rsp->line.status.code; const ForkState& fork_status = fork_state(fork_id); TRC_DEBUG("Check retry conditions for REGISTER, status = %d, S-CSCF %sresponsive", rsp_status, (fork_status.error_state != NONE) ? "not " : ""); if ((PJSIP_IS_STATUS_IN_CLASS(rsp_status, 300)) || (fork_status.error_state != NONE) || (rsp_status == PJSIP_SC_TEMPORARILY_UNAVAILABLE)) { // Indeed it is, first log to SAS. TRC_DEBUG("Attempt retry to alternate S-CSCF for REGISTER request"); std::string st_code = std::to_string(rsp_status); SAS::Event event(trail(), SASEvent::SCSCF_RETRY, 0); std::string method = "REGISTER"; event.add_var_param(method); event.add_var_param(st_code); SAS::report_event(event); // Now we can simply reuse the UA router we made on the initial request. pjsip_sip_uri* scscf_sip_uri = NULL; pjsip_msg* req = original_request(); std::string wildcard; int status_code = _router->get_scscf(get_pool(req), scscf_sip_uri, wildcard); if (status_code == PJSIP_SC_OK) { TRC_DEBUG("Found SCSCF for REGISTER"); req->line.req.uri = (pjsip_uri*)scscf_sip_uri; send_request(req); // We're not forwarding this response upstream. free_msg(rsp); } else { // In the register case the spec's are quite particular about how // failures are reported. if (status_code == PJSIP_SC_FORBIDDEN) { // The HSS has returned a negative response to the user registration // request - I-CSCF should respond with 403. rsp->line.status.code = PJSIP_SC_FORBIDDEN; rsp->line.status.reason = *pjsip_get_status_text(rsp->line.status.code); } else { // The I-CSCF can't select an S-CSCF for the REGISTER request (either // because there are no more S-CSCFs that meet the mandatory // capabilitires, or the HSS is temporarily unavailable). There was at // least one valid S-CSCF (as this is retry processing). The I-CSCF // must return 504 (TS 24.229, 5.3.1.3) in this case. rsp->line.status.code = PJSIP_SC_SERVER_TIMEOUT; rsp->line.status.reason = *pjsip_get_status_text(rsp->line.status.code); } // We're done, no more retries. free_msg(req); send_response(rsp); } } else { // Provisional, successful or non-retryable response, simply forward on // upstream. If this is a final response, there will be no more retries. send_response(rsp); } }
static void on_tsx_state(pjsip_transaction* tsx, pjsip_event* event) { StatefulSendState* sss; bool retrying = false; if ((mod_sprout_util.id < 0) || (event->type != PJSIP_EVENT_TSX_STATE)) { return; } sss = (StatefulSendState*)tsx->mod_data[mod_sprout_util.id]; if (sss == NULL) { return; } if (!sss->servers.empty()) { // The target for the request came from the resolver, so check to see // if the request failed. if ((tsx->state == PJSIP_TSX_STATE_COMPLETED) || (tsx->state == PJSIP_TSX_STATE_TERMINATED)) { // Transaction has completed or terminated. We need to look at both // states as // - timeouts and transport errors cause an immediate transition // to terminated state, bypassing completed state // - a 5xx response causes a transition to completed state, with a // possible delay until the transition to terminated state (5 seconds // for UDP transport), which would needlessly delay any retry. if ((event->body.tsx_state.type == PJSIP_EVENT_TIMER) || (event->body.tsx_state.type == PJSIP_EVENT_TRANSPORT_ERROR) || (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 500))) { // Either transaction failed on a timeout, transport error or received // 5xx error, so blacklist the failed target. LOG_DEBUG("Transaction failed with retriable error"); if ((event->body.tsx_state.type == PJSIP_EVENT_TIMER) || (event->body.tsx_state.type == PJSIP_EVENT_TRANSPORT_ERROR)) { // Either the connection failed, or the server didn't respond within // the timeout, so blacklist it. We don't blacklist servers that // return 5xx errors as this may indicate a transient overload. PJUtils::blacklist_server(sss->servers[sss->current_server]); } // Can we do a retry? ++sss->current_server; if (sss->current_server < (int)sss->servers.size()) { // More servers to try, so allocate a new branch ID and transaction. LOG_DEBUG("Attempt to resend request to next destination server"); pjsip_tx_data* tdata = sss->tdata; pjsip_transaction* retry_tsx; PJUtils::generate_new_branch_id(tdata); pj_status_t status = pjsip_tsx_create_uac(&mod_sprout_util, tdata, &retry_tsx); if (status == PJ_SUCCESS) { // The new transaction has been set up. // Set the trail ID in the transaction from the message. set_trail(retry_tsx, get_trail(tdata)); // Set up the module data for the new transaction to reference // the state information. retry_tsx->mod_data[mod_sprout_util.id] = sss; // Increment the reference count of the request as we are passing // it to a new transaction. pjsip_tx_data_add_ref(tdata); // Copy across the destination information for a retry and try to // resend the request. PJUtils::set_dest_info(tdata, sss->servers[sss->current_server]); status = pjsip_tsx_send_msg(retry_tsx, tdata); if (status == PJ_SUCCESS) { // Successfully sent a retry. Make sure this callback isn't // invoked again for the previous transaction. tsx->mod_data[mod_sprout_util.id] = NULL; retrying = true; } } } } } } if ((!retrying) && (tsx->status_code >= 200)) { // Call the user callback, if any, and prevent the callback to be called again // by clearing the transaction's module_data. LOG_DEBUG("Request transaction completed, status code = %d", tsx->status_code); tsx->mod_data[mod_sprout_util.id] = NULL; if (sss->user_cb != NULL) { (*sss->user_cb)(sss->user_token, event); } // The transaction has completed, so decrement our reference to the tx_data // and free the state data. pjsip_tx_data_dec_ref(sss->tdata); delete sss; } }
/* This function is called when we receive SUBSCRIBE request message * to refresh existing subscription. */ static void on_received_sub_refresh( pjsip_event_sub *sub, pjsip_transaction *tsx, pjsip_rx_data *rdata) { pjsip_event_hdr *e; pjsip_expires_hdr *expires; pj_str_t hname; int status = 200; pj_str_t reason_phrase = { NULL, 0 }; int new_state = sub->state; int old_state = sub->state; int new_interval = 0; pjsip_tx_data *tdata; PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received target refresh", sub, state[sub->state].ptr)); /* Check that the event matches. */ hname = pj_str("Event"); e = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL); if (!e) { status = 400; reason_phrase = pj_str("Missing Event header"); goto send_response; } if (pj_stricmp(&e->event_type, &sub->event->event_type) != 0 || pj_stricmp(&e->id_param, &sub->event->id_param) != 0) { status = 481; reason_phrase = pj_str("Subscription does not exist"); goto send_response; } /* Check server state. */ if (sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED) { status = 481; reason_phrase = pj_str("Subscription does not exist"); goto send_response; } /* Check expires header. */ expires = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_EXPIRES, NULL); if (!expires) { /* status = 400; reason_phrase = pj_str("Missing Expires header"); goto send_response; */ new_interval = sub->default_interval; } else { /* Check that interval is not too short. * Note that expires time may be zero (for unsubscription). */ new_interval = expires->ivalue; if (new_interval != 0 && new_interval < SECONDS_BEFORE_EXPIRY) { status = PJSIP_SC_INTERVAL_TOO_BRIEF; goto send_response; } } /* Update interval. */ sub->default_interval = new_interval; pj_gettimeofday(&sub->expiry_time); sub->expiry_time.sec += new_interval; /* Update timer only if this is not unsubscription. */ if (new_interval > 0) { sub->default_interval = new_interval; sub_schedule_uas_expire( sub, new_interval ); /* Call callback. */ if (sub->cb.on_received_refresh) { sub->pending_tsx++; (*sub->cb.on_received_refresh)(sub, rdata); sub->pending_tsx--; } } send_response: tdata = pjsip_endpt_create_response( sub->endpt, rdata, status); if (tdata) { if (reason_phrase.slen) tdata->msg->line.status.reason = reason_phrase; /* Add Expires header. */ expires = pjsip_expires_hdr_create(tdata->pool); expires->ivalue = sub->default_interval; pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires); if (PJSIP_IS_STATUS_IN_CLASS(status,200)) { pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events)); } /* Send down to transaction. */ pjsip_tsx_on_tx_msg(tsx, tdata); } if (sub->default_interval==0 || !PJSIP_IS_STATUS_IN_CLASS(status,200)) { /* Notify application if sub is terminated. */ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; sub_set_state(sub, new_state); if (new_state!=old_state && sub->cb.on_sub_terminated) { pj_str_t reason = {"", 0}; if (reason_phrase.slen) reason = reason_phrase; else reason = *pjsip_get_status_text(status); sub->pending_tsx++; (*sub->cb.on_sub_terminated)(sub, &reason); sub->pending_tsx--; } } pj_mutex_unlock(sub->mutex); /* Prefer to call log when we're not holding the mutex. */ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sent refresh response %s, status=%d", sub, state[sub->state].ptr, (tdata ? tdata->obj_name : "null"), status)); /* Check if application has requested deletion. */ if (sub->delete_flag && sub->pending_tsx <= 0) { pjsip_event_sub_destroy(sub); } }
/* * This callback called when we receive incoming NOTIFY request. */ static void on_notify_request(pjsip_transaction *tsx, pjsip_rx_data *rdata) { pjsip_event_sub *sub; pjsip_tx_data *tdata; int status = 200; int old_state; pj_str_t reason = { NULL, 0 }; pj_str_t reason_phrase = { NULL, 0 }; int new_state = PJSIP_EVENT_SUB_STATE_NULL; /* Find subscription based on Call-ID and From tag. * This will also automatically lock the subscription, if it's found. */ sub = find_sub(rdata); if (!sub) { /* RFC 3265: Section 3.2 Description of NOTIFY Behavior: * Answer with 481 Subscription does not exist. */ PJ_LOG(4,(THIS_FILE, "Unable to find subscription for incoming NOTIFY!")); status = 481; reason_phrase = pj_str("Subscription does not exist"); } else { pj_assert(sub->role == PJSIP_ROLE_UAC); PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received NOTIFY", sub, state[sub->state].ptr)); } new_state = old_state = sub->state; /* RFC 3265: Section 3.2.1 * Check that the Event header match the subscription. */ if (status == 200) { pjsip_event_hdr *hdr; pj_str_t hname = { "Event", 5 }; hdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL); if (!hdr) { status = PJSIP_SC_BAD_REQUEST; reason_phrase = pj_str("No Event header found"); } else if (pj_stricmp(&hdr->event_type, &sub->event->event_type) != 0 || pj_stricmp(&hdr->id_param, &sub->event->id_param) != 0) { status = 481; reason_phrase = pj_str("Subscription does not exist"); } } /* Update subscription state and timer. */ if (status == 200) { pjsip_sub_state_hdr *hdr; const pj_str_t hname = { "Subscription-State", 18 }; const pj_str_t state_active = { "active", 6 }, state_pending = { "pending", 7}, state_terminated = { "terminated", 10 }; hdr = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL); if (!hdr) { status = PJSIP_SC_BAD_REQUEST; reason_phrase = pj_str("No Subscription-State header found"); goto process; } /* * Update subscription state. */ if (pj_stricmp(&hdr->sub_state, &state_active) == 0) { if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) new_state = PJSIP_EVENT_SUB_STATE_ACTIVE; } else if (pj_stricmp(&hdr->sub_state, &state_pending) == 0) { if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) new_state = PJSIP_EVENT_SUB_STATE_PENDING; } else if (pj_stricmp(&hdr->sub_state, &state_terminated) == 0) { new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } else { new_state = PJSIP_EVENT_SUB_STATE_UNKNOWN; } reason = hdr->reason_param; if (new_state != sub->state && new_state != PJSIP_EVENT_SUB_STATE_NULL && sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) { sub_set_state(sub, new_state); if (new_state == PJSIP_EVENT_SUB_STATE_UNKNOWN) { pj_strdup_with_null(sub->pool, &sub->state_str, &hdr->sub_state); } else { sub->state_str = state[new_state]; } } /* * Update timeout timer in required, just in case notifier changed the * expiration to shorter time. * Section 3.2.2: the expires param can only shorten the interval. */ if ((sub->state==PJSIP_EVENT_SUB_STATE_ACTIVE || sub->state==PJSIP_EVENT_SUB_STATE_PENDING) && hdr->expires_param > 0) { pj_time_val now, new_expiry; pj_gettimeofday(&now); new_expiry.sec = now.sec + hdr->expires_param; if (sub->timer.id==0 || new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) { update_next_refresh(sub, hdr->expires_param); } } } process: /* Note: here we sub MAY BE NULL! */ /* Send response to NOTIFY */ tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status ); if (tdata) { if (reason_phrase.slen) tdata->msg->line.status.reason = reason_phrase; if (PJSIP_IS_STATUS_IN_CLASS(status,200)) { pjsip_hdr *hdr; hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events); pjsip_msg_add_hdr( tdata->msg, hdr); } pjsip_tsx_on_tx_msg(tsx, tdata); } /* Call NOTIFY callback, if any. */ if (sub && PJSIP_IS_STATUS_IN_CLASS(status,200) && sub->cb.on_received_notify) { sub->pending_tsx++; (*sub->cb.on_received_notify)(sub, rdata); sub->pending_tsx--; } /* Check if subscription is terminated and call callback. */ if (sub && new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) { if (sub->cb.on_sub_terminated) { sub->pending_tsx++; (*sub->cb.on_sub_terminated)(sub, &reason); sub->pending_tsx--; } } /* Check if application has requested deletion. */ if (sub && sub->delete_flag && sub->pending_tsx <= 0) { pjsip_event_sub_destroy(sub); } else if (sub) { pj_mutex_unlock(sub->mutex); } }
/* This callback is called when response to SUBSCRIBE is received. */ static void on_subscribe_response(void *token, pjsip_event *event) { pjsip_event_sub *sub = token; pjsip_transaction *tsx = event->obj.tsx; int new_state, old_state = sub->state; pj_assert(tsx->status_code >= 200); if (tsx->status_code < 200) return; pj_assert(sub->role == PJSIP_ROLE_UAC); /* Lock mutex. */ pj_mutex_lock(sub->mutex); /* If request failed with 401/407 error, silently retry the request. */ if (tsx->status_code==401 || tsx->status_code==407) { pjsip_tx_data *tdata; tdata = pjsip_auth_reinit_req(sub->endpt, sub->pool, &sub->auth_sess, sub->cred_cnt, sub->cred_info, tsx->last_tx, event->src.rdata ); if (tdata) { int status; pjsip_cseq_hdr *cseq; cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); cseq->cseq = sub->cseq++; status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, &on_subscribe_response); if (status == 0) { pj_mutex_unlock(sub->mutex); return; } } } if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) { /* Update To tag. */ if (sub->to->tag.slen == 0) pj_strdup(sub->pool, &sub->to->tag, &event->src.rdata->to_tag); new_state = sub->state; } else if (tsx->status_code == 481) { new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } else if (tsx->status_code >= 300) { /* RFC 3265 Section 3.1.4.2: * If a SUBSCRIBE request to refresh a subscription fails * with a non-481 response, the original subscription is still * considered valid for the duration of original exires. * * Note: * Since we normally send SUBSCRIBE for refreshing the subscription, * it means the subscription already expired anyway. So we terminate * the subscription now. */ if (sub->state != PJSIP_EVENT_SUB_STATE_ACTIVE) { new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } else { /* Use this to be compliant with Section 3.1.4.2 new_state = sub->state; */ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } } else { pj_assert(0); new_state = sub->state; } if (new_state != sub->state && sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) { sub_set_state(sub, new_state); } if (sub->state == PJSIP_EVENT_SUB_STATE_ACTIVE || sub->state == PJSIP_EVENT_SUB_STATE_PENDING) { /* * Register timer for next subscription refresh, but only when * we're not unsubscribing. Also update default_interval and Expires * header. */ if (sub->default_interval > 0 && !sub->delete_flag) { pjsip_expires_hdr *exp = NULL; /* Could be transaction timeout. */ if (event->src_type == PJSIP_EVENT_RX_MSG) { exp = pjsip_msg_find_hdr(event->src.rdata->msg, PJSIP_H_EXPIRES, NULL); } if (exp) { int delay = exp->ivalue; if (delay > 0) { pj_time_val new_expiry; pj_gettimeofday(&new_expiry); new_expiry.sec += delay; if (sub->timer.id==0 || new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) { //if (delay > 0 && delay < sub->default_interval) { sub->default_interval = delay; sub->uac_expires->ivalue = delay; update_next_refresh(sub, delay); } } } } } /* Call callback. */ if (!sub->delete_flag) { if (sub->cb.on_received_sub_response) { (*sub->cb.on_received_sub_response)(sub, event); } } /* Notify application if we're terminated. */ if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) { if (sub->cb.on_sub_terminated) { pj_str_t reason; if (event->src_type == PJSIP_EVENT_RX_MSG) reason = event->src.rdata->msg->line.status.reason; else reason = *pjsip_get_status_text(tsx->status_code); (*sub->cb.on_sub_terminated)(sub, &reason); } } /* Decrement pending tsx count. */ --sub->pending_tsx; pj_assert(sub->pending_tsx >= 0); if (sub->delete_flag && sub->pending_tsx <= 0) { pjsip_event_sub_destroy(sub); } else { pj_mutex_unlock(sub->mutex); } /* DO NOT ACCESS sub FROM NOW ON! IT MIGHT HAVE BEEN DELETED */ }
/* This function is called when we receive SUBSCRIBE request message for * a new subscription. */ static void on_new_subscription( pjsip_transaction *tsx, pjsip_rx_data *rdata ) { package *pkg; pj_pool_t *pool; pjsip_event_sub *sub = NULL; pj_str_t hname; int status = 200; pj_str_t reason = { NULL, 0 }; pjsip_tx_data *tdata; pjsip_expires_hdr *expires; pjsip_accept_hdr *accept; pjsip_event_hdr *evhdr; /* Get the Event header. */ hname = pj_str("Event"); evhdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL); if (!evhdr) { status = 400; reason = pj_str("No Event header in request"); goto send_response; } /* Find corresponding package. * We don't lock the manager's mutex since we assume the package list * won't change once the application is running! */ pkg = mgr.pkg_list.next; while (pkg != &mgr.pkg_list) { if (pj_stricmp(&pkg->event, &evhdr->event_type) == 0) break; pkg = pkg->next; } if (pkg == &mgr.pkg_list) { /* Event type is not supported by any packages! */ status = 489; reason = pj_str("Bad Event"); goto send_response; } /* First check that the Accept specification matches the * package's Accept types. */ accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL); if (accept) { unsigned i; pj_str_t *content_type = NULL; for (i=0; i<accept->count && !content_type; ++i) { int j; for (j=0; j<pkg->accept_cnt; ++j) { if (pj_stricmp(&accept->values[i], &pkg->accept[j])==0) { content_type = &pkg->accept[j]; break; } } } if (!content_type) { status = PJSIP_SC_NOT_ACCEPTABLE_HERE; goto send_response; } } /* Check whether the package wants to accept the subscription. */ pj_assert(pkg->cb.on_query_subscribe != NULL); (*pkg->cb.on_query_subscribe)(rdata, &status); if (!PJSIP_IS_STATUS_IN_CLASS(status,200)) goto send_response; /* Create new subscription record. */ pool = pjsip_endpt_create_pool(tsx->endpt, "esub", SUB_POOL_SIZE, SUB_POOL_INC); if (!pool) { status = 500; goto send_response; } sub = pj_pool_calloc(pool, 1, sizeof(*sub)); sub->pool = pool; sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE); if (!sub->mutex) { status = 500; goto send_response; } PJ_LOG(4,(THIS_FILE, "event_sub%p: notifier is created.", sub)); /* Start locking mutex. */ pj_mutex_lock(sub->mutex); /* Init UAS subscription */ sub->endpt = tsx->endpt; sub->role = PJSIP_ROLE_UAS; sub->state = PJSIP_EVENT_SUB_STATE_PENDING; sub->state_str = state[sub->state]; pj_list_init(&sub->auth_sess); pj_list_init(&sub->route_set); sub->from = pjsip_hdr_clone(pool, rdata->to); pjsip_fromto_set_from(sub->from); if (sub->from->tag.slen == 0) { pj_create_unique_string(pool, &sub->from->tag); rdata->to->tag = sub->from->tag; } sub->to = pjsip_hdr_clone(pool, rdata->from); pjsip_fromto_set_to(sub->to); sub->contact = pjsip_contact_hdr_create(pool); sub->contact->uri = sub->from->uri; sub->call_id = pjsip_cid_hdr_create(pool); pj_strdup(pool, &sub->call_id->id, &rdata->call_id); sub->cseq = pj_rand() % 0xFFFF; expires = pjsip_msg_find_hdr( rdata->msg, PJSIP_H_EXPIRES, NULL); if (expires) { sub->default_interval = expires->ivalue; if (sub->default_interval > 0 && sub->default_interval < SECONDS_BEFORE_EXPIRY) { status = 423; /* Interval too short. */ goto send_response; } } else { sub->default_interval = 600; } /* Clone Event header. */ sub->event = pjsip_hdr_clone(pool, evhdr); /* Register to hash table. */ create_subscriber_key(&sub->key, pool, PJSIP_ROLE_UAS, &sub->call_id->id, &sub->from->tag); pj_mutex_lock(mgr.mutex); pj_hash_set(pool, mgr.ht, sub->key.ptr, sub->key.slen, sub); pj_mutex_unlock(mgr.mutex); /* Set timer where subscription will expire only when expires<>0. * Subscriber may send new subscription with expires==0. */ if (sub->default_interval != 0) { sub_schedule_uas_expire( sub, sub->default_interval-SECONDS_BEFORE_EXPIRY); } /* Notify application. */ if (pkg->cb.on_subscribe) { pjsip_event_sub_cb *cb = NULL; sub->pending_tsx++; (*pkg->cb.on_subscribe)(sub, rdata, &cb, &sub->default_interval); sub->pending_tsx--; if (cb == NULL) pj_memset(&sub->cb, 0, sizeof(*cb)); else pj_memcpy(&sub->cb, cb, sizeof(*cb)); } send_response: PJ_LOG(4,(THIS_FILE, "event_sub%p (%s)(UAS): status=%d", sub, state[sub->state].ptr, status)); tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status); if (tdata) { if (reason.slen) { /* Customize reason text. */ tdata->msg->line.status.reason = reason; } if (PJSIP_IS_STATUS_IN_CLASS(status,200)) { /* Add Expires header. */ pjsip_expires_hdr *hdr; hdr = pjsip_expires_hdr_create(tdata->pool); hdr->ivalue = sub->default_interval; pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr ); } if (status == 423) { /* Add Min-Expires header. */ pjsip_min_expires_hdr *hdr; hdr = pjsip_min_expires_hdr_create(tdata->pool); hdr->ivalue = SECONDS_BEFORE_EXPIRY; pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr); } if (status == 489 || status==PJSIP_SC_NOT_ACCEPTABLE_HERE || PJSIP_IS_STATUS_IN_CLASS(status,200)) { /* Add Allow-Events header. */ pjsip_hdr *hdr; hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events); pjsip_msg_add_hdr(tdata->msg, hdr); /* Should add Accept header?. */ } pjsip_tsx_on_tx_msg(tsx, tdata); } /* If received new subscription with expires=0, terminate. */ if (sub && sub->default_interval == 0) { pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED); if (sub->cb.on_sub_terminated) { pj_str_t reason = { "timeout", 7 }; (*sub->cb.on_sub_terminated)(sub, &reason); } } if (!PJSIP_IS_STATUS_IN_CLASS(status,200) || (sub && sub->delete_flag)) { if (sub && sub->mutex) { pjsip_event_sub_destroy(sub); } else if (sub) { pjsip_endpt_destroy_pool(tsx->endpt, sub->pool); } } else { pj_assert(status >= 200); pj_mutex_unlock(sub->mutex); } }