#include "control_ng_flags_parser.h" #include #include #include #include #include "log.h" #include "log_funcs.h" /** * Data structures. */ static const char *transports[] = { [0x00] = "RTP/AVP", [0x01] = "RTP/SAVP", [0x02] = "RTP/AVPF", [0x03] = "RTP/SAVPF", [0x04] = "UDP/TLS/RTP/SAVP", [0x06] = "UDP/TLS/RTP/SAVPF", }; /** * Helpers. */ /* parsing of key and val from string */ static bool get_key_val(str * key, str * val, str *in_out) { if (!str_token_sep(key, in_out, ' ')) return false; // key=value ? str k; *val = STR_NULL; if (!str_token_sep(&k, key, '=')) return true; *val = *key; *key = k; return true; } /* handle either "foo-bar" or "foo=bar" from flags */ static bool str_key_val_prefix(const str * p, const char * q, const str * v, str * out) { if(str_eq(p, q)) { if(!v->s || !v->len) return 0; *out = *v; return true; } *out = *p; if (str_shift_cmp(out, q)) return false; if(out->len < 2) return false; if(*out->s != '-') return false; out->s++; out->len--; return true; } 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(rtpp_pos *a) { str list = a->cur; if (!skip_char(&list, '[')) return 0; // check contents if (list.len == 0) list = a->remainder; // could be just leading spaces? 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) == 1; } static bool rtpp_is_dict(rtpp_pos *a) { return rtpp_is_dict_list(a) == 2; } static str *rtpp_get_str(rtpp_pos *a, str *b) { if (rtpp_is_dict_list(a) != 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 (rtpp_is_dict_list(pos)) rtpp_list_iter(parser, pos, NULL, NULL, NULL); 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 = 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, const char * dictstr, sdp_ng_flags *flags) { str s; if(!str_key_val_prefix(key, cmp1, val, &s)) { if(!cmp2) return false; if(!str_key_val_prefix(key, cmp2, val, &s)) return false; } call_ng_codec_flags(&dummy_parser, STR_PTR(dictstr), &(rtpp_pos) {.cur = s}, flags); return true; } /* parse codec related flags */ static bool parse_codecs(sdp_ng_flags *flags, str * key, str * val) { if (parse_codec_to_dict(key, val, "transcode", "codec-transcode", "transcode", flags) || parse_codec_to_dict(key, val, "codec-strip", NULL, "strip", flags) || parse_codec_to_dict(key, val, "codec-offer", NULL, "offer", flags) || parse_codec_to_dict(key, val, "codec-mask", NULL, "mask", flags) || parse_codec_to_dict(key, val, "codec-set", NULL, "set", flags) || parse_codec_to_dict(key, val, "codec-accept", NULL, "accept", flags) || parse_codec_to_dict(key, val, "codec-except", NULL, "except", flags)) { return true; } return false; } /* prase transport, such as for example RTP/AVP */ static void parse_transports(unsigned int transport, sdp_ng_flags *out) { const char * val = transports[transport & 0x007]; if (!val) return; call_ng_main_flags(&dummy_parser, &STR_CONST("transport-protocol"), &(rtpp_pos) {.cur = STR(val), .remainder = STR_NULL}, out); } static void rtpp_direction_flag(sdp_ng_flags *flags, unsigned int *flagnum, str *val) { static const str keys[2] = {STR_CONST("from-interface"), STR_CONST("to-interface")}; if (*flagnum >= G_N_ELEMENTS(keys)) { ilog(LOG_WARN, "Too many 'direction=...' flags encountered"); return; } str key = keys[(*flagnum)++]; call_ng_main_flags(&dummy_parser, &key, &(rtpp_pos) {.cur = *val}, flags); } /** * Parse flags from bencode string into given bencode dictionary. * * Params: * @param rtpp_flags - raw str rtpp_flags * @param dict - root dict to store encoded flags */ void parse_rtpp_flags(const str * rtpp_flags, sdp_ng_flags *out) { str remainder, key, val; unsigned int direction_flag = 0; unsigned int transport = 0; if (!rtpp_flags->s) return; remainder = *rtpp_flags; while (remainder.len) { /* skip spaces */ skip_chars(&remainder, ' '); /* set key and val */ if (!get_key_val(&key, &val, &remainder)) break; /* codecs have own specific parsing as well */ if (parse_codecs(out, &key, &val)) goto next; /* parse other generic flags */ switch (key.len) { case 3: /* transport */ if (!val.s && str_eq(&key, "RTP")) transport = (transport | 0x100) & ~0x001; else if (!val.s && str_eq(&key, "AVP")) transport = (transport | 0x100) & ~0x002; /* other non-defined flags */ else goto generic; goto next; break; case 4: /* transport */ if (!val.s && str_eq(&key, "SRTP")) transport |= 0x101; else if (!val.s && str_eq(&key, "AVPF")) transport |= 0x102; else if (!val.s && str_eq(&key, "DTLS")) transport |= 0x104; /* other non-defined flags */ else goto generic; goto next; break; case 7: /* transport */ if (!val.s && str_eq(&key, "RTP/AVP")) transport = 0x100; /* other non-defined flags */ else goto generic; goto next; break; case 8: /* transport */ if (!val.s && str_eq(&key, "RTP/AVPF")) transport = 0x102; else if (!val.s && str_eq(&key, "RTP/SAVP")) transport = 0x101; /* from-tag can be overriden, but originally has to be provided */ else if (val.s && str_eq(&key, "from-tag")) { out->directional = 1; /* explicitly add directional for this case */ goto generic; } /* direction */ else if (str_eq(&key, "internal") || str_eq(&key, "external")) rtpp_direction_flag(out, &direction_flag, &key); /* other non-defined flags */ else goto generic; goto next; break; case 9: /* transport */ if (!val.s && str_eq(&key, "RTP/SAVPF")) transport = 0x103; /* direction */ else if (str_eq(&key, "direction") && rtpp_is_dict_list(&(rtpp_pos) {.cur=val}) == 0) rtpp_direction_flag(out, &direction_flag, &val); else goto generic; goto next; break; case 16: /* transport */ if (!val.s && str_eq(&key, "UDP/TLS/RTP/SAVP")) transport = 0x104; else goto generic; goto next; break; case 17: /* transport */ if (!val.s && str_eq(&key, "UDP/TLS/RTP/SAVPF")) transport = 0x106; else goto generic; goto next; break; } generic: /* generic one key flags */ if (!val.len) call_ng_flags_flags(&key, 0, out); /* generic flags with value, but no particular processing */ else { rtpp_pos pos = { .cur = val, .remainder = remainder }; call_ng_main_flags(&dummy_parser, &key, &pos, out); if (rtpp_is_dict_list(&pos)) rtpp_list_iter(&dummy_parser, &pos, NULL, NULL, NULL); remainder = pos.remainder; } next:; } /* define transport */ if (transport) parse_transports(transport, out); }