From a507c73a784d35cd35a67efc85e44714bee9fb60 Mon Sep 17 00:00:00 2001
From: Joshua Colp <jcolp@digium.com>
Date: Mon, 23 Apr 2018 14:04:01 +0000
Subject: [PATCH] rtp: Add support for RTP extension negotiation and
 abs-send-time.

When RTP was originally created it had the ability to place a single
extension in an RTP packet. In practice people wanted to potentially
put multiple extensions in one and so RFC 5285 (obsoleted by RFC
8285) came into existence. This allows RTP extensions to be negotiated
with a unique identifier to be used in the RTP packet, allowing
multiple extensions to be present in the packet.

This change extends the RTP engine API to add support for this. A
user of it can enable extensions and the API provides the ability to
retrieve the information (to construct SDP for example) and to provide
negotiated information (from SDP). The end result is that the RTP
engine can then query to see if the extension has been negotiated and
what unique identifier is to be used. It is then up to the RTP engine
implementation to construct the packet appropriately.

The first extension to use this support is abs-send-time which is
defined in the REMB draft[1] and is a second timestamp placed in an
RTP packet which is for when the packet has left the sending system.
It is used to more accurately determine the available bandwidth.

ASTERISK-27831

[1] https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03

Change-Id: I508deac557867b1e27fc7339be890c8018171588
---
 include/asterisk/rtp_engine.h | 133 +++++++++++++++++
 main/rtp_engine.c             | 260 ++++++++++++++++++++++++++++++++++
 res/res_pjsip_sdp_rtp.c       | 168 +++++++++++++++++++++-
 res/res_rtp_asterisk.c        |  83 ++++++++++-
 4 files changed, 636 insertions(+), 8 deletions(-)

diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 670dbbe15d..98d77734fa 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -533,6 +533,18 @@ enum ast_rtp_dtls_verify {
 	AST_RTP_DTLS_VERIFY_CERTIFICATE = (1 << 1), /*!< Verify the certificate */
 };
 
+/*!
+ * \brief Known RTP extensions
+ */
+enum ast_rtp_extension {
+	/*! Per the RFC 0 should not be used, so we treat it as an unsupported extension placeholder */
+	AST_RTP_EXTENSION_UNSUPPORTED = 0,
+	/*! abs-send-time from https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 */
+	AST_RTP_EXTENSION_ABS_SEND_TIME,
+	/*! The maximum number of known RTP extensions */
+	AST_RTP_EXTENSION_MAX,
+};
+
 /*! \brief DTLS configuration structure */
 struct ast_rtp_dtls_cfg {
 	unsigned int enabled:1;                /*!< Whether DTLS support is enabled or not */
@@ -654,6 +666,8 @@ struct ast_rtp_engine {
 	struct ast_rtp_engine_ice *ice;
 	/*! Callback to pointer for optional DTLS SRTP support */
 	struct ast_rtp_engine_dtls *dtls;
+	/*! Callback to enable an RTP extension (returns non-zero if supported) */
+	int (*extension_enable)(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
 	/*! Linked list information */
 	AST_RWLIST_ENTRY(ast_rtp_engine) entry;
 };
@@ -720,6 +734,22 @@ struct ast_rtp_glue {
 	AST_RWLIST_ENTRY(ast_rtp_glue) entry;
 };
 
+/*!
+ * \brief Directions for RTP extensions
+ */
+enum ast_rtp_extension_direction {
+	/*! The extension is not negotiated and is not flowing */
+	AST_RTP_EXTENSION_DIRECTION_NONE,
+	/*! Send and receive */
+	AST_RTP_EXTENSION_DIRECTION_SENDRECV,
+	/*! Send only */
+	AST_RTP_EXTENSION_DIRECTION_SENDONLY,
+	/*! Receive only */
+	AST_RTP_EXTENSION_DIRECTION_RECVONLY,
+	/*! Negotiated but not sending or receiving */
+	AST_RTP_EXTENSION_DIRECTION_INACTIVE,
+};
+
 /*!
  * \brief Allocation routine for \ref ast_rtp_payload_type
  *
@@ -1246,6 +1276,109 @@ int ast_rtp_instance_get_prop(struct ast_rtp_instance *instance, enum ast_rtp_pr
  */
 struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *instance);
 
+/*!
+ * \brief Enable support for an RTP extension on an instance
+ *
+ * \param instance The RTP instance to enable the extension on
+ * \param id The unique local identifier to use for this extension (-1 to have one auto selected)
+ * \param extension The RTP extension
+ * \param direction The initial direction that the RTP extension should be used in
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \since 15.5.0
+ */
+int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension,
+	enum ast_rtp_extension_direction direction);
+
+/*!
+ * \brief Negotiate received RTP extension information
+ *
+ * \param instance The RTP instance to set the extension on
+ * \param id The local identifier for the extension
+ * \param direction The direction that the extension should be used in
+ * \param uri The unique URI for the extension
+ * \param attributes Attributes specific to this extension (if NULL or empty then no attributes)
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \since 15.5.0
+ */
+int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction,
+	const char *uri, const char *attributes);
+
+/*!
+ * \brief Clear negotiated RTP extension information
+ *
+ * \param instance The RTP instance to clear negotiated extension information on
+ */
+void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance);
+
+/*!
+ * \brief Retrieve the id for an RTP extension
+ *
+ * \param instance The RTP instance to retrieve the id from
+ * \param extension The RTP extension
+ *
+ * \retval -1 not negotiated
+ * \retval id if negotiated
+ *
+ * \since 15.5.0
+ */
+int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
+
+/*!
+ * \brief Get the number of known unique identifiers
+ *
+ * \param instance The RTP instance to retrieve the count from
+ *
+ * \return the number of known unique identifiers
+ *
+ * \since 15.5.0
+ */
+size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance);
+
+/*!
+ * \brief Retrieve the extension for an RTP extension id
+ *
+ * \param instance The RTP instance to retrieve the extension from
+ * \param id The negotiated RTP extension id
+ *
+ * \retval extension the extension that maps to the id
+ *
+ * \since 15.5.0
+ *
+ * \note This will return AST_RTP_EXTENSION_UNSUPPORTED if an extension was proposed for this unique identifier
+ * but it is not supported or if the unique identifier is unused.
+ */
+enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id);
+
+/*!
+ * \brief Retrieve the negotiated direction for an RTP extension id
+ *
+ * \param instance The RTP instance to retrieve the direction from
+ * \param id The negotiated RTP extension id
+ *
+ * \retval direction the direction that has been negotiated
+ *
+ * \since 15.5.0
+ */
+enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id);
+
+/*!
+ * \brief Retrieve the URI for an RTP extension id
+ *
+ * \param instance The RTP instance to retrieve the direction from
+ * \param id The negotiated RTP extension id
+ *
+ * \retval uri The URI for the RTP extension
+ *
+ * \since 15.5.0
+ */
+const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id);
+
 /*!
  * \brief Initialize an RTP codecs structure
  *
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 3c8f6e82fa..c11027af2f 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -177,6 +177,14 @@
 struct ast_srtp_res *res_srtp = NULL;
 struct ast_srtp_policy_res *res_srtp_policy = NULL;
 
+/*! Structure that contains extmap negotiation information */
+struct rtp_extmap {
+	/*! The RTP extension */
+	enum ast_rtp_extension extension;
+	/*! The current negotiated direction */
+	enum ast_rtp_extension_direction direction;
+};
+
 /*! Structure that represents an RTP session (instance) */
 struct ast_rtp_instance {
 	/*! Engine that is handling this RTP instance */
@@ -213,6 +221,20 @@ struct ast_rtp_instance {
 	time_t last_tx;
 	/*! Time of last packet received */
 	time_t last_rx;
+	/*! Enabled RTP extensions */
+	AST_VECTOR(, enum ast_rtp_extension_direction) extmap_enabled;
+	/*! Negotiated RTP extensions (using index based on extension) */
+	AST_VECTOR(, int) extmap_negotiated;
+	/*! Negotiated RTP extensions (using index based on unique id) */
+	AST_VECTOR(, struct rtp_extmap) extmap_unique_ids;
+};
+
+/*!
+ * \brief URIs for known RTP extensions
+ */
+static const char * const rtp_extension_uris[AST_RTP_EXTENSION_MAX] = {
+	[AST_RTP_EXTENSION_UNSUPPORTED]		= "",
+	[AST_RTP_EXTENSION_ABS_SEND_TIME]	= "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
 };
 
 /*! List of RTP engines that are currently registered */
@@ -408,6 +430,10 @@ static void instance_destructor(void *obj)
 
 	ast_rtp_codecs_payloads_destroy(&instance->codecs);
 
+	AST_VECTOR_FREE(&instance->extmap_enabled);
+	AST_VECTOR_FREE(&instance->extmap_negotiated);
+	AST_VECTOR_FREE(&instance->extmap_unique_ids);
+
 	/* Drop our engine reference */
 	ast_module_unref(instance->engine->mod);
 
@@ -474,6 +500,14 @@ struct ast_rtp_instance *ast_rtp_instance_new(const char *engine_name,
 		return NULL;
 	}
 
+	/* Initialize RTP extension support */
+	if (AST_VECTOR_INIT(&instance->extmap_enabled, 0) ||
+		AST_VECTOR_INIT(&instance->extmap_negotiated, 0) ||
+		AST_VECTOR_INIT(&instance->extmap_unique_ids, 0)) {
+		ao2_ref(instance, -1);
+		return NULL;
+	}
+
 	ast_debug(1, "Using engine '%s' for RTP instance '%p'\n", engine->name, instance);
 
 	/*
@@ -680,6 +714,232 @@ struct ast_rtp_codecs *ast_rtp_instance_get_codecs(struct ast_rtp_instance *inst
 	return &instance->codecs;
 }
 
+int ast_rtp_instance_extmap_enable(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension extension,
+	enum ast_rtp_extension_direction direction)
+{
+	struct rtp_extmap extmap = {
+		.extension = extension,
+		.direction = direction,
+	};
+
+	ao2_lock(instance);
+
+	if (!instance->engine->extension_enable || !instance->engine->extension_enable(instance, extension)) {
+		ao2_unlock(instance);
+		return 0;
+	}
+
+	/* We store enabled extensions separately so we can easily do negotiation */
+	if (AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, direction)) {
+		ao2_unlock(instance);
+		return -1;
+	}
+
+	if (id <= 0) {
+		/* We find a free unique identifier for this extension by just appending it to the
+		 * vector of unique ids. The size of the vector will become its unique identifier.
+		 * As well when we are asking for information on the extensions it will be returned,
+		 * allowing it to be added to the SDP offer.
+		 */
+		if (AST_VECTOR_APPEND(&instance->extmap_unique_ids, extmap)) {
+			AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);
+			ao2_unlock(instance);
+			return -1;
+		}
+		id = AST_VECTOR_SIZE(&instance->extmap_unique_ids);
+	} else {
+		/* Otherwise we put it precisely where they want it */
+		if (AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap)) {
+			AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);
+			ao2_unlock(instance);
+			return -1;
+		}
+	}
+
+	/* Now that we have an id add the extension to here */
+	if (AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id)) {
+		extmap.extension = AST_RTP_EXTENSION_UNSUPPORTED;
+		extmap.direction = AST_RTP_EXTENSION_DIRECTION_NONE;
+		AST_VECTOR_REPLACE(&instance->extmap_enabled, extension, AST_RTP_EXTENSION_DIRECTION_NONE);
+		AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap);
+		ao2_unlock(instance);
+		return -1;
+	}
+
+	ao2_unlock(instance);
+
+	return 0;
+}
+
+/*! \brief Helper function which negotiates two RTP extension directions to get our current direction */
+static enum ast_rtp_extension_direction rtp_extmap_negotiate_direction(enum ast_rtp_extension_direction ours,
+	enum ast_rtp_extension_direction theirs)
+{
+	if (theirs == AST_RTP_EXTENSION_DIRECTION_NONE || ours == AST_RTP_EXTENSION_DIRECTION_NONE) {
+		/* This should not occur but if it does tolerate either side not having this extension
+		 * in use.
+		 */
+		return AST_RTP_EXTENSION_DIRECTION_NONE;
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_INACTIVE) {
+		/* Inactive is always inactive on our side */
+		return AST_RTP_EXTENSION_DIRECTION_INACTIVE;
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDRECV) {
+		return ours;
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_SENDONLY) {
+		/* If they are send only then we become recvonly if we are configured as sendrecv or recvonly */
+		if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_RECVONLY) {
+			return AST_RTP_EXTENSION_DIRECTION_RECVONLY;
+		}
+	} else if (theirs == AST_RTP_EXTENSION_DIRECTION_RECVONLY) {
+		/* If they are recv only then we become sendonly if we are configured as sendrecv or sendonly */
+		if (ours == AST_RTP_EXTENSION_DIRECTION_SENDRECV || ours == AST_RTP_EXTENSION_DIRECTION_SENDONLY) {
+			return AST_RTP_EXTENSION_DIRECTION_SENDONLY;
+		}
+	}
+
+	return AST_RTP_EXTENSION_DIRECTION_NONE;
+}
+
+int ast_rtp_instance_extmap_negotiate(struct ast_rtp_instance *instance, int id, enum ast_rtp_extension_direction direction,
+	const char *uri, const char *attributes)
+{
+	/* 'attributes' is currently unused but exists in the API to ensure it does not need to be altered
+	 * in the future in case we need to use it.
+	 */
+	int idx;
+	enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED;
+
+	/* Per the RFC the identifier has to be 1 or above */
+	if (id < 1) {
+		return -1;
+	}
+
+	/* Convert the provided URI to the internal representation */
+	for (idx = 0; idx < ARRAY_LEN(rtp_extension_uris); ++idx) {
+		if (!strcasecmp(rtp_extension_uris[idx], uri)) {
+			extension = idx;
+			break;
+		}
+	}
+
+	ao2_lock(instance);
+	/* We only accept the extension if it is enabled */
+	if (extension < AST_VECTOR_SIZE(&instance->extmap_enabled) &&
+		AST_VECTOR_GET(&instance->extmap_enabled, extension) != AST_RTP_EXTENSION_DIRECTION_NONE) {
+		struct rtp_extmap extmap = {
+			.extension = extension,
+			.direction = rtp_extmap_negotiate_direction(AST_VECTOR_GET(&instance->extmap_enabled, extension), direction),
+		};
+
+		/* If the direction negotiation failed then don't accept or use this extension */
+		if (extmap.direction != AST_RTP_EXTENSION_DIRECTION_NONE) {
+			if (extension != AST_RTP_EXTENSION_UNSUPPORTED) {
+				AST_VECTOR_REPLACE(&instance->extmap_negotiated, extension, id);
+			}
+			AST_VECTOR_REPLACE(&instance->extmap_unique_ids, id - 1, extmap);
+		}
+	}
+	ao2_unlock(instance);
+
+	return 0;
+}
+
+void ast_rtp_instance_extmap_clear(struct ast_rtp_instance *instance)
+{
+	static const struct rtp_extmap extmap_none = {
+		.extension = AST_RTP_EXTENSION_UNSUPPORTED,
+		.direction = AST_RTP_EXTENSION_DIRECTION_NONE,
+	};
+	int idx;
+
+	ao2_lock(instance);
+
+	/* Clear both the known unique ids and the negotiated extensions as we are about to have
+	 * new results set on us.
+	 */
+	for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_unique_ids); ++idx) {
+		AST_VECTOR_REPLACE(&instance->extmap_unique_ids, idx, extmap_none);
+	}
+
+	for (idx = 0; idx < AST_VECTOR_SIZE(&instance->extmap_negotiated); ++idx) {
+		AST_VECTOR_REPLACE(&instance->extmap_negotiated, idx, -1);
+	}
+
+	ao2_unlock(instance);
+}
+
+int ast_rtp_instance_extmap_get_id(struct ast_rtp_instance *instance, enum ast_rtp_extension extension)
+{
+	int id = -1;
+
+	ao2_lock(instance);
+	if (extension < AST_VECTOR_SIZE(&instance->extmap_negotiated)) {
+		id = AST_VECTOR_GET(&instance->extmap_negotiated, extension);
+	}
+	ao2_unlock(instance);
+
+	return id;
+}
+
+size_t ast_rtp_instance_extmap_count(struct ast_rtp_instance *instance)
+{
+	size_t count;
+
+	ao2_lock(instance);
+	count = AST_VECTOR_SIZE(&instance->extmap_unique_ids);
+	ao2_unlock(instance);
+
+	return count;
+}
+
+enum ast_rtp_extension ast_rtp_instance_extmap_get_extension(struct ast_rtp_instance *instance, int id)
+{
+	enum ast_rtp_extension extension = AST_RTP_EXTENSION_UNSUPPORTED;
+
+	ao2_lock(instance);
+
+	/* The local unique identifier starts at '1' so the highest unique identifier
+	 * can be the actual size of the vector. We compensate (as it is 0 index based)
+	 * by dropping it down to 1 to get the correct information.
+	 */
+	if (0 < id && id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) {
+		struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1);
+
+		extension = extmap->extension;
+	}
+	ao2_unlock(instance);
+
+	return extension;
+}
+
+enum ast_rtp_extension_direction ast_rtp_instance_extmap_get_direction(struct ast_rtp_instance *instance, int id)
+{
+	enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_NONE;
+
+	ao2_lock(instance);
+
+	if (0 < id && id <= AST_VECTOR_SIZE(&instance->extmap_unique_ids)) {
+		struct rtp_extmap *extmap = AST_VECTOR_GET_ADDR(&instance->extmap_unique_ids, id - 1);
+
+		direction = extmap->direction;
+	}
+	ao2_unlock(instance);
+
+	return direction;
+}
+
+const char *ast_rtp_instance_extmap_get_uri(struct ast_rtp_instance *instance, int id)
+{
+	enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(instance, id);
+
+	if (extension == AST_RTP_EXTENSION_UNSUPPORTED ||
+		(unsigned int)extension >= ARRAY_LEN(rtp_extension_uris)) {
+		return NULL;
+	}
+
+	return rtp_extension_uris[extension];
+}
+
 int ast_rtp_codecs_payloads_initialize(struct ast_rtp_codecs *codecs)
 {
 	int res;
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index 14ed3b1863..727aeb0dec 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -151,8 +151,57 @@ static void enable_rtcp(struct ast_sip_session *session, struct ast_sip_session_
 	ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RTCP, rtcp_type);
 }
 
+/*!
+ * \brief Enable an RTP extension on an RTP session.
+ */
+static void enable_rtp_extension(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	enum ast_rtp_extension extension, enum ast_rtp_extension_direction direction,
+	const pjmedia_sdp_session *sdp)
+{
+	int id = -1;
+
+	/* For a bundle group the local unique identifier space is shared across all streams within
+	 * it.
+	 */
+	if (session_media->bundle_group != -1) {
+		int index;
+
+		for (index = 0; index < sdp->media_count; ++index) {
+			struct ast_sip_session_media *other_session_media;
+			int other_id;
+
+			if (index >= AST_VECTOR_SIZE(&session->pending_media_state->sessions)) {
+				break;
+			}
+
+			other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+			if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) {
+				continue;
+			}
+
+			other_id = ast_rtp_instance_extmap_get_id(other_session_media->rtp, extension);
+			if (other_id == -1) {
+				/* Worst case we have to fall back to the highest available free local unique identifier
+				 * for the bundle group.
+				 */
+				other_id = ast_rtp_instance_extmap_count(other_session_media->rtp) + 1;
+				if (id < other_id) {
+					id = other_id;
+				}
+				continue;
+			}
+
+			id = other_id;
+			break;
+		}
+	}
+
+	ast_rtp_instance_extmap_enable(session_media->rtp, id, extension, direction);
+}
+
 /*! \brief Internal function which creates an RTP instance */
-static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+	const pjmedia_sdp_session *sdp)
 {
 	struct ast_rtp_engine_ice *ice;
 	struct ast_sockaddr temp_media_address;
@@ -223,6 +272,7 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
 		ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_RECV, session->endpoint->media.webrtc);
 		ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_SEND, session->endpoint->media.webrtc);
 		ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_REMB, session->endpoint->media.webrtc);
+		enable_rtp_extension(session, session_media, AST_RTP_EXTENSION_ABS_SEND_TIME, AST_RTP_EXTENSION_DIRECTION_SENDRECV, sdp);
 		if (session->endpoint->media.tos_video || session->endpoint->media.cos_video) {
 			ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,
 					session->endpoint->media.cos_video, "SIP RTP Video");
@@ -1101,6 +1151,113 @@ static void add_rtcp_fb_to_stream(struct ast_sip_session *session,
 	pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
 }
 
+static void add_extmap_to_stream(struct ast_sip_session *session,
+	struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+	int idx;
+	char extmap_value[256];
+
+	if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {
+		return;
+	}
+
+	/* RTP extension local unique identifiers start at '1' */
+	for (idx = 1; idx <= ast_rtp_instance_extmap_count(session_media->rtp); ++idx) {
+		enum ast_rtp_extension extension = ast_rtp_instance_extmap_get_extension(session_media->rtp, idx);
+		const char *direction_str = "";
+		pj_str_t stmp;
+		pjmedia_sdp_attr *attr;
+
+		/* If this is an unsupported RTP extension we can't place it into the SDP */
+		if (extension == AST_RTP_EXTENSION_UNSUPPORTED) {
+			continue;
+		}
+
+		switch (ast_rtp_instance_extmap_get_direction(session_media->rtp, idx)) {
+		case AST_RTP_EXTENSION_DIRECTION_SENDRECV:
+			/* Lack of a direction indicates sendrecv, so we leave it out */
+			direction_str = "";
+			break;
+		case AST_RTP_EXTENSION_DIRECTION_SENDONLY:
+			direction_str = "/sendonly";
+			break;
+		case AST_RTP_EXTENSION_DIRECTION_RECVONLY:
+			direction_str = "/recvonly";
+			break;
+		case AST_RTP_EXTENSION_DIRECTION_NONE:
+			/* It is impossible for a "none" direction extension to be negotiated but just in case
+			 * we treat it as inactive.
+			 */
+		case AST_RTP_EXTENSION_DIRECTION_INACTIVE:
+			direction_str = "/inactive";
+			break;
+		}
+
+		snprintf(extmap_value, sizeof(extmap_value), "%d%s %s", idx, direction_str,
+			ast_rtp_instance_extmap_get_uri(session_media->rtp, idx));
+		attr = pjmedia_sdp_attr_create(pool, "extmap", pj_cstr(&stmp, extmap_value));
+		pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+	}
+}
+
+/*! \brief Function which processes extmap attributes in a stream */
+static void process_extmap_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+				   const struct pjmedia_sdp_media *remote_stream)
+{
+	int index;
+
+	if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {
+		return;
+	}
+
+	ast_rtp_instance_extmap_clear(session_media->rtp);
+
+	for (index = 0; index < remote_stream->attr_count; ++index) {
+		pjmedia_sdp_attr *attr = remote_stream->attr[index];
+		char attr_value[pj_strlen(&attr->value) + 1];
+		char *uri;
+		int id;
+		char direction_str[10] = "";
+		char *attributes;
+		enum ast_rtp_extension_direction direction = AST_RTP_EXTENSION_DIRECTION_SENDRECV;
+
+		/* We only care about extmap attributes */
+		if (pj_strcmp2(&attr->name, "extmap")) {
+			continue;
+		}
+
+		ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
+
+		/* Split the combined unique identifier and direction away from the URI and attributes for easier parsing */
+		uri = strchr(attr_value, ' ');
+		if (ast_strlen_zero(uri)) {
+			continue;
+		}
+		*uri++ = '\0';
+
+		if ((sscanf(attr_value, "%30d%9s", &id, direction_str) < 1) || (id < 1)) {
+			/* We require at a minimum the unique identifier */
+			continue;
+		}
+
+		/* Convert from the string to the internal representation */
+		if (!strcasecmp(direction_str, "/sendonly")) {
+			direction = AST_RTP_EXTENSION_DIRECTION_SENDONLY;
+		} else if (!strcasecmp(direction_str, "/recvonly")) {
+			direction = AST_RTP_EXTENSION_DIRECTION_RECVONLY;
+		} else if (!strcasecmp(direction_str, "/inactive")) {
+			direction = AST_RTP_EXTENSION_DIRECTION_INACTIVE;
+		}
+
+		attributes = strchr(uri, ' ');
+		if (!ast_strlen_zero(attributes)) {
+			*attributes++ = '\0';
+		}
+
+		ast_rtp_instance_extmap_negotiate(session_media->rtp, id, direction, uri, attributes);
+	}
+}
+
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
@@ -1139,11 +1296,12 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
 	}
 
 	/* Using the connection information create an appropriate RTP instance */
-	if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media, sdp)) {
 		return -1;
 	}
 
 	process_ssrc_attributes(session, session_media, stream);
+	process_extmap_attributes(session, session_media, stream);
 	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
 	if (session_media_transport == session_media || !session_media->bundled) {
@@ -1377,7 +1535,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 		return 1;
 	}
 
-	if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media, sdp)) {
 		return -1;
 	}
 
@@ -1602,6 +1760,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 	add_ssrc_to_stream(session, session_media, pool, media);
 	add_msid_to_stream(session, session_media, pool, media, stream);
 	add_rtcp_fb_to_stream(session, session_media, pool, media);
+	add_extmap_to_stream(session, session_media, pool, media);
 
 	/* Add the media stream to the SDP */
 	sdp->media[sdp->media_count++] = media;
@@ -1676,11 +1835,12 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
 	}
 
 	/* Create an RTP instance if need be */
-	if (!session_media->rtp && create_rtp(session, session_media)) {
+	if (!session_media->rtp && create_rtp(session, session_media, local)) {
 		return -1;
 	}
 
 	process_ssrc_attributes(session, session_media, remote_stream);
+	process_extmap_attributes(session, session_media, remote_stream);
 
 	session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 5579914afd..deaca301f7 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -550,6 +550,7 @@ static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance);
 static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance);
 static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc);
 static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
+static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
 static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
 
 #ifdef HAVE_OPENSSL_SRTP
@@ -2248,6 +2249,7 @@ static struct ast_rtp_engine asterisk_rtp_engine = {
 	.cname_get = ast_rtp_get_cname,
 	.set_remote_ssrc = ast_rtp_set_remote_ssrc,
 	.set_stream_num = ast_rtp_set_stream_num,
+	.extension_enable = ast_rtp_extension_enable,
 	.bundle = ast_rtp_bundle,
 };
 
@@ -4281,6 +4283,20 @@ static int ast_rtcp_write(const void *data)
 	return res;
 }
 
+static void put_unaligned_time24(void *p, uint32_t time_msw, uint32_t time_lsw)
+{
+	unsigned char *cp = p;
+	uint32_t datum;
+
+	/* Convert the time to 6.18 format */
+	datum = (time_msw << 18) & 0x00fc0000;
+	datum |= (time_lsw >> 14) & 0x0003ffff;
+
+	cp[0] = datum >> 16;
+	cp[1] = datum >> 8;
+	cp[2] = datum;
+}
+
 /*! \pre instance is locked */
 static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *frame, int codec)
 {
@@ -4398,14 +4414,46 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 
 	/* If we know the remote address construct a packet and send it out */
 	if (!ast_sockaddr_isnull(&remote_address)) {
-		int hdrlen = 12, res, ice;
-		int packet_len = frame->datalen + hdrlen;
-		unsigned char *rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);
+		int hdrlen = 12;
+		int res;
+		int ice;
+		int ext = 0;
+		int abs_send_time_id;
+		int packet_len;
+		unsigned char *rtpheader;
 
-		put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (seqno) | (mark << 23)));
+		/* If the abs-send-time extension has been negotiated determine how much space we need */
+		abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME);
+		if (abs_send_time_id != -1) {
+			/* 4 bytes for the shared information, 1 byte for identifier, 3 bytes for abs-send-time */
+			hdrlen += 8;
+			ext = 1;
+		}
+
+		packet_len = frame->datalen + hdrlen;
+		rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);
+
+		put_unaligned_uint32(rtpheader, htonl((2 << 30) | (ext << 28) | (codec << 16) | (seqno) | (mark << 23)));
 		put_unaligned_uint32(rtpheader + 4, htonl(rtp->lastts));
 		put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc));
 
+		/* We assume right now that we will only ever have the abs-send-time extension in the packet
+		 * which simplifies things a bit.
+		 */
+		if (abs_send_time_id != -1) {
+			unsigned int now_msw;
+			unsigned int now_lsw;
+
+			/* This happens before being placed into the retransmission buffer so that when we
+			 * retransmit we only have to update the timestamp, not everything else.
+			 */
+			put_unaligned_uint32(rtpheader + 12, htonl((0xBEDE << 16) | 1));
+			rtpheader[16] = (abs_send_time_id << 4) | 2;
+
+			timeval2ntp(ast_tvnow(), &now_msw, &now_lsw);
+			put_unaligned_time24(rtpheader + 17, now_msw, now_lsw);
+		}
+
 		/* If retransmissions are enabled, we need to store this packet for future use */
 		if (rtp->send_buffer) {
 			struct ast_rtp_rtcp_nack_payload *payload;
@@ -5262,12 +5310,20 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned
 	unsigned int pid;	/* Packet ID which refers to seqno of lost packet */
 	unsigned int blp;	/* Bitmask of following lost packets */
 	struct ast_sockaddr remote_address = { {0,} };
+	int abs_send_time_id;
+	unsigned int now_msw = 0;
+	unsigned int now_lsw = 0;
 
 	if (!rtp->send_buffer) {
 		ast_debug(1, "Tried to handle NACK request, but we don't have a RTP packet storage!\n");
 		return res;
 	}
 
+	abs_send_time_id = ast_rtp_instance_extmap_get_id(instance, AST_RTP_EXTENSION_ABS_SEND_TIME);
+	if (abs_send_time_id != -1) {
+		timeval2ntp(ast_tvnow(), &now_msw, &now_lsw);
+	}
+
 	ast_rtp_instance_get_remote_address(instance, &remote_address);
 
 	/*
@@ -5280,6 +5336,12 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned
 		/* We know the remote end is missing this packet. Go ahead and send it if we still have it. */
 		payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, pid);
 		if (payload) {
+			if (abs_send_time_id != -1) {
+				/* On retransmission we need to update the timestamp within the packet, as it
+				 * is supposed to contain when the packet was actually sent.
+				 */
+				put_unaligned_time24(payload->buf + 17, now_msw, now_lsw);
+			}
 			res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice);
 		} else {
 			ast_debug(1, "Received NACK request for RTP packet with seqno %d, but we don't have it\n", pid);
@@ -5298,6 +5360,9 @@ static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned
 				unsigned int seqno = (pid + blp_index) % 65536;
 				payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, seqno);
 				if (payload) {
+					if (abs_send_time_id != -1) {
+						put_unaligned_time24(payload->buf + 17, now_msw, now_lsw);
+					}
 					res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice);
 				} else {
 					ast_debug(1, "Remote end also requested RTP packet with seqno %d, but we don't have it\n", seqno);
@@ -7158,6 +7223,16 @@ static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream
 	rtp->stream_num = stream_num;
 }
 
+static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension)
+{
+	switch (extension) {
+	case AST_RTP_EXTENSION_ABS_SEND_TIME:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
 /*! \pre child is locked */
 static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)
 {
-- 
GitLab