blob: a2a2d3d9a01c04f0c782b862d6cfff6eb165c516 [file] [log] [blame]
Neels Hofmeyreef45782019-10-21 03:24:04 +02001/* Minimalistic SDP parse/compose implementation, focused on GSM audio codecs */
2/*
3 * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
4 * All Rights Reserved
5 *
6 * SPDX-License-Identifier: AGPL-3.0+
7 *
8 * Author: Neels Hofmeyr
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include <string.h>
25#include <errno.h>
26
27#include <osmocom/core/utils.h>
28#include <osmocom/core/logging.h>
29
30#include <osmocom/msc/debug.h>
31#include <osmocom/msc/sdp_msg.h>
32
33/* Compare name, rate and fmtp, returning typical cmp result: 0 on match, and -1 / 1 on mismatch.
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010034 * If cmp_fmtp is false, do *not* compare the fmtp string; if true, compare fmtp 1:1 as strings.
35 * If cmp_payload_type is false, do *not* compare the payload_type number.
Neels Hofmeyreef45782019-10-21 03:24:04 +020036 * The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even
37 * though all parameters are the same. */
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010038int sdp_audio_codec_cmp(const struct sdp_audio_codec *a, const struct sdp_audio_codec *b,
39 bool cmp_fmtp, bool cmp_payload_type)
Neels Hofmeyreef45782019-10-21 03:24:04 +020040{
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010041 int cmp;
Neels Hofmeyreef45782019-10-21 03:24:04 +020042 if (a == b)
43 return 0;
44 if (!a)
45 return -1;
46 if (!b)
47 return 1;
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010048 cmp = strncmp(a->subtype_name, b->subtype_name, sizeof(a->subtype_name));
49 if (cmp)
50 return cmp;
51 cmp = OSMO_CMP(a->rate, b->rate);
52 if (cmp)
53 return cmp;
54 if (cmp_fmtp) {
55 cmp = strncmp(a->fmtp, b->fmtp, sizeof(a->fmtp));
56 if (cmp)
57 return cmp;
58 }
59 if (cmp_payload_type) {
60 cmp = OSMO_CMP(a->payload_type, b->payload_type);
61 if (cmp)
62 return cmp;
63 }
64 return 0;
65}
Neels Hofmeyreef45782019-10-21 03:24:04 +020066
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010067/* Compare two lists of audio codecs, returning typical cmp result: 0 on match, and -1 / 1 on mismatch.
68 * The ordering in the two lists may differ, except that the first codec in 'a' must also be the first codec in 'b'.
69 * This is because the first codec typically expresses the preferred codec to use.
70 * If cmp_fmtp is false, do *not* compare the fmtp strings; if true, compare fmtp 1:1 as strings.
71 * If cmp_payload_type is false, do *not* compare the payload_type numbers.
72 * The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even
73 * though all parameters are the same. */
74int sdp_audio_codecs_cmp(const struct sdp_audio_codecs *a, const struct sdp_audio_codecs *b,
75 bool cmp_fmtp, bool cmp_payload_type)
76{
77 const struct sdp_audio_codec *codec_a;
78 const struct sdp_audio_codec *codec_b;
79 int cmp;
80 if (a == b)
81 return 0;
82 if (!a)
Neels Hofmeyreef45782019-10-21 03:24:04 +020083 return -1;
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010084 if (!b)
Neels Hofmeyreef45782019-10-21 03:24:04 +020085 return 1;
86
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010087 cmp = OSMO_CMP(a->count, b->count);
88 if (cmp)
89 return cmp;
90
91 if (!a->count)
92 return 0;
93
94 /* The first codec is the "chosen" codec and should match. The others may appear in different order. */
95 cmp = sdp_audio_codec_cmp(&a->codec[0], &b->codec[0], cmp_fmtp, cmp_payload_type);
96 if (cmp)
97 return cmp;
98
99 /* See if each codec in a is also present in b */
100 foreach_sdp_audio_codec(codec_a, a) {
101 bool match_found = false;
102 foreach_sdp_audio_codec(codec_b, b) {
103 if (!sdp_audio_codec_cmp(codec_a, codec_b, cmp_fmtp, cmp_payload_type)) {
104 match_found = true;
105 break;
106 }
107 }
108 if (!match_found)
109 return -1;
110 }
Neels Hofmeyreef45782019-10-21 03:24:04 +0200111
112 return 0;
113}
114
115/* Given a predefined fixed payload_type number, add an SDP audio codec entry, if not present yet.
116 * The payload_type must exist in sdp_msg_payload_type_names.
117 * Return the audio codec created or already existing for this payload type number.
118 */
119struct sdp_audio_codec *sdp_audio_codec_add(struct sdp_audio_codecs *ac, unsigned int payload_type,
120 const char *subtype_name, unsigned int rate, const char *fmtp)
121{
122 struct sdp_audio_codec *codec;
123
124 /* Does an entry already exist? */
125 codec = sdp_audio_codec_by_payload_type(ac, payload_type, false);
126 if (codec) {
127 /* Already exists, sanity check */
128 if (!codec->subtype_name[0])
129 OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
130 else if (strcmp(codec->subtype_name, subtype_name)) {
131 /* There already is an entry with this payload_type number but a mismatching subtype_name. That is
132 * weird, rather abort. */
133 return NULL;
134 }
135 if (codec->rate != rate
136 || (fmtp && strcmp(fmtp, codec->fmtp))) {
137 /* Mismatching details. Rather abort */
138 return NULL;
139 }
140 return codec;
141 }
142
143 /* None exists, create codec entry for this payload type number */
144 codec = sdp_audio_codec_by_payload_type(ac, payload_type, true);
145 /* NULL means unable to add an entry */
146 if (!codec)
147 return NULL;
148
149 OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
150 if (fmtp)
151 OSMO_STRLCPY_ARRAY(codec->fmtp, fmtp);
152 return codec;
153}
154
155struct sdp_audio_codec *sdp_audio_codec_add_copy(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
156{
157 return sdp_audio_codec_add(ac, codec->payload_type, codec->subtype_name, codec->rate,
158 codec->fmtp[0] ? codec->fmtp : NULL);
159}
160
161struct sdp_audio_codec *sdp_audio_codec_by_payload_type(struct sdp_audio_codecs *ac, unsigned int payload_type,
162 bool create)
163{
164 struct sdp_audio_codec *codec;
165 foreach_sdp_audio_codec(codec, ac) {
166 if (codec->payload_type == payload_type)
167 return codec;
168 }
169
170 if (!create)
171 return NULL;
172
173 /* Not found; codec points after the last entry now. */
174 if ((codec - ac->codec) >= ARRAY_SIZE(ac->codec))
175 return NULL;
176
177 *codec = (struct sdp_audio_codec){
178 .payload_type = payload_type,
179 .rate = 8000,
180 };
181
182 ac->count = (codec - ac->codec) + 1;
183 return codec;
184}
185
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +0100186/* Return a given sdp_msg's codec entry that matches the subtype_name and rate of the given codec, or NULL if no
187 * match is found. Comparison is made by sdp_audio_codec_cmp(cmp_payload_type=false). */
Neels Hofmeyreef45782019-10-21 03:24:04 +0200188struct sdp_audio_codec *sdp_audio_codec_by_descr(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
189{
190 struct sdp_audio_codec *i;
191 foreach_sdp_audio_codec(i, ac) {
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +0100192 if (!sdp_audio_codec_cmp(i, codec, false, false))
Neels Hofmeyreef45782019-10-21 03:24:04 +0200193 return i;
194 }
195 return NULL;
196}
197
198/* Remove the codec entry pointed at by 'codec'. 'codec' must point at an entry of 'sdp' (to use an external codec
199 * instance, use sdp_audio_codec_by_descr()).
200 * Return 0 on success, -ENOENT if codec does not point at the sdp->codec array. */
201int sdp_audio_codec_remove(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
202{
203 struct sdp_audio_codec *i;
204 if ((codec < ac->codec)
205 || ((codec - ac->codec) >= OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec))))
206 return -ENOENT;
207
208 /* Move all following entries one up */
209 ac->count--;
210 foreach_sdp_audio_codec(i, ac) {
211 if (i < codec)
212 continue;
213 *i = *(i+1);
214 }
215 return 0;
216}
217
218/* Convert struct sdp_msg to the actual SDP protocol representation */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100219int sdp_msg_to_sdp_str_buf(char *dst, size_t dst_size, const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200220{
221 const struct sdp_audio_codec *codec;
222 struct osmo_strbuf sb = { .buf = dst, .len = dst_size };
223 const char *ip = sdp->rtp.ip[0] ? sdp->rtp.ip : "0.0.0.0";
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200224 char ipv = osmo_ip_str_type(ip) == AF_INET6 ? '6' : '4';
Neels Hofmeyreef45782019-10-21 03:24:04 +0200225
226 OSMO_STRBUF_PRINTF(sb,
227 "v=0\r\n"
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200228 "o=OsmoMSC 0 0 IN IP%c %s\r\n"
Neels Hofmeyreef45782019-10-21 03:24:04 +0200229 "s=GSM Call\r\n"
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200230 "c=IN IP%c %s\r\n"
Neels Hofmeyreef45782019-10-21 03:24:04 +0200231 "t=0 0\r\n"
232 "m=audio %d RTP/AVP",
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200233 ipv, ip, ipv, ip,
Neels Hofmeyreef45782019-10-21 03:24:04 +0200234 sdp->rtp.port);
235
236 /* Append all payload type numbers to 'm=audio <port> RTP/AVP 3 4 112' line */
237 foreach_sdp_audio_codec(codec, &sdp->audio_codecs)
238 OSMO_STRBUF_PRINTF(sb, " %d", codec->payload_type);
239 OSMO_STRBUF_PRINTF(sb, "\r\n");
240
241 /* Add details for all codecs */
242 foreach_sdp_audio_codec(codec, &sdp->audio_codecs) {
243 if (codec->subtype_name[0]) {
244 OSMO_STRBUF_PRINTF(sb, "a=rtpmap:%d %s/%d\r\n", codec->payload_type, codec->subtype_name,
245 codec->rate > 0? codec->rate : 8000);
246 }
247
248 if (codec->fmtp[0])
249 OSMO_STRBUF_PRINTF(sb, "a=fmtp:%d %s\r\n", codec->payload_type, codec->fmtp);
250 }
251
252 OSMO_STRBUF_PRINTF(sb, "a=ptime:%d\r\n", sdp->ptime > 0? sdp->ptime : 20);
253
254 return sb.chars_needed;
255}
256
257/* Return the first line ending (or the end of the string) at or after the given string position. */
258const char *sdp_msg_line_end(const char *src)
259{
260 const char *line_end = strchr(src, '\r');
261 if (!line_end)
262 line_end = strchr(src, '\n');
263 if (!line_end)
264 line_end = src + strlen(src);
265 return line_end;
266}
267
268/* parse a line like 'a=rtpmap:0 PCMU/8000', 'a=fmtp:112 octet-align=1; mode-set=4', 'a=ptime:20'.
269 * The src should point at the character after 'a=', e.g. at the start of 'rtpmap', 'fmtp', 'ptime'
270 */
271int sdp_parse_attrib(struct sdp_msg *sdp, const char *src)
272{
273 unsigned int payload_type;
274 struct sdp_audio_codec *codec;
275#define A_RTPMAP "rtpmap:"
276#define A_FMTP "fmtp:"
277#define A_PTIME "ptime:"
278#define A_RTCP "rtcp:"
279#define A_SENDRECV "sendrecv"
280#define A_SENDONLY "sendonly"
281#define A_RECVONLY "recvonly"
282
283 if (osmo_str_startswith(src, A_RTPMAP)) {
284 char *audio_name;
285 unsigned int channels = 1;
286 if (sscanf(src, A_RTPMAP "%u", &payload_type) != 1)
287 return -EINVAL;
288
289 audio_name = strchr(src, ' ');
290 if (!audio_name || audio_name >= sdp_msg_line_end(src))
291 return -EINVAL;
292
293 codec = sdp_audio_codec_by_payload_type(&sdp->audio_codecs, payload_type, true);
294 if (!codec)
295 return -ENOSPC;
296
297 if (sscanf(audio_name, " %31[^/]/%u/%u", codec->subtype_name, &codec->rate, &channels) < 1)
298 return -EINVAL;
299
300 if (channels != 1)
301 return -ENOTSUP;
302 }
303
304 else if (osmo_str_startswith(src, A_FMTP)) {
305 char *fmtp_str;
306 const char *line_end = sdp_msg_line_end(src);
307 if (sscanf(src, A_FMTP "%u", &payload_type) != 1)
308 return -EINVAL;
309
310 fmtp_str = strchr(src, ' ');
311 if (!fmtp_str)
312 return -EINVAL;
313 fmtp_str++;
314 if (fmtp_str >= line_end)
315 return -EINVAL;
316
317 codec = sdp_audio_codec_by_payload_type(&sdp->audio_codecs, payload_type, true);
318 if (!codec)
319 return -ENOSPC;
320
321 /* (+1 because osmo_strlcpy() interprets it as size including the '\0') */
322 osmo_strlcpy(codec->fmtp, fmtp_str, line_end - fmtp_str + 1);
323 }
324
325 else if (osmo_str_startswith(src, A_PTIME)) {
326 if (sscanf(src, A_PTIME "%u", &sdp->ptime) != 1)
327 return -EINVAL;
328
329 }
330
331 else if (osmo_str_startswith(src, A_RTCP)) {
332 /* TODO? */
333 }
334
335 else if (osmo_str_startswith(src, A_SENDRECV)) {
336 /* TODO? */
337 }
338
339 else if (osmo_str_startswith(src, A_SENDONLY)) {
340 /* TODO? */
341 }
342
343 else if (osmo_str_startswith(src, A_RECVONLY)) {
344 /* TODO? */
345 }
346
347 return 0;
348}
349
350const struct value_string sdp_msg_payload_type_names[] = {
351 { 0, "PCMU" },
352 { 3, "GSM" },
353 { 8, "PCMA" },
354 { 18, "G729" },
355 { 110, "GSM-EFR" },
356 { 111, "GSM-HR-08" },
357 { 112, "AMR" },
358 { 113, "AMR-WB" },
359 {}
360};
361
362/* Return payload type number matching given string ("AMR", "GSM", ...) or negative if not found. */
363int sdp_subtype_name_to_payload_type(const char *subtype_name)
364{
365 return get_string_value(sdp_msg_payload_type_names, subtype_name);
366}
367
368/* Parse a line like 'm=audio 16398 RTP/AVP 0 3 8 96 112', starting after the '=' */
369static int sdp_parse_media_description(struct sdp_msg *sdp, const char *src)
370{
371 unsigned int port;
372 int i;
373 const char *payload_type_str;
374 const char *line_end = sdp_msg_line_end(src);
375 if (sscanf(src, "audio %u RTP/AVP", &port) < 1)
376 return -ENOTSUP;
377
Vadim Yanitskiy40b11c92020-02-09 04:04:54 +0700378 if (port > 0xffff)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200379 return -EINVAL;
380
381 sdp->rtp.port = port;
382
383 /* skip "audio 12345 RTP/AVP ", i.e. 3 spaces on */
384 payload_type_str = src;
385 for (i = 0; i < 3; i++) {
386 payload_type_str = strchr(payload_type_str, ' ');
387 if (!payload_type_str)
388 return -EINVAL;
389 while (*payload_type_str == ' ')
390 payload_type_str++;
391 if (payload_type_str >= line_end)
392 return -EINVAL;
393 }
394
395 /* Parse listing of payload type numbers after "RTP/AVP" */
396 while (payload_type_str < line_end) {
397 unsigned int payload_type;
398 struct sdp_audio_codec *codec;
399 const char *subtype_name;
400 if (sscanf(payload_type_str, "%u", &payload_type) < 1)
401 return -EINVAL;
402
403 codec = sdp_audio_codec_by_payload_type(&sdp->audio_codecs, payload_type, true);
404 if (!codec)
405 return -ENOSPC;
406
407 /* Fill in subtype name for fixed payload types */
408 subtype_name = get_value_string_or_null(sdp_msg_payload_type_names, codec->payload_type);
409 if (subtype_name)
410 OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
411
412 payload_type_str = strchr(payload_type_str, ' ');
413 if (!payload_type_str)
414 payload_type_str = line_end;
415 while (*payload_type_str == ' ')
416 payload_type_str++;
417 }
418
419 return 0;
420}
421
422/* parse a line like 'c=IN IP4 192.168.11.151' starting after the '=' */
423static int sdp_parse_connection_info(struct sdp_msg *sdp, const char *src)
424{
425 char ipv[10];
426 char addr_str[INET6_ADDRSTRLEN];
427 if (sscanf(src, "IN %s %s", ipv, addr_str) < 2)
428 return -EINVAL;
429
430 /* supporting only IPv4 */
431 if (strcmp(ipv, "IP4"))
432 return -ENOTSUP;
433
434 osmo_sockaddr_str_from_str(&sdp->rtp, addr_str, sdp->rtp.port);
435 return 0;
436}
437
438/* Parse SDP string into struct sdp_msg. Return 0 on success, negative on error. */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100439int sdp_msg_from_sdp_str(struct sdp_msg *sdp, const char *src)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200440{
441 const char *pos;
442 *sdp = (struct sdp_msg){};
443
444 for (pos = src; pos && *pos; pos++) {
445 char attrib;
446 int rc = 0;
447
448 if (*pos == '\r' || *pos == '\n')
449 continue;
450
451 /* Expecting only lines starting with 'X='. Not being too strict about it is probably alright. */
452 if (pos[1] != '=')
453 goto next_line;
454
455 attrib = *pos;
456 pos += 2;
457 switch (attrib) {
458 /* a=... */
459 case 'a':
460 rc = sdp_parse_attrib(sdp, pos);
461 break;
462 case 'm':
463 rc = sdp_parse_media_description(sdp, pos);
464 break;
465 case 'c':
466 rc = sdp_parse_connection_info(sdp, pos);
467 break;
468 default:
469 /* ignore any other parameters */
470 break;
471 }
472
473 if (rc) {
474 size_t line_len;
475 const char *line_end = sdp_msg_line_end(pos);
476 pos -= 2;
477 line_len = line_end - pos;
478 switch (rc) {
479 case -EINVAL:
480 LOGP(DMNCC, LOGL_ERROR,
481 "Failed to parse SDP: invalid line: %s\n", osmo_quote_str(pos, line_len));
482 break;
483 case -ENOSPC:
484 LOGP(DMNCC, LOGL_ERROR,
485 "Failed to parse SDP: no more space for: %s\n", osmo_quote_str(pos, line_len));
486 break;
487 case -ENOTSUP:
488 LOGP(DMNCC, LOGL_ERROR,
489 "Failed to parse SDP: not supported: %s\n", osmo_quote_str(pos, line_len));
490 break;
491 default:
492 LOGP(DMNCC, LOGL_ERROR,
493 "Failed to parse SDP: %s\n", osmo_quote_str(pos, line_len));
494 break;
495 }
496 return rc;
497 }
498next_line:
499 pos = strstr(pos, "\r\n");
500 if (!pos)
501 break;
502 }
503
504 return 0;
505}
506
507/* Leave only those codecs in 'ac_dest' that are also present in 'ac_other'.
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +0100508 * The matching is made by sdp_audio_codec_cmp(cmp_payload_type=false), i.e. payload_type numbers are not compared and
509 * fmtp parameters are compared 1:1 as plain strings.
Neels Hofmeyreef45782019-10-21 03:24:04 +0200510 * If translate_payload_type_numbers has an effect if ac_dest and ac_other have mismatching payload_type numbers for the
511 * same SDP codec descriptions. If translate_payload_type_numbers is true, take the payload_type numbers from ac_other.
512 * If false, keep payload_type numbers in ac_dest unchanged. */
513void sdp_audio_codecs_intersection(struct sdp_audio_codecs *ac_dest, const struct sdp_audio_codecs *ac_other,
514 bool translate_payload_type_numbers)
515{
516 int i;
517 for (i = 0; i < ac_dest->count; i++) {
518 struct sdp_audio_codec *codec = &ac_dest->codec[i];
519 struct sdp_audio_codec *other;
520 OSMO_ASSERT(i < ARRAY_SIZE(ac_dest->codec));
521
522 other = sdp_audio_codec_by_descr((struct sdp_audio_codecs*)ac_other, codec);
523
524 if (!other) {
525 OSMO_ASSERT(sdp_audio_codec_remove(ac_dest, codec) == 0);
526 i--;
527 continue;
528 }
529
530 /* Doing payload_type number translation of part of the intersection because it makes the algorithm
531 * simpler: we already know ac_dest is a subset of ac_other, and there is no need to resolve payload
532 * type number conflicts. */
533 if (translate_payload_type_numbers)
534 codec->payload_type = other->payload_type;
535 }
536}
537
538/* Make sure the given codec is listed as the first codec. 'codec' must be an actual codec entry of the given audio
539 * codecs list. */
540void sdp_audio_codecs_select(struct sdp_audio_codecs *ac, struct sdp_audio_codec *codec)
541{
542 struct sdp_audio_codec tmp;
543 struct sdp_audio_codec *pos;
544 OSMO_ASSERT((codec >= ac->codec)
545 && ((codec - ac->codec) < OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec))));
546
547 /* Already the first? */
548 if (codec == ac->codec)
549 return;
550
551 tmp = *codec;
552 for (pos = codec - 1; pos >= ac->codec; pos--)
553 pos[1] = pos[0];
554
555 ac->codec[0] = tmp;
556 return;
557}
558
559/* Short single-line representation of an SDP audio codec, convenient for logging.
560 * Like "AMR/8000:octet-align=1#122" */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100561int sdp_audio_codec_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200562{
563 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
564 OSMO_STRBUF_PRINTF(sb, "%s", codec->subtype_name);
Neels Hofmeyr99ab7c52022-01-13 19:04:59 +0100565 if (codec->rate != 8000)
566 OSMO_STRBUF_PRINTF(sb, "/%u", codec->rate);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200567 if (codec->fmtp[0])
568 OSMO_STRBUF_PRINTF(sb, ":%s", codec->fmtp);
Neels Hofmeyr99ab7c52022-01-13 19:04:59 +0100569 OSMO_STRBUF_PRINTF(sb, "#%d", codec->payload_type);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200570 return sb.chars_needed;
571}
572
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100573char *sdp_audio_codec_to_str_c(void *ctx, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200574{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100575 OSMO_NAME_C_IMPL(ctx, 32, "sdp_audio_codec_to_str_c-ERROR", sdp_audio_codec_to_str_buf, codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200576}
577
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100578const char *sdp_audio_codec_to_str(const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200579{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100580 return sdp_audio_codec_to_str_c(OTC_SELECT, codec);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200581}
582
583/* Short single-line representation of a list of SDP audio codecs, convenient for logging */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100584int sdp_audio_codecs_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codecs *ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200585{
586 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
587 const struct sdp_audio_codec *codec;
588 if (!ac->count)
589 OSMO_STRBUF_PRINTF(sb, "(no-codecs)");
590 foreach_sdp_audio_codec(codec, ac) {
591 bool first = (codec == ac->codec);
592 if (!first)
593 OSMO_STRBUF_PRINTF(sb, ",");
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100594 OSMO_STRBUF_APPEND(sb, sdp_audio_codec_to_str_buf, codec);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200595 }
596 return sb.chars_needed;
597}
598
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100599char *sdp_audio_codecs_to_str_c(void *ctx, const struct sdp_audio_codecs *ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200600{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100601 OSMO_NAME_C_IMPL(ctx, 128, "sdp_audio_codecs_to_str_c-ERROR", sdp_audio_codecs_to_str_buf, ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200602}
603
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100604const char *sdp_audio_codecs_to_str(const struct sdp_audio_codecs *ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200605{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100606 return sdp_audio_codecs_to_str_c(OTC_SELECT, ac);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200607}
608
609/* Short single-line representation of an SDP message, convenient for logging */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100610int sdp_msg_to_str_buf(char *buf, size_t buflen, const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200611{
612 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
613 if (!sdp) {
614 OSMO_STRBUF_PRINTF(sb, "NULL");
615 return sb.chars_needed;
616 }
617
618 OSMO_STRBUF_PRINTF(sb, OSMO_SOCKADDR_STR_FMT, OSMO_SOCKADDR_STR_FMT_ARGS(&sdp->rtp));
619 OSMO_STRBUF_PRINTF(sb, "{");
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100620 OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &sdp->audio_codecs);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200621 OSMO_STRBUF_PRINTF(sb, "}");
622 return sb.chars_needed;
623}
624
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100625char *sdp_msg_to_str_c(void *ctx, const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200626{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100627 OSMO_NAME_C_IMPL(ctx, 128, "sdp_msg_to_str_c-ERROR", sdp_msg_to_str_buf, sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200628}
629
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100630const char *sdp_msg_to_str(const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200631{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100632 return sdp_msg_to_str_c(OTC_SELECT, sdp);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200633}