diff --git a/src/call.c b/src/call.c index 487be56..74c0a08 100644 --- a/src/call.c +++ b/src/call.c @@ -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); } diff --git a/src/sdp.c b/src/sdp.c index 46330cd..1629c2e 100644 --- a/src/sdp.c +++ b/src/sdp.c @@ -32,223 +32,86 @@ #include -/* - * 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; } diff --git a/src/sdp.h b/src/sdp.h index 8e4e314..3247a75 100644 --- a/src/sdp.h +++ b/src/sdp.h @@ -1,15 +1,20 @@ #pragma once -#include -#include +#include -#include +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); diff --git a/src/sip.c b/src/sip.c index 46e4277..072b8ab 100644 --- a/src/sip.c +++ b/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;