diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 232254ee5..bcf98fd75 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -644,12 +644,57 @@ INLINE void ng_sdp_attr_manipulations(struct sdp_manipulations_common ** sm_ptr, if (!bencode_get_str(it_c, &command_type)) continue; + GQueue * q_ptr = NULL; + switch (__csh_lookup(&command_type)) { - /* CMD_ADD commands */ - case CSH_LOOKUP("add"):; - GQueue * q_ptr = NULL; + /* CMD_ADD / CMD_SUBST commands */ + case CSH_LOOKUP("substitute"):; + struct sdp_substitute_attr * subst_command = NULL; + + switch (media) { + case MT_UNKNOWN: + q_ptr = &(*sm_ptr)->subst_commands_glob; + break; + case MT_AUDIO: + q_ptr = &(*sm_ptr)->subst_commands_audio; + break; + case MT_VIDEO: + q_ptr = &(*sm_ptr)->subst_commands_video; + break; + default: + ilog(LOG_WARN, "SDP manipulations: unspported SDP section targeted."); + continue; + } + + for (bencode_item_t *it_v = command_value->child; it_v; it_v = it_v->sibling) + { + str from; + str to; + + if (!bencode_get_str(it_v->child, &from)) + continue; + if (!bencode_get_str(it_v->child->sibling, &to)) + continue; + + str * s_copy_from = str_dup_escape(&from); + str * s_copy_to = str_dup_escape(&to); + + if (!s_copy_from->len || !s_copy_to->len) { + free(s_copy_from); + free(s_copy_to); + continue; + } + + subst_command = g_slice_alloc0(sizeof(*subst_command)); + subst_command->value_a = s_copy_from; + subst_command->value_b = s_copy_to; + g_queue_push_tail(q_ptr, subst_command); + } + break; + + case CSH_LOOKUP("add"):; switch (media) { case MT_UNKNOWN: q_ptr = &(*sm_ptr)->add_commands_glob; @@ -1775,7 +1820,20 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu call_ng_dict_iter(out, input, call_ng_main_flags); } +static void free_subst_attr(void *p) { + struct sdp_substitute_attr *attr = p; + if (attr->value_a) + free(attr->value_a); + if (attr->value_b) + free(attr->value_b); + g_slice_free1(sizeof(*attr), attr); +} + static void ng_sdp_attr_manipulations_free(struct sdp_manipulations_common * sdp_manipulations) { + GQueue *cpq_subst_glob = &sdp_manipulations->subst_commands_glob; + GQueue *cpq_subst_audio = &sdp_manipulations->subst_commands_audio; + GQueue *cpq_subst_video = &sdp_manipulations->subst_commands_video; + if (sdp_manipulations->rem_commands_glob) g_hash_table_destroy(sdp_manipulations->rem_commands_glob); if (sdp_manipulations->rem_commands_audio) @@ -1787,6 +1845,13 @@ static void ng_sdp_attr_manipulations_free(struct sdp_manipulations_common * sdp g_queue_clear_full(&sdp_manipulations->add_commands_audio, free); g_queue_clear_full(&sdp_manipulations->add_commands_video, free); + if (cpq_subst_glob && cpq_subst_glob->head) + g_queue_clear_full(cpq_subst_glob, free_subst_attr); + if (cpq_subst_audio && cpq_subst_audio->head) + g_queue_clear_full(cpq_subst_audio, free_subst_attr); + if (cpq_subst_video && cpq_subst_video->head) + g_queue_clear_full(cpq_subst_video, free_subst_attr); + g_slice_free1(sizeof(*sdp_manipulations), sdp_manipulations); } diff --git a/daemon/sdp.c b/daemon/sdp.c index 602d29ac9..ea6ccc291 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -291,12 +291,21 @@ static void attr_free(void *p); static void attr_insert(struct sdp_attributes *attrs, struct sdp_attribute *attr); INLINE void chopper_append_c(struct sdp_chopper *c, const char *s); +/** + * Helper, which compares a substitute command for SDP manipulations. + */ +static int sdp_manipulate_subst_cmp(gconstpointer a, gconstpointer b) { + const struct sdp_substitute_attr * subst = a; + const struct sdp_substitute_attr * subst_lookup = b; + return str_cmp_str(subst->value_a, subst_lookup->value_a); +} + /** * Checks whether a given type of SDP manipulation exists for a given session level. */ static int sdp_manipulate_check(enum command_type command_type, struct sdp_manipulations_common * sdp_manipulations, - enum media_type media_type, str *attr_name) { + enum media_type media_type, str * attr_name) { /* for now we only support session lvl, audio and media streams */ if (media_type == MT_OTHER) { @@ -309,9 +318,33 @@ static int sdp_manipulate_check(enum command_type command_type, if (!sdp_manipulations) return 0; + GQueue * q_ptr = NULL; + switch (command_type) { + case CMD_SUBST:; + q_ptr = NULL; + + if (!attr_name) + break; + + struct sdp_substitute_attr fictitious = {attr_name, NULL}; + switch (media_type) { + case MT_AUDIO: + q_ptr = &sdp_manipulations->subst_commands_audio; + break; + case MT_VIDEO: + q_ptr = &sdp_manipulations->subst_commands_video; + break; + default: /* MT_UNKNOWN */ + q_ptr = &sdp_manipulations->subst_commands_glob; + } + if (q_ptr && q_ptr->head && + g_queue_find_custom(q_ptr, &fictitious, sdp_manipulate_subst_cmp)) + return 1; + break; + case CMD_ADD:; - GQueue * q_ptr = NULL; + q_ptr = NULL; switch (media_type) { case MT_AUDIO: q_ptr = &sdp_manipulations->add_commands_audio; @@ -379,6 +412,37 @@ static void sdp_manipulations_add(struct sdp_chopper *chop, } } +/** + * Substitute values for a requested session level (global, audio, video) + */ +static void sdp_manipulations_subst(struct sdp_chopper *chop, + struct sdp_manipulations_common * sdp_manipulations, + enum media_type media_type, str * attr_name) { + + GQueue * q_ptr = NULL; + switch (media_type) { + case MT_AUDIO: + q_ptr = &sdp_manipulations->subst_commands_audio; + break; + case MT_VIDEO: + q_ptr = &sdp_manipulations->subst_commands_video; + break; + default: /* MT_UNKNOWN */ + q_ptr = &sdp_manipulations->subst_commands_glob; + } + + for (GList *l = q_ptr->head; l; l = l->next) + { + struct sdp_substitute_attr * attr_value = l->data; + + if (!str_cmp_str(attr_name, attr_value->value_a)) { + chopper_append_c(chop, "a="); + chopper_append_c(chop, attr_value->value_b->s); + chopper_append_c(chop, "\r\n"); + } + } +} + static void append_attr_to_gstring(GString *s, char * name, const str * value, struct sdp_ng_flags *flags, enum media_type media_type); static void append_attr_char_to_gstring(GString *s, char * name, const char * value, @@ -2273,6 +2337,10 @@ static int process_session_attributes(struct sdp_chopper *chop, struct sdp_attri if (sdp_manipulate_check(CMD_REM, sdp_manipulations, MT_UNKNOWN, &attr->line_value)) goto strip; + /* if attr is supposed to be substituted don't add to the chop->output, but add another value */ + if (sdp_manipulate_check(CMD_SUBST, sdp_manipulations, MT_UNKNOWN, &attr->line_value)) + goto strip_with_subst; + switch (attr->attr) { case ATTR_ICE: case ATTR_ICE_UFRAG: @@ -2332,6 +2400,14 @@ strip: return -1; if (skip_over(chop, &attr->full_line)) return -1; + continue; + +strip_with_subst: + if (copy_up_to(chop, &attr->full_line)) + return -1; + if (skip_over(chop, &attr->full_line)) + return -1; + sdp_manipulations_subst(chop, sdp_manipulations, MT_UNKNOWN, &attr->line_value); } return 0; @@ -2356,6 +2432,10 @@ static int process_media_attributes(struct sdp_chopper *chop, struct sdp_media * if (sdp_manipulate_check(CMD_REM, sdp_manipulations, sdp->media_type_id, &attr->line_value)) goto strip; + /* if attr is supposed to be substituted don't add to the chop->output, but add another value */ + if (sdp_manipulate_check(CMD_SUBST, sdp_manipulations, sdp->media_type_id, &attr->line_value)) + goto strip_with_subst; + switch (attr->attr) { case ATTR_ICE: case ATTR_ICE_UFRAG: @@ -2444,6 +2524,14 @@ strip: return -1; if (skip_over(chop, &attr->full_line)) return -1; + continue; + +strip_with_subst: + if (copy_up_to(chop, &attr->full_line)) + return -1; + if (skip_over(chop, &attr->full_line)) + return -1; + sdp_manipulations_subst(chop, sdp_manipulations, sdp->media_type_id, &attr->line_value); } return 0; diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 8f33b5217..2da9172fe 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -1256,14 +1256,19 @@ Description: Can take multiple values (so multiple attributes can removed per one command). + - `substitute` + + Substitutes a specified `a=` line taken from the concerned media attributes list. + If such line has been not found, the attributes list remains untouched. + + Substitutes one attribute at a time, so one attribute into another attribute. + Read more about that below in the `` section. + * `` The `value` doesn't take the `a=` lvalue part, only what must go after an equal sign. - For `remove` and `substitute`, the value of the command has a wildcard matching - using a prefix. So that, all values caught by the prefix are affected. - - No wild-cards and regex expressions accepted. Only a prefix or whole value are allowed. + No wild-cards and regex expressions accepted. Only a whole value is allowed. One should remember that some attributes are allowed to be present multiple times, as for example `a=ssrc:`. Therefore the RTPEngine does not expect specified `a=` lines @@ -1277,10 +1282,16 @@ Description: regardless of their content. On the other hand, one might want to remove all attributes corresponding to one SSRC only — so a removal of all `a=ssrc:123456`, for example. - Thus, in case of intention to remove multiple attribute lines related to a specific - scope of session parameters, one should specify a prefix as a value, - which would catch all of them. And vice-versa, in case of intention to remove quite - a specific attribute, one should consider tight uniqueness of the value (so full value). + Important remark regarding `substitute` command. + It takes only two values at a time, in other words substitutes one attribute per command: + - the first `value`, that matches the value to be substituted; and + - the second `value`, that is to be placed. + Therefore, the only allowed syntax for it is (per command): + + "substitute": ["from-this-attribute", "to-that-attribute"] + + All other possible usages will be ignored and only first two values will be taken. + However, multiple `substitute` commands can be given per time, see examples below. Examples: @@ -1318,6 +1329,20 @@ Examples: } } +* Substitute two attributes of the global session and one for audio media section (pay attention, `substitute` is using lists, not dictionaries): + + "sdp-attr" : + { + "none" : + { + "substitute": [[ "sendrecv" , "sendonly" ], [ "ptime:20" , "ptime:40" ]] + }, + "audio" : + { + "substitute": [["fmtp:101 0-15" , "fmtp:126 0-16" ]] + }, + } + An example of a complete `offer` request dictionary could be (SDP body abbreviated): { "command": "offer", "call-id": "cfBXzDSZqhYNcXM", "from-tag": "mS9rSAn0Cr", diff --git a/include/sdp.h b/include/sdp.h index 598361e71..84075866e 100644 --- a/include/sdp.h +++ b/include/sdp.h @@ -13,6 +13,12 @@ enum command_type { CMD_SUBST, }; +/* */ +struct sdp_substitute_attr { + str * value_a; /* from */ + str * value_b; /* to */ +}; + /* A structure for SDP arbitary manipulations on all levels of SDP: * session (global), media (audio/video). Works only on `a=` lines. */ @@ -24,6 +30,10 @@ struct sdp_manipulations_common { GHashTable * rem_commands_glob; GHashTable * rem_commands_audio; GHashTable * rem_commands_video; + + GQueue subst_commands_glob; + GQueue subst_commands_audio; + GQueue subst_commands_video; }; struct ice_candidate; diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 586e9cef7..063a58030 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -16052,6 +16052,140 @@ a=sendrecv a=rtcp:PORT SDP +new_call; + +offer('SDP attr manipulations - substitute a= line for media audio', { ICE => 'remove', DTLS => 'off', SDES => [ 'nonew' ], 'sdp-attr' => { audio => { substitute => [['test1', 'test2']] } }}, < 'remove' }, < 'remove', DTLS => 'off', SDES => [ 'nonew' ], 'sdp-attr' => { none => { substitute => [['test1', 'test2']] } }}, < 'remove' }, < 'remove', DTLS => 'off', SDES => [ 'nonew' ], 'sdp-attr' => { audio => [ substitute => [['test1', 'test2'] , ['test5', 'test6']] ] }}, < 'remove' }, <