|
|
|
@ -0,0 +1,214 @@ |
|
|
|
#include <glib.h> |
|
|
|
|
|
|
|
#include "sdp.h" |
|
|
|
#include "call.h" |
|
|
|
#include "log.h" |
|
|
|
|
|
|
|
struct string { |
|
|
|
const char *s; |
|
|
|
int len; |
|
|
|
}; |
|
|
|
|
|
|
|
struct sdp_origin { |
|
|
|
struct string username; |
|
|
|
struct string session_id; |
|
|
|
struct string version; |
|
|
|
struct string network_type; |
|
|
|
struct string address_type; |
|
|
|
struct string address; |
|
|
|
int parsed:1; |
|
|
|
}; |
|
|
|
|
|
|
|
struct sdp_connection { |
|
|
|
struct string network_type; |
|
|
|
struct string address_type; |
|
|
|
struct string address; |
|
|
|
int parsed:1; |
|
|
|
}; |
|
|
|
|
|
|
|
struct sdp_session { |
|
|
|
struct sdp_origin origin; |
|
|
|
struct sdp_connection connection; |
|
|
|
GQueue media_streams; |
|
|
|
GQueue attributes; |
|
|
|
}; |
|
|
|
|
|
|
|
struct sdp_media { |
|
|
|
struct string media_type; |
|
|
|
struct string port; |
|
|
|
struct string transport; |
|
|
|
/* ... format list */ |
|
|
|
|
|
|
|
struct sdp_connection connection; |
|
|
|
GQueue attributes; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static inline int extract_token(const char **sp, const char *end, struct string *out) { |
|
|
|
const char *space; |
|
|
|
|
|
|
|
out->s = *sp; |
|
|
|
space = memchr(*sp, ' ', end - *sp); |
|
|
|
if (space == *sp || end == *sp) |
|
|
|
return -1; |
|
|
|
|
|
|
|
if (!space) { |
|
|
|
out->len = end - *sp; |
|
|
|
*sp = end; |
|
|
|
} |
|
|
|
else { |
|
|
|
out->len = space - *sp; |
|
|
|
*sp = space + 1; |
|
|
|
} |
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
#define EXTRACT_TOKEN(field) if (extract_token(&start, end, &output->field)) return -1 |
|
|
|
|
|
|
|
static int parse_origin(const char *start, const char *end, struct sdp_origin *output) { |
|
|
|
if (output->parsed) |
|
|
|
return -1; |
|
|
|
|
|
|
|
EXTRACT_TOKEN(username); |
|
|
|
EXTRACT_TOKEN(session_id); |
|
|
|
EXTRACT_TOKEN(version); |
|
|
|
EXTRACT_TOKEN(network_type); |
|
|
|
EXTRACT_TOKEN(address_type); |
|
|
|
EXTRACT_TOKEN(address); |
|
|
|
|
|
|
|
output->parsed = 1; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int parse_connection(const char *start, const char *end, struct sdp_connection *output) { |
|
|
|
if (output->parsed) |
|
|
|
return -1; |
|
|
|
|
|
|
|
EXTRACT_TOKEN(network_type); |
|
|
|
EXTRACT_TOKEN(address_type); |
|
|
|
EXTRACT_TOKEN(address); |
|
|
|
|
|
|
|
output->parsed = 1; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int parse_media(const char *start, const char *end, struct sdp_media *output) { |
|
|
|
EXTRACT_TOKEN(media_type); |
|
|
|
EXTRACT_TOKEN(port); |
|
|
|
EXTRACT_TOKEN(transport); |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
GQueue *sdp_parse(const char *body, int len, GQueue *streams) { |
|
|
|
const char *b, *end, *value, *line_end, *next_line; |
|
|
|
struct sdp_session *session = NULL; |
|
|
|
GQueue *sessions; |
|
|
|
struct sdp_media *media = NULL; |
|
|
|
const char *errstr; |
|
|
|
struct string *attribute; |
|
|
|
|
|
|
|
sessions = g_queue_new(); |
|
|
|
b = body; |
|
|
|
end = body + len; |
|
|
|
|
|
|
|
while (b && b < end - 1) { |
|
|
|
errstr = "Missing '=' sign"; |
|
|
|
if (b[1] != '=') |
|
|
|
goto error; |
|
|
|
|
|
|
|
value = &b[2]; |
|
|
|
line_end = memchr(value, '\n', end - value); |
|
|
|
if (!line_end) { |
|
|
|
/* assume missing LF at end of body */ |
|
|
|
line_end = end; |
|
|
|
next_line = NULL; |
|
|
|
} |
|
|
|
else { |
|
|
|
next_line = line_end + 1; |
|
|
|
if (next_line >= end) |
|
|
|
next_line = NULL; |
|
|
|
if (line_end[-1] == '\r') |
|
|
|
line_end--; |
|
|
|
} |
|
|
|
|
|
|
|
switch (b[0]) { |
|
|
|
case 'v': |
|
|
|
errstr = "Error in v= line"; |
|
|
|
if (line_end != value + 1) |
|
|
|
goto error; |
|
|
|
if (value[0] != '0') |
|
|
|
goto error; |
|
|
|
|
|
|
|
session = g_slice_alloc0(sizeof(*session)); |
|
|
|
g_queue_init(&session->media_streams); |
|
|
|
g_queue_init(&session->attributes); |
|
|
|
g_queue_push_tail(sessions, session); |
|
|
|
media = NULL; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'o': |
|
|
|
errstr = "o= line found within media section"; |
|
|
|
if (media) |
|
|
|
goto error; |
|
|
|
errstr = "Error parsing o= line"; |
|
|
|
if (parse_origin(value, line_end, &session->origin)) |
|
|
|
goto error; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'm': |
|
|
|
media = g_slice_alloc0(sizeof(*media)); |
|
|
|
g_queue_init(&media->attributes); |
|
|
|
errstr = "Error parsing m= line"; |
|
|
|
if (parse_media(value, line_end, media)) |
|
|
|
goto error; |
|
|
|
g_queue_push_tail(&session->media_streams, media); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'c': |
|
|
|
errstr = "Error parsing c= line"; |
|
|
|
if (parse_connection(value, line_end, |
|
|
|
media ? &media->connection : &session->connection)) |
|
|
|
goto error; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'a': |
|
|
|
attribute = g_slice_alloc(sizeof(*attribute)); |
|
|
|
attribute->s = value; |
|
|
|
attribute->len = line_end - value; |
|
|
|
g_queue_push_tail(media ? &media->attributes : &session->attributes, |
|
|
|
attribute); |
|
|
|
break; |
|
|
|
|
|
|
|
case 's': |
|
|
|
case 'i': |
|
|
|
case 'u': |
|
|
|
case 'e': |
|
|
|
case 'p': |
|
|
|
case 'b': |
|
|
|
case 't': |
|
|
|
case 'r': |
|
|
|
case 'z': |
|
|
|
case 'k': |
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
errstr = "Unknown SDP line type found"; |
|
|
|
goto error; |
|
|
|
} |
|
|
|
|
|
|
|
b = next_line; |
|
|
|
} |
|
|
|
|
|
|
|
return sessions; |
|
|
|
|
|
|
|
error: |
|
|
|
mylog(LOG_WARNING, "Error parsing SDP: %s", errstr); |
|
|
|
/* XXX free sessions */ |
|
|
|
return NULL; |
|
|
|
} |