/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@ingroup sdp_parser * @CFILE sdp_parse.c * @brief Simple SDP parser interface. * * @author Pekka Pessi * @author Kai Vehmanen * * @date Created: Fri Feb 18 10:25:08 2000 ppessi * * @sa @RFC4566, @RFC2327. */ #include "config.h" #include #include #include "sofia-sip/sdp.h" #include #include #include #include #include #include #include /** @typedef struct sdp_parser_s sdp_parser_t; * * SDP parser handle. * * The SDP parser handle returned by sdp_parse() contains either * a successfully parsed SDP session #sdp_session_t or an error message. * If sdp_session() returns non-NULL, parsing was successful. * * @sa #sdp_session_t, sdp_parse(), sdp_session(), sdp_parsing_error(), * sdp_sanity_check(), sdp_parser_home(), sdp_parser_free(), @RFC4566, * @RFC2327. */ struct sdp_parser_s { su_home_t pr_home[1]; union { char pru_error[128]; sdp_session_t pru_session[1]; } pr_output; char *pr_message; sdp_mode_t pr_session_mode; unsigned pr_ok : 1; unsigned pr_strict : 1; unsigned pr_anynet : 1; unsigned pr_mode_0000 : 1; unsigned pr_mode_manual : 1; unsigned pr_insane : 1; unsigned pr_c_missing : 1; unsigned pr_config : 1; }; #define is_posdigit(c) ((c) >= '1' && (c) <= '9') #define is_digit(c) ((c) >= '0' && (c) <= '9') #define is_space(c) ((c) == ' ') #define is_tab(c) ((c) == '\t') #define pr_error pr_output.pru_error #define pr_session pr_output.pru_session #ifdef _MSC_VER #undef STRICT #endif #define STRICT(pr) (pr->pr_strict) /* Static parser object used when running out of memory */ static const struct sdp_parser_s no_mem_error = { { SU_HOME_INIT(no_mem_error) }, { "sdp: not enough memory" } }; /* Internal prototypes */ static void parse_message(sdp_parser_t *p); static int parsing_error(sdp_parser_t *p, char const *fmt, ...); /** Parse an SDP message. * * The function sdp_parse() parses an SDP message @a msg of size @a * msgsize. Parsing is done according to the given @a flags. The SDP message * may not contain a NUL. * * The parsing result is stored to an #sdp_session_t structure. * * @param home memory home * @param msg pointer to message * @param msgsize size of the message (excluding final NUL, if any) * @param flags flags affecting the parsing. * * The following flags are used by parser: * * @li #sdp_f_strict Parser should accept only messages conforming strictly * to the specification. * @li #sdp_f_anynet Parser accepts unknown network or address types. * @li #sdp_f_insane Do not run sanity check. * @li #sdp_f_c_missing Sanity check does not require c= for each m= line * @li #sdp_f_mode_0000 Parser regards "c=IN IP4 0.0.0.0" as "a=inactive" * (likewise with c=IN IP6 ::) * @li #sdp_f_mode_manual Do not generate or parse SDP mode * @li #sdp_f_config Parse config files (any line can be missing) * * @return * Always a valid parser handle. * * @todo Parser accepts some non-conforming SDP even with #sdp_f_strict. * * @sa sdp_session(), sdp_parsing_error(), sdp_sanity_check(), * sdp_parser_home(), sdp_parser_free(), @RFC4566, @RFC2327. */ sdp_parser_t * sdp_parse(su_home_t *home, char const msg[], issize_t msgsize, int flags) { sdp_parser_t *p; char *b; size_t len; if (msgsize == -1 || msg == NULL) { p = su_home_clone(home, sizeof(*p)); if (p) parsing_error(p, "invalid input message"); else p = (sdp_parser_t*)&no_mem_error; return p; } if (msgsize == -1 && msg) len = strlen(msg); else len = msgsize; if (len > ISSIZE_MAX) len = ISSIZE_MAX; p = su_home_clone(home, sizeof(*p) + len + 1); if (p) { b = strncpy((void *)(p + 1), msg, len); b[len] = 0; p->pr_message = b; p->pr_strict = (flags & sdp_f_strict) != 0; p->pr_anynet = (flags & sdp_f_anynet) != 0; p->pr_mode_0000 = (flags & sdp_f_mode_0000) != 0; p->pr_insane = (flags & sdp_f_insane) != 0; p->pr_c_missing = (flags & sdp_f_c_missing) != 0; if (flags & sdp_f_config) p->pr_c_missing = 1, p->pr_config = 1; p->pr_mode_manual = (flags & sdp_f_mode_manual) != 0; p->pr_session_mode = sdp_sendrecv; parse_message(p); return p; } if (p) sdp_parser_free(p); return (sdp_parser_t*)&no_mem_error; } /** Obtain memory home used by parser */ su_home_t *sdp_parser_home(sdp_parser_t *parser) { if (parser != &no_mem_error) return parser->pr_home; else return NULL; } /** Retrieve an SDP session structure. * * The function sdp_session() returns a pointer to the SDP session * structure associated with the SDP parser @a p. The pointer and all the * data in the structure are valid until sdp_parser_free() is called. * * @param p SDP parser * * @return * The function sdp_session() returns a pointer to an parsed SDP message * or NULL, if an error has occurred. */ sdp_session_t * sdp_session(sdp_parser_t *p) { return p && p->pr_ok ? p->pr_session : NULL; } /** Get a parsing error message. * * The function sdp_parsing_error() returns the error message associated * with an SDP parser @a p. * * @param p SDP parser * * @return * The function sdp_parsing_error() returns a C string describing parsing * error, or NULL if no error occurred. */ char const *sdp_parsing_error(sdp_parser_t *p) { return !p->pr_ok ? p->pr_error : NULL; } /** Free an SDP parser. * * The function sdp_parser_free() frees an SDP parser object along with * the memory blocks associated with it. * * @param p pointer to the SDP parser to be freed */ void sdp_parser_free(sdp_parser_t *p) { if (p && p != &no_mem_error) su_home_unref(p->pr_home); } /* ========================================================================= */ /* ========================================================================= * Private part */ /* Parsing tokens */ #define SPACE " " #define TAB "\011" #define CRLF "\015\012" #define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" #define DIGIT "0123456789" #define TOKEN ALPHA DIGIT "-!#$%&'*+.^_`{|}~" /* ========================================================================= */ /* Parsing functions */ static void post_session(sdp_parser_t *p, sdp_session_t *sdp); static void parse_origin(sdp_parser_t *p, char *r, sdp_origin_t **result); static void parse_subject(sdp_parser_t *p, char *r, sdp_text_t **result); static void parse_information(sdp_parser_t *p, char *r, sdp_text_t **result); static void parse_uri(sdp_parser_t *p, char *r, sdp_text_t **result); static void parse_email(sdp_parser_t *p, char *r, sdp_list_t **result); static void parse_phone(sdp_parser_t *p, char *r, sdp_list_t **result); static void parse_connection(sdp_parser_t *p, char *r, sdp_connection_t **result); static void parse_bandwidth(sdp_parser_t *p, char *r, sdp_bandwidth_t **result); static void parse_time(sdp_parser_t *p, char *r, sdp_time_t **result); static void parse_repeat(sdp_parser_t *p, char *r, sdp_repeat_t **result); static void parse_zone(sdp_parser_t *p, char *r, sdp_zone_t **result); static void parse_key(sdp_parser_t *p, char *r, sdp_key_t **result); static void parse_session_attr(sdp_parser_t *p, char *r, sdp_attribute_t **result); static void parse_media(sdp_parser_t *p, char *r, sdp_media_t **result); static void parse_payload(sdp_parser_t *p, char *r, sdp_rtpmap_t **result); static void parse_media_attr(sdp_parser_t *p, char *r, sdp_media_t *m, sdp_attribute_t **result); static int parse_rtpmap(sdp_parser_t *p, char *r, sdp_media_t *m); static int parse_fmtp(sdp_parser_t *p, char *r, sdp_media_t *m); static void parse_text_list(sdp_parser_t *p, char *r, sdp_list_t **result); static void parse_descs(sdp_parser_t *p, char *r, char *m, sdp_media_t **result); static int parse_ul(sdp_parser_t *p, char **r, unsigned long *result, unsigned long max_value); static int parse_ull(sdp_parser_t *p, char **r, uint64_t *result, uint64_t max_value); static void parse_alloc_error(sdp_parser_t *p, const char *typename); static char *next(char **message, const char *sep, const char *strip); static char *token(char **message, const char *sep, const char *legal, const char *strip); #if 0 static void check_mandatory(sdp_parser_t *p, sdp_session_t *sdp); #endif /* ------------------------------------------------------------------------- * Macro PARSE_ALLOC * * Description: * This macro declares a pointer (v) of given type (t). It then allocates * an structure of given type (t). If allocation was succesful, it assigns * the XX_size member with appropriate value. */ #define PARSE_ALLOC(p, t, v) \ t *v = su_salloc(p->pr_home, sizeof(*v)); \ if (!v && (parse_alloc_error(p, #t), 1)) return; /* ------------------------------------------------------------------------- * Macro PARSE_CHECK_REST * * Description: * This macro check if there is extra data at the end of field. */ #define PARSE_CHECK_REST(p, s, n)\ if (*s && (parsing_error(p, "extra data after %s (\"%.04s\")", n, s), 1)) \ return /* ------------------------------------------------------------------------- * Function parse_message() - parse an SDP message * * Description: * This function parses an SDP message, which is copied into the * p->pr_message. The p->pr_message is modified during the parsing, * and parts of it are returned in p->pr_session. * * Parameters: * p - pointer to SDP parser object */ static void parse_message(sdp_parser_t *p) { /* announcement = proto-version origin-field session-name-field information-field uri-field email-fields phone-fields connection-field bandwidth-fields time-fields key-field attribute-fields media-descriptions */ sdp_session_t *sdp = p->pr_session; char *record, *rest; char const *strip; char *message = p->pr_message; char field = '\0'; sdp_list_t **emails = &sdp->sdp_emails; sdp_list_t **phones = &sdp->sdp_phones; sdp_bandwidth_t **bandwidths = &sdp->sdp_bandwidths; sdp_time_t **times = &sdp->sdp_time; sdp_repeat_t **repeats = NULL; sdp_zone_t **zones = NULL; sdp_attribute_t **attributes = &sdp->sdp_attributes; if (!STRICT(p)) strip = SPACE TAB; /* skip initial whitespace */ else strip = ""; p->pr_ok = 1; p->pr_session->sdp_size = sizeof(p->pr_session); /* Require that version comes first */ record = next(&message, CRLF, strip); if (!su_strmatch(record, "v=0")) { if (!p->pr_config || !record || record[1] != '=') { parsing_error(p, "bad SDP message"); return; } } else { record = next(&message, CRLF, strip); } /* XXX - the lines in SDP are in certain order, which we don't check here. For stricter parsing we might want to parse o= and s= next. */ for (; record && p->pr_ok; record = next(&message, CRLF, strip)) { field = record[0]; rest = record + 2; rest += strspn(rest, strip); if (record[1] != '=') { parsing_error(p, "bad line \"%s\"", record); return; } switch (field) { case 'o': parse_origin(p, rest, &sdp->sdp_origin); break; case 's': parse_subject(p, rest, &sdp->sdp_subject); break; case 'i': parse_information(p, rest, &sdp->sdp_information); break; case 'u': parse_uri(p, rest, &sdp->sdp_uri); break; case 'e': parse_email(p, rest, emails); emails = &(*emails)->l_next; break; case 'p': parse_phone(p, rest, phones); phones = &(*phones)->l_next; break; case 'c': parse_connection(p, rest, &sdp->sdp_connection); break; case 'b': parse_bandwidth(p, rest, bandwidths); bandwidths = &(*bandwidths)->b_next; break; case 't': parse_time(p, rest, times); repeats = &(*times)->t_repeat; zones = &(*times)->t_zone; times = &(*times)->t_next; break; case 'r': if (repeats) parse_repeat(p, rest, repeats); else parsing_error(p, "repeat field without time field"); break; case 'z': if (zones) parse_zone(p, rest, zones), zones = NULL; else parsing_error(p, "zone field without time field"); break; case 'k': parse_key(p, rest, &sdp->sdp_key); break; case 'a': parse_session_attr(p, rest, attributes); if (*attributes) attributes = &(*attributes)->a_next; break; case 'm': parse_descs(p, record, message, &sdp->sdp_media); post_session(p, sdp); return; default: parsing_error(p, "unknown field \"%s\"", record); return; } } post_session(p, sdp); } #ifdef SOFIA_AUTO_CORRECT_INADDR_ANY int sdp_connection_is_inaddr_any(sdp_connection_t const *c) { return c && c->c_nettype == sdp_net_in && ((c->c_addrtype == sdp_addr_ip4 && su_strmatch(c->c_address, "0.0.0.0")) || (c->c_addrtype == sdp_addr_ip6 && su_strmatch(c->c_address, "::"))); } #endif /**Postprocess session description. * * Postprocessing includes setting the session backpointer for each media, * doing sanity checks and setting rejected and mode flags. */ static void post_session(sdp_parser_t *p, sdp_session_t *sdp) { sdp_media_t *m; #ifdef SOFIA_AUTO_CORRECT_INADDR_ANY sdp_connection_t const *c; #endif if (!p->pr_ok) return; /* Set session back-pointer */ for (m = sdp->sdp_media; m; m = m->m_next) { m->m_session = sdp; } if (p->pr_config) { if (sdp->sdp_version[0] != 0) parsing_error(p, "Incorrect version"); return; } /* Go through all media and set mode */ for (m = sdp->sdp_media; m; m = m->m_next) { if (m->m_port == 0) { m->m_mode = sdp_inactive; m->m_rejected = 1; continue; } #ifdef SOFIA_AUTO_CORRECT_INADDR_ANY c = sdp_media_connections(m); if (p->pr_mode_0000 && sdp_connection_is_inaddr_any(c)) { /* Reset recvonly flag */ m->m_mode &= ~sdp_recvonly; } #endif } if (p->pr_insane) return; /* Verify that all mandatory fields are present */ if (sdp_sanity_check(p) < 0) return; } /** Validates that all mandatory fields exist * * Checks that all necessary fields (v=, o=) exists in the parsed sdp. If * strict, check that all mandatory fields (c=, o=, s=, t=) are present. * This function also goes through all media, marks rejected media as such, * and updates the mode accordingly. * * @retval 0 if parsed SDP description is valid * @retval -1 if some SDP line is missing * @retval -2 if c= line is missing */ int sdp_sanity_check(sdp_parser_t *p) { sdp_session_t *sdp = p->pr_session; sdp_media_t *m; if (!p || !p->pr_ok) return -1; else if (sdp->sdp_version[0] != 0) return parsing_error(p, "Incorrect version"); else if (!sdp->sdp_origin) return parsing_error(p, "No o= present"); else if (p->pr_strict && !sdp->sdp_subject) return parsing_error(p, "No s= present"); else if (p->pr_strict && !sdp->sdp_time) return parsing_error(p, "No t= present"); /* If there is no session level c= check that one exists for all media */ /* c= line may be missing if this is a RTSP description */ if (!p->pr_c_missing && !sdp->sdp_connection) { for (m = sdp->sdp_media ; m ; m = m->m_next) { if (!m->m_connections && !m->m_rejected) { parsing_error(p, "No c= on either session level or all mediums"); return -2; } } } return 0; } #if 0 /** * Parse a "v=" field * * The function parser_version() parses the SDP version field. * * @param p pointer to SDP parser object * @param r pointer to record data * @param result pointer to which parsed record is assigned */ static void parse_version(sdp_parser_t *p, char *r, sdp_version_t *result) { /* proto-version = "v=" 1*DIGIT CRLF ;[RFC2327] describes version 0 */ if (parse_ul(p, &r, result, 0)) parsing_error(p, "version \"%s\" is invalid", r); else if (*result > 0) parsing_error(p, "unknown version v=%s", r); } #endif /* ------------------------------------------------------------------------- * Function parse_origin() - parse an "o=" field * * Description: * This function parses an SDP origin field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_origin(sdp_parser_t *p, char *r, sdp_origin_t **result) { /* origin-field = "o=" username space sess-id space sess-version space nettype space addrtype space addr CRLF username = safe ;pretty wide definition, but doesn't include space sess-id = 1*(DIGIT) ;should be unique for this originating username/host sess-version = 1*(DIGIT) ;0 is a new session */ PARSE_ALLOC(p, sdp_origin_t, o); *result = o; o->o_username = token(&r, SPACE TAB, NULL, SPACE TAB); if (!o->o_username) { parsing_error(p, "invalid username"); return; } if (parse_ull(p, &r, &o->o_id, 0)) { parsing_error(p, "invalid session id"); return; } if (parse_ull(p, &r, &o->o_version, 0)) { parsing_error(p, "invalid session version"); return; } parse_connection(p, r, &o->o_address); } /* ------------------------------------------------------------------------- * Function parse_subject() - parse an "s=" field * * Description: * This function parses an SDP subject field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_subject(sdp_parser_t *p, char *r, sdp_text_t **result) { /* session-name-field = "s=" text CRLF text = byte-string */ *result = r; } /* ------------------------------------------------------------------------- * Function parse_information() - parse an "i=" field * * Description: * This function parses an SDP information field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_information(sdp_parser_t *p, char *r, sdp_text_t **result) { /* information-field = ["i=" text CRLF] */ *result = r; } /* ------------------------------------------------------------------------- * Function parse_uri() - parse an "u=" field * * Description: * This function parses an SDP URI field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_uri(sdp_parser_t *p, char *r, sdp_text_t **result) { /* uri-field = ["u=" uri CRLF] uri= ;defined in RFC1630 */ /* XXX - no syntax checking here */ *result = r; } /* ------------------------------------------------------------------------- * Function parse_email() - parse an "e=" field * * Description: * This function parses an SDP email field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_email(sdp_parser_t *p, char *r, sdp_list_t **result) { /* email-fields = *("e=" email-address CRLF) email-address = email | email "(" email-safe ")" | email-safe "<" email ">" email = ;defined in RFC822 */ parse_text_list(p, r, result); } /* ------------------------------------------------------------------------- * Function parse_phone() - parse an "p=" field * * Description: * This function parses an SDP phone field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_phone(sdp_parser_t *p, char *r, sdp_list_t **result) { /* phone-fields = *("p=" phone-number CRLF) phone-number = phone | phone "(" email-safe ")" | email-safe "<" phone ">" phone = "+" POS-DIGIT 1*(space | "-" | DIGIT) ;there must be a space or hyphen between the ;international code and the rest of the number. */ parse_text_list(p, r, result); } /* ------------------------------------------------------------------------- * Function parse_connection() - parse an "c=" field * * Description: * This function parses an SDP connection field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_connection(sdp_parser_t *p, char *r, sdp_connection_t **result) { /* connection-field = ["c=" nettype space addrtype space connection-address CRLF] ;a connection field must be present ;in every media description or at the ;session-level nettype = "IN" ;list to be extended addrtype = "IP4" | "IP6" ;list to be extended connection-address = multicast-address | addr multicast-address = 3*(decimal-uchar ".") decimal-uchar "/" ttl [ "/" integer ] ;multicast addresses may be in the range ;224.0.0.0 to 239.255.255.255 ttl = decimal-uchar addr = FQDN | unicast-address FQDN = 4*(alpha-numeric|"-"|".") ;fully qualified domain name as specified in RFC1035 unicast-address = IP4-address | IP6-address IP4-address = b1 "." decimal-uchar "." decimal-uchar "." b4 b1 = decimal-uchar ;less than "224"; not "0" or "127" b4 = decimal-uchar ;not "0" IP6-address = ;to be defined */ PARSE_ALLOC(p, sdp_connection_t, c); *result = c; if (su_casenmatch(r, "IN", 2)) { char *s; /* nettype is internet */ c->c_nettype = sdp_net_in; s = token(&r, SPACE TAB, NULL, NULL); /* addrtype */ s = token(&r, SPACE TAB, NULL, NULL); if (su_casematch(s, "IP4")) c->c_addrtype = sdp_addr_ip4; else if (su_casematch(s, "IP6")) c->c_addrtype = sdp_addr_ip6; else { parsing_error(p, "unknown IN address type: %s", s); return; } /* address */ s = next(&r, SPACE TAB, SPACE TAB); c->c_address = s; if (!s || !*s) { parsing_error(p, "invalid address"); return; } /* ttl */ s = strchr(s, '/'); if (s) { unsigned long value; *s++ = 0; if (parse_ul(p, &s, &value, 256) || (*s && *s != '/')) { parsing_error(p, "invalid ttl"); return; } c->c_ttl = value; c->c_mcast = 1; /* multiple groups */ value = 1; if (*s++ == '/') if (parse_ul(p, &s, &value, 0) || *s) { parsing_error(p, "invalid number of multicast groups"); return; } c->c_groups = value; } else c->c_groups = 1; } else if (p->pr_anynet) { c->c_nettype = sdp_net_x; c->c_addrtype = sdp_addr_x; c->c_address = r; c->c_ttl = 0; c->c_groups = 1; } else parsing_error(p, "invalid address"); } /* ------------------------------------------------------------------------- * Function parse_bandwidth() - parse an "b=" field * * Description: * This function parses an SDP bandwidth field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_bandwidth(sdp_parser_t *p, char *r, sdp_bandwidth_t **result) { /* bandwidth-fields = *("b=" bwtype ":" bandwidth CRLF) bwtype = token bandwidth = 1*(DIGIT) */ /* NOTE: bwtype can also be like X-barf */ sdp_bandwidth_e modifier; char *name; unsigned long value; name = token(&r, ":", TOKEN, SPACE TAB); if (name == NULL || parse_ul(p, &r, &value, 0)) { parsing_error(p, "invalid bandwidth"); return; } if (su_casematch(name, "CT")) modifier = sdp_bw_ct, name = "CT"; else if (su_casematch(name, "TIAS") == 1) modifier = sdp_bw_tias, name = "TIAS"; else if (su_casematch(name, "AS") == 1) modifier = sdp_bw_as, name = "AS"; else modifier = sdp_bw_x, name = "BW-X"; if (STRICT(p)) PARSE_CHECK_REST(p, r, "b"); { PARSE_ALLOC(p, sdp_bandwidth_t, b); *result = b; b->b_modifier = modifier; b->b_modifier_name = name; b->b_value = value; } } /* ------------------------------------------------------------------------- * Function parse_time() - parse an "t=" field * * Description: * This function parses an SDP time field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_time(sdp_parser_t *p, char *r, sdp_time_t **result) { /* time-fields = 1*( "t=" start-time SP stop-time *(CRLF repeat-fields) CRLF) [zone-adjustments CRLF] start-time = time / "0" stop-time = time / "0" time = POS-DIGIT 9*DIGIT ; Decimal representation of NTP time in ; seconds since 1900. The representation ; of NTP time is an unbounded length field ; containing at least 10 digits. Unlike the ; 64-bit representation used elsewhere, time ; in SDP does not wrap in the year 2036. */ PARSE_ALLOC(p, sdp_time_t, t); *result = t; if (parse_ul(p, &r, &t->t_start, 0) || parse_ul(p, &r, &t->t_stop, 0)) parsing_error(p, "invalid time"); else if (STRICT(p)) { PARSE_CHECK_REST(p, r, "t"); } } /** * Parse an "r=" field * * The function parse_repeat() parses an SDP repeat field. * * @param p pointer to SDP parser object * @param r pointer to record data * @param result pointer to which parsed record is assigned * */ static void parse_repeat(sdp_parser_t *p, char *d, sdp_repeat_t **result) { /* repeat-fields = %x72 "=" repeat-interval 2*(SP typed-time) repeat-interval = POS-DIGIT *DIGIT [fixed-len-time-unit] typed-time = 1*DIGIT [fixed-len-time-unit] fixed-len-time-unit = %x64 / %x68 / %x6d / %x73 ; "d" | "h" | "m" | "s" */ unsigned long tt, *interval; size_t i; int n, N; char *s; sdp_repeat_t *r; int strict = STRICT(p); /** Count number of intervals */ for (N = 0, s = d; *s; ) { if (!(is_posdigit(*s) || (!strict && (*s) == '0'))) break; do { s++; } while (is_digit(*s)); if (*s && strchr(strict ? "dhms" : "dhmsDHMS", *s)) s++; N++; if (!(i = strict ? is_space(*s) : strspn(s, SPACE TAB))) break; s += i; } PARSE_CHECK_REST(p, s, "r"); if (N < 2) { parsing_error(p, "invalid repeat"); return; } if (!(r = su_salloc(p->pr_home, offsetof(sdp_repeat_t, r_offsets[N - 1])))) { parse_alloc_error(p, "sdp_repeat_t"); return; } r->r_number_of_offsets = N - 2; r->r_offsets[N - 2] = 0; for (n = 0, interval = &r->r_interval; n < N; n++) { tt = strtoul(d, &d, 10); switch (*d) { case 'd': case 'D': tt *= 24; case 'h': case 'H': tt *= 60; case 'm': case 'M': tt *= 60; case 's': case 'S': d++; break; } interval[n] = tt; while (is_space(*d)) d++; } *result = r; } /* ------------------------------------------------------------------------- * Function parse_zone() - parse an "z=" field * * Description: * This function parses an SDP time zone field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned * */ static void parse_zone(sdp_parser_t *p, char *r, sdp_zone_t **result) { char *s; size_t i; int n, N; sdp_zone_t *z; /* zone-adjustments = time space ["-"] typed-time *(space time space ["-"] typed-time) */ /** Count number of timezones, check syntax */ for (N = 0, s = r; *s;) { if (!(is_posdigit(*s) || (!STRICT(p) && (*s) == '0'))) break; do { s++; } while (is_digit(*s)); if (!(i = STRICT(p) ? is_space(*s) : strspn(s, SPACE TAB))) break; s += i; if (!(*s == '-' || is_posdigit(*s) || (!STRICT(p) && (*s) == '0'))) break; do { s++; } while (is_digit(*s)); if (*s && strchr("dhms", *s)) s++; N++; if (!(i = STRICT(p) ? is_space(*s) : strspn(s, SPACE TAB))) break; s += i; } PARSE_CHECK_REST(p, s, "z"); if (N < 1) { parsing_error(p, "invalid timezone"); return; } if (!(z = su_salloc(p->pr_home, offsetof(sdp_zone_t, z_adjustments[N])))) { parse_alloc_error(p, "sdp_zone_t"); return; } z->z_number_of_adjustments = N; for (n = 0; n < N; n++) { unsigned long at = strtoul(r, &r, 10); long offset = strtol(r, &r, 10); switch (*r) { case 'd': offset *= 24; case 'h': offset *= 60; case 'm': offset *= 60; case 's': r++; break; } z->z_adjustments[n].z_at = at; z->z_adjustments[n].z_offset = offset; } *result = z; } /* ------------------------------------------------------------------------- * Function parse_key() - parse an "k=" field * * Description: * This function parses an SDP key field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned * */ static void parse_key(sdp_parser_t *p, char *r, sdp_key_t **result) { char *s; /* key-field = ["k=" key-type CRLF] key-type = "prompt" | "clear:" key-data | "base64:" key-data | "uri:" uri key-data = email-safe | "~" | " */ s = token(&r, ":", TOKEN, SPACE TAB); if (!s) { parsing_error(p, "invalid key method"); return; } { PARSE_ALLOC(p, sdp_key_t, k); *result = k; /* These are defined as key-sensitive in RFC 4566 */ #define MATCH(s, tok) \ (STRICT(p) ? su_strmatch((s), (tok)) : su_casematch((s), (tok))) if (MATCH(s, "clear")) k->k_method = sdp_key_clear, k->k_method_name = "clear"; else if (MATCH(s, "base64")) k->k_method = sdp_key_base64, k->k_method_name = "base64"; else if (MATCH(s, "uri")) k->k_method = sdp_key_uri, k->k_method_name = "uri"; else if (MATCH(s, "prompt")) k->k_method = sdp_key_prompt, k->k_method_name = "prompt"; else if (!STRICT(p)) k->k_method = sdp_key_x, k->k_method_name = s; else { parsing_error(p, "invalid key method"); return; } k->k_material = r; } } /* ------------------------------------------------------------------------- * Function parse_session_attr() - parse a session "a=" field * * Description: * This function parses an SDP attribute field regarding whole session. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_session_attr(sdp_parser_t *p, char *r, sdp_attribute_t **result) { /* attribute-fields = *("a=" attribute CRLF) attribute = (att-field ":" att-value) / att-field att-field = token att-value = byte-string */ char *name = NULL, *value = NULL; if (!(name = token(&r, ":", TOKEN, SPACE TAB))) { parsing_error(p,"invalid attribute name"); return; } if (*r) value = r; else PARSE_CHECK_REST(p, r, "a"); if (su_casematch(name, "charset")) { p->pr_session->sdp_charset = value; return; } if (p->pr_mode_manual) ; else if (su_casematch(name, "inactive")) p->pr_session_mode = sdp_inactive; else if (su_casematch(name, "sendonly")) p->pr_session_mode = sdp_sendonly; else if (su_casematch(name, "recvonly")) p->pr_session_mode = sdp_recvonly; else if (su_casematch(name, "sendrecv")) p->pr_session_mode = sdp_sendrecv; { PARSE_ALLOC(p, sdp_attribute_t, a); *result = a; a->a_name = name; a->a_value = value; } } /* ------------------------------------------------------------------------- * Function parse_media() - parse an "m=" field * * Description: * This function parses an SDP media field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_media(sdp_parser_t *p, char *r, sdp_media_t **result) { /* media-descriptions = *( media-field information-field *(connection-field) bandwidth-fields key-field attribute-fields ) media-field = "m=" media space port ["/" integer] space proto 1*(space fmt) CRLF media = token ;typically "audio", "video", "application" ;or "data" fmt = token ;typically an RTP payload type for audio ;and video media proto = token *("/" token) ;typically "RTP/AVP" or "udp" for IP4 port = 1*(DIGIT) ;should in the range "1024" to "65535" inclusive */ char *s; unsigned long value; PARSE_ALLOC(p, sdp_media_t, m); *result = m; m->m_mode = sdp_sendrecv; s = token(&r, SPACE, TOKEN, NULL); if (!s) { parsing_error(p, "m= invalid media field"); return; } sdp_media_type(m, s); /* Accept m=* in configuration file */ if (p->pr_config && m->m_type == sdp_media_any) { r += strspn(r, SPACE TAB); if (r[0] == '\0') { m->m_proto = sdp_proto_any, m->m_proto_name = "*"; return; } } if (parse_ul(p, &r, &value, 0)) { parsing_error(p, "m= invalid port number"); return; } m->m_port = value; if (*r == '/') { r++; if (parse_ul(p, &r, &value, 0)) { parsing_error(p, "m= invalid port specification"); return; } m->m_number_of_ports = value; } s = token(&r, SPACE, "/" TOKEN, SPACE); if (s == NULL) { parsing_error(p, "m= missing protocol"); return; } if (!STRICT(p) && su_casematch(s, "RTP")) m->m_proto = sdp_proto_rtp, m->m_proto_name = "RTP/AVP"; else sdp_media_transport(m, s); /* RTP format list */ if (*r && sdp_media_has_rtp(m)) { parse_payload(p, r, &m->m_rtpmaps); return; } /* "normal" format list */ if (*r) { sdp_list_t **fmt = &m->m_format; while (r && *r) { PARSE_ALLOC(p, sdp_list_t, l); *fmt = l; l->l_text = token(&r, SPACE TAB, TOKEN, SPACE TAB); fmt = &l->l_next; } } } /** Set media type */ void sdp_media_type(sdp_media_t *m, char const *s) { if (su_strmatch(s, "*")) m->m_type = sdp_media_any, m->m_type_name = "*"; else if (su_casematch(s, "audio")) m->m_type = sdp_media_audio, m->m_type_name = "audio"; else if (su_casematch(s, "video")) m->m_type = sdp_media_video, m->m_type_name = "video"; else if (su_casematch(s, "application")) m->m_type = sdp_media_application, m->m_type_name = "application"; else if (su_casematch(s, "data")) m->m_type = sdp_media_data, m->m_type_name = "data"; else if (su_casematch(s, "control")) m->m_type = sdp_media_control, m->m_type_name = "control"; else if (su_casematch(s, "message")) m->m_type = sdp_media_message, m->m_type_name = "message"; else if (su_casematch(s, "image")) m->m_type = sdp_media_image, m->m_type_name = "image"; else if (su_casematch(s, "red")) m->m_type = sdp_media_red, m->m_type_name = "red"; else m->m_type = sdp_media_x, m->m_type_name = s; } /** Set transport protocol. * * Set the @m->m_proto to a well-known protocol type as * well as canonize case of @a m_proto_name. */ void sdp_media_transport(sdp_media_t *m, char const *s) { if (m == NULL || s == NULL) ; else if (su_strmatch(s, "*")) m->m_proto = sdp_proto_any, m->m_proto_name = "*"; else if (su_casematch(s, "RTP/AVP")) m->m_proto = sdp_proto_rtp, m->m_proto_name = "RTP/AVP"; else if (su_casematch(s, "RTP/SAVP")) m->m_proto = sdp_proto_srtp, m->m_proto_name = "RTP/SAVP"; else if (su_casematch(s, "RTP/SAVPF")) m->m_proto = sdp_proto_extended_srtp, m->m_proto_name = "RTP/SAVPF"; else if (su_casematch(s, "UDP/TLS/RTP/SAVPF")) m->m_proto = sdp_proto_extended_srtp, m->m_proto_name = "UDP/TLS/RTP/SAVPF"; else if (su_casematch(s, "RTP/AVPF")) m->m_proto = sdp_proto_extended_rtp, m->m_proto_name = "RTP/AVPF"; else if (su_casematch(s, "UDP/RTP/AVPF")) m->m_proto = sdp_proto_extended_rtp, m->m_proto_name = "UDP/RTP/AVPF"; else if (su_casematch(s, "udptl")) /* Lower case - be compatible with people living by T.38 examples */ m->m_proto = sdp_proto_udptl, m->m_proto_name = "udptl"; else if (su_casematch(s, "TCP/MSRP")) m->m_proto = sdp_proto_msrp, m->m_proto_name = "TCP/MSRP"; else if (su_casematch(s, "TCP/TLS/MSRP")) m->m_proto = sdp_proto_msrps, m->m_proto_name = "TCP/TLS/MSRP"; else if (su_casematch(s, "UDP")) m->m_proto = sdp_proto_udp, m->m_proto_name = "UDP"; else if (su_casematch(s, "TCP")) m->m_proto = sdp_proto_tcp, m->m_proto_name = "TCP"; else if (su_casematch(s, "TLS")) m->m_proto = sdp_proto_tls, m->m_proto_name = "TLS"; else m->m_proto = sdp_proto_x, m->m_proto_name = s; } /** Check if media uses RTP as its transport protocol. */ int sdp_media_has_rtp(sdp_media_t const *m) { return m && (m->m_proto == sdp_proto_rtp || m->m_proto == sdp_proto_srtp || m->m_proto == sdp_proto_extended_srtp || m->m_proto == sdp_proto_extended_rtp); } #define RTPMAP(pt, encoding, rate, params) \ { sizeof(sdp_rtpmap_t), NULL, encoding, rate, (char *)params, NULL, 1, pt, 0 } /* rtpmaps for well-known codecs */ static sdp_rtpmap_t const sdp_rtpmap_pcmu = RTPMAP(0, "PCMU", 8000, 0), sdp_rtpmap_1016 = RTPMAP(1, "1016", 8000, 0), sdp_rtpmap_g721 = RTPMAP(2, "G721", 8000, 0), sdp_rtpmap_gsm = RTPMAP(3, "GSM", 8000, 0), sdp_rtpmap_g723 = RTPMAP(4, "G723", 8000, 0), sdp_rtpmap_dvi4_8000 = RTPMAP(5, "DVI4", 8000, 0), sdp_rtpmap_dvi4_16000 = RTPMAP(6, "DVI4", 16000, 0), sdp_rtpmap_lpc = RTPMAP(7, "LPC", 8000, 0), sdp_rtpmap_pcma = RTPMAP(8, "PCMA", 8000, 0), sdp_rtpmap_g722 = RTPMAP(9, "G722", 8000, 0), sdp_rtpmap_l16_2 = RTPMAP(10, "L16", 44100, "2"), sdp_rtpmap_l16 = RTPMAP(11, "L16", 44100, 0), sdp_rtpmap_qcelp = RTPMAP(12, "QCELP", 8000, 0), sdp_rtpmap_cn = RTPMAP(13, "CN", 8000, 0), sdp_rtpmap_mpa = RTPMAP(14, "MPA", 90000, 0), sdp_rtpmap_g728 = RTPMAP(15, "G728", 8000, 0), sdp_rtpmap_dvi4_11025 = RTPMAP(16, "DVI4", 11025, 0), sdp_rtpmap_dvi4_22050 = RTPMAP(17, "DVI4", 22050, 0), sdp_rtpmap_g729 = RTPMAP(18, "G729", 8000, 0), sdp_rtpmap_reserved_cn = RTPMAP(19, "CN", 8000, 0), /* video codecs */ sdp_rtpmap_celb = RTPMAP(25, "CelB", 90000, 0), sdp_rtpmap_jpeg = RTPMAP(26, "JPEG", 90000, 0), sdp_rtpmap_nv = RTPMAP(28, "nv", 90000, 0), sdp_rtpmap_h261 = RTPMAP(31, "H261", 90000, 0), sdp_rtpmap_mpv = RTPMAP(32, "MPV", 90000, 0), sdp_rtpmap_mp2t = RTPMAP(33, "MP2T", 90000, 0), sdp_rtpmap_h263 = RTPMAP(34, "H263", 90000, 0); /** Table of rtpmap structures by payload type numbers. * * The table of reserved payload numbers is constructed from @RFC3551 * and @RFC1890. Note the clock rate of G722. * * Use sdp_rtpmap_dup() to copy these structures. */ sdp_rtpmap_t const * const sdp_rtpmap_well_known[128] = { &sdp_rtpmap_pcmu, /* 0 */ &sdp_rtpmap_1016, /* 1 */ &sdp_rtpmap_g721, /* 2 */ &sdp_rtpmap_gsm, /* 3 */ &sdp_rtpmap_g723, /* 4 */ &sdp_rtpmap_dvi4_8000, /* 5 */ &sdp_rtpmap_dvi4_16000, /* 6 */ &sdp_rtpmap_lpc, /* 7 */ &sdp_rtpmap_pcma, /* 8 */ &sdp_rtpmap_g722, /* 9 */ &sdp_rtpmap_l16_2, /* 10 */ &sdp_rtpmap_l16, /* 11 */ &sdp_rtpmap_qcelp, /* 12 */ &sdp_rtpmap_cn, /* 13 */ &sdp_rtpmap_mpa, /* 14 */ &sdp_rtpmap_g728, /* 15 */ &sdp_rtpmap_dvi4_11025, /* 16 */ &sdp_rtpmap_dvi4_22050, /* 17 */ &sdp_rtpmap_g729, /* 18 */ &sdp_rtpmap_reserved_cn, /* 19 */ NULL, /* 20 */ NULL, /* 21 */ NULL, /* 22 */ NULL, /* 23 */ NULL, /* 24 */ &sdp_rtpmap_celb, /* 25 */ &sdp_rtpmap_jpeg, /* 26 */ NULL, /* 27 */ &sdp_rtpmap_nv, /* 28 */ NULL, /* 29 */ NULL, /* 30 */ &sdp_rtpmap_h261, /* 31 */ &sdp_rtpmap_mpv, /* 32 */ &sdp_rtpmap_mp2t, /* 33 */ &sdp_rtpmap_h263, /* 34 */ NULL, }; /** * The function parse_payload() parses an RTP payload type list, and * creates an rtpmap structure for each payload type. * * @param p pointer to SDP parser object * @param r pointer to record data * @param result pointer to which parsed record is assigned */ static void parse_payload(sdp_parser_t *p, char *r, sdp_rtpmap_t **result) { while (*r) { unsigned long value; if (parse_ul(p, &r, &value, 128) == 0) { PARSE_ALLOC(p, sdp_rtpmap_t, rm); assert(0 <= value && value < 128); *result = rm; result = &rm->rm_next; if (sdp_rtpmap_well_known[value]) { *rm = *sdp_rtpmap_well_known[value]; } else { rm->rm_predef = 1; rm->rm_pt = value; rm->rm_encoding = ""; rm->rm_rate = 0; } } else if (p->pr_config && r[0] == '*' && (r[1] == ' ' || r[1] == '\0')) { PARSE_ALLOC(p, sdp_rtpmap_t, rm); *result = rm; result = &rm->rm_next; rm->rm_predef = 1; rm->rm_any = 1; rm->rm_encoding = "*"; rm->rm_rate = 0; return; } else { parsing_error(p, "m= invalid format for RTP/AVT"); return; } } } /* ------------------------------------------------------------------------- * Function parse_media_attr() - parse a media-specific "a=" field * * Description: * This function parses a media-specific attribute field. * * Parameters: * p - pointer to SDP parser object * r - pointer to record data * result - pointer to which parsed record is assigned */ static void parse_media_attr(sdp_parser_t *p, char *r, sdp_media_t *m, sdp_attribute_t **result) { /* attribute-fields = *("a=" attribute CRLF) attribute = (att-field ":" att-value) / att-field att-field = token att-value = byte-string a=rtpmap: /[/] a=fmtp: */ int rtp = sdp_media_has_rtp(m); char *name = NULL, *value = NULL; int n; if (!(name = token(&r, ":", TOKEN, SPACE TAB))) { parsing_error(p,"invalid attribute name"); return; } if (*r) value = r; else PARSE_CHECK_REST(p, r, "a"); if (p->pr_mode_manual) ; else if (m->m_port == 0 || su_casematch(name, "inactive")) { m->m_mode = sdp_inactive; return; } else if (su_casematch(name, "sendonly")) { m->m_mode = sdp_sendonly; return; } else if (su_casematch(name, "recvonly")) { m->m_mode = sdp_recvonly; return; } else if (su_casematch(name, "sendrecv")) { m->m_mode = sdp_sendrecv; return; } if (rtp && su_casematch(name, "rtpmap")) { if ((n = parse_rtpmap(p, r, m)) == 0 || n < -1) return; } else if (rtp && su_casematch(name, "fmtp")) { if ((n = parse_fmtp(p, r, m)) == 0 || n < -1) return; } else { PARSE_ALLOC(p, sdp_attribute_t, a); *result = a; a->a_name = name; a->a_value = value; } } /** Parse rtpmap attribute. * * a=rtpmap: /[/] */ static int parse_rtpmap(sdp_parser_t *p, char *r, sdp_media_t *m) { unsigned long pt, rate; char *encoding, *params; sdp_rtpmap_t *rm; int strict = STRICT(p); if (parse_ul(p, &r, &pt, 128)) { if (strict) parsing_error(p, "a=rtpmap: invalid payload type"); return -1; } for (rm = m->m_rtpmaps; rm; rm = rm->rm_next) if (rm->rm_pt == pt) break; if (!rm) { if (strict) parsing_error(p, "a=rtpmap:%lu: unknown payload type", pt); return -1; } encoding = token(&r, "/", TOKEN, NULL); if (!r) { parsing_error(p, "a=rtpmap:%lu: missing ", pt); return -2; } if (parse_ul(p, &r, &rate, 0)) { parsing_error(p, "a=rtpmap:%lu %s: invalid ", pt, encoding); return -2; } if (*r == '/') params = ++r; else params = 0; rm->rm_predef = 0; rm->rm_encoding = encoding; rm->rm_rate = rate; rm->rm_params = params; return 0; } /** Parse fmtp attribute. * * a=fmtp: */ static int parse_fmtp(sdp_parser_t *p, char *r, sdp_media_t *m) { unsigned long pt; sdp_rtpmap_t *rm; int strict = STRICT(p); if (parse_ul(p, &r, &pt, 128)) { if (strict) parsing_error(p, "a=rtpmap: invalid payload type"); return -1; } for (rm = m->m_rtpmaps; rm; rm = rm->rm_next) if (rm->rm_pt == pt) break; if (!rm) { if (strict) parsing_error(p, "a=fmtp:%lu: unknown payload type", pt); return -1; } rm->rm_fmtp = r; return 0; } /* ------------------------------------------------------------------------- * Function parse_descs() - parse media descriptors * * Description: * This function parses media descriptors at the end of SDP message. * * Parameters: * p - pointer to SDP parser object * record - pointer to first media field * message - pointer to rest * medias - pointer to which parsed media structures are assigned */ static void parse_descs(sdp_parser_t *p, char *record, char *message, sdp_media_t **medias) { char *rest; const char *strip; sdp_media_t *m = NULL; sdp_connection_t **connections = NULL; sdp_bandwidth_t **bandwidths = NULL; sdp_attribute_t **attributes = NULL; if (!STRICT(p)) strip = SPACE TAB; /* skip initial whitespace */ else strip = ""; for (; record && p->pr_ok; record = next(&message, CRLF, strip)) { char field = record[0]; rest = record + 2; rest += strspn(rest, strip); if (record[1] == '=') switch (field) { case 'c': assert(connections); parse_connection(p, rest, connections); connections = &(*connections)->c_next; break; case 'b': assert(bandwidths); parse_bandwidth(p, rest, bandwidths); bandwidths = &(*bandwidths)->b_next; break; case 'k': parse_key(p, rest, &m->m_key); break; case 'a': assert(attributes); parse_media_attr(p, rest, m, attributes); if (*attributes) attributes = &(*attributes)->a_next; break; case 'm': parse_media(p, rest, medias); m = *medias; if (m) { m->m_mode = p->pr_session_mode; medias = &m->m_next; connections = &m->m_connections; bandwidths = &m->m_bandwidths; attributes = &m->m_attributes; } } } } static void parse_text_list(sdp_parser_t *p, char *r, sdp_list_t **result) { PARSE_ALLOC(p, sdp_list_t, l); *result = l; l->l_text = r; } /* * parse_ul: parse an unsigned long */ static int parse_ul(sdp_parser_t *p, char **r, unsigned long *result, unsigned long max) { char *ul = *r; ul += strspn(ul, SPACE TAB); *result = strtoul(ul, r, 10); if (ul != *r && !(max && max <= *result)) { *r += strspn(*r, SPACE TAB); return 0; } return -1; } #if !HAVE_STRTOULL #if !((defined(WIN32) || defined(_WIN32)) && (_MSC_VER >= 1800)) unsigned long long strtoull(char const *string, char **return_end, int base); #endif #endif /* * parse_ull: parse an unsigned long long */ static int parse_ull(sdp_parser_t *p, char **r, uint64_t *result, uint64_t max) { unsigned long long ull; char *s = *r; s += strspn(s, SPACE TAB); ull = strtoull(s, r, 10); if (s != *r && !(max && max <= ull)) { *result = (uint64_t)ull; *r += strspn(*r, SPACE TAB); return 0; } return -1; } static char *token(char **message, const char *sep, const char *legal, const char *strip) { size_t n; char *retval = *message; if (strip) retval += strspn(retval, strip); if (legal) n = strspn(retval, legal); else n = strcspn(retval, sep); if (n == 0) return NULL; if (retval[n]) { retval[n++] = '\0'; n += strspn(retval + n, sep); } *message = retval + n; if (*retval == '\0') return NULL; return retval; } static char *next(char **message, const char *sep, const char *strip) { size_t n; char *retval = *message; if (strip[0]) retval += strspn(retval, strip); n = strcspn(retval, sep); if (n == 0) return NULL; if (retval[n]) { retval[n++] = '\0'; n += strspn(retval + n, sep); } *message = retval + n; if (*retval == '\0') return NULL; return retval; } static int parsing_error(sdp_parser_t *p, char const *fmt, ...) { va_list ap; va_start(ap, fmt); memset(p->pr_error, 0, sizeof(p->pr_error)); vsnprintf(p->pr_error, sizeof(p->pr_error), fmt, ap); va_end(ap); p->pr_ok = 0; return -1; } static void parse_alloc_error(sdp_parser_t *p, const char *typename) { parsing_error(p, "memory exhausted (while allocating memory for %s)", typename); }