#include "sdp.h" #include #include #include #include #if defined(OS_WINDOWS) #define strdup _strdup #define strcasecmp _stricmp #endif #define N_EMAIL 1 #define N_PHONE 1 #define N_CONNECTION 1 #define N_BANDWIDTH 1 #define N_TIMING 1 #define N_REPEAT 1 #define N_TIMEZONE 1 #define N_REPEAT_OFFSET 1 #define N_ATTRIBUTE 5 #define N_MEDIA 3 // audio/video/whiteboard #define N_MEDIA_FORMAT 5 struct sdp_connection { char* network; char* addrtype; char* address; }; struct sdp_origin { char* username; char* session; char* session_version; struct sdp_connection c; }; struct sdp_email { char* email; }; struct sdp_phone { char* phone; }; struct sdp_bandwidth { char* bwtype; char* bandwidth; }; struct bandwidths { int count; int capacity; struct sdp_bandwidth bandwidths[N_BANDWIDTH]; struct sdp_bandwidth *ptr; }; struct sdp_repeat { char* interval; char* duration; struct offset { int count; int capacity; char *offsets[N_REPEAT_OFFSET]; char **ptr; } offsets; }; struct sdp_timezone { char* time; char* offset; }; struct sdp_timing { char* start; char* stop; struct repeat { int count; int capacity; struct sdp_repeat repeats[N_REPEAT]; struct sdp_repeat *ptr; } r; struct timezone_t { int count; int capacity; struct sdp_timezone timezones[N_TIMEZONE]; struct sdp_timezone *ptr; } z; }; struct sdp_encryption { char* method; char* key; }; struct sdp_attribute { char* name; char* value; }; struct attributes { int count; int capacity; struct sdp_attribute attrs[N_ATTRIBUTE]; struct sdp_attribute *ptr; }; struct sdp_media { char* media; //audio, video, text, application, message char* port; char* proto; // udp, RTP/AVP, RTP/SAVP struct format { int count; int capacity; char *formats[N_MEDIA_FORMAT]; char **ptr; } fmt; char* i; struct connection { int count; int capacity; struct sdp_connection connections[N_EMAIL]; struct sdp_connection *ptr; } c; struct attributes a; struct bandwidths b; struct sdp_encryption k; }; struct sdp_t { char *raw; // raw source string int offset; // parse offset int v; struct sdp_origin o; char* s; char* i; char* u; struct email { int count; int capacity; struct sdp_email emails[N_EMAIL]; struct sdp_email *ptr; } e; struct phone { int count; int capacity; struct sdp_phone phones[N_PHONE]; struct sdp_phone *ptr; } p; struct sdp_connection c; struct bandwidths b; struct timing { int count; int capacity; struct sdp_timing times[N_TIMING]; struct sdp_timing *ptr; } t; struct sdp_encryption k; struct attributes a; struct media { int count; int capacity; struct sdp_media medias[N_MEDIA]; struct sdp_media *ptr; } m; }; static inline void sdp_skip_space(struct sdp_t* sdp) { char c = sdp->raw[sdp->offset]; while(' ' == c || '\t' == c) c = sdp->raw[++sdp->offset]; } static inline int sdp_token_word(struct sdp_t* sdp, const char* escape) { int n = sdp->offset; sdp->offset += strcspn(sdp->raw + sdp->offset, escape); return sdp->offset - n; } static inline int sdp_token_crlf(struct sdp_t* sdp) { int i; sdp_skip_space(sdp); for(i = 0; '\r' == sdp->raw[sdp->offset] || '\n' == sdp->raw[sdp->offset]; i++) ++sdp->offset; // sdp end line if('\0' == sdp->raw[sdp->offset]) return 0; return i > 0 ? 0 : -1; } static inline void trim_right(const char* s, int *len) { //// left trim //while(*len > 0 && isspace(s[*pos])) //{ // --*len; // ++*pos; //} // right trim while(*len > 0 && ' '==(s[*len - 1])) { --*len; } } static inline struct sdp_timing* sdp_get_timing(struct sdp_t* sdp, int idx) { if(idx >= sdp->t.count) return NULL; return idx >= N_TIMING ? &sdp->t.ptr[idx - N_TIMING] : &sdp->t.times[idx]; } static inline struct sdp_media* sdp_get_media(struct sdp_t* sdp, int idx) { if(idx >= sdp->m.count) return NULL; return idx >= N_MEDIA ? &sdp->m.ptr[idx - N_MEDIA] : &sdp->m.medias[idx]; } // RFC4566 5.1 static int sdp_parse_version(struct sdp_t* sdp) { char c; assert(sdp); if(!sdp) return -1; sdp->v = 0; sdp_skip_space(sdp); c = sdp->raw[sdp->offset]; while('0' <= c && c <= '9') { sdp->v = sdp->v * 10 + (c - '0'); c = sdp->raw[++sdp->offset]; } return sdp_token_crlf(sdp); } // RFC4566 5.2 // o= // "-" if the originating host does not support the concept of user IDs. // is a numeric string // is a version number for this session description // IN // IP4/IP6 static int sdp_parse_origin(struct sdp_t* sdp) { char* v[6]; char** vv[6]; int i, j, n[6]; struct sdp_origin *o; o = &sdp->o; memset(o, 0, sizeof(struct sdp_origin)); memset(n, 0, sizeof(n)); o->username = (char*)"-"; // default value o->c.network = (char*)"IN"; o->c.addrtype = (char*)"IP4"; sdp_skip_space(sdp); for (i = 0; i < 6 && sdp->raw[sdp->offset] && !strchr("\r\n", sdp->raw[sdp->offset]); i++) { v[i] = sdp->raw + sdp->offset; n[i] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); } sdp_token_crlf(sdp); //if (i < 5) // return 0; vv[0] = &o->username; vv[1] = &o->session; vv[2] = &o->session_version; vv[3] = &o->c.network; vv[4] = &o->c.addrtype; vv[5] = &o->c.address; for (j = 0; j < i; j++) { v[j][n[j]] = '\0'; *vv[j] = v[j]; } return 0; } // RFC4566 5.3 // s= // There MUST be one and only one "s=" field per session description. can be empty static int sdp_parse_session(struct sdp_t* sdp) { int n = 0; sdp->s = sdp->raw + sdp->offset; n = sdp_token_word(sdp, "\r\n"); if(0 != sdp_token_crlf(sdp)) return -1; sdp->s[n] = '\0'; return 0; } // RFC4566 5.4 // i= // There MUST be at most one session-level "i=" field per session description, // and at most one "i=" field per media. // default UTF-8 static int sdp_parse_information(struct sdp_t* sdp) { int n = 0; char **i; if(sdp->m.count > 0) { i = &sdp_get_media(sdp, sdp->m.count-1)->i; } else { i = &sdp->i; } *i = sdp->raw + sdp->offset; n = sdp_token_word(sdp, "\r\n"); if(0 != sdp_token_crlf(sdp)) return -1; (*i)[n] = '\0'; return 0; } // RFC4566 5.5 // u= // This field is OPTIONAL, but if it is present it MUST be // specified before the first media field. static int sdp_parse_uri(struct sdp_t* sdp) { int n = 0; // No more than one URI field is allowed per session description. assert(!sdp->u); sdp->u = sdp->raw + sdp->offset; n = sdp_token_word(sdp, "\r\n"); if(0 != sdp_token_crlf(sdp)) return -1; sdp->u[n] = '\0'; return 0; } // RFC4566 5.6 // e= // p= // OPTIONAL, If an email address or phone number is present, it MUST be specified // before the first media field. More than one email or phone field can // be given for a session description. // p=+1 617 555-6011 // e=j.doe@example.com (Jane Doe) // e=Jane Doe static int sdp_parse_email(struct sdp_t* sdp) { int n = 0; struct sdp_email *e; if(sdp->e.count >= N_EMAIL) { if(sdp->e.count - N_EMAIL >= sdp->e.capacity) { void* ptr; ptr = (struct sdp_email*)realloc(sdp->e.ptr, (sdp->e.capacity+8)*sizeof(struct sdp_email)); if(!ptr) return -ENOMEM; sdp->e.ptr = ptr; sdp->e.capacity += 8; } e = &sdp->e.ptr[sdp->e.count - N_EMAIL]; } else { e = &sdp->e.emails[sdp->e.count]; } memset(e, 0, sizeof(struct sdp_email)); e->email = sdp->raw + sdp->offset; n = sdp_token_word(sdp, "\r\n"); if(0 != sdp_token_crlf(sdp)) return -1; e->email[n] = '\0'; ++sdp->e.count; return 0; } static int sdp_parse_phone(struct sdp_t* sdp) { int n = 0; struct sdp_phone *p; if(sdp->p.count >= N_PHONE) { if(sdp->p.count - N_PHONE >= sdp->p.capacity) { void* ptr; ptr = (struct sdp_phone*)realloc(sdp->p.ptr, (sdp->p.capacity+8)*sizeof(struct sdp_phone)); if(!ptr) return -ENOMEM; sdp->p.ptr = ptr; sdp->p.capacity += 8; } p = &sdp->p.ptr[sdp->p.count - N_PHONE]; } else { p = &sdp->p.phones[sdp->p.count]; } memset(p, 0, sizeof(struct sdp_phone)); p->phone = sdp->raw + sdp->offset; n = sdp_token_word(sdp, "\r\n"); if(0 != sdp_token_crlf(sdp)) return -1; trim_right(p->phone, &n); p->phone[n] = '\0'; ++sdp->p.count; return 0; } // RFC4566 5.7 // c= // A session description MUST contain either at least one "c=" field in // each media description or a single "c=" field at the session level. // c=IN IP4 224.2.36.42/127 // c=IN IP4 224.2.1.1/127/3 // c=IN IP6 FF15::101/3 // The slash notation for multiple addresses described above MUST NOT be // used for IP unicast addresses static int sdp_parse_connection(struct sdp_t* sdp) { int n[3]; struct sdp_media *m; struct sdp_connection *c; m = NULL; if(sdp->m.count > 0) { m = sdp_get_media(sdp, sdp->m.count-1); if(m->c.count >= N_CONNECTION) { if(m->c.count - N_CONNECTION >= m->c.capacity) { void* ptr; ptr = (struct sdp_connection*)realloc(m->c.ptr, (m->c.capacity+8)*sizeof(struct sdp_connection)); if(!ptr) return -ENOMEM; m->c.ptr = ptr; m->c.capacity += 8; } c = &m->c.ptr[m->c.count - N_CONNECTION]; } else { c = &m->c.connections[m->c.count]; } } else { c = &sdp->c; } memset(c, 0, sizeof(struct sdp_connection)); memset(n, 0, sizeof(n)); sdp_skip_space(sdp); c->network = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); c->addrtype = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); c->address = sdp->raw + sdp->offset; n[2] = sdp_token_word(sdp, "\r\n"); trim_right(c->address, &n[2]); // check before assign '\0' if(0==sdp_token_crlf(sdp) /* && n[0]>0 && n[1]>0 && n[2]>0*/ ) { c->network[n[0]] = '\0'; c->addrtype[n[1]] = '\0'; c->address[n[2]] = '\0'; if(m) ++m->c.count; // add media connection return 0; } return -1; } // RFC4566 5.8 // b=: // bwtype: CT/AS // bandwidth: kilobits per second by default // A prefix "X-" is defined for names. // b=X-YZ:128 static int sdp_parse_bandwidth(struct sdp_t* sdp) { int n[2]; struct bandwidths *bs; struct sdp_bandwidth *b; if(sdp->m.count > 0) { bs = &sdp_get_media(sdp, sdp->m.count-1)->b; } else { bs = &sdp->b; } if(bs->count >= N_BANDWIDTH) { if(bs->count - N_BANDWIDTH >= bs->capacity) { void* ptr; ptr = (struct sdp_bandwidth*)realloc(bs->ptr, (bs->capacity+8)*sizeof(struct sdp_bandwidth)); if(!ptr) return -ENOMEM; bs->ptr = ptr; bs->capacity += 8; } b = &bs->ptr[bs->count - N_BANDWIDTH]; } else { b = &bs->bandwidths[bs->count]; } memset(n, 0, sizeof(n)); memset(b, 0, sizeof(struct sdp_bandwidth)); sdp_skip_space(sdp); b->bwtype = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, ":\r\n"); trim_right(b->bwtype, &n[0]); if(':' == sdp->raw[sdp->offset]) ++sdp->offset; sdp_skip_space(sdp); b->bandwidth = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, "\r\n"); trim_right(b->bandwidth, &n[1]); if(0 == sdp_token_crlf(sdp)) { ++bs->count; b->bwtype[n[0]] = '\0'; b->bandwidth[n[1]] = '\0'; return 0; } return -1; } // RFC4566 5.9 (p17) // t= // If the is set to zero, then the session is not bounded, // though it will not become active until after the . If // the is also zero, the session is regarded as permanent. // // 1. These values are the decimal representation of Network Time Protocol (NTP) time values in seconds // since 1900 [13]. To convert these values to UNIX time, subtract decimal 2208988800. // 2. If the is set to zero, then the session is not bounded, though it will not become active // until after the . If the is also zero, the session is regarded as permanent. static int sdp_parse_timing(struct sdp_t* sdp) { int n[2]; struct sdp_timing *t; if(sdp->t.count >= N_TIMING) { if(sdp->t.count - N_TIMING >= sdp->t.capacity) { void* ptr; ptr = (struct sdp_timing*)realloc(sdp->t.ptr, (sdp->t.capacity+8)*sizeof(struct sdp_timing)); if(!ptr) return -ENOMEM; sdp->t.ptr = ptr; sdp->t.capacity += 8; } t = &sdp->t.ptr[sdp->t.count - N_TIMING]; } else { t = &sdp->t.times[sdp->t.count]; } memset(n, 0, sizeof(n)); memset(t, 0, sizeof(struct sdp_timing)); sdp_skip_space(sdp); t->start = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); t->stop = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, "\r\n"); trim_right(t->stop, &n[1]); if(0 == sdp_token_crlf(sdp)) { t->start[n[0]] = '\0'; t->stop[n[1]] = '\0'; ++sdp->t.count; return 0; } return -1; } static int sdp_append_timing_repeat_offset(struct sdp_repeat *r, char* offset) { if(r->offsets.count >= N_REPEAT_OFFSET) { if(r->offsets.count - N_REPEAT_OFFSET >= r->offsets.capacity) { void* ptr; ptr = (char**)realloc(r->offsets.ptr, (r->offsets.capacity+8)*sizeof(char*)); if(!ptr) return -ENOMEM; r->offsets.ptr = ptr; r->offsets.capacity += 8; } r->offsets.ptr[r->offsets.count - N_REPEAT_OFFSET] = offset; } else { r->offsets.offsets[r->offsets.count] = offset; } ++r->offsets.count; return 0; } // RFC4566 5.10 // r= // t=3034423619 3042462419 // r=604800 3600 0 90000 // r=7d 1h 0 25h static int sdp_parse_repeat(struct sdp_t* sdp) { int ret; int n[3]; char *offset; struct sdp_timing *t; struct sdp_repeat *r; assert(sdp->t.count > 0); if(sdp->t.count < 1) return -1; // repeat must after timing t = sdp_get_timing(sdp, sdp->t.count-1); if(t->r.count >= N_REPEAT) { if(t->r.count - N_REPEAT >= t->r.capacity) { void* ptr; ptr = (struct sdp_repeat*)realloc(t->r.ptr, (t->r.capacity+8)*sizeof(struct sdp_repeat)); if(!ptr) return -ENOMEM; t->r.ptr = ptr; t->r.capacity += 8; } r = &t->r.ptr[t->r.count - N_REPEAT]; } else { r = &t->r.repeats[t->r.count]; } offset = NULL; memset(n, 0, sizeof(n)); memset(r, 0, sizeof(struct sdp_repeat)); sdp_skip_space(sdp); r->interval = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); r->duration = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, " \t\r\n"); while(sdp->raw[sdp->offset] && strchr(" \t", sdp->raw[sdp->offset])) { if(n[2] > 0 && offset) { offset[n[2]] = '\0'; ret = sdp_append_timing_repeat_offset(r, offset); if(0 != ret) return ret; } sdp_skip_space(sdp); offset = sdp->raw + sdp->offset; n[2] = sdp_token_word(sdp, " \t\r\n"); } if(0 == sdp_token_crlf(sdp)) { r->interval[n[0]] = '\0'; r->duration[n[1]] = '\0'; if(n[2] > 0 && offset) { offset[n[2]] = '\0'; ret = sdp_append_timing_repeat_offset(r, offset); if(0 != ret) return ret; } ++t->r.count; return 0; } return -1; } // RFC4566 5.11 // z= .... // z=2882844526 -1h 2898848070 0 static int sdp_parse_timezone(struct sdp_t* sdp) { int n[2]; char *time, *offset; struct sdp_timing *t; struct sdp_timezone *z; assert(sdp->t.count > 0); if(sdp->t.count < 1) return -1; // timezone must after timing t = sdp_get_timing(sdp, sdp->t.count-1); do { sdp_skip_space(sdp); time = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); offset = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, " \t\r\n"); if(n[0] < 1 || n[1] < 1) break; if(t->z.count >= N_TIMEZONE) { if(t->z.count - N_TIMEZONE >= t->z.capacity) { void* ptr; ptr = (struct sdp_timezone*)realloc(t->z.ptr, (t->z.capacity+8)*sizeof(struct sdp_timezone)); if(!ptr) return -ENOMEM; t->z.ptr = ptr; t->z.capacity += 8; } z = &t->z.ptr[t->z.count - N_TIMEZONE]; } else { z = &t->z.timezones[t->z.count]; } z->time = time; z->offset = offset; ++t->z.count; } while(n[0] > 0 && n[1] > 0); return sdp_token_crlf(sdp); } // RFC4566 5.12 // k= // k=: // k=clear: // k=base64: // k=uri: // k=prompt // A key field is permitted before the first media entry (in which case // it applies to all media in the session), or for each media entry as required. static int sdp_parse_encryption(struct sdp_t* sdp) { int n[2]; struct sdp_encryption *k; if(sdp->m.count > 0) { k = &sdp_get_media(sdp, sdp->m.count-1)->k; } else { k = &sdp->k; } memset(k, 0, sizeof(struct sdp_encryption)); sdp_skip_space(sdp); k->method = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, ":\r\n"); trim_right(k->method, &n[0]); if(':' == sdp->raw[sdp->offset]) ++sdp->offset; // skip ':' sdp_skip_space(sdp); k->key = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, "\r\n"); trim_right(k->key, &n[1]); if(0 == sdp_token_crlf(sdp)) { k->method[n[0]] = '\0'; k->key[n[1]] = '\0'; return 0; } return -1; } // RFC4566 5.13 // a= // a=: // a=cat: // a=keywds: // a=tool: // a=ptime: // a=maxptime: // a=rtpmap: / [/] // a=recvonly // a=sendrecv // a=sendonly // a=inactive // a=orient: // a=type: // a=charset: // a=sdplang: // a=lang: // a=framerate: // a=quality: // a=fmtp: static int sdp_parse_attribute(struct sdp_t* sdp) { int n[2]; struct attributes *as; struct sdp_attribute *a; if(sdp->m.count > 0) { as = &sdp_get_media(sdp, sdp->m.count-1)->a; } else { as = &sdp->a; } if(as->count >= N_ATTRIBUTE) { if(as->count - N_ATTRIBUTE >= as->capacity) { void* ptr; ptr = (struct sdp_attribute*)realloc(as->ptr, (as->capacity+8)*sizeof(struct sdp_attribute)); if(!ptr) return -ENOMEM; as->ptr = ptr; as->capacity += 8; } a = &as->ptr[as->count - N_ATTRIBUTE]; } else { a = &as->attrs[as->count]; } memset(a, 0, sizeof(struct sdp_attribute)); sdp_skip_space(sdp); a->name = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, ":\r\n"); trim_right(a->name, &n[0]); if(':' == sdp->raw[sdp->offset]) ++sdp->offset; // skip ':' sdp_skip_space(sdp); a->value = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, "\r\n"); trim_right(a->value, &n[1]); if(0 == sdp_token_crlf(sdp)) { ++as->count; a->name[n[0]] = '\0'; a->value[n[1]] = '\0'; return 0; } return -1; } static int sdp_append_media_format(struct sdp_media *m, char* fmt) { if(m->fmt.count >= N_MEDIA_FORMAT) { if(m->fmt.count - N_MEDIA_FORMAT >= m->fmt.capacity) { void* ptr; ptr = (char**)realloc(m->fmt.ptr, (m->fmt.capacity+8)*sizeof(char*)); if(!ptr) return -ENOMEM; m->fmt.ptr = ptr; m->fmt.capacity += 8; } m->fmt.ptr[m->fmt.count - N_MEDIA_FORMAT] = fmt; } else { m->fmt.formats[m->fmt.count] = fmt; } ++m->fmt.count; return 0; } // RFC4566 5.14 // m= ... // media: audio/video/text/application/message // proto: udp, RTP/AVP, RTP/SAVP // m= / ... // m=video 49170/2 RTP/AVP 31 // c=IN IP4 224.2.1.1/127/2 // m=video 49170/2 RTP/AVP 31 // m=application 9 UDP/DTLS/SCTP webrtc-datachannel static int sdp_parse_media(struct sdp_t* sdp) { int ret; int n[4]; char *fmt; struct sdp_media *m; if(sdp->m.count >= N_MEDIA) { if(sdp->m.count - N_MEDIA >= sdp->m.capacity) { void* ptr; ptr = (struct sdp_media*)realloc(sdp->m.ptr, (sdp->m.capacity+8)*sizeof(struct sdp_media)); if(!ptr) return -ENOMEM; sdp->m.ptr = ptr; sdp->m.capacity += 8; } m = &sdp->m.ptr[sdp->m.count - N_MEDIA]; } else { m = &sdp->m.medias[sdp->m.count]; } memset(m, 0, sizeof(struct sdp_media)); sdp_skip_space(sdp); m->media = sdp->raw + sdp->offset; n[0] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); m->port = sdp->raw + sdp->offset; n[1] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); m->proto = sdp->raw + sdp->offset; n[2] = sdp_token_word(sdp, " \t\r\n"); sdp_skip_space(sdp); fmt = sdp->raw + sdp->offset; n[3] = sdp_token_word(sdp, " \t\r\n"); while (' ' == fmt[n[3]] || '\t' == fmt[n[3]]) { fmt[n[3]] = '\0'; ret = sdp_append_media_format(m, fmt); if(0 != ret) return ret; sdp->offset += 1; // skip '\0' sdp_skip_space(sdp); fmt = sdp->raw + sdp->offset; n[3] = sdp_token_word(sdp, " \t\r\n"); } if(0 == sdp_token_crlf(sdp)) { m->media[n[0]] = '\0'; m->port[n[1]] = '\0'; m->proto[n[2]] = '\0'; if(n[3] > 0) { fmt[n[3]] = '\0'; ret = sdp_append_media_format(m, fmt); if(0 != ret) return ret; } ++sdp->m.count; return 0; } return -1; } // gb28181 extension static int sdp_parse_gb28181_ssrc(struct sdp_t* sdp) { int n; struct attributes* as; struct sdp_attribute* a; if (sdp->m.count > 0) { as = &sdp_get_media(sdp, sdp->m.count - 1)->a; } else { as = &sdp->a; } if (as->count >= N_ATTRIBUTE) { if (as->count - N_ATTRIBUTE >= as->capacity) { void* ptr; ptr = (struct sdp_attribute*)realloc(as->ptr, (as->capacity + 8) * sizeof(struct sdp_attribute)); if (!ptr) return -ENOMEM; as->ptr = ptr; as->capacity += 8; } a = &as->ptr[as->count - N_ATTRIBUTE]; } else { a = &as->attrs[as->count]; } memset(a, 0, sizeof(struct sdp_attribute)); a->name = "ssrc"; a->value = sdp->raw + sdp->offset; n = sdp_token_word(sdp, "\r\n"); if (0 != sdp_token_crlf(sdp)) return -1; a->value[n] = '\0'; ++as->count; return 0; } static void* sdp_create(const char* s, int len) { struct sdp_t *sdp; sdp = (struct sdp_t*)malloc(sizeof(struct sdp_t) + len + 1); if( !sdp ) return NULL; memset(sdp, 0, sizeof(struct sdp_t)); sdp->raw = (char*)(sdp + 1); memcpy(sdp->raw, s, len); sdp->raw[len] = 0; return sdp; } void sdp_destroy(struct sdp_t* sdp) { int i; struct sdp_media *m; struct sdp_timing *t; if(sdp->e.count > N_EMAIL) free(sdp->e.ptr); if(sdp->p.count > N_PHONE) free(sdp->p.ptr); if(sdp->b.count > N_BANDWIDTH) free(sdp->b.ptr); for(i = 0; i < sdp->t.count; i++) { t = sdp_get_timing(sdp, i); if(t->r.count > N_REPEAT) free(t->r.ptr); if(t->z.count > N_TIMEZONE) free(t->z.ptr); } if(sdp->t.count > N_TIMING) free(sdp->t.ptr); if(sdp->a.count > N_ATTRIBUTE) free(sdp->a.ptr); for(i = 0; i < sdp->m.count; i++) { m = sdp_get_media(sdp, i); if(m->fmt.count > N_MEDIA_FORMAT) free(m->fmt.ptr); if(m->a.count > N_ATTRIBUTE) free(m->a.ptr); if(m->c.count > N_CONNECTION) free(m->c.ptr); if (m->b.count > N_BANDWIDTH) free(m->b.ptr); } if(sdp->m.count > N_MEDIA) free(sdp->m.ptr); free(sdp); } struct sdp_t* sdp_parse(const char* s, int len) { int r; char c; struct sdp_t *sdp; assert(s); sdp = (struct sdp_t*)sdp_create(s, len); if(!sdp) return NULL; while(sdp->raw[sdp->offset] && !strchr("\r\n", sdp->raw[sdp->offset])) { sdp_skip_space(sdp); c = sdp->raw[sdp->offset++]; sdp_skip_space(sdp); if('=' != sdp->raw[sdp->offset++]) goto parse_failed; sdp_skip_space(sdp); switch(c) { case 'v': r = sdp_parse_version(sdp); break; case 'o': r = sdp_parse_origin(sdp); break; case 's': r = sdp_parse_session(sdp); break; case 'i': r = sdp_parse_information(sdp); break; case 'u': r = sdp_parse_uri(sdp); break; case 'e': r = sdp_parse_email(sdp); break; case 'p': r = sdp_parse_phone(sdp); break; case 'c': r = sdp_parse_connection(sdp); break; case 'b': r = sdp_parse_bandwidth(sdp); break; case 't': r = sdp_parse_timing(sdp); break; case 'r': r = sdp_parse_repeat(sdp); break; case 'z': r = sdp_parse_timezone(sdp); break; case 'k': r = sdp_parse_encryption(sdp); break; case 'a': r = sdp_parse_attribute(sdp); break; case 'm': r = sdp_parse_media(sdp); break; case 'y': r = sdp_parse_gb28181_ssrc(sdp); break; default: // unknown sdp sdp_token_word(sdp, "\r\n"); r = sdp_token_crlf(sdp); break; } if(0 != r) goto parse_failed; } return sdp; parse_failed: sdp_destroy(sdp); return NULL; } int sdp_version_get(struct sdp_t* sdp) { return sdp->v; } //void sdp_version_set(struct sdp_t* sdp, int version) //{ // struct sdp_t *sdp; // sdp = (struct sdp_t*)sdp; // sdp->v = version; //} int sdp_origin_get(struct sdp_t* sdp, const char **username, const char** session, const char** version, const char** network, const char** addrtype, const char** address) { if(sdp->o.username && sdp->o.session && sdp->o.session_version && sdp->o.c.network && sdp->o.c.addrtype && sdp->o.c.address) { *username = sdp->o.username; *session = sdp->o.session; *version = sdp->o.session_version; *network = sdp->o.c.network; *addrtype = sdp->o.c.addrtype; *address = sdp->o.c.address; return 0; } return -1; } int sdp_origin_get_network(struct sdp_t* sdp) { if(0 == strcasecmp("IN", sdp->o.c.network)) return SDP_C_NETWORK_IN; return SDP_C_NETWORK_UNKNOWN; } int sdp_origin_get_addrtype(struct sdp_t* sdp) { if(0 == strcasecmp("IP4", sdp->o.c.addrtype)) return SDP_C_ADDRESS_IP4; if(0 == strcasecmp("IP6", sdp->o.c.addrtype)) return SDP_C_ADDRESS_IP6; return SDP_C_ADDRESS_UNKNOWN; } const char* sdp_session_get_name(struct sdp_t* sdp) { return sdp->s; } const char* sdp_session_get_information(struct sdp_t* sdp) { return sdp->i; } const char* sdp_uri_get(struct sdp_t* sdp) { return sdp->u; } int sdp_email_count(struct sdp_t* sdp) { return sdp->e.count; } const char* sdp_email_get(struct sdp_t* sdp, int idx) { if(idx >= sdp->e.count || idx < 0) return NULL; return idx < N_EMAIL ? sdp->e.emails[idx].email : sdp->e.ptr[idx - N_EMAIL].email; } int sdp_phone_count(struct sdp_t* sdp) { return sdp->p.count; } const char* sdp_phone_get(struct sdp_t* sdp, int idx) { if(idx >= sdp->p.count || idx < 0) return NULL; return idx < N_PHONE ? sdp->p.phones[idx].phone : sdp->p.ptr[idx - N_PHONE].phone; } int sdp_connection_get(struct sdp_t* sdp, const char** network, const char** addrtype, const char** address) { if(sdp->c.network && sdp->c.addrtype && sdp->c.address) { *network = sdp->c.network; *addrtype = sdp->c.addrtype; *address = sdp->c.address; return 0; } return -1; } int sdp_connection_get_address(struct sdp_t* sdp, char* ip, int bytes) { const char* p; if(sdp->c.address && bytes > 0) { p = sdp->c.address; while(*p && '/' != *p && bytes > 1) { *ip++ = *p++; --bytes; } if(0 == *p || '/' == *p) { *ip = '\0'; return 0; } } return -1; } int sdp_connection_get_network(struct sdp_t* sdp) { if(0 == strcasecmp("IN", sdp->c.network)) return SDP_C_NETWORK_IN; return SDP_C_NETWORK_UNKNOWN; } int sdp_connection_get_addrtype(struct sdp_t* sdp) { if(0 == strcasecmp("IP4", sdp->c.addrtype)) return SDP_C_ADDRESS_IP4; if(0 == strcasecmp("IP6", sdp->c.addrtype)) return SDP_C_ADDRESS_IP6; return SDP_C_ADDRESS_UNKNOWN; } int sdp_bandwidth_count(struct sdp_t* sdp) { return sdp->b.count; } const char* sdp_bandwidth_get_type(struct sdp_t* sdp, int idx) { if(idx >= (int)sdp->b.count || idx < 0) return NULL; return idx < N_BANDWIDTH ? sdp->b.bandwidths[idx].bwtype : sdp->b.ptr[idx - N_BANDWIDTH].bwtype; } int sdp_bandwidth_get_value(struct sdp_t* sdp, int idx) { const char* b; if(idx >= (int)sdp->b.count || idx < 0) return -1; b = idx < N_BANDWIDTH ? sdp->b.bandwidths[idx].bandwidth : sdp->b.ptr[idx - N_BANDWIDTH].bandwidth; return atoi(b); } int sdp_timing_count(struct sdp_t* sdp) { return sdp->t.count; } int sdp_timing_get(sdp_t* sdp, int idx, const char** start, const char** stop) { struct sdp_timing* t; t = sdp_get_timing(sdp, idx); if (NULL == t) return -1; *start = t->start; *stop = t->stop; return 0; } int sdp_timing_repeat_count(sdp_t* sdp, int idx) { struct sdp_timing* t; t = sdp_get_timing(sdp, idx); return t ? t->r.count : -1; } int sdp_timing_timezone_count(sdp_t* sdp, int idx) { struct sdp_timing* t; t = sdp_get_timing(sdp, idx); return t ? t->z.count : -1; } int sdp_media_count(struct sdp_t* sdp) { return sdp->m.count; } const char* sdp_media_type(struct sdp_t* sdp, int media) { struct sdp_media *m; m = sdp_get_media(sdp, media); return m ? m->media : NULL; } int sdp_media_port(struct sdp_t* sdp, int media, int port[], int num) { int i, n; const char* p; struct sdp_media *m; m = sdp_get_media(sdp, media); if (!m || !port) return -1; p = strchr(m->port, '/'); port[0] = atoi(m->port); n = atoi(p ? p + 1 : "1"); for (i = 1; i < num && i < n; i++) port[i] = port[0] + i; return i; } const char* sdp_media_proto(struct sdp_t* sdp, int media) { struct sdp_media *m; m = sdp_get_media(sdp, media); return m ? m->proto : NULL; } // rfc 4566 5.14. Media Descriptions ("m=") // (p24) If the sub-field is "udp" the sub-fields MUST // reference a media type describing the format under the "audio", // "video", "text", "application", or "message" top-level media types. static inline int sdp_media_format_value(const char* format) { switch(format[0]) { case 'a': return ('u' == format[1]) ? SDP_M_MEDIA_AUDIO : SDP_M_MEDIA_APPLICATION; case 'v': return SDP_M_MEDIA_VIDEO; case 't': return SDP_M_MEDIA_TEXT; case 'm': return SDP_M_MEDIA_MESSAGE; case 'w': return 0 == strcmp("webrtc-datachannel", format) ? SDP_M_MEDIA_APPLICATION : -1; default: return atoi(format); } //if(0 == strcasecmp("video", format)) // return SDP_M_MEDIA_VIDEO; //else if(0 == strcasecmp("audio", format)) // return SDP_M_MEDIA_AUDIO; //else if(0 == strcasecmp("text", format)) // return SDP_M_MEDIA_TEXT; //else if(0 == strcasecmp("application", format)) // return SDP_M_MEDIA_APPLICATION; //else if(0 == strcasecmp("message", format)) // return SDP_M_MEDIA_MESSAGE; //else // return atoi(format); } int sdp_media_formats(struct sdp_t* sdp, int media, int *formats, int count) { int i; struct sdp_media *m; m = sdp_get_media(sdp, media); if(!m) return -1; for(i = 0; i < count && i < m->fmt.count; i++) { if(i < N_MEDIA_FORMAT) formats[i] = sdp_media_format_value(m->fmt.formats[i]); else formats[i] = sdp_media_format_value(m->fmt.ptr[i-N_MEDIA_FORMAT]); } return (int)m->fmt.count; } int sdp_media_get_connection(sdp_t* sdp, int media, const char** network, const char** addrtype, const char** address) { struct sdp_media *m; struct sdp_connection *conn; m = sdp_get_media(sdp, media); if(m && m->c.count > 0) conn = &m->c.connections[0]; else conn = &sdp->c; if(conn && conn->network && conn->addrtype && conn->address) { *network = conn->network; *addrtype = conn->addrtype; *address = conn->address; return 0; } return -1; } int sdp_media_get_connection_address(struct sdp_t* sdp, int media, char* ip, int bytes) { const char* p; struct sdp_media *m; struct sdp_connection *conn; m = sdp_get_media(sdp, media); if(m && m->c.count > 0) conn = &m->c.connections[0]; else conn = &sdp->c; if(conn->address && bytes > 0) { p = conn->address; while(*p && '/' != *p && bytes > 1) { *ip++ = *p++; --bytes; } if(0 == *p || '/' == *p) { *ip = '\0'; return 0; } } return -1; } int sdp_media_get_connection_network(struct sdp_t* sdp, int media) { struct sdp_media *m; struct sdp_connection *conn; m = sdp_get_media(sdp, media); if(m && m->c.count > 0) conn = &m->c.connections[0]; else conn = &sdp->c; if(conn->network) { if(0 == strcasecmp("IN", conn->network)) return SDP_C_NETWORK_IN; } return SDP_C_NETWORK_UNKNOWN; } int sdp_media_get_connection_addrtype(struct sdp_t* sdp, int media) { struct sdp_media *m; struct sdp_connection *conn; m = sdp_get_media(sdp, media); if(m && m->c.count > 0) conn = &m->c.connections[0]; else conn = &sdp->c; if(conn->addrtype) { if(0 == strcasecmp("IP4", conn->addrtype)) return SDP_C_ADDRESS_IP4; if(0 == strcasecmp("IP6", conn->addrtype)) return SDP_C_ADDRESS_IP6; } return SDP_C_ADDRESS_UNKNOWN; } int sdp_media_bandwidth_count(struct sdp_t* sdp, int media) { struct sdp_media *m; m = sdp_get_media(sdp, media); if(!m) return 0; return m->b.count; } const char* sdp_media_bandwidth_get_type(struct sdp_t* sdp, int media, int idx) { struct sdp_media *m; m = sdp_get_media(sdp, media); if(!m) return NULL; if(idx >= (int)m->b.count || idx < 0) return NULL; return idx < N_BANDWIDTH ? m->b.bandwidths[idx].bwtype : m->b.ptr[idx - N_BANDWIDTH].bwtype; } int sdp_media_bandwidth_get_value(struct sdp_t* sdp, int media, int idx) { const char* b; struct sdp_media *m; m = sdp_get_media(sdp, media); if(!m) return -1; if(idx >= (int)m->b.count || idx < 0) return -1; b = idx < N_BANDWIDTH ? m->b.bandwidths[idx].bandwidth : m->b.ptr[idx - N_BANDWIDTH].bandwidth; return atoi(b); } int sdp_attribute_count(struct sdp_t* sdp) { return sdp->a.count; } int sdp_attribute_get(struct sdp_t* sdp, int idx, const char** name, const char** value) { struct sdp_attribute *attr; if(idx < 0 || idx > sdp->a.count) return -1; // not found if(idx < N_ATTRIBUTE) attr = sdp->a.attrs + idx; else attr = sdp->a.ptr + idx - N_ATTRIBUTE; *name = attr->name; *value = attr->value; return 0; } static const char* sdp_attribute_find_impl(const struct attributes* a, const char* name) { int i; const struct sdp_attribute* attr; for (i = 0; name && i < a->count; i++) { if (i < N_ATTRIBUTE) attr = a->attrs + i; else attr = a->ptr + i - N_ATTRIBUTE; if (attr->name && 0 == strcmp(attr->name, name)) return attr->value; } return NULL; } static int sdp_attribute_list_impl(const struct attributes* a, const char* name, void (*onattr)(void* param, const char* name, const char* value), void* param) { int i; const struct sdp_attribute* attr; for (i = 0; i < a->count; i++) { if (i < N_ATTRIBUTE) attr = a->attrs + i; else attr = a->ptr + i - N_ATTRIBUTE; if (!name || (attr->name && 0 == strcmp(attr->name, name))) onattr(param, attr->name, attr->value); } return 0; } const char* sdp_media_attribute_find(struct sdp_t* sdp, int media, const char* name) { struct sdp_media* m; m = sdp_get_media(sdp, media); return m ? sdp_attribute_find_impl(&m->a, name) : NULL; } int sdp_media_attribute_list(struct sdp_t* sdp, int media, const char* name, void (*onattr)(void* param, const char* name, const char* value), void* param) { struct sdp_media* m; m = sdp_get_media(sdp, media); return m ? sdp_attribute_list_impl(&m->a, name, onattr, param) : -1; } const char* sdp_attribute_find(struct sdp_t* sdp, const char* name) { return sdp_attribute_find_impl(&sdp->a, name); } int sdp_attribute_list(struct sdp_t* sdp, const char* name, void (*onattr)(void* param, const char* name, const char* value), void* param) { return sdp_attribute_list_impl(&sdp->a, name, onattr, param); } static int sdp_attribute_mode(struct attributes* a) { static const int values[] = { SDP_A_SENDRECV, SDP_A_SENDONLY, SDP_A_RECVONLY, SDP_A_INACTIVE }; static const char* strings[] = { "sendrecv", "sendonly", "recvonly", "inactive" }; int i, j; const struct sdp_attribute *attr; for (i = 0; i < a->count; i++) { if (i < N_ATTRIBUTE) attr = a->attrs + i; else attr = a->ptr + i - N_ATTRIBUTE; for (j = 0; j < sizeof(values) / sizeof(values[0]); j++) { if (attr->name && 0 == strcmp(strings[j], attr->name)) return values[j]; } } return -1; } int sdp_media_mode(struct sdp_t* sdp, int media) { int mode; struct sdp_media *m; m = sdp_get_media(sdp, media); mode = m ? sdp_attribute_mode(&m->a) : -1; mode = -1 == mode ? sdp_attribute_mode(&sdp->a) : mode; return -1 == mode ? SDP_A_SENDRECV : mode; // default SDP_A_SENDRECV }