/* $Id$ */ /* * Copyright (C) 2003-2006 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 #include #include #include #include #include #include /* TLS to keep dialog lock record (initialized by UA) */ int pjsip_dlg_lock_tls_id; struct dialog_lock_data { struct dialog_lock_data *prev; pjsip_dlg *dlg; int is_alive; }; /* * Static function prototypes. */ static void dlg_create_request_throw( pjsip_tx_data **p_tdata, pjsip_dlg *dlg, const pjsip_method *method, int cseq ); static int dlg_on_all_state_pre( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_all_state_post( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_null( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_incoming( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_calling( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_proceeding( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_proceeding_caller( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_proceeding_callee( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_connecting( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_established( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_disconnected( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); static int dlg_on_state_terminated( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event); /* * Dialog state handlers. */ static int (*dlg_state_handlers[])(struct pjsip_dlg *, pjsip_transaction *, pjsip_event *) = { &dlg_on_state_null, &dlg_on_state_incoming, &dlg_on_state_calling, &dlg_on_state_proceeding, &dlg_on_state_connecting, &dlg_on_state_established, &dlg_on_state_disconnected, &dlg_on_state_terminated, }; /* * Dialog state names. */ static const char* dlg_state_names[] = { "STATE_NULL", "STATE_INCOMING", "STATE_CALLING", "STATE_PROCEEDING", "STATE_CONNECTING", "STATE_ESTABLISHED", "STATE_DISCONNECTED", "STATE_TERMINATED", }; /* * Get the dialog string state, normally for logging purpose. */ const char *pjsip_dlg_state_str(pjsip_dlg_state_e state) { return dlg_state_names[state]; } /* Lock dialog mutex. */ static void lock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck) { struct dialog_lock_data *prev; pj_mutex_lock(dlg->mutex); prev = pj_thread_local_get(pjsip_dlg_lock_tls_id); lck->prev = prev; lck->dlg = dlg; lck->is_alive = 1; pj_thread_local_set(pjsip_dlg_lock_tls_id, lck); } /* Carefully unlock dialog mutex, protect against situation when the dialog * has already been destroyed. */ static pj_status_t unlock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck) { pj_assert(pj_thread_local_get(pjsip_dlg_lock_tls_id) == lck); pj_assert(dlg == lck->dlg); pj_thread_local_set(pjsip_dlg_lock_tls_id, lck->prev); if (lck->is_alive) pj_mutex_unlock(dlg->mutex); return lck->is_alive ? 0 : -1; } /* * This is called by dialog's FSM to change dialog's state. */ static void dlg_set_state( pjsip_dlg *dlg, pjsip_dlg_state_e state, pjsip_event *event) { PJ_UNUSED_ARG(event); PJ_LOG(4, (dlg->obj_name, "State %s-->%s (ev=%s, src=%s, data=%p)", pjsip_dlg_state_str(dlg->state), pjsip_dlg_state_str(state), event ? pjsip_event_str(event->type) : "", event ? pjsip_event_str(event->src_type) : "", event ? event->src.data : NULL)); dlg->state = state; dlg->handle_tsx_event = dlg_state_handlers[state]; } /* * Invoke dialog's callback. * This function is called by dialog's FSM, and interpret the event and call * the corresponding callback registered by application. */ static void dlg_call_dlg_callback( pjsip_dlg *dlg, pjsip_dlg_event_e dlg_event, pjsip_event *event ) { pjsip_dlg_callback *cb = dlg->ua->dlg_cb; if (!cb) { PJ_LOG(4,(dlg->obj_name, "Can not call callback (none registered).")); return; } /* Low level event: call the all-events callback. */ if (cb->on_all_events) { (*cb->on_all_events)(dlg, dlg_event, event); } /* Low level event: call the tx/rx callback if this is a tx/rx event. */ if (event->type == PJSIP_EVENT_BEFORE_TX && cb->on_before_tx) { (*cb->on_before_tx)(dlg, event->obj.tsx, event->src.tdata, event->data.long_data); } else if (event->type == PJSIP_EVENT_TX_MSG && event->src_type == PJSIP_EVENT_TX_MSG && cb->on_tx_msg) { (*cb->on_tx_msg)(dlg, event->obj.tsx, event->src.tdata); } else if (event->type == PJSIP_EVENT_RX_MSG && event->src_type == PJSIP_EVENT_RX_MSG && cb->on_rx_msg) { (*cb->on_rx_msg)(dlg, event->obj.tsx, event->src.rdata); } /* Now trigger high level events. * High level event should only occurs when dialog's state has changed, * except for on_provisional, which may be called multiple times whenever * response message is sent */ if (dlg->state == PJSIP_DIALOG_STATE_PROCEEDING && (event->type== PJSIP_EVENT_TSX_STATE_CHANGED) && event->obj.tsx == dlg->invite_tsx) { /* Sent/received provisional responses. */ if (cb->on_provisional) (*cb->on_provisional)(dlg, event->obj.tsx, event); } if (dlg_event == PJSIP_DIALOG_EVENT_MID_CALL_REQUEST) { if (cb->on_mid_call_events) (*cb->on_mid_call_events)(dlg, event); return; } if (dlg_event != PJSIP_DIALOG_EVENT_STATE_CHANGED) return; if (dlg->state == PJSIP_DIALOG_STATE_INCOMING) { /* New incoming dialog. */ pj_assert (event->src_type == PJSIP_EVENT_RX_MSG); (*cb->on_incoming)(dlg, event->obj.tsx, event->src.rdata); } else if (dlg->state == PJSIP_DIALOG_STATE_CALLING) { /* Dialog has just sent the first INVITE. */ if (cb->on_calling) { (*cb->on_calling)(dlg, event->obj.tsx, event->src.tdata); } } else if (dlg->state == PJSIP_DIALOG_STATE_DISCONNECTED) { if (cb->on_disconnected) (*cb->on_disconnected)(dlg, event); } else if (dlg->state == PJSIP_DIALOG_STATE_TERMINATED) { if (cb->on_terminated) (*cb->on_terminated)(dlg); pjsip_ua_destroy_dialog(dlg); } else if (dlg->state == PJSIP_DIALOG_STATE_CONNECTING) { if (cb->on_connecting) (*cb->on_connecting)(dlg, event); } else if (dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) { if (cb->on_established) (*cb->on_established)(dlg, event); } } /* * This callback receives event from the transaction layer (via User Agent), * or from dialog timer (for 200/INVITE or ACK retransmission). */ void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { int status = 0; struct dialog_lock_data lck; PJ_LOG(4, (dlg->obj_name, "state=%s (evt=%s, src=%s)", pjsip_dlg_state_str(dlg->state), pjsip_event_str(event->type), pjsip_event_str(event->src_type))); lock_dialog(dlg, &lck); status = dlg_on_all_state_pre( dlg, tsx, event); if (status==0) { status = (*dlg->handle_tsx_event)(dlg, tsx, event); } if (status==0) { status = dlg_on_all_state_post( dlg, tsx, event); } unlock_dialog(dlg, &lck); } /* * This function contains common processing in all states, and it is called * before the FSM is invoked. */ static int dlg_on_all_state_pre( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { PJ_UNUSED_ARG(event) if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED) return 0; if (tsx && (tsx->state==PJSIP_TSX_STATE_CALLING || tsx->state==PJSIP_TSX_STATE_TRYING)) { ++dlg->pending_tsx_count; } else if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) { --dlg->pending_tsx_count; if (tsx == dlg->invite_tsx) dlg->invite_tsx = NULL; } if (tsx->method.id == PJSIP_INVITE_METHOD) { tsx->handle_ack = 1; } return 0; } /* * This function contains common processing in all states, and it is called * after the FSM is invoked. */ static int dlg_on_all_state_post( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { PJ_UNUSED_ARG(event) if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) { if (dlg->pending_tsx_count == 0 && dlg->state != PJSIP_DIALOG_STATE_CONNECTING && dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED && dlg->state != PJSIP_DIALOG_STATE_TERMINATED) { dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); return -1; } } return 0; } /* * Internal function to initialize dialog, contains common initialization * for both UAS and UAC dialog. */ static pj_status_t dlg_init( pjsip_dlg *dlg ) { /* Init mutex. */ dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0); if (!dlg->mutex) { PJ_PERROR((dlg->obj_name, "pj_mutex_create()")); return -1; } /* Init route-set (Initially empty) */ pj_list_init(&dlg->route_set); /* Init auth credential list. */ pj_list_init(&dlg->auth_sess); return PJ_SUCCESS; } /* * This one is called just before dialog is destroyed. * It is called while mutex is held. */ PJ_DEF(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg ) { struct dialog_lock_data *lck; //PJ_TODO(CHECK_THIS); pj_assert(dlg->pending_tsx_count == 0); /* Mark dialog as dead. */ lck = pj_thread_local_get(pjsip_dlg_lock_tls_id); while (lck) { if (lck->dlg == dlg) lck->is_alive = 0; lck = lck->prev; } } /* * Initialize dialog from the received request. * This is an internal function which is called by the User Agent (sip_ua.c), * and it will initialize most of dialog's properties with values from the * received message. */ pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg, pjsip_rx_data *rdata ) { pjsip_msg *msg = rdata->msg; pjsip_to_hdr *to; pjsip_contact_hdr *contact; pjsip_name_addr *name_addr; pjsip_sip_uri *url; unsigned flag; pjsip_event event; pj_assert(dlg && rdata); PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p)", rdata)); /* Must be an INVITE request. */ pj_assert(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_INVITE_METHOD); /* Init general dialog data. */ if (dlg_init(dlg) != PJ_SUCCESS) { return -1; } /* Get the To header. */ to = rdata->to; /* Copy URI in the To header as our local URI. */ dlg->local.info = pjsip_hdr_clone( dlg->pool, to); /* Set tag in the local info. */ dlg->local.info->tag = dlg->local.tag; /* Create local Contact to be advertised in the response. * At the moment, just copy URI from the local URI as our contact. */ dlg->local.contact = pjsip_contact_hdr_create( dlg->pool ); dlg->local.contact->star = 0; name_addr = (pjsip_name_addr *)dlg->local.info->uri; dlg->local.contact->uri = (pjsip_uri*) name_addr; url = (pjsip_sip_uri*) name_addr->uri; //url->port = rdata->via->sent_by.port; //url->port = pj_sockaddr_get_port( pjsip_transport_get_local_addr(rdata->transport) ); /* Save remote URI. */ dlg->remote.info = pjsip_hdr_clone( dlg->pool, rdata->from ); pjsip_fromto_set_to( dlg->remote.info ); pj_strdup( dlg->pool, &dlg->remote.tag, &rdata->from->tag ); /* Save remote Contact. */ contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); if (contact) { dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact ); } else { PJ_LOG(3,(dlg->obj_name, "No Contact header in INVITE from %s", pj_sockaddr_get_str_addr(&rdata->addr))); dlg->remote.contact = pjsip_contact_hdr_create( dlg->pool ); dlg->remote.contact->uri = dlg->remote.info->uri; } /* Save Call-ID. */ dlg->call_id = pjsip_cid_hdr_create(dlg->pool); pj_strdup( dlg->pool, &dlg->call_id->id, &rdata->call_id ); /* Initialize local CSeq and save remote CSeq.*/ dlg->local.cseq = rdata->timestamp.sec & 0xFFFF; dlg->remote.cseq = rdata->cseq->cseq; /* Secure? */ flag = pjsip_transport_get_flag(rdata->transport); dlg->secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; /* Initial state is NULL. */ event.type = event.src_type = PJSIP_EVENT_RX_MSG; event.src.rdata = rdata; dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event); PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p) complete", rdata)); return PJ_SUCCESS; } /* * Set the contact details. */ PJ_DEF(pj_status_t) pjsip_dlg_set_contact( pjsip_dlg *dlg, const pj_str_t *contact ) { pjsip_uri *local_uri; pj_str_t tmp; pj_strdup_with_null(dlg->pool, &tmp, contact); local_uri = pjsip_parse_uri( dlg->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (local_uri == NULL) { PJ_LOG(2, (dlg->obj_name, "set_contact: invalid URI")); return -1; } dlg->local.contact->star = 0; dlg->local.contact->uri = local_uri; return 0; } /* * Set route set. */ PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dlg *dlg, const pjsip_route_hdr *route_set ) { pjsip_route_hdr *hdr; pj_list_init(&dlg->route_set); hdr = route_set->next; while (hdr != route_set) { pjsip_route_hdr *cloned = pjsip_hdr_clone(dlg->pool, hdr); pj_list_insert_before( &dlg->route_set, cloned); hdr = hdr->next; } return 0; } /* * Set route set without cloning the header. */ PJ_DEF(pj_status_t) pjsip_dlg_set_route_set_np( pjsip_dlg *dlg, pjsip_route_hdr *route_set) { pjsip_route_hdr *hdr; pj_list_init(&dlg->route_set); hdr = route_set->next; while (hdr != route_set) { pj_list_insert_before( &dlg->route_set, hdr); hdr = hdr->next; } return 0; } /* * Application calls this function when it wants to initiate an outgoing * dialog (incoming dialogs are created automatically by UA when it receives * INVITE, by calling pjsip_dlg_init_from_rdata()). * This function should initialize most of the dialog's properties. */ PJ_DEF(pj_status_t) pjsip_dlg_init( pjsip_dlg *dlg, const pj_str_t *c_local_info, const pj_str_t *c_remote_info, const pj_str_t *c_target) { pj_time_val tv; pjsip_event event; pj_str_t buf; if (!dlg || !c_local_info || !c_remote_info) { pj_assert(dlg && c_local_info && c_remote_info); return -1; } PJ_LOG(5, (dlg->obj_name, "initializing")); /* Init general dialog */ if (dlg_init(dlg) != PJ_SUCCESS) { return -1; } /* Duplicate local info. */ pj_strdup_with_null( dlg->pool, &buf, c_local_info); /* Build local URI. */ dlg->local.target = pjsip_parse_uri(dlg->pool, buf.ptr, buf.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (dlg->local.target == NULL) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_init: invalid local URI %s", buf.ptr)); return -1; } /* Set local URI. */ dlg->local.info = pjsip_from_hdr_create(dlg->pool); dlg->local.info->uri = dlg->local.target; dlg->local.info->tag = dlg->local.tag; /* Create local Contact to be advertised in the response. */ dlg->local.contact = pjsip_contact_hdr_create( dlg->pool ); dlg->local.contact->star = 0; dlg->local.contact->uri = dlg->local.target; /* Set remote URI. */ dlg->remote.info = pjsip_to_hdr_create(dlg->pool); /* Duplicate to buffer. */ pj_strdup_with_null( dlg->pool, &buf, c_remote_info); /* Build remote info. */ dlg->remote.info->uri = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (dlg->remote.info->uri == NULL) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_init: invalid remote URI %s", buf.ptr)); return -1; } /* Set remote Contact initially equal to the remote URI. */ dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool); dlg->remote.contact->star = 0; dlg->remote.contact->uri = dlg->remote.info->uri; /* Set initial remote target. */ if (c_target != NULL) { pj_strdup_with_null( dlg->pool, &buf, c_target); dlg->remote.target = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen, 0); if (dlg->remote.target == NULL) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_init: invalid remote target %s", buf.ptr)); return -1; } } else { dlg->remote.target = dlg->remote.info->uri; } /* Create globally unique Call-ID */ dlg->call_id = pjsip_cid_hdr_create(dlg->pool); pj_create_unique_string( dlg->pool, &dlg->call_id->id ); /* Local and remote CSeq */ pj_gettimeofday(&tv); dlg->local.cseq = tv.sec & 0xFFFF; dlg->remote.cseq = 0; /* Initial state is NULL. */ event.type = event.src_type = PJSIP_EVENT_TX_MSG; event.src.data = NULL; dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event); /* Done. */ PJ_LOG(4, (dlg->obj_name, "%s dialog initialized, From: %.*s, To: %.*s", pjsip_role_name(dlg->role), c_local_info->slen, c_local_info->ptr, c_remote_info->slen, c_remote_info->ptr)); return PJ_SUCCESS; } /* * Set credentials. */ PJ_DEF(pj_status_t) pjsip_dlg_set_credentials( pjsip_dlg *dlg, int count, const pjsip_cred_info *cred) { if (count > 0) { dlg->cred_info = pj_pool_alloc(dlg->pool, count * sizeof(pjsip_cred_info)); pj_memcpy(dlg->cred_info, cred, count * sizeof(pjsip_cred_info)); } dlg->cred_count = count; return 0; } /* * Create a new request within dialog (i.e. after the dialog session has been * established). The construction of such requests follows the rule in * RFC3261 section 12.2.1. */ static void dlg_create_request_throw( pjsip_tx_data **p_tdata, pjsip_dlg *dlg, const pjsip_method *method, int cseq ) { pjsip_tx_data *tdata; pjsip_contact_hdr *contact; pjsip_route_hdr *route, *end_list; /* Contact Header field. * Contact can only be present in requests that establish dialog (in the * core SIP spec, only INVITE). */ if (method->id == PJSIP_INVITE_METHOD) contact = dlg->local.contact; else contact = NULL; tdata = pjsip_endpt_create_request_from_hdr( dlg->ua->endpt, method, dlg->remote.target, dlg->local.info, dlg->remote.info, contact, dlg->call_id, cseq, NULL); if (!tdata) { PJ_THROW(1); return; } /* Just copy dialog route-set to Route header. * The transaction will do the processing as specified in Section 12.2.1 * of RFC 3261 in function tsx_process_route() in sip_transaction.c. */ route = dlg->route_set.next; end_list = &dlg->route_set; for (; route != end_list; route = route->next ) { pjsip_route_hdr *r; r = pjsip_hdr_shallow_clone( tdata->pool, route ); pjsip_routing_hdr_set_route(r); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r); } /* Copy authorization headers. */ pjsip_auth_init_req( dlg->pool, tdata, &dlg->auth_sess, dlg->cred_count, dlg->cred_info); *p_tdata = tdata; } /* * This function is called by application to create new outgoing request * message for this dialog. After the request is created, application can * modify the message (such adding headers), and eventually send the request. */ PJ_DEF(pjsip_tx_data*) pjsip_dlg_create_request( pjsip_dlg *dlg, const pjsip_method *method, int cseq) { PJ_USE_EXCEPTION; struct dialog_lock_data lck; pjsip_tx_data *tdata = NULL; pj_assert(dlg != NULL && method != NULL); if (!dlg || !method) { return NULL; } PJ_LOG(5, (dlg->obj_name, "Creating request")); /* Lock dialog. */ lock_dialog(dlg, &lck); /* Use outgoing CSeq and increment it by one. */ if (cseq < 0) cseq = dlg->local.cseq + 1; PJ_LOG(5, (dlg->obj_name, "creating request %.*s cseq=%d", method->name.slen, method->name.ptr, cseq)); /* Create the request. */ PJ_TRY { dlg_create_request_throw(&tdata, dlg, method, cseq); PJ_LOG(5, (dlg->obj_name, "request data %s created", tdata->obj_name)); } PJ_CATCH_ANY { /* Failed! Delete transmit data. */ if (tdata) { pjsip_tx_data_dec_ref( tdata ); tdata = NULL; } } PJ_END; /* Unlock dialog. */ unlock_dialog(dlg, &lck); return tdata; } /* * Sends request. * Select the transport for the request message */ static pj_status_t dlg_send_request( pjsip_dlg *dlg, pjsip_tx_data *tdata ) { pjsip_transaction *tsx; pj_status_t status = PJ_SUCCESS; struct dialog_lock_data lck; pj_assert(dlg != NULL && tdata != NULL); if (!dlg || !tdata) { return -1; } PJ_LOG(5, (dlg->obj_name, "sending request %s", tdata->obj_name)); /* Lock dialog. */ lock_dialog(dlg, &lck); /* Create a new transaction. */ tsx = pjsip_endpt_create_tsx( dlg->ua->endpt ); if (!tsx) { unlock_dialog(dlg, &lck); return -1; } PJ_LOG(4, (dlg->obj_name, "Created new UAC transaction: %s", tsx->obj_name)); /* Initialize transaction */ tsx->module_data[dlg->ua->mod_id] = dlg; status = pjsip_tsx_init_uac( tsx, tdata ); if (status != PJ_SUCCESS) { unlock_dialog(dlg, &lck); pjsip_endpt_destroy_tsx( dlg->ua->endpt, tsx ); return -1; } pjsip_endpt_register_tsx( dlg->ua->endpt, tsx ); /* Start the transaction. */ pjsip_tsx_on_tx_msg(tsx, tdata); /* Unlock dialog. */ unlock_dialog(dlg, &lck); return status; } /* * This function can be called by application to send ANY outgoing message * to remote party. */ PJ_DEF(pj_status_t) pjsip_dlg_send_msg( pjsip_dlg *dlg, pjsip_tx_data *tdata ) { pj_status_t status; int tsx_status; struct dialog_lock_data lck; pj_assert(dlg != NULL && tdata != NULL); if (!dlg || !tdata) { return -1; } lock_dialog(dlg, &lck); if (tdata->msg->type == PJSIP_REQUEST_MSG) { int request_cseq; pjsip_msg *msg = tdata->msg; pjsip_cseq_hdr *cseq_hdr; switch (msg->line.req.method.id) { case PJSIP_CANCEL_METHOD: /* Check the INVITE transaction state. */ tsx_status = dlg->invite_tsx->status_code; if (tsx_status >= 200) { /* Already terminated. Can't cancel. */ status = -1; goto on_return; } /* If we've got provisional response, then send CANCEL and wait for * the response to INVITE to arrive. Otherwise just send CANCEL and * terminate the INVITE. */ if (tsx_status < 100) { pjsip_tsx_terminate( dlg->invite_tsx, PJSIP_SC_REQUEST_TERMINATED); status = 0; goto on_return; } status = 0; request_cseq = dlg->invite_tsx->cseq; break; case PJSIP_ACK_METHOD: /* Sending ACK outside of transaction is not supported at present! */ pj_assert(0); status = 0; request_cseq = dlg->local.cseq; break; case PJSIP_INVITE_METHOD: /* For an initial INVITE, reset dialog state to NULL so we get * 'normal' UAC notifications such as on_provisional(), etc. * Initial INVITE is the request that is sent when the dialog has * not been established yet. It's not necessarily the first INVITE * sent, as when the Authorization fails, subsequent INVITE are also * considered as an initial INVITE. */ if (dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { /* Set state to NULL. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, NULL); } else { /* This is a re-INVITE */ } status = 0; request_cseq = dlg->local.cseq + 1; break; default: status = 0; request_cseq = dlg->local.cseq + 1; break; } if (status != 0) goto on_return; /* Update dialog's local CSeq, if necessary. */ if (request_cseq != dlg->local.cseq) dlg->local.cseq = request_cseq; /* Update CSeq header in the request. */ cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg, PJSIP_H_CSEQ, NULL); pj_assert(cseq_hdr != NULL); /* Update the CSeq */ cseq_hdr->cseq = request_cseq; /* Force the whole message to be re-printed. */ pjsip_tx_data_invalidate_msg( tdata ); /* Now send the request. */ status = dlg_send_request(dlg, tdata); } else { /* * This is only valid for sending response to INVITE! */ pjsip_cseq_hdr *cseq_hdr; if (dlg->invite_tsx == NULL || dlg->invite_tsx->status_code >= 200) { status = -1; goto on_return; } cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg, PJSIP_H_CSEQ, NULL); pj_assert(cseq_hdr); if (cseq_hdr->method.id != PJSIP_INVITE_METHOD) { status = -1; goto on_return; } pj_assert(cseq_hdr->cseq == dlg->invite_tsx->cseq); pjsip_tsx_on_tx_msg(dlg->invite_tsx, tdata); status = 0; } on_return: /* Unlock dialog. */ unlock_dialog(dlg, &lck); /* Whatever happen delete the message. */ pjsip_tx_data_dec_ref( tdata ); return status; } /* * Sends outgoing invitation. */ PJ_DEF(pjsip_tx_data*) pjsip_dlg_invite( pjsip_dlg *dlg ) { pjsip_method method; struct dialog_lock_data lck; const pjsip_allow_hdr *allow_hdr; pjsip_tx_data *tdata; pj_assert(dlg != NULL); if (!dlg) { return NULL; } PJ_LOG(4, (dlg->obj_name, "request to send invitation")); /* Lock dialog. */ lock_dialog(dlg, &lck); /* Create request. */ pjsip_method_set( &method, PJSIP_INVITE_METHOD); tdata = pjsip_dlg_create_request( dlg, &method, -1 ); if (tdata == NULL) { unlock_dialog(dlg, &lck); return NULL; } /* Invite SHOULD contain "Allow" header. */ allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt ); if (allow_hdr) { pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone( tdata->pool, allow_hdr)); } /* Unlock dialog. */ unlock_dialog(dlg, &lck); return tdata; } /* * Cancel pending outgoing dialog invitation. */ PJ_DEF(pjsip_tx_data*) pjsip_dlg_cancel( pjsip_dlg *dlg ) { pjsip_tx_data *tdata = NULL; struct dialog_lock_data lck; pj_assert(dlg != NULL); if (!dlg) { return NULL; } PJ_LOG(4, (dlg->obj_name, "request to cancel invitation")); lock_dialog(dlg, &lck); /* Check the INVITE transaction. */ if (dlg->invite_tsx == NULL || dlg->role != PJSIP_ROLE_UAC) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: " "no INVITE transaction found")); goto on_return; } /* Construct the CANCEL request. */ tdata = pjsip_endpt_create_cancel( dlg->ua->endpt, dlg->invite_tsx->last_tx ); if (tdata == NULL) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: " "unable to construct request")); goto on_return; } /* Add reference counter to tdata. */ pjsip_tx_data_add_ref(tdata); on_return: unlock_dialog(dlg, &lck); return tdata; } /* * Answer incoming dialog invitation, with either provisional responses * or a final response. */ PJ_DEF(pjsip_tx_data*) pjsip_dlg_answer( pjsip_dlg *dlg, int code ) { pjsip_tx_data *tdata = NULL; pjsip_msg *msg; struct dialog_lock_data lck; pj_assert(dlg != NULL); if (!dlg) { return NULL; } PJ_LOG(4, (dlg->obj_name, "pjsip_dlg_answer: code=%d", code)); /* Lock dialog. */ lock_dialog(dlg, &lck); /* Must have pending INVITE. */ if (dlg->invite_tsx == NULL) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: no INVITE transaction found")); goto on_return; } /* Must be UAS. */ if (dlg->role != PJSIP_ROLE_UAS) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: not UAS")); goto on_return; } /* Must have not answered with final response before. */ if (dlg->invite_tsx->status_code >= 200) { PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: transaction already terminated " "with status %d", dlg->invite_tsx->status_code)); goto on_return; } /* Get transmit data and the message. * We will rewrite the message with a new status code. */ only if tdata is not pending!!! tdata = dlg->invite_tsx->last_tx; msg = tdata->msg; /* Set status code and reason phrase. */ if (code < 100 || code >= 700) code = 500; msg->line.status.code = code; msg->line.status.reason = *pjsip_get_status_text(code); /* For 2xx response, Contact and Record-Route must be added. */ if (PJSIP_IS_STATUS_IN_CLASS(code,200)) { const pjsip_allow_hdr *allow_hdr; if (pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL) == NULL) { pjsip_contact_hdr *contact; contact = pjsip_hdr_shallow_clone( tdata->pool, dlg->local.contact); pjsip_msg_add_hdr( msg, (pjsip_hdr*)contact ); } /* 2xx response MUST contain "Allow" header. */ allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt ); if (allow_hdr) { pjsip_msg_add_hdr( msg, pjsip_hdr_shallow_clone( tdata->pool, allow_hdr)); } } /* for all but 100 responses, To-tag must be set. */ if (code != 100) { pjsip_to_hdr *to; to = pjsip_msg_find_hdr( msg, PJSIP_H_TO, NULL); to->tag = dlg->local.tag; } /* Reset packet buffer. */ pjsip_tx_data_invalidate_msg(tdata); /* Add reference counter */ pjsip_tx_data_add_ref(tdata); on_return: /* Unlock dialog. */ unlock_dialog(dlg, &lck); return tdata; } /* * Send BYE request to terminate the dialog's session. */ PJ_DEF(pjsip_tx_data*) pjsip_dlg_bye( pjsip_dlg *dlg ) { pjsip_method method; struct dialog_lock_data lck; pjsip_tx_data *tdata; if (!dlg) { pj_assert(dlg != NULL); return NULL; } PJ_LOG(4, (dlg->obj_name, "request to terminate session")); lock_dialog(dlg, &lck); pjsip_method_set( &method, PJSIP_BYE_METHOD); tdata = pjsip_dlg_create_request( dlg, &method, -1 ); unlock_dialog(dlg, &lck); return tdata; } /* * High level function to disconnect dialog's session. Depending on dialog's * state, this function will either send CANCEL, final response, or BYE to * trigger the disconnection. A status code must be supplied, which will be * sent if dialog will be transmitting a final response to INVITE. */ PJ_DEF(pjsip_tx_data*) pjsip_dlg_disconnect( pjsip_dlg *dlg, int status_code ) { pjsip_tx_data *tdata = NULL; pj_assert(dlg != NULL); if (!dlg) { return NULL; } switch (dlg->state) { case PJSIP_DIALOG_STATE_INCOMING: tdata = pjsip_dlg_answer(dlg, status_code); break; case PJSIP_DIALOG_STATE_CALLING: tdata = pjsip_dlg_cancel(dlg); break; case PJSIP_DIALOG_STATE_PROCEEDING: if (dlg->role == PJSIP_ROLE_UAC) { tdata = pjsip_dlg_cancel(dlg); } else { tdata = pjsip_dlg_answer(dlg, status_code); } break; case PJSIP_DIALOG_STATE_ESTABLISHED: tdata = pjsip_dlg_bye(dlg); break; default: PJ_LOG(4,(dlg->obj_name, "Invalid state %s in pjsip_dlg_disconnect()", dlg_state_names[dlg->state])); break; } return tdata; } /* * Handling of the receipt of 2xx/INVITE response. */ static void dlg_on_recv_2xx_invite( pjsip_dlg *dlg, pjsip_event *event ) { pjsip_msg *msg; pjsip_contact_hdr *contact; pjsip_hdr *hdr, *end_hdr; pjsip_method method; pjsip_tx_data *ack_tdata; /* Get the message */ msg = event->src.rdata->msg; /* Update remote's tag information. */ pj_strdup(dlg->pool, &dlg->remote.info->tag, &event->src.rdata->to_tag); /* Copy Contact information in the 2xx/INVITE response to dialog's. * remote contact */ contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); if (contact) { dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact ); } else { /* duplicate contact from "From" header (?) */ PJ_LOG(4,(dlg->obj_name, "Received 200/OK to INVITE with no Contact!")); dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool); dlg->remote.contact->uri = dlg->remote.info->uri; } /* Copy Record-Route header (in reverse order) as dialog's route-set, * overwriting previous route-set, if any, even if the received route-set * is empty. */ pj_list_init(&dlg->route_set); end_hdr = &msg->hdr; for (hdr = msg->hdr.prev; hdr!=end_hdr; hdr = hdr->prev) { if (hdr->type == PJSIP_H_RECORD_ROUTE) { pjsip_route_hdr *r; r = pjsip_hdr_clone(dlg->pool, hdr); pjsip_routing_hdr_set_route(r); pj_list_insert_before(&dlg->route_set, r); } } /* On receipt of 200/INVITE response, send ACK. * This ack must be saved and retransmitted whenever we receive * 200/INVITE retransmission, until 64*T1 seconds elapsed. */ pjsip_method_set( &method, PJSIP_ACK_METHOD); ack_tdata = pjsip_dlg_create_request( dlg, &method, dlg->invite_tsx->cseq); if (ack_tdata == NULL) { //PJ_TODO(HANDLE_CREATE_ACK_FAILURE) PJ_LOG(2, (dlg->obj_name, "Error sending ACK msg: can't create request")); return; } /* Send with the transaction. */ pjsip_tsx_on_tx_ack( dlg->invite_tsx, ack_tdata); /* Decrement reference counter because pjsip_dlg_create_request * automatically increments the request. */ pjsip_tx_data_dec_ref( ack_tdata ); } /* * State NULL, before any events have been received. */ static int dlg_on_state_null( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_RX_MSG) { pjsip_hdr *hdr, *hdr_list; pj_assert(tsx->method.id == PJSIP_INVITE_METHOD); /* Save the INVITE transaction. */ dlg->invite_tsx = tsx; /* Change state to INCOMING */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_INCOMING, event); /* Create response buffer. */ tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 100); pjsip_tx_data_add_ref(tsx->last_tx); /* Copy the Record-Route headers into dialog's route_set, maintaining * the order. */ pj_list_init(&dlg->route_set); hdr_list = &event->src.rdata->msg->hdr; hdr = hdr_list->next; while (hdr != hdr_list) { if (hdr->type == PJSIP_H_RECORD_ROUTE) { pjsip_route_hdr *route; route = pjsip_hdr_clone(dlg->pool, hdr); pjsip_routing_hdr_set_route(route); pj_list_insert_before(&dlg->route_set, route); } hdr = hdr->next; } /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_TX_MSG) { pj_assert(tsx->method.id == PJSIP_INVITE_METHOD); /* Save the INVITE transaction. */ dlg->invite_tsx = tsx; /* Change state to CALLING. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_CALLING, event); /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); } else { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } return 0; } /* * State INCOMING is after the (callee) dialog has been initialized with * the incoming request, but before any responses is sent by the dialog. */ static int dlg_on_state_incoming( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { return dlg_on_state_proceeding_callee( dlg, tsx, event ); } /* * State CALLING is after the (caller) dialog has sent outgoing invitation * but before any responses are received. */ static int dlg_on_state_calling( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { if (tsx == dlg->invite_tsx) { return dlg_on_state_proceeding_caller( dlg, tsx, event ); } return 0; } /* * State PROCEEDING is after provisional response is received. * Since the processing is similar to state CALLING, this function is also * called for CALLING state. */ static int dlg_on_state_proceeding_caller( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { int dlg_is_terminated = 0; /* We only care about our INVITE transaction. * Ignore other transaction progression (such as CANCEL). */ if (tsx != dlg->invite_tsx) { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); return 0; } if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED) { switch (tsx->state) { case PJSIP_TSX_STATE_PROCEEDING: if (dlg->state != PJSIP_DIALOG_STATE_PROCEEDING) { /* Change state to PROCEEDING */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event); /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); } else { /* Also notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } break; case PJSIP_TSX_STATE_COMPLETED: /* Change dialog state. */ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { /* Update remote target, take it from the contact hdr. */ pjsip_contact_hdr *contact; contact = pjsip_msg_find_hdr(event->src.rdata->msg, PJSIP_H_CONTACT, NULL); if (contact) { dlg->remote.target = pjsip_uri_clone(dlg->pool, contact->uri); } else { PJ_LOG(4,(dlg->obj_name, "Warning: found no Contact hdr in 200/OK")); } dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event); } else if (tsx->status_code==401 || tsx->status_code==407) { /* Handle Authentication challenge. */ pjsip_tx_data *tdata; tdata = pjsip_auth_reinit_req( dlg->ua->endpt, dlg->pool, &dlg->auth_sess, dlg->cred_count, dlg->cred_info, tsx->last_tx, event->src.rdata); if (tdata) { /* Re-use original request, with a new transaction. * Need not to worry about CSeq, dialog will take care. */ pjsip_dlg_send_msg(dlg, tdata); return 0; } else { dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); } } else { dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); } /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); /* Send ACK when dialog is connected. */ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { pj_assert(event->src_type == PJSIP_EVENT_RX_MSG); dlg_on_recv_2xx_invite(dlg, event); } break; case PJSIP_TSX_STATE_TERMINATED: /* * Transaction is terminated because of timeout or transport error. * To let the application go to normal state progression, call the * callback twice. First is to emulate disconnection, and then call * again (with state TERMINATED) to destroy the dialog. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); /* The INVITE transaction will be destroyed, so release reference * to it. */ dlg->invite_tsx = NULL; /* We should terminate the dialog now. * But it's possible that we have other pending transactions (for * example, outgoing CANCEL is in progress). * So destroy the dialog only if there's no other transaction. */ if (dlg->pending_tsx_count == 0) { dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); dlg_is_terminated = 1; } break; default: pj_assert(0); break; } } else { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } return dlg_is_terminated ? -1 : 0; } /* * State PROCEEDING for UAS is after the callee send provisional response. * This function is also called for INCOMING state. */ static int dlg_on_state_proceeding_callee( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { int dlg_is_terminated = 0; pj_assert( dlg->invite_tsx != NULL ); if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_TX_MSG && tsx == dlg->invite_tsx) { switch (tsx->state) { case PJSIP_TSX_STATE_PROCEEDING: /* Change state to PROCEEDING */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event); /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); break; case PJSIP_TSX_STATE_COMPLETED: case PJSIP_TSX_STATE_TERMINATED: /* Change dialog state. */ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event); } else { dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); } /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); /* If transaction is terminated in non-2xx situation, * terminate dialog as well. This happens when something unexpected * occurs, such as transport error. */ if (tsx->state == PJSIP_TSX_STATE_TERMINATED && !PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); dlg_is_terminated = 1; } break; default: pj_assert(0); break; } } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_RX_MSG && tsx->method.id == PJSIP_CANCEL_METHOD) { pjsip_tx_data *tdata; /* Check if sequence number matches the pending INVITE. */ if (dlg->invite_tsx==NULL || pj_strcmp(&tsx->branch, &dlg->invite_tsx->branch) != 0) { PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no matching INVITE")); /* No matching INVITE transaction found. */ tdata = pjsip_endpt_create_response(dlg->ua->endpt, event->src.rdata, PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ); pjsip_tsx_on_tx_msg(tsx, tdata); return 0; } /* Always respond the CANCEL with 200/CANCEL no matter what. */ tdata = pjsip_endpt_create_response(dlg->ua->endpt, event->src.rdata, 200 ); pjsip_tsx_on_tx_msg( tsx, tdata ); /* Respond the INVITE transaction with 487, only if transaction has * not completed. */ if (dlg->invite_tsx->last_tx) { if (dlg->invite_tsx->status_code < 200) { tdata = dlg->invite_tsx->last_tx; tdata->msg->line.status.code = 487; tdata->msg->line.status.reason = *pjsip_get_status_text(487); /* Reset packet buffer. */ pjsip_tx_data_invalidate_msg(tdata); pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata ); } else { PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no effect, " "Transaction already terminated " "with status %d", dlg->invite_tsx->status_code)); } } else { tdata = pjsip_endpt_create_response(dlg->ua->endpt, event->src.rdata, 487); pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata ); } } else { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } return dlg_is_terminated ? -1 : 0; } static int dlg_on_state_proceeding( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { if (dlg->role == PJSIP_ROLE_UAC) { return dlg_on_state_proceeding_caller( dlg, tsx, event ); } else { return dlg_on_state_proceeding_callee( dlg, tsx, event ); } } static int dlg_on_state_connecting( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { if (tsx == dlg->invite_tsx) { if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && (tsx->state == PJSIP_TSX_STATE_TERMINATED || tsx->state == PJSIP_TSX_STATE_COMPLETED || tsx->state == PJSIP_TSX_STATE_CONFIRMED)) { if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { dlg_set_state(dlg, PJSIP_DIALOG_STATE_ESTABLISHED, event); } else { /* Probably because we never get the ACK, or transport error * when sending ACK. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); } dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); } else { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } } else { /* Handle case when transaction is started when dialog is connecting * (e.g. BYE requests cross wire. */ if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_RX_MSG && tsx->role == PJSIP_ROLE_UAS) { pjsip_tx_data *response; if (tsx->status_code >= 200) return 0; if (tsx->method.id == PJSIP_BYE_METHOD) { /* Set state to DISCONNECTED. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 200); } else { response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, PJSIP_SC_INTERNAL_SERVER_ERROR); } if (response) pjsip_tsx_on_tx_msg(tsx, response); return 0; } } return 0; } static int dlg_on_state_established( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { PJ_UNUSED_ARG(tsx) if (tsx && tsx->method.id == PJSIP_BYE_METHOD) { /* Set state to DISCONNECTED. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); /* Answer with 200/BYE. */ if (event->src_type == PJSIP_EVENT_RX_MSG) { pjsip_tx_data *tdata; tdata = pjsip_endpt_create_response(dlg->ua->endpt, event->src.rdata, 200 ); if (tdata) pjsip_tsx_on_tx_msg( tsx, tdata ); } } else if (tsx && event->src_type == PJSIP_EVENT_RX_MSG) { pjsip_method_e method = event->src.rdata->cseq->method.id; PJ_TODO(PROPERLY_HANDLE_REINVITATION) /* Reinvitation. The message may be INVITE or an ACK. */ if (method == PJSIP_INVITE_METHOD) { if (dlg->invite_tsx && dlg->invite_tsx->status_code < 200) { /* Section 14.2: A UAS that receives a second INVITE before it * sends the final response to a first INVITE with a lower * CSeq sequence number on the same dialog MUST return a 500 * (Server Internal Error) response to the second INVITE and * MUST include a Retry-After header field with a randomly * chosen value of between 0 and 10 seconds. */ pjsip_retry_after_hdr *hdr; pjsip_tx_data *tdata = pjsip_endpt_create_response(dlg->ua->endpt, event->src.rdata, 500); if (!tdata) return 0; /* Add Retry-After. */ hdr = pjsip_retry_after_hdr_create(tdata->pool); hdr->ivalue = 9; pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); /* Send. */ pjsip_tsx_on_tx_msg(tsx, tdata); return 0; } /* Keep this as our current INVITE transaction. */ dlg->invite_tsx = tsx; /* Create response buffer. */ tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 100); pjsip_tx_data_add_ref(tsx->last_tx); } /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_MID_CALL_REQUEST, event); } else { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } return 0; } static int dlg_on_state_disconnected( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { PJ_UNUSED_ARG(tsx) /* Handle case when transaction is started when dialog is disconnected * (e.g. BYE requests cross wire. */ if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_RX_MSG && tsx->role == PJSIP_ROLE_UAS) { pjsip_tx_data *response = NULL; if (tsx->status_code >= 200) return 0; if (tsx->method.id == PJSIP_BYE_METHOD) { response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 200); } else { response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, PJSIP_SC_INTERNAL_SERVER_ERROR); } if (response) pjsip_tsx_on_tx_msg(tsx, response); return 0; } /* Handle case when outgoing BYE was rejected with 401/407 */ else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && event->src_type == PJSIP_EVENT_RX_MSG && tsx->role == PJSIP_ROLE_UAC) { if (tsx->status_code==401 || tsx->status_code==407) { pjsip_tx_data *tdata; tdata = pjsip_auth_reinit_req( dlg->ua->endpt, dlg->pool, &dlg->auth_sess, dlg->cred_count, dlg->cred_info, tsx->last_tx, event->src.rdata); if (tdata) { pjsip_dlg_send_msg(dlg, tdata); } } } if (dlg->pending_tsx_count == 0) { /* Set state to TERMINATED. */ dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); /* Notify application. */ dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); return -1; } else { dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); } return 0; } static int dlg_on_state_terminated( pjsip_dlg *dlg, pjsip_transaction *tsx, pjsip_event *event) { PJ_UNUSED_ARG(dlg) PJ_UNUSED_ARG(tsx) PJ_UNUSED_ARG(event) return -1; }