diff --git a/daemon/media_socket.c b/daemon/media_socket.c index 26e379cb4..bf0023efe 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1728,7 +1728,6 @@ static const char *kernelize_target(kernelize_state *s, struct packet_stream *st * The linkage between userspace and kernel module is in the kernelize_one(). * * Called with stream->lock held. - * sink_handler can be NULL. */ __attribute__((nonnull(1, 2, 3))) static const char *kernelize_one(kernelize_state *s, @@ -1836,6 +1835,7 @@ static const char *kernelize_one(kernelize_state *s, } handler->out->kernel(&redi->output.encrypt, sink); + sink_handler->rtpext->kernel(&redi->output, media, sink->media); if (sink != stream) mutex_unlock(&sink->lock); @@ -2071,9 +2071,15 @@ static size_t rtpext_printer_copy_print(struct rtp_header *rh, void *dst, const return mp->extensions.len; } +static void rtpext_printer_copy_kernel(struct rtpengine_output_info *roi, + struct call_media *src, struct call_media *dst) +{ +} + const struct rtpext_printer rtpext_printer_copy = { .length = rtpext_printer_copy_length, .print = rtpext_printer_copy_print, + .kernel = rtpext_printer_copy_kernel, .may_copy = true, }; @@ -2154,9 +2160,41 @@ static size_t rtpext_printer_extmap_print(struct rtp_header *rh, void *dst, cons return padded; } + +static int uint8_sort(const void *a, const void *b) { + const uint8_t *A = a, *B = b; + if (*A < *B) + return -1; + if (*A > *B) + return 1; + return 0; +} + +static void rtpext_printer_extmap_kernel(struct rtpengine_output_info *roi, + struct call_media *src, struct call_media *dst) +{ + roi->extmap = 1; + + unsigned int u = 0; + + for (__auto_type l = dst->extmap.head; l; l = l->next) { + __auto_type ext = l->data; + if (u >= RTPE_NUM_EXTMAP_FILTER) { + ilog(LOG_WARN, "Too many RTP header extensions for kernel module"); + break; + } + roi->extmap_filter[u] = ext->id; + u++; + } + + qsort(roi->extmap_filter, u, sizeof(*roi->extmap_filter), uint8_sort); +} + + static const struct rtpext_printer rtpext_printer_extmap = { .length = rtpext_printer_extmap_length, .print = rtpext_printer_extmap_print, + .kernel = rtpext_printer_extmap_kernel, .may_copy = false, }; diff --git a/include/media_socket.h b/include/media_socket.h index ecc90882d..1bb577d44 100644 --- a/include/media_socket.h +++ b/include/media_socket.h @@ -257,6 +257,7 @@ struct sink_attrs { struct rtpext_printer { size_t (*length)(const struct media_packet *); size_t (*print)(struct rtp_header *, void *dst, const struct media_packet *); + void (*kernel)(struct rtpengine_output_info *, struct call_media *, struct call_media *); bool may_copy; }; diff --git a/kernel-module/extmap_filter.inc.c b/kernel-module/extmap_filter.inc.c new file mode 100644 index 000000000..5aed0c685 --- /dev/null +++ b/kernel-module/extmap_filter.inc.c @@ -0,0 +1,128 @@ +static int extmap_find(const void *key, const void *ele) { + const uint8_t *k = key, *e = ele; + if (*k < *e) + return -1; + if (*k > *e) + return 1; + return 0; +} + +static bool extmap_has_ext(uint8_t id, struct rtpengine_output *o) { + uint8_t *const *match = bsearch(&id, o->output.extmap_filter, o->output.num_extmap_filter, + sizeof(*o->output.extmap_filter), extmap_find); + return match != NULL; +} + +static bool apply_extmap_filter_ext(uint8_t id, size_t len, size_t size, + unsigned char **r, unsigned char **w, unsigned char *end, + unsigned int *count, + struct rtpengine_output *o) +{ + if (*r + size + len > end) + return false; + + if (extmap_has_ext(id, o)) { + // retain ext + if (*r != *w) + memmove(*w, *r, len + size); + *w += len + size; + (*count)++; + } + // else: skip over & filter out + + *r += len + size; + + return true; +} + +static void apply_extmap_filter_finish(unsigned char *r, unsigned char *w, unsigned int count, + struct sk_buff *skb, struct rtp_parsed *rtp) +{ + if (r == w) + return; // everything left as it was + + if (count == 0) { + // no extensions, remove header and trim packet + rtp->rtp_header->v_p_x_cc &= ~0x10; + size_t pull = rtp->payload - (unsigned char *) rtp->ext_hdr; + memmove(rtp->ext_hdr, rtp->payload, rtp->payload_len); + rtp->payload = (unsigned char *) rtp->ext_hdr; + rtp->ext_hdr = NULL; + rtp->extension_len = 0; + skb_trim(skb, skb->len - pull); + return; + } + + // shift up payload and trim packet + size_t size = w - rtp->extension; + size_t padded = (size + 3L) & ~3L; + memset(w, 0, padded - size); + rtp->ext_hdr->length = htons(padded / 4); + size_t pull = rtp->extension_len - padded; + rtp->extension_len = padded; + memmove(rtp->payload - pull, rtp->payload, rtp->payload_len); + rtp->payload -= pull; + skb_trim(skb, skb->len - pull); +} + +static void apply_extmap_filter_short(struct sk_buff *skb, struct rtpengine_output *o, struct rtp_parsed *rtp) { + unsigned char *r = rtp->extension; // reader + unsigned char *end = r + rtp->extension_len; + unsigned char *w = r; // writer + unsigned int count = 0; // non-padding extensions + + // XXX partly shared code + while (r < end) { + uint8_t id_len = r[0]; + if (id_len == '\0') { + // padding + r++; // don't copy padding to *w + continue; + } + + uint8_t id = id_len >> 4; + uint8_t len = (id_len & 0xf) + 1; + + if (!apply_extmap_filter_ext(id, len, 1, &r, &w, end, &count, o)) + break; + } + + apply_extmap_filter_finish(r, w, count, skb, rtp); +} + +static void apply_extmap_filter_long(struct sk_buff *skb, struct rtpengine_output *o, struct rtp_parsed *rtp) { + unsigned char *r = rtp->extension; // reader + unsigned char *end = r + rtp->extension_len; + unsigned char *w = r; // writer + unsigned int count = 0; // non-padding extensions + + // XXX partly shared code + while (r < end) { + uint8_t id = r[0]; + if (id == '\0') { + // padding + r++; // don't copy padding to *w + continue; + } + + uint8_t len = r[1]; + + if (!apply_extmap_filter_ext(id, len, 2, &r, &w, end, &count, o)) + break; + } + + apply_extmap_filter_finish(r, w, count, skb, rtp); +} + +static void apply_extmap_filter(struct sk_buff *skb, struct rtpengine_output *o, struct rtp_parsed *rtp) { + if (!rtp->ext_hdr) + return; + + if (ntohs(rtp->ext_hdr->undefined) == 0xbede) + apply_extmap_filter_short(skb, o, rtp); + else if ((ntohs(rtp->ext_hdr->undefined) & 0xfff0) == 0x0100) + apply_extmap_filter_long(skb, o, rtp); + // else: leave untouched +} + + diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index 158ff0f0c..1b891375b 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -510,9 +510,12 @@ struct rtp_parsed { struct rtp_header *rtp_header; struct rtcp_header *rtcp_header; }; - unsigned int header_len; + size_t header_len; unsigned char *payload; - unsigned int payload_len; + size_t payload_len; + struct rtp_exthdr *ext_hdr; + unsigned char *extension; + size_t extension_len; int ok; int rtcp; }; @@ -1804,6 +1807,13 @@ static int proc_list_show(struct seq_file *f, void *v) { g->target.pt_stats[j]->payload_type); } + if (o->output.extmap) { + seq_printf(f, " Allowed RTP extensions:"); + for (j = 0; j < o->output.num_extmap_filter; j++) + seq_printf(f, " %u", (unsigned int) o->output.extmap_filter[j]); + seq_printf(f, "\n"); + } + proc_list_crypto_print(f, &o->encrypt_rtp, &o->output.encrypt, "encryption"); } @@ -5131,7 +5141,6 @@ drop: /* XXX shared code */ static void parse_rtp(struct rtp_parsed *rtp, struct sk_buff *skb) { - struct rtp_exthdr *ext; size_t ext_len; if (skb->len < sizeof(*rtp->rtp_header)) @@ -5150,16 +5159,20 @@ static void parse_rtp(struct rtp_parsed *rtp, struct sk_buff *skb) { if ((rtp->rtp_header->v_p_x_cc & 0x10)) { /* extension */ - if (rtp->payload_len < sizeof(*ext)) + if (rtp->payload_len < sizeof(*rtp->ext_hdr)) goto error; - ext = (void *) rtp->payload; - ext_len = sizeof(*ext) + ntohs(ext->length) * 4; + rtp->ext_hdr = (struct rtp_exthdr *) rtp->payload; + rtp->extension_len = ntohs(rtp->ext_hdr->length) * 4; + ext_len = sizeof(*rtp->ext_hdr) + rtp->extension_len; if (rtp->payload_len < ext_len) goto error; + rtp->extension = rtp->payload + sizeof(*rtp->ext_hdr); rtp->payload += ext_len; rtp->payload_len -= ext_len; rtp->header_len += ext_len; } + else + rtp->ext_hdr = NULL; DBG("rtp header parsed, payload length is %u\n", rtp->payload_len); @@ -6065,6 +6078,8 @@ static uint32_t proxy_packet_srtp_encrypt(struct sk_buff *skb, struct re_crypto_ return pkt_idx; } +#include "extmap_filter.inc.c" + static bool proxy_packet_output_rtXp(struct sk_buff *skb, struct rtpengine_output *o, int rtp_pt_idx, struct rtp_parsed *rtp, int ssrc_idx) @@ -6077,6 +6092,9 @@ static bool proxy_packet_output_rtXp(struct sk_buff *skb, struct rtpengine_outpu return true; } + if (o->output.extmap) + apply_extmap_filter(skb, o, rtp); + if (rtp_pt_idx >= 0) { // blackhole? if (o->output.pt_output[rtp_pt_idx].blackhole) @@ -6465,6 +6483,10 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct sk_buff *oskb, if (rtp.rtp_header) rtp2.rtp_header = (void *) (((char *) rtp2.rtp_header) + offset); rtp2.payload = (void *) (((char *) rtp2.payload) + offset); + if (rtp2.extension) + rtp2.extension = (void *) (((char *) rtp2.extension) + offset); + if (rtp2.ext_hdr) + rtp2.ext_hdr = (void *) (((char *) rtp2.ext_hdr) + offset); datalen_out = skb2->len; diff --git a/kernel-module/xt_RTPENGINE.h b/kernel-module/xt_RTPENGINE.h index 738ee426c..786c72d31 100644 --- a/kernel-module/xt_RTPENGINE.h +++ b/kernel-module/xt_RTPENGINE.h @@ -8,6 +8,7 @@ #define RTPE_NUM_PAYLOAD_TYPES 32 #define RTPE_MAX_FORWARD_DESTINATIONS 32 #define RTPE_NUM_SSRC_TRACKING 4 +#define RTPE_NUM_EXTMAP_FILTER 32 @@ -125,12 +126,16 @@ struct rtpengine_output_info { uint32_t seq_offset[RTPE_NUM_SSRC_TRACKING]; // Rewrite output seq struct rtpengine_pt_output pt_output[RTPE_NUM_PAYLOAD_TYPES]; // same indexes as pt_input + uint8_t extmap_filter[RTPE_NUM_EXTMAP_FILTER]; // must be sorted + unsigned int num_extmap_filter; + struct interface_stats_block *iface_stats; // for egress stats struct stream_stats *stats; // for egress stats struct ssrc_stats *ssrc_stats[RTPE_NUM_SSRC_TRACKING]; unsigned char tos; - unsigned int ssrc_subst:1; + unsigned int ssrc_subst:1, + extmap:1; }; struct rtpengine_destination_info { diff --git a/t/.gitignore b/t/.gitignore index 82a4dbdc0..54fc0e471 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -29,3 +29,4 @@ test-amr-decode test-amr-encode aead-decrypt s3-auth +kernel-extmap-filter diff --git a/t/Makefile b/t/Makefile index 86362dde7..e89961e2d 100644 --- a/t/Makefile +++ b/t/Makefile @@ -97,6 +97,8 @@ endif SRCS+= s3-auth.c LIBSRCS+= s3utils.c +SRCS+= kernel-extmap-filter.c + COMMONOBJS= str.o auxlib.o rtplib.o loglib.o ssllib.o include ../lib/common.Makefile @@ -129,7 +131,7 @@ all-tests: unit-tests endif true # override linking recipe from common.Makefile -unit-tests: $(TESTS) aead-decrypt +unit-tests: $(TESTS) aead-decrypt kernel-extmap-filter failed="" ; \ for x in $(TESTS); do \ echo `date +"%Y-%m-%d %H:%M:%S"` testing: $$x ; \ @@ -343,6 +345,8 @@ test-const_str_hash.strhash: test-const_str_hash.strhash.o $(COMMONOBJS) bencode s3-auth: s3-auth.o s3utils.o +kernel-extmap-filter: kernel-extmap-filter.o + PRELOAD_CFLAGS += -D_GNU_SOURCE -std=c11 PRELOAD_LIBS += -ldl diff --git a/t/kernel-extmap-filter.c b/t/kernel-extmap-filter.c new file mode 100644 index 000000000..f0a33266f --- /dev/null +++ b/t/kernel-extmap-filter.c @@ -0,0 +1,474 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +struct rtpengine_output { + struct { + uint8_t extmap_filter[32]; + unsigned int num_extmap_filter; + } output; +}; + +struct sk_buff { + unsigned int len; +}; + +struct rtp_header { + unsigned char v_p_x_cc; + unsigned char _pad[3]; +}; + +struct rtp_exthdr { + uint16_t undefined; + uint16_t length; +} __attribute__ ((packed)); + +struct rtp_parsed { + struct rtp_header *rtp_header; + unsigned char *payload; + size_t payload_len; + struct rtp_exthdr *ext_hdr; + unsigned char *extension; + size_t extension_len; +}; + +static void skb_trim(struct sk_buff *s, unsigned int len) { + s->len = len; +} + +#include "../kernel-module/extmap_filter.inc.c" + +static void pkt(unsigned char *d, struct sk_buff *skb, struct rtp_parsed *r, + uint8_t hdr_val, + size_t ext_hdr_len, const unsigned char *ext_hdr, + size_t extensions_len, const unsigned char *extensions) +{ + *d = hdr_val; + r->rtp_header = (struct rtp_header *) d; + memset(r->rtp_header, 0, sizeof(*r->rtp_header)); + d += sizeof(struct rtp_header); + + if (ext_hdr_len == 0) { + assert(extensions_len == 0); + r->ext_hdr = NULL; + } + else if (ext_hdr_len == 2) { + r->ext_hdr = (struct rtp_exthdr *) d; + *d++ = ext_hdr[0]; + *d++ = ext_hdr[1]; + size_t padded = (extensions_len + 3L) & ~3L; + // verify math + assert((padded & 0x3) == 0); + assert(padded >= extensions_len); + size_t padding = padded - extensions_len; + assert(padding < 4); + size_t blocks = padded / 4L; + assert(blocks <= 0xffff); + *d++ = blocks >> 8; + *d++ = blocks & 0xff; + r->extension = d; + memcpy(d, extensions, extensions_len); + d += extensions_len; + memset(d, 0, padding); + d += padding; + r->extension_len = padded; + } + else + abort(); + + // fixed dummy payload + r->payload = d; + for (unsigned i = 0; i < 128; i++) + *d++ = i + 64; + r->payload_len = 128; + + skb->len = d - (unsigned char *) r->rtp_header; +} + +static void dump(uint8_t *d, size_t len) { + for (size_t i = 0; i < len; i++) + printf("%02x ", d[i]); + printf("\n"); +} + +static void tester( + unsigned int line, + uint8_t rtp_hdr_val_in, size_t ext_hdr_in_len, const unsigned char *ext_hdr_in, + size_t extensions_in_len, const unsigned char *extensions_in, + unsigned int filter_len, const uint8_t *filter, + uint8_t rtp_hdr_val_exp, size_t ext_hdr_exp_len, const unsigned char *ext_hdr_exp, + size_t extensions_exp_len, const unsigned char *extensions_exp) +{ + printf("test @ line %u\n", line); + + // build packets + unsigned char in [sizeof(struct rtp_header) + ext_hdr_in_len + 2 + extensions_in_len + 3 + 128]; + unsigned char exp[sizeof(struct rtp_header) + ext_hdr_exp_len + 2 + extensions_exp_len + 3 + 128]; + struct sk_buff is; + struct sk_buff es; + struct rtp_parsed ip; + struct rtp_parsed ep; + + pkt(in, &is, &ip, rtp_hdr_val_in, ext_hdr_in_len, ext_hdr_in, extensions_in_len, extensions_in); + pkt(exp, &es, &ep, rtp_hdr_val_exp, ext_hdr_exp_len, ext_hdr_exp, extensions_exp_len, extensions_exp); + + struct rtpengine_output o = {0}; + assert(filter_len <= sizeof(o.output.extmap_filter)); + o.output.num_extmap_filter = filter_len; + memcpy(o.output.extmap_filter, filter, filter_len); + + apply_extmap_filter(&is, &o, &ip); + + if (is.len != es.len) { + printf("%u != %u\n", is.len, es.len); + assert(0); + } + if (memcmp(in, exp, is.len)) { + dump(in, is.len); + dump(exp, is.len); + assert(0); + } + assert(ip.payload_len == ep.payload_len); + assert(memcmp(ip.payload, ep.payload, ip.payload_len) == 0); + + printf("ok\n"); +} + +#define TEST( \ + rtp_hdr_val_in, ext_hdr_in, extensions_in, \ + filter, \ + rtp_hdr_val_exp, ext_hdr_exp, extensions_exp \ + ) \ + tester( \ + __LINE__, \ + rtp_hdr_val_in, \ + sizeof(ext_hdr_in) - 1, (unsigned char *) ext_hdr_in, \ + sizeof(extensions_in) - 1, (unsigned char *) extensions_in, \ + sizeof(filter) - 1, (uint8_t *) filter, \ + rtp_hdr_val_exp, \ + sizeof(ext_hdr_exp) - 1, (unsigned char *) ext_hdr_exp, \ + sizeof(extensions_exp) - 1, (unsigned char *) extensions_exp \ + ); + +int main(void) { + // no extensions, no filter + TEST( + 0x80, "", "", + "", + 0x80, "", "" + ); + + // no extensions, filter + TEST( + 0x80, "", "", + "\x01\x02\x03\x04", + 0x80, "", "" + ); + + + // one-byte extension, empty filter (not allowed) + TEST( + 0x90, "\xbe\xde", "\x12" "foo", + "", + 0x80, "", "" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x", + "", + 0x80, "", "" + ); + + + // multiple one-byte extensions, empty filter (not allowed) + TEST( + 0x90, "\xbe\xde", "\x12" "foo" "\x22" "bar" "\x32" "yax" "\x42" "wuz", + "", + 0x80, "", "" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\x20" "y" "\x30" "z" "\x40" "p", + "", + 0x80, "", "" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\0\0" "\x20" "y" "\0\0" "\x30" "z" "\0\0" "\x40" "p", + "", + 0x80, "", "" + ); + + + // multiple one-byte extensions, allow first + TEST( + 0x90, "\xbe\xde", "\x12" "foo" "\x22" "bar" "\x32" "yax" "\x42" "wuz", + "\x01", + 0x90, "\xbe\xde", "\x12" "foo" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\x20" "y" "\x30" "z" "\x40" "p", + "\x01", + 0x90, "\xbe\xde", "\x10" "x" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\0\0" "\x20" "y" "\0\0" "\x30" "z" "\0\0" "\x40" "p", + "\x01", + 0x90, "\xbe\xde", "\x10" "x" + ); + + + // multiple one-byte extensions, allow second + TEST( + 0x90, "\xbe\xde", "\x12" "foo" "\x22" "bar" "\x32" "yax" "\x42" "wuz", + "\x02", + 0x90, "\xbe\xde", "\x22" "bar" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\x20" "y" "\x30" "z" "\x40" "p", + "\x02", + 0x90, "\xbe\xde", "\x20" "y" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\0\0" "\x20" "y" "\0\0" "\x30" "z" "\0\0" "\x40" "p", + "\x02", + 0x90, "\xbe\xde", "\x20" "y" + ); + + + // multiple one-byte extensions, allow last + TEST( + 0x90, "\xbe\xde", "\x12" "foo" "\x22" "bar" "\x32" "yax" "\x42" "wuz", + "\x04", + 0x90, "\xbe\xde", "\x42" "wuz" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\x20" "y" "\x30" "z" "\x40" "p", + "\x04", + 0x90, "\xbe\xde", "\x40" "p" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\0\0" "\x20" "y" "\0\0" "\x30" "z" "\0\0" "\x40" "p", + "\x04", + 0x90, "\xbe\xde", "\x40" "p" + ); + + + // multiple one-byte extensions, allow first and third + TEST( + 0x90, "\xbe\xde", "\x12" "foo" "\x22" "bar" "\x32" "yax" "\x42" "wuz", + "\x01\x03", + 0x90, "\xbe\xde", "\x12" "foo" "\x32" "yax" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\x20" "y" "\x30" "z" "\x40" "p", + "\x01\x03", + 0x90, "\xbe\xde", "\x10" "x" "\x30" "z" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\0\0" "\x20" "y" "\0\0" "\x30" "z" "\0\0" "\x40" "p", + "\x01\x03", + 0x90, "\xbe\xde", "\x10" "x" "\x30" "z" + ); + + + // multiple one-byte extensions, allow second and last + TEST( + 0x90, "\xbe\xde", "\x12" "foo" "\x22" "bar" "\x32" "yax" "\x42" "wuz", + "\x02\x04", + 0x90, "\xbe\xde", "\x22" "bar" "\x42" "wuz" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\x20" "y" "\x30" "z" "\x40" "p", + "\x02\x04", + 0x90, "\xbe\xde", "\x20" "y" "\x40" "p" + ); + TEST( + 0x90, "\xbe\xde", "\x10" "x" "\0\0" "\x20" "y" "\0\0" "\x30" "z" "\0\0" "\x40" "p", + "\x02\x04", + 0x90, "\xbe\xde", "\x20" "y" "\x40" "p" + ); + + + // random padding, allow multiple + TEST( + 0x90, "\xbe\xde", + "\x10" "a" "\x20" "b" "\0" "\x30" "c" "\0\0" "\x40" "d" "\0\0\0" + "\x51" "ee" "\x61" "ff" "\0" "\x71" "gg" "\0\0" "\x81" "hh" "\0\0\0" + "\x92" "kkk" "\xa2" "lll" "\0" "\xb2" "mmm" "\0\0" "\xc2" "nnn" "\0\0\0" + "\xd3" "oooo", + "\x01\x04\x07\x0a\x0c\x0d", + 0x90, "\xbe\xde", "\x10" "a" "\x40" "d" "\x71" "gg" "\xa2" "lll" "\xc2" "nnn" "\xd3" "oooo" + ); + + TEST( + 0x90, "\xbe\xde", + "\x10" "a" "\x20" "b" "\0" "\x30" "c" "\0\0" "\x40" "d" "\0\0\0" + "\x51" "ee" "\x61" "ff" "\0" "\x71" "gg" "\0\0" "\x81" "hh" "\0\0\0" + "\x92" "kkk" "\xa2" "lll" "\0" "\xb2" "mmm" "\0\0" "\xc2" "nnn" "\0\0\0" + "\xd5" "oooooo", + "\x01\x04\x07\x0a\x0c\x0d", + 0x90, "\xbe\xde", "\x10" "a" "\x40" "d" "\x71" "gg" "\xa2" "lll" "\xc2" "nnn" "\xd5" "oooooo" + ); + + + // two-byte extension, empty filter (not allowed) + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo", + "", + 0x80, "", "" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x", + "", + 0x80, "", "" + ); + + + // multiple one-byte extensions, empty filter (not allowed) + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo" "\x02\x03" "bar" "\x03\x03" "yax" "\x04\x03" "wuz", + "", + 0x80, "", "" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\x02\x01" "y" "\x03\x01" "z" "\x40" "p", + "", + 0x80, "", "" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\0\0" "\x02\x01" "y" "\0\0" "\x03\x01" "z" "\0\0" "\x40" "p", + "", + 0x80, "", "" + ); + + + // multiple one-byte extensions, allow first + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo" "\x02\x03" "bar" "\x03\x03" "yax" "\x04\x03" "wuz", + "\x01", + 0x90, "\x01\x00", "\x01\x03" "foo" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\x02\x01" "y" "\x03\x01" "z" "\x40" "p", + "\x01", + 0x90, "\x01\x00", "\x01\x01" "x" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\0\0" "\x02\x01" "y" "\0\0" "\x03\x01" "z" "\0\0" "\x40" "p", + "\x01", + 0x90, "\x01\x00", "\x01\x01" "x" + ); + + + // multiple one-byte extensions, allow second + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo" "\x02\x03" "bar" "\x03\x03" "yax" "\x04\x03" "wuz", + "\x02", + 0x90, "\x01\x00", "\x02\x03" "bar" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\x02\x01" "y" "\x03\x01" "z" "\x40" "p", + "\x02", + 0x90, "\x01\x00", "\x02\x01" "y" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\0\0" "\x02\x01" "y" "\0\0" "\x03\x01" "z" "\0\0" "\x40" "p", + "\x02", + 0x90, "\x01\x00", "\x02\x01" "y" + ); + + + // multiple one-byte extensions, allow last + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo" "\x02\x03" "bar" "\x03\x03" "yax" "\x04\x03" "wuz", + "\x04", + 0x90, "\x01\x00", "\x04\x03" "wuz" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\x02\x01" "y" "\x03\x01" "z" "\x04\x01" "p", + "\x04", + 0x90, "\x01\x00", "\x04\x01" "p" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\0\0" "\x02\x01" "y" "\0\0" "\x03\x01" "z" "\0\0" "\x04\x01" "p", + "\x04", + 0x90, "\x01\x00", "\x04\x01" "p" + ); + + + // multiple one-byte extensions, allow first and third + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo" "\x02\x03" "bar" "\x03\x03" "yax" "\x04\x03" "wuz", + "\x01\x03", + 0x90, "\x01\x00", "\x01\x03" "foo" "\x03\x03" "yax" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\x02\x01" "y" "\x03\x01" "z" "\x04\x01" "p", + "\x01\x03", + 0x90, "\x01\x00", "\x01\x01" "x" "\x03\x01" "z" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\0\0" "\x02\x01" "y" "\0\0" "\x03\x01" "z" "\0\0" "\x04\x01" "p", + "\x01\x03", + 0x90, "\x01\x00", "\x01\x01" "x" "\x03\x01" "z" + ); + + + // multiple one-byte extensions, allow second and last + TEST( + 0x90, "\x01\x00", "\x01\x03" "foo" "\x02\x03" "bar" "\x03\x03" "yax" "\x04\x03" "wuz", + "\x02\x04", + 0x90, "\x01\x00", "\x02\x03" "bar" "\x04\x03" "wuz" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\x02\x01" "y" "\x03\x01" "z" "\x04\x01" "p", + "\x02\x04", + 0x90, "\x01\x00", "\x02\x01" "y" "\x04\x01" "p" + ); + TEST( + 0x90, "\x01\x00", "\x01\x01" "x" "\0\0" "\x02\x01" "y" "\0\0" "\x03\x01" "z" "\0\0" "\x04\x01" "p", + "\x02\x04", + 0x90, "\x01\x00", "\x02\x01" "y" "\x04\x01" "p" + ); + + + // random padding, allow multiple + TEST( + 0x90, "\x01\x00", + "\x01\x01" "a" "\x02\x01" "b" "\0" "\x03\x01" "c" "\0\0" "\x04\x01" "d" "\0\0\0" + "\x05\x02" "ee" "\x06\x02" "ff" "\0" "\x07\x02" "gg" "\0\0" "\x08\x02" "hh" "\0\0\0" + "\x09\x03" "kkk" "\x0a\x03" "lll" "\0" "\x0b\x03" "mmm" "\0\0" "\x0c\x03" "nnn" "\0\0\0" + "\x0d\x04" "oooo", + "\x01\x04\x07\x0a\x0c\x0d", + 0x90, "\x01\x00", "\x01\x01" "a" "\x04\x01" "d" "\x07\x02" "gg" "\x0a\x03" "lll" "\x0c\x03" "nnn" "\x0d\04" "oooo" + ); + + TEST( + 0x90, "\x01\x00", + "\x01\x01" "a" "\x02\x01" "b" "\0" "\x03\x01" "c" "\0\0" "\x04\x01" "d" "\0\0\0" + "\x05\x02" "ee" "\x06\x02" "ff" "\0" "\x07\x02" "gg" "\0\0" "\x08\x02" "hh" "\0\0\0" + "\x09\x03" "kkk" "\x0a\x03" "lll" "\0" "\x0b\x03" "mmm" "\0\0" "\x0c\x03" "nnn" "\0\0\0" + "\x0d\x06" "oooooo", + "\x01\x04\x07\x0a\x0c\x0d", + 0x90, "\x01\x00", "\x01\x01" "a" "\x04\x01" "d" "\x07\x02" "gg" "\x0a\x03" "lll" "\x0c\x03" "nnn" "\x0d\x06" "oooooo" + ); + + + // higher IDs and longer values + TEST( + 0x90, "\x01\x00", + "\x31\x01" "a" "\x32\x21" "bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\0" "\x33\x01" "c" "\0\0" "\x34\x21" "dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\0\0\0" + "\x35\x02" "ee" "\x36\x22" "ffxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\0" "\x37\x02" "gg" "\0\0" "\x38\x22" "hhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\0\0\0" + "\x39\x03" "kkk" "\x3a\x23" "lllxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\0" "\x3b\x03" "mmm" "\0\0" "\x3c\x23" "nnnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\0\0\0" + "\x3d\x04" "oooo", + "\x31\x34\x37\x3a\x3c\x3d", + 0x90, "\x01\x00", "\x31\x01" "a" "\x34\x21" "dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\x37\x02" "gg" "\x3a\x23" "lllxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\x3c\x23" "nnnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "\x3d\04" "oooo" + ); + + + return 0; +}