diff --git a/src/call.c b/src/call.c index 9f593ea..3f7beb2 100644 --- a/src/call.c +++ b/src/call.c @@ -172,3 +172,20 @@ const char *call_leg_state(struct call_leg *leg) return "Unknown call type"; } } + +void call_leg_rx_sdp(struct call_leg *leg, const char *rx_sdp) +{ + /* If no SDP was received, keep whatever SDP was previously seen. */ + if (!rx_sdp || !*rx_sdp || !strncmp(leg->rx_sdp, rx_sdp, sizeof(leg->rx_sdp))) { + LOGP(DAPP, LOGL_DEBUG, "call(%u) leg(0x%p) no new SDP in %s\n", leg->call->id, leg, + osmo_quote_str(rx_sdp, -1)); + LOGP(DAPP, LOGL_DEBUG, "call(%u) leg(0x%p) keep stored SDP=%s\n", leg->call->id, leg, + osmo_quote_str(leg->rx_sdp, -1)); + return; + } + LOGP(DAPP, LOGL_DEBUG, "call(%u) leg(0x%p) received new SDP=%s\n", leg->call->id, leg, osmo_quote_str(rx_sdp, -1)); + LOGP(DAPP, LOGL_DEBUG, "call(%u) leg(0x%p) replaced old SDP=%s\n", leg->call->id, leg, + osmo_quote_str(leg->rx_sdp, -1)); + OSMO_STRLCPY_ARRAY(leg->rx_sdp, rx_sdp); + leg->rx_sdp_changed = true; +} diff --git a/src/call.h b/src/call.h index a835f71..cd8d630 100644 --- a/src/call.h +++ b/src/call.h @@ -62,7 +62,11 @@ struct call_leg { /* SDP as received for this call leg. If this is an MNCC call leg, contains the SDP most recently received in an * MNCC message; if this is a SIP call leg, contains the SDP most recently received in a SIP message. If no SDP * was received yet, this string is empty. Otherwise a nul terminated string. */ - char sdp[1024]; + char rx_sdp[1024]; + /* If the contents of rx_sdp[] changes, set rx_sdp_changed = true. When the other call leg transmits the next + * message, it can decide whether to include SDP because there is new information, or whether to omit SDP + * because it was already sent identically earlier. */ + bool rx_sdp_changed; /** * Remote started to ring/alert @@ -167,6 +171,8 @@ void calls_init(void); struct call_leg *call_leg_other(struct call_leg *leg); +void call_leg_rx_sdp(struct call_leg *cl, const char *rx_sdp); + void call_leg_release(struct call_leg *leg); diff --git a/src/mncc.c b/src/mncc.c index 02ba2cc..a12f615 100644 --- a/src/mncc.c +++ b/src/mncc.c @@ -170,12 +170,14 @@ static int mncc_rtp_write(struct mncc_connection *conn, struct gsm_mncc_rtp *rtp return rc; } -static int mncc_rtp_send(struct mncc_connection *conn, uint32_t msg_type, uint32_t callref) +static int mncc_rtp_send(struct mncc_connection *conn, uint32_t msg_type, uint32_t callref, const char *sdp) { struct gsm_mncc_rtp mncc = { 0, }; mncc.msg_type = msg_type; mncc.callref = callref; + if (sdp) + OSMO_STRLCPY_ARRAY(mncc.sdp, sdp); return mncc_rtp_write(conn, &mncc); } @@ -195,6 +197,10 @@ static bool send_rtp_connect(struct mncc_call_leg *leg, struct call_leg *other) mncc.callref = leg->callref; mncc.addr = other->addr; mncc.payload_type = other->payload_type; + + /* Forward whichever SDP was last received on the other call leg */ + OSMO_STRLCPY_ARRAY(mncc.sdp, other->rx_sdp); + /* * FIXME: mncc.payload_msg_type should already be compatible.. but * payload_type should be different.. @@ -408,6 +414,8 @@ static void check_rtp_connect(struct mncc_connection *conn, const char *buf, int return; } + call_leg_rx_sdp(&leg->base, rtp->sdp); + /* extract information about where the RTP is */ if (rtp->addr.ss_family != AF_UNSPEC || osmo_sockaddr_port((const struct sockaddr *)&rtp->addr) != 0 || @@ -446,6 +454,7 @@ static void check_rtp_create(struct mncc_connection *conn, const char *buf, int leg->base.addr = rtp->addr; leg->base.payload_type = rtp->payload_type; leg->base.payload_msg_type = rtp->payload_msg_type; + call_leg_rx_sdp(&leg->base, rtp->sdp); /* TODO.. now we can continue with the call */ LOGP(DMNCC, LOGL_INFO, @@ -485,6 +494,8 @@ static void check_setup(struct mncc_connection *conn, const char *buf, int rc) const struct gsm_mncc_number *called; struct call *call; struct mncc_call_leg *leg; + struct call_leg *other_leg; + const char *sdp = NULL; struct osmo_gcr_parsed gcr; if (rc < sizeof(*data)) { @@ -557,6 +568,7 @@ static void check_setup(struct mncc_connection *conn, const char *buf, int rc) memcpy(&leg->called, called, sizeof(leg->called)); memcpy(&leg->calling, &data->calling, sizeof(leg->calling)); memcpy(&leg->imsi, data->imsi, sizeof(leg->imsi)); + call_leg_rx_sdp(&leg->base, data->sdp); if (data->fields & MNCC_F_GCR) { leg->base.call->gcr_present = true; @@ -567,8 +579,14 @@ static void check_setup(struct mncc_connection *conn, const char *buf, int rc) "Created call(%u) with MNCC leg(%u) IMSI(%.16s)\n", call->id, leg->callref, data->imsi); + other_leg = call_leg_other(&leg->base); + if (other_leg && *other_leg->rx_sdp && other_leg->rx_sdp_changed) { + sdp = other_leg->rx_sdp; + other_leg->rx_sdp_changed = false; + } + start_cmd_timer(leg, MNCC_RTP_CREATE); - mncc_rtp_send(conn, MNCC_RTP_CREATE, data->callref); + mncc_rtp_send(conn, MNCC_RTP_CREATE, data->callref, sdp); } /*! Find MNCC Call leg by given MNCC message @@ -672,6 +690,8 @@ static void check_stp_cmpl_ind(struct mncc_connection *conn, const char *buf, in if (!leg) return; + call_leg_rx_sdp(&leg->base, data->sdp); + LOGP(DMNCC, LOGL_INFO, "leg(%u) is now connected.\n", leg->callref); stop_cmd_timer(leg, MNCC_SETUP_COMPL_IND); leg->state = MNCC_CC_CONNECTED; @@ -701,17 +721,29 @@ static void check_cnf_ind(struct mncc_connection *conn, const char *buf, int rc) { const struct gsm_mncc *data; struct mncc_call_leg *leg; + struct call_leg *other_leg; + const char *sdp = NULL; leg = find_leg(conn, buf, rc, &data); if (!leg) return; + /* Remember last received SDP from MNCC (if any) */ + call_leg_rx_sdp(&leg->base, data->sdp); + + /* Forward SDP received from the other side */ + other_leg = call_leg_other(&leg->base); + if (other_leg && *other_leg->rx_sdp && other_leg->rx_sdp_changed) { + sdp = other_leg->rx_sdp; + other_leg->rx_sdp_changed = false; + } + LOGP(DMNCC, LOGL_DEBUG, "leg(%u) confirmed. creating RTP socket.\n", leg->callref); start_cmd_timer(leg, MNCC_RTP_CREATE); - mncc_rtp_send(conn, MNCC_RTP_CREATE, data->callref); + mncc_rtp_send(conn, MNCC_RTP_CREATE, data->callref, sdp); } static void check_alrt_ind(struct mncc_connection *conn, const char *buf, int rc) @@ -724,6 +756,8 @@ static void check_alrt_ind(struct mncc_connection *conn, const char *buf, int rc if (!leg) return; + call_leg_rx_sdp(&leg->base, data->sdp); + LOGP(DMNCC, LOGL_DEBUG, "leg(%u) is alerting.\n", leg->callref); @@ -802,6 +836,8 @@ static void check_stp_cnf(struct mncc_connection *conn, const char *buf, int rc) if (!leg) return; + call_leg_rx_sdp(&leg->base, data->sdp); + LOGP(DMNCC, LOGL_DEBUG, "leg(%u) setup completed\n", leg->callref); other_leg = call_leg_other(&leg->base); @@ -831,6 +867,8 @@ static void check_dtmf_start(struct mncc_connection *conn, const char *buf, int if (!leg) return; + call_leg_rx_sdp(&leg->base, data->sdp); + LOGP(DMNCC, LOGL_DEBUG, "leg(%u) DTMF key=%c\n", leg->callref, data->keypad); other_leg = call_leg_other(&leg->base); @@ -853,6 +891,8 @@ static void check_dtmf_stop(struct mncc_connection *conn, const char *buf, int r if (!leg) return; + call_leg_rx_sdp(&leg->base, data->sdp); + LOGP(DMNCC, LOGL_DEBUG, "leg(%u) DTMF key=%c\n", leg->callref, data->keypad); mncc_fill_header(&out_mncc, MNCC_STOP_DTMF_RSP, leg->callref); @@ -945,11 +985,17 @@ int mncc_create_remote_leg(struct mncc_connection *conn, struct call *call) msgb_free(msg); } + /* The call->initial leg is a SIP call leg that starts an MT call. There was SDP received in the SIP INVITE that + * started this call. This here will be the call->remote, always forwarding the SDP that came in on + * call->initial. */ + if (call->initial && call->initial->rx_sdp_changed) { + OSMO_STRLCPY_ARRAY(mncc.sdp, call->initial->rx_sdp); + call->initial->rx_sdp_changed = false; + } + /* * TODO/FIXME: - * - Determine/request channel based on offered audio codecs * - Screening, redirect? - * - Synth. the bearer caps based on codecs? */ rc = mncc_write(conn, &mncc); if (rc != sizeof(mncc)) { diff --git a/src/sdp.c b/src/sdp.c index 6ef9656..f3ca5a4 100644 --- a/src/sdp.c +++ b/src/sdp.c @@ -191,7 +191,9 @@ bool sdp_extract_sdp(struct sip_call_leg *leg, const sip_t *sip, bool any_codec) continue; for (map = media->m_rtpmaps; map; map = map->rm_next) { - if (!any_codec && strcasecmp(map->rm_encoding, leg->wanted_codec) != 0) + if (!any_codec + && leg->wanted_codec + && strcasecmp(map->rm_encoding, leg->wanted_codec) != 0) continue; port = media->m_port; @@ -226,21 +228,43 @@ bool sdp_extract_sdp(struct sip_call_leg *leg, const sip_t *sip, bool any_codec) return true; } +/* One leg has sent a SIP or MNCC message, which is now translated/forwarded to the counterpart MNCC or SIP. + * Take as much from the source's SDP as possible, but make sure the connection mode reflects the 'mode' arg (sendrecv, + * recvonly, sendonly, inactive). + * For example, if the MSC sent an MNCC_SETUP_IND, the SDP from the MNCC is found in 'other', while 'leg' reflects the + * SIP side that should receive this SDP in the SIP Invite that is being composed by the caller of this function. + * \param leg The target for which the returned SDP is intended. + * \param other The source of which we are to reflect the SDP. + * \return SDP string, using 'leg' as talloc ctx. + */ char *sdp_create_file(struct sip_call_leg *leg, struct call_leg *other, sdp_mode_t mode) { - char *fmtp_str = NULL, *sdp; - char *mode_attribute; - char ip_addr[INET6_ADDRSTRLEN]; - char ipv; + sdp_parser_t *parser; + sdp_session_t *sdp; + sdp_media_t *media; + const char *sdp_data; + sdp_printer_t *printer; + char buf[1024]; + const char *sdp_str; + char *ret; - osmo_sockaddr_ntop((const struct sockaddr*)&other->addr, ip_addr); - ipv = other->addr.ss_family == AF_INET6 ? '6' : '4'; - leg->wanted_codec = app_media_name(other->payload_msg_type); + sdp_data = other->rx_sdp; - if (strcmp(leg->wanted_codec, "AMR") == 0) - fmtp_str = talloc_asprintf(leg, "a=fmtp:%d octet-align=1\r\n", other->payload_type); + if (!*sdp_data) { + /* Legacy compat: We have not received any SDP from the other call leg. Compose some original SDP from + * the RTP information we have. */ + char *fmtp_str = NULL, *sdp; + char *mode_attribute; + char ip_addr[INET6_ADDRSTRLEN]; + char ipv; - switch (mode) { + osmo_sockaddr_ntop((const struct sockaddr*)&other->addr, ip_addr); + ipv = other->addr.ss_family == AF_INET6 ? '6' : '4'; + 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; @@ -256,25 +280,59 @@ char *sdp_create_file(struct sip_call_leg *leg, struct call_leg *other, sdp_mode default: OSMO_ASSERT(false); break; + } + + return talloc_asprintf(leg, + "v=0\r\n" + "o=Osmocom 0 0 IN IP%c %s\r\n" + "s=GSM Call\r\n" + "c=IN IP%c %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", + ipv, ip_addr, ipv, ip_addr, + osmo_sockaddr_port((const struct sockaddr *)&other->addr), + other->payload_type, + fmtp_str ? fmtp_str : "", + other->payload_type, + leg->wanted_codec, + mode_attribute); } - sdp = talloc_asprintf(leg, - "v=0\r\n" - "o=Osmocom 0 0 IN IP%c %s\r\n" - "s=GSM Call\r\n" - "c=IN IP%c %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", - ipv, ip_addr, ipv, ip_addr, - osmo_sockaddr_port((const struct sockaddr *)&other->addr), - other->payload_type, - fmtp_str ? fmtp_str : "", - other->payload_type, - leg->wanted_codec, - mode_attribute); - talloc_free(fmtp_str); - return sdp; + parser = sdp_parse(NULL, sdp_data, strlen(sdp_data), 0); + if (!parser) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to parse SDP\n", other); + return talloc_strdup(leg, sdp_data); + } + + sdp = sdp_session(parser); + if (!sdp) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) no sdp session\n", other); + sdp_parser_free(parser); + return talloc_strdup(leg, sdp_data); + } + + for (media = sdp->sdp_media; media; media = media->m_next) + media->m_mode = mode; + + printer = sdp_print(NULL, sdp, buf, sizeof(buf), sdp_f_mode_always); + if (!printer) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to print SDP\n", other); + sdp_parser_free(parser); + return talloc_strdup(leg, sdp_data); + } + + sdp_str = sdp_message(printer); + if (!sdp_str) { + LOGP(DSIP, LOGL_ERROR, "leg(%p) failed to print SDP: %s\n", other, sdp_printing_error(printer)); + sdp_str = sdp_data; + } + + ret = talloc_strdup(leg, sdp_str); + + sdp_parser_free(parser); + sdp_printer_free(printer); + return ret; } diff --git a/src/sdp.h b/src/sdp.h index 8e4e314..dae2e96 100644 --- a/src/sdp.h +++ b/src/sdp.h @@ -11,5 +11,4 @@ struct call_leg; 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); +char *sdp_create_file(struct sip_call_leg *leg, struct call_leg *other, sdp_mode_t mode); diff --git a/src/sip.c b/src/sip.c index 61c7cbd..f3df721 100644 --- a/src/sip.c +++ b/src/sip.c @@ -46,6 +46,12 @@ static void sip_dtmf_call(struct call_leg *_leg, int keypad); static void sip_hold_call(struct call_leg *_leg); static void sip_retrieve_call(struct call_leg *_leg); +static const char *sip_get_sdp(const sip_t *sip) +{ + if (!sip || !sip->sip_payload) + return NULL; + return sip->sip_payload->pl_data; +} /* Find a SIP Call leg by given nua_handle */ static struct sip_call_leg *sip_find_leg(nua_handle_t *nh) @@ -196,6 +202,8 @@ static void new_call(struct sip_agent *agent, nua_handle_t *nh, nua_handle_bind(nh, leg); leg->sdp_payload = talloc_strdup(leg, sip->sip_payload->pl_data); + call_leg_rx_sdp(&leg->base, sip_get_sdp(sip)); + app_route_call(call, talloc_strdup(leg, from), talloc_strdup(leg, to)); @@ -236,6 +244,8 @@ static void sip_handle_reinvite(struct sip_call_leg *leg, nua_handle_t *nh, cons osmo_sockaddr_ntop((struct sockaddr*)&prev_addr, ip_addr), osmo_sockaddr_port((struct sockaddr*)&prev_addr)); + call_leg_rx_sdp(&leg->base, sip_get_sdp(sip)); + if (mode == sdp_sendonly) { /* SIP side places call on HOLD */ sdp = sdp_create_file(leg, other, sdp_recvonly); @@ -355,6 +365,8 @@ void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, struct sip_call_leg *leg; leg = (struct sip_call_leg *) hmagic; + call_leg_rx_sdp(&leg->base, sip_get_sdp(sip)); + /* MT call is moving forward */ /* The dialogue is now confirmed */ @@ -396,8 +408,10 @@ void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, * respond to the re-INVITE query. */ if (sip->sip_payload && sip->sip_payload->pl_data) { struct sip_call_leg *leg = sip_find_leg(nh); - if (leg) + if (leg) { + call_leg_rx_sdp(&leg->base, sip_get_sdp(sip)); sip_handle_reinvite(leg, nh, sip); + } } } else if (event == nua_r_bye || event == nua_r_cancel) { /* our bye or hang up is answered */ @@ -423,10 +437,12 @@ void nua_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, if (status == 100) { struct sip_call_leg *leg = sip_find_leg(nh); - if (leg) + if (leg) { + call_leg_rx_sdp(&leg->base, sip_get_sdp(sip)); sip_handle_reinvite(leg, nh, sip); - else + } else { new_call((struct sip_agent *) magic, nh, sip); + } } } else if (event == nua_i_cancel) { struct sip_call_leg *leg; @@ -517,11 +533,21 @@ static void sip_release_call(struct call_leg *_leg) static void sip_ring_call(struct call_leg *_leg) { struct sip_call_leg *leg; + struct call_leg *other; + const char *sdp = ""; OSMO_ASSERT(_leg->type == CALL_TYPE_SIP); leg = (struct sip_call_leg *) _leg; - nua_respond(leg->nua_handle, SIP_180_RINGING, TAG_END()); + other = call_leg_other(&leg->base); + if (other && other->rx_sdp_changed) { + sdp = other->rx_sdp; + other->rx_sdp_changed = false; + } + + nua_respond(leg->nua_handle, SIP_180_RINGING, + SIPTAG_PAYLOAD_STR(sdp), + TAG_END()); } static void sip_connect_call(struct call_leg *_leg)