<p>dexter has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.osmocom.org/9649">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">client: add features to generate and parse codec information<br><br>The current implementation does not support any way to influence the<br>codec that is negotiated via SDP or LCO. The client statically<br>negotitates AMR on an invalid payload type number. Also we ignore<br>any codec information in the responses.<br><br>- Add struct members to allow setting of user defined codec information.<br>- Add struct members to retrieve parsed codec info from responses.<br>- Add code to generate codec information in SDP<br>- Add code to parse SDP codec info in MGCP responses<br><br>Change-Id: I78e72d41b73acfcb40599a0ff4823f17c3642059<br>Related: OS#2728<br>Related: OS#3334<br>---<br>M include/osmocom/mgcp_client/mgcp_client.h<br>M include/osmocom/mgcp_client/mgcp_client_fsm.h<br>M src/libosmo-mgcp-client/mgcp_client.c<br>M src/libosmo-mgcp-client/mgcp_client_fsm.c<br>M tests/mgcp_client/mgcp_client_test.c<br>M tests/mgcp_client/mgcp_client_test.err<br>M tests/mgcp_client/mgcp_client_test.ok<br>7 files changed, 721 insertions(+), 85 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.osmocom.org:29418/osmo-mgw refs/changes/49/9649/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/include/osmocom/mgcp_client/mgcp_client.h b/include/osmocom/mgcp_client/mgcp_client.h</span><br><span>index 73f3bba..3cecb8f 100644</span><br><span>--- a/include/osmocom/mgcp_client/mgcp_client.h</span><br><span>+++ b/include/osmocom/mgcp_client/mgcp_client.h</span><br><span>@@ -26,6 +26,33 @@</span><br><span> </span><br><span> typedef unsigned int mgcp_trans_id_t;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! Enumeration of the codec types that mgcp_client is able to handle. */</span><br><span style="color: hsl(120, 100%, 40%);">+enum mgcp_codecs {</span><br><span style="color: hsl(120, 100%, 40%);">+ CODEC_PCMU_8000_1 = 0,</span><br><span style="color: hsl(120, 100%, 40%);">+        CODEC_GSM_8000_1 = 3,</span><br><span style="color: hsl(120, 100%, 40%);">+ CODEC_PCMA_8000_1 = 8,</span><br><span style="color: hsl(120, 100%, 40%);">+        CODEC_G729_8000_1 = 18,</span><br><span style="color: hsl(120, 100%, 40%);">+       CODEC_GSMEFR_8000_1 = 110,</span><br><span style="color: hsl(120, 100%, 40%);">+    CODEC_GSMHR_8000_1 = 111,       </span><br><span style="color: hsl(120, 100%, 40%);">+      CODEC_AMR_8000_1 = 112,</span><br><span style="color: hsl(120, 100%, 40%);">+       CODEC_AMRWB_16000_1 = 113,</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+/* Note: when new codec types are added, the corresponding value strings</span><br><span style="color: hsl(120, 100%, 40%);">+ * in mgcp_client.c (codec_table) must be updated as well. Enumerations</span><br><span style="color: hsl(120, 100%, 40%);">+ * in enum mgcp_codecs must correspond to a valid payload type. However,</span><br><span style="color: hsl(120, 100%, 40%);">+ * this is an internal assumption that is made to avoid lookup tables.</span><br><span style="color: hsl(120, 100%, 40%);">+ * The API-User should not rely on this coincidence! */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Structure to build a payload type map to allow the defiition custom payload</span><br><span style="color: hsl(120, 100%, 40%);">+ *  types. */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ptmap {</span><br><span style="color: hsl(120, 100%, 40%);">+        /*!< codec for which a payload type number should be defined */</span><br><span style="color: hsl(120, 100%, 40%);">+    enum mgcp_codecs codec;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /*!< payload type number (96-127) */</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int pt;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> struct mgcp_response_head {</span><br><span>        int response_code;</span><br><span>   mgcp_trans_id_t trans_id;</span><br><span>@@ -39,6 +66,11 @@</span><br><span>       struct mgcp_response_head head;</span><br><span>      uint16_t audio_port;</span><br><span>         char audio_ip[INET_ADDRSTRLEN];</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int ptime;</span><br><span style="color: hsl(120, 100%, 40%);">+   enum mgcp_codecs codecs[MGCP_MAX_CODECS];</span><br><span style="color: hsl(120, 100%, 40%);">+     unsigned int codecs_len;</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ptmap ptmap[MGCP_MAX_CODECS];</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int ptmap_len; </span><br><span> };</span><br><span> </span><br><span> enum mgcp_verb {</span><br><span>@@ -66,6 +98,11 @@</span><br><span>  uint16_t audio_port;</span><br><span>         char *audio_ip;</span><br><span>      enum mgcp_connection_mode conn_mode;</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int ptime;</span><br><span style="color: hsl(120, 100%, 40%);">+   enum mgcp_codecs codecs[MGCP_MAX_CODECS];</span><br><span style="color: hsl(120, 100%, 40%);">+     unsigned int codecs_len;</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ptmap ptmap[MGCP_MAX_CODECS];</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int ptmap_len;</span><br><span> };</span><br><span> </span><br><span> void mgcp_client_conf_init(struct mgcp_client_conf *conf);</span><br><span>@@ -117,3 +154,9 @@</span><br><span> {</span><br><span>   return get_value_string(mgcp_client_connection_mode_strs, mode);</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+enum mgcp_codecs map_str_to_codec(const char *str);</span><br><span style="color: hsl(120, 100%, 40%);">+unsigned int map_codec_to_pt(struct ptmap *ptmap, unsigned int ptmap_len,</span><br><span style="color: hsl(120, 100%, 40%);">+                            enum mgcp_codecs codec);</span><br><span style="color: hsl(120, 100%, 40%);">+enum mgcp_codecs map_pt_to_codec(struct ptmap *ptmap, unsigned int ptmap_len,</span><br><span style="color: hsl(120, 100%, 40%);">+                           unsigned int pt);</span><br><span>diff --git a/include/osmocom/mgcp_client/mgcp_client_fsm.h b/include/osmocom/mgcp_client/mgcp_client_fsm.h</span><br><span>index 93fe582..47d9fab 100644</span><br><span>--- a/include/osmocom/mgcp_client/mgcp_client_fsm.h</span><br><span>+++ b/include/osmocom/mgcp_client/mgcp_client_fsm.h</span><br><span>@@ -25,6 +25,15 @@</span><br><span> </span><br><span>         /*!< CALL ID (unique per connection) */</span><br><span>   unsigned int call_id;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       /*!< RTP packetization interval (optional) */</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned int ptime;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /*!< RTP codec list (optional) */</span><br><span style="color: hsl(120, 100%, 40%);">+  enum mgcp_codecs codecs[MGCP_MAX_CODECS];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /*!< Number of codecs in RTP codec list (optional) */</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned int codecs_len;</span><br><span> };</span><br><span> </span><br><span> struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm_inst *parent_fi, uint32_t parent_term_evt,</span><br><span>diff --git a/src/libosmo-mgcp-client/mgcp_client.c b/src/libosmo-mgcp-client/mgcp_client.c</span><br><span>index e054593..00b52f8 100644</span><br><span>--- a/src/libosmo-mgcp-client/mgcp_client.c</span><br><span>+++ b/src/libosmo-mgcp-client/mgcp_client.c</span><br><span>@@ -36,6 +36,150 @@</span><br><span> #include <unistd.h></span><br><span> #include <string.h></span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Codec descripton for dynamic payload types (SDP) */</span><br><span style="color: hsl(120, 100%, 40%);">+static const struct value_string codec_table[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+    { CODEC_PCMU_8000_1, "PCMU/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+       { CODEC_GSM_8000_1, "GSM/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+ { CODEC_PCMA_8000_1, "PCMA/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+       { CODEC_G729_8000_1, "G729/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+       { CODEC_GSMEFR_8000_1, "GSM-EFR/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+  { CODEC_GSMHR_8000_1, "GSM-HR-08/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+ { CODEC_AMR_8000_1, "AMR/8000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+ { CODEC_AMRWB_16000_1, "AMR-WB/16000/1" },</span><br><span style="color: hsl(120, 100%, 40%);">+  { 0, NULL },</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Get encoding name from a full codec string e,g.</span><br><span style="color: hsl(120, 100%, 40%);">+ * ("CODEC/8000/2" => returns "CODEC") */</span><br><span style="color: hsl(120, 100%, 40%);">+static char *extract_codec_name(const char *str)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ static char buf[64];</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!str)</span><br><span style="color: hsl(120, 100%, 40%);">+             return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        /* FIXME osmo_strlcpy */</span><br><span style="color: hsl(120, 100%, 40%);">+      osmo_strlcpy(buf, str, sizeof(buf));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        for (i = 0; i < strlen(buf); i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+                if (buf[i] == '/')</span><br><span style="color: hsl(120, 100%, 40%);">+                    buf[i] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return buf;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Map a string to a codec.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] str input string (e.g "GSM/8000/1", "GSM/8000" or "GSM")</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \returns codec that corresponds to the given string representation. */</span><br><span style="color: hsl(120, 100%, 40%);">+enum mgcp_codecs map_str_to_codec(const char *str)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+       char *codec_name;</span><br><span style="color: hsl(120, 100%, 40%);">+     char str_buf[64];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   osmo_strlcpy(str_buf, extract_codec_name(str), sizeof(str_buf));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    for (i = 0; i < ARRAY_SIZE(codec_table); i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+            codec_name = extract_codec_name(codec_table[i].str);</span><br><span style="color: hsl(120, 100%, 40%);">+          if (!codec_name)</span><br><span style="color: hsl(120, 100%, 40%);">+                      continue;</span><br><span style="color: hsl(120, 100%, 40%);">+             if (strcmp(codec_name, str_buf) == 0)</span><br><span style="color: hsl(120, 100%, 40%);">+                 return codec_table[i].value;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Check the ptmap for illegal mappings */</span><br><span style="color: hsl(120, 100%, 40%);">+static int check_ptmap(struct ptmap *ptmap)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Check if there are mappings that leave the IANA assigned dynamic</span><br><span style="color: hsl(120, 100%, 40%);">+    * payload type range. Under normal conditions such mappings should</span><br><span style="color: hsl(120, 100%, 40%);">+    * not occur */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* Its ok to have a 1:1 mapping in the statically defined</span><br><span style="color: hsl(120, 100%, 40%);">+      * range, this won't hurt */</span><br><span style="color: hsl(120, 100%, 40%);">+      if (ptmap->codec == ptmap->pt)</span><br><span style="color: hsl(120, 100%, 40%);">+          return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ptmap->codec < 96 || ptmap->codec > 127)</span><br><span style="color: hsl(120, 100%, 40%);">+              goto error;</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ptmap->pt < 96 || ptmap->pt > 127)</span><br><span style="color: hsl(120, 100%, 40%);">+            goto error;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+error:</span><br><span style="color: hsl(120, 100%, 40%);">+     LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+           "ptmap contains illegal mapping: codec=%u maps to pt=%u\n",</span><br><span style="color: hsl(120, 100%, 40%);">+         ptmap->codec, ptmap->pt);</span><br><span style="color: hsl(120, 100%, 40%);">+  return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Map a codec to a payload type.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] payload pointer to payload type map with specified payload types.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] ptmap_len length of the payload type map.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] codec the codec for which the payload type should be looked up.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \returns assigned payload type */</span><br><span style="color: hsl(120, 100%, 40%);">+unsigned int map_codec_to_pt(struct ptmap *ptmap, unsigned int ptmap_len,</span><br><span style="color: hsl(120, 100%, 40%);">+                          enum mgcp_codecs codec)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /*! Note: If the payload type map is empty or the codec is not found</span><br><span style="color: hsl(120, 100%, 40%);">+   *  in the map, then a 1:1 mapping is performed. If the codec falls</span><br><span style="color: hsl(120, 100%, 40%);">+    *  into the statically defined range or if the mapping table isself</span><br><span style="color: hsl(120, 100%, 40%);">+   *  tries to map to the statically defined range, then the mapping</span><br><span style="color: hsl(120, 100%, 40%);">+     *  is also ignored and a 1:1 mapping is performed instead. */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      /* we may return the codec directly since enum mgcp_codecs directly</span><br><span style="color: hsl(120, 100%, 40%);">+    * corresponds to the statićally assigned payload types */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (codec < 96 || codec > 127)</span><br><span style="color: hsl(120, 100%, 40%);">+          return codec;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       for (i = 0; i < ptmap_len; i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+          /* Skip illegal map entries */</span><br><span style="color: hsl(120, 100%, 40%);">+                if (check_ptmap(ptmap) == 0 && ptmap->codec == codec)</span><br><span style="color: hsl(120, 100%, 40%);">+                      return ptmap->pt;</span><br><span style="color: hsl(120, 100%, 40%);">+          ptmap++;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* If nothing is found, do not perform any mapping */</span><br><span style="color: hsl(120, 100%, 40%);">+ return codec;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Map a payload type to a codec.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] payload pointer to payload type map with specified payload types.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] ptmap_len length of the payload type map.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \ptmap[in] payload type for which the codec should be looked up.</span><br><span style="color: hsl(120, 100%, 40%);">+ *  \returns codec that corresponds to the specified payload type */</span><br><span style="color: hsl(120, 100%, 40%);">+enum mgcp_codecs map_pt_to_codec(struct ptmap *ptmap, unsigned int ptmap_len,</span><br><span style="color: hsl(120, 100%, 40%);">+                          unsigned int pt)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /*! Note: If the payload type map is empty or the payload type is not</span><br><span style="color: hsl(120, 100%, 40%);">+  *  found in the map, then a 1:1 mapping is performed. If the payload</span><br><span style="color: hsl(120, 100%, 40%);">+  *  type falls into the statically defined range or if the mapping</span><br><span style="color: hsl(120, 100%, 40%);">+     *  table isself tries to map to the statically defined range, then</span><br><span style="color: hsl(120, 100%, 40%);">+    *  the mapping is also ignored and a 1:1 mapping is performed</span><br><span style="color: hsl(120, 100%, 40%);">+         *  instead. */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* See also note in map_codec_to_pt() */</span><br><span style="color: hsl(120, 100%, 40%);">+      if (pt < 96 || pt > 127)</span><br><span style="color: hsl(120, 100%, 40%);">+                return pt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  for (i = 0; i < ptmap_len; i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+          if (check_ptmap(ptmap) == 0 && ptmap->pt == pt)</span><br><span style="color: hsl(120, 100%, 40%);">+                    return ptmap->codec;</span><br><span style="color: hsl(120, 100%, 40%);">+               ptmap++;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* If nothing is found, do not perform any mapping */</span><br><span style="color: hsl(120, 100%, 40%);">+ return pt;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! Initalize MGCP client configuration struct with default values.</span><br><span>  *  \param[out] conf Client configuration.*/</span><br><span> void mgcp_client_conf_init(struct mgcp_client_conf *conf)</span><br><span>@@ -178,22 +322,114 @@</span><br><span>         return true;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-/* Parse a line like "m=audio 16002 RTP/AVP 98" */</span><br><span style="color: hsl(0, 100%, 40%);">-static int mgcp_parse_audio_port(struct mgcp_response *r, const char *line)</span><br><span style="color: hsl(120, 100%, 40%);">+/* Parse a line like "m=audio 16002 RTP/AVP 98", extract port and payload types */</span><br><span style="color: hsl(120, 100%, 40%);">+static int mgcp_parse_audio_port_pt(struct mgcp_response *r, char *line)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-       if (sscanf(line, "m=audio %hu",</span><br><span style="color: hsl(0, 100%, 40%);">-                  &r->audio_port) != 1)</span><br><span style="color: hsl(0, 100%, 40%);">-         goto response_parse_failure;</span><br><span style="color: hsl(120, 100%, 40%);">+  char *pt_str;</span><br><span style="color: hsl(120, 100%, 40%);">+ unsigned int pt;</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned int count = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int i;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+   /* Extract port information */</span><br><span style="color: hsl(120, 100%, 40%);">+        if (sscanf(line, "m=audio %hu", &r->audio_port) != 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                goto response_parse_failure_port;</span><br><span>    if (r->audio_port == 0)</span><br><span style="color: hsl(0, 100%, 40%);">-              goto response_parse_failure;</span><br><span style="color: hsl(120, 100%, 40%);">+          goto response_parse_failure_port;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* Extract payload types */</span><br><span style="color: hsl(120, 100%, 40%);">+   line = strstr(line, "RTP/AVP ");</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!line)</span><br><span style="color: hsl(120, 100%, 40%);">+            goto exit;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  pt_str = strtok(line, " ");</span><br><span style="color: hsl(120, 100%, 40%);">+ while (1) {</span><br><span style="color: hsl(120, 100%, 40%);">+           /* Do not allow excessive payload types */</span><br><span style="color: hsl(120, 100%, 40%);">+            if (count > ARRAY_SIZE(r->codecs))</span><br><span style="color: hsl(120, 100%, 40%);">+                      goto response_parse_failure_pt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             pt_str = strtok(NULL, " ");</span><br><span style="color: hsl(120, 100%, 40%);">+         if (!pt_str)</span><br><span style="color: hsl(120, 100%, 40%);">+                  break;</span><br><span style="color: hsl(120, 100%, 40%);">+                pt = atoi(pt_str);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          /* Do not allow duplicate payload types */</span><br><span style="color: hsl(120, 100%, 40%);">+            for (i = 0; i < count; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+                        if (r->codecs[i] == pt)</span><br><span style="color: hsl(120, 100%, 40%);">+                            goto response_parse_failure_pt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             /* Note: The payload type we store may not necessarly match</span><br><span style="color: hsl(120, 100%, 40%);">+            * the codec types we have defined in enum mgcp_codecs. To</span><br><span style="color: hsl(120, 100%, 40%);">+             * ensure that the end result only contains codec types which</span><br><span style="color: hsl(120, 100%, 40%);">+          * match enum mgcp_codecs, we will go through afterwards and</span><br><span style="color: hsl(120, 100%, 40%);">+           * remap the affected entries with the inrofmation we learn</span><br><span style="color: hsl(120, 100%, 40%);">+            * from rtpmap */</span><br><span style="color: hsl(120, 100%, 40%);">+             r->codecs[count] = pt;</span><br><span style="color: hsl(120, 100%, 40%);">+             count++;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   r->codecs_len = count;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+exit:</span><br><span>   return 0;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-response_parse_failure:</span><br><span style="color: hsl(120, 100%, 40%);">+response_parse_failure_port:</span><br><span>   LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">-             "Failed to parse MGCP response header (audio port)\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             "Failed to parse SDP parameter port (%s)\n", line);</span><br><span>   return -EINVAL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+response_parse_failure_pt:</span><br><span style="color: hsl(120, 100%, 40%);">+ LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+           "Failed to parse SDP parameter payload types (%s)\n", line);</span><br><span style="color: hsl(120, 100%, 40%);">+   return -EINVAL;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Parse a line like "m=audio 16002 RTP/AVP 98", extract port and payload types */</span><br><span style="color: hsl(120, 100%, 40%);">+static int mgcp_parse_audio_ptime_rtpmap(struct mgcp_response *r, const char *line)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    unsigned int pt;</span><br><span style="color: hsl(120, 100%, 40%);">+      char codec_resp[64];</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int codec;</span><br><span style="color: hsl(120, 100%, 40%);">+   </span><br><span style="color: hsl(120, 100%, 40%);">+      </span><br><span style="color: hsl(120, 100%, 40%);">+      if (strstr(line, "ptime")) {</span><br><span style="color: hsl(120, 100%, 40%);">+                if (sscanf(line, "a=ptime:%u", &r->ptime) != 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                      goto response_parse_failure_ptime;</span><br><span style="color: hsl(120, 100%, 40%);">+    } else if (strstr(line, "rtpmap")) {</span><br><span style="color: hsl(120, 100%, 40%);">+                if (sscanf(line, "a=rtpmap:%d %63s", &pt, codec_resp) == 2) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   /* The MGW may assign an own payload type in the</span><br><span style="color: hsl(120, 100%, 40%);">+                       * response if the choosen codec falls into the IANA</span><br><span style="color: hsl(120, 100%, 40%);">+                   * assigned dynamic payload type range (96-127).</span><br><span style="color: hsl(120, 100%, 40%);">+                       * Normally the MGW should obey the 3gpp payload type</span><br><span style="color: hsl(120, 100%, 40%);">+                  * assignments, which are fixed, so we likely wont see</span><br><span style="color: hsl(120, 100%, 40%);">+                         * anything unexpected here. In order to be sure that</span><br><span style="color: hsl(120, 100%, 40%);">+                  * we will now check the codec string and if the result</span><br><span style="color: hsl(120, 100%, 40%);">+                        * does not match to what is IANA / 3gpp assigned, we</span><br><span style="color: hsl(120, 100%, 40%);">+                  * will create an entry in the ptmap table so we can</span><br><span style="color: hsl(120, 100%, 40%);">+                   * lookup later what has been assigned. */</span><br><span style="color: hsl(120, 100%, 40%);">+                    codec = map_str_to_codec(codec_resp);</span><br><span style="color: hsl(120, 100%, 40%);">+                 if (codec != pt) {</span><br><span style="color: hsl(120, 100%, 40%);">+                            if (r->ptmap_len < ARRAY_SIZE(r->ptmap)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                   r->ptmap[r->ptmap_len].pt = pt;</span><br><span style="color: hsl(120, 100%, 40%);">+                                 r->ptmap[r->ptmap_len].codec = codec;</span><br><span style="color: hsl(120, 100%, 40%);">+                                   r->ptmap_len++;</span><br><span style="color: hsl(120, 100%, 40%);">+                            } else</span><br><span style="color: hsl(120, 100%, 40%);">+                                        goto response_parse_failure_rtpmap;</span><br><span style="color: hsl(120, 100%, 40%);">+                   }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           } else</span><br><span style="color: hsl(120, 100%, 40%);">+                        goto response_parse_failure_rtpmap;</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+     </span><br><span style="color: hsl(120, 100%, 40%);">+      return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+response_parse_failure_ptime:</span><br><span style="color: hsl(120, 100%, 40%);">+    LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+           "Failed to parse SDP parameter, invalid ptime (%s)\n", line);</span><br><span style="color: hsl(120, 100%, 40%);">+  return -EINVAL;         </span><br><span style="color: hsl(120, 100%, 40%);">+response_parse_failure_rtpmap:</span><br><span style="color: hsl(120, 100%, 40%);">+      LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+           "Failed to parse SDP parameter, invalid rtpmap (%s)\n", line);</span><br><span style="color: hsl(120, 100%, 40%);">+ return -EINVAL;         </span><br><span> }</span><br><span> </span><br><span> /* Parse a line like "c=IN IP4 10.11.12.13" */</span><br><span>@@ -253,6 +489,7 @@</span><br><span>  int rc;</span><br><span>      char *data;</span><br><span>  char *data_ptr;</span><br><span style="color: hsl(120, 100%, 40%);">+       int i;</span><br><span> </span><br><span>   /* Since this functions performs a destructive parsing, we create a</span><br><span>   * local copy of the body data */</span><br><span>@@ -277,8 +514,13 @@</span><br><span>                     return -EINVAL;</span><br><span> </span><br><span>          switch (line[0]) {</span><br><span style="color: hsl(120, 100%, 40%);">+            case 'a':</span><br><span style="color: hsl(120, 100%, 40%);">+                     rc = mgcp_parse_audio_ptime_rtpmap(r, line);</span><br><span style="color: hsl(120, 100%, 40%);">+                  if (rc)</span><br><span style="color: hsl(120, 100%, 40%);">+                               goto exit;</span><br><span style="color: hsl(120, 100%, 40%);">+                    break;</span><br><span>               case 'm':</span><br><span style="color: hsl(0, 100%, 40%);">-                       rc = mgcp_parse_audio_port(r, line);</span><br><span style="color: hsl(120, 100%, 40%);">+                  rc = mgcp_parse_audio_port_pt(r, line);</span><br><span>                      if (rc)</span><br><span>                              goto exit;</span><br><span>                   break;</span><br><span>@@ -293,6 +535,10 @@</span><br><span>                }</span><br><span>    }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* See also note in mgcp_parse_audio_port_pt() */</span><br><span style="color: hsl(120, 100%, 40%);">+     for (i = 0; i < r->codecs_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+             r->codecs[i] =  map_pt_to_codec(r->ptmap, r->ptmap_len, r->codecs[i]);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         rc = 0;</span><br><span> exit:</span><br><span>     talloc_free(data);</span><br><span>@@ -813,6 +1059,119 @@</span><br><span> #define MGCP_AUEP_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT)</span><br><span> #define MGCP_RSIP_MANDATORY 0        /* none */</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Helper function for mgcp_msg_gen(): Add LCO information to MGCP message */</span><br><span style="color: hsl(120, 100%, 40%);">+static int add_lco(struct msgb *msg, struct mgcp_msg *mgcp_msg)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+       int rc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+   const char *codec;</span><br><span style="color: hsl(120, 100%, 40%);">+    unsigned int pt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    rc += msgb_printf(msg, "L:");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (mgcp_msg->ptime)</span><br><span style="color: hsl(120, 100%, 40%);">+               rc += msgb_printf(msg, " p:%u,", mgcp_msg->ptime);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (mgcp_msg->codecs_len) {</span><br><span style="color: hsl(120, 100%, 40%);">+                rc += msgb_printf(msg, " a:");</span><br><span style="color: hsl(120, 100%, 40%);">+              for (i = 0; i < mgcp_msg->codecs_len; i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    pt = mgcp_msg->codecs[i];</span><br><span style="color: hsl(120, 100%, 40%);">+                  codec = get_value_string_or_null(codec_table, pt);</span><br><span style="color: hsl(120, 100%, 40%);">+                    </span><br><span style="color: hsl(120, 100%, 40%);">+                      /* Note: Use codec descriptors from enum mgcp_codecs</span><br><span style="color: hsl(120, 100%, 40%);">+                   * in mgcp_client only! */</span><br><span style="color: hsl(120, 100%, 40%);">+                    OSMO_ASSERT(codec);</span><br><span style="color: hsl(120, 100%, 40%);">+                   rc += msgb_printf(msg, "%s", extract_codec_name(codec));</span><br><span style="color: hsl(120, 100%, 40%);">+                    if (i < mgcp_msg->codecs_len - 1)</span><br><span style="color: hsl(120, 100%, 40%);">+                               rc += msgb_printf(msg, ";");</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+             rc += msgb_printf(msg, ",");</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   rc += msgb_printf(msg, " nt:IN\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     return rc;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Helper function for mgcp_msg_gen(): Add SDP information to MGCP message */</span><br><span style="color: hsl(120, 100%, 40%);">+static int add_sdp(struct msgb *msg, struct mgcp_msg *mgcp_msg, struct mgcp_client *mgcp)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+       int rc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+   char local_ip[INET_ADDRSTRLEN];</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *codec;</span><br><span style="color: hsl(120, 100%, 40%);">+    unsigned int pt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    /* Add separator to mark the beginning of the SDP block */</span><br><span style="color: hsl(120, 100%, 40%);">+    rc += msgb_printf(msg, "\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Add SDP protocol version */</span><br><span style="color: hsl(120, 100%, 40%);">+        rc += msgb_printf(msg, "v=0\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        /* Determine local IP-Address */</span><br><span style="color: hsl(120, 100%, 40%);">+      if (osmo_sock_local_ip(local_ip, mgcp->actual.remote_addr) < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+               LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                   "Could not determine local IP-Address!\n");</span><br><span style="color: hsl(120, 100%, 40%);">+            msgb_free(msg);</span><br><span style="color: hsl(120, 100%, 40%);">+               return -2;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Add owner/creator (SDP) */</span><br><span style="color: hsl(120, 100%, 40%);">+ rc += msgb_printf(msg, "o=- %x 23 IN IP4 %s\r\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                     mgcp_msg->call_id, local_ip);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  /* Add session name (none) */</span><br><span style="color: hsl(120, 100%, 40%);">+ rc += msgb_printf(msg, "s=-\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        /* Add RTP address and port */</span><br><span style="color: hsl(120, 100%, 40%);">+        if (mgcp_msg->audio_port == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+           LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                   "Invalid port number, can not generate MGCP message\n");</span><br><span style="color: hsl(120, 100%, 40%);">+               msgb_free(msg);</span><br><span style="color: hsl(120, 100%, 40%);">+               return -2;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (strlen(mgcp_msg->audio_ip) <= 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+          LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                   "Empty ip address, can not generate MGCP message\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          msgb_free(msg);</span><br><span style="color: hsl(120, 100%, 40%);">+               return -2;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+     rc += msgb_printf(msg, "c=IN IP4 %s\r\n", mgcp_msg->audio_ip);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Add time description, active time (SDP) */</span><br><span style="color: hsl(120, 100%, 40%);">+ rc += msgb_printf(msg, "t=0 0\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      rc += msgb_printf(msg, "m=audio %u RTP/AVP", mgcp_msg->audio_port);</span><br><span style="color: hsl(120, 100%, 40%);">+      for (i = 0; i < mgcp_msg->codecs_len; i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+            pt = map_codec_to_pt(mgcp_msg->ptmap, mgcp_msg->ptmap_len, mgcp_msg->codecs[i]);</span><br><span style="color: hsl(120, 100%, 40%);">+             rc += msgb_printf(msg, " %u", pt);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+     rc += msgb_printf(msg, "\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   for (i = 0; i < mgcp_msg->codecs_len; i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+            pt = map_codec_to_pt(mgcp_msg->ptmap, mgcp_msg->ptmap_len, mgcp_msg->codecs[i]);</span><br><span style="color: hsl(120, 100%, 40%);">+             </span><br><span style="color: hsl(120, 100%, 40%);">+              /* Note: Only dynamic payload type from the range 96-127</span><br><span style="color: hsl(120, 100%, 40%);">+               * require to be explained further via rtpmap. All others</span><br><span style="color: hsl(120, 100%, 40%);">+              * are implcitly definedby the number in m=audio */</span><br><span style="color: hsl(120, 100%, 40%);">+           if (pt >= 96 && pt <= 127) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    codec = get_value_string_or_null(codec_table, mgcp_msg->codecs[i]);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                      /* Note: Use codec descriptors from enum mgcp_codecs</span><br><span style="color: hsl(120, 100%, 40%);">+                   * in mgcp_client only! */</span><br><span style="color: hsl(120, 100%, 40%);">+                    OSMO_ASSERT(codec);</span><br><span style="color: hsl(120, 100%, 40%);">+                   </span><br><span style="color: hsl(120, 100%, 40%);">+                      rc += msgb_printf(msg, "a=rtpmap:%u %s\r\n", pt, codec);</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     </span><br><span style="color: hsl(120, 100%, 40%);">+      if (mgcp_msg->ptime)</span><br><span style="color: hsl(120, 100%, 40%);">+               rc += msgb_printf(msg, "a=ptime:%u\r\n", mgcp_msg->ptime);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     return rc;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! Generate an MGCP message</span><br><span>  *  \param[in] mgcp MGCP client descriptor.</span><br><span>  *  \param[in] mgcp_msg Message description</span><br><span>@@ -823,7 +1182,8 @@</span><br><span>         uint32_t mandatory_mask;</span><br><span>     struct msgb *msg = msgb_alloc_headroom(4096, 128, "MGCP tx");</span><br><span>      int rc = 0;</span><br><span style="color: hsl(0, 100%, 40%);">-     char local_ip[INET_ADDRSTRLEN];</span><br><span style="color: hsl(120, 100%, 40%);">+       int rc_sdp;</span><br><span style="color: hsl(120, 100%, 40%);">+   bool use_sdp = false;</span><br><span> </span><br><span>    msg->l2h = msg->data;</span><br><span>  msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id;</span><br><span>@@ -902,9 +1262,17 @@</span><br><span>                rc += msgb_printf(msg, "I: %s\r\n", mgcp_msg->conn_id);</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   /* Add local connection options */</span><br><span style="color: hsl(0, 100%, 40%);">-      if (mgcp_msg->verb == MGCP_VERB_CRCX)</span><br><span style="color: hsl(0, 100%, 40%);">-                rc += msgb_printf(msg, "L: p:20, a:AMR, nt:IN\r\n");</span><br><span style="color: hsl(120, 100%, 40%);">+        /* Using SDP makes sense when a valid IP/Port combination is specifiec,</span><br><span style="color: hsl(120, 100%, 40%);">+        * if we do not know this information yet, we fall back to LCO */</span><br><span style="color: hsl(120, 100%, 40%);">+     if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP</span><br><span style="color: hsl(120, 100%, 40%);">+        && mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT)</span><br><span style="color: hsl(120, 100%, 40%);">+              use_sdp = true;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* Add local connection options (LCO) */</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!use_sdp</span><br><span style="color: hsl(120, 100%, 40%);">+      && (mgcp_msg->verb == MGCP_VERB_CRCX</span><br><span style="color: hsl(120, 100%, 40%);">+           || mgcp_msg->verb == MGCP_VERB_MDCX))</span><br><span style="color: hsl(120, 100%, 40%);">+              rc += add_lco(msg, mgcp_msg);</span><br><span> </span><br><span>    /* Add mode */</span><br><span>       if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_MODE)</span><br><span>@@ -912,52 +1280,15 @@</span><br><span>                    msgb_printf(msg, "M: %s\r\n",</span><br><span>                          mgcp_client_cmode_name(mgcp_msg->conn_mode));</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    /* Add SDP body */</span><br><span style="color: hsl(0, 100%, 40%);">-      if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP</span><br><span style="color: hsl(0, 100%, 40%);">-          && mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT) {</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-              /* Add separator to mark the beginning of the SDP block */</span><br><span style="color: hsl(0, 100%, 40%);">-              rc += msgb_printf(msg, "\r\n");</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               /* Add SDP protocol version */</span><br><span style="color: hsl(0, 100%, 40%);">-          rc += msgb_printf(msg, "v=0\r\n");</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-            /* Determine local IP-Address */</span><br><span style="color: hsl(0, 100%, 40%);">-                if (osmo_sock_local_ip(local_ip, mgcp->actual.remote_addr) < 0) {</span><br><span style="color: hsl(0, 100%, 40%);">-                 LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">-                             "Could not determine local IP-Address!\n");</span><br><span style="color: hsl(0, 100%, 40%);">-                      msgb_free(msg);</span><br><span style="color: hsl(120, 100%, 40%);">+       /* Add session description protocol (SDP) */</span><br><span style="color: hsl(120, 100%, 40%);">+  if (use_sdp</span><br><span style="color: hsl(120, 100%, 40%);">+       && (mgcp_msg->verb == MGCP_VERB_CRCX</span><br><span style="color: hsl(120, 100%, 40%);">+           || mgcp_msg->verb == MGCP_VERB_MDCX)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            rc_sdp = add_sdp(msg, mgcp_msg, mgcp);</span><br><span style="color: hsl(120, 100%, 40%);">+                if (rc_sdp == -2)</span><br><span>                    return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               /* Add owner/creator (SDP) */</span><br><span style="color: hsl(0, 100%, 40%);">-           rc += msgb_printf(msg, "o=- %x 23 IN IP4 %s\r\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                               mgcp_msg->call_id, local_ip);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-              /* Add session name (none) */</span><br><span style="color: hsl(0, 100%, 40%);">-           rc += msgb_printf(msg, "s=-\r\n");</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-            /* Add RTP address and port */</span><br><span style="color: hsl(0, 100%, 40%);">-          if (mgcp_msg->audio_port == 0) {</span><br><span style="color: hsl(0, 100%, 40%);">-                     LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">-                             "Invalid port number, can not generate MGCP message\n");</span><br><span style="color: hsl(0, 100%, 40%);">-                 msgb_free(msg);</span><br><span style="color: hsl(0, 100%, 40%);">-                 return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(0, 100%, 40%);">-               if (strlen(mgcp_msg->audio_ip) <= 0) {</span><br><span style="color: hsl(0, 100%, 40%);">-                    LOGP(DLMGCP, LOGL_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">-                             "Empty ip address, can not generate MGCP message\n");</span><br><span style="color: hsl(0, 100%, 40%);">-                    msgb_free(msg);</span><br><span style="color: hsl(0, 100%, 40%);">-                 return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(0, 100%, 40%);">-               rc += msgb_printf(msg, "c=IN IP4 %s\r\n", mgcp_msg->audio_ip);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-             /* Add time description, active time (SDP) */</span><br><span style="color: hsl(0, 100%, 40%);">-           rc += msgb_printf(msg, "t=0 0\r\n");</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-          rc +=</span><br><span style="color: hsl(0, 100%, 40%);">-               msgb_printf(msg, "m=audio %u RTP/AVP 255\r\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                            mgcp_msg->audio_port);</span><br><span style="color: hsl(120, 100%, 40%);">+             else</span><br><span style="color: hsl(120, 100%, 40%);">+                  rc += rc_sdp;</span><br><span>        }</span><br><span> </span><br><span>        if (rc != 0) {</span><br><span>diff --git a/src/libosmo-mgcp-client/mgcp_client_fsm.c b/src/libosmo-mgcp-client/mgcp_client_fsm.c</span><br><span>index 10a5b6d..eb97949 100644</span><br><span>--- a/src/libosmo-mgcp-client/mgcp_client_fsm.c</span><br><span>+++ b/src/libosmo-mgcp-client/mgcp_client_fsm.c</span><br><span>@@ -113,8 +113,11 @@</span><br><span>               .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE),</span><br><span>          .call_id = mgcp_ctx->conn_peer_local.call_id,</span><br><span>             .conn_mode = MGCP_CONN_RECV_ONLY,</span><br><span style="color: hsl(120, 100%, 40%);">+             .ptime = mgcp_ctx->conn_peer_local.ptime,</span><br><span style="color: hsl(120, 100%, 40%);">+          .codecs_len = mgcp_ctx->conn_peer_local.codecs_len</span><br><span>        };</span><br><span>   osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN);</span><br><span style="color: hsl(120, 100%, 40%);">+ memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs));</span><br><span> </span><br><span>   return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);</span><br><span> }</span><br><span>@@ -124,15 +127,19 @@</span><br><span>  struct mgcp_msg mgcp_msg;</span><br><span> </span><br><span>        mgcp_msg = (struct mgcp_msg) {</span><br><span style="color: hsl(0, 100%, 40%);">-          .verb = MGCP_VERB_CRCX,.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |</span><br><span style="color: hsl(0, 100%, 40%);">-                                                MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |</span><br><span style="color: hsl(0, 100%, 40%);">-                                              MGCP_MSG_PRESENCE_AUDIO_PORT),</span><br><span style="color: hsl(120, 100%, 40%);">+            .verb = MGCP_VERB_CRCX,</span><br><span style="color: hsl(120, 100%, 40%);">+               .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |</span><br><span style="color: hsl(120, 100%, 40%);">+                      MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |</span><br><span style="color: hsl(120, 100%, 40%);">+                            MGCP_MSG_PRESENCE_AUDIO_PORT),</span><br><span>          .call_id = mgcp_ctx->conn_peer_local.call_id,</span><br><span>             .conn_mode = MGCP_CONN_RECV_SEND,</span><br><span>            .audio_ip = mgcp_ctx->conn_peer_local.addr,</span><br><span>               .audio_port = mgcp_ctx->conn_peer_local.port,</span><br><span style="color: hsl(120, 100%, 40%);">+              .ptime = mgcp_ctx->conn_peer_local.ptime,</span><br><span style="color: hsl(120, 100%, 40%);">+          .codecs_len = mgcp_ctx->conn_peer_local.codecs_len</span><br><span>        };</span><br><span>   osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN);</span><br><span style="color: hsl(120, 100%, 40%);">+ memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs));</span><br><span> </span><br><span>   return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg);</span><br><span> }</span><br><span>@@ -150,8 +157,11 @@</span><br><span>           .conn_mode = MGCP_CONN_RECV_SEND,</span><br><span>            .audio_ip = mgcp_ctx->conn_peer_local.addr,</span><br><span>               .audio_port = mgcp_ctx->conn_peer_local.port,</span><br><span style="color: hsl(120, 100%, 40%);">+              .ptime = mgcp_ctx->conn_peer_local.ptime,</span><br><span style="color: hsl(120, 100%, 40%);">+          .codecs_len = mgcp_ctx->conn_peer_local.codecs_len</span><br><span>        };</span><br><span>   osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN);</span><br><span style="color: hsl(120, 100%, 40%);">+        memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs));</span><br><span> </span><br><span>   /* Note: We take the endpoint and the call_id from the remote</span><br><span>         * connection info, because we can be confident that the</span><br><span>@@ -573,7 +583,7 @@</span><br><span>       OSMO_ASSERT(mgcp);</span><br><span>   OSMO_ASSERT(conn_peer);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-     /* Check if IP/Port informstaion in conn info makes sense */</span><br><span style="color: hsl(120, 100%, 40%);">+  /* Check if IP/Port information in conn info makes sense */</span><br><span>  if (conn_peer->port && inet_aton(conn_peer->addr, &ip_test) == 0)</span><br><span>          return NULL;</span><br><span> </span><br><span>diff --git a/tests/mgcp_client/mgcp_client_test.c b/tests/mgcp_client/mgcp_client_test.c</span><br><span>index 007b90c..9978f79 100644</span><br><span>--- a/tests/mgcp_client/mgcp_client_test.c</span><br><span>+++ b/tests/mgcp_client/mgcp_client_test.c</span><br><span>@@ -95,21 +95,26 @@</span><br><span> </span><br><span> void test_response_cb(struct mgcp_response *response, void *priv)</span><br><span> {</span><br><span style="color: hsl(120, 100%, 40%);">+     unsigned int i;</span><br><span>      OSMO_ASSERT(priv == mgcp);</span><br><span>   mgcp_response_parse_params(response);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-       printf("response cb received:\n"</span><br><span style="color: hsl(0, 100%, 40%);">-             "  head.response_code = %d\n"</span><br><span style="color: hsl(0, 100%, 40%);">-         "  head.trans_id = %u\n"</span><br><span style="color: hsl(0, 100%, 40%);">-              "  head.comment = %s\n"</span><br><span style="color: hsl(0, 100%, 40%);">-               "  audio_port = %u\n"</span><br><span style="color: hsl(0, 100%, 40%);">-         "  audio_ip = %s\n",</span><br><span style="color: hsl(0, 100%, 40%);">-          response->head.response_code,</span><br><span style="color: hsl(0, 100%, 40%);">-        response->head.trans_id,</span><br><span style="color: hsl(0, 100%, 40%);">-             response->head.comment,</span><br><span style="color: hsl(0, 100%, 40%);">-              response->audio_port,</span><br><span style="color: hsl(0, 100%, 40%);">-        response->audio_ip</span><br><span style="color: hsl(0, 100%, 40%);">-          );</span><br><span style="color: hsl(120, 100%, 40%);">+      printf("response cb received:\n");</span><br><span style="color: hsl(120, 100%, 40%);">+  printf("  head.response_code = %d\n", response->head.response_code);</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("  head.trans_id = %u\n", response->head.trans_id);</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("  head.comment = %s\n", response->head.comment);</span><br><span style="color: hsl(120, 100%, 40%);">+ printf("  audio_port = %u\n", response->audio_port);</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("  audio_ip = %s\n", response->audio_ip);</span><br><span style="color: hsl(120, 100%, 40%);">+ printf("  ptime = %u\n", response->ptime);</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("  codecs_len = %u\n", response->codecs_len);</span><br><span style="color: hsl(120, 100%, 40%);">+     for(i=0;i<response->codecs_len;i++)</span><br><span style="color: hsl(120, 100%, 40%);">+             printf("  codecs[%u] = %u\n", i, response->codecs[i]);</span><br><span style="color: hsl(120, 100%, 40%);">+   printf("  ptmap_len = %u\n", response->ptmap_len);</span><br><span style="color: hsl(120, 100%, 40%);">+       for(i=0;i<response->ptmap_len;i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+            printf("  ptmap[%u].codec = %u\n", i, response->ptmap[i].codec);</span><br><span style="color: hsl(120, 100%, 40%);">+         printf("  ptmap[%u].pt = %u\n", i, response->ptmap[i].pt);         </span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+     </span><br><span> }</span><br><span> </span><br><span> mgcp_trans_id_t dummy_mgcp_send(struct msgb *msg)</span><br><span>@@ -149,8 +154,9 @@</span><br><span>                 "s=-\r\n"</span><br><span>          "c=IN IP4 10.9.1.120\r\n"</span><br><span>          "t=0 0\r\n"</span><br><span style="color: hsl(0, 100%, 40%);">-           "m=audio 16002 RTP/AVP 98\r\n"</span><br><span style="color: hsl(0, 100%, 40%);">-                "a=rtpmap:98 AMR/8000\r\n"</span><br><span style="color: hsl(120, 100%, 40%);">+          "m=audio 16002 RTP/AVP 110 96\r\n"</span><br><span style="color: hsl(120, 100%, 40%);">+          "a=rtpmap:110 AMR/8000\r\n"</span><br><span style="color: hsl(120, 100%, 40%);">+         "a=rtpmap:96 GSM-EFR/8000\r\n"</span><br><span>             "a=ptime:20\r\n");</span><br><span> }</span><br><span> </span><br><span>@@ -166,7 +172,15 @@</span><br><span>                 .audio_port = 1234,</span><br><span>          .call_id = 47,</span><br><span>               .conn_id = "11",</span><br><span style="color: hsl(0, 100%, 40%);">-              .conn_mode = MGCP_CONN_RECV_SEND</span><br><span style="color: hsl(120, 100%, 40%);">+              .conn_mode = MGCP_CONN_RECV_SEND,</span><br><span style="color: hsl(120, 100%, 40%);">+             .ptime = 20,</span><br><span style="color: hsl(120, 100%, 40%);">+          .codecs[0] = CODEC_GSM_8000_1,</span><br><span style="color: hsl(120, 100%, 40%);">+                .codecs[1] = CODEC_AMR_8000_1,</span><br><span style="color: hsl(120, 100%, 40%);">+                .codecs[2] = CODEC_GSMEFR_8000_1,</span><br><span style="color: hsl(120, 100%, 40%);">+             .codecs_len = 1,</span><br><span style="color: hsl(120, 100%, 40%);">+              .ptmap[0].codec = CODEC_GSMEFR_8000_1,</span><br><span style="color: hsl(120, 100%, 40%);">+                .ptmap[0].pt = 96,</span><br><span style="color: hsl(120, 100%, 40%);">+            .ptmap_len = 1</span><br><span>       };</span><br><span> </span><br><span>       if (mgcp)</span><br><span>@@ -183,6 +197,26 @@</span><br><span>     msg = mgcp_msg_gen(mgcp, &mgcp_msg);</span><br><span>     printf("%s\n", (char *)msg->data);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+   printf("Generated CRCX message (two codecs):\n");</span><br><span style="color: hsl(120, 100%, 40%);">+   mgcp_msg.verb = MGCP_VERB_CRCX;</span><br><span style="color: hsl(120, 100%, 40%);">+       mgcp_msg.presence =</span><br><span style="color: hsl(120, 100%, 40%);">+       (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |</span><br><span style="color: hsl(120, 100%, 40%);">+      MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE);</span><br><span style="color: hsl(120, 100%, 40%);">+        mgcp_msg.codecs_len = 2;</span><br><span style="color: hsl(120, 100%, 40%);">+      msg = mgcp_msg_gen(mgcp, &mgcp_msg);</span><br><span style="color: hsl(120, 100%, 40%);">+      mgcp_msg.codecs_len = 1;        </span><br><span style="color: hsl(120, 100%, 40%);">+      printf("%s\n", (char *)msg->data);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Generated CRCX message (three codecs, one with custom pt):\n");</span><br><span style="color: hsl(120, 100%, 40%);">+     mgcp_msg.verb = MGCP_VERB_CRCX;</span><br><span style="color: hsl(120, 100%, 40%);">+       mgcp_msg.presence =</span><br><span style="color: hsl(120, 100%, 40%);">+       (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |</span><br><span style="color: hsl(120, 100%, 40%);">+      MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE);</span><br><span style="color: hsl(120, 100%, 40%);">+        mgcp_msg.codecs_len = 3;</span><br><span style="color: hsl(120, 100%, 40%);">+      msg = mgcp_msg_gen(mgcp, &mgcp_msg);</span><br><span style="color: hsl(120, 100%, 40%);">+      mgcp_msg.codecs_len = 1;        </span><br><span style="color: hsl(120, 100%, 40%);">+      printf("%s\n", (char *)msg->data);         </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>   printf("Generated MDCX message:\n");</span><br><span>       mgcp_msg.verb = MGCP_VERB_MDCX;</span><br><span>      mgcp_msg.presence =</span><br><span>@@ -192,6 +226,28 @@</span><br><span>   msg = mgcp_msg_gen(mgcp, &mgcp_msg);</span><br><span>     printf("%s\n", (char *)msg->data);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+   printf("Generated MDCX message (two codecs):\n");</span><br><span style="color: hsl(120, 100%, 40%);">+   mgcp_msg.verb = MGCP_VERB_MDCX;</span><br><span style="color: hsl(120, 100%, 40%);">+       mgcp_msg.presence =</span><br><span style="color: hsl(120, 100%, 40%);">+       (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |</span><br><span style="color: hsl(120, 100%, 40%);">+      MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE |</span><br><span style="color: hsl(120, 100%, 40%);">+             MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT);</span><br><span style="color: hsl(120, 100%, 40%);">+      mgcp_msg.codecs_len = 2;</span><br><span style="color: hsl(120, 100%, 40%);">+      msg = mgcp_msg_gen(mgcp, &mgcp_msg);</span><br><span style="color: hsl(120, 100%, 40%);">+      mgcp_msg.codecs_len = 1;        </span><br><span style="color: hsl(120, 100%, 40%);">+      printf("%s\n", (char *)msg->data);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     printf("Generated MDCX message (three codecs, one with custom pt):\n");</span><br><span style="color: hsl(120, 100%, 40%);">+     mgcp_msg.verb = MGCP_VERB_MDCX;</span><br><span style="color: hsl(120, 100%, 40%);">+       mgcp_msg.presence =</span><br><span style="color: hsl(120, 100%, 40%);">+       (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |</span><br><span style="color: hsl(120, 100%, 40%);">+      MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE |</span><br><span style="color: hsl(120, 100%, 40%);">+             MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT);</span><br><span style="color: hsl(120, 100%, 40%);">+      mgcp_msg.codecs_len = 3;</span><br><span style="color: hsl(120, 100%, 40%);">+      msg = mgcp_msg_gen(mgcp, &mgcp_msg);</span><br><span style="color: hsl(120, 100%, 40%);">+      mgcp_msg.codecs_len = 1;        </span><br><span style="color: hsl(120, 100%, 40%);">+      printf("%s\n", (char *)msg->data); </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>   printf("Generated DLCX message:\n");</span><br><span>       mgcp_msg.verb = MGCP_VERB_DLCX;</span><br><span>      mgcp_msg.presence =</span><br><span>@@ -242,6 +298,9 @@</span><br><span>            .conn_mode = MGCP_CONN_RECV_SEND,</span><br><span>            .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID</span><br><span>                       | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE),</span><br><span style="color: hsl(120, 100%, 40%);">+              .ptime = 20,</span><br><span style="color: hsl(120, 100%, 40%);">+          .codecs[0] = CODEC_AMR_8000_1,</span><br><span style="color: hsl(120, 100%, 40%);">+                .codecs_len = 1         </span><br><span>     };</span><br><span> </span><br><span>       printf("\n%s():\n", __func__);</span><br><span>@@ -376,6 +435,99 @@</span><br><span>      OSMO_ASSERT(!failures);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static void test_map_pt_to_codec(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Full form */</span><br><span style="color: hsl(120, 100%, 40%);">+       OSMO_ASSERT(map_str_to_codec("PCMU/8000/1") == CODEC_PCMU_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(map_str_to_codec("GSM/8000/1") == CODEC_GSM_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("PCMA/8000/1") == CODEC_PCMA_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(map_str_to_codec("G729/8000/1") == CODEC_G729_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(map_str_to_codec("GSM-EFR/8000/1") == CODEC_GSMEFR_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_ASSERT(map_str_to_codec("GSM-HR-08/8000/1") == CODEC_GSMHR_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("AMR/8000/1") == CODEC_AMR_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("AMR-WB/16000/1") == CODEC_AMRWB_16000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Short form */</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_ASSERT(map_str_to_codec("GSM-EFR") == CODEC_GSMEFR_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("G729") == CODEC_G729_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_ASSERT(map_str_to_codec("GSM-HR-08") == CODEC_GSMHR_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* We do not care about what is after the first delimiter */</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(map_str_to_codec("AMR-WB/123///456") == CODEC_AMRWB_16000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+   OSMO_ASSERT(map_str_to_codec("PCMA/asdf") == CODEC_PCMA_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("GSM/qwertz") == CODEC_GSM_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  /* A trailing delimiter should not hurt */</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("AMR/") == CODEC_AMR_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+  OSMO_ASSERT(map_str_to_codec("G729/") == CODEC_G729_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+        OSMO_ASSERT(map_str_to_codec("GSM/") == CODEC_GSM_8000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        /* This is expected to fail */</span><br><span style="color: hsl(120, 100%, 40%);">+        OSMO_ASSERT(map_str_to_codec("INVALID/1234/7") == -1);</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_ASSERT(map_str_to_codec(NULL) == -1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("") == -1);</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("/////") == -1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* The buffers are 64 bytes long, check what happens with overlong</span><br><span style="color: hsl(120, 100%, 40%);">+     * strings as input (This schould still work.) */</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_ASSERT(map_str_to_codec("AMR-WB/16000/1############################################################################################################") == CODEC_AMRWB_16000_1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       /* This should not work, as there is no delimiter after the codec</span><br><span style="color: hsl(120, 100%, 40%);">+      * name */</span><br><span style="color: hsl(120, 100%, 40%);">+    OSMO_ASSERT(map_str_to_codec("AMR-WB####################################################################################################################") == -1);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void test_map_codec_to_pt_and_map_pt_to_codec(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ptmap ptmap[10];</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int ptmap_len;</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     ptmap[0].codec = CODEC_GSMEFR_8000_1;</span><br><span style="color: hsl(120, 100%, 40%);">+ ptmap[0].pt = 96;</span><br><span style="color: hsl(120, 100%, 40%);">+     ptmap[1].codec = CODEC_GSMHR_8000_1;</span><br><span style="color: hsl(120, 100%, 40%);">+  ptmap[1].pt = 97;</span><br><span style="color: hsl(120, 100%, 40%);">+     ptmap[2].codec = CODEC_AMR_8000_1;</span><br><span style="color: hsl(120, 100%, 40%);">+    ptmap[2].pt = 98;</span><br><span style="color: hsl(120, 100%, 40%);">+     ptmap[3].codec = CODEC_AMRWB_16000_1;</span><br><span style="color: hsl(120, 100%, 40%);">+ ptmap[3].pt = 99;</span><br><span style="color: hsl(120, 100%, 40%);">+     ptmap_len = 4;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      /* Mappings that are covered by the table */</span><br><span style="color: hsl(120, 100%, 40%);">+  for (i = 0; i < ptmap_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+            printf(" %u => %u\n", ptmap[i].codec, map_codec_to_pt(ptmap, ptmap_len, ptmap[i].codec));</span><br><span style="color: hsl(120, 100%, 40%);">+        for (i = 0; i < ptmap_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+            printf(" %u <= %u\n", ptmap[i].pt, map_pt_to_codec(ptmap, ptmap_len, ptmap[i].pt));</span><br><span style="color: hsl(120, 100%, 40%);">+      printf("\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* Map some codecs/payload types from the static range, result must</span><br><span style="color: hsl(120, 100%, 40%);">+    * always be a 1:1 mapping */</span><br><span style="color: hsl(120, 100%, 40%);">+ printf(" %u => %u\n", CODEC_PCMU_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_PCMU_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+  printf(" %u => %u\n", CODEC_GSM_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_GSM_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+    printf(" %u => %u\n", CODEC_PCMA_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_PCMA_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+  printf(" %u => %u\n", CODEC_G729_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_G729_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+  printf(" %u <= %u\n", CODEC_PCMU_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_PCMU_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+  printf(" %u <= %u\n", CODEC_GSM_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_GSM_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+    printf(" %u <= %u\n", CODEC_PCMA_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_PCMA_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+  printf(" %u <= %u\n", CODEC_G729_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_G729_8000_1));</span><br><span style="color: hsl(120, 100%, 40%);">+  printf("\n");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* Try to do mappings from statically defined range to danymic range and vice versa. This</span><br><span style="color: hsl(120, 100%, 40%);">+      * is illegal and should result into a 1:1 mapping */</span><br><span style="color: hsl(120, 100%, 40%);">+ ptmap[3].codec = CODEC_AMRWB_16000_1;</span><br><span style="color: hsl(120, 100%, 40%);">+ ptmap[3].pt = 2;</span><br><span style="color: hsl(120, 100%, 40%);">+      ptmap[4].codec = CODEC_PCMU_8000_1;</span><br><span style="color: hsl(120, 100%, 40%);">+   ptmap[4].pt = 100;</span><br><span style="color: hsl(120, 100%, 40%);">+    ptmap_len = 5;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      /* Apply all mappings again, the illegal ones we defined should result into 1:1 mappings */</span><br><span style="color: hsl(120, 100%, 40%);">+   for (i = 0; i < ptmap_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+            printf(" %u => %u\n", ptmap[i].codec, map_codec_to_pt(ptmap, ptmap_len, ptmap[i].codec));</span><br><span style="color: hsl(120, 100%, 40%);">+        for (i = 0; i < ptmap_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+            printf(" %u <= %u\n", ptmap[i].pt, map_pt_to_codec(ptmap, ptmap_len, ptmap[i].pt));</span><br><span style="color: hsl(120, 100%, 40%);">+      printf("\n");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static const struct log_info_cat log_categories[] = {</span><br><span> };</span><br><span> </span><br><span>@@ -403,6 +555,8 @@</span><br><span>        test_mgcp_msg();</span><br><span>     test_mgcp_client_cancel();</span><br><span>   test_sdp_section_start();</span><br><span style="color: hsl(120, 100%, 40%);">+     test_map_codec_to_pt_and_map_pt_to_codec();</span><br><span style="color: hsl(120, 100%, 40%);">+   test_map_pt_to_codec();</span><br><span> </span><br><span>  printf("Done\n");</span><br><span>  fprintf(stderr, "Done\n");</span><br><span>diff --git a/tests/mgcp_client/mgcp_client_test.err b/tests/mgcp_client/mgcp_client_test.err</span><br><span>index 7309242..1d5a1a0 100644</span><br><span>--- a/tests/mgcp_client/mgcp_client_test.err</span><br><span>+++ b/tests/mgcp_client/mgcp_client_test.err</span><br><span>@@ -62,4 +62,8 @@</span><br><span> body: "some mgcp header data\r\nand header params\n\r\rm=audio 23\r\n"</span><br><span> DLMGCP MGCP response: cannot find start of SDP parameters</span><br><span> got rc=-22</span><br><span style="color: hsl(120, 100%, 40%);">+DLMGCP ptmap contains illegal mapping: codec=113 maps to pt=2</span><br><span style="color: hsl(120, 100%, 40%);">+DLMGCP ptmap contains illegal mapping: codec=0 maps to pt=100</span><br><span style="color: hsl(120, 100%, 40%);">+DLMGCP ptmap contains illegal mapping: codec=113 maps to pt=2</span><br><span style="color: hsl(120, 100%, 40%);">+DLMGCP ptmap contains illegal mapping: codec=0 maps to pt=100</span><br><span> Done</span><br><span>diff --git a/tests/mgcp_client/mgcp_client_test.ok b/tests/mgcp_client/mgcp_client_test.ok</span><br><span>index fc6db30..454ee3d 100644</span><br><span>--- a/tests/mgcp_client/mgcp_client_test.ok</span><br><span>+++ b/tests/mgcp_client/mgcp_client_test.ok</span><br><span>@@ -18,8 +18,9 @@</span><br><span> s=- </span><br><span> c=IN IP4 10.9.1.120 </span><br><span> t=0 0 </span><br><span style="color: hsl(0, 100%, 40%);">-m=audio 16002 RTP/AVP 98 </span><br><span style="color: hsl(0, 100%, 40%);">-a=rtpmap:98 AMR/8000 </span><br><span style="color: hsl(120, 100%, 40%);">+m=audio 16002 RTP/AVP 110 96 </span><br><span style="color: hsl(120, 100%, 40%);">+a=rtpmap:110 AMR/8000 </span><br><span style="color: hsl(120, 100%, 40%);">+a=rtpmap:96 GSM-EFR/8000 </span><br><span> a=ptime:20 </span><br><span> </span><br><span> -----</span><br><span>@@ -29,16 +30,39 @@</span><br><span>   head.comment = OK</span><br><span>   audio_port = 16002</span><br><span>   audio_ip = 10.9.1.120</span><br><span style="color: hsl(120, 100%, 40%);">+  ptime = 20</span><br><span style="color: hsl(120, 100%, 40%);">+  codecs_len = 2</span><br><span style="color: hsl(120, 100%, 40%);">+  codecs[0] = 112</span><br><span style="color: hsl(120, 100%, 40%);">+  codecs[1] = 110</span><br><span style="color: hsl(120, 100%, 40%);">+  ptmap_len = 2</span><br><span style="color: hsl(120, 100%, 40%);">+  ptmap[0].codec = 112</span><br><span style="color: hsl(120, 100%, 40%);">+  ptmap[0].pt = 110</span><br><span style="color: hsl(120, 100%, 40%);">+  ptmap[1].codec = 110</span><br><span style="color: hsl(120, 100%, 40%);">+  ptmap[1].pt = 96</span><br><span> </span><br><span> Generated CRCX message:</span><br><span> CRCX 1 23@mgw MGCP 1.0 </span><br><span> C: 2f </span><br><span> I: 11 </span><br><span style="color: hsl(0, 100%, 40%);">-L: p:20, a:AMR, nt:IN </span><br><span style="color: hsl(120, 100%, 40%);">+L: p:20, a:GSM, nt:IN </span><br><span style="color: hsl(120, 100%, 40%);">+M: sendrecv </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Generated CRCX message (two codecs):</span><br><span style="color: hsl(120, 100%, 40%);">+CRCX 2 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+C: 2f </span><br><span style="color: hsl(120, 100%, 40%);">+I: 11 </span><br><span style="color: hsl(120, 100%, 40%);">+L: p:20, a:GSM;AMR, nt:IN </span><br><span style="color: hsl(120, 100%, 40%);">+M: sendrecv </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Generated CRCX message (three codecs, one with custom pt):</span><br><span style="color: hsl(120, 100%, 40%);">+CRCX 3 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+C: 2f </span><br><span style="color: hsl(120, 100%, 40%);">+I: 11 </span><br><span style="color: hsl(120, 100%, 40%);">+L: p:20, a:GSM;AMR;GSM-EFR, nt:IN </span><br><span> M: sendrecv </span><br><span> </span><br><span> Generated MDCX message:</span><br><span style="color: hsl(0, 100%, 40%);">-MDCX 2 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+MDCX 4 23@mgw MGCP 1.0 </span><br><span> C: 2f </span><br><span> I: 11 </span><br><span> M: sendrecv </span><br><span>@@ -48,18 +72,50 @@</span><br><span> s=- </span><br><span> c=IN IP4 192.168.100.23 </span><br><span> t=0 0 </span><br><span style="color: hsl(0, 100%, 40%);">-m=audio 1234 RTP/AVP 255 </span><br><span style="color: hsl(120, 100%, 40%);">+m=audio 1234 RTP/AVP 3 </span><br><span style="color: hsl(120, 100%, 40%);">+a=ptime:20 </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Generated MDCX message (two codecs):</span><br><span style="color: hsl(120, 100%, 40%);">+MDCX 5 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+C: 2f </span><br><span style="color: hsl(120, 100%, 40%);">+I: 11 </span><br><span style="color: hsl(120, 100%, 40%);">+M: sendrecv </span><br><span style="color: hsl(120, 100%, 40%);">+ </span><br><span style="color: hsl(120, 100%, 40%);">+v=0 </span><br><span style="color: hsl(120, 100%, 40%);">+o=- 2f 23 IN IP4 127.0.0.1 </span><br><span style="color: hsl(120, 100%, 40%);">+s=- </span><br><span style="color: hsl(120, 100%, 40%);">+c=IN IP4 192.168.100.23 </span><br><span style="color: hsl(120, 100%, 40%);">+t=0 0 </span><br><span style="color: hsl(120, 100%, 40%);">+m=audio 1234 RTP/AVP 3 112 </span><br><span style="color: hsl(120, 100%, 40%);">+a=rtpmap:112 AMR/8000/1 </span><br><span style="color: hsl(120, 100%, 40%);">+a=ptime:20 </span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Generated MDCX message (three codecs, one with custom pt):</span><br><span style="color: hsl(120, 100%, 40%);">+MDCX 6 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+C: 2f </span><br><span style="color: hsl(120, 100%, 40%);">+I: 11 </span><br><span style="color: hsl(120, 100%, 40%);">+M: sendrecv </span><br><span style="color: hsl(120, 100%, 40%);">+ </span><br><span style="color: hsl(120, 100%, 40%);">+v=0 </span><br><span style="color: hsl(120, 100%, 40%);">+o=- 2f 23 IN IP4 127.0.0.1 </span><br><span style="color: hsl(120, 100%, 40%);">+s=- </span><br><span style="color: hsl(120, 100%, 40%);">+c=IN IP4 192.168.100.23 </span><br><span style="color: hsl(120, 100%, 40%);">+t=0 0 </span><br><span style="color: hsl(120, 100%, 40%);">+m=audio 1234 RTP/AVP 3 112 96 </span><br><span style="color: hsl(120, 100%, 40%);">+a=rtpmap:112 AMR/8000/1 </span><br><span style="color: hsl(120, 100%, 40%);">+a=rtpmap:96 GSM-EFR/8000/1 </span><br><span style="color: hsl(120, 100%, 40%);">+a=ptime:20 </span><br><span> </span><br><span> Generated DLCX message:</span><br><span style="color: hsl(0, 100%, 40%);">-DLCX 3 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+DLCX 7 23@mgw MGCP 1.0 </span><br><span> C: 2f </span><br><span> I: 11 </span><br><span> </span><br><span> Generated AUEP message:</span><br><span style="color: hsl(0, 100%, 40%);">-AUEP 4 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+AUEP 8 23@mgw MGCP 1.0 </span><br><span> </span><br><span> Generated RSIP message:</span><br><span style="color: hsl(0, 100%, 40%);">-RSIP 5 23@mgw MGCP 1.0 </span><br><span style="color: hsl(120, 100%, 40%);">+RSIP 9 23@mgw MGCP 1.0 </span><br><span> </span><br><span> Overfolow test:</span><br><span> </span><br><span>@@ -102,4 +158,33 @@</span><br><span> test_sdp_section_start() test [8]:</span><br><span> </span><br><span> test_sdp_section_start() test [9]:</span><br><span style="color: hsl(120, 100%, 40%);">+ 110 => 96</span><br><span style="color: hsl(120, 100%, 40%);">+ 111 => 97</span><br><span style="color: hsl(120, 100%, 40%);">+ 112 => 98</span><br><span style="color: hsl(120, 100%, 40%);">+ 113 => 99</span><br><span style="color: hsl(120, 100%, 40%);">+ 96 <= 110</span><br><span style="color: hsl(120, 100%, 40%);">+ 97 <= 111</span><br><span style="color: hsl(120, 100%, 40%);">+ 98 <= 112</span><br><span style="color: hsl(120, 100%, 40%);">+ 99 <= 113</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ 0 => 0</span><br><span style="color: hsl(120, 100%, 40%);">+ 3 => 3</span><br><span style="color: hsl(120, 100%, 40%);">+ 8 => 8</span><br><span style="color: hsl(120, 100%, 40%);">+ 18 => 18</span><br><span style="color: hsl(120, 100%, 40%);">+ 0 <= 0</span><br><span style="color: hsl(120, 100%, 40%);">+ 3 <= 3</span><br><span style="color: hsl(120, 100%, 40%);">+ 8 <= 8</span><br><span style="color: hsl(120, 100%, 40%);">+ 18 <= 18</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ 110 => 96</span><br><span style="color: hsl(120, 100%, 40%);">+ 111 => 97</span><br><span style="color: hsl(120, 100%, 40%);">+ 112 => 98</span><br><span style="color: hsl(120, 100%, 40%);">+ 113 => 113</span><br><span style="color: hsl(120, 100%, 40%);">+ 0 => 0</span><br><span style="color: hsl(120, 100%, 40%);">+ 96 <= 110</span><br><span style="color: hsl(120, 100%, 40%);">+ 97 <= 111</span><br><span style="color: hsl(120, 100%, 40%);">+ 98 <= 112</span><br><span style="color: hsl(120, 100%, 40%);">+ 2 <= 2</span><br><span style="color: hsl(120, 100%, 40%);">+ 100 <= 100</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> Done</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/9649">change 9649</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/9649"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: osmo-mgw </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I78e72d41b73acfcb40599a0ff4823f17c3642059 </div>
<div style="display:none"> Gerrit-Change-Number: 9649 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: dexter <pmaier@sysmocom.de> </div>