From 1cd169d034da59581c65d6a196a5b697b34affca Mon Sep 17 00:00:00 2001 From: Mark Michelson Date: Wed, 4 Apr 2012 20:50:55 +0000 Subject: [PATCH] Changes to the channels directory for Digium phone support. Next step: Get this poopoo compiling! git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/10-digiumphones@361261 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- channels/chan_sip.c | 183 +++++++++++++++++++++++++++++++------ channels/chan_skinny.c | 10 +- channels/sip/include/sip.h | 3 + 3 files changed, 164 insertions(+), 32 deletions(-) diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 7cd8dd71a3..d426860180 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -264,6 +264,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cel.h" #include "asterisk/data.h" #include "asterisk/aoc.h" +#include "asterisk/custom_control_frame.h" #include "asterisk/message.h" #include "sip/include/sip.h" #include "sip/include/globals.h" @@ -1086,6 +1087,13 @@ static void destroy_escs(void) } } +struct state_notify_data { + int state; + int presence_state; + const char *presence_subtype; + const char *presence_message; +}; + /*! * \details * Here we implement the container for dialogs which are in the @@ -1369,7 +1377,8 @@ static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context); /*--- Device monitoring and Device/extension state/event handling */ -static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data); +static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p); +static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data); static int sip_devicestate(void *data); static int sip_poke_noanswer(const void *data); static int sip_poke_peer(struct sip_peer *peer, int force); @@ -1500,7 +1509,7 @@ static int get_rpid(struct sip_pvt *p, struct sip_request *oreq); static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason); static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id); static int get_msg_text(char *buf, int len, struct sip_request *req); -static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout); +static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout); static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen); static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen); static int get_domain(const char *str, char *domain, int len); @@ -3858,7 +3867,10 @@ static int __sip_autodestruct(const void *data) /* If this is a subscription, tell the phone that we got a timeout */ if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) { - transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */ + struct state_notify_data data = { 0, }; + data.state = AST_EXTENSION_DEACTIVATED; + + transmit_state_notify(p, &data, 1, TRUE); /* Send last notification */ p->subscribed = NONE; append_history(p, "Subscribestatus", "timeout"); ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : ""); @@ -6866,6 +6878,54 @@ static int initialize_udptl(struct sip_pvt *p) return 0; } +/*! + * \brief Sends AST_CUSTOM_FRAME of type sip info. + * + * \note pvt is expected to be locked before entering this function. + */ +static int sip_handle_custom_info(struct sip_pvt *pvt, struct ast_custom_payload *pl) +{ + struct ast_variable *headers = NULL; + char *content_type = NULL; + char *content = NULL; + char *useragent_filter = NULL; + struct ast_variable *var; + struct sip_request req; + int res = -1; + + if (ast_custom_payload_sipinfo_decode(pl, &headers, &content_type, &content, &useragent_filter)) { + goto custom_info_cleanup; + } + + if (!(ast_strlen_zero(useragent_filter))) { + int match = (strstr(pvt->useragent, useragent_filter)) ? 1 : 0; + if (!match) { + goto custom_info_cleanup; + } + } + + reqprep(&req, pvt, SIP_INFO, 0, 1); + for (var = headers; var; var = var->next) { + add_header(&req, var->name, var->value); + } + if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) { + add_header(&req, "Content-Type", content_type); + add_content(&req, content); + } + + res = send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); + +custom_info_cleanup: + + ast_free(content); + ast_free(content_type); + ast_free(useragent_filter); + ast_variables_destroy(headers); + + return res; +} + + /*! \brief Play indication to user * With SIP a lot of indications is sent as messages, letting the device play the indication - busy signal, congestion etc @@ -6995,6 +7055,11 @@ static int sip_indicate(struct ast_channel *ast, int condition, const void *data case AST_CONTROL_REDIRECTING: update_redirecting(p, data, datalen); break; + case AST_CONTROL_CUSTOM: + if (datalen && ast_custom_payload_type((struct ast_custom_payload *) data) == AST_CUSTOM_SIP_INFO) { + sip_handle_custom_info(p, (struct ast_custom_payload *) data); + } + break; case AST_CONTROL_AOC: { struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, ast); @@ -12875,8 +12940,13 @@ static int find_calling_channel(void *obj, void *arg, void *data, int flags) return res ? CMP_MATCH | CMP_STOP : 0; } +static int allow_notify_user_presence(struct sip_pvt *p) +{ + return (strstr(p->useragent, "Digium")) ? 1 : 0; +} + /*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */ -static void state_notify_build_xml(int state, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto) +static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto) { enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; const char *statestring = "terminated"; @@ -12884,7 +12954,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const const char *pidfnote= "Ready"; char hint[AST_MAX_EXTENSION]; - switch (state) { + switch (data->state) { case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): statestring = (sip_cfg.notifyringing) ? "early" : "confirmed"; local_state = NOTIFY_INUSE; @@ -12929,9 +12999,16 @@ static void state_notify_build_xml(int state, int full, const char *exten, const /* Check which device/devices we are watching and if they are registered */ if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) { - char *hint2 = hint, *individual_hint = NULL; + char *hint2; + char *individual_hint = NULL; int hint_count = 0, unavailable_count = 0; + /* strip off any possible PRESENCE providers from hint */ + if ((hint2 = strrchr(hint, ','))) { + *hint2 = '\0'; + } + hint2 = hint; + while ((individual_hint = strsep(&hint2, "&"))) { hint_count++; @@ -12979,12 +13056,24 @@ static void state_notify_build_xml(int state, int full, const char *exten, const ast_str_append(tmp, 0, "open\n"); else ast_str_append(tmp, 0, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed"); + + if (allow_notify_user_presence(p) && (data->presence_state > 0)) { + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "%s\n", + ast_presence_state2str(data->presence_state), + S_OR(data->presence_subtype, ""), + S_OR(data->presence_message, "")); + ast_str_append(tmp, 0, "\n"); + } + ast_str_append(tmp, 0, "\n\n"); break; case DIALOG_INFO_XML: /* SNOM subscribes in this format */ ast_str_append(tmp, 0, "\n"); ast_str_append(tmp, 0, "\n", p->dialogver, full ? "full" : "partial", mto); - if ((state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) { + if ((data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) { const char *local_display = exten; char *local_target = ast_strdupa(mto); const char *remote_display = exten; @@ -13053,7 +13142,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const ast_str_append(tmp, 0, "\n", exten); } ast_str_append(tmp, 0, "%s\n", statestring); - if (state == AST_EXTENSION_ONHOLD) { + if (data->state == AST_EXTENSION_ONHOLD) { ast_str_append(tmp, 0, "\n\n" "\n" "\n\n", mto); @@ -13097,7 +13186,7 @@ static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscr } /*! \brief Used in the SUBSCRIBE notification subsystem (RFC3265) */ -static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout) +static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout) { struct ast_str *tmp = ast_str_alloca(4000); char from[256], to[256]; @@ -13129,7 +13218,7 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim reqprep(&req, p, SIP_NOTIFY, 0, 1); - switch(state) { + switch (data->state) { case AST_EXTENSION_DEACTIVATED: if (timeout) add_header(&req, "Subscription-State", "terminated;reason=timeout"); @@ -13152,19 +13241,19 @@ static int transmit_state_notify(struct sip_pvt *p, int state, int full, int tim case XPIDF_XML: case CPIM_PIDF_XML: add_header(&req, "Event", subscriptiontype->event); - state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); + state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); add_header(&req, "Content-Type", subscriptiontype->mediatype); p->dialogver++; break; case PIDF_XML: /* Eyebeam supports this format */ add_header(&req, "Event", subscriptiontype->event); - state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); + state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); add_header(&req, "Content-Type", subscriptiontype->mediatype); p->dialogver++; break; case DIALOG_INFO_XML: /* SNOM subscribes in this format */ add_header(&req, "Event", subscriptiontype->event); - state_notify_build_xml(state, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); + state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); add_header(&req, "Content-Type", subscriptiontype->mediatype); p->dialogver++; break; @@ -14946,37 +15035,35 @@ static void cb_extensionstate_destroy(int id, void *data) dialog_unref(p, "the extensionstate containing this dialog ptr was destroyed"); } -/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem -\note If you add an "hint" priority to the extension in the dial plan, - you will get notifications on device state changes */ -static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data) +static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p) { - struct sip_pvt *p = data; - sip_pvt_lock(p); - switch(state) { + switch (data->state) { case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ case AST_EXTENSION_REMOVED: /* Extension is gone */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* Delete subscription in 32 secs */ - ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username); + ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, data->state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username); p->subscribed = NONE; - append_history(p, "Subscribestatus", "%s", state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated"); + append_history(p, "Subscribestatus", "%s", data->state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated"); break; default: /* Tell user */ - p->laststate = state; + p->laststate = data->state; + p->last_presence_state = data->presence_state; + ast_string_field_set(p, last_presence_subtype, S_OR(data->presence_subtype, "")); + ast_string_field_set(p, last_presence_message, S_OR(data->presence_message, "")); break; } if (p->subscribed != NONE) { /* Only send state NOTIFY if we know the format */ if (!p->pendinginvite) { - transmit_state_notify(p, state, 1, FALSE); + transmit_state_notify(p, data, 1, FALSE); } else { /* We already have a NOTIFY sent that is not answered. Queue the state up. if many state changes happen meanwhile, we will only send a notification of the last one */ ast_set_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE); } } - ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(state), p->username, + ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : ""); sip_pvt_unlock(p); @@ -14984,6 +15071,27 @@ static int cb_extensionstate(const char *context, const char *exten, enum ast_ex return 0; } +/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem +\note If you add an "hint" priority to the extension in the dial plan, + you will get notifications on device state changes */ +static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data) +{ + struct sip_pvt *p = data; + struct state_notify_data notify_data = { + .state = info->exten_state, + .presence_state = info->presence_state, + .presence_subtype = info->presence_subtype, + .presence_message = info->presence_message, + }; + + if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) { + /* ignore a presence triggered update if we know the useragent doesn't care */ + return 0; + } + + return extensionstate_update(context, exten, ¬ify_data, p); +} + /*! \brief Send a fake 401 Unauthorized response when the administrator wants to hide the names of local devices from fishers */ @@ -21022,9 +21130,15 @@ static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest pvt_set_needdestroy(p, "received 200 response"); } if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) { + struct state_notify_data data = { + .state = p->laststate, + .presence_state = p->last_presence_state, + .presence_subtype = p->last_presence_subtype, + .presence_message = p->last_presence_message, + }; /* Ready to send the next state we have on queue */ ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE); - cb_extensionstate((char *)p->context, (char *)p->exten, p->laststate, (void *) p); + extensionstate_update((char *)p->context, (char *)p->exten, &data, (void *) p); } } break; @@ -25714,8 +25828,10 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, sip_unref_peer(peer, "release a peer ref now that MWI is sent"); } } else if (p->subscribed != CALL_COMPLETION) { - - if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) { + struct state_notify_data data = { 0, }; + char *subtype = NULL; + char *message = NULL; + if ((data.state = ast_extension_state(NULL, p->context, p->exten)) < 0) { ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", p->exten, p->context, ast_sockaddr_stringify(&p->sa)); transmit_response(p, "404 Not found", req); @@ -25725,14 +25841,21 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, } return 0; } + if (allow_notify_user_presence(p)) { + data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); + data.presence_subtype = subtype; + data.presence_message = message; + } ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response(p, "200 OK", req); - transmit_state_notify(p, firststate, 1, FALSE); /* Send first notification */ - append_history(p, "Subscribestatus", "%s", ast_extension_state2str(firststate)); + transmit_state_notify(p, &data, 1, FALSE); /* Send first notification */ + append_history(p, "Subscribestatus", "%s", ast_extension_state2str(data.state)); /* hide the 'complete' exten/context in the refer_to field for later display */ ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->context); /* Deleted the slow iteration of all sip dialogs to find old subscribes from this peer for exten@context */ + ast_free(subtype); + ast_free(message); } if (!p->expiry) { pvt_set_needdestroy(p, "forcing expiration"); diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c index 82330a0376..3af0298617 100644 --- a/channels/chan_skinny.c +++ b/channels/chan_skinny.c @@ -1481,7 +1481,7 @@ static struct ast_channel_tech skinny_tech = { .bridge = ast_rtp_instance_bridge, }; -static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data); +static int skinny_extensionstate_cb(char *context, char* id, struct ast_state_cb_info *info, void *data); static int skinny_transfer(struct skinny_subchannel *sub); static struct skinny_line *skinny_line_alloc(void) @@ -2990,11 +2990,17 @@ static void transmit_capabilitiesreq(struct skinny_device *d) transmit_response(d, req); } -static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data) +static int skinny_extensionstate_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data) { struct skinny_container *container = data; struct skinny_device *d = NULL; char hint[AST_MAX_EXTENSION]; + int state = info->exten_state; + + /* only interested in device state here */ + if (info->reason != AST_HINT_UPDATE_DEVICE) { + return 0; + } if (container->type == SKINNY_SDCONTAINER) { struct skinny_speeddial *sd = container->data; diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index dcd4199391..8e0581a01d 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -1019,6 +1019,8 @@ struct sip_pvt { AST_STRING_FIELD(parkinglot); /*!< Parkinglot */ AST_STRING_FIELD(engine); /*!< RTP engine to use */ AST_STRING_FIELD(dialstring); /*!< The dialstring used to call this SIP endpoint */ + AST_STRING_FIELD(last_presence_subtype); /*!< The last presence subtype sent for a subscription. */ + AST_STRING_FIELD(last_presence_message); /*!< The last presence message for a subscription */ AST_STRING_FIELD(msg_body); /*!< Text for a MESSAGE body */ ); char via[128]; /*!< Via: header */ @@ -1117,6 +1119,7 @@ struct sip_pvt { enum subscriptiontype subscribed; /*!< SUBSCRIBE: Is this dialog a subscription? */ int stateid; /*!< SUBSCRIBE: ID for devicestate subscriptions */ int laststate; /*!< SUBSCRIBE: Last known extension state */ + int last_presence_state; /*!< SUBSCRIBE: Last known presence state */ uint32_t dialogver; /*!< SUBSCRIBE: Version for subscription dialog-info */ struct ast_dsp *dsp; /*!< Inband DTMF or Fax CNG tone Detection dsp */