diff --git a/src/include/switch_types.h b/src/include/switch_types.h index 068b79a3a1..22375eb087 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -1063,6 +1063,7 @@ typedef uint32_t switch_io_flag_t; SWITCH_EVENT_DTMF - DTMF was sent SWITCH_EVENT_MESSAGE - A Basic Message SWITCH_EVENT_PRESENCE_IN - Presence in + SWITCH_EVENT_NOTIFY_IN - Received incoming NOTIFY from gateway subscription SWITCH_EVENT_PRESENCE_OUT - Presence out SWITCH_EVENT_PRESENCE_PROBE - Presence probe SWITCH_EVENT_MESSAGE_WAITING - A message is waiting @@ -1121,6 +1122,7 @@ typedef enum { SWITCH_EVENT_DTMF, SWITCH_EVENT_MESSAGE, SWITCH_EVENT_PRESENCE_IN, + SWITCH_EVENT_NOTIFY_IN, SWITCH_EVENT_PRESENCE_OUT, SWITCH_EVENT_PRESENCE_PROBE, SWITCH_EVENT_MESSAGE_WAITING, diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.h b/src/mod/endpoints/mod_sofia/mod_sofia.h index 838a0ee65c..d29d86abf8 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.h +++ b/src/mod/endpoints/mod_sofia/mod_sofia.h @@ -60,6 +60,9 @@ static const switch_state_handler_table_t noop_state_handler = { 0 }; struct sofia_gateway; typedef struct sofia_gateway sofia_gateway_t; +struct sofia_gateway_subscription; +typedef struct sofia_gateway_subscription sofia_gateway_subscription_t; + struct sofia_profile; typedef struct sofia_profile sofia_profile_t; #define NUA_MAGIC_T sofia_profile_t @@ -272,6 +275,31 @@ typedef enum { SOFIA_GATEWAY_UP } sofia_gateway_status_t; +typedef enum { + SUB_STATE_UNSUBED, + SUB_STATE_TRYING, + SUB_STATE_SUBSCRIBE, + SUB_STATE_SUBED, + SUB_STATE_UNSUBSCRIBE, + SUB_STATE_FAILED, + SUB_STATE_EXPIRED, + SUB_STATE_NOSUB, + v_STATE_LAST +} sub_state_t; + +struct sofia_gateway_subscription { + sofia_gateway_t *gateway; + char *expires_str; + char *event; /* eg, 'message-summary' to subscribe to MWI events */ + char *content_type; /* eg, application/simple-message-summary in the case of MWI events */ + uint32_t freq; + int32_t retry_seconds; + time_t expires; + time_t retry; + sub_state_t state; + struct sofia_gateway_subscription *next; +}; + struct sofia_gateway { sofia_private_t *sofia_private; nua_handle_t *nh; @@ -306,6 +334,7 @@ struct sofia_gateway { switch_event_t *vars; char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; struct sofia_gateway *next; + sofia_gateway_subscription_t *subscriptions; }; typedef enum { @@ -622,6 +651,7 @@ void sofia_glue_execute_sql(sofia_profile_t *profile, char **sqlp, switch_bool_t void sofia_glue_actually_execute_sql(sofia_profile_t *profile, switch_bool_t master, char *sql, switch_mutex_t *mutex); void sofia_reg_check_expire(sofia_profile_t *profile, time_t now, int reboot); void sofia_reg_check_gateway(sofia_profile_t *profile, time_t now); +void sofia_sub_check_gateway(sofia_profile_t *profile, time_t now); void sofia_reg_unregister(sofia_profile_t *profile); switch_status_t sofia_glue_ext_address_lookup(sofia_profile_t *profile, private_object_t *tech_pvt, char **ip, switch_port_t *port, char *sourceip, switch_memory_pool_t *pool); @@ -660,6 +690,8 @@ switch_status_t sofia_reg_add_gateway(char *key, sofia_gateway_t *gateway); sofia_gateway_t *sofia_reg_find_gateway__(const char *file, const char *func, int line, const char *key); #define sofia_reg_find_gateway(x) sofia_reg_find_gateway__(__FILE__, __SWITCH_FUNC__, __LINE__, x) +sofia_gateway_subscription_t *sofia_find_gateway_subscription(sofia_gateway_t *gateway_ptr, const char *event); + void sofia_reg_release_gateway__(const char *file, const char *func, int line, sofia_gateway_t *gateway); #define sofia_reg_release_gateway(x) sofia_reg_release_gateway__(__FILE__, __SWITCH_FUNC__, __LINE__, x); diff --git a/src/mod/endpoints/mod_sofia/sofia.c b/src/mod/endpoints/mod_sofia/sofia.c index cd07fa2f8b..c08ea25a59 100644 --- a/src/mod/endpoints/mod_sofia/sofia.c +++ b/src/mod/endpoints/mod_sofia/sofia.c @@ -85,6 +85,7 @@ void sofia_handle_sip_i_notify(switch_core_session_t *session, int status, switch_channel_t *channel = NULL; private_object_t *tech_pvt = NULL; switch_event_t *s_event = NULL; + sofia_gateway_subscription_t *gw_sub_ptr; /* make sure we have a proper event */ if (!sip || !sip->sip_event) { @@ -172,21 +173,60 @@ void sofia_handle_sip_i_notify(switch_core_session_t *session, int status, nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS(nua), TAG_END()); } + /* if no session, assume it could be an incoming notify from a gateway subscription */ + if (session) { + /* make sure we have a proper "talk" event */ + if (strcasecmp(sip->sip_event->o_type, "talk")) { + goto error; + } - /* make sure we have a proper "talk" event */ - if (!session || strcasecmp(sip->sip_event->o_type, "talk")) { - goto error; - } - - - if (!switch_channel_test_flag(channel, CF_OUTBOUND)) { - switch_channel_answer(channel); - switch_channel_set_variable(channel, "auto_answer_destination", switch_channel_get_variable(channel, "destination_number")); - switch_ivr_session_transfer(session, "auto_answer", NULL, NULL); - nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS(nua), TAG_END()); - return; + if (!switch_channel_test_flag(channel, CF_OUTBOUND)) { + switch_channel_answer(channel); + switch_channel_set_variable(channel, "auto_answer_destination", switch_channel_get_variable(channel, "destination_number")); + switch_ivr_session_transfer(session, "auto_answer", NULL, NULL); + nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS(nua), TAG_END()); + return; + } } + if (!sofia_private || !sofia_private->gateway) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Gateway information missing\n"); + goto error; + } + + /* find the corresponding gateway subscription (if any) */ + if (!(gw_sub_ptr = sofia_find_gateway_subscription(sofia_private->gateway, sip->sip_event->o_type))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "Could not find gateway subscription. Gateway: %s. Subscription Event: %s\n", + sofia_private->gateway->name, sip->sip_event->o_type); + goto error; + } + + if (!(gw_sub_ptr->state == SUB_STATE_SUBED || gw_sub_ptr->state == SUB_STATE_SUBSCRIBE)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "Ignoring notify due to subscription state: %d\n", + gw_sub_ptr->state); + goto error; + } + + /* dispatch freeswitch event */ + if (switch_event_create(&s_event, SWITCH_EVENT_NOTIFY_IN) == SWITCH_STATUS_SUCCESS) { + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "event", "%s", sip->sip_event->o_type); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "pl_data", "%s", sip->sip_payload->pl_data); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "sip_content_type", "%s", sip->sip_content_type->c_type); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "port", "%d", sofia_private->gateway->profile->sip_port); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "module_name", "%s", "mod_sofia"); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "profile_name", "%s", sofia_private->gateway->profile->name); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "profile_uri", "%s", sofia_private->gateway->profile->url); + switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "gateway_name", "%s", sofia_private->gateway->name); + switch_event_fire(&s_event); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "dispatched freeswitch event for message-summary NOTIFY\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create event\n"); + goto error; + } + + return; error: nua_respond(nh, 481, "Subscription Does Not Exist", NUTAG_WITH_THIS(nua), TAG_END()); @@ -565,6 +605,7 @@ void *SWITCH_THREAD_FUNC sofia_profile_worker_thread_run(switch_thread_t *thread sofia_reg_check_gateway(profile, switch_timestamp(NULL)); gateway_loops = 0; } + sofia_sub_check_gateway(profile, time(NULL)); loops = 0; } @@ -859,9 +900,56 @@ static void logger(void *logarg, char const *fmt, va_list ap) } } +static void parse_gateway_subscriptions(sofia_profile_t *profile, sofia_gateway_t *gateway, switch_xml_t gw_subs_tag) +{ + switch_xml_t subscription_tag, param; + + for (subscription_tag = switch_xml_child(gw_subs_tag, "subscription"); subscription_tag; subscription_tag = subscription_tag->next) { + sofia_gateway_subscription_t *gw_sub; + + if ((gw_sub = switch_core_alloc(profile->pool, sizeof(*gw_sub)))) { + char *expire_seconds = "3600", *retry_seconds = "30", *content_type = "NO_CONTENT_TYPE"; + char *event = (char *) switch_xml_attr_soft(subscription_tag, "event"); + gw_sub->event = switch_core_strdup(gateway->pool, event); + gw_sub->gateway = gateway; + gw_sub->next = NULL; + + for (param = switch_xml_child(subscription_tag, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + if (!strcmp(var, "expire-seconds")) { + expire_seconds = val; + } else if (!strcmp(var, "retry-seconds")) { + retry_seconds = val; + } else if (!strcmp(var, "content-type")) { + content_type = val; + } + } + + gw_sub->retry_seconds = atoi(retry_seconds); + if (gw_sub->retry_seconds < 10) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "INVALID: retry_seconds correcting the value to 30\n"); + gw_sub->retry_seconds = 30; + } + + gw_sub->expires_str = switch_core_strdup(gateway->pool, expire_seconds); + + if ((gw_sub->freq = atoi(gw_sub->expires_str)) < 5) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "Invalid Freq: %d. Setting Register-Frequency to 3600\n", gw_sub->freq); + gw_sub->freq = 3600; + } + gw_sub->freq -= 2; + gw_sub->content_type = switch_core_strdup(gateway->pool, content_type); + gw_sub->next = gateway->subscriptions; + } + gateway->subscriptions = gw_sub; + } +} + static void parse_gateways(sofia_profile_t *profile, switch_xml_t gateways_tag) { - switch_xml_t gateway_tag, param; + switch_xml_t gateway_tag, param, gw_subs_tag; sofia_gateway_t *gp; for (gateway_tag = switch_xml_child(gateways_tag, "gateway"); gateway_tag; gateway_tag = gateway_tag->next) { @@ -969,6 +1057,10 @@ static void parse_gateways(sofia_profile_t *profile, switch_xml_t gateways_tag) } } + if ((gw_subs_tag = switch_xml_child(gateway_tag, "subscriptions"))) { + parse_gateway_subscriptions(profile, gateway, gw_subs_tag); + } + if (switch_strlen_zero(realm)) { realm = name; } diff --git a/src/mod/endpoints/mod_sofia/sofia_presence.c b/src/mod/endpoints/mod_sofia/sofia_presence.c index 32ce348b6b..5a206f7368 100644 --- a/src/mod/endpoints/mod_sofia/sofia_presence.c +++ b/src/mod/endpoints/mod_sofia/sofia_presence.c @@ -1617,12 +1617,66 @@ void sofia_presence_handle_sip_i_subscribe(int status, } } +sofia_gateway_subscription_t *sofia_find_gateway_subscription(sofia_gateway_t *gateway_ptr, const char *event) { + sofia_gateway_subscription_t *gw_sub_ptr; + for (gw_sub_ptr = gateway_ptr->subscriptions; gw_sub_ptr; gw_sub_ptr = gw_sub_ptr->next) { + if (!strcasecmp(gw_sub_ptr->event, event)) { + /* this is the gateway subscription we are interested in */ + return gw_sub_ptr; + } + } + return NULL; +} + void sofia_presence_handle_sip_r_subscribe(int status, char const *phrase, nua_t *nua, sofia_profile_t *profile, nua_handle_t *nh, sofia_private_t *sofia_private, sip_t const *sip, tagi_t tags[]) { + sip_event_t const *o = NULL; + sofia_gateway_subscription_t *gw_sub_ptr; + + if (!sip) { + return; + } + tl_gets(tags, SIPTAG_EVENT_REF(o), TAG_END()); + /* o->o_type: message-summary (for example) */ + if (!o) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Event information not given\n"); + return; + } + + if (!sofia_private || !sofia_private->gateway) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Gateway information missing\n"); + return; + } + + /* Find the subscription if one exists */ + if (!(gw_sub_ptr = sofia_find_gateway_subscription(sofia_private->gateway, o->o_type))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Could not find gateway subscription. Gateway: %s. Subscription Event: %s\n", + sofia_private->gateway->name, o->o_type); + return; + } + + /* Update the subscription status for the subscription */ + switch (status) { + case 200: + /* TODO: in the spec it is possible for the other side to change the original expiry time, + * this needs to be researched (eg, what sip header this information will be in) and implemented. + * Although, since it seems the sofia stack is pretty much handling the subscription expiration + * anyway, then maybe its not even worth bothering. + */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "got 200 OK response, updated state to SUB_STATE_SUBSCRIBE.\n"); + gw_sub_ptr->state = SUB_STATE_SUBSCRIBE; + break; + case 100: + break; + default: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "status (%d) != 200, updated state to SUB_STATE_FAILED.\n", status); + gw_sub_ptr->state = SUB_STATE_FAILED; + break; + } } void sofia_presence_handle_sip_i_publish(nua_t *nua, sofia_profile_t *profile, nua_handle_t *nh, sofia_private_t *sofia_private, sip_t const *sip, diff --git a/src/mod/endpoints/mod_sofia/sofia_reg.c b/src/mod/endpoints/mod_sofia/sofia_reg.c index 03d63a84f0..10d3cd5cb7 100644 --- a/src/mod/endpoints/mod_sofia/sofia_reg.c +++ b/src/mod/endpoints/mod_sofia/sofia_reg.c @@ -77,6 +77,107 @@ void sofia_reg_unregister(sofia_profile_t *profile) } } +void sofia_sub_check_gateway(sofia_profile_t *profile, time_t now) +{ + /* NOTE: A lot of the mechanism in place here for refreshing subscriptions is + * pretty much redundant, as the sofia stack takes it upon itself to + * refresh subscriptions on its own, based on the value of the Expires + * header (which we control in the outgoing subscription request) + */ + sofia_gateway_t *gateway_ptr; + + for (gateway_ptr = profile->gateways; gateway_ptr; gateway_ptr = gateway_ptr->next) { + sofia_gateway_subscription_t *gw_sub_ptr; + + for (gw_sub_ptr = gateway_ptr->subscriptions; gw_sub_ptr; gw_sub_ptr = gw_sub_ptr->next) { + int ss_state = nua_callstate_authenticating; + sub_state_t ostate = gw_sub_ptr->state; + + if (!now) { + gw_sub_ptr->state = ostate = SUB_STATE_UNSUBED; + gw_sub_ptr->expires_str = "0"; + } + + switch (ostate) { + case SUB_STATE_NOSUB: + break; + case SUB_STATE_SUBSCRIBE: + gw_sub_ptr->expires = now + gw_sub_ptr->freq; + gw_sub_ptr->state = SUB_STATE_SUBED; + break; + case SUB_STATE_UNSUBSCRIBE: + gw_sub_ptr->state = SUB_STATE_NOSUB; + + /* not tested .. */ + nua_unsubscribe(gateway_ptr->nh, + NUTAG_URL(gateway_ptr->register_url), + SIPTAG_EVENT_STR(gw_sub_ptr->event), + SIPTAG_ACCEPT_STR(gw_sub_ptr->content_type), + SIPTAG_TO_STR(gateway_ptr->register_from), + SIPTAG_FROM_STR(gateway_ptr->register_from), + SIPTAG_CONTACT_STR(gateway_ptr->register_contact), + TAG_NULL()); + + break; + case SUB_STATE_UNSUBED: + if ((gateway_ptr->nh = nua_handle(gateway_ptr->profile->nua, NULL, + NUTAG_URL(gateway_ptr->register_proxy), + SIPTAG_TO_STR(gateway_ptr->register_to), + NUTAG_CALLSTATE_REF(ss_state), + SIPTAG_FROM_STR(gateway_ptr->register_from), TAG_END()))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "subscribing to [%s] on gateway [%s]\n", gw_sub_ptr->event, gateway_ptr->name); + } + + gateway_ptr->sofia_private = malloc(sizeof(*gateway_ptr->sofia_private)); + switch_assert(gateway_ptr->sofia_private); + + memset(gateway_ptr->sofia_private, 0, sizeof(*gateway_ptr->sofia_private)); + + gateway_ptr->sofia_private->gateway = gateway_ptr; + nua_handle_bind(gateway_ptr->nh, gateway_ptr->sofia_private); + + if (now) { + nua_subscribe(gateway_ptr->nh, + NUTAG_URL(gateway_ptr->register_url), + SIPTAG_EVENT_STR(gw_sub_ptr->event), + SIPTAG_ACCEPT_STR(gw_sub_ptr->content_type), + SIPTAG_TO_STR(gateway_ptr->register_from), + SIPTAG_FROM_STR(gateway_ptr->register_from), + SIPTAG_CONTACT_STR(gateway_ptr->register_contact), + SIPTAG_EXPIRES_STR(gw_sub_ptr->expires_str), // sofia stack bases its auto-refresh stuff on this + TAG_NULL()); + gw_sub_ptr->retry = now + gw_sub_ptr->retry_seconds; + } else { + nua_unsubscribe(gateway_ptr->nh, + NUTAG_URL(gateway_ptr->register_url), + SIPTAG_EVENT_STR(gw_sub_ptr->event), + SIPTAG_ACCEPT_STR(gw_sub_ptr->content_type), + SIPTAG_FROM_STR(gateway_ptr->register_from), + SIPTAG_TO_STR(gateway_ptr->register_from), + SIPTAG_CONTACT_STR(gateway_ptr->register_contact), + SIPTAG_EXPIRES_STR(gw_sub_ptr->expires_str), + TAG_NULL()); + } + gw_sub_ptr->state = SUB_STATE_TRYING; + break; + + case SUB_STATE_FAILED: + case SUB_STATE_TRYING: + if (gw_sub_ptr->retry && now >= gw_sub_ptr->retry) { + gw_sub_ptr->state = SUB_STATE_UNSUBED; + gw_sub_ptr->retry = 0; + } + break; + default: + if (now >= gw_sub_ptr->expires) { + gw_sub_ptr->state = SUB_STATE_UNSUBED; + } + break; + } + } + } +} + void sofia_reg_check_gateway(sofia_profile_t *profile, time_t now) { sofia_gateway_t *gateway_ptr, *last = NULL; diff --git a/src/switch_event.c b/src/switch_event.c index 5d322690bc..05c5cd2dd1 100644 --- a/src/switch_event.c +++ b/src/switch_event.c @@ -142,6 +142,7 @@ static char *EVENT_NAMES[] = { "DTMF", "MESSAGE", "PRESENCE_IN", + "NOTIFY_IN", "PRESENCE_OUT", "PRESENCE_PROBE", "MESSAGE_WAITING",