[Asterisk-code-review] ACN: res_pjsip endpoint options (asterisk[master])

George Joseph asteriskteam at digium.com
Wed Jul 8 13:45:20 CDT 2020


George Joseph has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/14629 )

Change subject: ACN: res_pjsip endpoint options
......................................................................

ACN: res_pjsip endpoint options

This commit adds the endpoint options required to control
Advanced Codec Negotiation.

incoming_offer_codec_prefs
outgoing_offer_codec_prefs
incoming_answer_codec_prefs
outgoing_answer_codec_prefs

The documentation may need tweaking and some additional edits
added, especially for the "answer" prefs.  That'll be handled
when things finalize.

This commit is safe to merge as it doens't alter any existing
functionality nor does it alter the previous codec negotiation
work which may now be obsolete.

Change-Id: I920ba925d7dd36430dfd2ebd9d82d23f123d0e11
---
M configs/samples/pjsip.conf.sample
A contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
M include/asterisk/res_pjsip.h
M res/res_pjsip.c
M res/res_pjsip/pjsip_configuration.c
5 files changed, 434 insertions(+), 0 deletions(-)

Approvals:
  Kevin Harwell: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved; Approved for Submit



diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index a559dfa..cdb585d 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -839,6 +839,54 @@
                            ; at the end of the joint list.
                            ; remote_first - Include only the first codec in
                            ; the remote list.
+;incoming_offer_codec_prefs=; This is a string that describes how the codecs
+                            ; specified on an incoming SDP offer (pending) are
+                            ; reconciled with the codecs specified on an endpoint
+                            ; (configured) before being sent to the Asterisk core.
+                            ; The string actually specifies 4 name:value pair
+                            ; parameters separated by commas. Whitespace is
+                            ; ignored and they may be specified in any order.
+                            ; prefer: <pending | configured>,
+                            ; operation: <intersect | only_preferred
+                            ;    | only_nonpreferred>,
+                            ; keep: <first | all>,
+                            ; transcode: <allow | prevent>
+;outgoing_offer_codec_prefs=; This is a string that describes how the codecs
+                            ; specified in the topology that comes from the
+                            ; Asterisk core (pending) are reconciled with the
+                            ; codecs specified on an endpoint (configured)
+                            ; when sending an SDP offer.
+                            ; The string actually specifies 4 name:value pair
+                            ; parameters separated by commas. Whitespace is
+                            ; ignored and they may be specified in any order.
+                            ; prefer: <pending | configured>,
+                            ; operation: <intersect | union
+                            ;    | only_preferred | only_nonpreferred>,
+                            ; keep: <first | all>,
+                            ; transcode: <allow | prevent>
+;incoming_answer_codec_prefs=; This is a string that describes how the codecs
+                             ; specified in an incoming SDP answer (pending)
+                             ; are reconciled with the codecs specified on an
+                             ; endpoint (configured) when receiving an SDP
+                             ; answer.
+                             ; The string actually specifies 4 name:value pair
+                             ; parameters separated by commas. Whitespace is
+                             ; ignored and they may be specified in any order.
+                             ; prefer: <pending | configured>,
+                             ; operation: <intersect | union
+                             ;    | only_preferred | only_nonpreferred>,
+                             ; keep: <first | all>
+;outgoing_answer_codec_prefs=; This is a string that describes how the codecs
+                             ; that come from the core (pending) are reconciled
+                             ; with the codecs specified on an endpoint
+                             ; (configured) when sending an SDP answer.
+                             ; The string actually specifies 4 name:value pair
+                             ; parameters separated by commas. Whitespace is
+                             ; ignored and they may be specified in any order.
+                             ; prefer: <pending | configured>,
+                             ; operation: <intersect | union
+                             ;    | only_preferred | only_nonpreferred>,
+                             ; keep: <first | all>
 ;preferred_codec_only=no   ; Respond to a SIP invite with the single most
                            ; preferred codec rather than advertising all joint
                            ; codec capabilities. This limits the other side's
diff --git a/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py b/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
new file mode 100644
index 0000000..241185a
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
@@ -0,0 +1,29 @@
+"""Add pjsip endpoint ACN options
+
+Revision ID: b80485ff4dd0
+Revises: fbb7766f17bc
+Create Date: 2020-07-06 08:29:53.974820
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'b80485ff4dd0'
+down_revision = '79290b511e4b'
+
+from alembic import op
+import sqlalchemy as sa
+
+max_value_length = 128
+
+def upgrade():
+    op.add_column('ps_endpoints', sa.Column('incoming_offer_codec_prefs', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('outgoing_offer_codec_prefs', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('incoming_answer_codec_prefs', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('outgoing_answer_codec_prefs', sa.String(max_value_length)))
+
+
+def downgrade():
+    op.drop_column('ps_endpoints', 'incoming_offer_codecs_prefs')
+    op.drop_column('ps_endpoints', 'outgoing_offer_codecs_prefs')
+    op.drop_column('ps_endpoints', 'incoming_answer_codecs_prefs')
+    op.drop_column('ps_endpoints', 'outgoing_answer_codecs_prefs')
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 2993103..0ca29ff 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -49,6 +49,7 @@
 /* Needed for ast_sip_for_each_channel_snapshot struct */
 #include "asterisk/stasis_channels.h"
 #include "asterisk/stasis_endpoints.h"
+#include "asterisk/stream.h"
 
 #define PJSIP_MINVERSION(m,n,p) (((m << 24) | (n << 16) | (p << 8)) >= PJ_VERSION_NUM)
 
@@ -802,6 +803,14 @@
 	struct ast_flags incoming_call_offer_pref;
 	/*! Codec preference for an outgoing offer */
 	struct ast_flags outgoing_call_offer_pref;
+	/*! Codec negotiation prefs for incoming offers */
+	struct ast_stream_codec_negotiation_prefs incoming_offer_codec_prefs;
+	/*! Codec negotiation prefs for outgoing offers */
+	struct ast_stream_codec_negotiation_prefs outgoing_offer_codec_prefs;
+	/*! Codec negotiation prefs for incoming answers */
+	struct ast_stream_codec_negotiation_prefs incoming_answer_codec_prefs;
+	/*! Codec negotiation prefs for outgoing answers */
+	struct ast_stream_codec_negotiation_prefs outgoing_answer_codec_prefs;
 };
 
 /*!
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 32907e3..847a14c 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -102,6 +102,239 @@
 				<configOption name="allow">
 					<synopsis>Media Codec(s) to allow</synopsis>
 				</configOption>
+				<configOption name="incoming_offer_codec_prefs">
+					<synopsis>Codec negotiation prefs for incoming offers.</synopsis>
+					<description>
+						<para>
+							This is a string that describes how the codecs
+							specified on an incoming SDP offer (pending) are reconciled with the codecs specified
+							on an endpoint (configured) before being sent to the Asterisk core.
+							The string actually specifies 4 <literal>name:value</literal> pair parameters
+							separated by commas. Whitespace is ignored and they may be specified in any order.
+
+						</para>
+						<para>
+							Parameters:
+						</para>
+						<enumlist>
+							<enum name="prefer: < pending | configured >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="pending"><para>The codec list from the caller. (default)</para></enum>
+									<enum name="configured"><para>The codec list from the endpoint.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="operation : < intersect | only_preferred | only_nonpreferred >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="intersect"><para>Only common codecs with the preferred codecs first. (default)</para></enum>
+									<enum name="only_preferred"><para>Use only the preferred codecs.</para></enum>
+									<enum name="only_nonpreferred"><para>Use only the non-preferred codecs.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="keep : < all | first >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="all"><para>After the operation, keep all codecs. (default)</para></enum>
+									<enum name="first"><para>After the operation, keep only the first codec.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="transcode : < allow | prevent >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="allow"><para>Allow transcoding. (default)</para></enum>
+									<enum name="prevent"><para>Prevent transcoding.</para></enum>
+								</enumlist>
+							</enum>
+						</enumlist>
+						<para>
+						</para>
+						<example>
+							incoming_offer_codec_prefs = prefer: pending, operation: intersect, keep: all, transcode: allow
+						</example>
+						<para>
+							Prefer the codecs coming from the caller.  Use only the ones that are common.
+							keeping the order of the preferred list. Keep all codecs in the result. Allow transcoding.
+						</para>
+					</description>
+				</configOption>
+				<configOption name="outgoing_offer_codec_prefs">
+					<synopsis>Codec negotiation prefs for outgoing offers.</synopsis>
+					<description>
+						<para>
+							This is a string that describes how the codecs specified in the topology that
+							comes from the Asterisk core (pending) are reconciled with the codecs specified on an
+							endpoint (configured) when sending an SDP offer.
+							The string actually specifies 4 <literal>name:value</literal> pair parameters
+							separated by commas. Whitespace is ignored and they may be specified in any order.
+
+						</para>
+						<para>
+							Parameters:
+						</para>
+						<enumlist>
+							<enum name="prefer: < pending | configured >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="pending"><para>The codec list from the core. (default)</para></enum>
+									<enum name="configured"><para>The codec list from the endpoint.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="operation : < union | intersect | only_preferred | only_nonpreferred >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="union"><para>Merge the lists with the preferred codecs first. (default)</para></enum>
+									<enum name="intersect"><para>Only common codecs with the preferred codecs first. (default)</para></enum>
+									<enum name="only_preferred"><para>Use only the preferred codecs.</para></enum>
+									<enum name="only_nonpreferred"><para>Use only the non-preferred codecs.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="keep : < all | first >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="all"><para>After the operation, keep all codecs. (default)</para></enum>
+									<enum name="first"><para>After the operation, keep only the first codec.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="transcode : < allow | prevent >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="allow"><para>Allow transcoding. (default)</para></enum>
+									<enum name="prevent"><para>Prevent transcoding.</para></enum>
+								</enumlist>
+							</enum>
+						</enumlist>
+						<para>
+						</para>
+						<example>
+						outgoing_offer_codec_prefs = prefer: configured, operation: union, keep: first, transcode: prevent
+						</example>
+						<para>
+						Prefer the codecs coming from the endpoint.  Merge them with the codecs from the core
+						keeping the order of the preferred list. Keep only the first one. No transcoding allowed.
+						</para>
+					</description>
+				</configOption>
+				<configOption name="incoming_answer_codec_prefs">
+					<synopsis>Codec negotiation prefs for incoming answers.</synopsis>
+					<description>
+						<para>
+							This is a string that describes how the codecs specified in an incoming SDP answer
+							(pending) are reconciled with the codecs specified on an endpoint (configured)
+							when receiving an SDP answer.
+							The string actually specifies 4 <literal>name:value</literal> pair parameters
+							separated by commas. Whitespace is ignored and they may be specified in any order.
+						</para>
+						<para>
+							Parameters:
+						</para>
+						<enumlist>
+							<enum name="prefer: < pending | configured >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="pending"><para>The codec list in the received SDP answer. (default)</para></enum>
+									<enum name="configured"><para>The codec list from the endpoint.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="operation : < union | intersect | only_preferred | only_nonpreferred >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="union"><para>Merge the lists with the preferred codecs first.</para></enum>
+									<enum name="intersect"><para>Only common codecs with the preferred codecs first. (default)</para></enum>
+									<enum name="only_preferred"><para>Use only the preferred codecs.</para></enum>
+									<enum name="only_nonpreferred"><para>Use only the non-preferred codecs.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="keep : < all | first >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="all"><para>After the operation, keep all codecs. (default)</para></enum>
+									<enum name="first"><para>After the operation, keep only the first codec.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="transcode : < allow | prevent >">
+								<para>
+								The transcode parameter is ignored when processing answers.
+								</para>
+							</enum>
+						</enumlist>
+						<para>
+						</para>
+						<example>
+						incoming_answer_codec_prefs = keep: first
+						</example>
+						<para>
+						Use the defaults but keep oinly the first codec.
+						</para>
+					</description>
+				</configOption>
+				<configOption name="outgoing_answer_codec_prefs">
+					<synopsis>Codec negotiation prefs for outgoing answers.</synopsis>
+					<description>
+						<para>
+							This is a string that describes how the codecs that come from the core (pending)
+							are reconciled with the codecs specified on an endpoint (configured)
+							when sending an SDP answer.
+							The string actually specifies 4 <literal>name:value</literal> pair parameters
+							separated by commas. Whitespace is ignored and they may be specified in any order.
+						</para>
+						<para>
+							Parameters:
+						</para>
+						<enumlist>
+							<enum name="prefer: < pending | configured >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="pending"><para>The codec list that came from the core. (default)</para></enum>
+									<enum name="configured"><para>The codec list from the endpoint.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="operation : < union | intersect | only_preferred | only_nonpreferred >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="union"><para>Merge the lists with the preferred codecs first.</para></enum>
+									<enum name="intersect"><para>Only common codecs with the preferred codecs first. (default)</para></enum>
+									<enum name="only_preferred"><para>Use only the preferred codecs.</para></enum>
+									<enum name="only_nonpreferred"><para>Use only the non-preferred codecs.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="keep : < all | first >">
+								<para>
+								</para>
+								<enumlist>
+									<enum name="all"><para>After the operation, keep all codecs. (default)</para></enum>
+									<enum name="first"><para>After the operation, keep only the first codec.</para></enum>
+								</enumlist>
+							</enum>
+							<enum name="transcode : < allow | prevent >">
+								<para>
+								The transcode parameter is ignored when processing answers.
+								</para>
+							</enum>
+						</enumlist>
+						<para>
+						</para>
+						<example>
+						incoming_answer_codec_prefs = keep: first
+						</example>
+						<para>
+						Use the defaults but keep oinly the first codec.
+						</para>
+					</description>
+				</configOption>
 				<configOption name="allow_overlap" default="yes">
 					<synopsis>Enable RFC3578 overlap dialing support.</synopsis>
 				</configOption>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index f95ee9e..e3eab8a 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1166,6 +1166,109 @@
 	return 0;
 }
 
+static int codec_prefs_handler(const struct aco_option *opt,
+	struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+	struct ast_stream_codec_negotiation_prefs prefs;
+	struct ast_str *error_message = ast_str_create(128);
+	enum ast_stream_codec_negotiation_prefs_prefer_values default_prefer;
+	enum ast_stream_codec_negotiation_prefs_operation_values default_operation;
+	int res = 0;
+
+	res = ast_stream_codec_prefs_parse(var->value, &prefs, &error_message);
+	if (res < 0) {
+		ast_log(LOG_ERROR, "Endpoint '%s': %s for option '%s'\n",
+			ast_sorcery_object_get_id(endpoint), ast_str_buffer(error_message), var->name);
+		ast_free(error_message);
+		return -1;
+	}
+	ast_free(error_message);
+
+	if (strcmp(var->name, "incoming_offer_codec_prefs") == 0) {
+		if (prefs.operation == CODEC_NEGOTIATION_OPERATION_UNION) {
+			ast_log(LOG_ERROR, "Endpoint '%s': Codec preference '%s' has invalid value '%s' for option: '%s'",
+				ast_sorcery_object_get_id(endpoint),
+				ast_stream_codec_param_to_str(CODEC_NEGOTIATION_PARAM_OPERATION),
+				ast_stream_codec_operation_to_str(CODEC_NEGOTIATION_OPERATION_UNION),
+				var->name);
+			return -1;
+		}
+		endpoint->media.incoming_offer_codec_prefs = prefs;
+		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
+		default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
+	} else if (strcmp(var->name, "outgoing_offer_codec_prefs") == 0) {
+		endpoint->media.outgoing_offer_codec_prefs = prefs;
+		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
+		default_operation = CODEC_NEGOTIATION_OPERATION_UNION;
+	} else if (strcmp(var->name, "incoming_answer_codec_prefs") == 0) {
+		endpoint->media.incoming_answer_codec_prefs = prefs;
+		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
+		default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
+	} else if (strcmp(var->name, "outgoing_answer_codec_prefs") == 0) {
+		endpoint->media.outgoing_answer_codec_prefs = prefs;
+		default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
+		default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
+	}
+
+	if (prefs.prefer == CODEC_NEGOTIATION_PREFER_UNSPECIFIED) {
+		prefs.prefer = default_prefer;
+	}
+
+	if (prefs.operation == CODEC_NEGOTIATION_OPERATION_UNSPECIFIED) {
+		prefs.operation = default_operation;
+	}
+
+	if (prefs.keep == CODEC_NEGOTIATION_KEEP_UNSPECIFIED) {
+		prefs.keep = CODEC_NEGOTIATION_KEEP_ALL;
+	}
+
+	if (prefs.transcode == CODEC_NEGOTIATION_TRANSCODE_UNSPECIFIED) {
+		prefs.transcode = CODEC_NEGOTIATION_TRANSCODE_ALLOW;
+	}
+
+	return 0;
+}
+
+static int codec_prefs_to_str(const struct ast_stream_codec_negotiation_prefs *prefs,
+	const void *obj, const intptr_t *args, char **buf)
+{
+	struct ast_str *codecs = ast_str_create(AST_STREAM_MAX_CODEC_PREFS_LENGTH);
+
+	if (!codecs) {
+		return -1;
+	}
+
+	*buf = ast_strdup(ast_stream_codec_prefs_to_str(prefs, &codecs));
+	ast_free(codecs);
+
+	return 0;
+}
+
+static int incoming_offer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	return codec_prefs_to_str(&endpoint->media.incoming_offer_codec_prefs, obj, args, buf);
+}
+
+static int outgoing_offer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	return codec_prefs_to_str(&endpoint->media.outgoing_offer_codec_prefs, obj, args, buf);
+}
+
+static int incoming_answer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	return codec_prefs_to_str(&endpoint->media.incoming_answer_codec_prefs, obj, args, buf);
+}
+
+static int outgoing_answer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	return codec_prefs_to_str(&endpoint->media.outgoing_answer_codec_prefs, obj, args, buf);
+}
+
 static void *sip_nat_hook_alloc(const char *name)
 {
 	return ast_sorcery_generic_alloc(sizeof(struct ast_sip_nat_hook), NULL);
@@ -2025,6 +2128,18 @@
 		call_offer_pref_handler, incoming_call_offer_pref_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_call_offer_pref", "remote",
 		call_offer_pref_handler, outgoing_call_offer_pref_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_offer_codec_prefs",
+		"prefer: pending, operation: intersect, keep: all, transcode: allow",
+		codec_prefs_handler, incoming_offer_codec_prefs_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_offer_codec_prefs",
+		"prefer: pending, operation: union, keep: all, transcode: allow",
+		codec_prefs_handler, outgoing_offer_codec_prefs_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_answer_codec_prefs",
+		"prefer: pending, operation: intersect, keep: all",
+		codec_prefs_handler, incoming_answer_codec_prefs_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_answer_codec_prefs",
+		"prefer: pending, operation: intersect, keep: all",
+		codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);
 
 	if (ast_sip_initialize_sorcery_transport()) {
 		ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/14629
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: I920ba925d7dd36430dfd2ebd9d82d23f123d0e11
Gerrit-Change-Number: 14629
Gerrit-PatchSet: 4
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-CC: Stanislav Abramenkov <stas.abramenkov at gmail.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200708/2ffe7670/attachment-0001.html>


More information about the asterisk-code-review mailing list