add libosmo-sdp: add sdp_msg.h,.c
Change-Id: Id53c2c6eea3726f63a1399ae985f8aa3344e32c8
This commit is contained in:
parent
948e66b259
commit
24c09fbacb
|
@ -11,6 +11,7 @@ nobase_include_HEADERS = \
|
|||
osmocom/sdp/fmtp.h \
|
||||
osmocom/sdp/sdp_codec.h \
|
||||
osmocom/sdp/sdp_codec_list.h \
|
||||
osmocom/sdp/sdp_msg.h \
|
||||
osmocom/sdp/sdp_strings.h \
|
||||
$(NULL)
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/* Public API for SDP message encoding and decoding */
|
||||
/*
|
||||
* (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
#include <osmocom/sdp/sdp_codec.h>
|
||||
#include <osmocom/sdp/sdp_codec_list.h>
|
||||
|
||||
/* Media Direction Attributes "a=recvonly", "a=sendrecv", "a=sendonly", "a=inactive" RFC-8866 6.7. */
|
||||
enum osmo_sdp_media_direcion_e {
|
||||
OSMO_SDP_MDIR_UNSET = 0,
|
||||
OSMO_SDP_MDIR_RECVONLY = 1,
|
||||
OSMO_SDP_MDIR_SENDRECV = 2,
|
||||
OSMO_SDP_MDIR_SENDONLY = 3,
|
||||
OSMO_SDP_MDIR_INACTIVE = 4,
|
||||
};
|
||||
|
||||
/* Session Description Protocol (SDP) message, RFC-8866. */
|
||||
struct osmo_sdp_msg {
|
||||
/* 5.2 Origin ("o="). */
|
||||
struct {
|
||||
struct osmo_sockaddr_str addr;
|
||||
char *username;
|
||||
int64_t sess_id;
|
||||
int64_t sess_version;
|
||||
} origin;
|
||||
|
||||
/* 5.3 Session Name ("s="). */
|
||||
char *session_name;
|
||||
|
||||
/* 5.7 Connection Information ("c=") and port from 5.14 Media Descriptions ("m="). */
|
||||
struct osmo_sockaddr_str rtp;
|
||||
|
||||
/* 5.9. Time Active ("t="). */
|
||||
struct {
|
||||
int64_t start;
|
||||
int64_t stop;
|
||||
} time_active;
|
||||
|
||||
/* 6.4 "a=ptime:<val>". */
|
||||
unsigned int ptime;
|
||||
|
||||
/* 6.7 "a=sendrecv"... */
|
||||
enum osmo_sdp_media_direcion_e media_direction;
|
||||
|
||||
/* List of codecs defined in the SDP message.
|
||||
* This should not be NULL -- osmo_sdp_msg_alloc() returns an empty osmo_sdp_codec_list instance, ready for
|
||||
* adding codecs.
|
||||
* Combination of:
|
||||
* - payload_type numbers from 5.14 Media Descriptions ("m="),
|
||||
* - 6.6 "a=rtpmap",
|
||||
* - 6.15 Format Parameters "a=fmtp".
|
||||
*/
|
||||
struct osmo_sdp_codec_list *codecs;
|
||||
|
||||
/* For future extension, always set to false. */
|
||||
bool v2;
|
||||
};
|
||||
|
||||
struct osmo_sdp_err {
|
||||
int rc;
|
||||
/* Point at the position that caused the error, in the src string. */
|
||||
const char *src_str;
|
||||
/* Nr of characters at *src_str that are relevant to the error. */
|
||||
size_t src_str_len;
|
||||
};
|
||||
|
||||
struct osmo_sdp_msg *osmo_sdp_msg_alloc(void *ctx);
|
||||
|
||||
struct osmo_sdp_msg *osmo_sdp_msg_decode(void *ctx, const char *src, struct osmo_sdp_err *err);
|
||||
|
||||
int osmo_sdp_msg_encode_buf(char *dst, size_t dst_size, const struct osmo_sdp_msg *sdp);
|
||||
char *osmo_sdp_msg_encode_c(void *ctx, const struct osmo_sdp_msg *sdp);
|
||||
|
||||
int osmo_sdp_msg_to_str_buf(char *buf, size_t buflen, const struct osmo_sdp_msg *sdp, bool summarize);
|
||||
char *osmo_sdp_msg_to_str_c(void *ctx, const struct osmo_sdp_msg *sdp, bool summarize);
|
|
@ -17,6 +17,7 @@ noinst_LTLIBRARIES = \
|
|||
libosmo_sdp_la_SOURCES = \
|
||||
sdp_codec.c \
|
||||
sdp_codec_list.c \
|
||||
sdp_msg.c \
|
||||
sdp_internal.c \
|
||||
fmtp.c \
|
||||
$(NULL)
|
||||
|
|
|
@ -0,0 +1,448 @@
|
|||
/* Implementation for SDP message encoding and decoding */
|
||||
/*
|
||||
* (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/sdp/sdp_msg.h>
|
||||
#include <osmocom/sdp/sdp_strings.h>
|
||||
#include <osmocom/sdp/sdp_internal.h>
|
||||
|
||||
static const char * const mdir_str[] = {
|
||||
[OSMO_SDP_MDIR_UNSET] = "-",
|
||||
[OSMO_SDP_MDIR_SENDONLY] = OSMO_SDP_STR_SENDONLY,
|
||||
[OSMO_SDP_MDIR_RECVONLY] = OSMO_SDP_STR_RECVONLY,
|
||||
[OSMO_SDP_MDIR_SENDRECV] = OSMO_SDP_STR_SENDRECV,
|
||||
[OSMO_SDP_MDIR_INACTIVE] = OSMO_SDP_STR_INACTIVE,
|
||||
};
|
||||
|
||||
/*! Convert struct osmo_sdp_msg to the actual SDP protocol representation. */
|
||||
int osmo_sdp_msg_encode_buf(char *dst, size_t dst_size, const struct osmo_sdp_msg *sdp)
|
||||
{
|
||||
const struct osmo_sdp_codec *codec;
|
||||
struct osmo_strbuf sb = { .buf = dst, .len = dst_size };
|
||||
const char *oip;
|
||||
char oipv;
|
||||
const char *ip;
|
||||
char ipv;
|
||||
|
||||
if (!sdp) {
|
||||
OSMO_STRBUF_PRINTF(sb, "%s", "");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
oip = sdp->origin.addr.ip[0] ? sdp->origin.addr.ip : "0.0.0.0";
|
||||
oipv = (osmo_ip_str_type(oip) == AF_INET6) ? '6' : '4';
|
||||
|
||||
ip = sdp->rtp.ip[0] ? sdp->rtp.ip : "0.0.0.0";
|
||||
ipv = (osmo_ip_str_type(oip) == AF_INET6) ? '6' : '4';
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb,
|
||||
"v=0\r\n"
|
||||
"o=%s %"PRId64" %"PRId64" IN IP%c %s\r\n"
|
||||
"s=%s\r\n"
|
||||
"c=IN IP%c %s\r\n"
|
||||
"t=%"PRId64" %"PRId64"\r\n"
|
||||
"m=audio %d RTP/AVP",
|
||||
sdp->origin.username ? : "libosmo-sdp",
|
||||
sdp->origin.sess_id, sdp->origin.sess_version,
|
||||
oipv, oip,
|
||||
sdp->session_name ? : "-",
|
||||
ipv, ip,
|
||||
sdp->time_active.start,
|
||||
sdp->time_active.stop,
|
||||
sdp->rtp.port);
|
||||
|
||||
/* Append all payload type numbers to 'm=audio <port> RTP/AVP 3 4 112' line */
|
||||
osmo_sdp_codec_list_foreach(codec, sdp->codecs)
|
||||
OSMO_STRBUF_PRINTF(sb, " %d", codec->payload_type);
|
||||
OSMO_STRBUF_PRINTF(sb, "\r\n");
|
||||
|
||||
/* Add details for all codecs */
|
||||
osmo_sdp_codec_list_foreach(codec, sdp->codecs) {
|
||||
if (!osmo_sdp_codec_is_set(codec))
|
||||
continue;
|
||||
OSMO_STRBUF_PRINTF(sb, OSMO_SDP_A_PREFIX(OSMO_SDP_STR_RTPMAP) "%d %s/%d\r\n", codec->payload_type, codec->encoding_name,
|
||||
codec->rate > 0 ? codec->rate : 8000);
|
||||
if (codec->fmtp && codec->fmtp[0])
|
||||
OSMO_STRBUF_PRINTF(sb, OSMO_SDP_A_PREFIX(OSMO_SDP_STR_FMTP) "%d %s\r\n", codec->payload_type, codec->fmtp);
|
||||
}
|
||||
|
||||
if (sdp->ptime)
|
||||
OSMO_STRBUF_PRINTF(sb, OSMO_SDP_A_PREFIX(OSMO_SDP_STR_PTIME) "%d\r\n", sdp->ptime);
|
||||
|
||||
if (sdp->media_direction != OSMO_SDP_MDIR_UNSET && sdp->media_direction < ARRAY_SIZE(mdir_str))
|
||||
OSMO_STRBUF_PRINTF(sb, "a=%s\r\n", mdir_str[sdp->media_direction]);
|
||||
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *osmo_sdp_msg_encode_c(void *ctx, const struct osmo_sdp_msg *sdp)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 256, "osmo_sdp_msg_to_str_c-ERROR", osmo_sdp_msg_encode_buf, sdp)
|
||||
}
|
||||
|
||||
/* Return the first line ending (or the end of the string) at or after the given string position. */
|
||||
const char *get_line_end(const char *src)
|
||||
{
|
||||
const char *line_end = strchr(src, '\r');
|
||||
if (!line_end)
|
||||
line_end = strchr(src, '\n');
|
||||
if (!line_end)
|
||||
line_end = src + strlen(src);
|
||||
return line_end;
|
||||
}
|
||||
|
||||
static bool str_is_attrib(const char *str, const char *attrib_name, char expect_next_char)
|
||||
{
|
||||
char next_c;
|
||||
if (!osmo_str_startswith(str, attrib_name))
|
||||
return false;
|
||||
next_c = str[strlen(attrib_name)];
|
||||
if (expect_next_char == next_c)
|
||||
return true;
|
||||
/* Treat \0 as equivalent with line end */
|
||||
if (!expect_next_char && (next_c == '\r' || next_c == '\n'))
|
||||
return true;
|
||||
/* It started with the string, but continued otherwise */
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum osmo_sdp_media_direcion_e check_for_media_direction(const char *str)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(mdir_str); i++) {
|
||||
if (i == OSMO_SDP_MDIR_UNSET)
|
||||
continue;
|
||||
if (str_is_attrib(str, mdir_str[i], 0))
|
||||
return i;
|
||||
}
|
||||
return OSMO_SDP_MDIR_UNSET;
|
||||
}
|
||||
|
||||
static struct osmo_sdp_codec *find_or_create_payload_type(struct osmo_sdp_msg *sdp, unsigned int payload_type)
|
||||
{
|
||||
struct osmo_sdp_codec *codec;
|
||||
codec = osmo_sdp_codec_list_by_payload_type(sdp->codecs, payload_type);
|
||||
if (!codec) {
|
||||
codec = osmo_sdp_codec_list_add_empty(sdp->codecs);
|
||||
codec->payload_type = payload_type;
|
||||
codec->rate = 8000;
|
||||
}
|
||||
return codec;
|
||||
}
|
||||
|
||||
|
||||
/* parse a line like 'a=rtpmap:0 PCMU/8000', 'a=fmtp:112 octet-align=1; mode-set=4', 'a=ptime:20'.
|
||||
* The src should point at the character after 'a=', e.g. at the start of 'rtpmap', 'fmtp', 'ptime'
|
||||
*/
|
||||
int sdp_parse_attrib(struct osmo_sdp_msg *sdp, const char *src)
|
||||
{
|
||||
unsigned int payload_type;
|
||||
struct osmo_sdp_codec *codec;
|
||||
enum osmo_sdp_media_direcion_e mdir;
|
||||
const char *line_end = get_line_end(src);
|
||||
|
||||
if (str_is_attrib(src, OSMO_SDP_STR_RTPMAP, ':')) {
|
||||
/* "a=rtpmap:96 AMR/8000" */
|
||||
struct token audio_name;
|
||||
unsigned int channels = 1;
|
||||
if (sscanf(src, OSMO_SDP_STR_RTPMAP ":%u", &payload_type) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
codec = find_or_create_payload_type(sdp, payload_type);
|
||||
|
||||
audio_name.start = strchr(src, ' ');
|
||||
if (!audio_name.start)
|
||||
return -EINVAL;
|
||||
audio_name.start++;
|
||||
if (audio_name.start >= get_line_end(src))
|
||||
return -EINVAL;
|
||||
|
||||
audio_name.end = strchr(audio_name.start, '/');
|
||||
if (!audio_name.end || audio_name.end > line_end)
|
||||
audio_name.end = line_end;
|
||||
token_copy(codec, &codec->encoding_name, &audio_name);
|
||||
/* '< 1' is enough, the second '/%u' is optional */
|
||||
if (sscanf(audio_name.end, "/%u/%u", &codec->rate, &channels) < 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (channels != 1)
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
else if (str_is_attrib(src, OSMO_SDP_STR_FMTP, ':')) {
|
||||
/* "a=fmtp:112 octet-align=1;mode-set=0,1,2,3" */
|
||||
struct token fmtp_str;
|
||||
const char *line_end = get_line_end(src);
|
||||
if (sscanf(src, OSMO_SDP_STR_FMTP ":%u", &payload_type) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
codec = find_or_create_payload_type(sdp, payload_type);
|
||||
|
||||
fmtp_str.start = strchr(src, ' ');
|
||||
if (!fmtp_str.start)
|
||||
return -EINVAL;
|
||||
fmtp_str.start++;
|
||||
if (fmtp_str.start >= line_end)
|
||||
return -EINVAL;
|
||||
|
||||
fmtp_str.end = line_end;
|
||||
token_copy(codec, &codec->fmtp, &fmtp_str);
|
||||
}
|
||||
|
||||
else if (str_is_attrib(src, OSMO_SDP_STR_PTIME, ':')) {
|
||||
/* "a=ptime:20" */
|
||||
if (sscanf(src, OSMO_SDP_STR_PTIME ":%u", &sdp->ptime) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
}
|
||||
|
||||
/* "a=sendrecv" ... */
|
||||
else if ((mdir = check_for_media_direction(src)) != OSMO_SDP_MDIR_UNSET) {
|
||||
sdp->media_direction = mdir;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct value_string fixed_payload_types[] = {
|
||||
{ 0, "PCMU" },
|
||||
{ 3, "GSM" },
|
||||
{ 8, "PCMA" },
|
||||
{ 18, "G729" },
|
||||
{ 110, "GSM-EFR" },
|
||||
{ 111, "GSM-HR-08" },
|
||||
{ 112, "AMR" },
|
||||
{ 113, "AMR-WB" },
|
||||
{}
|
||||
};
|
||||
|
||||
/* Parse a line like 'm=audio 16398 RTP/AVP 0 3 8 96 112', starting after the '=' */
|
||||
static int sdp_parse_media_description(struct osmo_sdp_msg *sdp, const char *src)
|
||||
{
|
||||
unsigned int port;
|
||||
int i;
|
||||
const char *payload_type_str;
|
||||
const char *line_end = get_line_end(src);
|
||||
if (sscanf(src, "audio %u RTP/AVP", &port) < 1)
|
||||
return -ENOTSUP;
|
||||
|
||||
if (port > 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
sdp->rtp.port = port;
|
||||
|
||||
/* skip "audio 12345 RTP/AVP ", i.e. 3 spaces on */
|
||||
payload_type_str = src;
|
||||
for (i = 0; i < 3; i++) {
|
||||
payload_type_str = strchr(payload_type_str, ' ');
|
||||
if (!payload_type_str)
|
||||
return -EINVAL;
|
||||
while (*payload_type_str == ' ')
|
||||
payload_type_str++;
|
||||
if (payload_type_str >= line_end)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Parse listing of payload type numbers after "RTP/AVP" */
|
||||
while (payload_type_str < line_end) {
|
||||
unsigned int payload_type;
|
||||
struct osmo_sdp_codec *codec;
|
||||
const char *encoding_name;
|
||||
if (sscanf(payload_type_str, "%u", &payload_type) < 1)
|
||||
return -EINVAL;
|
||||
|
||||
codec = find_or_create_payload_type(sdp, payload_type);
|
||||
|
||||
/* Fill in encoding name for fixed payload types */
|
||||
encoding_name = get_value_string_or_null(fixed_payload_types, codec->payload_type);
|
||||
if (encoding_name)
|
||||
osmo_talloc_replace_string(codec, &codec->encoding_name, encoding_name);
|
||||
|
||||
payload_type_str = strchr(payload_type_str, ' ');
|
||||
if (!payload_type_str)
|
||||
payload_type_str = line_end;
|
||||
while (*payload_type_str == ' ')
|
||||
payload_type_str++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse a line like 'c=IN IP4 192.168.11.151' starting after the '=' */
|
||||
static int sdp_parse_connection_info(struct osmo_sdp_msg *sdp, const char *src)
|
||||
{
|
||||
char ipv[10];
|
||||
char addr_str[INET6_ADDRSTRLEN];
|
||||
if (sscanf(src, "IN %s %s", ipv, addr_str) < 2)
|
||||
return -EINVAL;
|
||||
|
||||
if (strcmp(ipv, "IP4") && strcmp(ipv, "IP6"))
|
||||
return -ENOTSUP;
|
||||
|
||||
return osmo_sockaddr_str_from_str(&sdp->rtp, addr_str, sdp->rtp.port);
|
||||
}
|
||||
|
||||
/* parse a line like 'o=jdoe 3724394400 3724394405 IN IP4 198.51.100.1' starting after the '=' */
|
||||
static int sdp_parse_origin(struct osmo_sdp_msg *sdp, const char *src)
|
||||
{
|
||||
struct token username;
|
||||
char ipv[16] = {};
|
||||
char addr_str[INET6_ADDRSTRLEN] = {};
|
||||
const char *line_end = get_line_end(src);
|
||||
|
||||
username.start = src;
|
||||
username.end = strchr(src, ' ');
|
||||
if (!username.end || username.end >= line_end)
|
||||
return -EINVAL;
|
||||
token_copy(sdp, &sdp->origin.username, &username);
|
||||
|
||||
if (sscanf(username.end + 1, "%"PRId64" %"PRId64" IN %15s %45s",
|
||||
&sdp->origin.sess_id, &sdp->origin.sess_version, ipv, addr_str) < 4)
|
||||
return -EINVAL;
|
||||
|
||||
if (strcmp(ipv, "IP4") && strcmp(ipv, "IP6"))
|
||||
return -ENOTSUP;
|
||||
return osmo_sockaddr_str_from_str(&sdp->origin.addr, addr_str, 0);
|
||||
}
|
||||
|
||||
static int sdp_parse_session_name(struct osmo_sdp_msg *sdp, const char *src)
|
||||
{
|
||||
const char *line_end = get_line_end(src);
|
||||
if (sdp->session_name)
|
||||
talloc_free(sdp->session_name);
|
||||
if (line_end <= src)
|
||||
sdp->session_name = NULL;
|
||||
else
|
||||
sdp->session_name = talloc_strndup(sdp, src, line_end - src);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct osmo_sdp_msg *osmo_sdp_msg_alloc(void *ctx)
|
||||
{
|
||||
struct osmo_sdp_msg *sdp;
|
||||
sdp = talloc_zero(ctx, struct osmo_sdp_msg);
|
||||
sdp->codecs = osmo_sdp_codec_list_alloc(sdp);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
/* Parse SDP string into struct osmo_sdp_msg. Return 0 on success, negative on error.
|
||||
* Return a new osmo_sdp_msg instance allocated from ctx, or NULL on error.
|
||||
* When NULL is returned and if err is non-NULL, details of the error are returned in err->*.
|
||||
*/
|
||||
struct osmo_sdp_msg *osmo_sdp_msg_decode(void *ctx, const char *src, struct osmo_sdp_err *err)
|
||||
{
|
||||
struct osmo_sdp_msg *sdp;
|
||||
const char *pos;
|
||||
|
||||
if (err)
|
||||
*err = (struct osmo_sdp_err){};
|
||||
|
||||
sdp = osmo_sdp_msg_alloc(ctx);
|
||||
|
||||
for (pos = src; pos && *pos; pos++) {
|
||||
char attrib;
|
||||
int rc = 0;
|
||||
|
||||
if (*pos == '\r' || *pos == '\n')
|
||||
continue;
|
||||
|
||||
/* Expecting only lines starting with 'X='. Not being too strict about it is probably alright. */
|
||||
if (pos[1] != '=')
|
||||
goto next_line;
|
||||
|
||||
attrib = *pos;
|
||||
pos += 2;
|
||||
switch (attrib) {
|
||||
/* a=... */
|
||||
case 'a':
|
||||
rc = sdp_parse_attrib(sdp, pos);
|
||||
break;
|
||||
case 'm':
|
||||
rc = sdp_parse_media_description(sdp, pos);
|
||||
break;
|
||||
case 'c':
|
||||
rc = sdp_parse_connection_info(sdp, pos);
|
||||
break;
|
||||
case 'o':
|
||||
rc = sdp_parse_origin(sdp, pos);
|
||||
break;
|
||||
case 's':
|
||||
rc = sdp_parse_session_name(sdp, pos);
|
||||
break;
|
||||
default:
|
||||
/* ignore any other parameters */
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
if (err) {
|
||||
const char *line_end = get_line_end(pos);
|
||||
/* shift back to include the 'x=' part as well */
|
||||
pos -= 2;
|
||||
*err = (struct osmo_sdp_err){
|
||||
.rc = rc,
|
||||
.src_str = pos,
|
||||
.src_str_len = line_end - pos,
|
||||
};
|
||||
}
|
||||
talloc_free(sdp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
next_line:
|
||||
pos = strstr(pos, "\r\n");
|
||||
if (!pos)
|
||||
break;
|
||||
}
|
||||
|
||||
return sdp;
|
||||
}
|
||||
|
||||
/*! Short single-line representation of an SDP message, convenient for logging.
|
||||
* To obtain a valid SDP message, use osmo_sdp_msg_encode_buf() instead.
|
||||
*/
|
||||
int osmo_sdp_msg_to_str_buf(char *buf, size_t buflen, const struct osmo_sdp_msg *sdp, bool summarize)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
if (!sdp) {
|
||||
OSMO_STRBUF_PRINTF(sb, "NULL");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, OSMO_SOCKADDR_STR_FMT, OSMO_SOCKADDR_STR_FMT_ARGS(&sdp->rtp));
|
||||
OSMO_STRBUF_PRINTF(sb, "{");
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sdp_codec_list_to_str_buf, sdp->codecs, summarize);
|
||||
OSMO_STRBUF_PRINTF(sb, "}");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *osmo_sdp_msg_to_str_c(void *ctx, const struct osmo_sdp_msg *sdp, bool summarize)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 128, "sdp_msg_to_str_c-ERROR", osmo_sdp_msg_to_str_buf, sdp, summarize)
|
||||
}
|
|
@ -27,6 +27,7 @@ EXTRA_DIST = \
|
|||
check_PROGRAMS = \
|
||||
sdp_fmtp_test \
|
||||
sdp_codec_test \
|
||||
sdp_msg_test \
|
||||
$(NULL)
|
||||
|
||||
sdp_fmtp_test_SOURCES = \
|
||||
|
@ -46,6 +47,16 @@ sdp_codec_test_LDADD = \
|
|||
$(LIBOSMOCORE_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
sdp_msg_test_SOURCES = \
|
||||
sdp_msg_test.c \
|
||||
$(NULL)
|
||||
|
||||
sdp_msg_test_LDADD = \
|
||||
$(top_builddir)/src/libosmo-sdp/libosmo-sdp.la \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
update_exp:
|
||||
$(builddir)/sdp_fmtp_test >$(srcdir)/sdp_fmtp_test.ok 2>$(srcdir)/sdp_fmtp_test.err
|
||||
$(builddir)/sdp_codec_test >$(srcdir)/sdp_codec_test.ok 2>$(srcdir)/sdp_codec_test.err
|
||||
$(builddir)/sdp_msg_test >$(srcdir)/sdp_msg_test.ok 2>$(srcdir)/sdp_msg_test.err
|
||||
|
|
|
@ -0,0 +1,754 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
#include <osmocom/sdp/sdp_msg.h>
|
||||
|
||||
void *test_ctx = NULL;
|
||||
|
||||
static void report_callback(const void *ptr, int depth, int max_depth, int is_ref, void *priv)
|
||||
{
|
||||
const char *name = talloc_get_name(ptr);
|
||||
printf(" |%*s%3zu %s\n", depth, "", talloc_total_blocks(ptr), name);
|
||||
}
|
||||
|
||||
/* Print a talloc report that is reproducible for test output verification. It contains no pointer addresses. */
|
||||
#define report(CTX) _report(CTX, #CTX)
|
||||
static void _report(void *ctx, const char *label)
|
||||
{
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
printf("%s\n", label);
|
||||
talloc_report_depth_cb(ctx, 0, 100, report_callback, NULL);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void dump_sdp(const char *str, const char *prefix)
|
||||
{
|
||||
while (str && *str) {
|
||||
const char *line_end = str;
|
||||
while (*line_end && *line_end != '\r' && *line_end != '\n')
|
||||
line_end++;
|
||||
while (*line_end == '\r' || *line_end == '\n')
|
||||
line_end++;
|
||||
printf("%s%s\n", prefix, osmo_escape_str(str, line_end - str));
|
||||
str = line_end;
|
||||
}
|
||||
}
|
||||
|
||||
struct sdp_msg_test_data {
|
||||
const char *sdp_input;
|
||||
const char *expect_sdp_str;
|
||||
};
|
||||
|
||||
static const struct sdp_msg_test_data sdp_msg_tests[] = {
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=- 5628250 5628250 IN IP4 192.168.11.121\r\n"
|
||||
"s=-\r\n"
|
||||
"c=IN IP4 192.168.11.121\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 10020 RTP/AVP 18 0 2 4 8 96 97 98 100 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:2 G726-32/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:96 G726-40/8000\r\n"
|
||||
"a=rtpmap:97 G726-24/8000\r\n"
|
||||
"a=rtpmap:98 G726-16/8000\r\n"
|
||||
"a=rtpmap:100 NSE/8000\r\n"
|
||||
"a=fmtp:100 192-193\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
"a=sendrecv\r\n"
|
||||
,
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.151\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 16398 RTP/AVP 98\r\n"
|
||||
"a=rtpmap:98 AMR/8000\r\n"
|
||||
"a=fmtp:98 octet-align=1; mode-set=4\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
"a=rtcp:16399 IN IP4 192.168.11.151\r\n"
|
||||
,
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.151\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 16398 RTP/AVP 98\r\n"
|
||||
"a=rtpmap:98 AMR/8000\r\n"
|
||||
"a=fmtp:98 octet-align=1; mode-set=4\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
/* The rtcp line is dropped, not supported yet */
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.140\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
"a=sendrecv\r\n"
|
||||
"a=rtcp:30437\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
,
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.140\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
/* a=sendrecv ends up further below */
|
||||
/* The rtcp line is dropped, not supported yet */
|
||||
"a=ptime:20\r\n"
|
||||
"a=sendrecv\r\n"
|
||||
,
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.140\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
"a=recvonly\r\n"
|
||||
"a=rtcp:30437\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
,
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.140\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
/* a=recvonly ends up further below */
|
||||
/* The rtcp line is dropped, not supported yet */
|
||||
"a=ptime:20\r\n"
|
||||
"a=recvonly\r\n"
|
||||
,
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.140\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
"a=sendonly\r\n"
|
||||
,
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=FooBar 1565090289 1565090290 IN IP4 192.168.11.151\r\n"
|
||||
"s=FooBar\r\n"
|
||||
"c=IN IP4 192.168.11.140\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 18 0 4 8 101\r\n"
|
||||
"a=rtpmap:18 G729/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:4 G723/8000\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\n"
|
||||
"a=fmtp:101 0-15\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
"a=inactive\r\n"
|
||||
,
|
||||
},
|
||||
};
|
||||
|
||||
static void test_parse_and_compose(void)
|
||||
{
|
||||
void *ctx = talloc_named_const(test_ctx, 0, __func__);
|
||||
int i;
|
||||
bool ok = true;
|
||||
|
||||
printf("\n\n%s\n", __func__);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sdp_msg_tests); i++) {
|
||||
const struct sdp_msg_test_data *t = &sdp_msg_tests[i];
|
||||
struct osmo_sdp_msg *sdp_msg;
|
||||
char str[1024];
|
||||
const char *expect;
|
||||
|
||||
printf("\n[%d]\n", i);
|
||||
dump_sdp(t->sdp_input, "sdp input: ");
|
||||
|
||||
sdp_msg = osmo_sdp_msg_decode(ctx, t->sdp_input, NULL);
|
||||
|
||||
osmo_sdp_msg_encode_buf(str, sizeof(str), sdp_msg);
|
||||
dump_sdp(str, "osmo_sdp_msg_encode_buf: ");
|
||||
expect = t->expect_sdp_str ? : t->sdp_input;
|
||||
if (strcmp(str, expect)) {
|
||||
int j;
|
||||
ok = false;
|
||||
printf("ERROR:\n");
|
||||
dump_sdp(expect, "expect: ");
|
||||
for (j = 0; expect[j]; j++) {
|
||||
if (expect[j] != str[j]) {
|
||||
printf("ERROR at position %d, at:\n", j);
|
||||
dump_sdp(str + j, " mismatch: ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
printf("[%d] ok\n", i);
|
||||
|
||||
report(ctx);
|
||||
printf("talloc_free(sdp_msg)\n");
|
||||
talloc_free(sdp_msg);
|
||||
report(ctx);
|
||||
|
||||
if (talloc_total_blocks(ctx) != 1) {
|
||||
printf("ERROR: memleak\n");
|
||||
talloc_free_children(ctx);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
OSMO_ASSERT(ok);
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
struct intersect_test_data {
|
||||
const char *descr;
|
||||
const char *sdp_msg_a;
|
||||
const char *sdp_msg_b;
|
||||
const char *expect_intersection;
|
||||
};
|
||||
|
||||
#define SDP_1 \
|
||||
"v=0\r\n" \
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n" \
|
||||
"s=GSM Call\r\n" \
|
||||
"c=IN IP4 23.42.23.42\r\n" \
|
||||
"t=0 0\r\n" \
|
||||
"m=audio 30436 RTP/AVP 112 3 111 110\r\n" \
|
||||
"a=rtpmap:112 AMR/8000\r\n" \
|
||||
"a=fmtp:112 octet-align=1\r\n" \
|
||||
"a=rtpmap:3 GSM/8000\r\n" \
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n" \
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n" \
|
||||
"a=ptime:20\r\n"
|
||||
|
||||
#define SDP_2 \
|
||||
"v=0\r\n" \
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n" \
|
||||
"s=GSM Call\r\n" \
|
||||
"c=IN IP4 23.42.23.42\r\n" \
|
||||
"t=0 0\r\n" \
|
||||
"m=audio 30436 RTP/AVP 112 110\r\n" \
|
||||
"a=rtpmap:112 AMR/8000\r\n" \
|
||||
"a=fmtp:112 octet-align=1\r\n" \
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n" \
|
||||
"a=ptime:20\r\n"
|
||||
|
||||
#define SDP_3 \
|
||||
"v=0\r\n" \
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n" \
|
||||
"s=GSM Call\r\n" \
|
||||
"c=IN IP4 23.42.23.42\r\n" \
|
||||
"t=0 0\r\n" \
|
||||
"m=audio 30436 RTP/AVP 3 111 123\r\n" \
|
||||
"a=rtpmap:3 GSM/8000\r\n" \
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n" \
|
||||
"a=rtpmap:123 FOO/8000\r\n" \
|
||||
"a=ptime:20\r\n"
|
||||
|
||||
#define SDP_4 \
|
||||
"v=0\r\n" \
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n" \
|
||||
"s=GSM Call\r\n" \
|
||||
"c=IN IP4 23.42.23.42\r\n" \
|
||||
"t=0 0\r\n" \
|
||||
"m=audio 30436 RTP/AVP 3 111\r\n" \
|
||||
"a=rtpmap:3 GSM/8000\r\n" \
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n" \
|
||||
"a=ptime:20\r\n"
|
||||
|
||||
#define SDP_5 \
|
||||
"v=0\r\n" \
|
||||
"o=libosmo-sdp 0 0 IN IP4 0.0.0.0\r\n" \
|
||||
"s=GSM Call\r\n" \
|
||||
"c=IN IP4 0.0.0.0\r\n" \
|
||||
"t=0 0\r\n" \
|
||||
"m=audio 0 RTP/AVP 112 113 110 3 111\r\n" \
|
||||
"a=rtpmap:112 AMR/8000\r\n" \
|
||||
"a=fmtp:112 octet-align=1;mode-set=0,1,2,3\r\n" \
|
||||
"a=rtpmap:113 AMR-WB/8000\r\n" \
|
||||
"a=fmtp:113 octet-align=1\r\n" \
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n" \
|
||||
"a=rtpmap:3 GSM/8000\r\n" \
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n" \
|
||||
"a=ptime:20\r\n"
|
||||
|
||||
static const struct intersect_test_data intersect_tests[] = {
|
||||
{
|
||||
"identical codecs lead to no change"
|
||||
,
|
||||
SDP_1
|
||||
,
|
||||
"c=IN IP4 5.6.7.8\r\n" \
|
||||
"m=audio 12345 RTP/AVP 112 3 111 110\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
,
|
||||
SDP_1
|
||||
},
|
||||
{
|
||||
"identical codecs in different order also lead to no change"
|
||||
,
|
||||
SDP_1
|
||||
,
|
||||
"c=IN IP4 5.6.7.8\r\n" \
|
||||
"m=audio 12345 RTP/AVP 3 110 111 112\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
,
|
||||
SDP_1
|
||||
},
|
||||
{
|
||||
"identical codecs with mismatching payload type numbers also lead to no change"
|
||||
,
|
||||
SDP_1
|
||||
,
|
||||
"c=IN IP4 5.6.7.8\r\n" \
|
||||
"m=audio 12345 RTP/AVP 96 97 98 99\r\n"
|
||||
"a=rtpmap:96 GSM/8000\r\n"
|
||||
"a=rtpmap:97 GSM-EFR/8000\r\n"
|
||||
"a=rtpmap:98 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:99 AMR/8000\r\n"
|
||||
"a=fmtp:99 octet-align=1\r\n"
|
||||
,
|
||||
SDP_1
|
||||
},
|
||||
{
|
||||
"identical codecs plus some extra codecs also lead to no change"
|
||||
,
|
||||
SDP_1
|
||||
,
|
||||
"c=IN IP4 5.6.7.8\r\n" \
|
||||
"m=audio 12345 RTP/AVP 8 0 96 97 98 99\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\n"
|
||||
"a=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:96 GSM/8000\r\n"
|
||||
"a=rtpmap:97 GSM-EFR/8000\r\n"
|
||||
"a=rtpmap:98 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:99 AMR/8000\r\n"
|
||||
"a=fmtp:99 octet-align=1\r\n"
|
||||
,
|
||||
SDP_1
|
||||
},
|
||||
{
|
||||
"some codecs removed",
|
||||
SDP_1,
|
||||
SDP_2,
|
||||
SDP_2,
|
||||
},
|
||||
{
|
||||
"other codecs removed",
|
||||
SDP_1,
|
||||
SDP_3,
|
||||
SDP_4,
|
||||
},
|
||||
{
|
||||
"all codecs removed",
|
||||
SDP_1
|
||||
,
|
||||
"s=empty"
|
||||
,
|
||||
"v=0\r\n" \
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n" \
|
||||
"s=GSM Call\r\n" \
|
||||
"c=IN IP4 23.42.23.42\r\n" \
|
||||
"t=0 0\r\n" \
|
||||
"m=audio 30436 RTP/AVP\r\n" \
|
||||
"a=ptime:20\r\n"
|
||||
},
|
||||
{
|
||||
"some real world test case",
|
||||
SDP_5, SDP_5, SDP_5,
|
||||
},
|
||||
};
|
||||
|
||||
static const char *sdp_msg_logstr(const struct osmo_sdp_msg *sdp_msg)
|
||||
{
|
||||
static char buf[1024];
|
||||
osmo_sdp_msg_encode_buf(buf, sizeof(buf), sdp_msg);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void test_intersect(void)
|
||||
{
|
||||
int i;
|
||||
bool ok = true;
|
||||
void *ctx = talloc_named_const(test_ctx, 0, __func__);
|
||||
|
||||
printf("\n\n%s\n", __func__);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(intersect_tests); i++) {
|
||||
const struct intersect_test_data *t = &intersect_tests[i];
|
||||
struct osmo_sdp_msg *sdp_msg_a = NULL;
|
||||
struct osmo_sdp_msg *sdp_msg_b = NULL;
|
||||
char str[1024];
|
||||
printf("\n[%d] %s\n", i, t->descr);
|
||||
dump_sdp(t->sdp_msg_a, "SDP A: ");
|
||||
dump_sdp(t->sdp_msg_b, " SDP B: ");
|
||||
|
||||
sdp_msg_a = osmo_sdp_msg_decode(ctx, t->sdp_msg_a, NULL);
|
||||
if (!sdp_msg_a) {
|
||||
printf("ERROR parsing SDP A\n");
|
||||
break;
|
||||
}
|
||||
dump_sdp(sdp_msg_logstr(sdp_msg_a), "parsed SDP A: ");
|
||||
|
||||
sdp_msg_b = osmo_sdp_msg_decode(ctx, t->sdp_msg_b, NULL);
|
||||
if (!sdp_msg_b) {
|
||||
printf("ERROR parsing SDP B\n");
|
||||
break;
|
||||
}
|
||||
dump_sdp(sdp_msg_logstr(sdp_msg_b), "parsed SDP B: ");
|
||||
|
||||
osmo_sdp_codec_list_intersection(sdp_msg_a->codecs, sdp_msg_b->codecs,
|
||||
&osmo_sdp_codec_cmp_equivalent,
|
||||
false);
|
||||
osmo_sdp_msg_encode_buf(str, sizeof(str), sdp_msg_a);
|
||||
dump_sdp(str, "intersection(a,b): ");
|
||||
if (strcmp(str, t->expect_intersection)) {
|
||||
int j;
|
||||
ok = false;
|
||||
printf("ERROR:\n");
|
||||
dump_sdp(t->expect_intersection, "expect_intersection: ");
|
||||
for (j = 0; t->expect_intersection[j]; j++) {
|
||||
if (t->expect_intersection[j] != str[j]) {
|
||||
printf("ERROR at position %d, at:\n", j);
|
||||
dump_sdp(str + j, " mismatch: ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
printf("[%d] ok\n", i);
|
||||
|
||||
report(ctx);
|
||||
printf("talloc_free(sdp_msg_a)\n");
|
||||
talloc_free(sdp_msg_a);
|
||||
report(ctx);
|
||||
printf("talloc_free(sdp_msg_b)\n");
|
||||
talloc_free(sdp_msg_b);
|
||||
report(ctx);
|
||||
|
||||
if (talloc_total_blocks(ctx) != 1) {
|
||||
printf("ERROR: memleak\n");
|
||||
talloc_free_children(ctx);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
OSMO_ASSERT(ok);
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
struct sdp_select_test_data {
|
||||
const char *sdp;
|
||||
const struct osmo_sdp_codec_cmp_flags *cmpf;
|
||||
const struct osmo_sdp_codec select;
|
||||
const char *expect_sdp;
|
||||
};
|
||||
|
||||
static const struct osmo_sdp_codec_cmp_flags pt_only = { .payload_type = true };
|
||||
|
||||
static const struct sdp_select_test_data sdp_select_tests[] = {
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
,
|
||||
&pt_only,
|
||||
{ .payload_type = 112, },
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
,
|
||||
&pt_only,
|
||||
{ .payload_type = 3, },
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 3 112 111 110\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
,
|
||||
&pt_only,
|
||||
{ .payload_type = 111, },
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 111 112 3 110\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
},
|
||||
{
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 112 3 111 110\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
,
|
||||
&pt_only,
|
||||
{ .payload_type = 110, },
|
||||
"v=0\r\n"
|
||||
"o=libosmo-sdp 0 0 IN IP4 23.42.23.42\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 23.42.23.42\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 30436 RTP/AVP 110 112 3 111\r\n"
|
||||
"a=rtpmap:110 GSM-EFR/8000\r\n"
|
||||
"a=rtpmap:112 AMR/8000\r\n"
|
||||
"a=fmtp:112 octet-align=1\r\n"
|
||||
"a=rtpmap:3 GSM/8000\r\n"
|
||||
"a=rtpmap:111 GSM-HR-08/8000\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
static void test_select(void)
|
||||
{
|
||||
int i;
|
||||
bool ok = true;
|
||||
void *ctx = talloc_named_const(test_ctx, 0, __func__);
|
||||
void *print_ctx = talloc_named_const(test_ctx, 0, "print");
|
||||
|
||||
printf("\n\n%s\n", __func__);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sdp_select_tests); i++) {
|
||||
const struct sdp_select_test_data *t = &sdp_select_tests[i];
|
||||
struct osmo_sdp_msg *sdp_msg;
|
||||
char buf[1024];
|
||||
const char *expect_sdp;
|
||||
|
||||
printf("\n[%d]\n", i);
|
||||
sdp_msg = osmo_sdp_msg_decode(ctx, t->sdp, NULL);
|
||||
if (!sdp_msg) {
|
||||
printf("ERROR parsing SDP\n");
|
||||
break;
|
||||
}
|
||||
printf("SDP: %s\n", osmo_sdp_codec_list_to_str_c(print_ctx, sdp_msg->codecs, false));
|
||||
|
||||
printf("Select: %s\n", osmo_sdp_codec_to_str_c(print_ctx, &t->select));
|
||||
osmo_sdp_codec_list_move_to_first(sdp_msg->codecs, &t->select, t->cmpf);
|
||||
|
||||
printf("SDP: %s\n", osmo_sdp_codec_list_to_str_c(print_ctx, sdp_msg->codecs, false));
|
||||
osmo_sdp_msg_encode_buf(buf, sizeof(buf), sdp_msg);
|
||||
|
||||
expect_sdp = t->expect_sdp ? : t->sdp;
|
||||
if (strcmp(buf, expect_sdp)) {
|
||||
int j;
|
||||
ok = false;
|
||||
printf("ERROR:\n");
|
||||
dump_sdp(buf, "selection result: ");
|
||||
dump_sdp(expect_sdp, "expect result: ");
|
||||
for (j = 0; expect_sdp[j]; j++) {
|
||||
if (expect_sdp[j] != buf[j]) {
|
||||
printf("ERROR at position %d, at:\n", j);
|
||||
dump_sdp(buf + j, " mismatch: ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
printf("[%d] ok\n", i);
|
||||
|
||||
report(ctx);
|
||||
printf("talloc_free(sdp_msg)\n");
|
||||
talloc_free(sdp_msg);
|
||||
report(ctx);
|
||||
if (talloc_total_blocks(ctx) != 1) {
|
||||
printf("ERROR: memleak\n");
|
||||
talloc_free_children(ctx);
|
||||
}
|
||||
printf("\n");
|
||||
talloc_free_children(print_ctx);
|
||||
}
|
||||
|
||||
OSMO_ASSERT(ok);
|
||||
talloc_free(ctx);
|
||||
talloc_free(print_ctx);
|
||||
}
|
||||
|
||||
|
||||
struct my_obj {
|
||||
struct osmo_sdp_msg *sdp_msg;
|
||||
};
|
||||
|
||||
static struct my_obj *my_obj_alloc(void *ctx)
|
||||
{
|
||||
struct my_obj *o = talloc_zero(ctx, struct my_obj);
|
||||
return o;
|
||||
}
|
||||
|
||||
static void test_obj_members(void)
|
||||
{
|
||||
void *ctx = talloc_named_const(test_ctx, 0, __func__);
|
||||
void *print_ctx = talloc_named_const(test_ctx, 0, "print");
|
||||
int i;
|
||||
|
||||
struct my_obj *o;
|
||||
|
||||
printf("\n\n--- %s()\n", __func__);
|
||||
o = my_obj_alloc(ctx);
|
||||
|
||||
o->sdp_msg = osmo_sdp_msg_alloc(o);
|
||||
|
||||
printf("o->sdp_msg = '%s'\n", osmo_sdp_msg_encode_c(print_ctx, o->sdp_msg));
|
||||
report(ctx);
|
||||
|
||||
const struct osmo_sdp_codec all_codecs[] = {
|
||||
{ .payload_type = 112, .encoding_name = "AMR", .rate = 8000, .fmtp = "octet-align=1;mode-set=0,2,4" },
|
||||
{ .payload_type = 3, .encoding_name = "GSM", .rate = 8000 },
|
||||
{ .payload_type = 111, .encoding_name = "GSM-HR-08", .rate = 8000 },
|
||||
};
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(all_codecs); i++)
|
||||
osmo_sdp_codec_list_add(o->sdp_msg->codecs, &all_codecs[i], false, false);
|
||||
|
||||
printf("o->sdp_msg = '%s'\n", osmo_sdp_msg_encode_c(print_ctx, o->sdp_msg));
|
||||
|
||||
report(ctx);
|
||||
printf("talloc_free(o)\n");
|
||||
talloc_free(o);
|
||||
report(ctx);
|
||||
talloc_free(ctx);
|
||||
talloc_free(print_ctx);
|
||||
}
|
||||
|
||||
typedef void (*test_func_t)(void);
|
||||
|
||||
static const test_func_t test_func[] = {
|
||||
test_parse_and_compose,
|
||||
test_intersect,
|
||||
test_select,
|
||||
test_obj_members,
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i;
|
||||
test_ctx = talloc_named_const(NULL, 0, "sdp_codec_test");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(test_func); i++) {
|
||||
|
||||
test_func[i]();
|
||||
|
||||
if (talloc_total_blocks(test_ctx) != 1) {
|
||||
talloc_report_full(test_ctx, stderr);
|
||||
printf("ERROR after test %d: memory leak\n", i);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(test_ctx);
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -27,3 +27,10 @@ cat $abs_srcdir/sdp/sdp_codec_test.ok > expout
|
|||
cat $abs_srcdir/sdp/sdp_codec_test.err > experr
|
||||
AT_CHECK([$abs_top_builddir/tests/sdp/sdp_codec_test], [], [expout], [experr])
|
||||
AT_CLEANUP
|
||||
|
||||
AT_SETUP([sdp_msg])
|
||||
AT_KEYWORDS([sdp_msg])
|
||||
cat $abs_srcdir/sdp/sdp_msg_test.ok > expout
|
||||
cat $abs_srcdir/sdp/sdp_msg_test.err > experr
|
||||
AT_CHECK([$abs_top_builddir/tests/sdp/sdp_msg_test], [], [expout], [experr])
|
||||
AT_CLEANUP
|
||||
|
|
Loading…
Reference in New Issue