diff --git a/channels/Makefile b/channels/Makefile index a30daa5bad..79404d6b6b 100644 --- a/channels/Makefile +++ b/channels/Makefile @@ -77,6 +77,9 @@ $(subst .c,.o,$(wildcard iax2/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_iax2) $(if $(filter chan_sip,$(EMBEDDED_MODS)),modules.link,chan_sip.so): $(subst .c,.o,$(wildcard sip/*.c)) $(subst .c,.o,$(wildcard sip/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_sip) +$(if $(filter chan_pjsip,$(EMBEDDED_MODS)),modules.link,chan_pjsip.so): $(subst .c,.o,$(wildcard pjsip/*.c)) +$(subst .c,.o,$(wildcard pjsip/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_pjsip) + # Additional objects to combine with chan_dahdi.so CHAN_DAHDI_OBJS= \ $(subst .c,.o,$(wildcard dahdi/*.c)) \ diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index ad74b574e0..71edb351b5 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -61,62 +61,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" -/*** DOCUMENTATION - - - Return a dial string for dialing all contacts on an AOR. - - - - Name of the endpoint - - - Name of an AOR to use, if not specified the configured AORs on the endpoint are used - - - Optional request user to use in the request URI - - - - Returns a properly formatted dial string for dialing all contacts on an AOR. - - - - - Media and codec offerings to be set on an outbound SIP channel prior to dialing. - - - - types of media offered - - - - Returns the codecs offered based upon the media choice - - - ***/ +#include "pjsip/include/chan_pjsip.h" +#include "pjsip/include/dialplan_functions.h" static const char desc[] = "PJSIP Channel"; static const char channel_type[] = "PJSIP"; static unsigned int chan_idx; -/*! - * \brief Positions of various media - */ -enum sip_session_media_position { - /*! \brief First is audio */ - SIP_MEDIA_AUDIO = 0, - /*! \brief Second is video */ - SIP_MEDIA_VIDEO, - /*! \brief Last is the size for media details */ - SIP_MEDIA_SIZE, -}; - -struct chan_pjsip_pvt { - struct ast_sip_session_media *media[SIP_MEDIA_SIZE]; -}; - static void chan_pjsip_pvt_dtor(void *obj) { struct chan_pjsip_pvt *pvt = obj; @@ -145,7 +97,7 @@ static int chan_pjsip_devicestate(const char *data); static int chan_pjsip_queryoption(struct ast_channel *ast, int option, void *data, int *datalen); /*! \brief PBX interface structure for channel registration */ -static struct ast_channel_tech chan_pjsip_tech = { +struct ast_channel_tech chan_pjsip_tech = { .type = channel_type, .description = "PJSIP Channel Driver", .requester = chan_pjsip_request, @@ -164,6 +116,7 @@ static struct ast_channel_tech chan_pjsip_tech = { .fixup = chan_pjsip_fixup, .devicestate = chan_pjsip_devicestate, .queryoption = chan_pjsip_queryoption, + .func_channel_read = pjsip_acf_channel_read, .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER }; @@ -191,184 +144,6 @@ static struct ast_sip_session_supplement chan_pjsip_ack_supplement = { .incoming_request = chan_pjsip_incoming_ack, }; -/*! \brief Dialplan function for constructing a dial string for calling all contacts */ -static int chan_pjsip_dial_contacts(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) -{ - RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); - RAII_VAR(struct ast_str *, dial, NULL, ast_free_ptr); - const char *aor_name; - char *rest; - - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(endpoint_name); - AST_APP_ARG(aor_name); - AST_APP_ARG(request_user); - ); - - AST_STANDARD_APP_ARGS(args, data); - - if (ast_strlen_zero(args.endpoint_name)) { - ast_log(LOG_WARNING, "An endpoint name must be specified when using the '%s' dialplan function\n", cmd); - return -1; - } else if (!(endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", args.endpoint_name))) { - ast_log(LOG_WARNING, "Specified endpoint '%s' was not found\n", args.endpoint_name); - return -1; - } - - aor_name = S_OR(args.aor_name, endpoint->aors); - - if (ast_strlen_zero(aor_name)) { - ast_log(LOG_WARNING, "No AOR has been provided and no AORs are configured on endpoint '%s'\n", args.endpoint_name); - return -1; - } else if (!(dial = ast_str_create(len))) { - ast_log(LOG_WARNING, "Could not get enough buffer space for dialing contacts\n"); - return -1; - } else if (!(rest = ast_strdupa(aor_name))) { - ast_log(LOG_WARNING, "Could not duplicate provided AORs\n"); - return -1; - } - - while ((aor_name = strsep(&rest, ","))) { - RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); - RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); - struct ao2_iterator it_contacts; - struct ast_sip_contact *contact; - - if (!aor) { - /* If the AOR provided is not found skip it, there may be more */ - continue; - } else if (!(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { - /* No contacts are available, skip it as well */ - continue; - } else if (!ao2_container_count(contacts)) { - /* We were given a container but no contacts are in it... */ - continue; - } - - it_contacts = ao2_iterator_init(contacts, 0); - for (; (contact = ao2_iterator_next(&it_contacts)); ao2_ref(contact, -1)) { - ast_str_append(&dial, -1, "PJSIP/"); - - if (!ast_strlen_zero(args.request_user)) { - ast_str_append(&dial, -1, "%s@", args.request_user); - } - ast_str_append(&dial, -1, "%s/%s&", args.endpoint_name, contact->uri); - } - ao2_iterator_destroy(&it_contacts); - } - - /* Trim the '&' at the end off */ - ast_str_truncate(dial, ast_str_strlen(dial) - 1); - - ast_copy_string(buf, ast_str_buffer(dial), len); - - return 0; -} - -static struct ast_custom_function chan_pjsip_dial_contacts_function = { - .name = "PJSIP_DIAL_CONTACTS", - .read = chan_pjsip_dial_contacts, -}; - -static int media_offer_read_av(struct ast_sip_session *session, char *buf, - size_t len, enum ast_format_type media_type) -{ - int i, size = 0; - struct ast_format fmt; - const char *name; - - for (i = 0; ast_codec_pref_index(&session->override_prefs, i, &fmt); ++i) { - if (AST_FORMAT_GET_TYPE(fmt.id) != media_type) { - continue; - } - - name = ast_getformatname(&fmt); - - if (ast_strlen_zero(name)) { - ast_log(LOG_WARNING, "PJSIP_MEDIA_OFFER unrecognized format %s\n", name); - continue; - } - - /* add one since we'll include a comma */ - size = strlen(name) + 1; - len -= size; - if ((len) < 0) { - break; - } - - /* no reason to use strncat here since we have already ensured buf has - enough space, so strcat can be safely used */ - strcat(buf, name); - strcat(buf, ","); - } - - if (size) { - /* remove the extra comma */ - buf[strlen(buf) - 1] = '\0'; - } - return 0; -} - -struct media_offer_data { - struct ast_sip_session *session; - enum ast_format_type media_type; - const char *value; -}; - -static int media_offer_write_av(void *obj) -{ - struct media_offer_data *data = obj; - int i; - struct ast_format fmt; - /* remove all of the given media type first */ - for (i = 0; ast_codec_pref_index(&data->session->override_prefs, i, &fmt); ++i) { - if (AST_FORMAT_GET_TYPE(fmt.id) == data->media_type) { - ast_codec_pref_remove(&data->session->override_prefs, &fmt); - } - } - ast_format_cap_remove_bytype(data->session->req_caps, data->media_type); - ast_parse_allow_disallow(&data->session->override_prefs, data->session->req_caps, data->value, 1); - - return 0; -} - -static int media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) -{ - struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - - if (!strcmp(data, "audio")) { - return media_offer_read_av(channel->session, buf, len, AST_FORMAT_TYPE_AUDIO); - } else if (!strcmp(data, "video")) { - return media_offer_read_av(channel->session, buf, len, AST_FORMAT_TYPE_VIDEO); - } - - return 0; -} - -static int media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) -{ - struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); - - struct media_offer_data mdata = { - .session = channel->session, - .value = value - }; - - if (!strcmp(data, "audio")) { - mdata.media_type = AST_FORMAT_TYPE_AUDIO; - } else if (!strcmp(data, "video")) { - mdata.media_type = AST_FORMAT_TYPE_VIDEO; - } - - return ast_sip_push_task_synchronous(channel->session->serializer, media_offer_write_av, &mdata); -} - -static struct ast_custom_function media_offer_function = { - .name = "PJSIP_MEDIA_OFFER", - .read = media_offer_read, - .write = media_offer_write -}; - /*! \brief Function called by RTP engine to get local audio RTP peer */ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { @@ -437,6 +212,20 @@ static int send_direct_media_request(void *data) session->endpoint->media.direct_media.method, 1); } +/*! \brief Destructor function for \ref transport_info_data */ +static void transport_info_destroy(void *obj) +{ + struct transport_info_data *data = obj; + ast_free(data); +} + +/*! \brief Datastore used to store local/remote addresses for the + * INVITE request that created the PJSIP channel */ +static struct ast_datastore_info transport_info = { + .type = "chan_pjsip_transport_info", + .destroy = transport_info_destroy, +}; + static struct ast_datastore_info direct_media_mitigation_info = { }; static int direct_media_mitigate_glare(struct ast_sip_session *session) @@ -1989,12 +1778,28 @@ static void chan_pjsip_session_end(struct ast_sip_session *session) /*! \brief Function called when a request is received on the session */ static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) { + RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); + struct transport_info_data *transport_data; pjsip_tx_data *packet = NULL; if (session->channel) { return 0; } + datastore = ast_sip_session_alloc_datastore(&transport_info, "transport_info"); + if (!datastore) { + return -1; + } + + transport_data = ast_calloc(1, sizeof(*transport_data)); + if (!transport_data) { + return -1; + } + pj_sockaddr_cp(&transport_data->local_addr, &rdata->tp_info.transport->local_addr); + pj_sockaddr_cp(&transport_data->remote_addr, &rdata->pkt_info.src_addr); + datastore->data = transport_data; + ast_sip_session_add_datastore(session, datastore); + if (!(session->channel = chan_pjsip_new(session, AST_STATE_RING, session->exten, NULL, NULL, NULL))) { if (pjsip_inv_end_session(session->inv_session, 503, NULL, &packet) == PJ_SUCCESS) { ast_sip_session_send_response(session, packet); @@ -2078,6 +1883,17 @@ static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip return 0; } +static struct ast_custom_function chan_pjsip_dial_contacts_function = { + .name = "PJSIP_DIAL_CONTACTS", + .read = pjsip_acf_dial_contacts_read, +}; + +static struct ast_custom_function media_offer_function = { + .name = "PJSIP_MEDIA_OFFER", + .read = pjsip_acf_media_offer_read, + .write = pjsip_acf_media_offer_write +}; + /*! * \brief Load the module * @@ -2110,6 +1926,7 @@ static int load_module(void) if (ast_custom_function_register(&media_offer_function)) { ast_log(LOG_WARNING, "Unable to register PJSIP_MEDIA_OFFER dialplan function\n"); + goto end; } if (ast_sip_session_register_supplement(&chan_pjsip_supplement)) { @@ -2150,13 +1967,13 @@ static int reload(void) /*! \brief Unload the PJSIP channel from Asterisk */ static int unload_module(void) { - ast_custom_function_unregister(&media_offer_function); - ast_sip_session_unregister_supplement(&chan_pjsip_supplement); ast_sip_session_unregister_supplement(&pbx_start_supplement); ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement); + ast_custom_function_unregister(&media_offer_function); ast_custom_function_unregister(&chan_pjsip_dial_contacts_function); + ast_channel_unregister(&chan_pjsip_tech); ast_rtp_glue_unregister(&chan_pjsip_rtp_glue); diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c new file mode 100644 index 0000000000..ac98be5261 --- /dev/null +++ b/channels/pjsip/dialplan_functions.c @@ -0,0 +1,893 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \author \verbatim Joshua Colp \endverbatim + * \author \verbatim Matt Jordan \endverbatim + * + * \ingroup functions + * + * \brief PJSIP channel dialplan functions + */ + +/*** MODULEINFO + core + ***/ + +/*** DOCUMENTATION + + + Return a dial string for dialing all contacts on an AOR. + + + + Name of the endpoint + + + Name of an AOR to use, if not specified the configured AORs on the endpoint are used + + + Optional request user to use in the request URI + + + + Returns a properly formatted dial string for dialing all contacts on an AOR. + + + + + Media and codec offerings to be set on an outbound SIP channel prior to dialing. + + + + types of media offered + + + + Returns the codecs offered based upon the media choice + + + + + + R/O Retrieve media related information. + + When rtp is specified, the + type parameter must be provided. It specifies + which RTP parameter to read. + + + Retrieve the local address for RTP. + + + Retrieve the remote address for RTP. + + + If direct media is enabled, this address is the remote address + used for RTP. + + + Whether or not the media stream is encrypted. + + + The media stream is not encrypted. + + + The media stream is encrypted. + + + + + Whether or not the media stream is currently restricted + due to a call hold. + + + The media stream is not held. + + + The media stream is held. + + + + + + + When rtp is specified, the + media_type parameter may be provided. It specifies + which media stream the chosen RTP parameter should be retrieved + from. + + + Retrieve information from the audio media stream. + If not specified, audio is used + by default. + + + Retrieve information from the video media stream. + + + + + + R/O Retrieve RTCP statistics. + + When rtcp is specified, the + statistic parameter must be provided. It specifies + which RTCP statistic parameter to read. + + + Retrieve a summary of all RTCP statistics. + The following data items are returned in a semi-colon + delineated list: + + + Our Synchronization Source identifier + + + Their Synchronization Source identifier + + + Our lost packet count + + + Received packet jitter + + + Received packet count + + + Transmitted packet jitter + + + Transmitted packet count + + + Remote lost packet count + + + Round trip time + + + + + Retrieve a summary of all RTCP Jitter statistics. + The following data items are returned in a semi-colon + delineated list: + + + Our minimum jitter + + + Our max jitter + + + Our average jitter + + + Our jitter standard deviation + + + Their minimum jitter + + + Their max jitter + + + Their average jitter + + + Their jitter standard deviation + + + + + Retrieve a summary of all RTCP packet loss statistics. + The following data items are returned in a semi-colon + delineated list: + + + Our minimum lost packets + + + Our max lost packets + + + Our average lost packets + + + Our lost packets standard deviation + + + Their minimum lost packets + + + Their max lost packets + + + Their average lost packets + + + Their lost packets standard deviation + + + + + Retrieve a summary of all RTCP round trip time information. + The following data items are returned in a semi-colon + delineated list: + + + Minimum round trip time + + + Maximum round trip time + + + Average round trip time + + + Standard deviation round trip time + + + + Transmitted packet count + Received packet count + Transmitted packet jitter + Received packet jitter + Their max jitter + Their minimum jitter + Their average jitter + Their jitter standard deviation + Our max jitter + Our minimum jitter + Our average jitter + Our jitter standard deviation + Transmitted packet loss + Received packet loss + Their max lost packets + Their minimum lost packets + Their average lost packets + Their lost packets standard deviation + Our max lost packets + Our minimum lost packets + Our average lost packets + Our lost packets standard deviation + Round trip time + Maximum round trip time + Minimum round trip time + Average round trip time + Standard deviation round trip time + Our Synchronization Source identifier + Their Synchronization Source identifier + + + + When rtcp is specified, the + media_type parameter may be provided. It specifies + which media stream the chosen RTCP parameter should be retrieved + from. + + + Retrieve information from the audio media stream. + If not specified, audio is used + by default. + + + Retrieve information from the video media stream. + + + + + + R/O The name of the endpoint associated with this channel. + Use the PJSIP_ENDPOINT function to obtain + further endpoint related information. + + + R/O Obtain information about the current PJSIP channel and its + session. + + When pjsip is specified, the + type parameter must be provided. It specifies + which signalling parameter to read. + + + Whether or not the signalling uses a secure transport. + + The signalling uses a non-secure transport. + The signalling uses a secure transport. + + + + The request URI of the INVITE request associated with the creation of this channel. + + + The local URI. + + + The remote URI. + + + The current state of any T.38 fax on this channel. + + T.38 faxing is disabled on this channel. + Asterisk has sent a re-INVITE to the remote end to initiate a T.38 fax. + The remote end has sent a re-INVITE to Asterisk to initiate a T.38 fax. + A T.38 fax session has been enabled. + A T.38 fax session was attempted but was rejected. + + + + On inbound calls, the full IP address and port number that + the INVITE request was received on. On outbound + calls, the full IP address and port number that the INVITE + request was transmitted from. + + + On inbound calls, the full IP address and port number that + the INVITE request was received from. On outbound + calls, the full IP address and port number that the INVITE + request was transmitted to. + + + + + + +***/ + +#include "asterisk.h" + +#include +#include +#include + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/astobj2.h" +#include "asterisk/module.h" +#include "asterisk/acl.h" +#include "asterisk/app.h" +#include "asterisk/channel.h" +#include "asterisk/format.h" +#include "asterisk/pbx.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_session.h" +#include "include/chan_pjsip.h" +#include "include/dialplan_functions.h" + +/*! + * \brief String representations of the T.38 state enum + */ +static const char *t38state_to_string[T38_MAX_ENUM] = { + [T38_DISABLED] = "DISABLED", + [T38_LOCAL_REINVITE] = "LOCAL_REINVITE", + [T38_PEER_REINVITE] = "REMOTE_REINVITE", + [T38_ENABLED] = "ENABLED", + [T38_REJECTED] = "REJECTED", +}; + +/*! + * \internal \brief Handle reading RTP information + */ +static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) +{ + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + struct chan_pjsip_pvt *pvt; + struct ast_sip_session_media *media = NULL; + struct ast_sockaddr addr; + + if (!channel) { + ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); + return -1; + } + + pvt = channel->pvt; + if (!pvt) { + ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan)); + return -1; + } + + if (ast_strlen_zero(type)) { + ast_log(AST_LOG_WARNING, "You must supply a type field for 'rtp' information\n"); + return -1; + } + + if (ast_strlen_zero(field) || !strcmp(field, "audio")) { + media = pvt->media[SIP_MEDIA_AUDIO]; + } else if (!strcmp(field, "video")) { + media = pvt->media[SIP_MEDIA_VIDEO]; + } else { + ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field); + return -1; + } + + if (!media || !media->rtp) { + ast_log(AST_LOG_WARNING, "Channel %s has no %s media/RTP session\n", + ast_channel_name(chan), S_OR(field, "audio")); + return -1; + } + + if (!strcmp(type, "src")) { + ast_rtp_instance_get_local_address(media->rtp, &addr); + ast_copy_string(buf, ast_sockaddr_stringify(&addr), buflen); + } else if (!strcmp(type, "dest")) { + ast_rtp_instance_get_remote_address(media->rtp, &addr); + ast_copy_string(buf, ast_sockaddr_stringify(&addr), buflen); + } else if (!strcmp(type, "direct")) { + ast_copy_string(buf, ast_sockaddr_stringify(&media->direct_media_addr), buflen); + } else if (!strcmp(type, "secure")) { + snprintf(buf, buflen, "%u", media->srtp ? 1 : 0); + } else if (!strcmp(type, "hold")) { + snprintf(buf, buflen, "%u", media->held ? 1 : 0); + } else { + ast_log(AST_LOG_WARNING, "Unknown type field '%s' specified for 'rtp' information\n", type); + return -1; + } + + return 0; +} + +/*! + * \internal \brief Handle reading RTCP information + */ +static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) +{ + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + struct chan_pjsip_pvt *pvt; + struct ast_sip_session_media *media = NULL; + + if (!channel) { + ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); + return -1; + } + + pvt = channel->pvt; + if (!pvt) { + ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan)); + return -1; + } + + if (ast_strlen_zero(type)) { + ast_log(AST_LOG_WARNING, "You must supply a type field for 'rtcp' information\n"); + return -1; + } + + if (ast_strlen_zero(field) || !strcmp(field, "audio")) { + media = pvt->media[SIP_MEDIA_AUDIO]; + } else if (!strcmp(field, "video")) { + media = pvt->media[SIP_MEDIA_VIDEO]; + } else { + ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field); + return -1; + } + + if (!media || !media->rtp) { + ast_log(AST_LOG_WARNING, "Channel %s has no %s media/RTP session\n", + ast_channel_name(chan), S_OR(field, "audio")); + return -1; + } + + if (!strncasecmp(type, "all", 3)) { + enum ast_rtp_instance_stat_field stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY; + + if (!strcasecmp(type, "all_jitter")) { + stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER; + } else if (!strcasecmp(type, "all_rtt")) { + stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT; + } else if (!strcasecmp(type, "all_loss")) { + stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS; + } + + if (!ast_rtp_instance_get_quality(media->rtp, stat_field, buf, buflen)) { + ast_log(AST_LOG_WARNING, "Unable to retrieve 'rtcp' statistics for %s\n", ast_channel_name(chan)); + return -1; + } + } else { + struct ast_rtp_instance_stats stats; + int i; + struct { + const char *name; + enum { INT, DBL } type; + union { + unsigned int *i4; + double *d8; + }; + } lookup[] = { + { "txcount", INT, { .i4 = &stats.txcount, }, }, + { "rxcount", INT, { .i4 = &stats.rxcount, }, }, + { "txjitter", DBL, { .d8 = &stats.txjitter, }, }, + { "rxjitter", DBL, { .d8 = &stats.rxjitter, }, }, + { "remote_maxjitter", DBL, { .d8 = &stats.remote_maxjitter, }, }, + { "remote_minjitter", DBL, { .d8 = &stats.remote_minjitter, }, }, + { "remote_normdevjitter", DBL, { .d8 = &stats.remote_normdevjitter, }, }, + { "remote_stdevjitter", DBL, { .d8 = &stats.remote_stdevjitter, }, }, + { "local_maxjitter", DBL, { .d8 = &stats.local_maxjitter, }, }, + { "local_minjitter", DBL, { .d8 = &stats.local_minjitter, }, }, + { "local_normdevjitter", DBL, { .d8 = &stats.local_normdevjitter, }, }, + { "local_stdevjitter", DBL, { .d8 = &stats.local_stdevjitter, }, }, + { "txploss", INT, { .i4 = &stats.txploss, }, }, + { "rxploss", INT, { .i4 = &stats.rxploss, }, }, + { "remote_maxrxploss", DBL, { .d8 = &stats.remote_maxrxploss, }, }, + { "remote_minrxploss", DBL, { .d8 = &stats.remote_minrxploss, }, }, + { "remote_normdevrxploss", DBL, { .d8 = &stats.remote_normdevrxploss, }, }, + { "remote_stdevrxploss", DBL, { .d8 = &stats.remote_stdevrxploss, }, }, + { "local_maxrxploss", DBL, { .d8 = &stats.local_maxrxploss, }, }, + { "local_minrxploss", DBL, { .d8 = &stats.local_minrxploss, }, }, + { "local_normdevrxploss", DBL, { .d8 = &stats.local_normdevrxploss, }, }, + { "local_stdevrxploss", DBL, { .d8 = &stats.local_stdevrxploss, }, }, + { "rtt", DBL, { .d8 = &stats.rtt, }, }, + { "maxrtt", DBL, { .d8 = &stats.maxrtt, }, }, + { "minrtt", DBL, { .d8 = &stats.minrtt, }, }, + { "normdevrtt", DBL, { .d8 = &stats.normdevrtt, }, }, + { "stdevrtt", DBL, { .d8 = &stats.stdevrtt, }, }, + { "local_ssrc", INT, { .i4 = &stats.local_ssrc, }, }, + { "remote_ssrc", INT, { .i4 = &stats.remote_ssrc, }, }, + { NULL, }, + }; + + if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { + ast_log(AST_LOG_WARNING, "Unable to retrieve 'rtcp' statistics for %s\n", ast_channel_name(chan)); + return -1; + } + + for (i = 0; !ast_strlen_zero(lookup[i].name); i++) { + if (!strcasecmp(type, lookup[i].name)) { + if (lookup[i].type == INT) { + snprintf(buf, buflen, "%u", *lookup[i].i4); + } else { + snprintf(buf, buflen, "%f", *lookup[i].d8); + } + return 0; + } + } + ast_log(AST_LOG_WARNING, "Unrecognized argument '%s' for 'rtcp' information\n", type); + return -1; + } + + return 0; +} + +/*! + * \internal \brief Handle reading signalling information + */ +static int channel_read_pjsip(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) +{ + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + char *buf_copy; + pjsip_dialog *dlg; + + if (!channel) { + ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); + return -1; + } + + dlg = channel->session->inv_session->dlg; + + if (!strcmp(type, "secure")) { + snprintf(buf, buflen, "%u", dlg->secure ? 1 : 0); + } else if (!strcmp(type, "target_uri")) { + pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->target, buf, sizeof(buflen)); + buf_copy = ast_strdupa(buf); + ast_escape_quoted(buf_copy, buf, buflen); + } else if (!strcmp(type, "local_uri")) { + pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->local.info->uri, buf, sizeof(buflen)); + buf_copy = ast_strdupa(buf); + ast_escape_quoted(buf_copy, buf, buflen); + } else if (!strcmp(type, "remote_uri")) { + pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->remote.info->uri, buf, sizeof(buflen)); + buf_copy = ast_strdupa(buf); + ast_escape_quoted(buf_copy, buf, buflen); + } else if (!strcmp(type, "t38state")) { + ast_copy_string(buf, t38state_to_string[channel->session->t38state], buflen); + } else if (!strcmp(type, "local_addr")) { + RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); + struct transport_info_data *transport_data; + + datastore = ast_sip_session_get_datastore(channel->session, "transport_info"); + if (!datastore) { + ast_log(AST_LOG_WARNING, "No transport information for channel %s\n", ast_channel_name(chan)); + return -1; + } + transport_data = datastore->data; + + if (pj_sockaddr_has_addr(&transport_data->local_addr)) { + pj_sockaddr_print(&transport_data->local_addr, buf, buflen, 3); + } + } else if (!strcmp(type, "remote_addr")) { + RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); + struct transport_info_data *transport_data; + + datastore = ast_sip_session_get_datastore(channel->session, "transport_info"); + if (!datastore) { + ast_log(AST_LOG_WARNING, "No transport information for channel %s\n", ast_channel_name(chan)); + return -1; + } + transport_data = datastore->data; + + if (pj_sockaddr_has_addr(&transport_data->remote_addr)) { + pj_sockaddr_print(&transport_data->remote_addr, buf, buflen, 3); + } + } else { + ast_log(AST_LOG_WARNING, "Unrecognized argument '%s' for 'pjsip' information\n", type); + return -1; + } + + return 0; +} + +/*! \brief Struct used to push function arguments to task processor */ +struct pjsip_func_args { + struct ast_channel *chan; + const char *param; + const char *type; + const char *field; + char *buf; + size_t len; + int ret; +}; + +/*! \internal \brief Taskprocessor callback that handles the read on a PJSIP thread */ +static int read_pjsip(void *data) +{ + struct pjsip_func_args *func_args = data; + + if (!strcmp(func_args->param, "rtp")) { + func_args->ret = channel_read_rtp(func_args->chan, func_args->type, + func_args->field, func_args->buf, + func_args->len); + } else if (!strcmp(func_args->param, "rtcp")) { + func_args->ret = channel_read_rtcp(func_args->chan, func_args->type, + func_args->field, func_args->buf, + func_args->len); + } else if (!strcmp(func_args->param, "endpoint")) { + struct ast_sip_channel_pvt *pvt = ast_channel_tech_pvt(func_args->chan); + + if (!pvt) { + ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(func_args->chan)); + return -1; + } + if (!pvt->session || !pvt->session->endpoint) { + ast_log(AST_LOG_WARNING, "Channel %s has no endpoint!\n", ast_channel_name(func_args->chan)); + return -1; + } + snprintf(func_args->buf, func_args->len, "%s", ast_sorcery_object_get_id(pvt->session->endpoint)); + } else if (!strcmp(func_args->param, "pjsip")) { + func_args->ret = channel_read_pjsip(func_args->chan, func_args->type, + func_args->field, func_args->buf, + func_args->len); + } else { + func_args->ret = -1; + } + + return 0; +} + + +int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct pjsip_func_args func_args = { 0, }; + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + char *parse = ast_strdupa(data); + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(param); + AST_APP_ARG(type); + AST_APP_ARG(field); + ); + + /* Check for zero arguments */ + if (ast_strlen_zero(parse)) { + ast_log(LOG_ERROR, "Cannot call %s without arguments\n", cmd); + return -1; + } + + AST_STANDARD_APP_ARGS(args, parse); + + /* Sanity check */ + if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) { + ast_log(LOG_ERROR, "Cannot call %s on a non-PJSIP channel\n", cmd); + return 0; + } + + if (!channel) { + ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); + return -1; + } + + memset(buf, 0, len); + + func_args.chan = chan; + func_args.param = args.param; + func_args.type = args.type; + func_args.field = args.field; + func_args.buf = buf; + func_args.len = len; + if (ast_sip_push_task_synchronous(channel->session->serializer, read_pjsip, &func_args)) { + ast_log(LOG_WARNING, "Unable to read properties of channel %s: failed to push task\n", ast_channel_name(chan)); + return -1; + } + + return func_args.ret; +} + +int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + RAII_VAR(struct ast_str *, dial, NULL, ast_free_ptr); + const char *aor_name; + char *rest; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(endpoint_name); + AST_APP_ARG(aor_name); + AST_APP_ARG(request_user); + ); + + AST_STANDARD_APP_ARGS(args, data); + + if (ast_strlen_zero(args.endpoint_name)) { + ast_log(LOG_WARNING, "An endpoint name must be specified when using the '%s' dialplan function\n", cmd); + return -1; + } else if (!(endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", args.endpoint_name))) { + ast_log(LOG_WARNING, "Specified endpoint '%s' was not found\n", args.endpoint_name); + return -1; + } + + aor_name = S_OR(args.aor_name, endpoint->aors); + + if (ast_strlen_zero(aor_name)) { + ast_log(LOG_WARNING, "No AOR has been provided and no AORs are configured on endpoint '%s'\n", args.endpoint_name); + return -1; + } else if (!(dial = ast_str_create(len))) { + ast_log(LOG_WARNING, "Could not get enough buffer space for dialing contacts\n"); + return -1; + } else if (!(rest = ast_strdupa(aor_name))) { + ast_log(LOG_WARNING, "Could not duplicate provided AORs\n"); + return -1; + } + + while ((aor_name = strsep(&rest, ","))) { + RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); + RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); + struct ao2_iterator it_contacts; + struct ast_sip_contact *contact; + + if (!aor) { + /* If the AOR provided is not found skip it, there may be more */ + continue; + } else if (!(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { + /* No contacts are available, skip it as well */ + continue; + } else if (!ao2_container_count(contacts)) { + /* We were given a container but no contacts are in it... */ + continue; + } + + it_contacts = ao2_iterator_init(contacts, 0); + for (; (contact = ao2_iterator_next(&it_contacts)); ao2_ref(contact, -1)) { + ast_str_append(&dial, -1, "PJSIP/"); + + if (!ast_strlen_zero(args.request_user)) { + ast_str_append(&dial, -1, "%s@", args.request_user); + } + ast_str_append(&dial, -1, "%s/%s&", args.endpoint_name, contact->uri); + } + ao2_iterator_destroy(&it_contacts); + } + + /* Trim the '&' at the end off */ + ast_str_truncate(dial, ast_str_strlen(dial) - 1); + + ast_copy_string(buf, ast_str_buffer(dial), len); + + return 0; +} + +static int media_offer_read_av(struct ast_sip_session *session, char *buf, + size_t len, enum ast_format_type media_type) +{ + int i, size = 0; + struct ast_format fmt; + const char *name; + + for (i = 0; ast_codec_pref_index(&session->override_prefs, i, &fmt); ++i) { + if (AST_FORMAT_GET_TYPE(fmt.id) != media_type) { + continue; + } + + name = ast_getformatname(&fmt); + + if (ast_strlen_zero(name)) { + ast_log(LOG_WARNING, "PJSIP_MEDIA_OFFER unrecognized format %s\n", name); + continue; + } + + /* add one since we'll include a comma */ + size = strlen(name) + 1; + len -= size; + if ((len) < 0) { + break; + } + + /* no reason to use strncat here since we have already ensured buf has + enough space, so strcat can be safely used */ + strcat(buf, name); + strcat(buf, ","); + } + + if (size) { + /* remove the extra comma */ + buf[strlen(buf) - 1] = '\0'; + } + return 0; +} + +struct media_offer_data { + struct ast_sip_session *session; + enum ast_format_type media_type; + const char *value; +}; + +static int media_offer_write_av(void *obj) +{ + struct media_offer_data *data = obj; + int i; + struct ast_format fmt; + /* remove all of the given media type first */ + for (i = 0; ast_codec_pref_index(&data->session->override_prefs, i, &fmt); ++i) { + if (AST_FORMAT_GET_TYPE(fmt.id) == data->media_type) { + ast_codec_pref_remove(&data->session->override_prefs, &fmt); + } + } + ast_format_cap_remove_bytype(data->session->req_caps, data->media_type); + ast_parse_allow_disallow(&data->session->override_prefs, data->session->req_caps, data->value, 1); + + return 0; +} + +int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + + if (!strcmp(data, "audio")) { + return media_offer_read_av(channel->session, buf, len, AST_FORMAT_TYPE_AUDIO); + } else if (!strcmp(data, "video")) { + return media_offer_read_av(channel->session, buf, len, AST_FORMAT_TYPE_VIDEO); + } + + return 0; +} + +int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); + + struct media_offer_data mdata = { + .session = channel->session, + .value = value + }; + + if (!strcmp(data, "audio")) { + mdata.media_type = AST_FORMAT_TYPE_AUDIO; + } else if (!strcmp(data, "video")) { + mdata.media_type = AST_FORMAT_TYPE_VIDEO; + } + + return ast_sip_push_task_synchronous(channel->session->serializer, media_offer_write_av, &mdata); +} diff --git a/channels/pjsip/include/chan_pjsip.h b/channels/pjsip/include/chan_pjsip.h new file mode 100644 index 0000000000..b229a0487f --- /dev/null +++ b/channels/pjsip/include/chan_pjsip.h @@ -0,0 +1,58 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief PJSIP Channel Driver shared data structures + */ + +#ifndef _CHAN_PJSIP_HEADER +#define _CHAN_PJSIP_HEADER + +struct ast_sip_session_media; + +/*! + * \brief Transport information stored in transport_info datastore + */ +struct transport_info_data { + /*! \brief The address that sent the request */ + pj_sockaddr remote_addr; + /*! \brief Our address that received the request */ + pj_sockaddr local_addr; +}; + +/*! + * \brief Positions of various media + */ +enum sip_session_media_position { + /*! \brief First is audio */ + SIP_MEDIA_AUDIO = 0, + /*! \brief Second is video */ + SIP_MEDIA_VIDEO, + /*! \brief Last is the size for media details */ + SIP_MEDIA_SIZE, +}; + +/*! + * \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt + * data structure + */ +struct chan_pjsip_pvt { + /*! \brief The available media sessions */ + struct ast_sip_session_media *media[SIP_MEDIA_SIZE]; +}; + +#endif /* _CHAN_PJSIP_HEADER */ diff --git a/channels/pjsip/include/dialplan_functions.h b/channels/pjsip/include/dialplan_functions.h new file mode 100644 index 0000000000..cbc06f0761 --- /dev/null +++ b/channels/pjsip/include/dialplan_functions.h @@ -0,0 +1,76 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief PJSIP dialplan functions header file + */ + +#ifndef _PJSIP_DIALPLAN_FUNCTIONS +#define _PJSIP_DIALPLAN_FUNCTIONS + +/*! + * \brief CHANNEL function read callback + * \param chan The channel the function is called on + * \param cmd The name of the function + * \param data Arguments passed to the function + * \param buf Out buffer that should be populated with the data + * \param len Size of the buffer + * + * \retval 0 on success + * \retval -1 on failure + */ +int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); + +/*! + * \brief PJSIP_MEDIA_OFFER function write callback + * \param chan The channel the function is called on + * \param cmd The name of the function + * \param data Arguments passed to the function + * \param value Value to be set by the function + * + * \retval 0 on success + * \retval -1 on failure + */ +int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value); + +/*! + * \brief PJSIP_MEDIA_OFFER function read callback + * \param chan The channel the function is called on + * \param cmd The name of the function + * \param data Arguments passed to the function + * \param buf Out buffer that should be populated with the data + * \param len Size of the buffer + * + * \retval 0 on success + * \retval -1 on failure + */ +int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); + +/*! + * \brief PJSIP_DIAL_CONTACTS function read callback + * \param chan The channel the function is called on + * \param cmd The name of the function + * \param data Arguments passed to the function + * \param buf Out buffer that should be populated with the data + * \param len Size of the buffer + * + * \retval 0 on success + * \retval -1 on failure + */ +int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); + +#endif /* _PJSIP_DIALPLAN_FUNCTIONS */ \ No newline at end of file diff --git a/funcs/func_channel.c b/funcs/func_channel.c index ccdbb6e7d3..af9a6a9843 100644 --- a/funcs/func_channel.c +++ b/funcs/func_channel.c @@ -280,6 +280,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Defaults to audio if unspecified. + chan_iax2 provides the following additional options: diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index 7b359cdf1f..615a6214cc 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -52,6 +52,7 @@ enum ast_sip_session_t38state { T38_PEER_REINVITE, /*!< Offered from peer - REINVITE */ T38_ENABLED, /*!< Negotiated (enabled) */ T38_REJECTED, /*!< Refused */ + T38_MAX_ENUM, /*!< Not an actual state; used as max value in the enum */ }; struct ast_sip_session_sdp_handler; diff --git a/main/xmldoc.c b/main/xmldoc.c index 8a54d6f120..7a48c87015 100644 --- a/main/xmldoc.c +++ b/main/xmldoc.c @@ -70,6 +70,7 @@ struct documentation_tree { static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *name, int printname); static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer); +static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer); static int xmldoc_parse_info(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer); static int xmldoc_parse_para(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer); static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *tabs, const char *posttabs, struct ast_str **buffer); @@ -1490,58 +1491,6 @@ static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *ta return ret; } -/*! - * \internal - * \brief Parse an 'info' tag inside an element. - * - * \param node A pointer to the 'info' xml node. - * \param tabs A string to be appended at the beginning of each line being printed - * inside 'buffer' - * \param posttabs Add this string after the content of the element, if one exists - * \param String buffer to put values found inide the info element. - * - * \retval 2 if the information contained a para element, and it returned a value of 2 - * \retval 1 if information was put into the buffer - * \retval 0 if no information was put into the buffer or error - */ -static int xmldoc_parse_info(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer) -{ - const char *tech; - char *internaltabs; - int internal_ret; - int ret = 0; - - if (strcasecmp(ast_xml_node_get_name(node), "info")) { - return ret; - } - - ast_asprintf(&internaltabs, "%s ", tabs); - if (!internaltabs) { - return ret; - } - - tech = ast_xml_get_attribute(node, "tech"); - if (tech) { - ast_str_append(buffer, 0, "%sTechnology: %s\n", internaltabs, tech); - ast_xml_free_attr(tech); - } - - ret = 1; - - for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { - if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) { - xmldoc_parse_enumlist(node, internaltabs, buffer); - } else if ((internal_ret = xmldoc_parse_common_elements(node, internaltabs, posttabs, buffer))) { - if (internal_ret > ret) { - ret = internal_ret; - } - } - } - ast_free(internaltabs); - - return ret; -} - /*! * \internal * \brief Parse an element from the xml documentation. @@ -1829,6 +1778,7 @@ static int xmldoc_parse_enum(struct ast_xml_node *fixnode, const char *tabs, str } xmldoc_parse_enumlist(node, optiontabs, buffer); + xmldoc_parse_parameter(node, optiontabs, buffer); } ast_free(optiontabs); @@ -2051,6 +2001,60 @@ static void xmldoc_parse_parameter(struct ast_xml_node *fixnode, const char *tab ast_free(internaltabs); } +/*! + * \internal + * \brief Parse an 'info' tag inside an element. + * + * \param node A pointer to the 'info' xml node. + * \param tabs A string to be appended at the beginning of each line being printed + * inside 'buffer' + * \param posttabs Add this string after the content of the element, if one exists + * \param String buffer to put values found inide the info element. + * + * \retval 2 if the information contained a para element, and it returned a value of 2 + * \retval 1 if information was put into the buffer + * \retval 0 if no information was put into the buffer or error + */ +static int xmldoc_parse_info(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer) +{ + const char *tech; + char *internaltabs; + int internal_ret; + int ret = 0; + + if (strcasecmp(ast_xml_node_get_name(node), "info")) { + return ret; + } + + ast_asprintf(&internaltabs, "%s ", tabs); + if (!internaltabs) { + return ret; + } + + tech = ast_xml_get_attribute(node, "tech"); + if (tech) { + ast_str_append(buffer, 0, "%sTechnology: %s\n", internaltabs, tech); + ast_xml_free_attr(tech); + } + + ret = 1; + + for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { + if (!strcasecmp(ast_xml_node_get_name(node), "enumlist")) { + xmldoc_parse_enumlist(node, internaltabs, buffer); + } else if (!strcasecmp(ast_xml_node_get_name(node), "parameter")) { + xmldoc_parse_parameter(node, internaltabs, buffer); + } else if ((internal_ret = xmldoc_parse_common_elements(node, internaltabs, posttabs, buffer))) { + if (internal_ret > ret) { + ret = internal_ret; + } + } + } + ast_free(internaltabs); + + return ret; +} + /*! * \internal * \brief Build the arguments for an item diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c index 3c97784a13..afe12505d2 100644 --- a/res/res_pjsip_t38.c +++ b/res/res_pjsip_t38.c @@ -172,6 +172,10 @@ static void t38_change_state(struct ast_sip_session *session, struct ast_sip_ses case T38_LOCAL_REINVITE: /* wait until we get a peer response before responding to local reinvite */ break; + case T38_MAX_ENUM: + /* Well, that shouldn't happen */ + ast_assert(0); + break; } if (parameters.request_response) {