| /* |
| * Some SDP file parsing... |
| * |
| * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> |
| * (C) 2009-2014 by On-Waves |
| * All Rights Reserved |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU Affero 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 Affero General Public License for more details. |
| * |
| * You should have received a copy of the GNU Affero General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| */ |
| |
| #include <osmocom/core/msgb.h> |
| #include <osmocom/mgcp/mgcp.h> |
| #include <osmocom/mgcp/mgcp_internal.h> |
| #include <osmocom/mgcp/mgcp_msg.h> |
| #include <osmocom/mgcp/mgcp_endp.h> |
| #include <osmocom/mgcp/mgcp_codec.h> |
| |
| #include <errno.h> |
| |
| /* A struct to store intermediate parsing results. The function |
| * mgcp_parse_sdp_data() is using it as temporary storage for parsing the SDP |
| * codec information. */ |
| struct sdp_rtp_map { |
| /* the type */ |
| int payload_type; |
| /* null, static or later dynamic codec name */ |
| char *codec_name; |
| /* A pointer to the original line for later parsing */ |
| char *map_line; |
| |
| int rate; |
| int channels; |
| }; |
| |
| /* Helper function to extrapolate missing codec parameters in a codec mao from |
| * an already filled in payload_type, called from: mgcp_parse_sdp_data() */ |
| static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) |
| { |
| int i; |
| |
| for (i = 0; i < used; ++i) { |
| switch (codecs[i].payload_type) { |
| case 0: |
| codecs[i].codec_name = "PCMU"; |
| codecs[i].rate = 8000; |
| codecs[i].channels = 1; |
| break; |
| case 3: |
| codecs[i].codec_name = "GSM"; |
| codecs[i].rate = 8000; |
| codecs[i].channels = 1; |
| break; |
| case 8: |
| codecs[i].codec_name = "PCMA"; |
| codecs[i].rate = 8000; |
| codecs[i].channels = 1; |
| break; |
| case 18: |
| codecs[i].codec_name = "G729"; |
| codecs[i].rate = 8000; |
| codecs[i].channels = 1; |
| break; |
| default: |
| codecs[i].codec_name = NULL; |
| codecs[i].rate = 0; |
| codecs[i].channels = 0; |
| } |
| } |
| } |
| |
| /* Helper function to update codec map information with additional data from |
| * SDP, called from: mgcp_parse_sdp_data() */ |
| static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, |
| int payload, const char *audio_name) |
| { |
| int i; |
| |
| for (i = 0; i < used; ++i) { |
| char audio_codec[64]; |
| int rate = -1; |
| int channels = -1; |
| |
| /* Note: We can only update payload codecs that already exist |
| * in our codec list. If we get an unexpected payload type, |
| * we just drop it */ |
| if (codecs[i].payload_type != payload) |
| continue; |
| |
| if (sscanf(audio_name, "%63[^/]/%d/%d", |
| audio_codec, &rate, &channels) < 1) { |
| LOGP(DLMGCP, LOGL_ERROR, "Failed to parse '%s'\n", |
| audio_name); |
| continue; |
| } |
| |
| codecs[i].map_line = talloc_strdup(ctx, audio_name); |
| codecs[i].codec_name = talloc_strdup(ctx, audio_codec); |
| codecs[i].rate = rate; |
| codecs[i].channels = channels; |
| return; |
| } |
| |
| LOGP(DLMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload, |
| audio_name); |
| } |
| |
| /* Extract payload types from SDP, also check for duplicates */ |
| static int pt_from_sdp(void *ctx, struct sdp_rtp_map *codecs, |
| unsigned int codecs_len, char *sdp) |
| { |
| char *str; |
| char *str_ptr; |
| char *pt_str; |
| unsigned int pt; |
| unsigned int count = 0; |
| unsigned int i; |
| |
| str = talloc_zero_size(ctx, strlen(sdp) + 1); |
| str_ptr = str; |
| strcpy(str_ptr, sdp); |
| |
| str_ptr = strstr(str_ptr, "RTP/AVP "); |
| if (!str_ptr) |
| goto exit; |
| |
| pt_str = strtok(str_ptr, " "); |
| if (!pt_str) |
| goto exit; |
| |
| while (1) { |
| /* Do not allow excessive payload types */ |
| if (count > codecs_len) |
| goto error; |
| |
| pt_str = strtok(NULL, " "); |
| if (!pt_str) |
| break; |
| |
| pt = atoi(pt_str); |
| |
| /* Do not allow duplicate payload types */ |
| for (i = 0; i < count; i++) |
| if (codecs[i].payload_type == pt) |
| goto error; |
| |
| codecs[count].payload_type = pt; |
| count++; |
| } |
| |
| exit: |
| talloc_free(str); |
| return count; |
| error: |
| talloc_free(str); |
| return -EINVAL; |
| } |
| |
| /*! Analyze SDP input string. |
| * \param[in] endp trunk endpoint. |
| * \param[out] conn associated rtp connection. |
| * \param[out] caller provided memory to store the parsing results. |
| * |
| * Note: In conn (conn->end) the function returns the packet duration, |
| * rtp port, rtcp port and the codec information. |
| * \returns 0 on success, -1 on failure. */ |
| int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, |
| struct mgcp_conn_rtp *conn, struct mgcp_parse_data *p) |
| { |
| struct sdp_rtp_map codecs[MGCP_MAX_CODECS]; |
| unsigned int codecs_used = 0; |
| char *line; |
| unsigned int i; |
| void *tmp_ctx = talloc_new(NULL); |
| struct mgcp_rtp_end *rtp; |
| |
| int payload; |
| int ptime, ptime2 = 0; |
| char audio_name[64]; |
| int port, rc; |
| char ipv4[16]; |
| |
| OSMO_ASSERT(endp); |
| OSMO_ASSERT(conn); |
| OSMO_ASSERT(p); |
| |
| rtp = &conn->end; |
| memset(&codecs, 0, sizeof(codecs)); |
| |
| for_each_line(line, p->save) { |
| switch (line[0]) { |
| case 'o': |
| case 's': |
| case 't': |
| case 'v': |
| /* skip these SDP attributes */ |
| break; |
| case 'a': |
| if (sscanf(line, "a=rtpmap:%d %63s", |
| &payload, audio_name) == 2) { |
| codecs_update(tmp_ctx, codecs, |
| codecs_used, payload, audio_name); |
| } else |
| if (sscanf |
| (line, "a=ptime:%d-%d", &ptime, &ptime2) >= 1) { |
| if (ptime2 > 0 && ptime2 != ptime) |
| rtp->packet_duration_ms = 0; |
| else |
| rtp->packet_duration_ms = ptime; |
| } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) { |
| rtp->maximum_packet_time = ptime2; |
| } |
| break; |
| case 'm': |
| rc = sscanf(line, "m=audio %d RTP/AVP", &port); |
| if (rc == 1) { |
| rtp->rtp_port = htons(port); |
| rtp->rtcp_port = htons(port + 1); |
| } |
| |
| rc = pt_from_sdp(conn->conn, codecs, |
| ARRAY_SIZE(codecs), line); |
| if (rc > 0) |
| codecs_used = rc; |
| break; |
| case 'c': |
| |
| if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) { |
| inet_aton(ipv4, &rtp->addr); |
| } |
| break; |
| default: |
| if (p->endp) |
| LOGP(DLMGCP, LOGL_NOTICE, |
| "Unhandled SDP option: '%c'/%d on 0x%x\n", |
| line[0], line[0], |
| ENDPOINT_NUMBER(p->endp)); |
| else |
| LOGP(DLMGCP, LOGL_NOTICE, |
| "Unhandled SDP option: '%c'/%d\n", |
| line[0], line[0]); |
| break; |
| } |
| } |
| OSMO_ASSERT(codecs_used <= MGCP_MAX_CODECS); |
| |
| /* So far we have only set the payload type in the codec struct. Now we |
| * fill up the remaining fields of the codec description with some default |
| * information */ |
| codecs_initialize(tmp_ctx, codecs, codecs_used); |
| |
| /* Store parsed codec information */ |
| for (i = 0; i < codecs_used; i++) { |
| rc = mgcp_codec_add(conn, codecs[i].payload_type, codecs[i].map_line); |
| if (rc < 0) |
| LOGP(DLMGCP, LOGL_NOTICE, "endpoint:0x%x, failed to add codec\n", ENDPOINT_NUMBER(p->endp)); |
| } |
| |
| talloc_free(tmp_ctx); |
| |
| LOGP(DLMGCP, LOGL_NOTICE, |
| "Got media info via SDP: port:%d, addr:%s, duration:%d, payload-types:", |
| ntohs(rtp->rtp_port), inet_ntoa(rtp->addr), |
| rtp->packet_duration_ms); |
| if (codecs_used == 0) |
| LOGPC(DLMGCP, LOGL_NOTICE, "none"); |
| for (i = 0; i < codecs_used; i++) { |
| LOGPC(DLMGCP, LOGL_NOTICE, "%d=%s", |
| rtp->codecs[i].payload_type, |
| rtp->codecs[i].subtype_name ? rtp-> codecs[i].subtype_name : "unknown"); |
| LOGPC(DLMGCP, LOGL_NOTICE, " "); |
| } |
| LOGPC(DLMGCP, LOGL_NOTICE, "\n"); |
| |
| return 0; |
| } |
| |
| /*! Generate SDP response string. |
| * \param[in] endp trunk endpoint. |
| * \param[in] conn associated rtp connection. |
| * \param[out] sdp msg buffer to append resulting SDP string data. |
| * \param[in] addr IPV4 address string (e.g. 192.168.100.1). |
| * \returns 0 on success, -1 on failure. */ |
| int mgcp_write_response_sdp(const struct mgcp_endpoint *endp, |
| const struct mgcp_conn_rtp *conn, struct msgb *sdp, |
| const char *addr) |
| { |
| const char *fmtp_extra; |
| const char *audio_name; |
| int payload_type; |
| int rc; |
| |
| OSMO_ASSERT(endp); |
| OSMO_ASSERT(conn); |
| OSMO_ASSERT(sdp); |
| OSMO_ASSERT(addr); |
| |
| /* FIXME: constify endp and conn args in get_net_donwlink_format_cb() */ |
| endp->cfg->get_net_downlink_format_cb((struct mgcp_endpoint *)endp, |
| &payload_type, &audio_name, |
| &fmtp_extra, |
| (struct mgcp_conn_rtp *)conn); |
| |
| rc = msgb_printf(sdp, |
| "v=0\r\n" |
| "o=- %s 23 IN IP4 %s\r\n" |
| "s=-\r\n" |
| "c=IN IP4 %s\r\n" |
| "t=0 0\r\n", conn->conn->id, addr, addr); |
| |
| if (rc < 0) |
| goto buffer_too_small; |
| |
| if (payload_type >= 0) { |
| rc = msgb_printf(sdp, "m=audio %d RTP/AVP %d\r\n", |
| conn->end.local_port, payload_type); |
| if (rc < 0) |
| goto buffer_too_small; |
| |
| /* FIXME: Check if the payload type is from the static range, |
| * if yes, omitthe a=rtpmap since it is unnecessary */ |
| if (audio_name && endp->tcfg->audio_send_name && (payload_type >= 96 && payload_type <= 127)) { |
| rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n", |
| payload_type, audio_name); |
| |
| if (rc < 0) |
| goto buffer_too_small; |
| } |
| |
| if (fmtp_extra) { |
| rc = msgb_printf(sdp, "%s\r\n", fmtp_extra); |
| |
| if (rc < 0) |
| goto buffer_too_small; |
| } |
| } |
| if (conn->end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) { |
| rc = msgb_printf(sdp, "a=ptime:%u\r\n", |
| conn->end.packet_duration_ms); |
| if (rc < 0) |
| goto buffer_too_small; |
| } |
| |
| return 0; |
| |
| buffer_too_small: |
| LOGP(DLMGCP, LOGL_ERROR, "SDP messagebuffer too small\n"); |
| return -1; |
| } |