| /* $Header: /pjproject-0.3/pjsip/src/pjsua/main.c 40 10/14/05 12:23a Bennylp $ */ |
| |
| #include <pjlib.h> |
| #include <pjsip_core.h> |
| #include <pjsip_ua.h> |
| #include <pjsip_simple.h> |
| #include <pjmedia.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <pj/stun.h> |
| |
| #define START_PORT 5060 |
| #define MAX_BUDDIES 32 |
| #define THIS_FILE "main.c" |
| #define MAX_PRESENTITY 32 |
| #define PRESENCE_TIMEOUT 60 |
| |
| /* By default we'll have one worker thread, except when threading |
| * is disabled. |
| */ |
| #if PJ_HAS_THREADS |
| # define WORKER_COUNT 1 |
| #else |
| # define WORKER_COUNT 0 |
| #endif |
| |
| /* Global variable. */ |
| static struct |
| { |
| /* Control. */ |
| pj_pool_factory *pf; |
| pjsip_endpoint *endpt; |
| pj_pool_t *pool; |
| pjsip_user_agent *user_agent; |
| int worker_cnt; |
| int worker_quit_flag; |
| |
| /* User info. */ |
| char user_id[64]; |
| pj_str_t local_uri; |
| pj_str_t contact; |
| pj_str_t real_contact; |
| |
| /* Dialog. */ |
| pjsip_dlg *cur_dlg; |
| |
| /* Authentication. */ |
| int cred_count; |
| pjsip_cred_info cred_info[4]; |
| |
| /* Media stack. */ |
| pj_bool_t null_audio; |
| pj_med_mgr_t *mmgr; |
| |
| /* Misc. */ |
| int app_log_level; |
| char *log_filename; |
| FILE *log_file; |
| |
| /* Proxy URLs */ |
| pj_str_t proxy; |
| pj_str_t outbound_proxy; |
| |
| /* UA auto options. */ |
| int auto_answer; /* -1 to disable. */ |
| int auto_hangup; /* -1 to disable */ |
| |
| /* Registration. */ |
| pj_str_t registrar_uri; |
| pjsip_regc *regc; |
| pj_int32_t reg_timeout; |
| pj_timer_entry regc_timer; |
| |
| /* STUN */ |
| pj_str_t stun_srv1; |
| int stun_port1; |
| pj_str_t stun_srv2; |
| int stun_port2; |
| |
| /* UDP sockets and their public address. */ |
| int sip_port; |
| pj_sock_t sip_sock; |
| pj_sockaddr_in sip_sock_name; |
| pj_sock_t rtp_sock; |
| pj_sockaddr_in rtp_sock_name; |
| pj_sock_t rtcp_sock; |
| pj_sockaddr_in rtcp_sock_name; |
| |
| /* SIMPLE */ |
| pj_bool_t hide_status; |
| pj_bool_t offer_x_ms_msg; |
| int im_counter; |
| int buddy_cnt; |
| pj_str_t buddy[MAX_BUDDIES]; |
| pj_bool_t buddy_status[MAX_BUDDIES]; |
| pj_bool_t no_presence; |
| pjsip_presentity *buddy_pres[MAX_BUDDIES]; |
| |
| int pres_cnt; |
| pjsip_presentity *pres[MAX_PRESENTITY]; |
| |
| } global; |
| |
| enum { AUTO_ANSWER, AUTO_HANGUP }; |
| |
| /* This is the data that will be 'attached' on per dialog basis. */ |
| struct dialog_data |
| { |
| /* Media session. */ |
| pj_media_session_t *msession; |
| |
| /* x-ms-chat session. */ |
| pj_bool_t x_ms_msg_session; |
| |
| /* Cached SDP body, updated when media session changed. */ |
| pjsip_msg_body *body; |
| |
| /* Timer. */ |
| pj_bool_t has_auto_timer; |
| pj_timer_entry auto_timer; |
| }; |
| |
| /* |
| * These are the callbacks to be registered to dialog to receive notifications |
| * about various events in the dialog. |
| */ |
| static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, |
| pjsip_event *event ); |
| static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_tx_data *tdata, int retransmission); |
| static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_tx_data *tdata); |
| static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_rx_data *rdata); |
| static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_rx_data *rdata); |
| static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_tx_data *tdata); |
| static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_event *event); |
| static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event); |
| static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event); |
| static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event); |
| static void dlg_on_terminated (pjsip_dlg *dlg); |
| static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event); |
| |
| /* The callback structure that will be registered to UA layer. */ |
| struct pjsip_dlg_callback dlg_callback = { |
| &dlg_on_all_events, |
| &dlg_on_before_tx, |
| &dlg_on_tx_msg, |
| &dlg_on_rx_msg, |
| &dlg_on_incoming, |
| &dlg_on_calling, |
| &dlg_on_provisional, |
| &dlg_on_connecting, |
| &dlg_on_established, |
| &dlg_on_disconnected, |
| &dlg_on_terminated, |
| &dlg_on_mid_call_evt |
| }; |
| |
| |
| /* |
| * Auxiliary things are put in misc.c, so that this main.c file is more |
| * readable. |
| */ |
| #include "misc.c" |
| |
| static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap, |
| struct pj_timer_entry *entry) |
| { |
| pjsip_dlg *dlg = entry->user_data; |
| struct dialog_data *dlg_data = dlg->user_data; |
| |
| PJ_UNUSED_ARG(timer_heap) |
| |
| dlg_data->has_auto_timer = 0; |
| |
| if (entry->id == AUTO_ANSWER) { |
| pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200); |
| if (tdata) { |
| struct dialog_data *dlg_data = global.cur_dlg->user_data; |
| tdata->msg->body = dlg_data->body; |
| pjsip_dlg_send_msg(dlg, tdata); |
| } |
| } else { |
| pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500); |
| if (tdata) |
| pjsip_dlg_send_msg(dlg, tdata); |
| } |
| } |
| |
| static void update_registration(pjsip_regc *regc, int renew) |
| { |
| pjsip_tx_data *tdata; |
| |
| PJ_LOG(3,(THIS_FILE, "Performing SIP registration...")); |
| |
| if (renew) { |
| tdata = pjsip_regc_register(regc, 1); |
| } else { |
| tdata = pjsip_regc_unregister(regc); |
| } |
| |
| pjsip_regc_send( regc, tdata ); |
| } |
| |
| static void regc_cb(struct pjsip_regc_cbparam *param) |
| { |
| /* |
| * Print registration status. |
| */ |
| if (param->code < 0 || param->code >= 300) { |
| PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", |
| param->code, pjsip_get_status_text(param->code)->ptr)); |
| global.regc = NULL; |
| |
| } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { |
| PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), " |
| "will re-register in %d seconds", |
| param->code, |
| pjsip_get_status_text(param->code)->ptr, |
| param->expiration)); |
| |
| } else { |
| PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); |
| } |
| } |
| |
| static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata, |
| int *timeout) |
| { |
| int state; |
| int i; |
| char url[PJSIP_MAX_URL_SIZE]; |
| int urllen; |
| |
| PJ_UNUSED_ARG(rdata) |
| |
| if (*timeout > 0) { |
| state = PJSIP_EVENT_SUB_STATE_ACTIVE; |
| if (*timeout > 300) |
| *timeout = 300; |
| } else { |
| state = PJSIP_EVENT_SUB_STATE_TERMINATED; |
| } |
| |
| urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1); |
| if (urllen < 1) { |
| strcpy(url, "<unknown>"); |
| } else { |
| url[urllen] = '\0'; |
| } |
| PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s", |
| url, |
| (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated"))); |
| |
| for (i=0; i<global.pres_cnt; ++i) |
| if (global.pres[i] == pres) |
| break; |
| if (i == global.pres_cnt) |
| global.pres[global.pres_cnt++] = pres; |
| |
| pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info ); |
| pjsip_presence_notify(pres, state, !global.hide_status); |
| |
| } |
| |
| static void pres_on_received_refresh(pjsip_presentity *pres, pjsip_rx_data *rdata) |
| { |
| pres_on_received_request(pres, rdata, &pres->sub->default_interval); |
| } |
| |
| /* This is called by presence framework when we receives presence update |
| * of a resource (buddy). |
| */ |
| static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open) |
| { |
| int buddy_index = (int)pres->user_data; |
| |
| global.buddy_status[buddy_index] = is_open; |
| PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s", |
| global.buddy[buddy_index].ptr, |
| (is_open ? "Online" : "Offline"))); |
| } |
| |
| /* This is called when the subscription is terminated. */ |
| static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason) |
| { |
| if (pres->sub->role == PJSIP_ROLE_UAC) { |
| int buddy_index = (int)pres->user_data; |
| PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)", |
| global.buddy[buddy_index].ptr, |
| reason->slen, reason->ptr)); |
| global.buddy_pres[buddy_index] = NULL; |
| global.buddy_status[buddy_index] = 0; |
| } else { |
| int i; |
| PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)", |
| reason->slen, reason->ptr)); |
| pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1); |
| for (i=0; i<global.pres_cnt; ++i) { |
| if (global.pres[i] == pres) { |
| int j; |
| global.pres[i] = NULL; |
| for (j=i+1; j<global.pres_cnt; ++j) |
| global.pres[j-1] = global.pres[j]; |
| global.pres_cnt--; |
| break; |
| } |
| } |
| } |
| pjsip_presence_destroy(pres); |
| } |
| |
| |
| /* Callback attached to SIP body to print the body to message buffer. */ |
| static int print_msg_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) |
| { |
| pjsip_msg_body *body = msg_body; |
| return pjsdp_print ((pjsdp_session_desc*)body->data, buf, size); |
| } |
| |
| /* When media session has changed, call this function to update the cached body |
| * information in the dialog. |
| */ |
| static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg) |
| { |
| struct dialog_data *dlg_data = dlg->user_data; |
| pjsdp_session_desc *sdp; |
| |
| sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg); |
| if (!sdp) { |
| dlg_data->body = NULL; |
| return NULL; |
| } |
| |
| /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new |
| * "m=" line in the SDP. |
| */ |
| if (dlg_data->x_ms_msg_session >= 0 && |
| dlg_data->x_ms_msg_session >= (int)sdp->media_count) |
| { |
| pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m)); |
| sdp->media[sdp->media_count] = m; |
| dlg_data->x_ms_msg_session = sdp->media_count++; |
| } |
| |
| /* |
| * For "x-ms-message" line, remove all attributes and connection line etc. |
| */ |
| if (dlg_data->x_ms_msg_session >= 0) { |
| pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session]; |
| if (m) { |
| m->desc.media = pj_str("x-ms-message"); |
| m->desc.port = 5060; |
| m->desc.transport = pj_str("sip"); |
| m->desc.fmt_count = 1; |
| m->desc.fmt[0] = pj_str("null"); |
| m->attr_count = 0; |
| m->conn = NULL; |
| } |
| } |
| |
| dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body)); |
| dlg_data->body->content_type.type = pj_str("application"); |
| dlg_data->body->content_type.subtype = pj_str("sdp"); |
| dlg_data->body->len = 0; /* ignored */ |
| dlg_data->body->print_body = &print_msg_body; |
| |
| dlg_data->body->data = sdp; |
| return dlg_data->body; |
| } |
| |
| /* This callback will be called on every occurence of events in dialogs */ |
| static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, |
| pjsip_event *event ) |
| { |
| PJ_UNUSED_ARG(dlg_evt) |
| PJ_UNUSED_ARG(event) |
| |
| PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg)); |
| } |
| |
| /* This callback is called before each outgoing msg is sent (including |
| * retransmission). Application can override this notification if it wants |
| * to modify the message before transmission or if it wants to do something |
| * else for each transmission. |
| */ |
| static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_tx_data *tdata, int ret_cnt) |
| { |
| PJ_UNUSED_ARG(tsx) |
| PJ_UNUSED_ARG(tdata) |
| |
| if (ret_cnt > 0) { |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)", |
| dlg->obj_name, ret_cnt)); |
| } |
| } |
| |
| /* This callback is called after a message is sent. */ |
| static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_tx_data *tdata) |
| { |
| PJ_UNUSED_ARG(tsx) |
| PJ_UNUSED_ARG(tdata) |
| |
| PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); |
| } |
| |
| /* This callback is called on receipt of incoming message. */ |
| static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_rx_data *rdata) |
| { |
| PJ_UNUSED_ARG(tsx) |
| PJ_UNUSED_ARG(rdata) |
| PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); |
| } |
| |
| /* This callback is called after dialog has sent INVITE */ |
| static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_tx_data *tdata) |
| { |
| PJ_UNUSED_ARG(tsx) |
| PJ_UNUSED_ARG(tdata) |
| |
| pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG && |
| tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && |
| tsx->method.id == PJSIP_INVITE_METHOD); |
| |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name)); |
| } |
| |
| static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp) |
| { |
| struct dialog_data *dlg_data = dlg->user_data; |
| pj_bool_t sdp_x_ms_msg_index = -1; |
| int i; |
| int mcnt; |
| const pj_media_stream_info *mi[PJSDP_MAX_MEDIA]; |
| int has_active; |
| pj_media_sock_info sock_info; |
| |
| /* Find "m=x-ms-message" line in the SDP. */ |
| for (i=0; i<(int)sdp->media_count; ++i) { |
| if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0) |
| sdp_x_ms_msg_index = i; |
| } |
| |
| /* |
| * Create media session. |
| */ |
| pj_memset(&sock_info, 0, sizeof(sock_info)); |
| sock_info.rtp_sock = global.rtp_sock; |
| sock_info.rtcp_sock = global.rtcp_sock; |
| pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); |
| |
| dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info); |
| |
| /* A session will always be created, unless there is memory |
| * alloc problem. |
| */ |
| pj_assert(dlg_data->msession); |
| |
| /* See if we can take the offer by checking that we have at least |
| * one media stream active. |
| */ |
| mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi); |
| for (i=0, has_active=0; i<mcnt; ++i) { |
| if (mi[i]->fmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) { |
| has_active = 1; |
| break; |
| } |
| } |
| |
| if (!has_active && sdp_x_ms_msg_index==-1) { |
| pjsip_tx_data *tdata; |
| |
| /* Unable to accept remote's SDP. |
| * Answer with 488 (Not Acceptable Here) |
| */ |
| /* Create 488 response. */ |
| tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE); |
| |
| /* Send response. */ |
| if (tdata) |
| pjsip_dlg_send_msg(dlg, tdata); |
| return; |
| } |
| |
| dlg_data->x_ms_msg_session = sdp_x_ms_msg_index; |
| |
| /* Create msg body to be used later in 2xx/response */ |
| create_msg_body(dlg, 0); |
| |
| } |
| |
| /* This callback is called after an INVITE is received. */ |
| static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_rx_data *rdata) |
| { |
| struct dialog_data *dlg_data; |
| pjsip_msg *msg; |
| pjsip_tx_data *tdata; |
| char buf[128]; |
| int len; |
| |
| PJ_UNUSED_ARG(tsx) |
| |
| pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG && |
| rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && |
| tsx->method.id == PJSIP_INVITE_METHOD); |
| |
| /* |
| * Notify user! |
| */ |
| PJ_LOG(3, (THIS_FILE, "")); |
| PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name)); |
| PJ_LOG(3, (THIS_FILE, "")); |
| len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, |
| (pjsip_name_addr*)dlg->remote.info->uri, |
| buf, sizeof(buf)-1); |
| if (len > 0) { |
| buf[len] = '\0'; |
| PJ_LOG(3,(THIS_FILE, "From:\t%s", buf)); |
| } |
| len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, |
| (pjsip_name_addr*)dlg->local.info->uri, |
| buf, sizeof(buf)-1); |
| if (len > 0) { |
| buf[len] = '\0'; |
| PJ_LOG(3,(THIS_FILE, "To:\t%s", buf)); |
| } |
| PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name)); |
| PJ_LOG(3, (THIS_FILE, "")); |
| |
| /* |
| * Process incoming dialog. |
| */ |
| |
| dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); |
| dlg->user_data = dlg_data; |
| |
| /* Update contact. */ |
| pjsip_dlg_set_contact(dlg, &global.contact); |
| |
| /* Initialize credentials. */ |
| pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); |
| |
| /* Create media session if the request has "application/sdp" body. */ |
| msg = rdata->msg; |
| if (msg->body && |
| pj_stricmp2(&msg->body->content_type.type, "application")==0 && |
| pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0) |
| { |
| pjsdp_session_desc *sdp; |
| |
| /* Parse SDP body, and instantiate media session based on remote's SDP. |
| * Then create our SDP body from the session. |
| */ |
| sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool); |
| if (!sdp) |
| goto send_answer; |
| |
| create_session_from_sdp(dlg, sdp); |
| |
| } else if (msg->body) { |
| /* The request has a message body other than "application/sdp" */ |
| pjsip_accept_hdr *accept; |
| |
| /* Create response. */ |
| tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); |
| |
| /* Add "Accept" header. */ |
| accept = pjsip_accept_hdr_create(tdata->pool); |
| accept->values[0] = pj_str("application/sdp"); |
| accept->count = 1; |
| pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept); |
| |
| /* Send response. */ |
| pjsip_dlg_send_msg(dlg, tdata); |
| return; |
| |
| } else { |
| /* The request has no message body. We can take this request, but |
| * no media session will be activated. |
| */ |
| /* Nothing to do here. */ |
| } |
| |
| send_answer: |
| /* Immediately answer with 100 (or 180? */ |
| tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING ); |
| pjsip_dlg_send_msg(dlg, tdata); |
| |
| /* Set current dialog to this dialog if we don't currently have |
| * current dialog. |
| */ |
| if (global.cur_dlg == NULL) { |
| global.cur_dlg = dlg; |
| } |
| |
| /* Auto-answer if option is specified. */ |
| if (global.auto_answer >= 0) { |
| pj_time_val delay = { 0, 0}; |
| struct dialog_data *dlg_data = dlg->user_data; |
| |
| PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds", |
| global.auto_answer)); |
| |
| delay.sec = global.auto_answer; |
| dlg_data->auto_timer.user_data = dlg; |
| dlg_data->auto_timer.id = AUTO_ANSWER; |
| dlg_data->auto_timer.cb = &dlg_auto_timer_callback; |
| dlg_data->has_auto_timer = 1; |
| pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); |
| } |
| } |
| |
| /* This callback is called when dialog has sent/received a provisional response |
| * to INVITE. |
| */ |
| static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx, |
| pjsip_event *event) |
| { |
| const char *action; |
| |
| pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && |
| event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && |
| event->src.tdata->msg->line.status.code/100 == 1 && |
| tsx->method.id == PJSIP_INVITE_METHOD) |
| || |
| (event->src_type == PJSIP_EVENT_RX_MSG && |
| event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && |
| event->src.rdata->msg->line.status.code/100 == 1 && |
| tsx->method.id == PJSIP_INVITE_METHOD)); |
| |
| if (event->src_type == PJSIP_EVENT_TX_MSG) |
| action = "Sending"; |
| else |
| action = "Received"; |
| |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)", |
| dlg->obj_name, action, tsx->status_code, |
| pjsip_get_status_text(tsx->status_code)->ptr)); |
| } |
| |
| /* This callback is called when 200 response to INVITE is sent/received. */ |
| static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event) |
| { |
| struct dialog_data *dlg_data = dlg->user_data; |
| const char *action; |
| |
| pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && |
| event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && |
| event->src.tdata->msg->line.status.code/100 == 2) |
| || |
| (event->src_type == PJSIP_EVENT_RX_MSG && |
| event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && |
| event->src.rdata->msg->line.status.code/100 == 2)); |
| |
| if (event->src_type == PJSIP_EVENT_RX_MSG) |
| action = "Received"; |
| else |
| action = "Sending"; |
| |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action)); |
| |
| if (event->src_type == PJSIP_EVENT_RX_MSG) { |
| /* On receipt of 2xx response, negotiate our media capability |
| * and start media. |
| */ |
| pjsip_msg *msg = event->src.rdata->msg; |
| pjsip_msg_body *body; |
| pjsdp_session_desc *sdp; |
| |
| /* Get SDP from message. */ |
| |
| /* Ignore if no SDP body is present. */ |
| body = msg->body; |
| if (!body) { |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!", |
| dlg->obj_name)); |
| return; |
| } |
| |
| if (pj_stricmp2(&body->content_type.type, "application") != 0 && |
| pj_stricmp2(&body->content_type.subtype, "sdp") != 0) |
| { |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!", |
| dlg->obj_name)); |
| return; |
| } |
| |
| /* Got what seems to be a SDP content. Parse it. */ |
| sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool); |
| if (!sdp) { |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!", |
| dlg->obj_name)); |
| return; |
| } |
| |
| /* Negotiate media session with remote's media capability. */ |
| if (pj_media_session_update (dlg_data->msession, sdp) != 0) { |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!", |
| dlg->obj_name)); |
| return; |
| } |
| |
| /* Update the saved SDP body because media session has changed. |
| * Also set ack flag to '1', because we only want to send one format/ |
| * codec for each media streams. |
| */ |
| create_msg_body(dlg, 1); |
| |
| /* Activate media. */ |
| pj_media_session_activate (dlg_data->msession); |
| |
| } else { |
| pjsip_msg *msg = event->src.tdata->msg; |
| |
| if (msg->body) { |
| /* On transmission of 2xx response, start media session. */ |
| pj_media_session_activate (dlg_data->msession); |
| } |
| } |
| |
| } |
| |
| /* This callback is called when ACK to initial INVITE is sent/received. */ |
| static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event) |
| { |
| const char *action; |
| |
| pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && |
| event->src.tdata->msg->type == PJSIP_REQUEST_MSG && |
| event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) |
| || |
| (event->src_type == PJSIP_EVENT_RX_MSG && |
| event->src.rdata->msg->type == PJSIP_REQUEST_MSG && |
| event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD)); |
| |
| if (event->src_type == PJSIP_EVENT_RX_MSG) |
| action = "Received"; |
| else |
| action = "Sending"; |
| |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED", |
| dlg->obj_name, action)); |
| |
| /* Attach SDP body for outgoing ACK. */ |
| if (event->src_type == PJSIP_EVENT_TX_MSG && |
| event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) |
| { |
| struct dialog_data *dlg_data = dlg->user_data; |
| event->src.tdata->msg->body = dlg_data->body; |
| } |
| |
| /* Auto-hangup if option is specified. */ |
| if (global.auto_hangup >= 0) { |
| pj_time_val delay = { 0, 0}; |
| struct dialog_data *dlg_data = dlg->user_data; |
| |
| PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds", |
| global.auto_hangup)); |
| |
| delay.sec = global.auto_hangup; |
| dlg_data->auto_timer.user_data = dlg; |
| dlg_data->auto_timer.id = AUTO_HANGUP; |
| dlg_data->auto_timer.cb = &dlg_auto_timer_callback; |
| dlg_data->has_auto_timer = 1; |
| pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); |
| } |
| } |
| |
| |
| /* This callback is called when dialog is disconnected (because of final |
| * response, BYE, or timer). |
| */ |
| static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event) |
| { |
| struct dialog_data *dlg_data = dlg->user_data; |
| int status_code; |
| const pj_str_t *reason; |
| |
| PJ_UNUSED_ARG(event) |
| |
| /* Cancel auto-answer/auto-hangup timer. */ |
| if (dlg_data->has_auto_timer) { |
| pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer); |
| dlg_data->has_auto_timer = 0; |
| } |
| |
| if (dlg->invite_tsx) |
| status_code = dlg->invite_tsx->status_code; |
| else |
| status_code = 200; |
| |
| if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) { |
| if (event->src_type == PJSIP_EVENT_RX_MSG) |
| reason = &event->src.rdata->msg->line.status.reason; |
| else if (event->src_type == PJSIP_EVENT_TX_MSG) |
| reason = &event->src.tdata->msg->line.status.reason; |
| else |
| reason = pjsip_get_status_text(event->obj.tsx->status_code); |
| } else { |
| reason = &event->obj.tsx->method.name; |
| } |
| |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)", |
| dlg->obj_name, status_code, |
| reason->slen, reason->ptr)); |
| |
| if (dlg_data->msession) { |
| pj_media_session_destroy (dlg_data->msession); |
| dlg_data->msession = NULL; |
| } |
| } |
| |
| /* This callback is called when dialog is about to be destroyed. */ |
| static void dlg_on_terminated(pjsip_dlg *dlg) |
| { |
| PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name)); |
| |
| /* If current dialog is equal to this dialog, update it. */ |
| if (global.cur_dlg == dlg) { |
| global.cur_dlg = global.cur_dlg->next; |
| if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { |
| global.cur_dlg = NULL; |
| } |
| } |
| } |
| |
| /* This callback is called for any requests when dialog is established. */ |
| static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event) |
| { |
| pjsip_transaction *tsx = event->obj.tsx; |
| |
| if (event->src_type == PJSIP_EVENT_RX_MSG && |
| event->src.rdata->msg->type == PJSIP_REQUEST_MSG) |
| { |
| if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) { |
| /* Re-invitation. */ |
| pjsip_tx_data *tdata; |
| |
| PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)", |
| dlg->obj_name)); |
| tdata = pjsip_dlg_answer(dlg, 200); |
| if (tdata) { |
| struct dialog_data *dlg_data = dlg->user_data; |
| tdata->msg->body = dlg_data->body; |
| pjsip_dlg_send_msg(dlg, tdata); |
| } |
| } else { |
| /* Don't worry, endpoint will answer with 500 or whetever. */ |
| } |
| |
| } else if (tsx->status_code/100 == 2) { |
| PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)", |
| dlg->obj_name, |
| tsx->method.name.slen, tsx->method.name.ptr, |
| tsx->status_code, |
| pjsip_get_status_text(tsx->status_code)->ptr)); |
| |
| |
| } else if (tsx->status_code >= 300) { |
| pj_bool_t report_failure = PJ_TRUE; |
| |
| /* Check for authentication failures. */ |
| if (tsx->status_code==401 || tsx->status_code==407) { |
| pjsip_tx_data *tdata; |
| tdata = pjsip_auth_reinit_req( global.endpt, |
| dlg->pool, &dlg->auth_sess, |
| dlg->cred_count, dlg->cred_info, |
| tsx->last_tx, event->src.rdata ); |
| if (tdata) { |
| int rc; |
| rc = pjsip_dlg_send_msg( dlg, tdata); |
| report_failure = (rc != 0); |
| } |
| } |
| if (report_failure) { |
| const 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); |
| } |
| PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)", |
| dlg->obj_name, tsx->status_code, |
| reason->slen, reason->ptr)); |
| } |
| } |
| } |
| |
| /* Initialize sockets and optionally get the public address via STUN. */ |
| static pj_status_t init_sockets() |
| { |
| enum { |
| RTP_START_PORT = 4000, |
| RTP_RANDOM_START = 2, |
| RTP_RETRY = 10 |
| }; |
| enum { |
| SIP_SOCK, |
| RTP_SOCK, |
| RTCP_SOCK, |
| }; |
| int i; |
| int rtp_port; |
| pj_sock_t sock[3]; |
| pj_sockaddr_in mapped_addr[3]; |
| |
| for (i=0; i<3; ++i) |
| sock[i] = PJ_INVALID_SOCKET; |
| |
| /* Create and bind SIP UDP socket. */ |
| sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); |
| if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) { |
| PJ_LOG(2,(THIS_FILE, "Unable to create socket")); |
| goto on_error; |
| } |
| if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) { |
| PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port")); |
| goto on_error; |
| } |
| |
| /* Initialize start of RTP port to try. */ |
| rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2; |
| |
| /* Loop retry to bind RTP and RTCP sockets. */ |
| for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) { |
| |
| /* Create and bind RTP socket. */ |
| sock[RTP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); |
| if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) |
| goto on_error; |
| if (pj_sock_bind_in(sock[RTP_SOCK], 0, (pj_uint16_t)rtp_port) != 0) { |
| pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; |
| continue; |
| } |
| |
| /* Create and bind RTCP socket. */ |
| sock[RTCP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); |
| if (sock[RTCP_SOCK] == PJ_INVALID_SOCKET) |
| goto on_error; |
| if (pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)) != 0) { |
| pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; |
| pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; |
| continue; |
| } |
| |
| /* |
| * If we're configured to use STUN, then find out the mapped address, |
| * and make sure that the mapped RTCP port is adjacent with the RTP. |
| */ |
| if (global.stun_port1 == 0) { |
| pj_str_t hostname; |
| pj_sockaddr_in addr; |
| |
| /* Get local IP address. */ |
| char hostname_buf[PJ_MAX_HOSTNAME]; |
| if (gethostname(hostname_buf, sizeof(hostname_buf))) |
| goto on_error; |
| hostname = pj_str(hostname_buf); |
| |
| pj_memset( &addr, 0, sizeof(addr)); |
| addr.sin_family = PJ_AF_INET; |
| if (pj_sockaddr_set_str_addr( &addr, &hostname) != PJ_SUCCESS) |
| goto on_error; |
| |
| for (i=0; i<3; ++i) |
| pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); |
| |
| mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)global.sip_port); |
| mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port); |
| mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1)); |
| break; |
| } else { |
| pj_status_t rc; |
| rc = pj_stun_get_mapped_addr( global.pf, 3, sock, |
| &global.stun_srv1, global.stun_port1, |
| &global.stun_srv2, global.stun_port2, |
| mapped_addr); |
| if (rc != 0) { |
| PJ_LOG(3,(THIS_FILE, "Error: %s", pj_stun_get_err_msg(rc))); |
| goto on_error; |
| } |
| |
| if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1) |
| break; |
| |
| pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; |
| pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; |
| } |
| } |
| |
| if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) { |
| PJ_LOG(2,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination")); |
| goto on_error; |
| } |
| |
| global.sip_sock = sock[SIP_SOCK]; |
| pj_memcpy(&global.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in)); |
| global.rtp_sock = sock[RTP_SOCK]; |
| pj_memcpy(&global.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in)); |
| global.rtcp_sock = sock[RTCP_SOCK]; |
| pj_memcpy(&global.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in)); |
| |
| PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", |
| pj_inet_ntoa(global.sip_sock_name.sin_addr), |
| pj_ntohs(global.sip_sock_name.sin_port))); |
| PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", |
| pj_inet_ntoa(global.rtp_sock_name.sin_addr), |
| pj_ntohs(global.rtp_sock_name.sin_port))); |
| PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d", |
| pj_inet_ntoa(global.rtcp_sock_name.sin_addr), |
| pj_ntohs(global.rtcp_sock_name.sin_port))); |
| return 0; |
| |
| on_error: |
| for (i=0; i<3; ++i) { |
| if (sock[i] != PJ_INVALID_SOCKET) |
| pj_sock_close(sock[i]); |
| } |
| return -1; |
| } |
| |
| static void log_function(int level, const char *buffer, int len) |
| { |
| /* Write to both stdout and file. */ |
| if (level <= global.app_log_level) |
| pj_log_to_stdout(level, buffer, len); |
| if (global.log_file) { |
| fwrite(buffer, len, 1, global.log_file); |
| fflush(global.log_file); |
| } |
| } |
| |
| /* Initialize stack. */ |
| static pj_status_t init_stack() |
| { |
| pj_status_t status; |
| pj_sockaddr_in bind_addr; |
| pj_sockaddr_in bind_name; |
| const char *local_addr; |
| static char local_uri[128]; |
| |
| /* Optionally set logging file. */ |
| if (global.log_filename) { |
| global.log_file = fopen(global.log_filename, "wt"); |
| } |
| |
| /* Initialize endpoint. This will also call initialization to all the |
| * modules. |
| */ |
| global.endpt = pjsip_endpt_create(global.pf); |
| if (global.endpt == NULL) { |
| return -1; |
| } |
| |
| /* Set dialog callback. */ |
| pjsip_ua_set_dialog_callback(global.user_agent, &dlg_callback); |
| |
| /* Init listener's bound address and port. */ |
| pj_sockaddr_init2(&bind_addr, "0.0.0.0", global.sip_port); |
| pj_sockaddr_init(&bind_name, pj_gethostname(), global.sip_port); |
| |
| /* Add UDP transport listener. */ |
| status = pjsip_endpt_create_udp_listener( global.endpt, global.sip_sock, |
| &global.sip_sock_name); |
| if (status != 0) |
| return -1; |
| |
| local_addr = pj_inet_ntoa(global.sip_sock_name.sin_addr); |
| |
| #if PJ_HAS_TCP |
| /* Add TCP transport listener. */ |
| status = pjsip_endpt_create_listener( global.endpt, PJSIP_TRANSPORT_TCP, |
| &bind_addr, &bind_name); |
| if (status != 0) |
| return -1; |
| #endif |
| |
| /* Determine user_id to be put in Contact */ |
| if (global.local_uri.slen) { |
| pj_pool_t *pool = pj_pool_create(global.pf, "parser", 1024, 0, NULL); |
| pjsip_uri *uri; |
| |
| uri = pjsip_parse_uri(pool, global.local_uri.ptr, global.local_uri.slen, 0); |
| if (uri) { |
| if (pj_stricmp2(pjsip_uri_get_scheme(uri), "sip")==0) { |
| pjsip_url *url = (pjsip_url*)pjsip_uri_get_uri(uri); |
| if (url->user.slen) |
| strncpy(global.user_id, url->user.ptr, url->user.slen); |
| } |
| } |
| pj_pool_release(pool); |
| } |
| |
| if (global.user_id[0]=='\0') { |
| strcpy(global.user_id, "user"); |
| } |
| |
| /* build contact */ |
| global.real_contact.ptr = local_uri; |
| global.real_contact.slen = |
| sprintf(local_uri, "<sip:%s@%s:%d>", global.user_id, local_addr, global.sip_port); |
| |
| if (global.contact.slen == 0) |
| global.contact = global.real_contact; |
| |
| /* initialize local_uri with contact if it's not specified in cmdline */ |
| if (global.local_uri.slen == 0) |
| global.local_uri = global.contact; |
| |
| /* Init proxy. */ |
| if (global.proxy.slen || global.outbound_proxy.slen) { |
| int count = 0; |
| pj_str_t proxy_url[2]; |
| |
| if (global.outbound_proxy.slen) { |
| proxy_url[count++] = global.outbound_proxy; |
| } |
| if (global.proxy.slen) { |
| proxy_url[count++] = global.proxy; |
| } |
| |
| if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) { |
| PJ_LOG(2,(THIS_FILE, "Error setting proxy address!")); |
| return -1; |
| } |
| } |
| |
| /* initialize SIP registration if registrar is configured */ |
| if (global.registrar_uri.slen) { |
| global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb); |
| pjsip_regc_init( global.regc, &global.registrar_uri, |
| &global.local_uri, |
| &global.local_uri, |
| 1, &global.contact, |
| global.reg_timeout); |
| pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info ); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* Worker thread function, only used when threading is enabled. */ |
| static void *PJ_THREAD_FUNC worker_thread(void *unused) |
| { |
| PJ_UNUSED_ARG(unused) |
| |
| while (!global.worker_quit_flag) { |
| pj_time_val timeout = { 0, 10 }; |
| pjsip_endpt_handle_events (global.endpt, &timeout); |
| } |
| return NULL; |
| } |
| |
| |
| /* Make call to the specified URI. */ |
| static pjsip_dlg *make_call(pj_str_t *remote_uri) |
| { |
| pjsip_dlg *dlg; |
| pj_str_t local = global.contact; |
| pj_str_t remote = *remote_uri; |
| struct dialog_data *dlg_data; |
| pjsip_tx_data *tdata; |
| pj_media_sock_info sock_info; |
| |
| /* Create new dialog instance. */ |
| dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC); |
| |
| /* Attach our own user data. */ |
| dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); |
| dlg->user_data = dlg_data; |
| |
| /* Create media session. */ |
| pj_memset(&sock_info, 0, sizeof(sock_info)); |
| sock_info.rtp_sock = global.rtp_sock; |
| sock_info.rtcp_sock = global.rtcp_sock; |
| pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); |
| |
| dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info); |
| dlg_data->x_ms_msg_session = -1; |
| |
| if (global.offer_x_ms_msg) { |
| const pj_media_stream_info *minfo[32]; |
| unsigned cnt; |
| |
| cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo); |
| if (cnt > 0) |
| dlg_data->x_ms_msg_session = cnt; |
| } |
| |
| /* Initialize dialog with local and remote URI. */ |
| if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) { |
| pjsip_ua_destroy_dialog(dlg); |
| return NULL; |
| } |
| |
| /* Initialize credentials. */ |
| pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); |
| |
| /* Send INVITE! */ |
| tdata = pjsip_dlg_invite(dlg); |
| tdata->msg->body = create_msg_body (dlg, 0); |
| |
| if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) { |
| pjsip_ua_destroy_dialog(dlg); |
| return NULL; |
| } |
| |
| return dlg; |
| } |
| |
| /* |
| * Callback to receive incoming IM message. |
| */ |
| static int on_incoming_im_msg(pjsip_rx_data *rdata) |
| { |
| pjsip_msg *msg = rdata->msg; |
| pjsip_msg_body *body = msg->body; |
| int len; |
| char to[128], from[128]; |
| |
| |
| len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, |
| rdata->from->uri, from, sizeof(from)); |
| if (len > 0) from[len] = '\0'; |
| else strcpy(from, "<URL too long..>"); |
| |
| len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, |
| rdata->to->uri, to, sizeof(to)); |
| if (len > 0) to[len] = '\0'; |
| else strcpy(to, "<URL too long..>"); |
| |
| PJ_LOG(3,(THIS_FILE, "Incoming instant message:")); |
| |
| printf("----- BEGIN INSTANT MESSAGE ----->\n"); |
| printf("From:\t%s\n", from); |
| printf("To:\t%s\n", to); |
| printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : "")); |
| printf("<------ END INSTANT MESSAGE ------\n"); |
| |
| fflush(stdout); |
| |
| /* Must answer with final response. */ |
| return 200; |
| } |
| |
| /* |
| * Input URL. |
| */ |
| static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection) |
| { |
| int i; |
| |
| *selection = -1; |
| |
| printf("\nBuddy list:\n"); |
| printf("---------------------------------------\n"); |
| for (i=0; i<global.buddy_cnt; ++i) { |
| printf(" %d\t%s <%s>\n", i+1, global.buddy[i].ptr, |
| (global.buddy_status[i]?"Online":"Offline")); |
| } |
| printf("-------------------------------------\n"); |
| |
| printf("Choices\n" |
| "\t0 For current dialog.\n" |
| "\t[1-%02d] Select from buddy list\n" |
| "\tURL An URL\n" |
| , global.buddy_cnt); |
| printf("Input: "); |
| |
| fflush(stdout); |
| fgets(buf, len, stdin); |
| buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */ |
| |
| while (isspace(*buf)) ++buf; |
| |
| if (!*buf || *buf=='\n' || *buf=='\r') |
| return NULL; |
| |
| i = atoi(buf); |
| |
| if (i == 0) { |
| if (isdigit(*buf)) { |
| *selection = 0; |
| *out = pj_str("0"); |
| return out; |
| } else { |
| if (verify_sip_url(buf) != 0) { |
| puts("Invalid URL specified!"); |
| return NULL; |
| } |
| *out = pj_str(buf); |
| return out; |
| } |
| } else if (i > global.buddy_cnt || i < 0) { |
| printf("Error: invalid selection!\n"); |
| return NULL; |
| } else { |
| *out = global.buddy[i-1]; |
| *selection = i; |
| return out; |
| } |
| } |
| |
| |
| static void generic_request_callback( void *token, pjsip_event *event ) |
| { |
| pjsip_transaction *tsx = event->obj.tsx; |
| |
| PJ_UNUSED_ARG(token) |
| |
| if (tsx->status_code/100 == 2) { |
| PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)", |
| event->obj.tsx->method.name.slen, |
| event->obj.tsx->method.name.ptr, |
| tsx->status_code, |
| pjsip_get_status_text(tsx->status_code)->ptr)); |
| } else if (tsx->status_code==401 || tsx->status_code==407) { |
| pjsip_tx_data *tdata; |
| tdata = pjsip_auth_reinit_req( global.endpt, |
| global.pool, NULL, global.cred_count, global.cred_info, |
| tsx->last_tx, event->src.rdata); |
| if (tdata) { |
| int rc; |
| pjsip_cseq_hdr *cseq; |
| cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); |
| cseq->cseq++; |
| rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, |
| &generic_request_callback); |
| if (rc == 0) |
| return; |
| } |
| PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)", |
| event->obj.tsx->method.name.slen, |
| event->obj.tsx->method.name.ptr, |
| event->obj.tsx->status_code, |
| pjsip_get_status_text(event->obj.tsx->status_code)->ptr)); |
| } else { |
| const 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); |
| PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)", |
| event->obj.tsx->method.name.slen, |
| event->obj.tsx->method.name.ptr, |
| event->obj.tsx->status_code, |
| reason->slen, reason->ptr)); |
| } |
| } |
| |
| |
| static void ui_send_im_message() |
| { |
| char line[100]; |
| char text_buf[100]; |
| pj_str_t str; |
| pj_str_t text_msg; |
| int selection, rc; |
| pjsip_tx_data *tdata; |
| |
| if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) |
| return; |
| |
| |
| printf("Enter text to send (empty to cancel): "); fflush(stdout); |
| fgets(text_buf, sizeof(text_buf), stdin); |
| text_buf[strlen(text_buf)-1] = '\0'; |
| if (!*text_buf) |
| return; |
| |
| text_msg = pj_str(text_buf); |
| |
| if (selection==0) { |
| pjsip_method message_method; |
| pj_str_t str_MESSAGE = { "MESSAGE", 7 }; |
| |
| /* Send IM to current dialog. */ |
| if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { |
| printf("No current dialog or dialog state is not ESTABLISHED!\n"); |
| return; |
| } |
| |
| pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE); |
| tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 ); |
| |
| if (tdata) { |
| /* Create message body for the text. */ |
| pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body)); |
| body->content_type.type = pj_str("text"); |
| body->content_type.subtype = pj_str("plain"); |
| body->data = pj_pool_alloc(tdata->pool, text_msg.slen); |
| pj_memcpy(body->data, text_msg.ptr, text_msg.slen); |
| body->len = text_msg.slen; |
| body->print_body = &pjsip_print_text_body; |
| |
| /* Assign body to message, and send the message! */ |
| tdata->msg->body = body; |
| pjsip_dlg_send_msg( global.cur_dlg, tdata ); |
| } |
| |
| } else { |
| /* Send IM to buddy list. */ |
| pjsip_method message; |
| static pj_str_t MESSAGE = { "MESSAGE", 7 }; |
| pjsip_method_init_np(&message, &MESSAGE); |
| tdata = pjsip_endpt_create_request(global.endpt, &message, |
| &str, |
| &global.real_contact, |
| &str, &global.real_contact, NULL, -1, |
| &text_msg); |
| if (!tdata) { |
| puts("Error creating request"); |
| return; |
| } |
| rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback); |
| if (rc == 0) { |
| printf("Sending IM message %d\n", global.im_counter); |
| ++global.im_counter; |
| } else { |
| printf("Error: unable to send IM message!\n"); |
| } |
| } |
| } |
| |
| static void ui_send_options() |
| { |
| char line[100]; |
| pj_str_t str; |
| int selection, rc; |
| pjsip_tx_data *tdata; |
| pjsip_method options; |
| |
| if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) |
| return; |
| |
| pjsip_method_set( &options, PJSIP_OPTIONS_METHOD ); |
| |
| if (selection == 0) { |
| /* Send OPTIONS to current dialog. */ |
| tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1); |
| if (tdata) |
| pjsip_dlg_send_msg( global.cur_dlg, tdata ); |
| } else { |
| /* Send OPTIONS to arbitrary party. */ |
| tdata = pjsip_endpt_create_request( global.endpt, &options, |
| &str, |
| &global.local_uri, &str, |
| &global.real_contact, |
| NULL, -1, NULL); |
| if (tdata) { |
| rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, |
| &generic_request_callback); |
| if (rc != 0) |
| PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!")); |
| } |
| } |
| } |
| |
| static void init_presence() |
| { |
| const pjsip_presence_cb pres_cb = { |
| NULL, |
| &pres_on_received_request, |
| &pres_on_received_refresh, |
| &pres_on_received_update, |
| &pres_on_terminated |
| }; |
| |
| pjsip_presence_init(&pres_cb); |
| } |
| |
| /* Subscribe presence information for all buddies. */ |
| static void subscribe_buddies_presence() |
| { |
| int i; |
| for (i=0; i<global.buddy_cnt; ++i) { |
| pjsip_presentity *pres; |
| if (global.buddy_pres[i]) |
| continue; |
| pres = pjsip_presence_create( global.endpt, &global.local_uri, |
| &global.buddy[i], PRESENCE_TIMEOUT, (void*)i); |
| if (pres) { |
| pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info ); |
| pjsip_presence_subscribe( pres ); |
| } |
| global.buddy_pres[i] = pres; |
| } |
| } |
| |
| /* Unsubscribe presence information for all buddies. */ |
| static void unsubscribe_buddies_presence() |
| { |
| int i; |
| for (i=0; i<global.buddy_cnt; ++i) { |
| pjsip_presentity *pres = global.buddy_pres[i]; |
| if (pres) { |
| pjsip_presence_unsubscribe(pres); |
| pjsip_presence_destroy(pres); |
| global.buddy_pres[i] = NULL; |
| } |
| } |
| } |
| |
| /* Unsubscribe presence. */ |
| static void unsubscribe_presence() |
| { |
| int i; |
| |
| unsubscribe_buddies_presence(); |
| for (i=0; i<global.pres_cnt; ++i) { |
| pjsip_presentity *pres = global.pres[i]; |
| pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 0); |
| pjsip_presence_destroy( pres ); |
| } |
| } |
| |
| /* Advertise online status to subscribers. */ |
| static void update_im_status() |
| { |
| int i; |
| for (i=0; i<global.pres_cnt; ++i) { |
| pjsip_presentity *pres = global.pres[i]; |
| pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_ACTIVE, |
| !global.hide_status); |
| } |
| } |
| |
| /* |
| * Main program. |
| */ |
| int main(int argc, char *argv[]) |
| { |
| /* set to WORKER_COUNT+1 to avoid zero size warning |
| * when threading is disabled. */ |
| pj_thread_t *thread[WORKER_COUNT+1]; |
| pj_caching_pool cp; |
| int i; |
| |
| global.sip_port = 5060; |
| global.auto_answer = -1; |
| global.auto_hangup = -1; |
| global.app_log_level = 3; |
| |
| pj_log_set_level(4); |
| pj_log_set_log_func(&log_function); |
| |
| /* Init PJLIB */ |
| if (pj_init() != PJ_SUCCESS) |
| return 1; |
| |
| /* Init caching pool. */ |
| pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); |
| global.pf = &cp.factory; |
| |
| /* Create memory pool for application. */ |
| global.pool = pj_pool_create(global.pf, "main", 1024, 0, NULL); |
| |
| /* Parse command line arguments. */ |
| if (parse_args(global.pool, argc, argv) != PJ_SUCCESS) { |
| pj_caching_pool_destroy(&cp); |
| return 1; |
| } |
| |
| /* Init sockets */ |
| if (init_sockets() != 0) { |
| pj_caching_pool_destroy(&cp); |
| return 1; |
| } |
| |
| /* Initialize stack. */ |
| if (init_stack() != PJ_SUCCESS) { |
| pj_caching_pool_destroy(&cp); |
| return 1; |
| } |
| |
| /* Set callback to receive incoming IM */ |
| pjsip_messaging_set_incoming_callback( &on_incoming_im_msg ); |
| |
| /* Set default worker count (can be zero) */ |
| global.worker_cnt = WORKER_COUNT; |
| |
| /* Create user worker thread(s), only when threading is enabled. */ |
| for (i=0; i<global.worker_cnt; ++i) { |
| thread[i] = pj_thread_create( global.pool, "sip%p", |
| &worker_thread, |
| NULL, 0, NULL, 0); |
| if (thread == NULL) { |
| global.worker_quit_flag = 1; |
| for (--i; i>=0; --i) { |
| pj_thread_join(thread[i]); |
| pj_thread_destroy(thread[i]); |
| } |
| pj_caching_pool_destroy(&cp); |
| return 1; |
| } |
| } |
| |
| printf("Worker thread count: %d\n", global.worker_cnt); |
| |
| /* Perform registration, if required. */ |
| if (global.regc) { |
| update_registration(global.regc, 1); |
| } |
| |
| /* Initialize media manager. */ |
| global.mmgr = pj_med_mgr_create(global.pf); |
| |
| /* Init presence. */ |
| init_presence(); |
| |
| /* Subscribe presence information of all buddies. */ |
| if (!global.no_presence) |
| subscribe_buddies_presence(); |
| |
| /* Initializatio completes, loop waiting for commands. */ |
| for (;!global.worker_quit_flag;) { |
| pj_str_t str; |
| char line[128]; |
| |
| #if WORKER_COUNT==0 |
| /* If worker thread does not exist, main thread must poll for evetns. |
| * But this won't work very well since main thread is blocked by |
| * fgets(). So keep pressing the ENTER key to get the events! |
| */ |
| pj_time_val timeout = { 0, 100 }; |
| pjsip_endpt_handle_events(global.endpt, &timeout); |
| puts("Keep pressing ENTER key to get the events!"); |
| #endif |
| |
| printf("\nCurrent dialog: "); |
| print_dialog(global.cur_dlg); |
| puts(""); |
| |
| keystroke_help(); |
| |
| fgets(line, sizeof(line), stdin); |
| |
| switch (*line) { |
| case 'm': |
| puts("Make outgoing call"); |
| if (ui_input_url(&str, line, sizeof(line), &i) != NULL) { |
| pjsip_dlg *dlg = make_call(&str); |
| if (global.cur_dlg == NULL) { |
| global.cur_dlg = dlg; |
| } |
| } |
| break; |
| case 'i': |
| puts("Send Instant Messaging"); |
| ui_send_im_message(); |
| break; |
| case 'o': |
| puts("Send OPTIONS"); |
| ui_send_options(); |
| break; |
| case 'a': |
| if (global.cur_dlg) { |
| unsigned code; |
| pjsip_tx_data *tdata; |
| struct dialog_data *dlg_data = global.cur_dlg->user_data; |
| |
| printf("Answer with status code (1xx-6xx): "); |
| fflush(stdout); |
| fgets(line, sizeof(line), stdin); |
| str = pj_str(line); |
| str.slen -= 1; |
| |
| code = pj_strtoul(&str); |
| tdata = pjsip_dlg_answer(global.cur_dlg, code); |
| if (tdata) { |
| if (code/100 == 2) { |
| tdata->msg->body = dlg_data->body; |
| } |
| pjsip_dlg_send_msg(global.cur_dlg, tdata); |
| |
| } |
| } else { |
| puts("No current dialog"); |
| } |
| break; |
| case 'h': |
| if (global.cur_dlg) { |
| pjsip_tx_data *tdata; |
| tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE); |
| if (tdata) { |
| pjsip_dlg_send_msg(global.cur_dlg, tdata); |
| } |
| } else { |
| puts("No current dialog"); |
| } |
| break; |
| case ']': |
| if (global.cur_dlg) { |
| global.cur_dlg = global.cur_dlg->next; |
| if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { |
| global.cur_dlg = global.cur_dlg->next; |
| } |
| } else { |
| puts("No current dialog"); |
| } |
| break; |
| case '[': |
| if (global.cur_dlg) { |
| global.cur_dlg = global.cur_dlg->prev; |
| if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { |
| global.cur_dlg = global.cur_dlg->prev; |
| } |
| } else { |
| puts("No current dialog"); |
| } |
| break; |
| case 'd': |
| pjsip_endpt_dump(global.endpt, *(line+1)=='1'); |
| pjsip_ua_dump(global.user_agent); |
| break; |
| case 's': |
| if (*(line+1) == 'u') |
| subscribe_buddies_presence(); |
| break; |
| case 'u': |
| if (*(line+1) == 's') |
| unsubscribe_presence(); |
| break; |
| case 't': |
| global.hide_status = !global.hide_status; |
| update_im_status(); |
| break; |
| case 'q': |
| goto on_exit; |
| case 'l': |
| print_all_dialogs(); |
| break; |
| } |
| } |
| |
| on_exit: |
| /* Unregister, if required. */ |
| if (global.regc) { |
| update_registration(global.regc, 0); |
| } |
| |
| /* Unsubscribe presence. */ |
| unsubscribe_presence(); |
| |
| /* Allow one second to get all events. */ |
| if (1) { |
| pj_time_val end_time; |
| |
| pj_gettimeofday(&end_time); |
| end_time.sec++; |
| |
| PJ_LOG(3,(THIS_FILE, "Shutting down..")); |
| for (;;) { |
| pj_time_val timeout = { 0, 20 }, now; |
| pjsip_endpt_handle_events (global.endpt, &timeout); |
| pj_gettimeofday(&now); |
| PJ_TIME_VAL_SUB(now, end_time); |
| if (now.sec >= 1) |
| break; |
| } |
| } |
| |
| global.worker_quit_flag = 1; |
| |
| pj_med_mgr_destroy(global.mmgr); |
| |
| /* Wait all threads to quit. */ |
| for (i=0; i<global.worker_cnt; ++i) { |
| pj_thread_join(thread[i]); |
| pj_thread_destroy(thread[i]); |
| } |
| |
| /* Destroy endpoint. */ |
| pjsip_endpt_destroy(global.endpt); |
| |
| /* Destroy caching pool. */ |
| pj_caching_pool_destroy(&cp); |
| |
| /* Close log file, if any. */ |
| if (global.log_file) |
| fclose(global.log_file); |
| |
| return 0; |
| } |
| |
| /* |
| * Register static modules to the endpoint. |
| */ |
| pj_status_t register_static_modules( pj_size_t *count, |
| pjsip_module **modules ) |
| { |
| /* Reset count. */ |
| *count = 0; |
| |
| /* Register user agent module. */ |
| modules[(*count)++] = pjsip_ua_get_module(); |
| global.user_agent = modules[0]->mod_data; |
| modules[(*count)++] = pjsip_messaging_get_module(); |
| modules[(*count)++] = pjsip_event_sub_get_module(); |
| |
| return PJ_SUCCESS; |
| } |