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/sipsess/accept.c b/src/sipsess/accept.c
new file mode 100644
index 0000000..b8d02f8
--- /dev/null
+++ b/src/sipsess/accept.c
@@ -0,0 +1,246 @@
+/**
+ * @file accept.c  SIP Session Accept
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void cancel_handler(void *arg)
+{
+	struct sipsess *sess = arg;
+
+	(void)sip_treply(&sess->st, sess->sip, sess->msg,
+			 487, "Request Terminated");
+
+	sess->peerterm = true;
+
+	if (sess->terminated)
+		return;
+
+	sipsess_terminate(sess, ECONNRESET, NULL);
+}
+
+
+/**
+ * Accept an incoming SIP Session connection
+ *
+ * @param sessp     Pointer to allocated SIP Session
+ * @param sock      SIP Session socket
+ * @param msg       Incoming SIP message
+ * @param scode     Response status code
+ * @param reason    Response reason phrase
+ * @param cuser     Contact username or URI
+ * @param ctype     Session content-type
+ * @param desc      Content description (e.g. SDP)
+ * @param authh     SIP Authentication handler
+ * @param aarg      Authentication handler argument
+ * @param aref      True to mem_ref() aarg
+ * @param offerh    Session offer handler
+ * @param answerh   Session answer handler
+ * @param estabh    Session established handler
+ * @param infoh     Session info handler
+ * @param referh    Session refer handler
+ * @param closeh    Session close handler
+ * @param arg       Handler argument
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_accept(struct sipsess **sessp, struct sipsess_sock *sock,
+		   const struct sip_msg *msg, uint16_t scode,
+		   const char *reason, const char *cuser, const char *ctype,
+		   struct mbuf *desc,
+		   sip_auth_h *authh, void *aarg, bool aref,
+		   sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+		   sipsess_estab_h *estabh, sipsess_info_h *infoh,
+		   sipsess_refer_h *referh, sipsess_close_h *closeh,
+		   void *arg, const char *fmt, ...)
+{
+	struct sipsess *sess;
+	va_list ap;
+	int err;
+
+	if (!sessp || !sock || !msg || scode < 101 || scode > 299 ||
+	    !cuser || !ctype)
+		return EINVAL;
+
+	err = sipsess_alloc(&sess, sock, cuser, ctype, NULL, authh, aarg, aref,
+			    offerh, answerh, NULL, estabh, infoh, referh,
+			    closeh, arg);
+	if (err)
+		return err;
+
+	err = sip_dialog_accept(&sess->dlg, msg);
+	if (err)
+		goto out;
+
+	hash_append(sock->ht_sess,
+		    hash_joaat_str(sip_dialog_callid(sess->dlg)),
+		    &sess->he, sess);
+
+	sess->msg = mem_ref((void *)msg);
+
+	err = sip_strans_alloc(&sess->st, sess->sip, msg, cancel_handler,
+			       sess);
+	if (err)
+		goto out;
+
+	va_start(ap, fmt);
+
+	if (scode >= 200)
+		err = sipsess_reply_2xx(sess, msg, scode, reason, desc,
+					fmt, &ap);
+	else {
+		struct sip_contact contact;
+
+		sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp);
+
+		err = sip_treplyf(&sess->st, NULL, sess->sip,
+				  msg, true, scode, reason,
+				  "%H"
+				  "%v"
+				  "%s%s%s"
+				  "Content-Length: %zu\r\n"
+				  "\r\n"
+				  "%b",
+				  sip_contact_print, &contact,
+				  fmt, &ap,
+				  desc ? "Content-Type: " : "",
+				  desc ? sess->ctype : "",
+				  desc ? "\r\n" : "",
+				  desc ? mbuf_get_left(desc) : (size_t)0,
+				  desc ? mbuf_buf(desc) : NULL,
+				  desc ? mbuf_get_left(desc) : (size_t)0);
+	}
+
+	va_end(ap);
+
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(sess);
+	else
+		*sessp = sess;
+
+	return err;
+}
+
+
+/**
+ * Send progress response
+ *
+ * @param sess      SIP Session
+ * @param scode     Response status code
+ * @param reason    Response reason phrase
+ * @param desc      Content description (e.g. SDP)
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_progress(struct sipsess *sess, uint16_t scode, const char *reason,
+		     struct mbuf *desc, const char *fmt, ...)
+{
+	struct sip_contact contact;
+	va_list ap;
+	int err;
+
+	if (!sess || !sess->st || !sess->msg || scode < 101 || scode > 199)
+		return EINVAL;
+
+	va_start(ap, fmt);
+
+	sip_contact_set(&contact, sess->cuser, &sess->msg->dst, sess->msg->tp);
+
+	err = sip_treplyf(&sess->st, NULL, sess->sip, sess->msg, true,
+			  scode, reason,
+			  "%H"
+			  "%v"
+			  "%s%s%s"
+			  "Content-Length: %zu\r\n"
+			  "\r\n"
+			  "%b",
+			  sip_contact_print, &contact,
+			  fmt, &ap,
+			  desc ? "Content-Type: " : "",
+			  desc ? sess->ctype : "",
+			  desc ? "\r\n" : "",
+			  desc ? mbuf_get_left(desc) : (size_t)0,
+			  desc ? mbuf_buf(desc) : NULL,
+			  desc ? mbuf_get_left(desc) : (size_t)0);
+
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Answer an incoming SIP Session connection
+ *
+ * @param sess      SIP Session
+ * @param scode     Response status code
+ * @param reason    Response reason phrase
+ * @param desc      Content description (e.g. SDP)
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_answer(struct sipsess *sess, uint16_t scode, const char *reason,
+		   struct mbuf *desc, const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	if (!sess || !sess->st || !sess->msg || scode < 200 || scode > 299)
+		return EINVAL;
+
+	va_start(ap, fmt);
+	err = sipsess_reply_2xx(sess, sess->msg, scode, reason, desc,
+				fmt, &ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Reject an incoming SIP Session connection
+ *
+ * @param sess      SIP Session
+ * @param scode     Response status code
+ * @param reason    Response reason phrase
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_reject(struct sipsess *sess, uint16_t scode, const char *reason,
+		   const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	if (!sess || !sess->st || !sess->msg || scode < 300)
+		return EINVAL;
+
+	va_start(ap, fmt);
+	err = sip_treplyf(&sess->st, NULL, sess->sip, sess->msg, false,
+			  scode, reason, fmt ? "%v" : NULL, fmt, &ap);
+	va_end(ap);
+
+	return err;
+}
diff --git a/src/sipsess/ack.c b/src/sipsess/ack.c
new file mode 100644
index 0000000..4a9be68
--- /dev/null
+++ b/src/sipsess/ack.c
@@ -0,0 +1,147 @@
+/**
+ * @file ack.c  SIP Session ACK
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+struct sipsess_ack {
+	struct le he;
+	struct tmr tmr;
+	struct sa dst;
+	struct sip_request *req;
+	struct sip_dialog *dlg;
+	struct mbuf *mb;
+	enum sip_transp tp;
+	uint32_t cseq;
+};
+
+
+static void destructor(void *arg)
+{
+	struct sipsess_ack *ack = arg;
+
+	hash_unlink(&ack->he);
+	tmr_cancel(&ack->tmr);
+	mem_deref(ack->req);
+	mem_deref(ack->dlg);
+	mem_deref(ack->mb);
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sipsess_ack *ack = arg;
+
+	mem_deref(ack);
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+			const struct sa *dst, struct mbuf *mb, void *arg)
+{
+	struct sipsess_ack *ack = arg;
+	(void)src;
+
+	mem_deref(ack->mb);
+	ack->mb = mem_ref(mb);
+	ack->dst = *dst;
+	ack->tp  = tp;
+
+	tmr_start(&ack->tmr, 64 * SIP_T1, tmr_handler, ack);
+
+	return 0;
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	struct sipsess_ack *ack = arg;
+	(void)err;
+	(void)msg;
+
+	mem_deref(ack);
+}
+
+
+int sipsess_ack(struct sipsess_sock *sock, struct sip_dialog *dlg,
+		uint32_t cseq, struct sip_auth *auth,
+		const char *ctype, struct mbuf *desc)
+{
+	struct sipsess_ack *ack;
+	int err;
+
+	ack = mem_zalloc(sizeof(*ack), destructor);
+	if (!ack)
+		return ENOMEM;
+
+	hash_append(sock->ht_ack,
+		    hash_joaat_str(sip_dialog_callid(dlg)),
+		    &ack->he, ack);
+
+	ack->dlg  = mem_ref(dlg);
+	ack->cseq = cseq;
+
+	err = sip_drequestf(&ack->req, sock->sip, false, "ACK", dlg, cseq,
+			    auth, send_handler, resp_handler, ack,
+			    "%s%s%s"
+			    "Content-Length: %zu\r\n"
+			    "\r\n"
+			    "%b",
+			    desc ? "Content-Type: " : "",
+			    desc ? ctype : "",
+			    desc ? "\r\n" : "",
+			    desc ? mbuf_get_left(desc) : (size_t)0,
+			    desc ? mbuf_buf(desc) : NULL,
+			    desc ? mbuf_get_left(desc) : (size_t)0);
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(ack);
+
+	return err;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct sipsess_ack *ack = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (!sip_dialog_cmp(ack->dlg, msg))
+		return false;
+
+	if (ack->cseq != msg->cseq.num)
+		return false;
+
+	return true;
+}
+
+
+int sipsess_ack_again(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+	struct sipsess_ack *ack;
+
+	ack = list_ledata(hash_lookup(sock->ht_ack,
+				      hash_joaat_pl(&msg->callid),
+				      cmp_handler, (void *)msg));
+	if (!ack)
+		return ENOENT;
+
+	return sip_send(sock->sip, NULL, ack->tp, &ack->dst, ack->mb);
+}
diff --git a/src/sipsess/close.c b/src/sipsess/close.c
new file mode 100644
index 0000000..b680a9a
--- /dev/null
+++ b/src/sipsess/close.c
@@ -0,0 +1,74 @@
+/**
+ * @file close.c  SIP Session Close
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void bye_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	struct sipsess *sess = arg;
+
+	if (err || sip_request_loops(&sess->ls, msg->scode))
+		goto out;
+
+	if (msg->scode < 200) {
+		return;
+	}
+	else if (msg->scode < 300) {
+		;
+	}
+	else {
+		if (sess->peerterm)
+			goto out;
+
+		switch (msg->scode) {
+
+		case 401:
+		case 407:
+			err = sip_auth_authenticate(sess->auth, msg);
+			if (err)
+				break;
+
+			err = sipsess_bye(sess, false);
+			if (err)
+				break;
+
+			return;
+		}
+	}
+
+ out:
+	mem_deref(sess);
+}
+
+
+int sipsess_bye(struct sipsess *sess, bool reset_ls)
+{
+	if (sess->req)
+		return EPROTO;
+
+	if (reset_ls)
+		sip_loopstate_reset(&sess->ls);
+
+	return sip_drequestf(&sess->req, sess->sip, true, "BYE",
+			     sess->dlg, 0, sess->auth,
+			     NULL, bye_resp_handler, sess,
+			     "%s"
+			     "Content-Length: 0\r\n"
+			     "\r\n",
+			     sess->close_hdrs);
+}
diff --git a/src/sipsess/connect.c b/src/sipsess/connect.c
new file mode 100644
index 0000000..5f6317e
--- /dev/null
+++ b/src/sipsess/connect.c
@@ -0,0 +1,245 @@
+/**
+ * @file connect.c  SIP Session Connect
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static int invite(struct sipsess *sess);
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+			const struct sa *dst, struct mbuf *mb, void *arg)
+{
+	struct sip_contact contact;
+	struct sipsess *sess = arg;
+	(void)dst;
+
+	sip_contact_set(&contact, sess->cuser, src, tp);
+
+	return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static void invite_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	struct sipsess *sess = arg;
+	struct mbuf *desc = NULL;
+
+	if (err || sip_request_loops(&sess->ls, msg->scode))
+		goto out;
+
+	if (msg->scode < 200) {
+		sess->progrh(msg, sess->arg);
+		return;
+	}
+	else if (msg->scode < 300) {
+
+		sess->hdrs = mem_deref(sess->hdrs);
+
+		err = sip_dialog_create(sess->dlg, msg);
+		if (err)
+			goto out;
+
+		if (sess->sent_offer)
+			err = sess->answerh(msg, sess->arg);
+		else {
+			sess->modify_pending = false;
+			err = sess->offerh(&desc, msg, sess->arg);
+		}
+
+		err |= sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
+				   sess->auth, sess->ctype, desc);
+
+		sess->established = true;
+		mem_deref(desc);
+
+		if (err || sess->terminated)
+			goto out;
+
+		if (sess->modify_pending)
+			(void)sipsess_reinvite(sess, true);
+		else
+			sess->desc = mem_deref(sess->desc);
+
+		sess->estabh(msg, sess->arg);
+		return;
+	}
+	else if (msg->scode < 400) {
+
+		/* Redirect to first Contact */
+
+		if (sess->terminated)
+			goto out;
+
+		err = sip_dialog_update(sess->dlg, msg);
+		if (err)
+			goto out;
+
+		err = invite(sess);
+		if (err)
+			goto out;
+
+		return;
+	}
+	else {
+		if (sess->terminated)
+			goto out;
+
+		switch (msg->scode) {
+
+		case 401:
+		case 407:
+			err = sip_auth_authenticate(sess->auth, msg);
+			if (err) {
+				err = (err == EAUTH) ? 0 : err;
+				break;
+			}
+
+			err = invite(sess);
+			if (err)
+				break;
+
+			return;
+		}
+	}
+
+ out:
+	if (!sess->terminated)
+		sipsess_terminate(sess, err, msg);
+	else
+		mem_deref(sess);
+}
+
+
+static int invite(struct sipsess *sess)
+{
+	sess->sent_offer = sess->desc ? true : false;
+	sess->modify_pending = false;
+
+	return sip_drequestf(&sess->req, sess->sip, true, "INVITE",
+			     sess->dlg, 0, sess->auth,
+			     send_handler, invite_resp_handler, sess,
+			     "%b"
+			     "%s%s%s"
+			     "Content-Length: %zu\r\n"
+			     "\r\n"
+			     "%b",
+			     sess->hdrs ? mbuf_buf(sess->hdrs) : NULL,
+			     sess->hdrs ? mbuf_get_left(sess->hdrs) :(size_t)0,
+			     sess->desc ? "Content-Type: " : "",
+			     sess->desc ? sess->ctype : "",
+			     sess->desc ? "\r\n" : "",
+			     sess->desc ? mbuf_get_left(sess->desc) :(size_t)0,
+			     sess->desc ? mbuf_buf(sess->desc) : NULL,
+			     sess->desc ? mbuf_get_left(sess->desc):(size_t)0);
+}
+
+
+/**
+ * Connect to a remote SIP useragent
+ *
+ * @param sessp     Pointer to allocated SIP Session
+ * @param sock      SIP Session socket
+ * @param to_uri    To SIP uri
+ * @param from_name From display name
+ * @param from_uri  From SIP uri
+ * @param cuser     Contact username or URI
+ * @param routev    Outbound route vector
+ * @param routec    Outbound route vector count
+ * @param ctype     Session content-type
+ * @param desc      Content description (e.g. SDP)
+ * @param authh     SIP Authentication handler
+ * @param aarg      Authentication handler argument
+ * @param aref      True to mem_ref() aarg
+ * @param offerh    Session offer handler
+ * @param answerh   Session answer handler
+ * @param progrh    Session progress handler
+ * @param estabh    Session established handler
+ * @param infoh     Session info handler
+ * @param referh    Session refer handler
+ * @param closeh    Session close handler
+ * @param arg       Handler argument
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_connect(struct sipsess **sessp, struct sipsess_sock *sock,
+		    const char *to_uri, const char *from_name,
+		    const char *from_uri, const char *cuser,
+		    const char *routev[], uint32_t routec,
+		    const char *ctype, struct mbuf *desc,
+		    sip_auth_h *authh, void *aarg, bool aref,
+		    sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+		    sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+		    sipsess_info_h *infoh, sipsess_refer_h *referh,
+		    sipsess_close_h *closeh, void *arg, const char *fmt, ...)
+{
+	struct sipsess *sess;
+	int err;
+
+	if (!sessp || !sock || !to_uri || !from_uri || !cuser || !ctype)
+		return EINVAL;
+
+	err = sipsess_alloc(&sess, sock, cuser, ctype, desc, authh, aarg, aref,
+			    offerh, answerh, progrh, estabh, infoh, referh,
+			    closeh, arg);
+	if (err)
+		return err;
+
+	/* Custom SIP headers */
+	if (fmt) {
+		va_list ap;
+
+		sess->hdrs = mbuf_alloc(256);
+		if (!sess->hdrs) {
+			err = ENOMEM;
+			goto out;
+		}
+
+		va_start(ap, fmt);
+		err = mbuf_vprintf(sess->hdrs, fmt, ap);
+		sess->hdrs->pos = 0;
+		va_end(ap);
+
+		if (err)
+			goto out;
+	}
+
+	sess->owner = true;
+
+	err = sip_dialog_alloc(&sess->dlg, to_uri, to_uri, from_name,
+			       from_uri, routev, routec);
+	if (err)
+		goto out;
+
+	hash_append(sock->ht_sess,
+		    hash_joaat_str(sip_dialog_callid(sess->dlg)),
+		    &sess->he, sess);
+
+	err = invite(sess);
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(sess);
+	else
+		*sessp = sess;
+
+	return err;
+}
diff --git a/src/sipsess/info.c b/src/sipsess/info.c
new file mode 100644
index 0000000..e30bc0d
--- /dev/null
+++ b/src/sipsess/info.c
@@ -0,0 +1,123 @@
+/**
+ * @file info.c  SIP Session Info
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static int info_request(struct sipsess_request *req);
+
+
+static void info_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	struct sipsess_request *req = arg;
+
+	if (err || sip_request_loops(&req->ls, msg->scode))
+		goto out;
+
+	if (msg->scode < 200) {
+		return;
+	}
+	else if (msg->scode < 300) {
+		;
+	}
+	else {
+		if (req->sess->terminated)
+			goto out;
+
+		switch (msg->scode) {
+
+		case 401:
+		case 407:
+			err = sip_auth_authenticate(req->sess->auth, msg);
+			if (err) {
+				err = (err == EAUTH) ? 0 : err;
+				break;
+			}
+
+			err = info_request(req);
+			if (err)
+				break;
+
+			return;
+
+		case 408:
+		case 481:
+			sipsess_terminate(req->sess, 0, msg);
+			break;
+		}
+	}
+
+ out:
+	if (!req->sess->terminated) {
+		if (err == ETIMEDOUT)
+			sipsess_terminate(req->sess, err, NULL);
+		else
+			req->resph(err, msg, req->arg);
+	}
+
+	mem_deref(req);
+}
+
+
+static int info_request(struct sipsess_request *req)
+{
+	return sip_drequestf(&req->req, req->sess->sip, true, "INFO",
+			     req->sess->dlg, 0, req->sess->auth,
+			     NULL, info_resp_handler, req,
+			     "Content-Type: %s\r\n"
+			     "Content-Length: %zu\r\n"
+			     "\r\n"
+			     "%b",
+			     req->ctype,
+			     mbuf_get_left(req->body),
+			     mbuf_buf(req->body), mbuf_get_left(req->body));
+}
+
+
+/**
+ * Send a SIP INFO request in the SIP Session
+ *
+ * @param sess      SIP Session
+ * @param ctype     Content-type
+ * @param body      Content description (e.g. SDP)
+ * @param resph     Response handler
+ * @param arg       Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_info(struct sipsess *sess, const char *ctype, struct mbuf *body,
+		 sip_resp_h *resph, void *arg)
+{
+	struct sipsess_request *req;
+	int err;
+
+	if (!sess || sess->terminated || !ctype || !body)
+		return EINVAL;
+
+	if (!sip_dialog_established(sess->dlg))
+		return ENOTCONN;
+
+	err = sipsess_request_alloc(&req, sess, ctype, body, resph, arg);
+	if (err)
+		return err;
+
+	err = info_request(req);
+	if (err)
+		mem_deref(req);
+
+	return err;
+}
diff --git a/src/sipsess/listen.c b/src/sipsess/listen.c
new file mode 100644
index 0000000..86a0ece
--- /dev/null
+++ b/src/sipsess/listen.c
@@ -0,0 +1,367 @@
+/**
+ * @file sipsess/listen.c  SIP Session Listen
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void destructor(void *arg)
+{
+	struct sipsess_sock *sock = arg;
+
+	mem_deref(sock->lsnr_resp);
+	mem_deref(sock->lsnr_req);
+	hash_flush(sock->ht_sess);
+	mem_deref(sock->ht_sess);
+	hash_flush(sock->ht_ack);
+	mem_deref(sock->ht_ack);
+}
+
+
+static void internal_connect_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sipsess_sock *sock = arg;
+
+	(void)sip_treply(NULL, sock->sip, msg, 486, "Busy Here");
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct sipsess *sess = le->data;
+	const struct sip_msg *msg = arg;
+
+	return sip_dialog_cmp(sess->dlg, msg);
+}
+
+
+static struct sipsess *sipsess_find(struct sipsess_sock *sock,
+				    const struct sip_msg *msg)
+{
+	return list_ledata(hash_lookup(sock->ht_sess,
+				       hash_joaat_pl(&msg->callid),
+				       cmp_handler, (void *)msg));
+}
+
+
+static void info_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+	struct sip *sip = sock->sip;
+	struct sipsess *sess;
+
+	sess = sipsess_find(sock, msg);
+	if (!sess || sess->terminated) {
+		(void)sip_reply(sip, msg, 481, "Call Does Not Exist");
+		return;
+	}
+
+	if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+		(void)sip_reply(sip, msg, 500, "Server Internal Error");
+		return;
+	}
+
+	if (!sess->infoh) {
+		(void)sip_reply(sip, msg, 501, "Not Implemented");
+		return;
+	}
+
+	sess->infoh(sip, msg, sess->arg);
+}
+
+
+static void refer_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+	struct sip *sip = sock->sip;
+	struct sipsess *sess;
+
+	sess = sipsess_find(sock, msg);
+	if (!sess || sess->terminated) {
+		(void)sip_reply(sip, msg, 481, "Call Does Not Exist");
+		return;
+	}
+
+	if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+		(void)sip_reply(sip, msg, 500, "Server Internal Error");
+		return;
+	}
+
+	if (!sess->referh) {
+		(void)sip_reply(sip, msg, 501, "Not Implemented");
+		return;
+	}
+
+	sess->referh(sip, msg, sess->arg);
+}
+
+
+static void bye_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+	struct sip *sip = sock->sip;
+	struct sipsess *sess;
+
+	sess = sipsess_find(sock, msg);
+	if (!sess) {
+		(void)sip_reply(sip, msg, 481, "Call Does Not Exist");
+		return;
+	}
+
+	if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+		(void)sip_reply(sip, msg, 500, "Server Internal Error");
+		return;
+	}
+
+	(void)sip_treplyf(NULL, NULL, sip, msg, false, 200, "OK",
+			  "%s"
+			  "Content-Length: 0\r\n"
+			  "\r\n",
+			  sess->close_hdrs);
+
+	sess->peerterm = true;
+
+	if (sess->terminated)
+		return;
+
+	if (sess->st) {
+		(void)sip_treply(&sess->st, sess->sip, sess->msg,
+				 487, "Request Terminated");
+	}
+
+	sipsess_terminate(sess, ECONNRESET, NULL);
+}
+
+
+static void ack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+	struct sipsess *sess;
+	bool awaiting_answer;
+	int err = 0;
+
+	sess = sipsess_find(sock, msg);
+	if (!sess)
+		return;
+
+	if (sipsess_reply_ack(sess, msg, &awaiting_answer))
+		return;
+
+	if (sess->terminated) {
+		if (!sess->replyl.head) {
+			sess->established = true;
+			mem_deref(sess);
+		}
+		return;
+	}
+
+	if (awaiting_answer) {
+		sess->awaiting_answer = false;
+		err = sess->answerh(msg, sess->arg);
+	}
+
+	if (sess->modify_pending && !sess->replyl.head)
+		(void)sipsess_reinvite(sess, true);
+
+	if (sess->established)
+		return;
+
+	sess->msg = mem_deref((void *)sess->msg);
+	sess->established = true;
+
+	if (err)
+		sipsess_terminate(sess, err, NULL);
+	else
+		sess->estabh(msg, sess->arg);
+}
+
+
+static void reinvite_handler(struct sipsess_sock *sock,
+			     const struct sip_msg *msg)
+{
+	struct sip *sip = sock->sip;
+	struct sipsess *sess;
+	struct mbuf *desc;
+	char m[256];
+	int err;
+
+	sess = sipsess_find(sock, msg);
+	if (!sess || sess->terminated) {
+		(void)sip_treply(NULL, sip, msg, 481, "Call Does Not Exist");
+		return;
+	}
+
+	if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+		(void)sip_treply(NULL, sip, msg, 500, "Server Internal Error");
+		return;
+	}
+
+	if (sess->st || sess->awaiting_answer) {
+		(void)sip_treplyf(NULL, NULL, sip, msg, false,
+				  500, "Server Internal Error",
+				  "Retry-After: 5\r\n"
+				  "Content-Length: 0\r\n"
+				  "\r\n");
+		return;
+	}
+
+	if (sess->req) {
+		(void)sip_treply(NULL, sip, msg, 491, "Request Pending");
+		return;
+	}
+
+	err = sess->offerh(&desc, msg, sess->arg);
+	if (err) {
+		(void)sip_reply(sip, msg, 488, str_error(err, m, sizeof(m)));
+		return;
+	}
+
+	(void)sip_dialog_update(sess->dlg, msg);
+	(void)sipsess_reply_2xx(sess, msg, 200, "OK", desc,
+				NULL, NULL);
+
+	/* pending modifications considered outdated;
+	   sdp may have changed in above exchange */
+	sess->desc = mem_deref(sess->desc);
+	sess->modify_pending = false;
+	tmr_cancel(&sess->tmr);
+	mem_deref(desc);
+}
+
+
+static void invite_handler(struct sipsess_sock *sock,
+			   const struct sip_msg *msg)
+{
+	sock->connh(msg, sock->arg);
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sipsess_sock *sock = arg;
+
+	if (!pl_strcmp(&msg->met, "INVITE")) {
+
+		if (pl_isset(&msg->to.tag))
+			reinvite_handler(sock, msg);
+		else
+			invite_handler(sock, msg);
+
+		return true;
+	}
+	else if (!pl_strcmp(&msg->met, "ACK")) {
+		ack_handler(sock, msg);
+		return true;
+	}
+	else if (!pl_strcmp(&msg->met, "BYE")) {
+		bye_handler(sock, msg);
+		return true;
+	}
+	else if (!pl_strcmp(&msg->met, "INFO")) {
+		info_handler(sock, msg);
+		return true;
+	}
+	else if (!pl_strcmp(&msg->met, "REFER")) {
+
+		if (!pl_isset(&msg->to.tag))
+			return false;
+
+		refer_handler(sock, msg);
+		return true;
+	}
+
+	return false;
+}
+
+
+static bool response_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sipsess_sock *sock = arg;
+
+	if (pl_strcmp(&msg->cseq.met, "INVITE"))
+		return false;
+
+	if (msg->scode < 200 || msg->scode > 299)
+		return false;
+
+	(void)sipsess_ack_again(sock, msg);
+
+	return true;
+}
+
+
+/**
+ * Listen to a SIP Session socket for incoming connections
+ *
+ * @param sockp    Pointer to allocated SIP Session socket
+ * @param sip      SIP Stack instance
+ * @param htsize   Hashtable size
+ * @param connh    Connection handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_listen(struct sipsess_sock **sockp, struct sip *sip,
+		   int htsize, sipsess_conn_h *connh, void *arg)
+{
+	struct sipsess_sock *sock;
+	int err;
+
+	if (!sockp || !sip || !htsize)
+		return EINVAL;
+
+	sock = mem_zalloc(sizeof(*sock), destructor);
+	if (!sock)
+		return ENOMEM;
+
+	err = sip_listen(&sock->lsnr_resp, sip, false, response_handler, sock);
+	if (err)
+		goto out;
+
+	err = sip_listen(&sock->lsnr_req, sip, true, request_handler, sock);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&sock->ht_sess, htsize);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&sock->ht_ack, htsize);
+	if (err)
+		goto out;
+
+	sock->sip   = sip;
+	sock->connh = connh ? connh : internal_connect_handler;
+	sock->arg   = connh ? arg : sock;
+
+ out:
+	if (err)
+		mem_deref(sock);
+	else
+		*sockp = sock;
+
+	return err;
+}
+
+
+/**
+ * Close all SIP Sessions
+ *
+ * @param sock      SIP Session socket
+ */
+void sipsess_close_all(struct sipsess_sock *sock)
+{
+	if (!sock)
+		return;
+
+	hash_flush(sock->ht_sess);
+}
diff --git a/src/sipsess/mod.mk b/src/sipsess/mod.mk
new file mode 100644
index 0000000..c2644e7
--- /dev/null
+++ b/src/sipsess/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= sipsess/sess.c
+SRCS	+= sipsess/accept.c
+SRCS	+= sipsess/ack.c
+SRCS	+= sipsess/close.c
+SRCS	+= sipsess/connect.c
+SRCS	+= sipsess/info.c
+SRCS	+= sipsess/listen.c
+SRCS	+= sipsess/modify.c
+SRCS	+= sipsess/reply.c
+SRCS	+= sipsess/request.c
diff --git a/src/sipsess/modify.c b/src/sipsess/modify.c
new file mode 100644
index 0000000..452115f
--- /dev/null
+++ b/src/sipsess/modify.c
@@ -0,0 +1,171 @@
+/**
+ * @file modify.c  SIP Session Modify
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void tmr_handler(void *arg)
+{
+	struct sipsess *sess = arg;
+
+	(void)sipsess_reinvite(sess, true);
+}
+
+
+static void reinvite_resp_handler(int err, const struct sip_msg *msg,
+				  void *arg)
+{
+	struct sipsess *sess = arg;
+	const struct sip_hdr *hdr;
+	struct mbuf *desc = NULL;
+
+	if (err || sip_request_loops(&sess->ls, msg->scode))
+		goto out;
+
+	if (msg->scode < 200) {
+		return;
+	}
+	else if (msg->scode < 300) {
+
+		(void)sip_dialog_update(sess->dlg, msg);
+
+		if (sess->sent_offer)
+			(void)sess->answerh(msg, sess->arg);
+		else {
+			sess->modify_pending = false;
+			(void)sess->offerh(&desc, msg, sess->arg);
+		}
+
+		(void)sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
+				  sess->auth, sess->ctype, desc);
+		mem_deref(desc);
+	}
+	else {
+		if (sess->terminated)
+			goto out;
+
+		switch (msg->scode) {
+
+		case 401:
+		case 407:
+			err = sip_auth_authenticate(sess->auth, msg);
+			if (err) {
+				err = (err == EAUTH) ? 0 : err;
+				break;
+			}
+
+			err = sipsess_reinvite(sess, false);
+			if (err)
+				break;
+
+			return;
+
+		case 408:
+		case 481:
+			sipsess_terminate(sess, 0, msg);
+			return;
+
+		case 491:
+			tmr_start(&sess->tmr, sess->owner ? 3000 : 1000,
+				  tmr_handler, sess);
+			return;
+
+		case 500:
+			hdr = sip_msg_hdr(msg, SIP_HDR_RETRY_AFTER);
+			if (!hdr)
+				break;
+
+			tmr_start(&sess->tmr, pl_u32(&hdr->val) * 1000,
+				  tmr_handler, sess);
+			return;
+		}
+	}
+ out:
+	if (sess->terminated)
+		mem_deref(sess);
+	else if (err == ETIMEDOUT)
+		sipsess_terminate(sess, err, NULL);
+	else if (sess->modify_pending)
+		(void)sipsess_reinvite(sess, true);
+	else
+		sess->desc = mem_deref(sess->desc);
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+			const struct sa *dst, struct mbuf *mb, void *arg)
+{
+	struct sip_contact contact;
+	struct sipsess *sess = arg;
+	(void)dst;
+
+	sip_contact_set(&contact, sess->cuser, src, tp);
+
+	return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+int sipsess_reinvite(struct sipsess *sess, bool reset_ls)
+{
+	if (sess->req)
+		return EPROTO;
+
+	sess->sent_offer = sess->desc ? true : false;
+	sess->modify_pending = false;
+
+	if (reset_ls)
+		sip_loopstate_reset(&sess->ls);
+
+	return sip_drequestf(&sess->req, sess->sip, true, "INVITE",
+			     sess->dlg, 0, sess->auth,
+			     send_handler, reinvite_resp_handler, sess,
+			     "%s%s%s"
+			     "Content-Length: %zu\r\n"
+			     "\r\n"
+			     "%b",
+			     sess->desc ? "Content-Type: " : "",
+			     sess->desc ? sess->ctype : "",
+			     sess->desc ? "\r\n" : "",
+			     sess->desc ? mbuf_get_left(sess->desc) :(size_t)0,
+			     sess->desc ? mbuf_buf(sess->desc) : NULL,
+			     sess->desc ? mbuf_get_left(sess->desc):(size_t)0);
+}
+
+
+/**
+ * Modify an established SIP Session sending Re-INVITE or UPDATE
+ *
+ * @param sess      SIP Session
+ * @param desc      Content description (e.g. SDP)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_modify(struct sipsess *sess, struct mbuf *desc)
+{
+	if (!sess || sess->st || sess->terminated)
+		return EINVAL;
+
+	mem_deref(sess->desc);
+	sess->desc = mem_ref(desc);
+
+	if (sess->req || sess->tmr.th || sess->replyl.head) {
+		sess->modify_pending = true;
+		return 0;
+	}
+
+	return sipsess_reinvite(sess, true);
+}
diff --git a/src/sipsess/reply.c b/src/sipsess/reply.c
new file mode 100644
index 0000000..4c3aa6f
--- /dev/null
+++ b/src/sipsess/reply.c
@@ -0,0 +1,161 @@
+/**
+ * @file sipsess/reply.c  SIP Session Reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+struct sipsess_reply {
+	struct le le;
+	struct tmr tmr;
+	struct tmr tmrg;
+	const struct sip_msg *msg;
+	struct mbuf *mb;
+	struct sipsess *sess;
+	bool awaiting_answer;
+	uint32_t seq;
+	uint32_t txc;
+};
+
+
+static void destructor(void *arg)
+{
+	struct sipsess_reply *reply = arg;
+
+	list_unlink(&reply->le);
+	tmr_cancel(&reply->tmr);
+	tmr_cancel(&reply->tmrg);
+	mem_deref((void *)reply->msg);
+	mem_deref(reply->mb);
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sipsess_reply *reply = arg;
+	struct sipsess *sess = reply->sess;
+
+	mem_deref(reply);
+
+	/* wait for all pending ACKs */
+	if (sess->replyl.head)
+		return;
+
+	/* we want to send bye */
+	sess->established = true;
+
+	if (!sess->terminated)
+		sipsess_terminate(sess, ETIMEDOUT, NULL);
+	else
+		mem_deref(sess);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+	struct sipsess_reply *reply = arg;
+
+	(void)sip_send(reply->sess->sip, reply->msg->sock, reply->msg->tp,
+		       &reply->msg->src, reply->mb);
+
+	reply->txc++;
+	tmr_start(&reply->tmrg, MIN(SIP_T1<<reply->txc, SIP_T2),
+		  retransmit_handler, reply);
+}
+
+
+int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
+		      uint16_t scode, const char *reason, struct mbuf *desc,
+		      const char *fmt, va_list *ap)
+{
+	struct sipsess_reply *reply;
+	struct sip_contact contact;
+	int err = ENOMEM;
+
+	reply = mem_zalloc(sizeof(*reply), destructor);
+	if (!reply)
+		goto out;
+
+	list_append(&sess->replyl, &reply->le, reply);
+	reply->seq  = msg->cseq.num;
+	reply->msg  = mem_ref((void *)msg);
+	reply->sess = sess;
+
+	sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp);
+
+	err = sip_treplyf(&sess->st, &reply->mb, sess->sip,
+			  msg, true, scode, reason,
+			  "%H"
+			  "%v"
+			  "%s%s%s"
+			  "Content-Length: %zu\r\n"
+			  "\r\n"
+			  "%b",
+			  sip_contact_print, &contact,
+			  fmt, ap,
+			  desc ? "Content-Type: " : "",
+			  desc ? sess->ctype : "",
+			  desc ? "\r\n" : "",
+			  desc ? mbuf_get_left(desc) : (size_t)0,
+			  desc ? mbuf_buf(desc) : NULL,
+			  desc ? mbuf_get_left(desc) : (size_t)0);
+
+	if (err)
+		goto out;
+
+	tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply);
+	tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply);
+
+	if (!mbuf_get_left(msg->mb) && desc) {
+		reply->awaiting_answer = true;
+		sess->awaiting_answer = true;
+	}
+
+ out:
+	if (err) {
+		sess->st = mem_deref(sess->st);
+		mem_deref(reply);
+	}
+
+	return err;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct sipsess_reply *reply = le->data;
+	const struct sip_msg *msg = arg;
+
+	return msg->cseq.num == reply->seq;
+}
+
+
+int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
+		      bool *awaiting_answer)
+{
+	struct sipsess_reply *reply;
+
+	reply = list_ledata(list_apply(&sess->replyl, false, cmp_handler,
+				       (void *)msg));
+	if (!reply)
+		return ENOENT;
+
+	*awaiting_answer = reply->awaiting_answer;
+
+	mem_deref(reply);
+
+	return 0;
+}
diff --git a/src/sipsess/request.c b/src/sipsess/request.c
new file mode 100644
index 0000000..cd7384a
--- /dev/null
+++ b/src/sipsess/request.c
@@ -0,0 +1,79 @@
+/**
+ * @file sipsess/request.c  SIP Session Non-INVITE Request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void destructor(void *arg)
+{
+	struct sipsess_request *req = arg;
+
+	list_unlink(&req->le);
+	mem_deref(req->ctype);
+	mem_deref(req->body);
+	mem_deref(req->req);
+
+	/* wait for pending requests */
+	if (req->sess->terminated && !req->sess->requestl.head)
+		mem_deref(req->sess);
+}
+
+
+static void internal_resp_handler(int err, const struct sip_msg *msg,
+				  void *arg)
+{
+	(void)err;
+	(void)msg;
+	(void)arg;
+}
+
+
+int sipsess_request_alloc(struct sipsess_request **reqp, struct sipsess *sess,
+			  const char *ctype, struct mbuf *body,
+			  sip_resp_h *resph, void *arg)
+{
+	struct sipsess_request *req;
+	int err = 0;
+
+	if (!reqp || !sess || sess->terminated)
+		return EINVAL;
+
+	req = mem_zalloc(sizeof(*req), destructor);
+	if (!req)
+		return ENOMEM;
+
+	list_append(&sess->requestl, &req->le, req);
+
+	if (ctype) {
+		err = str_dup(&req->ctype, ctype);
+		if (err)
+			goto out;
+	}
+
+	req->sess  = sess;
+	req->body  = mem_ref(body);
+	req->resph = resph ? resph : internal_resp_handler;
+	req->arg   = arg;
+
+ out:
+	if (err)
+		mem_deref(req);
+	else
+		*reqp = req;
+
+	return 0;
+}
diff --git a/src/sipsess/sess.c b/src/sipsess/sess.c
new file mode 100644
index 0000000..ff505eb
--- /dev/null
+++ b/src/sipsess/sess.c
@@ -0,0 +1,264 @@
+/**
+ * @file sipsess/sess.c  SIP Session Core
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static int internal_offer_handler(struct mbuf **descp,
+				  const struct sip_msg *msg, void *arg)
+{
+	(void)descp;
+	(void)msg;
+	(void)arg;
+
+	return ENOSYS;
+}
+
+
+static int internal_answer_handler(const struct sip_msg *msg, void *arg)
+{
+	(void)msg;
+	(void)arg;
+
+	return ENOSYS;
+}
+
+
+static void internal_progress_handler(const struct sip_msg *msg, void *arg)
+{
+	(void)msg;
+	(void)arg;
+}
+
+
+static void internal_establish_handler(const struct sip_msg *msg, void *arg)
+{
+	(void)msg;
+	(void)arg;
+}
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+				   void *arg)
+{
+	(void)err;
+	(void)msg;
+	(void)arg;
+}
+
+
+static bool termwait(struct sipsess *sess)
+{
+	bool wait = false;
+
+	sess->terminated = 1;
+	sess->offerh  = internal_offer_handler;
+	sess->answerh = internal_answer_handler;
+	sess->progrh  = internal_progress_handler;
+	sess->estabh  = internal_establish_handler;
+	sess->infoh   = NULL;
+	sess->referh  = NULL;
+	sess->closeh  = internal_close_handler;
+	sess->arg     = sess;
+
+	tmr_cancel(&sess->tmr);
+
+	if (sess->st) {
+		(void)sip_treply(&sess->st, sess->sip, sess->msg,
+				 486, "Busy Here");
+	}
+
+	if (sess->req) {
+		sip_request_cancel(sess->req);
+		mem_ref(sess);
+		wait = true;
+	}
+
+	if (sess->replyl.head) {
+		mem_ref(sess);
+		wait = true;
+	}
+
+	if (sess->requestl.head) {
+		mem_ref(sess);
+		wait = true;
+	}
+
+	return wait;
+}
+
+
+static bool terminate(struct sipsess *sess)
+{
+	sess->terminated = 2;
+
+	if (!sess->established || sess->peerterm)
+		return false;
+
+	if (sipsess_bye(sess, true))
+		return false;
+
+	mem_ref(sess);
+	return true;
+}
+
+
+static void destructor(void *arg)
+{
+	struct sipsess *sess = arg;
+
+	switch (sess->terminated) {
+
+	case 0:
+		if (termwait(sess))
+			return;
+
+		/*@fallthrough@*/
+
+	case 1:
+		if (terminate(sess))
+			return;
+		break;
+	}
+
+	hash_unlink(&sess->he);
+	tmr_cancel(&sess->tmr);
+	list_flush(&sess->replyl);
+	list_flush(&sess->requestl);
+	mem_deref((void *)sess->msg);
+	mem_deref(sess->req);
+	mem_deref(sess->dlg);
+	mem_deref(sess->auth);
+	mem_deref(sess->cuser);
+	mem_deref(sess->ctype);
+	mem_deref(sess->close_hdrs);
+	mem_deref(sess->hdrs);
+	mem_deref(sess->desc);
+	mem_deref(sess->sock);
+	mem_deref(sess->sip);
+	mem_deref(sess->st);
+}
+
+
+int sipsess_alloc(struct sipsess **sessp, struct sipsess_sock *sock,
+		  const char *cuser, const char *ctype, struct mbuf *desc,
+		  sip_auth_h *authh, void *aarg, bool aref,
+		  sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+		  sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+		  sipsess_info_h *infoh, sipsess_refer_h *referh,
+		  sipsess_close_h *closeh, void *arg)
+{
+	struct sipsess *sess;
+	int err;
+
+	sess = mem_zalloc(sizeof(*sess), destructor);
+	if (!sess)
+		return ENOMEM;
+
+	err = sip_auth_alloc(&sess->auth, authh, aarg, aref);
+	if (err)
+		goto out;
+
+	err = str_dup(&sess->cuser, cuser);
+	if (err)
+		goto out;
+
+	err = str_dup(&sess->ctype, ctype);
+	if (err)
+		goto out;
+
+	sess->sock    = mem_ref(sock);
+	sess->desc    = mem_ref(desc);
+	sess->sip     = mem_ref(sock->sip);
+	sess->offerh  = offerh  ? offerh  : internal_offer_handler;
+	sess->answerh = answerh ? answerh : internal_answer_handler;
+	sess->progrh  = progrh  ? progrh  : internal_progress_handler;
+	sess->estabh  = estabh  ? estabh  : internal_establish_handler;
+	sess->infoh   = infoh;
+	sess->referh  = referh;
+	sess->closeh  = closeh  ? closeh  : internal_close_handler;
+	sess->arg     = arg;
+
+ out:
+	if (err)
+		mem_deref(sess);
+	else
+		*sessp = sess;
+
+	return err;
+}
+
+
+void sipsess_terminate(struct sipsess *sess, int err,
+		       const struct sip_msg *msg)
+{
+	sipsess_close_h *closeh;
+	void *arg;
+
+	if (sess->terminated)
+		return;
+
+	closeh = sess->closeh;
+	arg    = sess->arg;
+
+	if (!termwait(sess))
+		(void)terminate(sess);
+
+	closeh(err, msg, arg);
+}
+
+
+/**
+ * Get the SIP dialog from a SIP Session
+ *
+ * @param sess      SIP Session
+ *
+ * @return SIP Dialog object
+ */
+struct sip_dialog *sipsess_dialog(const struct sipsess *sess)
+{
+	return sess ? sess->dlg : NULL;
+}
+
+
+/**
+ * Set extra SIP headers for inclusion in Session "close" messages
+ * like BYE and 200 OK. Multiple headers can be included.
+ *
+ * @param sess      SIP Session
+ * @param hdrs      Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_set_close_headers(struct sipsess *sess, const char *hdrs, ...)
+{
+	int err = 0;
+	va_list ap;
+
+	if (!sess)
+		return EINVAL;
+
+	sess->close_hdrs = mem_deref(sess->close_hdrs);
+
+	if (hdrs) {
+		va_start(ap, hdrs);
+		err = re_vsdprintf(&sess->close_hdrs, hdrs, ap);
+		va_end(ap);
+	}
+
+	return err;
+}
diff --git a/src/sipsess/sipsess.h b/src/sipsess/sipsess.h
new file mode 100644
index 0000000..3bdfcc7
--- /dev/null
+++ b/src/sipsess/sipsess.h
@@ -0,0 +1,89 @@
+/**
+ * @file sipsess.h  SIP Session Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct sipsess {
+	struct le he;
+	struct tmr tmr;
+	struct list replyl;
+	struct list requestl;
+	struct sip_loopstate ls;
+	struct sipsess_sock *sock;
+	const struct sip_msg *msg;
+	struct sip_request *req;
+	struct sip_dialog *dlg;
+	struct sip_strans *st;
+	struct sip_auth *auth;
+	struct sip *sip;
+	char *cuser;
+	char *ctype;
+	char *close_hdrs;
+	struct mbuf *hdrs;
+	struct mbuf *desc;
+	sipsess_offer_h *offerh;
+	sipsess_answer_h *answerh;
+	sipsess_progr_h *progrh;
+	sipsess_estab_h *estabh;
+	sipsess_info_h *infoh;
+	sipsess_refer_h *referh;
+	sipsess_close_h *closeh;
+	void *arg;
+	bool owner;
+	bool sent_offer;
+	bool awaiting_answer;
+	bool modify_pending;
+	bool established;
+	bool peerterm;
+	int terminated;
+};
+
+
+struct sipsess_sock {
+	struct sip_lsnr *lsnr_resp;
+	struct sip_lsnr *lsnr_req;
+	struct hash *ht_sess;
+	struct hash *ht_ack;
+	struct sip *sip;
+	sipsess_conn_h *connh;
+	void *arg;
+};
+
+
+struct sipsess_request {
+	struct le le;
+	struct sip_loopstate ls;
+	struct sipsess *sess;
+	struct sip_request *req;
+	char *ctype;
+	struct mbuf *body;
+	sip_resp_h *resph;
+	void *arg;
+};
+
+
+int  sipsess_alloc(struct sipsess **sessp, struct sipsess_sock *sock,
+		   const char *cuser, const char *ctype, struct mbuf *desc,
+		   sip_auth_h *authh, void *aarg, bool aref,
+		   sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+		   sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+		   sipsess_info_h *infoh, sipsess_refer_h *referh,
+		   sipsess_close_h *closeh, void *arg);
+void sipsess_terminate(struct sipsess *sess, int err,
+		       const struct sip_msg *msg);
+int  sipsess_ack(struct sipsess_sock *sock, struct sip_dialog *dlg,
+		uint32_t cseq, struct sip_auth *auth,
+		const char *ctype, struct mbuf *desc);
+int  sipsess_ack_again(struct sipsess_sock *sock, const struct sip_msg *msg);
+int  sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
+		       uint16_t scode, const char *reason, struct mbuf *desc,
+		       const char *fmt, va_list *ap);
+int  sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
+		       bool *awaiting_answer);
+int  sipsess_reinvite(struct sipsess *sess, bool reset_ls);
+int  sipsess_bye(struct sipsess *sess, bool reset_ls);
+int  sipsess_request_alloc(struct sipsess_request **reqp, struct sipsess *sess,
+			   const char *ctype, struct mbuf *body,
+			   sip_resp_h *resph, void *arg);