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/sip/ctrans.c b/src/sip/ctrans.c
new file mode 100644
index 0000000..0ff3266
--- /dev/null
+++ b/src/sip/ctrans.c
@@ -0,0 +1,449 @@
+/**
+ * @file sip/ctrans.c  SIP Client Transaction
+ *
+ * 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_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum state {
+	TRYING,
+	CALLING,
+	PROCEEDING,
+	COMPLETED,
+};
+
+
+enum {
+	COMPLETE_WAIT = 32000,
+};
+
+
+struct sip_ctrans {
+	struct le he;
+	struct sa dst;
+	struct tmr tmr;
+	struct tmr tmre;
+	struct sip *sip;
+	struct mbuf *mb;
+	struct mbuf *mb_ack;
+	struct sip_msg *req;
+	struct sip_connqent *qent;
+	char *met;
+	char *branch;
+	sip_resp_h *resph;
+	void *arg;
+	enum sip_transp tp;
+	enum state state;
+	uint32_t txc;
+	bool invite;
+};
+
+
+static bool route_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+			  void *arg)
+{
+	(void)msg;
+	return 0 != mbuf_printf(arg, "Route: %r\r\n", &hdr->val);
+}
+
+
+static int request_copy(struct mbuf **mbp, struct sip_ctrans *ct,
+			const char *met, const struct sip_msg *resp)
+{
+	struct mbuf *mb;
+	int err;
+
+	if (!ct->req) {
+		err = sip_msg_decode(&ct->req, ct->mb);
+		if (err)
+			return err;
+	}
+
+	mb = mbuf_alloc(1024);
+	if (!mb)
+		return ENOMEM;
+
+	err  = mbuf_printf(mb, "%s %r SIP/2.0\r\n", met, &ct->req->ruri);
+	err |= mbuf_printf(mb, "Via: %r\r\n", &ct->req->via.val);
+	err |= mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+	err |= sip_msg_hdr_apply(ct->req, true, SIP_HDR_ROUTE,
+				 route_handler, mb) ? ENOMEM : 0;
+	err |= mbuf_printf(mb, "To: %r\r\n",
+			   resp ? &resp->to.val : &ct->req->to.val);
+	err |= mbuf_printf(mb, "From: %r\r\n", &ct->req->from.val);
+	err |= mbuf_printf(mb, "Call-ID: %r\r\n", &ct->req->callid);
+	err |= mbuf_printf(mb, "CSeq: %u %s\r\n", ct->req->cseq.num, met);
+	if (ct->sip->software)
+		err |= mbuf_printf(mb, "User-Agent: %s\r\n",ct->sip->software);
+	err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n");
+
+	mb->pos = 0;
+
+	if (err)
+		mem_deref(mb);
+	else
+		*mbp = mb;
+
+	return err;
+}
+
+
+static void destructor(void *arg)
+{
+	struct sip_ctrans *ct = arg;
+
+	hash_unlink(&ct->he);
+	tmr_cancel(&ct->tmr);
+	tmr_cancel(&ct->tmre);
+	mem_deref(ct->met);
+	mem_deref(ct->branch);
+	mem_deref(ct->qent);
+	mem_deref(ct->req);
+	mem_deref(ct->mb);
+	mem_deref(ct->mb_ack);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+	struct sip_ctrans *ct = le->data;
+	const struct sip_msg *msg = arg;
+
+	if (pl_strcmp(&msg->via.branch, ct->branch))
+		return false;
+
+	if (pl_strcmp(&msg->cseq.met, ct->met))
+		return false;
+
+	return true;
+}
+
+
+static void dummy_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	(void)err;
+	(void)msg;
+	(void)arg;
+}
+
+
+static void terminate(struct sip_ctrans *ct, int err)
+{
+	switch (ct->state) {
+
+	case TRYING:
+	case CALLING:
+	case PROCEEDING:
+		ct->resph(err, NULL, ct->arg);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void transport_handler(int err, void *arg)
+{
+	struct sip_ctrans *ct = arg;
+
+	terminate(ct, err);
+	mem_deref(ct);
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sip_ctrans *ct = arg;
+
+	terminate(ct, ETIMEDOUT);
+	mem_deref(ct);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+	struct sip_ctrans *ct = arg;
+	uint32_t timeout;
+	int err;
+
+	ct->txc++;
+
+	switch (ct->state) {
+
+	case TRYING:
+		timeout = MIN(SIP_T1<<ct->txc, SIP_T2);
+		break;
+
+	case CALLING:
+		timeout = SIP_T1<<ct->txc;
+		break;
+
+	case PROCEEDING:
+		timeout = SIP_T2;
+		break;
+
+	default:
+		return;
+	}
+
+	tmr_start(&ct->tmre, timeout, retransmit_handler, ct);
+
+	err = sip_transp_send(&ct->qent, ct->sip, NULL, ct->tp, &ct->dst,
+			      ct->mb, transport_handler, ct);
+	if (err) {
+		terminate(ct, err);
+		mem_deref(ct);
+	}
+}
+
+
+static void invite_response(struct sip_ctrans *ct, const struct sip_msg *msg)
+{
+	switch (ct->state) {
+
+	case CALLING:
+		tmr_cancel(&ct->tmr);
+		tmr_cancel(&ct->tmre);
+		/*@fallthrough@*/
+	case PROCEEDING:
+		if (msg->scode < 200) {
+			ct->state = PROCEEDING;
+			ct->resph(0, msg, ct->arg);
+		}
+		else if (msg->scode < 300) {
+			ct->resph(0, msg, ct->arg);
+			mem_deref(ct);
+		}
+		else {
+			ct->state = COMPLETED;
+
+			(void)request_copy(&ct->mb_ack, ct, "ACK", msg);
+			(void)sip_send(ct->sip, NULL, ct->tp, &ct->dst,
+				       ct->mb_ack);
+
+			ct->resph(0, msg, ct->arg);
+
+			if (sip_transp_reliable(ct->tp)) {
+				mem_deref(ct);
+				break;
+			}
+
+			tmr_start(&ct->tmr, COMPLETE_WAIT, tmr_handler, ct);
+		}
+		break;
+
+	case COMPLETED:
+		if (msg->scode < 300)
+			break;
+
+		(void)sip_send(ct->sip, NULL, ct->tp, &ct->dst, ct->mb_ack);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static bool response_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sip_ctrans *ct;
+	struct sip *sip = arg;
+
+	ct = list_ledata(hash_lookup(sip->ht_ctrans,
+				     hash_joaat_pl(&msg->via.branch),
+				     cmp_handler, (void *)msg));
+	if (!ct)
+		return false;
+
+	if (ct->invite) {
+		invite_response(ct, msg);
+		return true;
+	}
+
+	switch (ct->state) {
+
+	case TRYING:
+	case PROCEEDING:
+		if (msg->scode < 200) {
+			ct->state = PROCEEDING;
+			ct->resph(0, msg, ct->arg);
+		}
+		else {
+			ct->state = COMPLETED;
+			ct->resph(0, msg, ct->arg);
+
+			if (sip_transp_reliable(ct->tp)) {
+				mem_deref(ct);
+				break;
+			}
+
+			tmr_start(&ct->tmr, SIP_T4, tmr_handler, ct);
+			tmr_cancel(&ct->tmre);
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return true;
+}
+
+
+int sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip,
+		       enum sip_transp tp, const struct sa *dst, char *met,
+		       char *branch, struct mbuf *mb,
+		       sip_resp_h *resph, void *arg)
+{
+	struct sip_ctrans *ct;
+	int err;
+
+	if (!sip || !dst || !met || !branch || !mb)
+		return EINVAL;
+
+	ct = mem_zalloc(sizeof(*ct), destructor);
+	if (!ct)
+		return ENOMEM;
+
+	hash_append(sip->ht_ctrans, hash_joaat_str(branch), &ct->he, ct);
+
+	ct->invite = !strcmp(met, "INVITE");
+	ct->branch = mem_ref(branch);
+	ct->met    = mem_ref(met);
+	ct->mb     = mem_ref(mb);
+	ct->dst    = *dst;
+	ct->tp     = tp;
+	ct->sip    = sip;
+	ct->state  = ct->invite ? CALLING : TRYING;
+	ct->resph  = resph ? resph : dummy_handler;
+	ct->arg    = arg;
+
+	err = sip_transp_send(&ct->qent, sip, NULL, tp, dst, mb,
+			      transport_handler, ct);
+	if (err)
+		goto out;
+
+	tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct);
+
+	if (!sip_transp_reliable(ct->tp))
+		tmr_start(&ct->tmre, SIP_T1, retransmit_handler, ct);
+
+ out:
+	if (err)
+		mem_deref(ct);
+	else if (ctp)
+		*ctp = ct;
+
+	return err;
+}
+
+
+int sip_ctrans_cancel(struct sip_ctrans *ct)
+{
+	struct mbuf *mb = NULL;
+	char *cancel = NULL;
+	int err;
+
+	if (!ct)
+		return EINVAL;
+
+	if (!ct->invite)
+		return 0;
+
+	switch (ct->state) {
+
+	case PROCEEDING:
+		tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct);
+		break;
+
+	default:
+		return EPROTO;
+	}
+
+	err = str_dup(&cancel, "CANCEL");
+	if (err)
+		goto out;
+
+	err = request_copy(&mb, ct, cancel, NULL);
+	if (err)
+		goto out;
+
+	err = sip_ctrans_request(NULL, ct->sip, ct->tp, &ct->dst, cancel,
+				 ct->branch, mb, NULL, NULL);
+	if (err)
+		goto out;
+
+ out:
+	mem_deref(cancel);
+	mem_deref(mb);
+
+	return err;
+}
+
+
+int sip_ctrans_init(struct sip *sip, uint32_t sz)
+{
+	int err;
+
+	err = sip_listen(NULL, sip, false, response_handler, sip);
+	if (err)
+		return err;
+
+	return hash_alloc(&sip->ht_ctrans, sz);
+}
+
+
+static const char *statename(enum state state)
+{
+	switch (state) {
+
+	case TRYING:     return "TRYING";
+	case CALLING:    return "CALLING";
+	case PROCEEDING: return "PROCEEDING";
+	case COMPLETED:  return "COMPLETED";
+	default:         return "???";
+	}
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+	struct sip_ctrans *ct = le->data;
+	struct re_printf *pf = arg;
+
+	(void)re_hprintf(pf, "  %-10s %-10s %2llus (%s)\n",
+			 ct->met,
+			 statename(ct->state),
+			 tmr_get_expire(&ct->tmr)/1000,
+			 ct->branch);
+
+	return false;
+}
+
+
+int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip)
+{
+	int err;
+
+	err = re_hprintf(pf, "client transactions:\n");
+	hash_apply(sip->ht_ctrans, debug_handler, pf);
+
+	return err;
+}