#include "sdp-a-webrtc.h" #include "sdp.h" #include #include #include #include #if defined(OS_WINDOWS) #define strncasecmp _strnicmp #endif struct sdp_string_token_t { const char* p; int n; }; struct sdp_string_parser_t { const char* s; // start const char* e; // end }; static inline int sdp_get_token(struct sdp_string_parser_t* s, struct sdp_string_token_t* t, const char* escape) { // left trim while (s->s && s->s < s->e && ' ' == *s->s) ++s->s; t->p = s->s; for (t->n = 0; s->s && s->s + t->n < s->e; ++t->n) { if (strchr(escape, s->s[t->n])) break; } s->s += t->n + 1; // skip escape return t->n; } static inline int sdp_string_count(const char* s, int n, char c) { int i; for(i = 0; s && n > 0; n--) { if (*s++ == c) i++; } return i; } static inline void sdp_strcpy(char* p, const char* s, int n) { memcpy(p, s, n); p[n] = '\0'; } static inline char* sdp_strdup(const char* s, int n) { char* p; p = malloc(n + 1); if (p) { memcpy(p, s, n); p[n] = '\0'; } return p; } static char** sdp_string_split(const char* s, int n, const char* c, int* num) { int i, cn; char* pv, **pk; struct sdp_string_parser_t t; struct sdp_string_token_t k; t.s = s; t.e = s + n; *num = 0; if (t.s >= t.e) return NULL; cn = sdp_string_count(t.s, n, * c) + 1; pk = (char**)malloc(t.e - t.s + 1 + sizeof(char*) * cn); if (pk) { pv = (char*)(pk + cn); sdp_strcpy(pv, t.s, n); t.e = pv + (t.e - t.s); t.s = pv; for (i = 0; i < cn && sdp_get_token(&t, &k, c); i++) { assert(k.p + k.n <= t.e); pk[i] = (char*)k.p; ((char*)k.p)[k.n] = '\0'; } *num = i; } return pk; } /// @return 0-ok, other-error static inline int sdp_str2int(const char* s, int n, int* v) { char* e; *v = (int)strtoul(s, &e, 10); return e - s == n ? 0 : -1; } // "extmap:" 1*5DIGIT ["/" direction] // direction = "sendonly" / "recvonly" / "sendrecv" / "inactive" int sdp_a_extmap(const char* s, int n, int* ext, int* direction, char url[128]) { struct sdp_string_parser_t t; struct sdp_string_token_t k, v, d; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, "/ ") || 0 != sdp_str2int(k.p, k.n, ext)) return -1; // direction if (direction) *direction = SDP_A_SENDRECV; if (t.s > s && '/' == *(t.s-1)) { if (!sdp_get_token(&t, &d, " ") || d.n != 8) return -1; if (direction) { if (0 == strncmp(d.p, "sendonly", d.n)) *direction = SDP_A_SENDONLY; else if (0 == strncmp(d.p, "recvonly", d.n)) *direction = SDP_A_RECVONLY; else if (0 == strncmp(d.p, "sendrecv", d.n)) *direction = SDP_A_SENDRECV; else if (0 == strncmp(d.p, "inactive", d.n)) *direction = SDP_A_INACTIVE; } } if(!sdp_get_token(&t, &v, " ") || v.n >= 128) return -1; sdp_strcpy(url, v.p, v.n); return 0; } int sdp_a_fmtp(const char* s, int n, int* fmt, char** param) { struct sdp_string_parser_t t; struct sdp_string_token_t k, v; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ") || 0 != sdp_str2int(k.p, k.n, fmt) || !sdp_get_token(&t, &v, " ")) return -1; *param = sdp_strdup(v.p, v.n); return 0; } /* * https://www.rfc-editor.org/rfc/rfc8122.html#section-5 * attribute =/ fingerprint-attribute fingerprint-attribute = "fingerprint" ":" hash-func SP fingerprint hash-func = "sha-1" / "sha-224" / "sha-256" / "sha-384" / "sha-512" / "md5" / "md2" / token ; Additional hash functions can only come ; from updates to RFC 3279 fingerprint = 2UHEX *(":" 2UHEX) ; Each byte in upper-case hex, separated ; by colons. UHEX = DIGIT / %x41-46 ; A-F uppercase */ int sdp_a_fingerprint(const char* s, int n, char hash[16], char fingerprint[128]) { struct sdp_string_parser_t t; struct sdp_string_token_t k, v; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ") || !sdp_get_token(&t, &v, " ")) return -1; sdp_strcpy(hash, k.p, k.n); sdp_strcpy(fingerprint, v.p, v.n); return 0; } /* * https://www.rfc-editor.org/rfc/rfc5888.html#section-5 * group-attribute = "a=group:" semantics *(SP identification-tag) * semantics = "LS" / "FID" / semantics-extension * semantics-extension = token ; token is defined in RFC 4566 * * a=group:LS 1 2 * a=group:BUNDLE audio video */ int sdp_a_group(const char* s, int n, char semantics[32], char*** groups, int* count) { struct sdp_string_parser_t t; struct sdp_string_token_t k; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ")) return -1; sdp_strcpy(semantics, k.p, k.n); *count = 0; *groups = sdp_string_split(t.s, (int)(intptr_t)(t.e - t.s), " ", count); return 0; } /* * https://www.rfc-editor.org/rfc/rfc8839#name-candidate-attribute * candidate-attribute = "candidate" ":" foundation SP component-id SP transport SP priority SP connection-address SP ;from RFC 4566 port ;port from RFC 4566 SP cand-type [SP rel-addr] [SP rel-port] *(SP cand-extension) foundation = 1*32ice-char component-id = 1*3DIGIT transport = "UDP" / transport-extension transport-extension = token ; from RFC 3261 priority = 1*10DIGIT cand-type = "typ" SP candidate-types candidate-types = "host" / "srflx" / "prflx" / "relay" / token rel-addr = "raddr" SP connection-address rel-port = "rport" SP port cand-extension = extension-att-name SP extension-att-value extension-att-name = token extension-att-value = *VCHAR ice-char = ALPHA / DIGIT / "+" / "/" * a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr 203.0.113.141 rport 8998 */ int sdp_a_ice_candidate(const char* s, int n, struct sdp_ice_candidate_t* c) { int i, val; int uid, uport, upriority; struct sdp_string_parser_t t; struct sdp_string_token_t foundation, component, transport, priority, address, port, candtype; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &foundation, " ") || foundation.n >= sizeof(c->foundation) || !sdp_get_token(&t, &component, " ") || 0 != sdp_str2int(component.p, component.n, &uid) || !sdp_get_token(&t, &transport, " ") || transport.n >= sizeof(c->transport) || !sdp_get_token(&t, &priority, " ") || 0 != sdp_str2int(priority.p, priority.n, &upriority) || !sdp_get_token(&t, &address, " ") || address.n >= sizeof(c->address) || !sdp_get_token(&t, &port, " ") || 0 != sdp_str2int(port.p, port.n, &uport) || !sdp_get_token(&t, &candtype, " ") || 3 != candtype.n || 0 != strncmp("typ", candtype.p, candtype.n) || !sdp_get_token(&t, &candtype, " ") || candtype.n >= sizeof(c->candtype)) return -1; sdp_strcpy(c->foundation, foundation.p, foundation.n); sdp_strcpy(c->transport, transport.p, transport.n); sdp_strcpy(c->candtype, candtype.p, candtype.n); sdp_strcpy(c->address, address.p, address.n); c->component = (uint16_t)uid; c->priority = (uint32_t)upriority; c->port = (uint16_t)uport; c->relport = 0; c->generation = 0; c->nextension = 0; if (t.e > t.s) c->extensions = sdp_string_split(t.s, (int)(intptr_t)(t.e - t.s), " ", &c->nextension); for (i = 0; i + 1 < c->nextension; i += 2) { if (0 == strcmp("raddr", c->extensions[i]) && strlen(c->extensions[i + 1]) < sizeof(c->reladdr)) { snprintf(c->reladdr, sizeof(c->reladdr), "%s", c->extensions[i + 1]); } if (0 == strcmp("rport", c->extensions[i]) && 0 == sdp_str2int(c->extensions[i + 1], (int)strlen(c->extensions[i + 1]), &val)) { c->relport = (uint16_t)val; } else if (0 == strcmp("generation", c->extensions[i]) && 0 == sdp_str2int(c->extensions[i + 1], (int)strlen(c->extensions[i + 1]), &val)) { c->generation = val; } } return 0; } /* * https://www.rfc-editor.org/rfc/rfc8839#name-remote-candidates-attribute * remote-candidate-att = "remote-candidates:" remote-candidate 0*(SP remote-candidate) * remote-candidate = component-id SP connection-address SP port * * a=remote-candidates:1 192.0.2.3 45664 */ int sdp_a_ice_remote_candidates(const char* s, int n, struct sdp_ice_candidate_t* c) { // TODO: multiple remote candidates int uid, uport; struct sdp_string_parser_t t; struct sdp_string_token_t component, address, port; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &component, " ") || 0 != sdp_str2int(component.p, component.n, &uid) || !sdp_get_token(&t, &address, " ") || address.n >= sizeof(c->address) || !sdp_get_token(&t, &port, " ") || 0 != sdp_str2int(port.p, port.n, &uport)) return -1; sdp_strcpy(c->address, address.p, address.n); c->component = (uint16_t)uid; c->port = (uint16_t)uport; return 0; } /* * https://www.rfc-editor.org/rfc/rfc8839#name-ice-pacing-attribute * ice-pacing-att = "ice-pacing:" pacing-value * pacing-value = 1*10DIGIT * * a=ice-pacing:50 */ int sdp_a_ice_pacing(const char* s, int n, int* pacing) { if (0 != sdp_str2int(s, n, pacing)) return -1; return 0; } /* * https://www.rfc-editor.org/rfc/rfc8839#name-ice-options-attribute * ice-options = "ice-options:" ice-option-tag *(SP ice-option-tag) * ice-option-tag = 1*ice-char * * a=ice-options:ice2 rtp+ecn */ int sdp_a_ice_options(const char* s, int n, char*** options, int* count) { *count = 0; *options = sdp_string_split(s, n, " ", count); return 0; } /* * https://www.rfc-editor.org/rfc/rfc5888.html#section-4 * mid-attribute = "a=mid:" identification-tag * identification-tag = token ; token is defined in RFC 4566 */ int sdp_a_mid(const char* s, int n, char tag[256]) { struct sdp_string_parser_t t; struct sdp_string_token_t k; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ") || k.n > 256) return -1; sdp_strcpy(tag, k.p, k.n); return 0; } /* * https://www.rfc-editor.org/rfc/rfc8830#name-attribute-registration-in-e * msid-value = msid-id [ SP msid-appdata ] * msid-id = 1*64token-char ; see RFC 4566 * msid-appdata = 1*64token-char ; see RFC 4566 * * a=msid:examplefoo examplebar */ int sdp_a_msid(const char* s, int n, char id[65], char appdata[65]) { struct sdp_string_parser_t t; struct sdp_string_token_t k, v; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ") || k.n > 64) return -1; sdp_strcpy(id, k.p, k.n); appdata[0] = '\0'; if (sdp_get_token(&t, &v, " ") && v.n <= 64) sdp_strcpy(appdata, v.p, v.n); return 0; } /* * https://www.rfc-editor.org/rfc/rfc8866#name-orient-orientation * orient-value = portrait / landscape / seascape * a=orient:portrait */ int sdp_a_orient(const char* s, int n, char orient[16]) { struct sdp_string_parser_t t; struct sdp_string_token_t k; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ")) return -1; sdp_strcpy(orient, k.p, k.n); return 0; } /* * https://www.rfc-editor.org/rfc/rfc8851.html#name-sdp-arid-media-level-attrib * a=rid: [pt=;]=... * a=rid:5 send pt=99,102;max-br=64000;max-width=1280;max-height=720;max-fps=30 */ int sdp_a_rid(const char* s, int n, struct sdp_rid_t* rid) { int i, len; struct sdp_string_parser_t t; struct sdp_string_token_t id, dir; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &id, " ") || id.n >= sizeof(rid->rid) || !sdp_get_token(&t, &dir, " ") || dir.n >= sizeof(rid->direction)) return -1; sdp_strcpy(rid->rid, id.p, id.n); sdp_strcpy(rid->direction, dir.p, dir.n); rid->params = sdp_string_split(t.s, (int)(intptr_t)(t.e - t.s), ";", &rid->nparam); for (i = 0; i < rid->nparam; i++) { len = (int)strlen(rid->params[i]); if (len > 10 && 0 == strncmp("max-width=", rid->params[i], 10)) rid->width = atoi(rid->params[i] + 10); else if (len > 11 && 0 == strncmp("max-height=", rid->params[i], 11)) rid->height = atoi(rid->params[i] + 11); else if (len > 8 && 0 == strncmp("max-fps=", rid->params[i], 8)) rid->fps = atoi(rid->params[i] + 8); else if (len > 7 && 0 == strncmp("max-fs=", rid->params[i], 7)) rid->fs = atoi(rid->params[i] + 7); else if (len > 7 && 0 == strncmp("max-br=", rid->params[i], 7)) rid->br = atoi(rid->params[i] + 7); else if (len > 8 && 0 == strncmp("max-pps=", rid->params[i], 8)) rid->pps = atoi(rid->params[i] + 8); else if (len > 8 && 0 == strncmp("max-bpp=", rid->params[i], 8)) rid->bpp = atof(rid->params[i] + 8); else if (len > 7 && 0 == strncmp("depend=", rid->params[i], 7)) rid->depends = sdp_string_split(rid->params[i] + 7, len, ",", &rid->ndepend); else if (len > 3 && 0 == strncmp("pt=", rid->params[i], 3)) rid->payloads = sdp_string_split(rid->params[i] + 3, len, ",", &rid->npayload); } return rid->rid ? 0 : -1; } /* * https://www.rfc-editor.org/rfc/rfc3605 * rtcp-attribute = "a=rtcp:" port [nettype space addrtype space connection-address] CRLF * a=rtcp:53020 * a=rtcp:53020 IN IP6 2001:2345:6789:ABCD:EF01:2345:6789:ABCD */ int sdp_a_rtcp(const char* s, int n, struct sdp_address_t* address) { int port; struct sdp_string_parser_t t; struct sdp_string_token_t k, net, addr, conn; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ")) return -1; if (0 != sdp_str2int(k.p, k.n, &port)) return -1; address->port[1] = address->port[0] = port; // copy if (sdp_get_token(&t, &net, " ") && sdp_get_token(&t, &addr, " ") && sdp_get_token(&t, &conn, " ") && net.n < sizeof(address->network) && addr.n < sizeof(address->addrtype) && conn.n < sizeof(address->address)) { sdp_strcpy(address->network, net.p, net.n); sdp_strcpy(address->addrtype, addr.p, addr.n); sdp_strcpy(address->address, conn.p, conn.n); } return 0; } /* * https://www.rfc-editor.org/rfc/rfc8859.html#name-rtcp-fb-attribute-analysis * rtcp-fb-syntax = "a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF rtcp-fb-pt = "*" ; wildcard: applies to all formats / fmt ; as defined in SDP spec rtcp-fb-val = "ack" rtcp-fb-ack-param / "nack" rtcp-fb-nack-param / "trr-int" SP 1*DIGIT / rtcp-fb-id rtcp-fb-param rtcp-fb-id = 1*(alpha-numeric / "-" / "_") rtcp-fb-param = SP "app" [SP byte-string] / SP token [SP byte-string] / ; empty rtcp-fb-ack-param = SP "rpsi" / SP "app" [SP byte-string] / SP token [SP byte-string] / ; empty rtcp-fb-nack-param = SP "pli" / SP "sli" / SP "rpsi" / SP "app" [SP byte-string] / SP token [SP byte-string] / ; empty */ int sdp_a_rtcp_fb(const char* s, int n, struct sdp_rtcp_fb_t* fb) { struct sdp_string_parser_t t; struct sdp_string_token_t fmt, k, v; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &fmt, " ") || !sdp_get_token(&t, &k, " ") || k.n >= sizeof(fb->feedback)) return -1; if (fmt.n == 1 && '*' == *fmt.p) fb->fmt = -1; else if (0 != sdp_str2int(fmt.p, fmt.n, &fb->fmt)) return -1; sdp_strcpy(fb->feedback, k.p, k.n); if (sdp_get_token(&t, &v, " ") && v.n < sizeof(fb->param)) { sdp_strcpy(fb->param, v.p, v.n); if (3 == k.n && 0 == strncmp(k.p, "ack", 3)) { } else if (4 == k.n && 0 == strncmp(k.p, "nack", 4)) { } else if (7 == k.n && 0 == strncmp(k.p, "trr-int", 7)) { if (0 != sdp_str2int(v.p, v.n, &fb->trr_int)) return -1; } else { } } return 0; } /* * https://www.rfc-editor.org/rfc/rfc8853.html#name-detailed-description * a=simulcast:recv 1;2 send 4 sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] ) sc-send = %s"send" SP sc-str-list sc-recv = %s"recv" SP sc-str-list sc-str-list = sc-alt-list *( ";" sc-alt-list ) sc-alt-list = sc-id *( "," sc-id ) sc-id-paused = "~" sc-id = [sc-id-paused] rid-id ; SP defined in [RFC5234] ; rid-id defined in [RFC8851] */ int sdp_a_simulcast(const char* s, int n, struct sdp_simulcast_t* simulcast) { char** pk; int i, num, direction; struct sdp_string_parser_t t; struct sdp_string_token_t k, v; t.s = s; t.e = s + n; simulcast->nrecv = simulcast->nsend = 0; simulcast->recvs = simulcast->sends = NULL; for (i = 0; i < 2; i++) { if (!sdp_get_token(&t, &k, " ") || !sdp_get_token(&t, &v, " ")) break; if (4 == k.n && 0 == memcmp(k.p, "send", 4)) direction = 1; else if (4 == k.n && 0 == memcmp(k.p, "recv", 4)) direction = 2; else continue; pk = sdp_string_split(v.p, v.n, ";", &num); if (1 == direction) { simulcast->sends = pk; simulcast->nsend = num; } else if (2 == direction) { simulcast->recvs = pk; simulcast->nrecv = num; } else { assert(0); } } return 0; } /* * https://www.rfc-editor.org/rfc/rfc5576.html#section-4.1 * a=ssrc: * a=ssrc: : */ int sdp_a_ssrc(const char* s, int n, uint32_t* ssrc, char attribute[64], char value[128]) { int u; struct sdp_string_parser_t t; struct sdp_string_token_t id, k, v; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &id, " ") || !sdp_get_token(&t, &k, ":") || k.n >= 64) return -1; if (0 != sdp_str2int(id.p, id.n, &u)) return -1; *ssrc = (uint32_t)u; sdp_strcpy(attribute, k.p, k.n); if(sdp_get_token(&t, &v, "\r\n") && v.n < 128) sdp_strcpy(value, v.p, v.n); else *value = '\0'; return 0; } /* * https://www.rfc-editor.org/rfc/rfc5576.html#section-4.2 * a=ssrc-group: ... * * a=ssrc-group:FID 3720618323 2524685008 * a=ssrc:3720618323 cname:VQFSeIb012G+W2KA * a=ssrc:3720618323 msid:LCMSv0 LCMSv0 * a=ssrc:3720618323 mslabel:LCMSv0 * a=ssrc:3720618323 label:LCMSv0 * a=ssrc:2524685008 cname:VQFSeIb012G+W2KA * a=ssrc:2524685008 msid:LCMSv0 LCMSv0 * a=ssrc:2524685008 mslabel:LCMSv0 * a=ssrc:2524685008 label:LCMSv0 */ int sdp_a_ssrc_group(const char* s, int n, struct sdp_ssrc_group_t* group) { struct sdp_string_parser_t t; struct sdp_string_token_t k; t.s = s; t.e = s + n; if (!sdp_get_token(&t, &k, " ") || k.n >= sizeof(group->key)) return -1; sdp_strcpy(group->key, k.p, k.n); group->values = sdp_string_split(t.s, (int)(intptr_t)(t.e - t.s), " ", &group->count); return group->key ? 0 : -1; } #if defined(DEBUG) || defined(_DEBUG) static void sdp_a_extmap_test(void) { char url[128]; int ext, direction; const char* s = "6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"; assert(0 == sdp_a_extmap(s, strlen(s), &ext, &direction, url)); assert(6 == ext && SDP_A_RECVONLY == direction && 0 == strcmp("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", url)); s = "7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"; assert(0 == sdp_a_extmap(s, strlen(s), &ext, &direction, url)); assert(7 == ext && SDP_A_SENDRECV == direction && 0 == strcmp("http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", url)); } static void sdp_a_simulcast_test(void) { struct sdp_simulcast_t simulcast; memset(&simulcast, 0, sizeof(simulcast)); sdp_a_simulcast("recv 1;2 send 4", 15, &simulcast); assert(2 == simulcast.nrecv && 1 == simulcast.nsend && 0 == strcmp(simulcast.recvs[0], "1") && 0 == strcmp(simulcast.recvs[1], "2") && 0 == strcmp(simulcast.sends[0], "4")); free(simulcast.recvs); free(simulcast.sends); memset(&simulcast, 0, sizeof(simulcast)); sdp_a_simulcast("send 4 recv 1;2", 15, &simulcast); assert(2 == simulcast.nrecv && 1 == simulcast.nsend && 0 == strcmp(simulcast.recvs[0], "1") && 0 == strcmp(simulcast.recvs[1], "2") && 0 == strcmp(simulcast.sends[0], "4")); free(simulcast.recvs); free(simulcast.sends); } static void sdp_a_ssrc_test(void) { uint32_t ssrc; char attr[64], value[128]; assert(0 == sdp_a_ssrc("123456 key", 10, &ssrc, attr, value) && ssrc == 123456 && 0 == strcmp(attr, "key") && !*value); assert(0 == sdp_a_ssrc("123456 key:value", 16, &ssrc, attr, value) && ssrc == 123456 && 0 == strcmp(attr, "key") && 0 == strcmp(value, "value")); assert(0 == sdp_a_ssrc("3720618323 msid:LCMSv0 LCMSv0", 29, &ssrc, attr, value) && ssrc == 3720618323 && 0 == strcmp(attr, "msid") && 0 == strcmp(value, "LCMSv0 LCMSv0")); assert(0 != sdp_a_ssrc("123456f key:value", 17, &ssrc, attr, value)); } static void sdp_a_ssrc_group_test(void) { struct sdp_ssrc_group_t group; memset(&group, 0, sizeof(group)); assert(0 == sdp_a_ssrc_group("FID 3720618323 2524685008", 25, &group) && 0 == strcmp(group.key, "FID") && 2 == group.count && 0 == strcmp(group.values[0], "3720618323") && 0 == strcmp(group.values[1], "2524685008")); free(group.values); } static void sdp_a_rtcp_test(void) { struct sdp_address_t addr; memset(&addr, 0, sizeof(addr)); assert(0 == sdp_a_rtcp("53020", 5, &addr) && 53020 == addr.port[0]); assert(0 == sdp_a_rtcp("53020 IN IP6 2001:2345:6789:ABCD:EF01:2345:6789:ABCD", 52, &addr) && 53020 == addr.port[0] && 0 == strcmp(addr.network, "IN") && 0 == strcmp(addr.addrtype, "IP6") && 0 == strcmp(addr.address, "2001:2345:6789:ABCD:EF01:2345:6789:ABCD")); } static void sdp_a_rid_test(void) { struct sdp_rid_t rid; memset(&rid, 0, sizeof(rid)); assert(0 == sdp_a_rid("5 send", 6, &rid) && 0 == strcmp("5", rid.rid) && 0 == strcmp("send", rid.direction) && 0 == rid.nparam && 0 == rid.npayload && 0 == rid.ndepend); assert(0 == sdp_a_rid("5 send pt=99,102;max-br=64000;max-width=1280;max-height=720;max-fps=30", 70, &rid) && 0 == strcmp("5", rid.rid) && 0 == strcmp("send", rid.direction) && 2 == rid.npayload && 0 == strcmp("99", rid.payloads[0]) && 0 == strcmp("102", rid.payloads[1]) && 5 == rid.nparam && 64000 == rid.br && 1280 == rid.width && 720 == rid.height && 30 == rid.fps && 0 == rid.ndepend); free(rid.params); } static void sdp_a_fingerprint_test(void) { char hash[16]; char fingerprint[128]; assert(0 == sdp_a_fingerprint("sha-256 47:5A:AC:E6:8B:47:2A:A8:BC:AE:08:0D:E2:11:31:A5:38:F7:AE:F6:89:07:40:6C:CB:CC:99:CF:5F:C5:B8:4F", 103, hash, fingerprint) && 0 == strcmp("sha-256", hash) && 0 == strcmp("47:5A:AC:E6:8B:47:2A:A8:BC:AE:08:0D:E2:11:31:A5:38:F7:AE:F6:89:07:40:6C:CB:CC:99:CF:5F:C5:B8:4F", fingerprint)); } static void sdp_a_msid_test(void) { char msid[65]; char appdata[65]; assert(0 == sdp_a_msid("5jbA0s68fQKkMHiM7ZDVxoVOox1mXyMvC7bR 22ceddc2-d518-4ed4-b9ad-708c87720561", 73, msid, appdata) && 0 == strcmp("5jbA0s68fQKkMHiM7ZDVxoVOox1mXyMvC7bR", msid) && 0 == strcmp("22ceddc2-d518-4ed4-b9ad-708c87720561", appdata)); } static void sdp_a_mid_test(void) { char tag[256]; assert(0 == sdp_a_mid("audio", 5, tag) && 0 == strcmp("audio", tag)); } static void sdp_a_group_test(void) { int count; char** groups; char semantics[32]; assert(0 == sdp_a_group("LS 1 2", 6, semantics, &groups, &count) && 0 == strcmp("LS", semantics) && 2 == count && 0 == strcmp("1", groups[0]) && 0 == strcmp("2", groups[1])); free(groups); assert(0 == sdp_a_group("BUNDLE audio video", 18, semantics, &groups, &count) && 0 == strcmp("BUNDLE", semantics) && 2 == count && 0 == strcmp("audio", groups[0]) && 0 == strcmp("video", groups[1])); free(groups); } static void sdp_a_ice_candidate_test(void) { struct sdp_ice_candidate_t c; memset(&c, 0, sizeof(c)); assert(0 == sdp_a_ice_candidate("2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr 203.0.113.141 rport 8998", 75, &c) && 0 == strcmp(c.foundation, "2") && 1 == c.component && 0 == strcmp(c.transport, "UDP") && 1694498815 == c.priority && 0 == strcmp(c.address, "192.0.2.3") && 45664 == c.port && 0 == strcmp(c.candtype, "srflx") && 0 == strcmp(c.reladdr, "203.0.113.141") && 8998 == c.relport && 4 == c.nextension); memset(&c, 0, sizeof(c)); assert(0 == sdp_a_ice_candidate("1 1 udp 2013266431 47.95.197.19 50858 typ host generation 0", 59, &c) && 0 == strcmp(c.foundation, "1") && 1 == c.component && 0 == strcmp(c.transport, "udp") && 2013266431 == c.priority && 0 == strcmp(c.address, "47.95.197.19") && 50858 == c.port && 0 == strcmp(c.candtype, "host") && 0 == strcmp(c.reladdr, "") && 0 == c.relport && 2 == c.nextension && 0 == strcmp("generation", c.extensions[0]) && 0 == strcmp("0", c.extensions[1]) && 0 == c.generation); free(c.extensions); } static void sdp_a_ice_remote_candidates_test(void) { struct sdp_ice_candidate_t c; memset(&c, 0, sizeof(c)); assert(0 == sdp_a_ice_remote_candidates("1 192.0.2.3 45664", 17, &c) && 1 == c.component && 0 == strcmp("192.0.2.3", c.address) && 45664 == c.port); } static void sdp_a_rtcp_fb_test(void) { struct sdp_rtcp_fb_t fb; memset(&fb, 0, sizeof(fb)); assert(0 == sdp_a_rtcp_fb("111 nack", 8, &fb) && 111 == fb.fmt && 0 == strcmp("nack", fb.feedback) && !*fb.param); assert(0 == sdp_a_rtcp_fb("96 nack pli", 11, &fb) && 96 == fb.fmt && 0 == strcmp("nack", fb.feedback) && 0 == strcmp("pli", fb.param)); assert(0 == sdp_a_rtcp_fb("96 ccm fir", 10, &fb) && 96 == fb.fmt && 0 == strcmp("ccm", fb.feedback) && 0 == strcmp("fir", fb.param)); } void sdp_a_webrtc_test(void) { sdp_a_extmap_test(); sdp_a_fingerprint_test(); sdp_a_group_test(); sdp_a_ice_candidate_test(); sdp_a_ice_remote_candidates_test(); sdp_a_mid_test(); sdp_a_msid_test(); sdp_a_rid_test(); sdp_a_rtcp_test(); sdp_a_rtcp_fb_test(); sdp_a_simulcast_test(); sdp_a_ssrc_test(); sdp_a_ssrc_group_test(); } #endif