]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
MODENDP-77 Gateway event subscriptions.
authorMichael Jerris <mike@jerris.com>
Mon, 24 Nov 2008 15:52:55 +0000 (15:52 +0000)
committerMichael Jerris <mike@jerris.com>
Mon, 24 Nov 2008 15:52:55 +0000 (15:52 +0000)
Merged with minor modifications.
Still needs to add support for reload/rescan.

git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@10525 d0543943-73ff-0310-b7d9-9358b9ac24b2

src/include/switch_types.h
src/mod/endpoints/mod_sofia/mod_sofia.h
src/mod/endpoints/mod_sofia/sofia.c
src/mod/endpoints/mod_sofia/sofia_presence.c
src/mod/endpoints/mod_sofia/sofia_reg.c
src/switch_event.c

index 068b79a3a1120f53915a69b6cf1b324d1e6009a2..22375eb087a13cf092903fdcb1f769210a68e5c7 100644 (file)
@@ -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,
index 838a0ee65cb11f893742d820072304cf26a86403..d29d86abf8b8b10e97fa3e1c9f4d16a09505f66e 100644 (file)
@@ -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);
 
index cd07fa2f8b5ad54fd237ea683929622009fb9f89..c08ea25a5901d27866a5945733af831cd64b10ab 100644 (file)
@@ -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;
                        }
index 32ce348b6be268e396a898a1ad084c81cee90a47..5a206f736848a3d17a81767b87158650e77702f8 100644 (file)
@@ -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,
index 03d63a84f0c43ff3495cbfb6523989b96fc5569d..10d3cd5cb735f1cf97dc1a9d781e09961585892f 100644 (file)
@@ -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;
index 5d322690bca253c59a652637a15b7bf213a6b1f8..05c5cd2dd164a5ae35ce8de472d996ec7ff8430d 100644 (file)
@@ -142,6 +142,7 @@ static char *EVENT_NAMES[] = {
        "DTMF",
        "MESSAGE",
        "PRESENCE_IN",
+       "NOTIFY_IN",
        "PRESENCE_OUT",
        "PRESENCE_PROBE",
        "MESSAGE_WAITING",