Browse Source

MT#56128 SDP manipulations: add support for 'substitute' command.

It substitutes a specified `a=` line taken from the concerned
media attributes list. If such line has been not found,
the attributes list remains untouched.

It subsitutes one attribute at a time, so one attribute into
another attribute.

Change-Id: Ie0a48ba46a1b196fbe33b09dedc40e4498640e34
pull/1640/head
Donat Zenichev 3 years ago
parent
commit
4b79d20577
5 changed files with 335 additions and 13 deletions
  1. +68
    -3
      daemon/call_interfaces.c
  2. +90
    -2
      daemon/sdp.c
  3. +33
    -8
      docs/ng_control_protocol.md
  4. +10
    -0
      include/sdp.h
  5. +134
    -0
      t/auto-daemon-tests.pl

+ 68
- 3
daemon/call_interfaces.c View File

@ -644,12 +644,57 @@ INLINE void ng_sdp_attr_manipulations(struct sdp_manipulations_common ** sm_ptr,
if (!bencode_get_str(it_c, &command_type)) if (!bencode_get_str(it_c, &command_type))
continue; continue;
GQueue * q_ptr = NULL;
switch (__csh_lookup(&command_type)) { 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) { switch (media) {
case MT_UNKNOWN: case MT_UNKNOWN:
q_ptr = &(*sm_ptr)->add_commands_glob; 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); 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) { 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) if (sdp_manipulations->rem_commands_glob)
g_hash_table_destroy(sdp_manipulations->rem_commands_glob); g_hash_table_destroy(sdp_manipulations->rem_commands_glob);
if (sdp_manipulations->rem_commands_audio) 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_audio, free);
g_queue_clear_full(&sdp_manipulations->add_commands_video, 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); g_slice_free1(sizeof(*sdp_manipulations), sdp_manipulations);
} }


+ 90
- 2
daemon/sdp.c View File

@ -291,12 +291,21 @@ static void attr_free(void *p);
static void attr_insert(struct sdp_attributes *attrs, struct sdp_attribute *attr); static void attr_insert(struct sdp_attributes *attrs, struct sdp_attribute *attr);
INLINE void chopper_append_c(struct sdp_chopper *c, const char *s); 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. * Checks whether a given type of SDP manipulation exists for a given session level.
*/ */
static int sdp_manipulate_check(enum command_type command_type, static int sdp_manipulate_check(enum command_type command_type,
struct sdp_manipulations_common * sdp_manipulations, 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 */ /* for now we only support session lvl, audio and media streams */
if (media_type == MT_OTHER) if (media_type == MT_OTHER)
{ {
@ -309,9 +318,33 @@ static int sdp_manipulate_check(enum command_type command_type,
if (!sdp_manipulations) if (!sdp_manipulations)
return 0; return 0;
GQueue * q_ptr = NULL;
switch (command_type) { 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:; case CMD_ADD:;
GQueue * q_ptr = NULL;
q_ptr = NULL;
switch (media_type) { switch (media_type) {
case MT_AUDIO: case MT_AUDIO:
q_ptr = &sdp_manipulations->add_commands_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, static void append_attr_to_gstring(GString *s, char * name, const str * value,
struct sdp_ng_flags *flags, enum media_type media_type); struct sdp_ng_flags *flags, enum media_type media_type);
static void append_attr_char_to_gstring(GString *s, char * name, const char * value, 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)) if (sdp_manipulate_check(CMD_REM, sdp_manipulations, MT_UNKNOWN, &attr->line_value))
goto strip; 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) { switch (attr->attr) {
case ATTR_ICE: case ATTR_ICE:
case ATTR_ICE_UFRAG: case ATTR_ICE_UFRAG:
@ -2332,6 +2400,14 @@ strip:
return -1; return -1;
if (skip_over(chop, &attr->full_line)) if (skip_over(chop, &attr->full_line))
return -1; 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; 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)) if (sdp_manipulate_check(CMD_REM, sdp_manipulations, sdp->media_type_id, &attr->line_value))
goto strip; 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) { switch (attr->attr) {
case ATTR_ICE: case ATTR_ICE:
case ATTR_ICE_UFRAG: case ATTR_ICE_UFRAG:
@ -2444,6 +2524,14 @@ strip:
return -1; return -1;
if (skip_over(chop, &attr->full_line)) if (skip_over(chop, &attr->full_line))
return -1; 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; return 0;


+ 33
- 8
docs/ng_control_protocol.md View File

@ -1256,14 +1256,19 @@ Description:
Can take multiple values (so multiple attributes can removed per one command). 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 `<value>` section.
* `<value>` * `<value>`
The `value` doesn't take the `a=` lvalue part, only what must go after an equal sign. 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, 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 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 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. 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: 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): An example of a complete `offer` request dictionary could be (SDP body abbreviated):
{ "command": "offer", "call-id": "cfBXzDSZqhYNcXM", "from-tag": "mS9rSAn0Cr", { "command": "offer", "call-id": "cfBXzDSZqhYNcXM", "from-tag": "mS9rSAn0Cr",


+ 10
- 0
include/sdp.h View File

@ -13,6 +13,12 @@ enum command_type {
CMD_SUBST, 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: /* A structure for SDP arbitary manipulations on all levels of SDP:
* session (global), media (audio/video). Works only on `a=` lines. * 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_glob;
GHashTable * rem_commands_audio; GHashTable * rem_commands_audio;
GHashTable * rem_commands_video; GHashTable * rem_commands_video;
GQueue subst_commands_glob;
GQueue subst_commands_audio;
GQueue subst_commands_video;
}; };
struct ice_candidate; struct ice_candidate;


+ 134
- 0
t/auto-daemon-tests.pl View File

@ -16052,6 +16052,140 @@ a=sendrecv
a=rtcp:PORT a=rtcp:PORT
SDP 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']] } }}, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 2000 RTP/SAVP 0
c=IN IP4 198.51.100.1
a=sendrecv
a=test1
----------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio PORT RTP/SAVP 0
c=IN IP4 203.0.113.1
a=test2
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
answer('SDP attr manipulations - substitute a= line for media audio', { ICE => 'remove' }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 2002 RTP/SAVP 0
c=IN IP4 198.51.100.3
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/SAVP 0
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
new_call;
offer('SDP attr manipulations - substitute a= line for a session level', { ICE => 'remove', DTLS => 'off', SDES => [ 'nonew' ], 'sdp-attr' => { none => { substitute => [['test1', 'test2']] } }}, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
a=test1
m=audio 2000 RTP/SAVP 0
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
a=test2
m=audio PORT RTP/SAVP 0
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
answer('SDP attr manipulations - substitute a= line for a session level', { ICE => 'remove' }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 2002 RTP/SAVP 0
c=IN IP4 198.51.100.3
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/SAVP 0
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
new_call;
offer('SDP attr manipulations - substitute two a= lines for media audio', { ICE => 'remove', DTLS => 'off', SDES => [ 'nonew' ], 'sdp-attr' => { audio => [ substitute => [['test1', 'test2'] , ['test5', 'test6']] ] }}, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 2000 RTP/SAVP 0
c=IN IP4 198.51.100.1
a=sendrecv
a=test1
a=test5
----------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio PORT RTP/SAVP 0
c=IN IP4 203.0.113.1
a=test2
a=test6
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
answer('SDP attr manipulations - substitute two a= lines for media audio', { ICE => 'remove' }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 2002 RTP/SAVP 0
c=IN IP4 198.51.100.3
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/SAVP 0
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
# codec masking gh#664 # codec masking gh#664
new_call; new_call;


Loading…
Cancel
Save