Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b

Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/sdp/attr.c b/src/sdp/attr.c
new file mode 100644
index 0000000..00e6e24
--- /dev/null
+++ b/src/sdp/attr.c
@@ -0,0 +1,140 @@
+/**
+ * @file sdp/attr.c  SDP Attributes
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+struct sdp_attr {
+	struct le le;
+	char *name;
+	char *val;
+};
+
+
+static void destructor(void *arg)
+{
+	struct sdp_attr *attr = arg;
+
+	list_unlink(&attr->le);
+	mem_deref(attr->name);
+	mem_deref(attr->val);
+}
+
+
+int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val)
+{
+	struct sdp_attr *attr;
+	int err;
+
+	attr = mem_zalloc(sizeof(*attr), destructor);
+	if (!attr)
+		return ENOMEM;
+
+	list_append(lst, &attr->le, attr);
+
+	err = pl_strdup(&attr->name, name);
+
+	if (pl_isset(val))
+		err |= pl_strdup(&attr->val, val);
+
+	if (err)
+		mem_deref(attr);
+
+	return err;
+}
+
+
+int sdp_attr_addv(struct list *lst, const char *name, const char *val,
+		  va_list ap)
+{
+	struct sdp_attr *attr;
+	int err;
+
+	attr = mem_zalloc(sizeof(*attr), destructor);
+	if (!attr)
+		return ENOMEM;
+
+	list_append(lst, &attr->le, attr);
+
+	err = str_dup(&attr->name, name);
+
+	if (str_isset(val))
+		err |= re_vsdprintf(&attr->val, val, ap);
+
+	if (err)
+		mem_deref(attr);
+
+	return err;
+}
+
+
+void sdp_attr_del(const struct list *lst, const char *name)
+{
+	struct le *le = list_head(lst);
+
+	while (le) {
+
+		struct sdp_attr *attr = le->data;
+
+		le = le->next;
+
+		if (0 == str_casecmp(name, attr->name))
+			mem_deref(attr);
+	}
+}
+
+
+const char *sdp_attr_apply(const struct list *lst, const char *name,
+			   sdp_attr_h *attrh, void *arg)
+{
+	struct le *le = list_head(lst);
+
+	while (le) {
+
+		const struct sdp_attr *attr = le->data;
+
+		le = le->next;
+
+		if (name && (!attr->name || strcmp(name, attr->name)))
+			continue;
+
+		if (!attrh || attrh(attr->name, attr->val?attr->val : "", arg))
+			return attr->val ? attr->val : "";
+	}
+
+	return NULL;
+}
+
+
+int sdp_attr_print(struct re_printf *pf, const struct sdp_attr *attr)
+{
+	if (!attr)
+		return 0;
+
+	if (attr->val)
+		return re_hprintf(pf, "a=%s:%s\r\n", attr->name, attr->val);
+	else
+		return re_hprintf(pf, "a=%s\r\n", attr->name);
+}
+
+
+int sdp_attr_debug(struct re_printf *pf, const struct sdp_attr *attr)
+{
+	if (!attr)
+		return 0;
+
+	if (attr->val)
+		return re_hprintf(pf, "%s='%s'", attr->name, attr->val);
+	else
+		return re_hprintf(pf, "%s", attr->name);
+}
diff --git a/src/sdp/format.c b/src/sdp/format.c
new file mode 100644
index 0000000..9cc9dd9
--- /dev/null
+++ b/src/sdp/format.c
@@ -0,0 +1,266 @@
+/**
+ * @file sdp/format.c  SDP format
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+static void destructor(void *arg)
+{
+	struct sdp_format *fmt = arg;
+
+	list_unlink(&fmt->le);
+
+	if (fmt->ref)
+		mem_deref(fmt->data);
+
+	mem_deref(fmt->id);
+	mem_deref(fmt->params);
+	mem_deref(fmt->rparams);
+	mem_deref(fmt->name);
+}
+
+
+/**
+ * Add an SDP Format to an SDP Media line
+ *
+ * @param fmtp    Pointer to allocated SDP Format
+ * @param m       SDP Media line
+ * @param prepend True to prepend, False to append
+ * @param id      Format identifier
+ * @param name    Format name
+ * @param srate   Sampling rate
+ * @param ch      Number of channels
+ * @param ench    Optional format encode handler
+ * @param cmph    Optional format comparison handler
+ * @param data    Opaque data for handler
+ * @param ref     True to mem_ref() data
+ * @param params  Formatted parameters
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_format_add(struct sdp_format **fmtp, struct sdp_media *m,
+		   bool prepend, const char *id, const char *name,
+		   uint32_t srate, uint8_t ch, sdp_fmtp_enc_h *ench,
+		   sdp_fmtp_cmp_h *cmph, void *data, bool ref,
+		   const char *params, ...)
+{
+	struct sdp_format *fmt;
+	int err;
+
+	if (!m)
+		return EINVAL;
+
+	if (!id && (m->dynpt > RTP_DYNPT_END))
+		return ERANGE;
+
+	fmt = mem_zalloc(sizeof(*fmt), destructor);
+	if (!fmt)
+		return ENOMEM;
+
+	if (prepend)
+		list_prepend(&m->lfmtl, &fmt->le, fmt);
+	else
+		list_append(&m->lfmtl, &fmt->le, fmt);
+
+	if (id)
+		err = str_dup(&fmt->id, id);
+	else
+		err = re_sdprintf(&fmt->id, "%i", m->dynpt++);
+	if (err)
+		goto out;
+
+	if (name) {
+		err = str_dup(&fmt->name, name);
+		if (err)
+			goto out;
+	}
+
+	if (params) {
+		va_list ap;
+
+		va_start(ap, params);
+		err = re_vsdprintf(&fmt->params, params, ap);
+		va_end(ap);
+
+		if (err)
+			goto out;
+	}
+
+	fmt->pt    = atoi(fmt->id);
+	fmt->srate = srate;
+	fmt->ch    = ch;
+	fmt->ench  = ench;
+	fmt->cmph  = cmph;
+	fmt->data  = ref ? mem_ref(data) : data;
+	fmt->ref   = ref;
+	fmt->sup   = true;
+
+ out:
+	if (err)
+		mem_deref(fmt);
+	else if (fmtp)
+		*fmtp = fmt;
+
+	return err;
+}
+
+
+int sdp_format_radd(struct sdp_media *m, const struct pl *id)
+{
+	struct sdp_format *fmt;
+	int err;
+
+	if (!m || !id)
+		return EINVAL;
+
+	fmt = mem_zalloc(sizeof(*fmt), destructor);
+	if (!fmt)
+		return ENOMEM;
+
+	list_append(&m->rfmtl, &fmt->le, fmt);
+
+	err = pl_strdup(&fmt->id, id);
+	if (err)
+		goto out;
+
+	fmt->pt = atoi(fmt->id);
+
+ out:
+	if (err)
+		mem_deref(fmt);
+
+	return err;
+}
+
+
+struct sdp_format *sdp_format_find(const struct list *lst, const struct pl *id)
+{
+	struct le *le;
+
+	if (!lst || !id)
+		return NULL;
+
+	for (le=lst->head; le; le=le->next) {
+
+		struct sdp_format *fmt = le->data;
+
+		if (pl_strcmp(id, fmt->id))
+			continue;
+
+		return fmt;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Set the parameters of an SDP format
+ *
+ * @param fmt    SDP Format
+ * @param params Formatted parameters
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_format_set_params(struct sdp_format *fmt, const char *params, ...)
+{
+	int err = 0;
+
+	if (!fmt)
+		return EINVAL;
+
+	fmt->params = mem_deref(fmt->params);
+
+	if (params) {
+		va_list ap;
+
+		va_start(ap, params);
+		err = re_vsdprintf(&fmt->params, params, ap);
+		va_end(ap);
+	}
+
+	return err;
+}
+
+
+/**
+ * Compare two SDP Formats
+ *
+ * @param fmt1 First SDP format
+ * @param fmt2 Second SDP format
+ *
+ * @return True if matching, False if not
+ */
+bool sdp_format_cmp(const struct sdp_format *fmt1,
+		    const struct sdp_format *fmt2)
+{
+	if (!fmt1 || !fmt2)
+		return false;
+
+	if (fmt1->pt < RTP_DYNPT_START && fmt2->pt < RTP_DYNPT_START) {
+
+		if (!fmt1->id || !fmt2->id)
+			return false;
+
+		return strcmp(fmt1->id, fmt2->id) ? false : true;
+	}
+
+	if (str_casecmp(fmt1->name, fmt2->name))
+		return false;
+
+	if (fmt1->srate != fmt2->srate)
+		return false;
+
+	if (fmt1->ch != fmt2->ch)
+		return false;
+
+	if (fmt1->cmph && !fmt1->cmph(fmt1->params, fmt2->params, fmt1->data))
+		return false;
+
+	if (fmt2->cmph && !fmt2->cmph(fmt2->params, fmt1->params, fmt2->data))
+		return false;
+
+	return true;
+}
+
+
+/**
+ * Print SDP Format debug information
+ *
+ * @param pf  Print function for output
+ * @param fmt SDP Format
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_format_debug(struct re_printf *pf, const struct sdp_format *fmt)
+{
+	int err;
+
+	if (!fmt)
+		return 0;
+
+	err = re_hprintf(pf, "%3s", fmt->id);
+
+	if (fmt->name)
+		err |= re_hprintf(pf, " %s/%u/%u",
+				  fmt->name, fmt->srate, fmt->ch);
+
+	if (fmt->params)
+		err |= re_hprintf(pf, " (%s)", fmt->params);
+
+	if (fmt->sup)
+		err |= re_hprintf(pf, " *");
+
+	return err;
+}
diff --git a/src/sdp/media.c b/src/sdp/media.c
new file mode 100644
index 0000000..07ce3a2
--- /dev/null
+++ b/src/sdp/media.c
@@ -0,0 +1,958 @@
+/**
+ * @file sdp/media.c  SDP Media
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+static void destructor(void *arg)
+{
+	struct sdp_media *m = arg;
+	unsigned i;
+
+	list_flush(&m->lfmtl);
+	list_flush(&m->rfmtl);
+	list_flush(&m->rattrl);
+	list_flush(&m->lattrl);
+
+	if (m->le.list) {
+		m->disabled = true;
+		m->ench     = NULL;
+		mem_ref(m);
+		return;
+	}
+
+	for (i=0; i<ARRAY_SIZE(m->protov); i++)
+		mem_deref(m->protov[i]);
+
+	list_unlink(&m->le);
+	mem_deref(m->name);
+	mem_deref(m->proto);
+	mem_deref(m->uproto);
+}
+
+
+static int media_alloc(struct sdp_media **mp, struct list *list)
+{
+	struct sdp_media *m;
+	int i;
+
+	m = mem_zalloc(sizeof(*m), destructor);
+	if (!m)
+		return ENOMEM;
+
+	list_append(list, &m->le, m);
+
+	m->ldir  = SDP_SENDRECV;
+	m->rdir  = SDP_SENDRECV;
+	m->dynpt = RTP_DYNPT_START;
+
+	sa_init(&m->laddr, AF_INET);
+	sa_init(&m->raddr, AF_INET);
+	sa_init(&m->laddr_rtcp, AF_INET);
+	sa_init(&m->raddr_rtcp, AF_INET);
+
+	for (i=0; i<SDP_BANDWIDTH_MAX; i++) {
+		m->lbwv[i] = -1;
+		m->rbwv[i] = -1;
+	}
+
+	*mp = m;
+
+	return 0;
+}
+
+
+/**
+ * Add a media line to an SDP Session
+ *
+ * @param mp    Pointer to allocated SDP Media line object
+ * @param sess  SDP Session
+ * @param name  Media name
+ * @param port  Port number
+ * @param proto Transport protocol
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_add(struct sdp_media **mp, struct sdp_session *sess,
+		  const char *name, uint16_t port, const char *proto)
+{
+	struct sdp_media *m;
+	int err;
+
+	if (!sess || !name || !proto)
+		return EINVAL;
+
+	err = media_alloc(&m, &sess->lmedial);
+	if (err)
+		return err;
+
+	err  = str_dup(&m->name, name);
+	err |= str_dup(&m->proto, proto);
+	if (err)
+		goto out;
+
+	sa_set_port(&m->laddr, port);
+
+ out:
+	if (err)
+		mem_deref(m);
+	else if (mp)
+		*mp = m;
+
+	return err;
+}
+
+
+/**
+ * Add a remote SDP media line to an SDP Session
+ *
+ * @param mp    Pointer to allocated SDP Media line object
+ * @param sess  SDP Session
+ * @param name  Media name
+ * @param proto Transport protocol
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_radd(struct sdp_media **mp, struct sdp_session *sess,
+		   const struct pl *name, const struct pl *proto)
+{
+	struct sdp_media *m;
+	int err;
+
+	if (!mp || !sess || !name || !proto)
+		return EINVAL;
+
+	err = media_alloc(&m, &sess->medial);
+	if (err)
+		return err;
+
+	m->disabled = true;
+
+	err  = pl_strdup(&m->name, name);
+	err |= pl_strdup(&m->proto, proto);
+
+	if (err)
+		mem_deref(m);
+	else
+		*mp = m;
+
+	return err;
+}
+
+
+/**
+ * Reset the remote part of an SDP Media line
+ *
+ * @param m SDP Media line
+ */
+void sdp_media_rreset(struct sdp_media *m)
+{
+	int i;
+
+	if (!m)
+		return;
+
+	sa_init(&m->raddr, AF_INET);
+	sa_init(&m->raddr_rtcp, AF_INET);
+
+	list_flush(&m->rfmtl);
+	list_flush(&m->rattrl);
+
+	m->rdir = SDP_SENDRECV;
+
+	for (i=0; i<SDP_BANDWIDTH_MAX; i++)
+		m->rbwv[i] = -1;
+}
+
+
+/**
+ * Compare media line protocols
+ *
+ * @param m      SDP Media line
+ * @param proto  Transport protocol
+ * @param update Update media protocol if match is found in alternate set
+ *
+ * @return True if matching, False if not
+ */
+bool sdp_media_proto_cmp(struct sdp_media *m, const struct pl *proto,
+			 bool update)
+{
+	unsigned i;
+
+	if (!m || !proto)
+		return false;
+
+	if (!pl_strcmp(proto, m->proto))
+		return true;
+
+	for (i=0; i<ARRAY_SIZE(m->protov); i++) {
+
+		if (!m->protov[i] || pl_strcmp(proto, m->protov[i]))
+			continue;
+
+		if (update) {
+			mem_deref(m->proto);
+			m->proto = mem_ref(m->protov[i]);
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+
+/**
+ * Find an SDP Media line from name and transport protocol
+ *
+ * @param sess  SDP Session
+ * @param name  Media name
+ * @param proto Transport protocol
+ * @param update_proto Update media transport protocol
+ *
+ * @return Matching media line if found, NULL if not found
+ */
+struct sdp_media *sdp_media_find(const struct sdp_session *sess,
+				 const struct pl *name,
+				 const struct pl *proto,
+				 bool update_proto)
+{
+	struct le *le;
+
+	if (!sess || !name || !proto)
+		return NULL;
+
+	for (le=sess->lmedial.head; le; le=le->next) {
+
+		struct sdp_media *m = le->data;
+
+		if (pl_strcmp(name, m->name))
+			continue;
+
+		if (!sdp_media_proto_cmp(m, proto, update_proto))
+			continue;
+
+		return m;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Align the locate/remote formats of an SDP Media line
+ *
+ * @param m     SDP Media line
+ * @param offer True if SDP Offer, False if SDP Answer
+ */
+void sdp_media_align_formats(struct sdp_media *m, bool offer)
+{
+	struct sdp_format *rfmt, *lfmt;
+	struct le *rle, *lle;
+
+	if (!m || m->disabled || !sa_port(&m->raddr) || m->fmt_ignore)
+		return;
+
+	for (lle=m->lfmtl.head; lle; lle=lle->next) {
+
+		lfmt = lle->data;
+
+		lfmt->rparams = mem_deref(lfmt->rparams);
+		lfmt->sup = false;
+	}
+
+	for (rle=m->rfmtl.head; rle; rle=rle->next) {
+
+		rfmt = rle->data;
+
+		for (lle=m->lfmtl.head; lle; lle=lle->next) {
+
+			lfmt = lle->data;
+
+			if (sdp_format_cmp(lfmt, rfmt))
+				break;
+		}
+
+		if (!lle) {
+			rfmt->sup = false;
+			continue;
+		}
+
+		mem_deref(lfmt->rparams);
+		lfmt->rparams = mem_ref(rfmt->params);
+
+		lfmt->sup = true;
+		rfmt->sup = true;
+
+		if (rfmt->ref)
+			rfmt->data = mem_deref(rfmt->data);
+		else
+			rfmt->data = NULL;
+
+		if (lfmt->ref)
+			rfmt->data = mem_ref(lfmt->data);
+		else
+			rfmt->data = lfmt->data;
+
+		rfmt->ref = lfmt->ref;
+
+		if (offer) {
+			mem_deref(lfmt->id);
+			lfmt->id = mem_ref(rfmt->id);
+			lfmt->pt = atoi(lfmt->id ? lfmt->id : "");
+
+			list_unlink(&lfmt->le);
+			list_append(&m->lfmtl, &lfmt->le, lfmt);
+		}
+	}
+
+	if (offer) {
+
+		for (lle=m->lfmtl.tail; lle; ) {
+
+			lfmt = lle->data;
+
+			lle = lle->prev;
+
+			if (!lfmt->sup) {
+				list_unlink(&lfmt->le);
+				list_append(&m->lfmtl, &lfmt->le, lfmt);
+			}
+		}
+	}
+}
+
+
+/**
+ * Set alternative protocols for an SDP Media line
+ *
+ * @param m      SDP Media line
+ * @param protoc Number of alternative protocols
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_set_alt_protos(struct sdp_media *m, unsigned protoc, ...)
+{
+	const char *proto;
+	int err = 0;
+	unsigned i;
+	va_list ap;
+
+	if (!m)
+		return EINVAL;
+
+	va_start(ap, protoc);
+
+	for (i=0; i<ARRAY_SIZE(m->protov); i++) {
+
+		m->protov[i] = mem_deref(m->protov[i]);
+
+		if (i >= protoc)
+			continue;
+
+		proto = va_arg(ap, const char *);
+		if (proto)
+			err |= str_dup(&m->protov[i], proto);
+	}
+
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Set SDP Media line encode handler
+ *
+ * @param m    SDP Media line
+ * @param ench Encode handler
+ * @param arg  Encode handler argument
+ */
+void sdp_media_set_encode_handler(struct sdp_media *m, sdp_media_enc_h *ench,
+				  void *arg)
+{
+	if (!m)
+		return;
+
+	m->ench = ench;
+	m->arg  = arg;
+}
+
+
+/**
+ * Set an SDP Media line to ignore formats
+ *
+ * @param m          SDP Media line
+ * @param fmt_ignore True for ignore formats, otherwise false
+ */
+void sdp_media_set_fmt_ignore(struct sdp_media *m, bool fmt_ignore)
+{
+	if (!m)
+		return;
+
+	m->fmt_ignore = fmt_ignore;
+}
+
+
+/**
+ * Set an SDP Media line to enabled/disabled
+ *
+ * @param m        SDP Media line
+ * @param disabled True for disabled, False for enabled
+ */
+void sdp_media_set_disabled(struct sdp_media *m, bool disabled)
+{
+	if (!m)
+		return;
+
+	m->disabled = disabled;
+}
+
+
+/**
+ * Set the local port number of an SDP Media line
+ *
+ * @param m    SDP Media line
+ * @param port Port number
+ */
+void sdp_media_set_lport(struct sdp_media *m, uint16_t port)
+{
+	if (!m)
+		return;
+
+	sa_set_port(&m->laddr, port);
+}
+
+
+/**
+ * Set the local network address of an SDP media line
+ *
+ * @param m     SDP Media line
+ * @param laddr Local network address
+ */
+void sdp_media_set_laddr(struct sdp_media *m, const struct sa *laddr)
+{
+	if (!m || !laddr)
+		return;
+
+	m->laddr = *laddr;
+}
+
+
+/**
+ * Set a local bandwidth of an SDP Media line
+ *
+ * @param m    SDP Media line
+ * @param type Bandwidth type
+ * @param bw   Bandwidth value
+ */
+void sdp_media_set_lbandwidth(struct sdp_media *m, enum sdp_bandwidth type,
+			      int32_t bw)
+{
+	if (!m || type >= SDP_BANDWIDTH_MAX)
+		return;
+
+	m->lbwv[type] = bw;
+}
+
+
+/**
+ * Set the local RTCP port number of an SDP Media line
+ *
+ * @param m    SDP Media line
+ * @param port RTCP Port number
+ */
+void sdp_media_set_lport_rtcp(struct sdp_media *m, uint16_t port)
+{
+	if (!m)
+		return;
+
+	sa_set_port(&m->laddr_rtcp, port);
+}
+
+
+/**
+ * Set the local RTCP network address of an SDP media line
+ *
+ * @param m     SDP Media line
+ * @param laddr Local RTCP network address
+ */
+void sdp_media_set_laddr_rtcp(struct sdp_media *m, const struct sa *laddr)
+{
+	if (!m || !laddr)
+		return;
+
+	m->laddr_rtcp = *laddr;
+}
+
+
+/**
+ * Set the local direction flag of an SDP Media line
+ *
+ * @param m   SDP Media line
+ * @param dir Media direction flag
+ */
+void sdp_media_set_ldir(struct sdp_media *m, enum sdp_dir dir)
+{
+	if (!m)
+		return;
+
+	m->ldir = dir;
+}
+
+
+/**
+ * Set a local attribute of an SDP Media line
+ *
+ * @param m       SDP Media line
+ * @param replace True to replace attribute, False to append
+ * @param name    Attribute name
+ * @param value   Formatted attribute value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_set_lattr(struct sdp_media *m, bool replace,
+			const char *name, const char *value, ...)
+{
+	va_list ap;
+	int err;
+
+	if (!m || !name)
+		return EINVAL;
+
+	if (replace)
+		sdp_attr_del(&m->lattrl, name);
+
+	va_start(ap, value);
+	err = sdp_attr_addv(&m->lattrl, name, value, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Delete a local attribute of an SDP Media line
+ *
+ * @param m    SDP Media line
+ * @param name Attribute name
+ */
+void sdp_media_del_lattr(struct sdp_media *m, const char *name)
+{
+	if (!m || !name)
+		return;
+
+	sdp_attr_del(&m->lattrl, name);
+}
+
+
+const char *sdp_media_proto(const struct sdp_media *m)
+{
+	return m ? m->proto : NULL;
+}
+
+
+/**
+ * Get the remote port number of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Remote port number
+ */
+uint16_t sdp_media_rport(const struct sdp_media *m)
+{
+	return m ? sa_port(&m->raddr) : 0;
+}
+
+
+/**
+ * Get the remote network address of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Remote network address
+ */
+const struct sa *sdp_media_raddr(const struct sdp_media *m)
+{
+	return m ? &m->raddr : NULL;
+}
+
+
+/**
+ * Get the local network address of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Local network address
+ */
+const struct sa *sdp_media_laddr(const struct sdp_media *m)
+{
+	return m ? &m->laddr : NULL;
+}
+
+
+/**
+ * Get the remote RTCP network address of an SDP Media line
+ *
+ * @param m     SDP Media line
+ * @param raddr On return, contains remote RTCP network address
+ */
+void sdp_media_raddr_rtcp(const struct sdp_media *m, struct sa *raddr)
+{
+	if (!m || !raddr)
+		return;
+
+	if (sa_isset(&m->raddr_rtcp, SA_ALL)) {
+		*raddr = m->raddr_rtcp;
+	}
+	else if (sa_isset(&m->raddr_rtcp, SA_PORT)) {
+		*raddr = m->raddr;
+		sa_set_port(raddr, sa_port(&m->raddr_rtcp));
+	}
+	else {
+		uint16_t port = sa_port(&m->raddr);
+
+		*raddr = m->raddr;
+		sa_set_port(raddr, port ? port + 1 : 0);
+	}
+}
+
+
+/**
+ * Get a remote bandwidth of an SDP Media line
+ *
+ * @param m    SDP Media line
+ * @param type Bandwidth type
+ *
+ * @return Remote bandwidth value
+ */
+int32_t sdp_media_rbandwidth(const struct sdp_media *m,
+			      enum sdp_bandwidth type)
+{
+	if (!m || type >= SDP_BANDWIDTH_MAX)
+		return 0;
+
+	return m->rbwv[type];
+}
+
+
+/**
+ * Get the local media direction of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Local media direction
+ */
+enum sdp_dir sdp_media_ldir(const struct sdp_media *m)
+{
+	return m ? m->ldir : SDP_INACTIVE;
+}
+
+
+/**
+ * Get the remote media direction of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Remote media direction
+ */
+enum sdp_dir sdp_media_rdir(const struct sdp_media *m)
+{
+	return m ? m->rdir : SDP_INACTIVE;
+}
+
+
+/**
+ * Get the combined media direction of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Combined media direction
+ */
+enum sdp_dir sdp_media_dir(const struct sdp_media *m)
+{
+	return m ? (enum sdp_dir)(m->ldir & m->rdir) : SDP_INACTIVE;
+}
+
+
+/**
+ * Find a local SDP format from a payload type
+ *
+ * @param m  SDP Media line
+ * @param pt Payload type
+ *
+ * @return Local SDP format if found, NULL if not found
+ */
+const struct sdp_format *sdp_media_lformat(const struct sdp_media *m, int pt)
+{
+	struct le *le;
+
+	if (!m)
+		return NULL;
+
+	for (le=m->lfmtl.head; le; le=le->next) {
+
+		const struct sdp_format *fmt = le->data;
+
+		if (pt == fmt->pt)
+			return fmt;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Find a remote SDP format from a format name
+ *
+ * @param m    SDP Media line
+ * @param name Format name
+ *
+ * @return Remote SDP format if found, NULL if not found
+ */
+const struct sdp_format *sdp_media_rformat(const struct sdp_media *m,
+					   const char *name)
+{
+	struct le *le;
+
+	if (!m || !sa_port(&m->raddr))
+		return NULL;
+
+	for (le=m->rfmtl.head; le; le=le->next) {
+
+		const struct sdp_format *fmt = le->data;
+
+		if (!fmt->sup)
+			continue;
+
+		if (name && str_casecmp(name, fmt->name))
+			continue;
+
+		return fmt;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Find an SDP Format of an SDP Media line
+ *
+ * @param m     SDP Media line
+ * @param local True if local media, False if remote
+ * @param id    SDP format id
+ * @param pt    Payload type
+ * @param name  Format name
+ * @param srate Sampling rate
+ * @param ch    Number of channels
+ *
+ * @return SDP Format if found, NULL if not found
+ */
+struct sdp_format *sdp_media_format(const struct sdp_media *m,
+				    bool local, const char *id,
+				    int pt, const char *name,
+				    int32_t srate, int8_t ch)
+{
+	return sdp_media_format_apply(m, local, id, pt, name, srate, ch,
+				      NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to all matching SDP formats
+ *
+ * @param m     SDP Media line
+ * @param local True if local media, False if remote
+ * @param id    SDP format id
+ * @param pt    Payload type
+ * @param name  Format name
+ * @param srate Sampling rate
+ * @param ch    Number of channels
+ * @param fmth  SDP Format handler
+ * @param arg   Handler argument
+ *
+ * @return SDP Format if found, NULL if not found
+ */
+struct sdp_format *sdp_media_format_apply(const struct sdp_media *m,
+					  bool local, const char *id,
+					  int pt, const char *name,
+					  int32_t srate, int8_t ch,
+					  sdp_format_h *fmth, void *arg)
+{
+	struct le *le;
+
+	if (!m)
+		return NULL;
+
+	le = local ? m->lfmtl.head : m->rfmtl.head;
+
+	while (le) {
+
+		struct sdp_format *fmt = le->data;
+
+		le = le->next;
+
+		if (id && (!fmt->id || strcmp(id, fmt->id)))
+			continue;
+
+		if (pt >= 0 && pt != fmt->pt)
+			continue;
+
+		if (name && str_casecmp(name, fmt->name))
+			continue;
+
+		if (srate >= 0 && (uint32_t)srate != fmt->srate)
+			continue;
+
+		if (ch >= 0 && (uint8_t)ch != fmt->ch)
+			continue;
+
+		if (!fmth || fmth(fmt, arg))
+			return fmt;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Get the list of SDP Formats
+ *
+ * @param m     SDP Media line
+ * @param local True if local, False if remote
+ *
+ * @return List of SDP Formats
+ */
+const struct list *sdp_media_format_lst(const struct sdp_media *m, bool local)
+{
+	if (!m)
+		return NULL;
+
+	return local ? &m->lfmtl : &m->rfmtl;
+}
+
+
+/**
+ * Get a remote attribute from an SDP Media line
+ *
+ * @param m     SDP Media line
+ * @param name  Attribute name
+ *
+ * @return Attribute value, NULL if not found
+ */
+const char *sdp_media_rattr(const struct sdp_media *m, const char *name)
+{
+	if (!m || !name)
+		return NULL;
+
+	return sdp_attr_apply(&m->rattrl, name, NULL, NULL);
+}
+
+
+/**
+ * Get a remote attribute from an SDP Media line or the SDP session
+ *
+ * @param m     SDP Media line
+ * @param sess  SDP Session
+ * @param name  Attribute name
+ *
+ * @return Attribute value, NULL if not found
+ */
+const char *sdp_media_session_rattr(const struct sdp_media *m,
+				    const struct sdp_session *sess,
+				    const char *name)
+{
+	const char *val;
+
+	val = sdp_media_rattr(m, name);
+	if (!val)
+		val = sdp_session_rattr(sess, name);
+
+	return val;
+}
+
+
+/**
+ * Apply a function handler to all matching remote attributes
+ *
+ * @param m     SDP Media line
+ * @param name  Attribute name
+ * @param attrh Attribute handler
+ * @param arg   Handler argument
+ *
+ * @return Attribute value if match
+ */
+const char *sdp_media_rattr_apply(const struct sdp_media *m, const char *name,
+				  sdp_attr_h *attrh, void *arg)
+{
+	if (!m)
+		return NULL;
+
+	return sdp_attr_apply(&m->rattrl, name, attrh, arg);
+}
+
+
+/**
+ * Get the name of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return SDP Media line name
+ */
+const char *sdp_media_name(const struct sdp_media *m)
+{
+	return m ? m->name : NULL;
+}
+
+
+/**
+ * Print SDP Media line debug information
+ *
+ * @param pf Print function for output
+ * @param m  SDP Media line
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_debug(struct re_printf *pf, const struct sdp_media *m)
+{
+	struct le *le;
+	int err;
+
+	if (!m)
+		return 0;
+
+	err  = re_hprintf(pf, "%s %s\n", m->name, m->proto);
+
+	err |= re_hprintf(pf, "  local formats:\n");
+
+	for (le=m->lfmtl.head; le; le=le->next)
+		err |= re_hprintf(pf, "    %H\n", sdp_format_debug, le->data);
+
+	err |= re_hprintf(pf, "  remote formats:\n");
+
+	for (le=m->rfmtl.head; le; le=le->next)
+		err |= re_hprintf(pf, "    %H\n", sdp_format_debug, le->data);
+
+	err |= re_hprintf(pf, "  local attributes:\n");
+
+	for (le=m->lattrl.head; le; le=le->next)
+		err |= re_hprintf(pf, "    %H\n", sdp_attr_debug, le->data);
+
+	err |= re_hprintf(pf, "  remote attributes:\n");
+
+	for (le=m->rattrl.head; le; le=le->next)
+		err |= re_hprintf(pf, "    %H\n", sdp_attr_debug, le->data);
+
+	return err;
+}
diff --git a/src/sdp/mod.mk b/src/sdp/mod.mk
new file mode 100644
index 0000000..6c50025
--- /dev/null
+++ b/src/sdp/mod.mk
@@ -0,0 +1,13 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= sdp/attr.c
+SRCS	+= sdp/format.c
+SRCS	+= sdp/media.c
+SRCS	+= sdp/msg.c
+SRCS	+= sdp/session.c
+SRCS	+= sdp/str.c
+SRCS	+= sdp/util.c
diff --git a/src/sdp/msg.c b/src/sdp/msg.c
new file mode 100644
index 0000000..c1a8bbc
--- /dev/null
+++ b/src/sdp/msg.c
@@ -0,0 +1,531 @@
+/**
+ * @file sdp/msg.c  SDP Message processing
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+static int attr_decode_fmtp(struct sdp_media *m, const struct pl *pl)
+{
+	struct sdp_format *fmt;
+	struct pl id, params;
+
+	if (!m)
+		return 0;
+
+	if (re_regex(pl->p, pl->l, "[^ ]+ [^]*", &id, &params))
+		return EBADMSG;
+
+	fmt = sdp_format_find(&m->rfmtl, &id);
+	if (!fmt)
+		return 0;
+
+	fmt->params = mem_deref(fmt->params);
+
+	return pl_strdup(&fmt->params, &params);
+}
+
+
+static int attr_decode_rtcp(struct sdp_media *m, const struct pl *pl)
+{
+	struct pl port, addr;
+	int err = 0;
+
+	if (!m)
+		return 0;
+
+	if (!re_regex(pl->p, pl->l, "[0-9]+ IN IP[46]1 [^ ]+",
+		      &port, NULL, &addr)) {
+		(void)sa_set(&m->raddr_rtcp, &addr, pl_u32(&port));
+	}
+	else if (!re_regex(pl->p, pl->l, "[0-9]+", &port)) {
+		sa_set_port(&m->raddr_rtcp, pl_u32(&port));
+	}
+	else
+		err = EBADMSG;
+
+	return err;
+}
+
+
+static int attr_decode_rtpmap(struct sdp_media *m, const struct pl *pl)
+{
+	struct pl id, name, srate, ch;
+	struct sdp_format *fmt;
+	int err;
+
+	if (!m)
+		return 0;
+
+	if (re_regex(pl->p, pl->l, "[^ ]+ [^/]+/[0-9]+[/]*[^]*",
+		     &id, &name, &srate, NULL, &ch))
+		return EBADMSG;
+
+	fmt = sdp_format_find(&m->rfmtl, &id);
+	if (!fmt)
+		return 0;
+
+	fmt->name = mem_deref(fmt->name);
+
+	err = pl_strdup(&fmt->name, &name);
+	if (err)
+		return err;
+
+	fmt->srate = pl_u32(&srate);
+	fmt->ch = ch.l ? pl_u32(&ch) : 1;
+
+	return 0;
+}
+
+
+static int attr_decode(struct sdp_session *sess, struct sdp_media *m,
+		       enum sdp_dir *dir, const struct pl *pl)
+{
+	struct pl name, val;
+	int err = 0;
+
+	if (re_regex(pl->p, pl->l, "[^:]+:[^]+", &name, &val)) {
+		name = *pl;
+		val  = pl_null;
+	}
+
+	if (!pl_strcmp(&name, "fmtp"))
+		err = attr_decode_fmtp(m, &val);
+
+	else if (!pl_strcmp(&name, "inactive"))
+		*dir = SDP_INACTIVE;
+
+	else if (!pl_strcmp(&name, "recvonly"))
+		*dir = SDP_SENDONLY;
+
+	else if (!pl_strcmp(&name, "rtcp"))
+		err = attr_decode_rtcp(m, &val);
+
+	else if (!pl_strcmp(&name, "rtpmap"))
+		err = attr_decode_rtpmap(m, &val);
+
+	else if (!pl_strcmp(&name, "sendonly"))
+		*dir = SDP_RECVONLY;
+
+	else if (!pl_strcmp(&name, "sendrecv"))
+		*dir = SDP_SENDRECV;
+
+	else
+		err = sdp_attr_add(m ? &m->rattrl : &sess->rattrl,
+				   &name, &val);
+
+	return err;
+}
+
+
+static int bandwidth_decode(int32_t *bwv, const struct pl *pl)
+{
+	struct pl type, bw;
+
+	if (re_regex(pl->p, pl->l, "[^:]+:[0-9]+", &type, &bw))
+		return EBADMSG;
+
+	if (!pl_strcmp(&type, "CT"))
+		bwv[SDP_BANDWIDTH_CT] = pl_u32(&bw);
+
+	else if (!pl_strcmp(&type, "AS"))
+		bwv[SDP_BANDWIDTH_AS] = pl_u32(&bw);
+
+	else if (!pl_strcmp(&type, "RS"))
+		bwv[SDP_BANDWIDTH_RS] = pl_u32(&bw);
+
+	else if (!pl_strcmp(&type, "RR"))
+		bwv[SDP_BANDWIDTH_RR] = pl_u32(&bw);
+
+	else if (!pl_strcmp(&type, "TIAS"))
+		bwv[SDP_BANDWIDTH_TIAS] = pl_u32(&bw);
+
+	return 0;
+}
+
+
+static int conn_decode(struct sa *sa, const struct pl *pl)
+{
+	struct pl v;
+
+	if (re_regex(pl->p, pl->l, "IN IP[46]1 [^ ]+", NULL, &v))
+		return EBADMSG;
+
+	(void)sa_set(sa, &v, sa_port(sa));
+
+	return 0;
+}
+
+
+static int media_decode(struct sdp_media **mp, struct sdp_session *sess,
+			bool offer, const struct pl *pl)
+{
+	struct pl name, port, proto, fmtv, fmt;
+	struct sdp_media *m;
+	int err;
+
+	if (re_regex(pl->p, pl->l, "[a-z]+ [^ ]+ [^ ]+[^]*",
+		     &name, &port, &proto, &fmtv))
+		return EBADMSG;
+
+	m = list_ledata(*mp ? (*mp)->le.next : sess->medial.head);
+	if (!m) {
+		if (!offer)
+			return EPROTO;
+
+		m = sdp_media_find(sess, &name, &proto, true);
+		if (!m) {
+			err = sdp_media_radd(&m, sess, &name, &proto);
+			if (err)
+				return err;
+		}
+		else {
+			list_unlink(&m->le);
+			list_append(&sess->medial, &m->le, m);
+		}
+
+		m->uproto = mem_deref(m->uproto);
+	}
+	else {
+		if (pl_strcmp(&name, m->name))
+			return offer ? ENOTSUP : EPROTO;
+
+		m->uproto = mem_deref(m->uproto);
+
+		if (!sdp_media_proto_cmp(m, &proto, offer)) {
+
+			err = pl_strdup(&m->uproto, &proto);
+			if (err)
+				return err;
+		}
+	}
+
+	while (!re_regex(fmtv.p, fmtv.l, " [^ ]+", &fmt)) {
+
+		pl_advance(&fmtv, fmt.p + fmt.l - fmtv.p);
+
+		err = sdp_format_radd(m, &fmt);
+		if (err)
+			return err;
+	}
+
+	m->raddr = sess->raddr;
+	sa_set_port(&m->raddr, m->uproto ? 0 : pl_u32(&port));
+
+	m->rdir = sess->rdir;
+
+	*mp = m;
+
+	return 0;
+}
+
+
+static int version_decode(const struct pl *pl)
+{
+	return pl_strcmp(pl, "0") ? ENOSYS : 0;
+}
+
+
+/**
+ * Decode an SDP message into an SDP Session
+ *
+ * @param sess  SDP Session
+ * @param mb    Memory buffer containing SDP message
+ * @param offer True if SDP offer, False if SDP answer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_decode(struct sdp_session *sess, struct mbuf *mb, bool offer)
+{
+	struct sdp_media *m;
+	struct pl pl, val;
+	struct le *le;
+	char type = 0;
+	int err = 0;
+
+	if (!sess || !mb)
+		return EINVAL;
+
+	sdp_session_rreset(sess);
+
+	for (le=sess->medial.head; le; le=le->next) {
+
+		m = le->data;
+
+		sdp_media_rreset(m);
+	}
+
+	pl.p = (const char *)mbuf_buf(mb);
+	pl.l = mbuf_get_left(mb);
+
+	m = NULL;
+
+	for (;pl.l && !err; pl.p++, pl.l--) {
+
+		switch (*pl.p) {
+
+		case '\r':
+		case '\n':
+			if (!type)
+				break;
+
+			switch (type) {
+
+			case 'a':
+				err = attr_decode(sess, m,
+						  m ? &m->rdir : &sess->rdir,
+						  &val);
+				break;
+
+			case 'b':
+				err = bandwidth_decode(m? m->rbwv : sess->rbwv,
+						       &val);
+				break;
+
+			case 'c':
+				err = conn_decode(m ? &m->raddr : &sess->raddr,
+						  &val);
+				break;
+
+			case 'm':
+				err = media_decode(&m, sess, offer, &val);
+				break;
+
+			case 'v':
+				err = version_decode(&val);
+				break;
+			}
+
+#if 0
+			if (err)
+				re_printf("** %c='%r': %m\n", type, &val, err);
+#endif
+
+			type = 0;
+			break;
+
+		default:
+			if (type) {
+				val.l++;
+				break;
+			}
+
+			if (pl.l < 2 || *(pl.p + 1) != '=') {
+				err = EBADMSG;
+				break;
+			}
+
+			type  = *pl.p;
+			val.p = pl.p + 2;
+			val.l = 0;
+
+			pl.p += 1;
+			pl.l -= 1;
+			break;
+		}
+	}
+
+	if (err)
+		return err;
+
+	if (type)
+		return EBADMSG;
+
+	for (le=sess->medial.head; le; le=le->next)
+		sdp_media_align_formats(le->data, offer);
+
+	return 0;
+}
+
+
+static int media_encode(const struct sdp_media *m, struct mbuf *mb, bool offer)
+{
+	enum sdp_bandwidth i;
+	const char *proto;
+	int err, supc = 0;
+	bool disabled;
+	struct le *le;
+	uint16_t port;
+
+	for (le=m->lfmtl.head; le; le=le->next) {
+
+		const struct sdp_format *fmt = le->data;
+
+		if (fmt->sup)
+			++supc;
+	}
+
+	if (m->uproto && !offer) {
+		disabled = true;
+		port = 0;
+		proto = m->uproto;
+	}
+	else if (m->disabled || supc == 0 || (!offer && !sa_port(&m->raddr))) {
+		disabled = true;
+		port = 0;
+		proto = m->proto;
+	}
+	else {
+		disabled = false;
+		port = sa_port(&m->laddr);
+		proto = m->proto;
+	}
+
+	err = mbuf_printf(mb, "m=%s %u %s", m->name, port, proto);
+
+	if (disabled) {
+		err |= mbuf_write_str(mb, " 0\r\n");
+		return err;
+	}
+
+	for (le=m->lfmtl.head; le; le=le->next) {
+
+		const struct sdp_format *fmt = le->data;
+
+		if (!fmt->sup)
+			continue;
+
+		err |= mbuf_printf(mb, " %s", fmt->id);
+	}
+
+	err |= mbuf_write_str(mb, "\r\n");
+
+	if (sa_isset(&m->laddr, SA_ADDR)) {
+		const int ipver = sa_af(&m->laddr) == AF_INET ? 4 : 6;
+		err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &m->laddr);
+	}
+
+	for (i=SDP_BANDWIDTH_MIN; i<SDP_BANDWIDTH_MAX; i++) {
+
+		if (m->lbwv[i] < 0)
+			continue;
+
+		err |= mbuf_printf(mb, "b=%s:%i\r\n",
+				   sdp_bandwidth_name(i), m->lbwv[i]);
+	}
+
+	for (le=m->lfmtl.head; le; le=le->next) {
+
+		const struct sdp_format *fmt = le->data;
+
+		if (!fmt->sup || !str_isset(fmt->name))
+			continue;
+
+		err |= mbuf_printf(mb, "a=rtpmap:%s %s/%u",
+				   fmt->id, fmt->name, fmt->srate);
+
+		if (fmt->ch > 1)
+			err |= mbuf_printf(mb, "/%u", fmt->ch);
+
+		err |= mbuf_printf(mb, "\r\n");
+
+		if (str_isset(fmt->params))
+			err |= mbuf_printf(mb, "a=fmtp:%s %s\r\n",
+					   fmt->id, fmt->params);
+		if (fmt->ench)
+			err |= fmt->ench(mb, fmt, offer, fmt->data);
+	}
+
+	if (sa_isset(&m->laddr_rtcp, SA_ALL))
+		err |= mbuf_printf(mb, "a=rtcp:%u IN IP%d %j\r\n",
+				   sa_port(&m->laddr_rtcp),
+				   (AF_INET == sa_af(&m->laddr_rtcp)) ? 4 : 6,
+				   &m->laddr_rtcp);
+	else if (sa_isset(&m->laddr_rtcp, SA_PORT))
+		err |= mbuf_printf(mb, "a=rtcp:%u\r\n",
+				   sa_port(&m->laddr_rtcp));
+
+	err |= mbuf_printf(mb, "a=%s\r\n",
+			   sdp_dir_name(offer ? m->ldir : m->ldir & m->rdir));
+
+	for (le = m->lattrl.head; le; le = le->next)
+		err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data);
+
+	if (m->ench)
+		err |= m->ench(mb, offer, m->arg);
+
+	return err;
+}
+
+
+/**
+ * Encode an SDP Session into a memory buffer
+ *
+ * @param mbp   Pointer to allocated memory buffer
+ * @param sess  SDP Session
+ * @param offer True if SDP Offer, False if SDP Answer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_encode(struct mbuf **mbp, struct sdp_session *sess, bool offer)
+{
+	const int ipver = sa_af(&sess->laddr) == AF_INET ? 4 : 6;
+	enum sdp_bandwidth i;
+	struct mbuf *mb;
+	struct le *le;
+	int err;
+
+	if (!mbp || !sess)
+		return EINVAL;
+
+	mb = mbuf_alloc(512);
+	if (!mb)
+		return ENOMEM;
+
+	err  = mbuf_printf(mb, "v=%u\r\n", SDP_VERSION);
+	err |= mbuf_printf(mb, "o=- %u %u IN IP%d %j\r\n",
+			   sess->id, sess->ver++, ipver, &sess->laddr);
+	err |= mbuf_write_str(mb, "s=-\r\n");
+	err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &sess->laddr);
+
+	for (i=SDP_BANDWIDTH_MIN; i<SDP_BANDWIDTH_MAX; i++) {
+
+		if (sess->lbwv[i] < 0)
+			continue;
+
+		err |= mbuf_printf(mb, "b=%s:%i\r\n",
+				   sdp_bandwidth_name(i), sess->lbwv[i]);
+	}
+
+	err |= mbuf_write_str(mb, "t=0 0\r\n");
+
+	for (le = sess->lattrl.head; le; le = le->next)
+		err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data);
+
+	for (le=sess->lmedial.head; offer && le;) {
+
+		struct sdp_media *m = le->data;
+
+		le = le->next;
+
+		if (m->disabled)
+			continue;
+
+		list_unlink(&m->le);
+		list_append(&sess->medial, &m->le, m);
+	}
+
+	for (le=sess->medial.head; le; le=le->next) {
+
+		struct sdp_media *m = le->data;
+
+		err |= media_encode(m, mb, offer);
+	}
+
+	mb->pos = 0;
+
+	if (err)
+		mem_deref(mb);
+	else
+		*mbp = mb;
+
+	return err;
+}
diff --git a/src/sdp/sdp.h b/src/sdp/sdp.h
new file mode 100644
index 0000000..f0588d1
--- /dev/null
+++ b/src/sdp/sdp.h
@@ -0,0 +1,87 @@
+/**
+ * @file sdp.h  Internal SDP interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+	RTP_DYNPT_START =  96,
+	RTP_DYNPT_END   = 127,
+};
+
+
+struct sdp_session {
+	struct list lmedial;
+	struct list medial;
+	struct list lattrl;
+	struct list rattrl;
+	struct sa laddr;
+	struct sa raddr;
+	int32_t lbwv[SDP_BANDWIDTH_MAX];
+	int32_t rbwv[SDP_BANDWIDTH_MAX];
+	uint32_t id;
+	uint32_t ver;
+	enum sdp_dir rdir;
+};
+
+struct sdp_media {
+	struct le le;
+	struct list lfmtl;
+	struct list rfmtl;
+	struct list lattrl;
+	struct list rattrl;
+	struct sa laddr;
+	struct sa raddr;
+	struct sa laddr_rtcp;
+	struct sa raddr_rtcp;
+	int32_t lbwv[SDP_BANDWIDTH_MAX];
+	int32_t rbwv[SDP_BANDWIDTH_MAX];
+	char *name;
+	char *proto;
+	char *protov[8];
+	char *uproto;           /* unsupported protocol */
+	sdp_media_enc_h *ench;
+	void *arg;
+	enum sdp_dir ldir;
+	enum sdp_dir rdir;
+	bool fmt_ignore;
+	bool disabled;
+	int dynpt;
+};
+
+
+/* session */
+void sdp_session_rreset(struct sdp_session *sess);
+
+
+/* media */
+int  sdp_media_radd(struct sdp_media **mp, struct sdp_session *sess,
+		    const struct pl *name, const struct pl *proto);
+void sdp_media_rreset(struct sdp_media *m);
+bool sdp_media_proto_cmp(struct sdp_media *m, const struct pl *proto,
+			 bool update);
+struct sdp_media *sdp_media_find(const struct sdp_session *sess,
+				 const struct pl *name,
+				 const struct pl *proto,
+				 bool update_proto);
+void sdp_media_align_formats(struct sdp_media *m, bool offer);
+
+
+/* format */
+int  sdp_format_radd(struct sdp_media *m, const struct pl *id);
+struct sdp_format *sdp_format_find(const struct list *lst,
+				   const struct pl *id);
+
+
+/* attribute */
+struct sdp_attr;
+
+int  sdp_attr_add(struct list *lst, struct pl *name, struct pl *val);
+int  sdp_attr_addv(struct list *lst, const char *name, const char *val,
+		   va_list ap);
+void sdp_attr_del(const struct list *lst, const char *name);
+const char *sdp_attr_apply(const struct list *lst, const char *name,
+			   sdp_attr_h *attrh, void *arg);
+int sdp_attr_print(struct re_printf *pf, const struct sdp_attr *attr);
+int sdp_attr_debug(struct re_printf *pf, const struct sdp_attr *attr);
diff --git a/src/sdp/session.c b/src/sdp/session.c
new file mode 100644
index 0000000..f74cfe0
--- /dev/null
+++ b/src/sdp/session.c
@@ -0,0 +1,301 @@
+/**
+ * @file sdp/session.c  SDP Session
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include <re_sys.h>
+#include "sdp.h"
+
+
+static void destructor(void *arg)
+{
+	struct sdp_session *sess = arg;
+
+	list_flush(&sess->lmedial);
+	list_flush(&sess->medial);
+	list_flush(&sess->rattrl);
+	list_flush(&sess->lattrl);
+}
+
+
+/**
+ * Allocate a new SDP Session
+ *
+ * @param sessp Pointer to allocated SDP Session object
+ * @param laddr Local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_session_alloc(struct sdp_session **sessp, const struct sa *laddr)
+{
+	struct sdp_session *sess;
+	int err = 0, i;
+
+	if (!sessp || !laddr)
+		return EINVAL;
+
+	sess = mem_zalloc(sizeof(*sess), destructor);
+	if (!sess)
+		return ENOMEM;
+
+	sess->laddr = *laddr;
+	sess->id    = rand_u32();
+	sess->ver   = rand_u32() & 0x7fffffff;
+	sess->rdir  = SDP_SENDRECV;
+
+	sa_init(&sess->raddr, AF_INET);
+
+	for (i=0; i<SDP_BANDWIDTH_MAX; i++) {
+		sess->lbwv[i] = -1;
+		sess->rbwv[i] = -1;
+	}
+
+	if (err)
+		mem_deref(sess);
+	else
+		*sessp = sess;
+
+	return err;
+}
+
+
+/**
+ * Reset the remote side of an SDP Session
+ *
+ * @param sess SDP Session
+ */
+void sdp_session_rreset(struct sdp_session *sess)
+{
+	int i;
+
+	if (!sess)
+		return;
+
+	sa_init(&sess->raddr, AF_INET);
+
+	list_flush(&sess->rattrl);
+
+	sess->rdir = SDP_SENDRECV;
+
+	for (i=0; i<SDP_BANDWIDTH_MAX; i++)
+		sess->rbwv[i] = -1;
+}
+
+
+/**
+ * Set the local network address of an SDP Session
+ *
+ * @param sess  SDP Session
+ * @param laddr Local network address
+ */
+void sdp_session_set_laddr(struct sdp_session *sess, const struct sa *laddr)
+{
+	if (!sess || !laddr)
+		return;
+
+	sess->laddr = *laddr;
+}
+
+
+/**
+ * Set the local bandwidth of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param type Bandwidth type
+ * @param bw   Bandwidth value
+ */
+void sdp_session_set_lbandwidth(struct sdp_session *sess,
+				enum sdp_bandwidth type, int32_t bw)
+{
+	if (!sess || type >= SDP_BANDWIDTH_MAX)
+		return;
+
+	sess->lbwv[type] = bw;
+}
+
+
+/**
+ * Set a local attribute of an SDP Session
+ *
+ * @param sess    SDP Session
+ * @param replace True to replace any existing attributes, false to append
+ * @param name    Attribute name
+ * @param value   Formatted attribute value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_session_set_lattr(struct sdp_session *sess, bool replace,
+			  const char *name, const char *value, ...)
+{
+	va_list ap;
+	int err;
+
+	if (!sess || !name)
+		return EINVAL;
+
+	if (replace)
+		sdp_attr_del(&sess->lattrl, name);
+
+	va_start(ap, value);
+	err = sdp_attr_addv(&sess->lattrl, name, value, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Delete a local attribute of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param name Attribute name
+ */
+void sdp_session_del_lattr(struct sdp_session *sess, const char *name)
+{
+	if (!sess || !name)
+		return;
+
+	sdp_attr_del(&sess->lattrl, name);
+}
+
+
+/**
+ * Get the local bandwidth of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param type Bandwidth type
+ *
+ * @return Bandwidth value
+ */
+int32_t sdp_session_lbandwidth(const struct sdp_session *sess,
+			       enum sdp_bandwidth type)
+{
+	if (!sess || type >= SDP_BANDWIDTH_MAX)
+		return 0;
+
+	return sess->lbwv[type];
+}
+
+
+/**
+ * Get the remote bandwidth of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param type Bandwidth type
+ *
+ * @return Bandwidth value
+ */
+int32_t sdp_session_rbandwidth(const struct sdp_session *sess,
+				enum sdp_bandwidth type)
+{
+	if (!sess || type >= SDP_BANDWIDTH_MAX)
+		return 0;
+
+	return sess->rbwv[type];
+}
+
+
+/**
+ * Get a remote attribute of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param name Attribute name
+ *
+ * @return Attribute value if exist, NULL if not exist
+ */
+const char *sdp_session_rattr(const struct sdp_session *sess, const char *name)
+{
+	if (!sess || !name)
+		return NULL;
+
+	return sdp_attr_apply(&sess->rattrl, name, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler of all matching remote attributes
+ *
+ * @param sess  SDP Session
+ * @param name  Attribute name
+ * @param attrh Attribute handler
+ * @param arg   Handler argument
+ *
+ * @return Attribute value if match
+ */
+const char *sdp_session_rattr_apply(const struct sdp_session *sess,
+				    const char *name,
+				    sdp_attr_h *attrh, void *arg)
+{
+	if (!sess)
+		return NULL;
+
+	return sdp_attr_apply(&sess->rattrl, name, attrh, arg);
+}
+
+
+/**
+ * Get the list of media-lines from an SDP Session
+ *
+ * @param sess  SDP Session
+ * @param local True for local, False for remote
+ *
+ * @return List of media-lines
+ */
+const struct list *sdp_session_medial(const struct sdp_session *sess,
+				      bool local)
+{
+	if (!sess)
+		return NULL;
+
+	return local ? &sess->lmedial : &sess->medial;
+}
+
+
+/**
+ * Print SDP Session debug information
+ *
+ * @param pf   Print function for output
+ * @param sess SDP Session
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_session_debug(struct re_printf *pf, const struct sdp_session *sess)
+{
+	struct le *le;
+	int err;
+
+	if (!sess)
+		return 0;
+
+	err = re_hprintf(pf, "SDP session\n");
+
+	err |= re_hprintf(pf, "  local attributes:\n");
+
+	for (le=sess->lattrl.head; le; le=le->next)
+		err |= re_hprintf(pf, "    %H\n", sdp_attr_debug, le->data);
+
+	err |= re_hprintf(pf, "  remote attributes:\n");
+
+	for (le=sess->rattrl.head; le; le=le->next)
+		err |= re_hprintf(pf, "    %H\n", sdp_attr_debug, le->data);
+
+	err |= re_hprintf(pf, "session media:\n");
+
+	for (le=sess->medial.head; le; le=le->next)
+		err |= sdp_media_debug(pf, le->data);
+
+	err |= re_hprintf(pf, "local media:\n");
+
+	for (le=sess->lmedial.head; le; le=le->next)
+		err |= sdp_media_debug(pf, le->data);
+
+	return err;
+}
diff --git a/src/sdp/str.c b/src/sdp/str.c
new file mode 100644
index 0000000..e50d842
--- /dev/null
+++ b/src/sdp/str.c
@@ -0,0 +1,65 @@
+/**
+ * @file sdp/str.c  SDP strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+
+
+const char sdp_attr_fmtp[]     = "fmtp";      /**< fmtp                 */
+const char sdp_attr_maxptime[] = "maxptime";  /**< maxptime             */
+const char sdp_attr_ptime[]    = "ptime";     /**< ptime                */
+const char sdp_attr_rtcp[]     = "rtcp";      /**< rtcp                 */
+const char sdp_attr_rtpmap[]   = "rtpmap";    /**< rtpmap               */
+
+const char sdp_media_audio[]   = "audio";     /**< Media type 'audio'   */
+const char sdp_media_video[]   = "video";     /**< Media type 'video'   */
+const char sdp_media_text[]    = "text";      /**< Media type 'text'    */
+
+const char sdp_proto_rtpavp[]  = "RTP/AVP";   /**< RTP Profile          */
+const char sdp_proto_rtpsavp[] = "RTP/SAVP";  /**< Secure RTP Profile   */
+
+
+/**
+ * Get the SDP media direction name
+ *
+ * @param dir Media direction
+ *
+ * @return Name of media direction
+ */
+const char *sdp_dir_name(enum sdp_dir dir)
+{
+	switch (dir) {
+
+	case SDP_INACTIVE: return "inactive";
+	case SDP_RECVONLY: return "recvonly";
+	case SDP_SENDONLY: return "sendonly";
+	case SDP_SENDRECV: return "sendrecv";
+	default:           return "??";
+	}
+}
+
+
+/**
+ * Get the SDP bandwidth name
+ *
+ * @param type Bandwidth type
+ *
+ * @return Bandwidth name
+ */
+const char *sdp_bandwidth_name(enum sdp_bandwidth type)
+{
+	switch (type) {
+
+	case SDP_BANDWIDTH_CT:   return "CT";
+	case SDP_BANDWIDTH_AS:   return "AS";
+	case SDP_BANDWIDTH_RS:   return "RS";
+	case SDP_BANDWIDTH_RR:   return "RR";
+	case SDP_BANDWIDTH_TIAS: return "TIAS";
+	default:                 return "??";
+	}
+}
diff --git a/src/sdp/util.c b/src/sdp/util.c
new file mode 100644
index 0000000..754f5b8
--- /dev/null
+++ b/src/sdp/util.c
@@ -0,0 +1,50 @@
+/**
+ * @file sdp/util.c  SDP utility functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+
+
+/**
+ * Decode RTP Header Extension SDP attribute value
+ *
+ * @param ext Extension-map object
+ * @param val SDP attribute value
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sdp_extmap_decode(struct sdp_extmap *ext, const char *val)
+{
+	struct pl id, dir;
+
+	if (!ext || !val)
+		return EINVAL;
+
+	if (re_regex(val, strlen(val), "[0-9]+[/]*[a-z]* [^ ]+[ ]*[^ ]*",
+		     &id, NULL, &dir, &ext->name, NULL, &ext->attrs))
+		return EBADMSG;
+
+	ext->dir_set = false;
+	ext->dir = SDP_SENDRECV;
+
+	if (pl_isset(&dir)) {
+
+		ext->dir_set = true;
+
+		if      (!pl_strcmp(&dir, "sendonly")) ext->dir = SDP_SENDONLY;
+		else if (!pl_strcmp(&dir, "sendrecv")) ext->dir = SDP_SENDRECV;
+		else if (!pl_strcmp(&dir, "recvonly")) ext->dir = SDP_RECVONLY;
+		else if (!pl_strcmp(&dir, "inactive")) ext->dir = SDP_INACTIVE;
+		else ext->dir_set = false;
+	}
+
+	ext->id = pl_u32(&id);
+
+	return 0;
+}