diff --git a/daemon/call.c b/daemon/call.c index d9f60782c..2f5636827 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2327,8 +2327,6 @@ static void media_update_media_id(struct call_media *media, struct stream_params } } else { - // we already have a media ID, but re-invite offer did not specify - // one. we keep what we already have. ; } } @@ -2799,6 +2797,11 @@ static void __call_monologue_init_from_flags(struct call_monologue *ml, struct c for (__auto_type ll = flags->groups_other.head; ll; ll = ll->next) t_queue_push_tail(&ml->groups_other, call_str_dup(ll->data)); + if (t_hash_table_is_set(flags->bundles)) + ML_SET(ml, BUNDLE); + else + ML_CLEAR(ml, BUNDLE); + ml->sdp_session_uri = call_str_cpy(&flags->session_uri); ml->sdp_session_email = call_str_cpy(&flags->session_email); ml->sdp_session_phone = call_str_cpy(&flags->session_phone); @@ -3272,6 +3275,39 @@ static struct call_media * monologue_add_zero_media(struct call_monologue *sende return sender_media; } +__attribute__((nonnull(1, 2))) +static void monologue_bundle_accept(struct call_monologue *ml, sdp_ng_flags *flags) { + if (!ML_ISSET(ml, BUNDLE)) + return; + if (!t_hash_table_is_set(flags->bundles)) + return; + + // iterate all medias and set up bundle groups as requested + for (unsigned int i = 0; i < ml->medias->len; i++) { + __auto_type media = ml->medias->pdata[i]; + if (!media) + continue; + + // set bundle group + media->bundle = NULL; + + if (!media->media_id.len) + continue; + + str *bundle_head = t_hash_table_lookup(flags->bundles, &media->media_id); + if (!bundle_head) + continue; + + __auto_type bundle = media->bundle = t_hash_table_lookup(ml->media_ids, bundle_head); + if (!bundle) { + ilog(LOG_WARN, "Tagged media '" STR_FORMAT "' from BUNDLE group " + "doesn't exist", + STR_FMT(bundle_head)); + continue; + } + } +} + /* called with call->master_lock held in W */ int monologue_offer_answer(struct call_monologue *monologues[2], sdp_streams_q *streams, sdp_ng_flags *flags) @@ -3513,6 +3549,8 @@ int monologue_offer_answer(struct call_monologue *monologues[2], sdp_streams_q * media_update_transcoding_flag(sender_media); } + monologue_bundle_accept(sender_ml, flags); + // set ipv4/ipv6/mixed media stats if (flags->opmode == OP_OFFER || flags->opmode == OP_ANSWER) { statistics_update_ip46_inc_dec(receiver_ml->call, CMC_INCREMENT); diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 3fb4df250..03694a2ae 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -2424,6 +2424,7 @@ RTPE_NG_FLAGS_SDP_ATTR_Q_PARAMS RTPE_NG_FLAGS_STR_CASE_HT_PARAMS #undef X + str_ht_destroy_ptr(&flags->bundles); ng_sdp_attr_manipulations_free(flags->sdp_manipulations); t_queue_clear_full(&flags->medias, ng_media_free); diff --git a/daemon/sdp.c b/daemon/sdp.c index 1e8dd1b7f..02b3eb6cf 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -185,10 +185,14 @@ struct attribute_ssrc { }; struct attribute_group { + str semantics_str; + enum { GROUP_OTHER = 0, GROUP_BUNDLE, } semantics; + + str_q bundle; }; struct attribute_fingerprint { @@ -667,10 +671,20 @@ static void attr_insert(struct sdp_attributes *attrs, struct sdp_attribute *attr static bool parse_attribute_group(struct sdp_attribute *output) { output->attr = ATTR_GROUP; - output->group.semantics = GROUP_OTHER; - if (output->strs.value.len >= 7 && !strncmp(output->strs.value.s, "BUNDLE ", 7)) - output->group.semantics = GROUP_BUNDLE; + + PARSE_INIT; + + if (!str_token_sep(&output->group.semantics_str, value_str, ' ')) + return true; + + if (str_cmp(&output->group.semantics_str, "BUNDLE")) + return true; + + output->group.semantics = GROUP_BUNDLE; + str mid; + while (str_token_sep(&mid, value_str, ' ')) + t_queue_push_tail(&output->group.bundle, str_slice_dup(&mid)); return true; } @@ -1522,6 +1536,8 @@ error: } static void attr_free(struct sdp_attribute *p) { + if (p->attr == ATTR_GROUP) + t_queue_clear_full(&p->group.bundle, str_slice_free); g_free(p); } static void free_attributes(struct sdp_attributes *a) { @@ -1911,6 +1927,39 @@ static void sdp_attr_append_other(sdp_attr_q *dst, struct sdp_attributes *src) { sdp_attr_append(dst, attr_list_get_by_id(src, ATTR_OTHER)); } +static void sdp_bundle_group(sdp_ng_flags *flags, struct attribute_group *group) { + if (!group->bundle.head) { + ilog(LOG_WARN, "Empty BUNDLE group in SDP, ignoring"); + return; + } + + if (!t_hash_table_is_set(flags->bundles)) + flags->bundles = str_ht_new(); + + str *first = group->bundle.head->data; + if (t_hash_table_lookup(flags->bundles, first)) { + ilog(LOG_WARN, "Tagged media section '" STR_FORMAT "' found in multiple BUNDLE groups, " + "ignoring BUNDLE group", + STR_FMT(first)); + return; + } + + // first entry points to itself + t_hash_table_insert(flags->bundles, first, first); + + for (__auto_type kk = group->bundle.head->next; kk; kk = kk->next) { + str *mid = kk->data; + if (t_hash_table_lookup(flags->bundles, mid)) { + ilog(LOG_WARN, "Media section '" STR_FORMAT "' found in multiple BUNDLE groups", + STR_FMT(mid)); + continue; + } + + // the remaining entries point to the first one + t_hash_table_insert(flags->bundles, mid, first); + } +} + /* XXX split this function up */ bool sdp_streams(const sdp_sessions_q *sessions, sdp_streams_q *streams, sdp_ng_flags *flags) { struct sdp_session *session; @@ -1944,7 +1993,10 @@ bool sdp_streams(const sdp_sessions_q *sessions, sdp_streams_q *streams, sdp_ng_ __auto_type attrs = attr_list_get_by_id(&session->attributes, ATTR_GROUP); for (__auto_type ll = attrs ? attrs->head : NULL; ll; ll = ll->next) { attr = ll->data; - t_queue_push_tail(&flags->groups_other, &attr->strs.value); + if (attr->group.semantics == GROUP_BUNDLE) + sdp_bundle_group(flags, &attr->group); + else + t_queue_push_tail(&flags->groups_other, &attr->strs.value); } if (rtpe_config.moh_prevent_double_hold) { @@ -3016,6 +3068,70 @@ static void sdp_out_add_timing(GString *out, struct call_monologue *monologue) g_string_append(out, "\r\n"); } + +TYPED_GHASHTABLE(bundle_ht, struct call_media, GString, g_direct_hash, g_direct_equal, NULL, NULL); + +__attribute__((nonnull(1, 2))) +static void append_bundle_groups(GString *out, struct call_monologue *ml, sdp_ng_flags *flags) { + if (!ML_ISSET(ml, BUNDLE)) + return; + + g_auto(bundle_ht) ht = bundle_ht_new(); + + for (unsigned int i = 0; i < ml->medias->len; i++) { + __auto_type media = ml->medias->pdata[i]; + if (!media) + continue; + if (!media->bundle) + continue; + if (!media->media_id.len) + continue; + if (!media->bundle->media_id.len) + continue; + + __auto_type bundle_str = t_hash_table_lookup(ht, media->bundle); + if (!bundle_str) { + bundle_str = g_string_new(""); + t_hash_table_insert(ht, media->bundle, bundle_str); + } + + if (media == media->bundle) { + size_t orig_len = bundle_str->len; + + // this *should* be the first one, but we blindly do a prepend anyway. + // in the common case this is just writing to an empty string. + g_string_prepend_len(bundle_str, media->media_id.s, media->media_id.len); + + if (orig_len) // uncommon case: something else was there before, insert a space + g_string_insert_c(bundle_str, media->media_id.len, ' '); + } + else { + if (bundle_str->len != 0) + g_string_append_c(bundle_str, ' '); + + g_string_append_len(bundle_str, media->media_id.s, media->media_id.len); + } + } + + bundle_ht_iter iter; + t_hash_table_iter_init(&iter, ht); + + GString *bundle_str; + while (t_hash_table_iter_next(&iter, NULL, &bundle_str)) { + __auto_type state = __attr_begin(out, flags, MT_UNKNOWN); + if (__attr_append(&state, "group")) + goto next; + if (__attr_append(&state, ":BUNDLE")) + goto next; + g_string_append_c(out, ' '); + if (__attr_append_str(&state, &STR_GS(bundle_str))) + goto next; + __attr_end(&state); +next: + g_string_free(bundle_str, TRUE); + } +} + __attribute__((nonnull(1, 2, 4, 5))) static void sdp_out_add_other(GString *out, struct call_monologue *monologue, struct call_monologue *source_ml, @@ -3039,6 +3155,8 @@ static void sdp_out_add_other(GString *out, struct call_monologue *monologue, /* group */ if (source_ml && flags->ice_option == ICE_FORCE_RELAY) { + append_bundle_groups(out, source_ml, flags); + for (__auto_type ll = source_ml->groups_other.head; ll; ll = ll->next) append_attr_to_gstring(out, "group", ll->data, flags, media->type_id); } diff --git a/include/call.h b/include/call.h index 1ff01f53f..5ee5726c4 100644 --- a/include/call.h +++ b/include/call.h @@ -233,6 +233,7 @@ enum { #define ML_FLAG_MOH_SENDRECV (1LL << 25) #define ML_FLAG_MOH_ZEROCONN (1LL << 26) #define ML_FLAG_FORCE_TRANSCODING (1LL << 27) +#define ML_FLAG_BUNDLE (1LL << 28) /* call_t */ #define CALL_FLAG_IPV4_OFFER (1LL << 16) @@ -507,6 +508,7 @@ struct call_media { str media_id; str label; + struct call_media *bundle; sdes_q sdes_in, sdes_out; struct dtls_fingerprint fingerprint; /* as received */ const struct dtls_hash_func *fp_hash_func; /* outgoing */ diff --git a/include/call_interfaces.h b/include/call_interfaces.h index aa56d2f4a..cb5878e3f 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -95,6 +95,7 @@ struct sdp_ng_flags { str session_timing; /* t= line */ struct session_bandwidth session_bandwidth; str_q groups_other; /* a=group:xxx */ + str_ht bundles; // a=group:BUNDLE ... str session_information; // i= line str session_uri; // u= line str session_email; // e= line