diff --git a/daemon/control_ng_flags_parser.c b/daemon/control_ng_flags_parser.c index 82ffef6ce..a338b6f76 100644 --- a/daemon/control_ng_flags_parser.c +++ b/daemon/control_ng_flags_parser.c @@ -64,17 +64,188 @@ static bool str_key_val_prefix(const str * p, const char * q, return true; } -static bool dummy_is_list(parser_arg a) { - return false; +static inline bool skip_char(str *s, char c) { + if (s->len == 0 || s->s[0] != c) + return false; + str_shift(s, 1); + return true; +} +static inline void skip_chars(str *s, char c) { + while (skip_char(s, c)); +} + +static int rtpp_is_dict_list(str *a) { + str list = *a; + if (!skip_char(&list, '[')) + return 0; + // check contents + if (list.len == 0) + return 0; // unexpected end of string + if (list.s[0] == '[') + return 1; // contains sub-list, must be a list + // inspect first element for 'key=' + str key, val; + if (!get_key_val(&key, &val, &list)) + return 0; // nothing to read + if (val.len) + return 2; // is a dict + return 1; // is a list +} + +static bool rtpp_is_list(rtpp_pos *a) { + return rtpp_is_dict_list(&a->cur) == 1; } -static str *dummy_get_str(parser_arg a, str *b) { - *b = *a.str; +static bool rtpp_is_dict(rtpp_pos *a) { + return rtpp_is_dict_list(&a->cur) == 2; +} +static str *rtpp_get_str(rtpp_pos *a, str *b) { + if (rtpp_is_dict_list(&a->cur) != 0) + return NULL; + if (a->cur.len == 0) + return NULL; + *b = a->cur; return b; } +static long long rtpp_get_int_str(rtpp_pos *a, long long def) { + str s; + if (!rtpp_get_str(a, &s)) + return def; + return str_to_i(&s, def); +} +static bool rtpp_dict_list_end_rewind(rtpp_pos *pos) { + // check for dict/list end, which is only valid if it doesn't also start one + if (pos->cur.len == 0 || pos->cur.s[0] == '[' || pos->cur.s[pos->cur.len - 1] != ']') + return false; + + pos->cur.len--; + // remove any extra closing bracket, and return them to the remainder for + // the upper level function to parse + while (pos->cur.len && pos->cur.s[pos->cur.len - 1] == ']') { + pos->cur.len--; + pos->remainder.s--; + pos->remainder.len++; + // we might be on a space or something - go to the actual bracket, which must + // be there somewhere + while (pos->remainder.s[0] != ']') { + pos->remainder.s--; + pos->remainder.len++; + } + } + + return true; +} +static bool rtpp_dict_list_closing(rtpp_pos *pos) { + if (pos->cur.s[0] != ']') + return false; + + str_shift(&pos->cur, 1); + // anything left in the string, return it to the remainder + pos->remainder.len += pos->remainder.s - pos->cur.s; + pos->remainder.s = pos->cur.s; + + return true; +} +static void rtpp_list_iter(const ng_parser_t *parser, rtpp_pos *pos, + void (*str_callback)(str *key, unsigned int, helper_arg), + void (*item_callback)(const ng_parser_t *, parser_arg, helper_arg), helper_arg arg) +{ + // list opener + if (!skip_char(&pos->cur, '[')) + return; + + unsigned int idx = 0; + + while (true) { + skip_chars(&pos->cur, ' '); + if (!pos->cur.len) + goto next; // empty token? + + // list closing? + if (rtpp_dict_list_closing(pos)) + break; + + // does it start another list or dict? + if (pos->cur.s[0] == '[') { + if (item_callback) + item_callback(parser, pos, arg); + goto next; + } + + // guess it's a string token + // does it end the list? + bool end = rtpp_dict_list_end_rewind(pos); + + if (pos->cur.len == 0) + break; // nothing left + + if (str_callback) + str_callback(&pos->cur, idx++, arg); + if (end) + break; + goto next; + +next: + // find next token in remainder, put in `cur` + if (!str_token_sep(&pos->cur, &pos->remainder, ' ')) + break; + } +} +static bool rtpp_dict_iter(const ng_parser_t *parser, rtpp_pos *pos, + void (*callback)(const ng_parser_t *, str *, parser_arg, helper_arg), + helper_arg arg) +{ + // list opener + if (!skip_char(&pos->cur, '[')) + return false; + + while (true) { + skip_chars(&pos->cur, ' '); + if (!pos->cur.len) + goto next; // empty token? + + // dict closing? + if (rtpp_dict_list_closing(pos)) + break; + + str key; + if (!str_token(&key, &pos->cur, '=')) { + ilog(LOG_ERR, "Entry in dictionary without equals sign ('" STR_FORMAT "'), aborting", + STR_FMT(&pos->cur)); + break; + } + + // guess it's a string token + // does it end the dict? + bool end = rtpp_dict_list_end_rewind(pos); + + if (pos->cur.len == 0) + break; // nothing left + + callback(parser, &key, pos, arg); + if (end) + break; + goto next; + +next: + // find next token in remainder, put in `cur` + if (!str_token_sep(&pos->cur, &pos->remainder, ' ')) + break; + } + + return true; +} +static bool rtpp_is_int(rtpp_pos *pos) { + return false; +} const ng_parser_t dummy_parser = { - .is_list = dummy_is_list, - .get_str = dummy_get_str, + .is_list = rtpp_is_list, + .is_dict = rtpp_is_dict, + .is_int = rtpp_is_int, + .list_iter = rtpp_list_iter, + .dict_iter = rtpp_dict_iter, + .get_str = rtpp_get_str, + .get_int_str = rtpp_get_int_str, }; static bool parse_codec_to_dict(str * key, str * val, const char *cmp1, const char *cmp2, @@ -158,8 +329,7 @@ void parse_rtpp_flags(const str * rtpp_flags, sdp_ng_flags *out) while (remainder.len) { /* skip spaces */ - while (remainder.len && remainder.s[0] == ' ') - str_shift(&remainder, 1); + skip_chars(&remainder, ' '); /* set key and val */ if (!get_key_val(&key, &val, &remainder)) @@ -229,7 +399,7 @@ void parse_rtpp_flags(const str * rtpp_flags, sdp_ng_flags *out) if (!val.s && str_eq(&key, "RTP/SAVPF")) transport = 0x103; /* direction */ - else if (str_eq(&key, "direction")) + else if (str_eq(&key, "direction") && rtpp_is_dict_list(&val) == 0) rtpp_direction_flag(out, &direction_flag, &val); else goto generic; @@ -258,8 +428,11 @@ generic: if (!val.len) call_ng_flags_flags(&key, 0, out); /* generic flags with value, but no particular processing */ - else - call_ng_main_flags(&dummy_parser, &key, &val, out); + else { + rtpp_pos pos = { .cur = val, .remainder = remainder }; + call_ng_main_flags(&dummy_parser, &key, &pos, out); + remainder = pos.remainder; + } next:; } diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 8a3e439cd..5a61af2e4 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -121,6 +121,10 @@ When the flags are passed to rtpengine, they are formated as following: { "rtpp_flags": "replace-origin via-branch=auto-next strict-source label=callee OSRTP-accept transport-protocol=RTP/AVP address-family=IP4" } +Lists and dictionaries are supported in this format using square brackets `[ ]`, for example: + + { "rtpp_flags": "via-branch=auto-next OSRTP=[accept] codec=[transcode=[PCMA PCMU] accept=[AMR-WB AMR] strip=[EVS]]" } + Regardless whether the flags parsing is done by the module or daemon, a functional behavior remains the same and has no difference in terms of SDP processing. diff --git a/include/types.h b/include/types.h index c89c96b74..e98f36c34 100644 --- a/include/types.h +++ b/include/types.h @@ -43,10 +43,15 @@ typedef struct ng_command_ctx ng_command_ctx_t; typedef struct bencode_item bencode_item_t; +typedef struct { + str cur; + str remainder; +} rtpp_pos; + typedef union { bencode_item_t *benc; JsonNode *json; - str *str; + rtpp_pos *rtpp; void *gen; } parser_arg __attribute__ ((__transparent_union__)); diff --git a/t/auto-daemon-tests-rtpp-flags.pl b/t/auto-daemon-tests-rtpp-flags.pl index 965735bb7..7c3c1ac3f 100644 --- a/t/auto-daemon-tests-rtpp-flags.pl +++ b/t/auto-daemon-tests-rtpp-flags.pl @@ -199,6 +199,92 @@ SDP new_call; +offer('rtpp-flags: codec-accept obj first', + { 'rtpp-flags' => 'codec=[accept=[PCMU]] replace-origin strict-source label=caller OSRTP-accept address-family=IP4 transport-protocol=RTP/AVP' }, < 'replace-origin strict-source label=callee OSRTP-accept address-family=IP4 transport-protocol=RTP/AVP' }, < 'replace-origin strict-source label=caller OSRTP-accept address-family=IP4 transport-protocol=RTP/AVP codec=[accept=[PCMU]] ' }, < 'replace-origin strict-source label=callee OSRTP-accept address-family=IP4 transport-protocol=RTP/AVP' }, < 'rtcp-mux-demux replace-origin strict-source label=caller OSRTP-accept address-family=IP4 transport-protocol=RTP/AVP' }, < 'rtcp-mux=[demux] replace=[origin] strict-source label=caller OSRTP-accept address-family=IP4 transport-protocol=RTP/AVP' }, < 'SDES-only-AES_CM_128_HMAC_SHA1_80 ICE=remove DTLS=off replace-origin strict-source label=caller address-family=IP4 transport-protocol=RTP/SAVP' }, < 'direction=[foo bar]' }, < 'internal external' }, < 'SDP-attr=[audio=[remove=[test]]]' }, < 'replace=[origin] SDP-attr=[audio=[remove=[test] add=[foo bar]] global=[remove=[quux]]] rtcp-mux=[offer]' }, < 'digit=3 frequency=344 frequencies=[123]' }, < 'replace=[ origin ] SDP-attr= [audio=[ remove = [ test ] add = [foo bar]] global=[remove=[quux]]] rtcp-mux=[offer]' }, <