sdp.c FIXME
Change-Id: Iea61d25827893208cbb5323809908477a3118a54
This commit is contained in:
parent
effead9835
commit
71fa54959c
|
@ -179,6 +179,4 @@ void call_leg_update_sdp(struct call_leg *leg, const char *sdp)
|
|||
if (!sdp || !*sdp)
|
||||
return;
|
||||
OSMO_STRLCPY_ARRAY(leg->sdp, sdp);
|
||||
LOGP(DAPP, LOGL_NOTICE, "call(%u) leg(0x%p) received SDP: %s\n",
|
||||
leg->call->id, leg, leg->sdp);
|
||||
}
|
||||
|
|
263
src/sdp.c
263
src/sdp.c
|
@ -32,223 +32,86 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Check if the media mode attribute exists in SDP, in this
|
||||
* case update the passed pointer with the media mode
|
||||
*/
|
||||
bool sdp_get_sdp_mode(const sip_t *sip, sdp_mode_t *mode) {
|
||||
static const struct value_string sdp_mode_attr[] = {
|
||||
{ SDP_MODE_UNSET, ""},
|
||||
{ SDP_MODE_INACTIVE, "a=inactive\r\n"},
|
||||
{ SDP_MODE_SENDRECV, "a=sendrecv\r\n"},
|
||||
{ SDP_MODE_SENDONLY, "a=sendonly\r\n"},
|
||||
{ SDP_MODE_RECVONLY, "a=recvonly\r\n"},
|
||||
{}
|
||||
};
|
||||
|
||||
const char *sdp_data;
|
||||
sdp_parser_t *parser;
|
||||
sdp_session_t *sdp;
|
||||
const struct value_string sdp_mode_names[] = {
|
||||
{ SDP_MODE_UNSET, "unset"},
|
||||
{ SDP_MODE_INACTIVE, "inactive"},
|
||||
{ SDP_MODE_SENDRECV, "sendrecv"},
|
||||
{ SDP_MODE_SENDONLY, "sendonly"},
|
||||
{ SDP_MODE_RECVONLY, "recvonly"},
|
||||
{}
|
||||
};
|
||||
|
||||
if (!sip->sip_payload || !sip->sip_payload->pl_data) {
|
||||
LOGP(DSIP, LOGL_ERROR, "No SDP file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
sdp_data = sip->sip_payload->pl_data;
|
||||
parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), sdp_f_mode_0000);
|
||||
if (!parser) {
|
||||
LOGP(DSIP, LOGL_ERROR, "Failed to parse SDP\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
sdp = sdp_session(parser);
|
||||
if (!sdp) {
|
||||
LOGP(DSIP, LOGL_ERROR, "No sdp session\n");
|
||||
sdp_parser_free(parser);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sdp->sdp_media || !sdp->sdp_media->m_mode) {
|
||||
sdp_parser_free(parser);
|
||||
return sdp_sendrecv;
|
||||
}
|
||||
|
||||
sdp_parser_free(parser);
|
||||
*mode = sdp->sdp_media->m_mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to decide on the audio codec later but we need to see
|
||||
* if it is even including some of the supported ones.
|
||||
*/
|
||||
bool sdp_screen_sdp(const sip_t *sip)
|
||||
/* Return location of a given SDP sendrecv mode attribute, if any. */
|
||||
const char *sdp_find_mode(const char *sdp_str, enum sdp_mode mode)
|
||||
{
|
||||
const char *sdp_data;
|
||||
sdp_parser_t *parser;
|
||||
sdp_session_t *sdp;
|
||||
sdp_media_t *media;
|
||||
const char *found;
|
||||
if (mode == SDP_MODE_UNSET)
|
||||
return NULL;
|
||||
|
||||
if (!sip->sip_payload || !sip->sip_payload->pl_data) {
|
||||
LOGP(DSIP, LOGL_ERROR, "No SDP file\n");
|
||||
return false;
|
||||
}
|
||||
found = strstr(sdp_str, get_value_string(sdp_mode_attr, mode));
|
||||
if (!found)
|
||||
return NULL;
|
||||
|
||||
sdp_data = sip->sip_payload->pl_data;
|
||||
parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0);
|
||||
if (!parser) {
|
||||
LOGP(DSIP, LOGL_ERROR, "Failed to parse SDP\n");
|
||||
return false;
|
||||
}
|
||||
/* At the start of the line? */
|
||||
if (found > sdp_str && *(found-1) != '\n')
|
||||
return NULL;
|
||||
|
||||
sdp = sdp_session(parser);
|
||||
if (!sdp) {
|
||||
LOGP(DSIP, LOGL_ERROR, "No sdp session\n");
|
||||
sdp_parser_free(parser);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (media = sdp->sdp_media; media; media = media->m_next) {
|
||||
sdp_rtpmap_t *map;
|
||||
|
||||
if (media->m_proto != sdp_proto_rtp)
|
||||
continue;
|
||||
if (media->m_type != sdp_media_audio)
|
||||
continue;
|
||||
|
||||
for (map = media->m_rtpmaps; map; map = map->rm_next) {
|
||||
if (strcasecmp(map->rm_encoding, "GSM") == 0)
|
||||
goto success;
|
||||
if (strcasecmp(map->rm_encoding, "GSM-EFR") == 0)
|
||||
goto success;
|
||||
if (strcasecmp(map->rm_encoding, "GSM-HR-08") == 0)
|
||||
goto success;
|
||||
if (strcasecmp(map->rm_encoding, "AMR") == 0)
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
sdp_parser_free(parser);
|
||||
return false;
|
||||
|
||||
success:
|
||||
sdp_parser_free(parser);
|
||||
return true;
|
||||
return found;
|
||||
}
|
||||
|
||||
bool sdp_extract_sdp(struct sip_call_leg *leg, const sip_t *sip, bool any_codec)
|
||||
/* Return the last SDP sendrecv mode attribute, or SDP_MODE_UNSET */
|
||||
enum sdp_mode sdp_get_mode(const char *sdp_str, const char **found_at)
|
||||
{
|
||||
sdp_connection_t *conn;
|
||||
sdp_session_t *sdp;
|
||||
sdp_parser_t *parser;
|
||||
sdp_media_t *media;
|
||||
const char *sdp_data;
|
||||
bool found_conn = false, found_map = false;
|
||||
|
||||
if (!sip->sip_payload || !sip->sip_payload->pl_data) {
|
||||
LOGP(DSIP, LOGL_ERROR, "leg(%p) but no SDP file\n", leg);
|
||||
return false;
|
||||
}
|
||||
|
||||
sdp_data = sip->sip_payload->pl_data;
|
||||
parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0);
|
||||
if (!parser) {
|
||||
LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to parse SDP\n",
|
||||
leg);
|
||||
return false;
|
||||
}
|
||||
|
||||
sdp = sdp_session(parser);
|
||||
if (!sdp) {
|
||||
LOGP(DSIP, LOGL_ERROR, "leg(%p) no sdp session\n", leg);
|
||||
sdp_parser_free(parser);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (conn = sdp->sdp_connection; conn; conn = conn->c_next) {
|
||||
struct in_addr addr;
|
||||
|
||||
if (conn->c_addrtype != sdp_addr_ip4)
|
||||
const char *at = NULL;
|
||||
enum sdp_mode mode = SDP_MODE_UNSET;
|
||||
enum sdp_mode i;
|
||||
for (i = 0; i < SDP_MODES_COUNT; i++) {
|
||||
const char *found = sdp_find_mode(sdp_str, i);
|
||||
if (!found)
|
||||
continue;
|
||||
inet_aton(conn->c_address, &addr);
|
||||
leg->base.ip = addr.s_addr;
|
||||
found_conn = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (media = sdp->sdp_media; media; media = media->m_next) {
|
||||
sdp_rtpmap_t *map;
|
||||
|
||||
if (media->m_proto != sdp_proto_rtp)
|
||||
/* If more than one sendrecv attrib are in the SDP string, let the last one win */
|
||||
if (at > found)
|
||||
continue;
|
||||
if (media->m_type != sdp_media_audio)
|
||||
continue;
|
||||
|
||||
for (map = media->m_rtpmaps; map; map = map->rm_next) {
|
||||
if (!any_codec && strcasecmp(map->rm_encoding, leg->wanted_codec) != 0)
|
||||
continue;
|
||||
|
||||
leg->base.port = media->m_port;
|
||||
leg->base.payload_type = map->rm_pt;
|
||||
found_map = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (found_map)
|
||||
break;
|
||||
at = found;
|
||||
mode = i;
|
||||
}
|
||||
|
||||
if (!found_conn || !found_map) {
|
||||
LOGP(DSIP, LOGL_ERROR, "leg(%p) did not find %d/%d\n",
|
||||
leg, found_conn, found_map);
|
||||
sdp_parser_free(parser);
|
||||
return false;
|
||||
}
|
||||
|
||||
sdp_parser_free(parser);
|
||||
return true;
|
||||
if (found_at)
|
||||
*found_at = at;
|
||||
return mode;
|
||||
}
|
||||
|
||||
char *sdp_create_file(struct sip_call_leg *leg, struct call_leg *other, sdp_mode_t mode)
|
||||
/* Remove all pre-existing occurences of an SDP mode attribute, and then add the given mode attribute (if not
|
||||
* SDP_MODE_UNSET). Allocate the resulting string from ctx (talloc). */
|
||||
char *sdp_copy_and_set_mode(void *ctx, const char *sdp_str, enum sdp_mode mode)
|
||||
{
|
||||
struct in_addr net = { .s_addr = other->ip };
|
||||
char *fmtp_str = NULL, *sdp;
|
||||
char *mode_attribute;
|
||||
char ip_addr[INET_ADDRSTRLEN];
|
||||
char *removed = talloc_strdup(ctx, sdp_str);
|
||||
char *added;
|
||||
char *found_at;
|
||||
|
||||
inet_ntop(AF_INET, &net, ip_addr, sizeof(ip_addr));
|
||||
leg->wanted_codec = app_media_name(other->payload_msg_type);
|
||||
|
||||
if (strcmp(leg->wanted_codec, "AMR") == 0)
|
||||
fmtp_str = talloc_asprintf(leg, "a=fmtp:%d octet-align=1\r\n", other->payload_type);
|
||||
|
||||
switch (mode) {
|
||||
case sdp_inactive:
|
||||
mode_attribute = "a=inactive\r\n";
|
||||
break;
|
||||
case sdp_sendrecv:
|
||||
mode_attribute = "a=sendrecv\r\n";
|
||||
break;
|
||||
case sdp_sendonly:
|
||||
mode_attribute = "a=sendonly\r\n";
|
||||
break;
|
||||
case sdp_recvonly:
|
||||
mode_attribute = "a=recvonly\r\n";
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
break;
|
||||
/* Remove */
|
||||
while (sdp_get_mode(removed, (const char**)&found_at) != SDP_MODE_UNSET) {
|
||||
char *next_line = strchr(found_at, '\n');
|
||||
if (!next_line)
|
||||
*found_at = '\0';
|
||||
else
|
||||
strcpy(found_at, next_line);
|
||||
}
|
||||
|
||||
sdp = talloc_asprintf(leg,
|
||||
"v=0\r\n"
|
||||
"o=Osmocom 0 0 IN IP4 %s\r\n"
|
||||
"s=GSM Call\r\n"
|
||||
"c=IN IP4 %s\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio %d RTP/AVP %d\r\n"
|
||||
"%s"
|
||||
"a=rtpmap:%d %s/8000\r\n"
|
||||
"%s",
|
||||
ip_addr, ip_addr,
|
||||
other->port, other->payload_type,
|
||||
fmtp_str ? fmtp_str : "",
|
||||
other->payload_type,
|
||||
leg->wanted_codec,
|
||||
mode_attribute);
|
||||
talloc_free(fmtp_str);
|
||||
return sdp;
|
||||
/* Add */
|
||||
if (mode == SDP_MODE_UNSET)
|
||||
return removed;
|
||||
|
||||
added = talloc_asprintf(ctx, "%s%s", removed, get_value_string(sdp_mode_attr, mode));
|
||||
talloc_free(removed);
|
||||
return added;
|
||||
}
|
||||
|
|
25
src/sdp.h
25
src/sdp.h
|
@ -1,15 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <sofia-sip/sip.h>
|
||||
#include <sofia-sip/sdp.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
enum sdp_mode {
|
||||
SDP_MODE_UNSET = 0,
|
||||
SDP_MODE_INACTIVE,
|
||||
SDP_MODE_SENDRECV,
|
||||
SDP_MODE_SENDONLY,
|
||||
SDP_MODE_RECVONLY,
|
||||
SDP_MODES_COUNT,
|
||||
};
|
||||
|
||||
struct sip_call_leg;
|
||||
struct call_leg;
|
||||
extern const struct value_string sdp_mode_names[];
|
||||
static inline const char *sdp_mode_name(enum sdp_mode mode)
|
||||
{ return get_value_string(sdp_mode_names, mode); }
|
||||
|
||||
bool sdp_get_sdp_mode(const sip_t *sip, sdp_mode_t *mode);
|
||||
bool sdp_screen_sdp(const sip_t *sip);
|
||||
bool sdp_extract_sdp(struct sip_call_leg *leg, const sip_t *sip, bool any_codec);
|
||||
|
||||
char *sdp_create_file(struct sip_call_leg *, struct call_leg *, sdp_mode_t mode);
|
||||
const char *sdp_find_mode(const char *sdp_str, enum sdp_mode mode);
|
||||
enum sdp_mode sdp_get_mode(const char *sdp_str, const char **found_at);
|
||||
char *sdp_copy_and_set_mode(void *ctx, const char *sdp_str, enum sdp_mode mode);
|
||||
|
|
72
src/sip.c
72
src/sip.c
|
@ -104,7 +104,6 @@ static void new_call(struct sip_agent *agent, nua_handle_t *nh,
|
|||
struct call *call;
|
||||
struct sip_call_leg *leg;
|
||||
const char *from = NULL, *to = NULL;
|
||||
char ip_addr[INET_ADDRSTRLEN];
|
||||
|
||||
LOGP(DSIP, LOGL_INFO, "Incoming call(%s) handle(%p)\n", sip->sip_call_id->i_id, nh);
|
||||
|
||||
|
@ -146,11 +145,10 @@ static void new_call(struct sip_agent *agent, nua_handle_t *nh,
|
|||
|
||||
static void sip_handle_reinvite(struct sip_call_leg *leg, nua_handle_t *nh, const sip_t *sip) {
|
||||
|
||||
const char *reinvite_sdp;
|
||||
char *sdp;
|
||||
sdp_mode_t mode = sdp_sendrecv;
|
||||
uint32_t ip = leg->base.ip;
|
||||
uint16_t port = leg->base.port;
|
||||
char ip_addr[INET_ADDRSTRLEN];
|
||||
enum sdp_mode mode;
|
||||
enum sdp_mode mode_other;
|
||||
|
||||
LOGP(DSIP, LOGL_INFO, "re-INVITE for call %s\n", sip->sip_call_id->i_id);
|
||||
|
||||
|
@ -162,11 +160,12 @@ static void sip_handle_reinvite(struct sip_call_leg *leg, nua_handle_t *nh, cons
|
|||
return;
|
||||
}
|
||||
|
||||
if (!sdp_get_sdp_mode(sip, &mode)) {
|
||||
reinvite_sdp = sip_get_sdp(sip);
|
||||
if (!reinvite_sdp) {
|
||||
/* re-INVITE with no SDP.
|
||||
* We should respond with SDP reflecting current session
|
||||
*/
|
||||
sdp = sdp_create_file(leg, other, sdp_sendrecv);
|
||||
sdp = sdp_copy_and_set_mode(leg, other->sdp, SDP_MODE_SENDRECV);
|
||||
nua_respond(nh, SIP_200_OK,
|
||||
NUTAG_MEDIA_ENABLE(0),
|
||||
SIPTAG_CONTENT_TYPE_STR("application/sdp"),
|
||||
|
@ -176,37 +175,32 @@ static void sip_handle_reinvite(struct sip_call_leg *leg, nua_handle_t *nh, cons
|
|||
return;
|
||||
}
|
||||
|
||||
struct in_addr net = { .s_addr = leg->base.ip };
|
||||
inet_ntop(AF_INET, &net, ip_addr, sizeof(ip_addr));
|
||||
LOGP(DSIP, LOGL_DEBUG, "pre re-INVITE have IP:port (%s:%u)\n", ip_addr, leg->base.port);
|
||||
mode = sdp_get_mode(reinvite_sdp, NULL);
|
||||
|
||||
call_leg_update_sdp(&leg->base, sip_get_sdp(sip));
|
||||
call_leg_update_sdp(&leg->base, reinvite_sdp);
|
||||
|
||||
if (mode == sdp_sendonly) {
|
||||
switch (mode) {
|
||||
case SDP_MODE_SENDONLY:
|
||||
/* SIP side places call on HOLD */
|
||||
sdp = sdp_create_file(leg, other, sdp_recvonly);
|
||||
/* TODO: Tell core network to stop sending RTP ? */
|
||||
} else {
|
||||
/* SIP re-INVITE may want to change media, IP, port */
|
||||
if (!sdp_extract_sdp(leg, sip, true)) {
|
||||
LOGP(DSIP, LOGL_ERROR, "leg(%p) no audio, releasing\n", leg);
|
||||
nua_respond(nh, SIP_406_NOT_ACCEPTABLE, TAG_END());
|
||||
nua_handle_destroy(nh);
|
||||
call_leg_release(&leg->base);
|
||||
return;
|
||||
}
|
||||
struct in_addr net = { .s_addr = leg->base.ip };
|
||||
inet_ntop(AF_INET, &net, ip_addr, sizeof(ip_addr));
|
||||
LOGP(DSIP, LOGL_DEBUG, "Media IP:port in re-INVITE: (%s:%u)\n", ip_addr, leg->base.port);
|
||||
if (ip != leg->base.ip || port != leg->base.port) {
|
||||
LOGP(DSIP, LOGL_INFO, "re-INVITE changes media connection.\n");
|
||||
if (other->update_rtp)
|
||||
other->update_rtp(leg->base.call->remote);
|
||||
}
|
||||
sdp = sdp_create_file(leg, other, sdp_sendrecv);
|
||||
mode_other = SDP_MODE_RECVONLY;
|
||||
break;
|
||||
case SDP_MODE_RECVONLY:
|
||||
mode_other = SDP_MODE_SENDONLY;
|
||||
break;
|
||||
case SDP_MODE_INACTIVE:
|
||||
mode_other = SDP_MODE_INACTIVE;
|
||||
break;
|
||||
default:
|
||||
mode_other = SDP_MODE_SENDRECV;
|
||||
break;
|
||||
}
|
||||
|
||||
LOGP(DSIP, LOGL_DEBUG, "Sending 200 response to re-INVITE for mode(%u)\n", mode);
|
||||
if (other->update_rtp)
|
||||
other->update_rtp(leg->base.call->remote);
|
||||
|
||||
sdp = sdp_copy_and_set_mode(leg, other->sdp, mode_other);
|
||||
LOGP(DSIP, LOGL_DEBUG, "Sending 200 response (%s) to re-INVITE with mode '%s'\n",
|
||||
sdp_mode_name(mode_other), sdp_mode_name(mode));
|
||||
nua_respond(nh, SIP_200_OK,
|
||||
NUTAG_MEDIA_ENABLE(0),
|
||||
SIPTAG_CONTENT_TYPE_STR("application/sdp"),
|
||||
|
@ -472,17 +466,13 @@ static void sip_connect_call(struct call_leg *_leg)
|
|||
OSMO_ASSERT(_leg->type == CALL_TYPE_SIP);
|
||||
leg = (struct sip_call_leg *) _leg;
|
||||
|
||||
/*
|
||||
* TODO/FIXME: check if resulting codec is compatible..
|
||||
*/
|
||||
|
||||
other = call_leg_other(&leg->base);
|
||||
if (!other) {
|
||||
sip_release_call(&leg->base);
|
||||
return;
|
||||
}
|
||||
|
||||
sdp = sdp_create_file(leg, other, sdp_sendrecv);
|
||||
sdp = sdp_copy_and_set_mode(leg, other->sdp, SDP_MODE_SENDRECV);
|
||||
|
||||
leg->state = SIP_CC_CONNECTED;
|
||||
nua_respond(leg->nua_handle, SIP_200_OK,
|
||||
|
@ -521,7 +511,7 @@ static void sip_hold_call(struct call_leg *_leg)
|
|||
sip_release_call(&leg->base);
|
||||
return;
|
||||
}
|
||||
char *sdp = sdp_create_file(leg, other_leg, sdp_sendonly);
|
||||
char *sdp = sdp_copy_and_set_mode(leg, other_leg->sdp, SDP_MODE_SENDONLY);
|
||||
nua_invite(leg->nua_handle,
|
||||
NUTAG_MEDIA_ENABLE(0),
|
||||
SIPTAG_CONTENT_TYPE_STR("application/sdp"),
|
||||
|
@ -543,7 +533,7 @@ static void sip_retrieve_call(struct call_leg *_leg)
|
|||
sip_release_call(&leg->base);
|
||||
return;
|
||||
}
|
||||
char *sdp = sdp_create_file(leg, other_leg, sdp_sendrecv);
|
||||
char *sdp = sdp_copy_and_set_mode(leg, other_leg->sdp, SDP_MODE_SENDRECV);
|
||||
nua_invite(leg->nua_handle,
|
||||
NUTAG_MEDIA_ENABLE(0),
|
||||
SIPTAG_CONTENT_TYPE_STR("application/sdp"),
|
||||
|
@ -566,7 +556,7 @@ static int send_invite(struct sip_agent *agent, struct sip_call_leg *leg,
|
|||
called_num,
|
||||
agent->app->sip.remote_addr,
|
||||
agent->app->sip.remote_port);
|
||||
char *sdp = sdp_create_file(leg, other, sdp_sendrecv);
|
||||
char *sdp = sdp_copy_and_set_mode(leg, other->sdp, SDP_MODE_SENDRECV);
|
||||
|
||||
leg->state = SIP_CC_INITIAL;
|
||||
leg->dir = SIP_DIR_MT;
|
||||
|
|
Loading…
Reference in New Issue