From f90f421b165c7880cd88db8795f00073dd768f60 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 13 Mar 2021 17:10:08 +0100 Subject: Add libs --- src/libosmocc/sdp.c | 544 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 src/libosmocc/sdp.c (limited to 'src/libosmocc/sdp.c') diff --git a/src/libosmocc/sdp.c b/src/libosmocc/sdp.c new file mode 100644 index 0000000..0f8bca9 --- /dev/null +++ b/src/libosmocc/sdp.c @@ -0,0 +1,544 @@ +/* Session Description Protocol parsing and generator + * This shall be simple and is incomplete. + * + * (C) 2019 by Andreas Eversberg + * All Rights Reserved + * + * 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 3 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 . + */ + +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "endpoint.h" +#include "sdp.h" + +#define strncat_printf(sdp, fmt, arg...) \ + { \ + snprintf(sdp + strlen(sdp), sizeof(sdp) - strlen(sdp), fmt, ## arg); \ + sdp[sizeof(sdp) - 1] = '\0'; \ + } + +/* generate SDP from session structure */ +char *osmo_cc_session_gensdp(osmo_cc_session_t *session) +{ + /* calc max size of SDP: quick an dirty (close to max UDP payload size) */ + static char sdp[65000]; + const char *username, *sess_id, *sess_version, *nettype, *addrtype, *unicast_address; + const char *session_name; + int individual_connection_data = 1; /* in case there is no media, there is no connection data */ + int individual_send_receive = 1; /* in case there is no media, there is no send/receive attribute */ + struct osmo_cc_session_media *media; + struct osmo_cc_session_codec *codec; + + sdp[0] = 0; + + /* Version */ + strncat_printf(sdp, "v=0\r\n"); + + /* Origin */ + username = session->origin_local.username; + sess_id = session->origin_local.sess_id; + sess_version = session->origin_local.sess_version; + nettype = session->origin_local.nettype; + addrtype = session->origin_local.addrtype; + unicast_address = session->origin_local.unicast_address; + strncat_printf(sdp, "o=%s %s %s %s %s %s\r\n", username, sess_id, sess_version, nettype, addrtype, unicast_address); + + /* Session */ + session_name = session->name; + strncat_printf(sdp, "s=%s\r\n", session_name); + + /* Connection Data (if all media have the same data) */ + if (session->media_list) { + osmo_cc_session_for_each_media(session->media_list->next, media) { + if (session->media_list->connection_data_local.nettype != media->connection_data_local.nettype) + break; + if (session->media_list->connection_data_local.addrtype != media->connection_data_local.addrtype) + break; + if (!!strcmp(session->media_list->connection_data_local.address, media->connection_data_local.address)) + break; + } + if (!media) + individual_connection_data = 0; + } + if (!individual_connection_data) + strncat_printf(sdp, "c=%s %s %s\r\n", osmo_cc_session_nettype2string(session->media_list->connection_data_local.nettype), osmo_cc_session_addrtype2string(session->media_list->connection_data_local.addrtype), session->media_list->connection_data_local.address); + + /* timestamp */ + strncat_printf(sdp, "t=0 0\r\n"); + + /* sendonly /recvonly (if all media have the same data) */ + if (session->media_list) { + osmo_cc_session_for_each_media(session->media_list->next, media) { + if (session->media_list->send != media->send) + break; + if (session->media_list->receive != media->receive) + break; + } + if (!media) + individual_send_receive = 0; + } + if (!individual_send_receive) { + if (session->media_list->send && !session->media_list->receive) + strncat_printf(sdp, "a=sendonly\r\n"); + if (!session->media_list->send && session->media_list->receive) + strncat_printf(sdp, "a=recvonly\r\n"); + if (!session->media_list->send && !session->media_list->receive) + strncat_printf(sdp, "a=inactive\r\n"); + } + + /* media */ + osmo_cc_session_for_each_media(session->media_list, media) { + strncat_printf(sdp, "m=%s %u %s", + osmo_cc_session_media_type2string(media->description.type) ? : media->description.type_name, + media->description.port_local, + osmo_cc_session_media_proto2string(media->description.proto) ? : media->description.proto_name); + osmo_cc_session_for_each_codec(media->codec_list, codec) + strncat_printf(sdp, " %u", codec->payload_type_local); + strncat_printf(sdp, "\r\n"); + /* don't list rtpmap when session was canceled by setting port to 0 */ + if (media->description.port_local == 0) + continue; + if (individual_connection_data) + strncat_printf(sdp, "c=%s %s %s\r\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype), osmo_cc_session_addrtype2string(media->connection_data_local.addrtype), media->connection_data_local.address); + osmo_cc_session_for_each_codec(media->codec_list, codec) { + strncat_printf(sdp, "a=rtpmap:%u %s/%d", codec->payload_type_local, codec->payload_name, codec->payload_rate); + if (codec->payload_channels >= 2) + strncat_printf(sdp, "/%d", codec->payload_channels); + strncat_printf(sdp, "\r\n"); + } + if (individual_send_receive) { + if (media->send && !media->receive) + strncat_printf(sdp, "a=sendonly\r\n"); + if (!media->send && media->receive) + strncat_printf(sdp, "a=recvonly\r\n"); + if (!media->send && !media->receive) + strncat_printf(sdp, "a=inactive\r\n"); + } + } + + /* check for overflow and return */ + if (strlen(sdp) == sizeof(sdp) - 1) { + PDEBUG(DCC, DEBUG_ERROR, "Fatal error: Allocated SDP buffer with %d bytes is too small, please fix!\n", (int)sizeof(sdp)); + return NULL; + } + return sdp; +} + +/* separate a word from string that is delimited with one or more space characters */ +static char *wordsep(char **text_p) +{ + char *text = *text_p; + static char word[256]; + int i; + + /* no text */ + if (text == NULL || *text == '\0') + return NULL; + /* skip spaces before text */ + while (*text && *text <= ' ') + text++; + /* copy content */ + i = 0; + while (*text > ' ' && i < (int)sizeof(word)) + word[i++] = *text++; + word[i] = '\0'; + /* set next */ + *text_p = text; + return word; +} + +/* + * codecs and their default values + * + * if format is -1, payload type is dynamic + * if rate is 0, rate may be any rate + */ +struct codec_defaults { + int fmt; + char *name; + uint32_t rate; + int channels; +} codec_defaults[] = { + { 0, "PCMU", 8000, 1 }, + { 3, "GSM", 8000, 1 }, + { 4, "G723", 8000, 1 }, + { 5, "DVI4", 8000, 1 }, + { 6, "DVI4", 16000, 1 }, + { 7, "LPC", 8000, 1 }, + { 8, "PCMA", 8000, 1 }, + { 9, "G722", 8000, 1 }, + { 10, "L16", 44100, 2 }, + { 11, "L16", 44100, 1 }, + { 12, "QCELP", 8000, 1 }, + { 13, "CN", 8000, 1 }, + { 14, "MPA", 90000, 1 }, + { 15, "G728", 8000, 1 }, + { 16, "DVI4", 11025, 1 }, + { 17, "DVI4", 22050, 1 }, + { 18, "G729", 8000, 1 }, + { 25, "CELB", 90000, 0 }, + { 26, "JPEG", 90000, 0 }, + { 28, "nv", 90000, 0 }, + { 31, "H261", 90000, 0 }, + { 32, "MPV", 90000, 0 }, + { 33, "MP2T", 90000, 0 }, + { 34, "H263", 90000, 0 }, + { -1, NULL, 0, 0 }, +}; + +static void complete_codec_by_fmt(uint8_t fmt, const char **name, uint32_t *rate, int *channels) +{ + int i; + + for (i = 0; codec_defaults[i].name; i++) { + if (codec_defaults[i].fmt == fmt) + break; + } + if (!codec_defaults[i].name) + return; + + free((char *)*name); + *name = strdup(codec_defaults[i].name); + *rate = codec_defaults[i].rate; + *channels = codec_defaults[i].channels; +} + +int osmo_cc_payload_type_by_attrs(uint8_t *fmt, const char *name, uint32_t *rate, int *channels) +{ + int i; + + for (i = 0; codec_defaults[i].name; i++) { + if (!strcmp(codec_defaults[i].name, name) + && (*rate == 0 || codec_defaults[i].rate == *rate) + && (*channels == 0 || codec_defaults[i].channels == *channels)) + break; + } + if (!codec_defaults[i].name) + return -EINVAL; + + *fmt = codec_defaults[i].fmt; + *rate = codec_defaults[i].rate; + *channels = codec_defaults[i].channels; + + return 0; +} + +/* parses data and codec list from SDP + * + * sdp = given SDP text + * return: SDP session description structure */ +struct osmo_cc_session *osmo_cc_session_parsesdp(void *priv, const char *_sdp) +{ + char buffer[strlen(_sdp) + 1], *sdp = buffer; + char *line, *p, *word, *next_word; + int line_no = 0; + struct osmo_cc_session_connection_data ccd, *cd; + int csend = 1, creceive = 1; /* common default */ + struct osmo_cc_session *session = NULL; + struct osmo_cc_session_media *media = NULL; + struct osmo_cc_session_codec *codec = NULL; + + /* prepare data */ + strcpy(sdp, _sdp); + memset(&ccd, 0, sizeof(ccd)); + + /* create SDP session description */ + session = osmo_cc_new_session(priv, NULL, NULL, NULL, osmo_cc_session_nettype_inet, osmo_cc_session_addrtype_ipv4, "127.0.0.1", NULL, 0); // values will be replaced by local definitions during negotiation + + /* check every line of SDP and parse its data */ + while(*sdp) { + if ((p = strchr(sdp, '\r'))) { + *p++ = '\0'; + if (*p == '\n') + p++; + line = sdp; + sdp = p; + } else if ((p = strchr(sdp, '\n'))) { + *p++ = '\0'; + line = sdp; + sdp = p; + } else { + line = sdp; + sdp = strchr(sdp, '\0'); + } + next_word = line + 2; + line_no++; + + if (line[0] == '\0') + continue; + + if (line[1] != '=') { + PDEBUG(DCC, DEBUG_NOTICE, "SDP line %d = '%s' is garbage, expecting '=' as second character.\n", line_no, line); + continue; + } + + switch(line[0]) { + case 'v': + PDEBUG(DCC, DEBUG_DEBUG, " -> Version: %s\n", next_word); + if (atoi(next_word) != 0) { + PDEBUG(DCC, DEBUG_NOTICE, "SDP line %d = '%s' describes unsupported version.\n", line_no, line); + osmo_cc_free_session(session); + return NULL; + } + break; + case 'o': + PDEBUG(DCC, DEBUG_DEBUG, " -> Originator: %s\n", next_word); + /* Originator */ + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.username); // if already set + session->origin_remote.username = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.sess_id); // if already set + session->origin_remote.sess_id = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.sess_version); // if already set + session->origin_remote.sess_version = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.nettype); // if already set + session->origin_remote.nettype = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.addrtype); // if already set + session->origin_remote.addrtype = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.unicast_address); // if already set + session->origin_remote.unicast_address = strdup(word); + break; + case 's': + /* Session Name */ + PDEBUG(DCC, DEBUG_DEBUG, " -> Session Name: %s\n", next_word); + free((char *)session->name); // if already set + session->name = strdup(next_word); + break; + case 'c': /* Connection Data */ + PDEBUG(DCC, DEBUG_DEBUG, " -> Connection Data: %s\n", next_word); + if (media) + cd = &media->connection_data_remote; + else + cd = &ccd; + /* network type */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "IN")) + cd->nettype = osmo_cc_session_nettype_inet; + else { + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported network type '%s' in SDP line %d = '%s'\n", word, line_no, line); + break; + } + /* address type */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "IP4")) { + cd->addrtype = osmo_cc_session_addrtype_ipv4; + PDEBUG(DCC, DEBUG_DEBUG, " -> Address Type = IPv4\n"); + } else + if (!strcmp(word, "IP6")) { + cd->addrtype = osmo_cc_session_addrtype_ipv6; + PDEBUG(DCC, DEBUG_DEBUG, " -> Address Type = IPv6\n"); + } else { + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s' in SDP line %d = '%s'\n", word, line_no, line); + break; + } + /* connection address */ + if (!(word = wordsep(&next_word))) + break; + if ((p = strchr(word, '/'))) + *p++ = '\0'; + free((char *)cd->address); // in case of multiple lines of 'c' + cd->address = strdup(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> Address = %s\n", word); + break; + case 'm': /* Media Description */ + PDEBUG(DCC, DEBUG_DEBUG, " -> Media Description: %s\n", next_word); + /* add media description */ + media = osmo_cc_add_media(session, 0, 0, NULL, 0, 0, 0, csend, creceive, NULL, 0); + /* copy common connection data from common connection, if exists */ + cd = &media->connection_data_remote; + memcpy(cd, &ccd, sizeof(*cd)); + /* media type */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "audio")) + media->description.type = osmo_cc_session_media_type_audio; + else + if (!strcmp(word, "video")) + media->description.type = osmo_cc_session_media_type_video; + else { + media->description.type = osmo_cc_session_media_type_unknown; + media->description.type_name = strdup(word); + PDEBUG(DCC, DEBUG_DEBUG, "Unsupported media type in SDP line %d = '%s'\n", line_no, line); + } + /* port */ + if (!(word = wordsep(&next_word))) + break; + media->description.port_remote = atoi(word); + /* proto */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "RTP/AVP")) + media->description.proto = osmo_cc_session_media_proto_rtp; + else { + media->description.proto = osmo_cc_session_media_proto_unknown; + media->description.proto_name = strdup(word); + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported protocol type in SDP line %d = '%s'\n", line_no, line); + break; + } + /* create codec description for each codec and link */ + while ((word = wordsep(&next_word))) { + /* create codec */ + codec = osmo_cc_add_codec(media, NULL, 0, 1, NULL, NULL, 0); + /* fmt */ + codec->payload_type_remote = atoi(word); + complete_codec_by_fmt(codec->payload_type_remote, &codec->payload_name, &codec->payload_rate, &codec->payload_channels); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_remote); + if (codec->payload_name) + PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name); + if (codec->payload_rate) + PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate); + if (codec->payload_channels) + PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels); + } + break; + case 'a': + PDEBUG(DCC, DEBUG_DEBUG, " -> Attribute: %s\n", next_word); + word = wordsep(&next_word); + if (!strcmp(word, "sendrecv")) { + if (media) { + media->receive = 1; + media->send = 1; + } else { + creceive = 1; + csend = 1; + } + break; + } else + if (!strcmp(word, "recvonly")) { + if (media) { + media->receive = 1; + media->send = 0; + } else { + creceive = 1; + csend = 0; + } + break; + } else + if (!strcmp(word, "sendonly")) { + if (media) { + media->receive = 0; + media->send = 1; + } else { + creceive = 0; + csend = 1; + } + break; + } else + if (!strcmp(word, "inactive")) { + if (media) { + media->receive = 0; + media->send = 0; + } else { + creceive = 0; + csend = 0; + } + break; + } else + if (!media) { + PDEBUG(DCC, DEBUG_NOTICE, "Attribute without previously defined media in SDP line %d = '%s'\n", line_no, line); + break; + } + if (!strncmp(word, "rtpmap:", 7)) { + int fmt = atoi(word + 7); + osmo_cc_session_for_each_codec(media->codec_list, codec) { + if (codec->payload_type_remote == fmt) + break; + } + if (!codec) { + PDEBUG(DCC, DEBUG_NOTICE, "Attribute without previously defined codec in SDP line %d = '%s'\n", line_no, line); + break; + } + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload type = %d\n", codec->payload_type_remote); + if (!(word = wordsep(&next_word))) + goto rtpmap_done; + if ((p = strchr(word, '/'))) + *p++ = '\0'; + free((char *)codec->payload_name); // in case it is already set above + codec->payload_name = strdup(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload name = %s\n", codec->payload_name); + if (!(word = p)) + goto rtpmap_done; + if ((p = strchr(word, '/'))) + *p++ = '\0'; + codec->payload_rate = atoi(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload rate = %d\n", codec->payload_rate); + if (!(word = p)) { + /* if no channel is given and no default was specified, we must set 1 channel */ + if (!codec->payload_channels) + codec->payload_channels = 1; + goto rtpmap_done; + } + codec->payload_channels = atoi(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload channels = %d\n", codec->payload_channels); + rtpmap_done: + if (!codec->payload_name || !codec->payload_rate || !codec->payload_channels) { + PDEBUG(DCC, DEBUG_NOTICE, "Broken 'rtpmap' definition in SDP line %d = '%s' Skipping codec!\n", line_no, line); + osmo_cc_free_codec(codec); + } + } + break; + } + } + + /* if something is incomplete, abort here */ + if (osmo_cc_session_check(session, 1)) { + PDEBUG(DCC, DEBUG_NOTICE, "Parsing SDP failed.\n"); + osmo_cc_free_session(session); + return NULL; + } + + return session; +} + +void osmo_cc_debug_sdp(const char *_sdp) +{ + const unsigned char *sdp = (const unsigned char *)_sdp; + char text[256]; + int i; + + while (*sdp) { + for (i = 0; *sdp > 0 && *sdp >= 32 && i < (int)sizeof(text) - 1; i++) + text[i] = *sdp++; + text[i] = '\0'; + PDEBUG(DCC, DEBUG_DEBUG, " | %s\n", text); + while (*sdp > 0 && *sdp < 32) + sdp++; + } +} + -- cgit v1.2.3