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) {