blob: 9d787ad8d4bdfa4160edb36845cb3e46b35a44c5 [file] [log] [blame]
Neels Hofmeyreef45782019-10-21 03:24:04 +02001/* Minimalistic SDP parse/compose implementation, focused on GSM audio codecs */
2/*
Vadim Yanitskiy999a5932023-05-18 17:22:26 +07003 * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
Neels Hofmeyreef45782019-10-21 03:24:04 +02004 * 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
Neels Hofmeyr2d116822022-08-06 17:39:04 +020033bool sdp_audio_codec_is_set(const struct sdp_audio_codec *a)
34{
35 return a && a->subtype_name[0];
36}
37
Neels Hofmeyreef45782019-10-21 03:24:04 +020038/* Compare name, rate and fmtp, returning typical cmp result: 0 on match, and -1 / 1 on mismatch.
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010039 * If cmp_fmtp is false, do *not* compare the fmtp string; if true, compare fmtp 1:1 as strings.
40 * If cmp_payload_type is false, do *not* compare the payload_type number.
Neels Hofmeyreef45782019-10-21 03:24:04 +020041 * The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even
42 * though all parameters are the same. */
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010043int sdp_audio_codec_cmp(const struct sdp_audio_codec *a, const struct sdp_audio_codec *b,
44 bool cmp_fmtp, bool cmp_payload_type)
Neels Hofmeyreef45782019-10-21 03:24:04 +020045{
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010046 int cmp;
Neels Hofmeyreef45782019-10-21 03:24:04 +020047 if (a == b)
48 return 0;
49 if (!a)
50 return -1;
51 if (!b)
52 return 1;
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010053 cmp = strncmp(a->subtype_name, b->subtype_name, sizeof(a->subtype_name));
54 if (cmp)
55 return cmp;
56 cmp = OSMO_CMP(a->rate, b->rate);
57 if (cmp)
58 return cmp;
59 if (cmp_fmtp) {
60 cmp = strncmp(a->fmtp, b->fmtp, sizeof(a->fmtp));
61 if (cmp)
62 return cmp;
63 }
64 if (cmp_payload_type) {
65 cmp = OSMO_CMP(a->payload_type, b->payload_type);
66 if (cmp)
67 return cmp;
68 }
69 return 0;
70}
Neels Hofmeyreef45782019-10-21 03:24:04 +020071
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010072/* Compare two lists of audio codecs, returning typical cmp result: 0 on match, and -1 / 1 on mismatch.
73 * The ordering in the two lists may differ, except that the first codec in 'a' must also be the first codec in 'b'.
74 * This is because the first codec typically expresses the preferred codec to use.
75 * If cmp_fmtp is false, do *not* compare the fmtp strings; if true, compare fmtp 1:1 as strings.
76 * If cmp_payload_type is false, do *not* compare the payload_type numbers.
77 * The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even
78 * though all parameters are the same. */
79int sdp_audio_codecs_cmp(const struct sdp_audio_codecs *a, const struct sdp_audio_codecs *b,
80 bool cmp_fmtp, bool cmp_payload_type)
81{
82 const struct sdp_audio_codec *codec_a;
83 const struct sdp_audio_codec *codec_b;
84 int cmp;
85 if (a == b)
86 return 0;
87 if (!a)
Neels Hofmeyreef45782019-10-21 03:24:04 +020088 return -1;
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010089 if (!b)
Neels Hofmeyreef45782019-10-21 03:24:04 +020090 return 1;
91
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +010092 cmp = OSMO_CMP(a->count, b->count);
93 if (cmp)
94 return cmp;
95
96 if (!a->count)
97 return 0;
98
99 /* The first codec is the "chosen" codec and should match. The others may appear in different order. */
100 cmp = sdp_audio_codec_cmp(&a->codec[0], &b->codec[0], cmp_fmtp, cmp_payload_type);
101 if (cmp)
102 return cmp;
103
104 /* See if each codec in a is also present in b */
105 foreach_sdp_audio_codec(codec_a, a) {
106 bool match_found = false;
107 foreach_sdp_audio_codec(codec_b, b) {
108 if (!sdp_audio_codec_cmp(codec_a, codec_b, cmp_fmtp, cmp_payload_type)) {
109 match_found = true;
110 break;
111 }
112 }
113 if (!match_found)
114 return -1;
115 }
Neels Hofmeyreef45782019-10-21 03:24:04 +0200116
117 return 0;
118}
119
120/* Given a predefined fixed payload_type number, add an SDP audio codec entry, if not present yet.
121 * The payload_type must exist in sdp_msg_payload_type_names.
122 * Return the audio codec created or already existing for this payload type number.
123 */
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100124struct sdp_audio_codec *sdp_audio_codecs_add(struct sdp_audio_codecs *ac, unsigned int payload_type,
125 const char *subtype_name, unsigned int rate, const char *fmtp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200126{
127 struct sdp_audio_codec *codec;
128
129 /* Does an entry already exist? */
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100130 codec = sdp_audio_codecs_by_payload_type(ac, payload_type, false);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200131 if (codec) {
132 /* Already exists, sanity check */
133 if (!codec->subtype_name[0])
134 OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
135 else if (strcmp(codec->subtype_name, subtype_name)) {
136 /* There already is an entry with this payload_type number but a mismatching subtype_name. That is
137 * weird, rather abort. */
138 return NULL;
139 }
140 if (codec->rate != rate
141 || (fmtp && strcmp(fmtp, codec->fmtp))) {
142 /* Mismatching details. Rather abort */
143 return NULL;
144 }
145 return codec;
146 }
147
148 /* None exists, create codec entry for this payload type number */
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100149 codec = sdp_audio_codecs_by_payload_type(ac, payload_type, true);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200150 /* NULL means unable to add an entry */
151 if (!codec)
152 return NULL;
153
154 OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
155 if (fmtp)
156 OSMO_STRLCPY_ARRAY(codec->fmtp, fmtp);
Neels Hofmeyr8ff00e72023-03-04 03:56:46 +0100157 codec->rate = rate;
Neels Hofmeyreef45782019-10-21 03:24:04 +0200158 return codec;
159}
160
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100161struct sdp_audio_codec *sdp_audio_codecs_add_copy(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200162{
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100163 return sdp_audio_codecs_add(ac, codec->payload_type, codec->subtype_name, codec->rate,
164 codec->fmtp[0] ? codec->fmtp : NULL);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200165}
166
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200167/* Find or create an entry for the given payload_type number in the given list of codecs.
168 * If the given payload_type number is already present in ac, return the first matching entry.
169 * If no such payload_type number is present: a) return NULL if create == false;
170 * b) If create == true, add a mostly empty codec entry to the end of ac with the given payload_type number, and return
171 * the created entry.
172 * If create == true, a NULL return value means that there was no unused entry left in ac to add this payload_type.
173 */
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100174struct sdp_audio_codec *sdp_audio_codecs_by_payload_type(struct sdp_audio_codecs *ac, unsigned int payload_type,
175 bool create)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200176{
177 struct sdp_audio_codec *codec;
178 foreach_sdp_audio_codec(codec, ac) {
179 if (codec->payload_type == payload_type)
180 return codec;
181 }
Neels Hofmeyreef45782019-10-21 03:24:04 +0200182 if (!create)
183 return NULL;
184
Neels Hofmeyr28672a52024-01-30 05:55:59 +0100185 if (ac->count >= ARRAY_SIZE(ac->codec))
Neels Hofmeyreef45782019-10-21 03:24:04 +0200186 return NULL;
187
Neels Hofmeyr28672a52024-01-30 05:55:59 +0100188 codec = &ac->codec[ac->count];
Neels Hofmeyreef45782019-10-21 03:24:04 +0200189 *codec = (struct sdp_audio_codec){
190 .payload_type = payload_type,
191 .rate = 8000,
192 };
Neels Hofmeyr28672a52024-01-30 05:55:59 +0100193 ac->count++;
Neels Hofmeyreef45782019-10-21 03:24:04 +0200194 return codec;
195}
196
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +0100197/* Return a given sdp_msg's codec entry that matches the subtype_name and rate of the given codec, or NULL if no
198 * match is found. Comparison is made by sdp_audio_codec_cmp(cmp_payload_type=false). */
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100199struct sdp_audio_codec *sdp_audio_codecs_by_descr(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200200{
201 struct sdp_audio_codec *i;
202 foreach_sdp_audio_codec(i, ac) {
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +0100203 if (!sdp_audio_codec_cmp(i, codec, false, false))
Neels Hofmeyreef45782019-10-21 03:24:04 +0200204 return i;
205 }
206 return NULL;
207}
208
209/* Remove the codec entry pointed at by 'codec'. 'codec' must point at an entry of 'sdp' (to use an external codec
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100210 * instance, use sdp_audio_codecs_by_descr()).
Neels Hofmeyreef45782019-10-21 03:24:04 +0200211 * Return 0 on success, -ENOENT if codec does not point at the sdp->codec array. */
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100212int sdp_audio_codecs_remove(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200213{
214 struct sdp_audio_codec *i;
215 if ((codec < ac->codec)
216 || ((codec - ac->codec) >= OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec))))
217 return -ENOENT;
218
219 /* Move all following entries one up */
220 ac->count--;
221 foreach_sdp_audio_codec(i, ac) {
222 if (i < codec)
223 continue;
224 *i = *(i+1);
225 }
226 return 0;
227}
228
Neels Hofmeyr01431082022-07-19 12:16:02 +0200229static const char * const sdp_mode_str[] = {
230 [SDP_MODE_UNSET] = "-",
231 [SDP_MODE_SENDONLY] = "sendonly",
232 [SDP_MODE_RECVONLY] = "recvonly",
233 [SDP_MODE_SENDRECV] = "sendrecv",
234 [SDP_MODE_INACTIVE] = "inactive",
235};
236
Neels Hofmeyreef45782019-10-21 03:24:04 +0200237/* Convert struct sdp_msg to the actual SDP protocol representation */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100238int sdp_msg_to_sdp_str_buf(char *dst, size_t dst_size, const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200239{
240 const struct sdp_audio_codec *codec;
241 struct osmo_strbuf sb = { .buf = dst, .len = dst_size };
Neels Hofmeyr6d9b4852022-09-05 16:02:50 +0200242 const char *ip;
243 char ipv;
244
245 if (!sdp) {
246 OSMO_STRBUF_PRINTF(sb, "%s", "");
247 return sb.chars_needed;
248 }
249
250 ip = sdp->rtp.ip[0] ? sdp->rtp.ip : "0.0.0.0";
251 ipv = (osmo_ip_str_type(ip) == AF_INET6) ? '6' : '4';
Neels Hofmeyreef45782019-10-21 03:24:04 +0200252
253 OSMO_STRBUF_PRINTF(sb,
254 "v=0\r\n"
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200255 "o=OsmoMSC 0 0 IN IP%c %s\r\n"
Neels Hofmeyreef45782019-10-21 03:24:04 +0200256 "s=GSM Call\r\n"
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200257 "c=IN IP%c %s\r\n"
Neels Hofmeyreef45782019-10-21 03:24:04 +0200258 "t=0 0\r\n"
259 "m=audio %d RTP/AVP",
Pau Espin Pedroleeda9e12020-09-03 22:11:03 +0200260 ipv, ip, ipv, ip,
Neels Hofmeyreef45782019-10-21 03:24:04 +0200261 sdp->rtp.port);
262
263 /* Append all payload type numbers to 'm=audio <port> RTP/AVP 3 4 112' line */
264 foreach_sdp_audio_codec(codec, &sdp->audio_codecs)
265 OSMO_STRBUF_PRINTF(sb, " %d", codec->payload_type);
266 OSMO_STRBUF_PRINTF(sb, "\r\n");
267
268 /* Add details for all codecs */
269 foreach_sdp_audio_codec(codec, &sdp->audio_codecs) {
Neels Hofmeyr5b1f9a22022-08-06 17:40:52 +0200270 if (!sdp_audio_codec_is_set(codec))
271 continue;
272 OSMO_STRBUF_PRINTF(sb, "a=rtpmap:%d %s/%d\r\n", codec->payload_type, codec->subtype_name,
273 codec->rate > 0 ? codec->rate : 8000);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200274 if (codec->fmtp[0])
275 OSMO_STRBUF_PRINTF(sb, "a=fmtp:%d %s\r\n", codec->payload_type, codec->fmtp);
276 }
277
278 OSMO_STRBUF_PRINTF(sb, "a=ptime:%d\r\n", sdp->ptime > 0? sdp->ptime : 20);
279
Neels Hofmeyr01431082022-07-19 12:16:02 +0200280 if (sdp->mode != SDP_MODE_UNSET && sdp->mode < ARRAY_SIZE(sdp_mode_str))
281 OSMO_STRBUF_PRINTF(sb, "a=%s\r\n", sdp_mode_str[sdp->mode]);
282
Neels Hofmeyreef45782019-10-21 03:24:04 +0200283 return sb.chars_needed;
284}
285
286/* Return the first line ending (or the end of the string) at or after the given string position. */
287const char *sdp_msg_line_end(const char *src)
288{
289 const char *line_end = strchr(src, '\r');
290 if (!line_end)
291 line_end = strchr(src, '\n');
292 if (!line_end)
293 line_end = src + strlen(src);
294 return line_end;
295}
296
297/* parse a line like 'a=rtpmap:0 PCMU/8000', 'a=fmtp:112 octet-align=1; mode-set=4', 'a=ptime:20'.
298 * The src should point at the character after 'a=', e.g. at the start of 'rtpmap', 'fmtp', 'ptime'
299 */
300int sdp_parse_attrib(struct sdp_msg *sdp, const char *src)
301{
302 unsigned int payload_type;
303 struct sdp_audio_codec *codec;
304#define A_RTPMAP "rtpmap:"
305#define A_FMTP "fmtp:"
306#define A_PTIME "ptime:"
307#define A_RTCP "rtcp:"
Neels Hofmeyreef45782019-10-21 03:24:04 +0200308
309 if (osmo_str_startswith(src, A_RTPMAP)) {
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200310 /* "a=rtpmap:3 GSM/8000" */
Neels Hofmeyreef45782019-10-21 03:24:04 +0200311 char *audio_name;
312 unsigned int channels = 1;
313 if (sscanf(src, A_RTPMAP "%u", &payload_type) != 1)
314 return -EINVAL;
315
316 audio_name = strchr(src, ' ');
317 if (!audio_name || audio_name >= sdp_msg_line_end(src))
318 return -EINVAL;
319
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100320 codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200321 if (!codec)
322 return -ENOSPC;
323
324 if (sscanf(audio_name, " %31[^/]/%u/%u", codec->subtype_name, &codec->rate, &channels) < 1)
325 return -EINVAL;
326
327 if (channels != 1)
328 return -ENOTSUP;
329 }
330
331 else if (osmo_str_startswith(src, A_FMTP)) {
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200332 /* "a=fmtp:112 octet-align=1;mode-set=0,1,2,3" */
Neels Hofmeyreef45782019-10-21 03:24:04 +0200333 char *fmtp_str;
334 const char *line_end = sdp_msg_line_end(src);
335 if (sscanf(src, A_FMTP "%u", &payload_type) != 1)
336 return -EINVAL;
337
338 fmtp_str = strchr(src, ' ');
339 if (!fmtp_str)
340 return -EINVAL;
341 fmtp_str++;
342 if (fmtp_str >= line_end)
343 return -EINVAL;
344
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100345 codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200346 if (!codec)
347 return -ENOSPC;
348
349 /* (+1 because osmo_strlcpy() interprets it as size including the '\0') */
350 osmo_strlcpy(codec->fmtp, fmtp_str, line_end - fmtp_str + 1);
351 }
352
353 else if (osmo_str_startswith(src, A_PTIME)) {
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200354 /* "a=ptime:20" */
Neels Hofmeyreef45782019-10-21 03:24:04 +0200355 if (sscanf(src, A_PTIME "%u", &sdp->ptime) != 1)
356 return -EINVAL;
357
358 }
359
360 else if (osmo_str_startswith(src, A_RTCP)) {
361 /* TODO? */
362 }
363
Neels Hofmeyr01431082022-07-19 12:16:02 +0200364 else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_SENDRECV])) {
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200365 /* "a=sendrecv" */
Neels Hofmeyr01431082022-07-19 12:16:02 +0200366 sdp->mode = SDP_MODE_SENDRECV;
Neels Hofmeyreef45782019-10-21 03:24:04 +0200367 }
368
Neels Hofmeyr01431082022-07-19 12:16:02 +0200369 else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_SENDONLY])) {
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200370 /* "a=sendonly" */
Neels Hofmeyr01431082022-07-19 12:16:02 +0200371 sdp->mode = SDP_MODE_SENDONLY;
Neels Hofmeyreef45782019-10-21 03:24:04 +0200372 }
373
Neels Hofmeyr01431082022-07-19 12:16:02 +0200374 else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_RECVONLY])) {
Neels Hofmeyr40d34972022-07-19 12:15:27 +0200375 /* "a=recvonly" */
Neels Hofmeyr01431082022-07-19 12:16:02 +0200376 sdp->mode = SDP_MODE_RECVONLY;
377 }
378
379 else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_INACTIVE])) {
380 /* "a=inactive" */
381 sdp->mode = SDP_MODE_INACTIVE;
Neels Hofmeyreef45782019-10-21 03:24:04 +0200382 }
383
384 return 0;
385}
386
387const struct value_string sdp_msg_payload_type_names[] = {
388 { 0, "PCMU" },
389 { 3, "GSM" },
390 { 8, "PCMA" },
391 { 18, "G729" },
392 { 110, "GSM-EFR" },
393 { 111, "GSM-HR-08" },
394 { 112, "AMR" },
395 { 113, "AMR-WB" },
396 {}
397};
398
399/* Return payload type number matching given string ("AMR", "GSM", ...) or negative if not found. */
400int sdp_subtype_name_to_payload_type(const char *subtype_name)
401{
402 return get_string_value(sdp_msg_payload_type_names, subtype_name);
403}
404
405/* Parse a line like 'm=audio 16398 RTP/AVP 0 3 8 96 112', starting after the '=' */
406static int sdp_parse_media_description(struct sdp_msg *sdp, const char *src)
407{
408 unsigned int port;
409 int i;
410 const char *payload_type_str;
411 const char *line_end = sdp_msg_line_end(src);
412 if (sscanf(src, "audio %u RTP/AVP", &port) < 1)
413 return -ENOTSUP;
414
Vadim Yanitskiy40b11c92020-02-09 04:04:54 +0700415 if (port > 0xffff)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200416 return -EINVAL;
417
418 sdp->rtp.port = port;
419
420 /* skip "audio 12345 RTP/AVP ", i.e. 3 spaces on */
421 payload_type_str = src;
422 for (i = 0; i < 3; i++) {
423 payload_type_str = strchr(payload_type_str, ' ');
424 if (!payload_type_str)
425 return -EINVAL;
426 while (*payload_type_str == ' ')
427 payload_type_str++;
428 if (payload_type_str >= line_end)
429 return -EINVAL;
430 }
431
432 /* Parse listing of payload type numbers after "RTP/AVP" */
433 while (payload_type_str < line_end) {
434 unsigned int payload_type;
435 struct sdp_audio_codec *codec;
436 const char *subtype_name;
437 if (sscanf(payload_type_str, "%u", &payload_type) < 1)
438 return -EINVAL;
439
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100440 codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200441 if (!codec)
442 return -ENOSPC;
443
444 /* Fill in subtype name for fixed payload types */
445 subtype_name = get_value_string_or_null(sdp_msg_payload_type_names, codec->payload_type);
446 if (subtype_name)
447 OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name);
448
449 payload_type_str = strchr(payload_type_str, ' ');
450 if (!payload_type_str)
451 payload_type_str = line_end;
452 while (*payload_type_str == ' ')
453 payload_type_str++;
454 }
455
456 return 0;
457}
458
459/* parse a line like 'c=IN IP4 192.168.11.151' starting after the '=' */
460static int sdp_parse_connection_info(struct sdp_msg *sdp, const char *src)
461{
462 char ipv[10];
463 char addr_str[INET6_ADDRSTRLEN];
464 if (sscanf(src, "IN %s %s", ipv, addr_str) < 2)
465 return -EINVAL;
466
467 /* supporting only IPv4 */
468 if (strcmp(ipv, "IP4"))
469 return -ENOTSUP;
470
471 osmo_sockaddr_str_from_str(&sdp->rtp, addr_str, sdp->rtp.port);
472 return 0;
473}
474
475/* Parse SDP string into struct sdp_msg. Return 0 on success, negative on error. */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100476int sdp_msg_from_sdp_str(struct sdp_msg *sdp, const char *src)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200477{
478 const char *pos;
479 *sdp = (struct sdp_msg){};
480
481 for (pos = src; pos && *pos; pos++) {
482 char attrib;
483 int rc = 0;
484
485 if (*pos == '\r' || *pos == '\n')
486 continue;
487
488 /* Expecting only lines starting with 'X='. Not being too strict about it is probably alright. */
489 if (pos[1] != '=')
490 goto next_line;
491
492 attrib = *pos;
493 pos += 2;
494 switch (attrib) {
495 /* a=... */
496 case 'a':
497 rc = sdp_parse_attrib(sdp, pos);
498 break;
499 case 'm':
500 rc = sdp_parse_media_description(sdp, pos);
501 break;
502 case 'c':
503 rc = sdp_parse_connection_info(sdp, pos);
504 break;
505 default:
506 /* ignore any other parameters */
507 break;
508 }
509
510 if (rc) {
511 size_t line_len;
512 const char *line_end = sdp_msg_line_end(pos);
513 pos -= 2;
514 line_len = line_end - pos;
515 switch (rc) {
516 case -EINVAL:
517 LOGP(DMNCC, LOGL_ERROR,
518 "Failed to parse SDP: invalid line: %s\n", osmo_quote_str(pos, line_len));
519 break;
520 case -ENOSPC:
521 LOGP(DMNCC, LOGL_ERROR,
522 "Failed to parse SDP: no more space for: %s\n", osmo_quote_str(pos, line_len));
523 break;
524 case -ENOTSUP:
525 LOGP(DMNCC, LOGL_ERROR,
526 "Failed to parse SDP: not supported: %s\n", osmo_quote_str(pos, line_len));
527 break;
528 default:
529 LOGP(DMNCC, LOGL_ERROR,
530 "Failed to parse SDP: %s\n", osmo_quote_str(pos, line_len));
531 break;
532 }
533 return rc;
534 }
535next_line:
536 pos = strstr(pos, "\r\n");
537 if (!pos)
538 break;
539 }
540
541 return 0;
542}
543
544/* Leave only those codecs in 'ac_dest' that are also present in 'ac_other'.
Neels Hofmeyrc9e0ca32022-01-13 18:28:44 +0100545 * The matching is made by sdp_audio_codec_cmp(cmp_payload_type=false), i.e. payload_type numbers are not compared and
546 * fmtp parameters are compared 1:1 as plain strings.
Neels Hofmeyreef45782019-10-21 03:24:04 +0200547 * If translate_payload_type_numbers has an effect if ac_dest and ac_other have mismatching payload_type numbers for the
548 * same SDP codec descriptions. If translate_payload_type_numbers is true, take the payload_type numbers from ac_other.
549 * If false, keep payload_type numbers in ac_dest unchanged. */
550void sdp_audio_codecs_intersection(struct sdp_audio_codecs *ac_dest, const struct sdp_audio_codecs *ac_other,
551 bool translate_payload_type_numbers)
552{
553 int i;
554 for (i = 0; i < ac_dest->count; i++) {
555 struct sdp_audio_codec *codec = &ac_dest->codec[i];
556 struct sdp_audio_codec *other;
557 OSMO_ASSERT(i < ARRAY_SIZE(ac_dest->codec));
558
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100559 other = sdp_audio_codecs_by_descr((struct sdp_audio_codecs *)ac_other, codec);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200560
561 if (!other) {
Neels Hofmeyrd20dd222022-01-14 00:38:42 +0100562 OSMO_ASSERT(sdp_audio_codecs_remove(ac_dest, codec) == 0);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200563 i--;
564 continue;
565 }
566
567 /* Doing payload_type number translation of part of the intersection because it makes the algorithm
568 * simpler: we already know ac_dest is a subset of ac_other, and there is no need to resolve payload
569 * type number conflicts. */
570 if (translate_payload_type_numbers)
571 codec->payload_type = other->payload_type;
572 }
573}
574
575/* Make sure the given codec is listed as the first codec. 'codec' must be an actual codec entry of the given audio
576 * codecs list. */
577void sdp_audio_codecs_select(struct sdp_audio_codecs *ac, struct sdp_audio_codec *codec)
578{
579 struct sdp_audio_codec tmp;
580 struct sdp_audio_codec *pos;
581 OSMO_ASSERT((codec >= ac->codec)
582 && ((codec - ac->codec) < OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec))));
583
584 /* Already the first? */
585 if (codec == ac->codec)
586 return;
587
588 tmp = *codec;
589 for (pos = codec - 1; pos >= ac->codec; pos--)
590 pos[1] = pos[0];
591
592 ac->codec[0] = tmp;
593 return;
594}
595
596/* Short single-line representation of an SDP audio codec, convenient for logging.
597 * Like "AMR/8000:octet-align=1#122" */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100598int sdp_audio_codec_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200599{
600 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
601 OSMO_STRBUF_PRINTF(sb, "%s", codec->subtype_name);
Neels Hofmeyr99ab7c52022-01-13 19:04:59 +0100602 if (codec->rate != 8000)
603 OSMO_STRBUF_PRINTF(sb, "/%u", codec->rate);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200604 if (codec->fmtp[0])
605 OSMO_STRBUF_PRINTF(sb, ":%s", codec->fmtp);
Neels Hofmeyr99ab7c52022-01-13 19:04:59 +0100606 OSMO_STRBUF_PRINTF(sb, "#%d", codec->payload_type);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200607 return sb.chars_needed;
608}
609
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100610char *sdp_audio_codec_to_str_c(void *ctx, const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200611{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100612 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 +0200613}
614
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100615const char *sdp_audio_codec_to_str(const struct sdp_audio_codec *codec)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200616{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100617 return sdp_audio_codec_to_str_c(OTC_SELECT, codec);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200618}
619
620/* Short single-line representation of a list of SDP audio codecs, convenient for logging */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100621int sdp_audio_codecs_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codecs *ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200622{
623 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
624 const struct sdp_audio_codec *codec;
625 if (!ac->count)
626 OSMO_STRBUF_PRINTF(sb, "(no-codecs)");
627 foreach_sdp_audio_codec(codec, ac) {
628 bool first = (codec == ac->codec);
629 if (!first)
630 OSMO_STRBUF_PRINTF(sb, ",");
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100631 OSMO_STRBUF_APPEND(sb, sdp_audio_codec_to_str_buf, codec);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200632 }
633 return sb.chars_needed;
634}
635
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100636char *sdp_audio_codecs_to_str_c(void *ctx, const struct sdp_audio_codecs *ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200637{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100638 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 +0200639}
640
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100641const char *sdp_audio_codecs_to_str(const struct sdp_audio_codecs *ac)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200642{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100643 return sdp_audio_codecs_to_str_c(OTC_SELECT, ac);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200644}
645
646/* Short single-line representation of an SDP message, convenient for logging */
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100647int sdp_msg_to_str_buf(char *buf, size_t buflen, const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200648{
649 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
650 if (!sdp) {
651 OSMO_STRBUF_PRINTF(sb, "NULL");
652 return sb.chars_needed;
653 }
654
655 OSMO_STRBUF_PRINTF(sb, OSMO_SOCKADDR_STR_FMT, OSMO_SOCKADDR_STR_FMT_ARGS(&sdp->rtp));
656 OSMO_STRBUF_PRINTF(sb, "{");
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100657 OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &sdp->audio_codecs);
Oliver Smith10632132023-05-12 12:14:22 +0200658 if (sdp->bearer_services.count) {
659 OSMO_STRBUF_PRINTF(sb, ",");
660 OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &sdp->bearer_services);
661 }
Neels Hofmeyreef45782019-10-21 03:24:04 +0200662 OSMO_STRBUF_PRINTF(sb, "}");
663 return sb.chars_needed;
664}
665
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100666char *sdp_msg_to_str_c(void *ctx, const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200667{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100668 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 +0200669}
670
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100671const char *sdp_msg_to_str(const struct sdp_msg *sdp)
Neels Hofmeyreef45782019-10-21 03:24:04 +0200672{
Neels Hofmeyr9a515e52022-01-13 20:40:12 +0100673 return sdp_msg_to_str_c(OTC_SELECT, sdp);
Neels Hofmeyreef45782019-10-21 03:24:04 +0200674}
Oliver Smith10632132023-05-12 12:14:22 +0200675
676void sdp_audio_codecs_set_csd(struct sdp_audio_codecs *ac)
677{
678 *ac = (struct sdp_audio_codecs){
679 .count = 1,
680 .codec = {{
681 .payload_type = 120,
682 .subtype_name = "CLEARMODE",
683 .rate = 8000,
684 }},
685 };
686}