#include "sdp.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#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=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
// <username> "-" if the originating host does not support the concept of user IDs.
// <sess-id> is a numeric string
// <sess-version> is a version number for this session description
// <nettype> IN
// <addrtype> 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=<session name>
// 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=<session description>
// 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=<uri>
// 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=<email-address>
// p=<phone-number>
// 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 <j.doe@example.com>
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=<nettype> <addrtype> <connection-address>
// 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>:<bandwidth>
// bwtype: CT/AS
// bandwidth: kilobits per second by default
// A prefix "X-" is defined for <bwtype> 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=<start-time> <stop-time>
// If the <stop-time> is set to zero, then the session is not bounded,
// though it will not become active until after the <start-time>. If
// the <start-time> 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 <stop-time> is set to zero, then the session is not bounded, though it will not become active 
//    until after the <start-time>. If the <start-time> 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=<repeat interval> <active duration> <offsets from start-time>
// 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=<adjustment time> <offset> <adjustment time> <offset> ....
// 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=<method>
// k=<method>:<encryption key>
// k=clear:<encryption key>
// k=base64:<encoded encryption key>
// k=uri:<URI to obtain key>
// 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=<attribute>
// a=<attribute>:<value>
// a=cat:<category>
// a=keywds:<keywords>
// a=tool:<name and version of tool>
// a=ptime:<packet time>
// a=maxptime:<maximum packet time>
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
// a=recvonly
// a=sendrecv
// a=sendonly
// a=inactive
// a=orient:<orientation>
// a=type:<conference type>
// a=charset:<character set>
// a=sdplang:<language tag>
// a=lang:<language tag>
// a=framerate:<frame rate>
// a=quality:<quality>
// a=fmtp:<format> <format specific parameters>
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> <port> <proto> <fmt> ...
// media: audio/video/text/application/message
// proto: udp, RTP/AVP, RTP/SAVP
// m=<media> <port>/<number of ports> <proto> <fmt> ...
// 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 <proto> sub-field is "udp" the <fmt> 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
}
