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/addr.c b/src/sip/addr.c
new file mode 100644
index 0000000..72ab21f
--- /dev/null
+++ b/src/sip/addr.c
@@ -0,0 +1,56 @@
+/**
+ * @file sip/addr.c SIP Address decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Decode a pointer-length string into a SIP Address object
+ *
+ * @param addr SIP Address object
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_addr_decode(struct sip_addr *addr, const struct pl *pl)
+{
+ int err;
+
+ if (!addr || !pl)
+ return EINVAL;
+
+ memset(addr, 0, sizeof(*addr));
+
+ if (0 == re_regex(pl->p, pl->l, "[~ \t\r\n<]*[ \t\r\n]*<[^>]+>[^]*",
+ &addr->dname, NULL, &addr->auri, &addr->params)) {
+
+ if (!addr->dname.l)
+ addr->dname.p = NULL;
+
+ if (!addr->params.l)
+ addr->params.p = NULL;
+ }
+ else {
+ memset(addr, 0, sizeof(*addr));
+
+ if (re_regex(pl->p, pl->l, "[^;]+[^]*",
+ &addr->auri, &addr->params))
+ return EBADMSG;
+ }
+
+ err = uri_decode(&addr->uri, &addr->auri);
+ if (err)
+ memset(addr, 0, sizeof(*addr));
+
+ return err;
+}
diff --git a/src/sip/auth.c b/src/sip/auth.c
new file mode 100644
index 0000000..1357cad
--- /dev/null
+++ b/src/sip/auth.c
@@ -0,0 +1,325 @@
+/**
+ * @file sip/auth.c SIP Authentication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_md5.h>
+#include <re_httpauth.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+struct sip_auth {
+ struct list realml;
+ sip_auth_h *authh;
+ void *arg;
+ bool ref;
+ int err;
+};
+
+
+struct realm {
+ struct le le;
+ char *realm;
+ char *nonce;
+ char *qop;
+ char *opaque;
+ char *user;
+ char *pass;
+ uint32_t nc;
+ enum sip_hdrid hdr;
+};
+
+
+static int dummy_handler(char **user, char **pass, const char *rlm, void *arg)
+{
+ (void)user;
+ (void)pass;
+ (void)rlm;
+ (void)arg;
+
+ return EAUTH;
+}
+
+
+static void realm_destructor(void *arg)
+{
+ struct realm *realm = arg;
+
+ list_unlink(&realm->le);
+ mem_deref(realm->realm);
+ mem_deref(realm->nonce);
+ mem_deref(realm->qop);
+ mem_deref(realm->opaque);
+ mem_deref(realm->user);
+ mem_deref(realm->pass);
+}
+
+
+static void auth_destructor(void *arg)
+{
+ struct sip_auth *auth = arg;
+
+ if (auth->ref)
+ mem_deref(auth->arg);
+
+ list_flush(&auth->realml);
+}
+
+
+static int mkdigest(uint8_t *digest, const struct realm *realm,
+ const char *met, const char *uri, uint64_t cnonce)
+{
+ uint8_t ha1[MD5_SIZE], ha2[MD5_SIZE];
+ int err;
+
+ err = md5_printf(ha1, "%s:%s:%s",
+ realm->user, realm->realm, realm->pass);
+ if (err)
+ return err;
+
+ err = md5_printf(ha2, "%s:%s", met, uri);
+ if (err)
+ return err;
+
+ if (realm->qop)
+ return md5_printf(digest, "%w:%s:%08x:%016llx:auth:%w",
+ ha1, sizeof(ha1),
+ realm->nonce,
+ realm->nc,
+ cnonce,
+ ha2, sizeof(ha2));
+ else
+ return md5_printf(digest, "%w:%s:%w",
+ ha1, sizeof(ha1),
+ realm->nonce,
+ ha2, sizeof(ha2));
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct realm *realm = le->data;
+ struct pl *chrealm = arg;
+
+ /* handle multiple authenticate headers with equal realm value */
+ if (realm->nc == 1)
+ return false;
+
+ return 0 == pl_strcasecmp(chrealm, realm->realm);
+}
+
+
+static bool auth_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ struct httpauth_digest_chall ch;
+ struct sip_auth *auth = arg;
+ struct realm *realm = NULL;
+ int err;
+ (void)msg;
+
+ if (httpauth_digest_challenge_decode(&ch, &hdr->val)) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ if (pl_isset(&ch.algorithm) && pl_strcasecmp(&ch.algorithm, "md5")) {
+ err = ENOSYS;
+ goto out;
+ }
+
+ realm = list_ledata(list_apply(&auth->realml, true, cmp_handler,
+ &ch.realm));
+ if (!realm) {
+ realm = mem_zalloc(sizeof(*realm), realm_destructor);
+ if (!realm) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ list_append(&auth->realml, &realm->le, realm);
+
+ err = pl_strdup(&realm->realm, &ch.realm);
+ if (err)
+ goto out;
+
+ err = auth->authh(&realm->user, &realm->pass,
+ realm->realm, auth->arg);
+ if (err)
+ goto out;
+ }
+ else {
+ if (!pl_isset(&ch.stale) || pl_strcasecmp(&ch.stale, "true")) {
+ err = EAUTH;
+ goto out;
+ }
+
+ realm->nonce = mem_deref(realm->nonce);
+ realm->qop = mem_deref(realm->qop);
+ realm->opaque = mem_deref(realm->opaque);
+ }
+
+ realm->hdr = hdr->id;
+ realm->nc = 1;
+
+ err = pl_strdup(&realm->nonce, &ch.nonce);
+
+ if (pl_isset(&ch.qop))
+ err |= pl_strdup(&realm->qop, &ch.qop);
+
+ if (pl_isset(&ch.opaque))
+ err |= pl_strdup(&realm->opaque, &ch.opaque);
+
+ out:
+ if (err) {
+ mem_deref(realm);
+ auth->err = err;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Update a SIP authentication state from a SIP message
+ *
+ * @param auth SIP Authentication state
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_auth_authenticate(struct sip_auth *auth, const struct sip_msg *msg)
+{
+ if (!auth || !msg)
+ return EINVAL;
+
+ if (sip_msg_hdr_apply(msg, true, SIP_HDR_WWW_AUTHENTICATE,
+ auth_handler, auth))
+ return auth->err;
+
+ if (sip_msg_hdr_apply(msg, true, SIP_HDR_PROXY_AUTHENTICATE,
+ auth_handler, auth))
+ return auth->err;
+
+ return 0;
+}
+
+
+int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met,
+ const char *uri)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!mb || !auth || !met || !uri)
+ return EINVAL;
+
+ for (le = auth->realml.head; le; le = le->next) {
+
+ const uint64_t cnonce = rand_u64();
+ struct realm *realm = le->data;
+ uint8_t digest[MD5_SIZE];
+
+ err = mkdigest(digest, realm, met, uri, cnonce);
+ if (err)
+ break;
+
+ switch (realm->hdr) {
+
+ case SIP_HDR_WWW_AUTHENTICATE:
+ err = mbuf_write_str(mb, "Authorization: ");
+ break;
+
+ case SIP_HDR_PROXY_AUTHENTICATE:
+ err = mbuf_write_str(mb, "Proxy-Authorization: ");
+ break;
+
+ default:
+ continue;
+ }
+
+ err |= mbuf_printf(mb, "Digest username=\"%s\"", realm->user);
+ err |= mbuf_printf(mb, ", realm=\"%s\"", realm->realm);
+ err |= mbuf_printf(mb, ", nonce=\"%s\"", realm->nonce);
+ err |= mbuf_printf(mb, ", uri=\"%s\"", uri);
+ err |= mbuf_printf(mb, ", response=\"%w\"",
+ digest, sizeof(digest));
+
+ if (realm->opaque)
+ err |= mbuf_printf(mb, ", opaque=\"%s\"",
+ realm->opaque);
+
+ if (realm->qop) {
+ err |= mbuf_printf(mb, ", cnonce=\"%016llx\"", cnonce);
+ err |= mbuf_write_str(mb, ", qop=auth");
+ err |= mbuf_printf(mb, ", nc=%08x", realm->nc);
+ }
+
+ ++realm->nc;
+
+ err |= mbuf_write_str(mb, "\r\n");
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+
+/**
+ * Allocate a SIP authentication state
+ *
+ * @param authp Pointer to allocated SIP authentication state
+ * @param authh Authentication handler
+ * @param arg Handler argument
+ * @param ref True to mem_ref() argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_auth_alloc(struct sip_auth **authp, sip_auth_h *authh,
+ void *arg, bool ref)
+{
+ struct sip_auth *auth;
+
+ if (!authp)
+ return EINVAL;
+
+ auth = mem_zalloc(sizeof(*auth), auth_destructor);
+ if (!auth)
+ return ENOMEM;
+
+ auth->authh = authh ? authh : dummy_handler;
+ auth->arg = ref ? mem_ref(arg) : arg;
+ auth->ref = ref;
+
+ *authp = auth;
+
+ return 0;
+}
+
+
+/**
+ * Reset a SIP authentication state
+ *
+ * @param auth SIP Authentication state
+ */
+void sip_auth_reset(struct sip_auth *auth)
+{
+ if (!auth)
+ return;
+
+ list_flush(&auth->realml);
+}
diff --git a/src/sip/contact.c b/src/sip/contact.c
new file mode 100644
index 0000000..6703384
--- /dev/null
+++ b/src/sip/contact.c
@@ -0,0 +1,57 @@
+/**
+ * @file sip/contact.c SIP contact functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Set contact parameters
+ *
+ * @param contact SIP Contact object
+ * @param uri Username or URI
+ * @param addr IP-address and port
+ * @param tp SIP Transport
+ */
+void sip_contact_set(struct sip_contact *contact, const char *uri,
+ const struct sa *addr, enum sip_transp tp)
+{
+ if (!contact)
+ return;
+
+ contact->uri = uri;
+ contact->addr = addr;
+ contact->tp = tp;
+}
+
+
+/**
+ * Print contact header
+ *
+ * @param pf Print function
+ * @param contact SIP Contact object
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_contact_print(struct re_printf *pf, const struct sip_contact *contact)
+{
+ if (!contact)
+ return 0;
+
+ if (contact->uri && strchr(contact->uri, ':'))
+ return re_hprintf(pf, "Contact: <%s>\r\n", contact->uri);
+ else
+ return re_hprintf(pf, "Contact: <sip:%s@%J%s>\r\n",
+ contact->uri,
+ contact->addr,
+ sip_transp_param(contact->tp));
+}
diff --git a/src/sip/cseq.c b/src/sip/cseq.c
new file mode 100644
index 0000000..1be247b
--- /dev/null
+++ b/src/sip/cseq.c
@@ -0,0 +1,40 @@
+/**
+ * @file cseq.c SIP CSeq decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Decode a pointer-length string into a SIP CSeq header
+ *
+ * @param cseq SIP CSeq header
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_cseq_decode(struct sip_cseq *cseq, const struct pl *pl)
+{
+ struct pl num;
+ int err;
+
+ if (!cseq || !pl)
+ return EINVAL;
+
+ err = re_regex(pl->p, pl->l, "[0-9]+[ \t\r\n]+[^ \t\r\n]+",
+ &num, NULL, &cseq->met);
+ if (err)
+ return err;
+
+ cseq->num = pl_u32(&num);
+
+ return 0;
+}
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;
+}
diff --git a/src/sip/dialog.c b/src/sip/dialog.c
new file mode 100644
index 0000000..5863e20
--- /dev/null
+++ b/src/sip/dialog.c
@@ -0,0 +1,659 @@
+/**
+ * @file dialog.c SIP Dialog
+ *
+ * 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 {
+ ROUTE_OFFSET = 7,
+ X64_STRSIZE = 17,
+};
+
+struct sip_dialog {
+ struct uri route;
+ struct mbuf *mb;
+ char *callid;
+ char *ltag;
+ char *rtag;
+ char *uri;
+ uint32_t hash;
+ uint32_t lseq;
+ uint32_t rseq;
+ size_t cpos;
+};
+
+
+struct route_enc {
+ struct mbuf *mb;
+ size_t end;
+};
+
+
+static int x64_strdup(char **strp, uint64_t val)
+{
+ char *str;
+
+ str = mem_alloc(X64_STRSIZE, NULL);
+ if (!str)
+ return ENOMEM;
+
+ (void)re_snprintf(str, X64_STRSIZE, "%016llx", val);
+
+ *strp = str;
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_dialog *dlg = arg;
+
+ mem_deref(dlg->callid);
+ mem_deref(dlg->ltag);
+ mem_deref(dlg->rtag);
+ mem_deref(dlg->uri);
+ mem_deref(dlg->mb);
+}
+
+
+/**
+ * Allocate a SIP Dialog
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param uri Target URI
+ * @param to_uri To URI
+ * @param from_name From displayname (optional)
+ * @param from_uri From URI
+ * @param routev Route vector
+ * @param routec Route count
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_alloc(struct sip_dialog **dlgp,
+ const char *uri, const char *to_uri,
+ const char *from_name, const char *from_uri,
+ const char *routev[], uint32_t routec)
+{
+ const uint64_t ltag = rand_u64();
+ struct sip_dialog *dlg;
+ struct sip_addr addr;
+ size_t rend = 0;
+ struct pl pl;
+ uint32_t i;
+ int err;
+
+ if (!dlgp || !uri || !to_uri || !from_uri)
+ return EINVAL;
+
+ dlg = mem_zalloc(sizeof(*dlg), destructor);
+ if (!dlg)
+ return ENOMEM;
+
+ dlg->hash = hash_fast_str(from_uri);
+ dlg->lseq = rand_u16();
+
+ err = str_dup(&dlg->uri, uri);
+ if (err)
+ goto out;
+
+ err = x64_strdup(&dlg->callid, rand_u64());
+ if (err)
+ goto out;
+
+ err = x64_strdup(&dlg->ltag, ltag);
+ if (err)
+ goto out;
+
+ dlg->mb = mbuf_alloc(512);
+ if (!dlg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (i=0; i<routec; i++) {
+ err |= mbuf_printf(dlg->mb, "Route: <%s;lr>\r\n", routev[i]);
+ if (i == 0)
+ rend = dlg->mb->pos - 2;
+ }
+ err |= mbuf_printf(dlg->mb, "To: <%s>\r\n", to_uri);
+ dlg->cpos = dlg->mb->pos;
+ err |= mbuf_printf(dlg->mb, "From: %s%s%s<%s>;tag=%016llx\r\n",
+ from_name ? "\"" : "", from_name,
+ from_name ? "\" " : "",
+ from_uri, ltag);
+ if (err)
+ goto out;
+
+ dlg->mb->pos = 0;
+
+ if (rend) {
+ pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+ pl.l = rend - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ dlg->route = addr.uri;
+ }
+ else {
+ pl_set_str(&pl, dlg->uri);
+ err = uri_decode(&dlg->route, &pl);
+ }
+
+ out:
+ if (err)
+ mem_deref(dlg);
+ else
+ *dlgp = dlg;
+
+ return err;
+}
+
+
+static bool record_route_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg,
+ void *arg)
+{
+ struct route_enc *renc = arg;
+ (void)msg;
+
+ if (mbuf_printf(renc->mb, "Route: %r\r\n", &hdr->val))
+ return true;
+
+ if (!renc->end)
+ renc->end = renc->mb->pos - 2;
+
+ return false;
+}
+
+
+/**
+ * Accept and create a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg)
+{
+ const struct sip_hdr *contact;
+ struct sip_dialog *dlg;
+ struct route_enc renc;
+ struct sip_addr addr;
+ struct pl pl;
+ int err;
+
+ if (!dlgp || !msg || !msg->req)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+ if (!contact || !msg->callid.p)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ dlg = mem_zalloc(sizeof(*dlg), destructor);
+ if (!dlg)
+ return ENOMEM;
+
+ dlg->hash = rand_u32();
+ dlg->lseq = rand_u16();
+ dlg->rseq = msg->cseq.num;
+
+ err = pl_strdup(&dlg->uri, &addr.auri);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&dlg->callid, &msg->callid);
+ if (err)
+ goto out;
+
+ err = x64_strdup(&dlg->ltag, msg->tag);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&dlg->rtag, &msg->from.tag);
+ if (err)
+ goto out;
+
+ dlg->mb = mbuf_alloc(512);
+ if (!dlg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ renc.mb = dlg->mb;
+ renc.end = 0;
+
+ err |= sip_msg_hdr_apply(msg, true, SIP_HDR_RECORD_ROUTE,
+ record_route_handler, &renc) ? ENOMEM : 0;
+ err |= mbuf_printf(dlg->mb, "To: %r\r\n", &msg->from.val);
+ err |= mbuf_printf(dlg->mb, "From: %r;tag=%016llx\r\n", &msg->to.val,
+ msg->tag);
+ if (err)
+ goto out;
+
+ dlg->mb->pos = 0;
+
+ if (renc.end) {
+ pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+ pl.l = renc.end - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ dlg->route = addr.uri;
+ }
+ else {
+ pl_set_str(&pl, dlg->uri);
+ err = uri_decode(&dlg->route, &pl);
+ }
+
+ out:
+ if (err)
+ mem_deref(dlg);
+ else
+ *dlgp = dlg;
+
+ return err;
+}
+
+
+/**
+ * Initialize a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlg SIP Dialog to initialize
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ char *uri = NULL, *rtag = NULL;
+ const struct sip_hdr *contact;
+ struct route_enc renc;
+ struct sip_addr addr;
+ struct pl pl;
+ int err;
+
+ if (!dlg || dlg->rtag || !dlg->cpos || !msg)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+ if (!contact)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ renc.mb = mbuf_alloc(512);
+ if (!renc.mb)
+ return ENOMEM;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&rtag, msg->req ? &msg->from.tag : &msg->to.tag);
+ if (err)
+ goto out;
+
+ renc.end = 0;
+
+ err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE,
+ record_route_handler, &renc) ? ENOMEM : 0;
+ err |= mbuf_printf(renc.mb, "To: %r\r\n",
+ msg->req ? &msg->from.val : &msg->to.val);
+
+ dlg->mb->pos = dlg->cpos;
+ err |= mbuf_write_mem(renc.mb, mbuf_buf(dlg->mb),
+ mbuf_get_left(dlg->mb));
+ dlg->mb->pos = 0;
+
+ if (err)
+ goto out;
+
+ renc.mb->pos = 0;
+
+ if (renc.end) {
+ pl.p = (const char *)mbuf_buf(renc.mb) + ROUTE_OFFSET;
+ pl.l = renc.end - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ if (err)
+ goto out;
+
+ dlg->route = addr.uri;
+ }
+ else {
+ struct uri tmp;
+
+ pl_set_str(&pl, uri);
+ err = uri_decode(&tmp, &pl);
+ if (err)
+ goto out;
+
+ dlg->route = tmp;
+ }
+
+ mem_deref(dlg->mb);
+ mem_deref(dlg->uri);
+
+ dlg->mb = mem_ref(renc.mb);
+ dlg->rtag = mem_ref(rtag);
+ dlg->uri = mem_ref(uri);
+ dlg->rseq = msg->req ? msg->cseq.num : 0;
+ dlg->cpos = 0;
+
+ out:
+ mem_deref(renc.mb);
+ mem_deref(rtag);
+ mem_deref(uri);
+
+ return err;
+}
+
+
+/**
+ * Fork a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param odlg Original SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg,
+ const struct sip_msg *msg)
+{
+ const struct sip_hdr *contact;
+ struct sip_dialog *dlg;
+ struct route_enc renc;
+ struct sip_addr addr;
+ struct pl pl;
+ int err;
+
+ if (!dlgp || !odlg || !odlg->cpos || !msg)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+ if (!contact || !msg->callid.p)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ dlg = mem_zalloc(sizeof(*dlg), destructor);
+ if (!dlg)
+ return ENOMEM;
+
+ dlg->callid = mem_ref(odlg->callid);
+ dlg->ltag = mem_ref(odlg->ltag);
+ dlg->hash = odlg->hash;
+ dlg->lseq = odlg->lseq;
+ dlg->rseq = msg->req ? msg->cseq.num : 0;
+
+ err = pl_strdup(&dlg->uri, &addr.auri);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag);
+ if (err)
+ goto out;
+
+ dlg->mb = mbuf_alloc(512);
+ if (!dlg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ renc.mb = dlg->mb;
+ renc.end = 0;
+
+ err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE,
+ record_route_handler, &renc) ? ENOMEM : 0;
+ err |= mbuf_printf(dlg->mb, "To: %r\r\n",
+ msg->req ? &msg->from.val : &msg->to.val);
+
+ odlg->mb->pos = odlg->cpos;
+ err |= mbuf_write_mem(dlg->mb, mbuf_buf(odlg->mb),
+ mbuf_get_left(odlg->mb));
+ odlg->mb->pos = 0;
+
+ if (err)
+ goto out;
+
+ dlg->mb->pos = 0;
+
+ if (renc.end) {
+ pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+ pl.l = renc.end - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ dlg->route = addr.uri;
+ }
+ else {
+ pl_set_str(&pl, dlg->uri);
+ err = uri_decode(&dlg->route, &pl);
+ }
+
+ out:
+ if (err)
+ mem_deref(dlg);
+ else
+ *dlgp = dlg;
+
+ return err;
+}
+
+
+/**
+ * Update an existing SIP Dialog from a SIP Message
+ *
+ * @param dlg SIP Dialog to update
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ const struct sip_hdr *contact;
+ struct sip_addr addr;
+ char *uri;
+ int err;
+
+ if (!dlg || !msg)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+ if (!contact)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ return err;
+
+ if (dlg->route.scheme.p == dlg->uri) {
+
+ struct uri tmp;
+ struct pl pl;
+
+ pl_set_str(&pl, uri);
+ err = uri_decode(&tmp, &pl);
+ if (err)
+ goto out;
+
+ dlg->route = tmp;
+ }
+
+ mem_deref(dlg->uri);
+ dlg->uri = mem_ref(uri);
+
+ out:
+ mem_deref(uri);
+
+ return err;
+}
+
+
+/**
+ * Check if a remote sequence number is valid
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if valid, False if invalid
+ */
+bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ if (!dlg || !msg || !msg->req)
+ return false;
+
+ if (msg->cseq.num < dlg->rseq)
+ return false;
+
+ dlg->rseq = msg->cseq.num;
+
+ return true;
+}
+
+
+int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq,
+ const char *met)
+{
+ int err = 0;
+
+ if (!mb || !dlg || !met)
+ return EINVAL;
+
+ err |= mbuf_write_mem(mb, mbuf_buf(dlg->mb), mbuf_get_left(dlg->mb));
+ err |= mbuf_printf(mb, "Call-ID: %s\r\n", dlg->callid);
+ err |= mbuf_printf(mb, "CSeq: %u %s\r\n", strcmp(met, "ACK") ?
+ dlg->lseq++ : cseq, met);
+
+ return err;
+}
+
+
+const char *sip_dialog_uri(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->uri : NULL;
+}
+
+
+const struct uri *sip_dialog_route(const struct sip_dialog *dlg)
+{
+ return dlg ? &dlg->route : NULL;
+}
+
+
+uint32_t sip_dialog_hash(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->hash : 0;
+}
+
+
+/**
+ * Get the Call-ID from a SIP Dialog
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return Call-ID string
+ */
+const char *sip_dialog_callid(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->callid : NULL;
+}
+
+
+/**
+ * Get the local sequence number from a SIP Dialog
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return Local sequence number
+ */
+uint32_t sip_dialog_lseq(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->lseq : 0;
+}
+
+
+/**
+ * Check if a SIP Dialog is established
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return True if established, False if not
+ */
+bool sip_dialog_established(const struct sip_dialog *dlg)
+{
+ return dlg && dlg->rtag;
+}
+
+
+/**
+ * Compare a SIP Dialog against a SIP Message
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if match, False if no match
+ */
+bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ if (!dlg || !msg)
+ return false;
+
+ if (pl_strcmp(&msg->callid, dlg->callid))
+ return false;
+
+ if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag))
+ return false;
+
+ if (pl_strcmp(msg->req ? &msg->from.tag : &msg->to.tag, dlg->rtag))
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Compare a half SIP Dialog against a SIP Message
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if match, False if no match
+ */
+bool sip_dialog_cmp_half(const struct sip_dialog *dlg,
+ const struct sip_msg *msg)
+{
+ if (!dlg || !msg)
+ return false;
+
+ if (pl_strcmp(&msg->callid, dlg->callid))
+ return false;
+
+ if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag))
+ return false;
+
+ return true;
+}
diff --git a/src/sip/keepalive.c b/src/sip/keepalive.c
new file mode 100644
index 0000000..1e1594f
--- /dev/null
+++ b/src/sip/keepalive.c
@@ -0,0 +1,115 @@
+/**
+ * @file sip/keepalive.c SIP Keepalive
+ *
+ * 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_sa.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static void destructor(void *arg)
+{
+ struct sip_keepalive *ka = arg;
+
+ if (ka->kap)
+ *ka->kap = NULL;
+
+ list_unlink(&ka->le);
+}
+
+
+void sip_keepalive_signal(struct list *kal, int err)
+{
+ struct le *le = list_head(kal);
+
+ while (le) {
+
+ struct sip_keepalive *ka = le->data;
+ sip_keepalive_h *kah = ka->kah;
+ void *arg = ka->arg;
+
+ le = le->next;
+
+ list_unlink(&ka->le);
+ mem_deref(ka);
+
+ kah(err, arg);
+ }
+}
+
+
+uint64_t sip_keepalive_wait(uint32_t interval)
+{
+ return interval * (800 + rand_u16() % 201);
+}
+
+
+/**
+ * Start a keepalive handler on a SIP transport
+ *
+ * @param kap Pointer to allocated keepalive object
+ * @param sip SIP Stack instance
+ * @param msg SIP Message
+ * @param interval Keepalive interval in seconds
+ * @param kah Keepalive handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_keepalive_start(struct sip_keepalive **kap, struct sip *sip,
+ const struct sip_msg *msg, uint32_t interval,
+ sip_keepalive_h *kah, void *arg)
+{
+ struct sip_keepalive *ka;
+ int err;
+
+ if (!kap || !sip || !msg || !kah)
+ return EINVAL;
+
+ ka = mem_zalloc(sizeof(*ka), destructor);
+ if (!ka)
+ return ENOMEM;
+
+ ka->kah = kah;
+ ka->arg = arg;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ err = sip_keepalive_udp(ka, sip, (struct udp_sock *)msg->sock,
+ &msg->src, interval);
+ break;
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ err = sip_keepalive_tcp(ka, (struct sip_conn *)msg->sock,
+ interval);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ if (err) {
+ mem_deref(ka);
+ }
+ else {
+ ka->kap = kap;
+ *kap = ka;
+ }
+
+ return err;
+}
diff --git a/src/sip/keepalive_udp.c b/src/sip/keepalive_udp.c
new file mode 100644
index 0000000..39efa89
--- /dev/null
+++ b/src/sip/keepalive_udp.c
@@ -0,0 +1,188 @@
+/**
+ * @file keepalive_udp.c SIP UDP Keepalive
+ *
+ * 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_stun.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ UDP_KEEPALIVE_INTVAL = 29,
+};
+
+
+struct sip_udpconn {
+ struct le he;
+ struct list kal;
+ struct tmr tmr_ka;
+ struct sa maddr;
+ struct sa paddr;
+ struct udp_sock *us;
+ struct stun_ctrans *ct;
+ struct stun *stun;
+ uint32_t ka_interval;
+};
+
+
+static void udpconn_keepalive_handler(void *arg);
+
+
+static void destructor(void *arg)
+{
+ struct sip_udpconn *uc = arg;
+
+ list_flush(&uc->kal);
+ hash_unlink(&uc->he);
+ tmr_cancel(&uc->tmr_ka);
+ mem_deref(uc->ct);
+ mem_deref(uc->us);
+ mem_deref(uc->stun);
+}
+
+
+static void udpconn_close(struct sip_udpconn *uc, int err)
+{
+ sip_keepalive_signal(&uc->kal, err);
+ hash_unlink(&uc->he);
+ tmr_cancel(&uc->tmr_ka);
+ uc->ct = mem_deref(uc->ct);
+ uc->us = mem_deref(uc->us);
+ uc->stun = mem_deref(uc->stun);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct sip_udpconn *uc = arg;
+ struct stun_attr *attr;
+ (void)reason;
+
+ if (err || scode) {
+ err = err ? err : EPROTO;
+ goto out;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr) {
+ attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+ if (!attr) {
+ err = EPROTO;
+ goto out;
+ }
+ }
+
+ if (!sa_isset(&uc->maddr, SA_ALL)) {
+ uc->maddr = attr->v.sa;
+ }
+ else if (!sa_cmp(&uc->maddr, &attr->v.sa, SA_ALL)) {
+ err = ENOTCONN;
+ goto out;
+ }
+
+ out:
+ if (err) {
+ udpconn_close(uc, err);
+ mem_deref(uc);
+ }
+ else {
+ tmr_start(&uc->tmr_ka, sip_keepalive_wait(uc->ka_interval),
+ udpconn_keepalive_handler, uc);
+ }
+}
+
+
+static void udpconn_keepalive_handler(void *arg)
+{
+ struct sip_udpconn *uc = arg;
+ int err;
+
+ if (!uc->kal.head) {
+ /* no need for us anymore */
+ udpconn_close(uc, 0);
+ mem_deref(uc);
+ return;
+ }
+
+ err = stun_request(&uc->ct, uc->stun, IPPROTO_UDP, uc->us,
+ &uc->paddr, 0, STUN_METHOD_BINDING, NULL, 0,
+ false, stun_response_handler, uc, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+ if (err) {
+ udpconn_close(uc, err);
+ mem_deref(uc);
+ }
+}
+
+
+static struct sip_udpconn *udpconn_find(struct sip *sip, struct udp_sock *us,
+ const struct sa *paddr)
+{
+ struct le *le;
+
+ le = list_head(hash_list(sip->ht_udpconn, sa_hash(paddr, SA_ALL)));
+
+ for (; le; le = le->next) {
+
+ struct sip_udpconn *uc = le->data;
+
+ if (!sa_cmp(&uc->paddr, paddr, SA_ALL))
+ continue;
+
+ if (uc->us != us)
+ continue;
+
+ return uc;
+ }
+
+ return NULL;
+}
+
+
+int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
+ struct udp_sock *us, const struct sa *paddr,
+ uint32_t interval)
+{
+ struct sip_udpconn *uc;
+
+ if (!ka || !sip || !us || !paddr)
+ return EINVAL;
+
+ uc = udpconn_find(sip, us, paddr);
+ if (!uc) {
+ uc = mem_zalloc(sizeof(*uc), destructor);
+ if (!uc)
+ return ENOMEM;
+
+ hash_append(sip->ht_udpconn, sa_hash(paddr, SA_ALL),
+ &uc->he, uc);
+
+ uc->paddr = *paddr;
+ uc->stun = mem_ref(sip->stun);
+ uc->us = mem_ref(us);
+ uc->ka_interval = interval ? interval : UDP_KEEPALIVE_INTVAL;
+
+ /* learn mapped address immediately */
+ tmr_start(&uc->tmr_ka, 0, udpconn_keepalive_handler, uc);
+ }
+
+ list_append(&uc->kal, &ka->le, ka);
+
+ return 0;
+}
diff --git a/src/sip/mod.mk b/src/sip/mod.mk
new file mode 100644
index 0000000..446c028
--- /dev/null
+++ b/src/sip/mod.mk
@@ -0,0 +1,21 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sip/addr.c
+SRCS += sip/auth.c
+SRCS += sip/contact.c
+SRCS += sip/cseq.c
+SRCS += sip/ctrans.c
+SRCS += sip/dialog.c
+SRCS += sip/keepalive.c
+SRCS += sip/keepalive_udp.c
+SRCS += sip/msg.c
+SRCS += sip/reply.c
+SRCS += sip/request.c
+SRCS += sip/sip.c
+SRCS += sip/strans.c
+SRCS += sip/transp.c
+SRCS += sip/via.c
diff --git a/src/sip/msg.c b/src/sip/msg.c
new file mode 100644
index 0000000..2647e0b
--- /dev/null
+++ b/src/sip/msg.c
@@ -0,0 +1,682 @@
+/**
+ * @file sip/msg.c SIP Message decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_sys.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_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ HDR_HASH_SIZE = 32,
+ STARTLINE_MAX = 8192,
+};
+
+
+static void hdr_destructor(void *arg)
+{
+ struct sip_hdr *hdr = arg;
+
+ list_unlink(&hdr->le);
+ hash_unlink(&hdr->he);
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_msg *msg = arg;
+
+ list_flush(&msg->hdrl);
+ hash_flush(msg->hdrht);
+ mem_deref(msg->hdrht);
+ mem_deref(msg->sock);
+ mem_deref(msg->mb);
+}
+
+
+static enum sip_hdrid hdr_hash(const struct pl *name)
+{
+ if (!name->l)
+ return SIP_HDR_NONE;
+
+ if (name->l > 1) {
+ switch (name->p[0]) {
+
+ case 'x':
+ case 'X':
+ if (name->p[1] == '-')
+ return SIP_HDR_NONE;
+
+ /*@fallthrough@*/
+
+ default:
+ return (enum sip_hdrid)
+ (hash_joaat_ci(name->p, name->l) & 0xfff);
+ }
+ }
+
+ /* compact headers */
+ switch (tolower(name->p[0])) {
+
+ case 'a': return SIP_HDR_ACCEPT_CONTACT;
+ case 'b': return SIP_HDR_REFERRED_BY;
+ case 'c': return SIP_HDR_CONTENT_TYPE;
+ case 'd': return SIP_HDR_REQUEST_DISPOSITION;
+ case 'e': return SIP_HDR_CONTENT_ENCODING;
+ case 'f': return SIP_HDR_FROM;
+ case 'i': return SIP_HDR_CALL_ID;
+ case 'j': return SIP_HDR_REJECT_CONTACT;
+ case 'k': return SIP_HDR_SUPPORTED;
+ case 'l': return SIP_HDR_CONTENT_LENGTH;
+ case 'm': return SIP_HDR_CONTACT;
+ case 'n': return SIP_HDR_IDENTITY_INFO;
+ case 'o': return SIP_HDR_EVENT;
+ case 'r': return SIP_HDR_REFER_TO;
+ case 's': return SIP_HDR_SUBJECT;
+ case 't': return SIP_HDR_TO;
+ case 'u': return SIP_HDR_ALLOW_EVENTS;
+ case 'v': return SIP_HDR_VIA;
+ case 'x': return SIP_HDR_SESSION_EXPIRES;
+ case 'y': return SIP_HDR_IDENTITY;
+ default: return SIP_HDR_NONE;
+ }
+}
+
+
+static inline bool hdr_comma_separated(enum sip_hdrid id)
+{
+ switch (id) {
+
+ case SIP_HDR_ACCEPT:
+ case SIP_HDR_ACCEPT_CONTACT:
+ case SIP_HDR_ACCEPT_ENCODING:
+ case SIP_HDR_ACCEPT_LANGUAGE:
+ case SIP_HDR_ACCEPT_RESOURCE_PRIORITY:
+ case SIP_HDR_ALERT_INFO:
+ case SIP_HDR_ALLOW:
+ case SIP_HDR_ALLOW_EVENTS:
+ case SIP_HDR_AUTHENTICATION_INFO:
+ case SIP_HDR_CALL_INFO:
+ case SIP_HDR_CONTACT:
+ case SIP_HDR_CONTENT_ENCODING:
+ case SIP_HDR_CONTENT_LANGUAGE:
+ case SIP_HDR_ERROR_INFO:
+ case SIP_HDR_HISTORY_INFO:
+ case SIP_HDR_IN_REPLY_TO:
+ case SIP_HDR_P_ASSERTED_IDENTITY:
+ case SIP_HDR_P_ASSOCIATED_URI:
+ case SIP_HDR_P_EARLY_MEDIA:
+ case SIP_HDR_P_MEDIA_AUTHORIZATION:
+ case SIP_HDR_P_PREFERRED_IDENTITY:
+ case SIP_HDR_P_REFUSED_URI_LIST:
+ case SIP_HDR_P_VISITED_NETWORK_ID:
+ case SIP_HDR_PATH:
+ case SIP_HDR_PERMISSION_MISSING:
+ case SIP_HDR_PROXY_REQUIRE:
+ case SIP_HDR_REASON:
+ case SIP_HDR_RECORD_ROUTE:
+ case SIP_HDR_REJECT_CONTACT:
+ case SIP_HDR_REQUEST_DISPOSITION:
+ case SIP_HDR_REQUIRE:
+ case SIP_HDR_RESOURCE_PRIORITY:
+ case SIP_HDR_ROUTE:
+ case SIP_HDR_SECURITY_CLIENT:
+ case SIP_HDR_SECURITY_SERVER:
+ case SIP_HDR_SECURITY_VERIFY:
+ case SIP_HDR_SERVICE_ROUTE:
+ case SIP_HDR_SUPPORTED:
+ case SIP_HDR_TRIGGER_CONSENT:
+ case SIP_HDR_UNSUPPORTED:
+ case SIP_HDR_VIA:
+ case SIP_HDR_WARNING:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+
+static inline int hdr_add(struct sip_msg *msg, const struct pl *name,
+ enum sip_hdrid id, const char *p, ssize_t l,
+ bool atomic, bool line)
+{
+ struct sip_hdr *hdr;
+ int err = 0;
+
+ hdr = mem_zalloc(sizeof(*hdr), hdr_destructor);
+ if (!hdr)
+ return ENOMEM;
+
+ hdr->name = *name;
+ hdr->val.p = p;
+ hdr->val.l = MAX(l, 0);
+ hdr->id = id;
+
+ switch (id) {
+
+ case SIP_HDR_VIA:
+ case SIP_HDR_ROUTE:
+ if (!atomic)
+ break;
+
+ hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
+ list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
+ break;
+
+ default:
+ if (atomic)
+ hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
+ if (line)
+ list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
+ break;
+ }
+
+ /* parse common headers */
+ switch (id) {
+
+ case SIP_HDR_VIA:
+ if (!atomic || pl_isset(&msg->via.sentby))
+ break;
+
+ err = sip_via_decode(&msg->via, &hdr->val);
+ break;
+
+ case SIP_HDR_TO:
+ err = sip_addr_decode((struct sip_addr *)&msg->to, &hdr->val);
+ if (err)
+ break;
+
+ (void)msg_param_decode(&msg->to.params, "tag", &msg->to.tag);
+ msg->to.val = hdr->val;
+ break;
+
+ case SIP_HDR_FROM:
+ err = sip_addr_decode((struct sip_addr *)&msg->from,
+ &hdr->val);
+ if (err)
+ break;
+
+ (void)msg_param_decode(&msg->from.params, "tag",
+ &msg->from.tag);
+ msg->from.val = hdr->val;
+ break;
+
+ case SIP_HDR_CALL_ID:
+ msg->callid = hdr->val;
+ break;
+
+ case SIP_HDR_CSEQ:
+ err = sip_cseq_decode(&msg->cseq, &hdr->val);
+ break;
+
+ case SIP_HDR_MAX_FORWARDS:
+ msg->maxfwd = hdr->val;
+ break;
+
+ case SIP_HDR_CONTENT_TYPE:
+ err = msg_ctype_decode(&msg->ctyp, &hdr->val);
+ break;
+
+ case SIP_HDR_CONTENT_LENGTH:
+ msg->clen = hdr->val;
+ break;
+
+ case SIP_HDR_EXPIRES:
+ msg->expires = hdr->val;
+ break;
+
+ default:
+ /* re_printf("%r = %u\n", &hdr->name, id); */
+ break;
+ }
+
+ mem_deref(hdr);
+
+ return err;
+}
+
+
+/**
+ * Decode a SIP message
+ *
+ * @param msgp Pointer to allocated SIP Message
+ * @param mb Buffer containing SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb)
+{
+ struct pl x, y, z, e, name;
+ const char *p, *v, *cv;
+ struct sip_msg *msg;
+ bool comsep, quote;
+ enum sip_hdrid id = SIP_HDR_NONE;
+ uint32_t ws, lf;
+ size_t l;
+ int err;
+
+ if (!msgp || !mb)
+ return EINVAL;
+
+ p = (const char *)mbuf_buf(mb);
+ l = mbuf_get_left(mb);
+
+ if (re_regex(p, l, "[^ \t\r\n]+ [^ \t\r\n]+ [^\r\n]*[\r]*[\n]1",
+ &x, &y, &z, NULL, &e) || x.p != (char *)mbuf_buf(mb))
+ return (l > STARTLINE_MAX) ? EBADMSG : ENODATA;
+
+ msg = mem_zalloc(sizeof(*msg), destructor);
+ if (!msg)
+ return ENOMEM;
+
+ err = hash_alloc(&msg->hdrht, HDR_HASH_SIZE);
+ if (err)
+ goto out;
+
+ msg->tag = rand_u64();
+ msg->mb = mem_ref(mb);
+ msg->req = (0 == pl_strcmp(&z, "SIP/2.0"));
+
+ if (msg->req) {
+
+ msg->met = x;
+ msg->ruri = y;
+ msg->ver = z;
+
+ if (uri_decode(&msg->uri, &y)) {
+ err = EBADMSG;
+ goto out;
+ }
+ }
+ else {
+ msg->ver = x;
+ msg->scode = pl_u32(&y);
+ msg->reason = z;
+
+ if (!msg->scode) {
+ err = EBADMSG;
+ goto out;
+ }
+ }
+
+ l -= e.p + e.l - p;
+ p = e.p + e.l;
+
+ name.p = v = cv = NULL;
+ name.l = ws = lf = 0;
+ comsep = false;
+ quote = false;
+
+ for (; l > 0; p++, l--) {
+
+ switch (*p) {
+
+ case ' ':
+ case '\t':
+ lf = 0; /* folding */
+ ++ws;
+ break;
+
+ case '\r':
+ ++ws;
+ break;
+
+ case '\n':
+ ++ws;
+
+ if (!lf++)
+ break;
+
+ ++p; --l; /* eoh */
+
+ /*@fallthrough@*/
+
+ default:
+ if (lf || (*p == ',' && comsep && !quote)) {
+
+ if (!name.l) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ err = hdr_add(msg, &name, id, cv ? cv : p,
+ cv ? p - cv - ws : 0,
+ true, cv == v && lf);
+ if (err)
+ goto out;
+
+ if (!lf) { /* comma separated */
+ cv = NULL;
+ break;
+ }
+
+ if (cv != v) {
+ err = hdr_add(msg, &name, id,
+ v ? v : p,
+ v ? p - v - ws : 0,
+ false, true);
+ if (err)
+ goto out;
+ }
+
+ if (lf > 1) { /* eoh */
+ err = 0;
+ goto out;
+ }
+
+ comsep = false;
+ name.p = NULL;
+ cv = v = NULL;
+ lf = 0;
+ }
+
+ if (!name.p) {
+ name.p = p;
+ name.l = 0;
+ ws = 0;
+ }
+
+ if (!name.l) {
+ if (*p != ':') {
+ ws = 0;
+ break;
+ }
+
+ name.l = MAX((int)(p - name.p - ws), 0);
+ if (!name.l) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ id = hdr_hash(&name);
+ comsep = hdr_comma_separated(id);
+ break;
+ }
+
+ if (!cv) {
+ quote = false;
+ cv = p;
+ }
+
+ if (!v) {
+ v = p;
+ }
+
+ if (*p == '"')
+ quote = !quote;
+
+ ws = 0;
+ break;
+ }
+ }
+
+ err = ENODATA;
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else {
+ *msgp = msg;
+ mb->pos = mb->end - l;
+ }
+
+ return err;
+}
+
+
+/**
+ * Get a SIP Header from a SIP Message
+ *
+ * @param msg SIP Message
+ * @param id SIP Header ID
+ *
+ * @return SIP Header if found, NULL if not found
+ */
+const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg, enum sip_hdrid id)
+{
+ return sip_msg_hdr_apply(msg, true, id, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain SIP Headers
+ *
+ * @param msg SIP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param id SIP Header ID
+ * @param h Function handler
+ * @param arg Handler argument
+ *
+ * @return SIP Header if handler returns true, otherwise NULL
+ */
+const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg,
+ bool fwd, enum sip_hdrid id,
+ sip_hdr_h *h, void *arg)
+{
+ struct list *lst;
+ struct le *le;
+
+ if (!msg)
+ return NULL;
+
+ lst = hash_list(msg->hdrht, id);
+
+ le = fwd ? list_head(lst) : list_tail(lst);
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = fwd ? le->next : le->prev;
+
+ if (hdr->id != id)
+ continue;
+
+ if (!h || h(hdr, msg, arg))
+ return hdr;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get an unknown SIP Header from a SIP Message
+ *
+ * @param msg SIP Message
+ * @param name Header name
+ *
+ * @return SIP Header if found, NULL if not found
+ */
+const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg, const char *name)
+{
+ return sip_msg_xhdr_apply(msg, true, name, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain unknown SIP Headers
+ *
+ * @param msg SIP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param name SIP Header name
+ * @param h Function handler
+ * @param arg Handler argument
+ *
+ * @return SIP Header if handler returns true, otherwise NULL
+ */
+const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg,
+ bool fwd, const char *name,
+ sip_hdr_h *h, void *arg)
+{
+ struct list *lst;
+ struct le *le;
+ struct pl pl;
+
+ if (!msg || !name)
+ return NULL;
+
+ pl_set_str(&pl, name);
+
+ lst = hash_list(msg->hdrht, hdr_hash(&pl));
+
+ le = fwd ? list_head(lst) : list_tail(lst);
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = fwd ? le->next : le->prev;
+
+ if (pl_casecmp(&hdr->name, &pl))
+ continue;
+
+ if (!h || h(hdr, msg, arg))
+ return hdr;
+ }
+
+ return NULL;
+}
+
+
+static bool count_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ uint32_t *n = arg;
+ (void)hdr;
+ (void)msg;
+
+ ++(*n);
+
+ return false;
+}
+
+
+/**
+ * Count the number of SIP Headers
+ *
+ * @param msg SIP Message
+ * @param id SIP Header ID
+ *
+ * @return Number of SIP Headers
+ */
+uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id)
+{
+ uint32_t n = 0;
+
+ sip_msg_hdr_apply(msg, true, id, count_handler, &n);
+
+ return n;
+}
+
+
+/**
+ * Count the number of unknown SIP Headers
+ *
+ * @param msg SIP Message
+ * @param name SIP Header name
+ *
+ * @return Number of SIP Headers
+ */
+uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name)
+{
+ uint32_t n = 0;
+
+ sip_msg_xhdr_apply(msg, true, name, count_handler, &n);
+
+ return n;
+}
+
+
+static bool value_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)msg;
+
+ return 0 == pl_strcasecmp(&hdr->val, (const char *)arg);
+}
+
+
+/**
+ * Check if a SIP Header matches a certain value
+ *
+ * @param msg SIP Message
+ * @param id SIP Header ID
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id,
+ const char *value)
+{
+ return NULL != sip_msg_hdr_apply(msg, true, id, value_handler,
+ (void *)value);
+}
+
+
+/**
+ * Check if an unknown SIP Header matches a certain value
+ *
+ * @param msg SIP Message
+ * @param name SIP Header name
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name,
+ const char *value)
+{
+ return NULL != sip_msg_xhdr_apply(msg, true, name, value_handler,
+ (void *)value);
+}
+
+
+/**
+ * Print a SIP Message to stdout
+ *
+ * @param msg SIP Message
+ */
+void sip_msg_dump(const struct sip_msg *msg)
+{
+ struct le *le;
+ uint32_t i;
+
+ if (!msg)
+ return;
+
+ for (i=0; i<HDR_HASH_SIZE; i++) {
+
+ le = list_head(hash_list(msg->hdrht, i));
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = le->next;
+
+ (void)re_printf("%02u '%r'='%r'\n", i, &hdr->name,
+ &hdr->val);
+ }
+ }
+
+ le = list_head(&msg->hdrl);
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = le->next;
+
+ (void)re_printf("%02u '%r'='%r'\n", hdr->id, &hdr->name,
+ &hdr->val);
+ }
+}
diff --git a/src/sip/reply.c b/src/sip/reply.c
new file mode 100644
index 0000000..eb2e26f
--- /dev/null
+++ b/src/sip/reply.c
@@ -0,0 +1,268 @@
+/**
+ * @file sip/reply.c SIP 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_fmt.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static int vreplyf(struct sip_strans **stp, struct mbuf **mbp, bool trans,
+ struct sip *sip, const struct sip_msg *msg, bool rec_route,
+ uint16_t scode, const char *reason,
+ const char *fmt, va_list ap)
+{
+ bool rport = false;
+ uint32_t viac = 0;
+ struct mbuf *mb;
+ struct sa dst;
+ struct le *le;
+ int err;
+
+ if (!sip || !msg || !reason)
+ return EINVAL;
+
+ if (!pl_strcmp(&msg->met, "ACK"))
+ return 0;
+
+ mb = mbuf_alloc(1024);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = mbuf_printf(mb, "SIP/2.0 %u %s\r\n", scode, reason);
+
+ for (le = msg->hdrl.head; le; le = le->next) {
+
+ struct sip_hdr *hdr = le->data;
+ struct pl rp;
+
+ switch (hdr->id) {
+
+ case SIP_HDR_VIA:
+ err |= mbuf_printf(mb, "%r: ", &hdr->name);
+ if (viac++) {
+ err |= mbuf_printf(mb, "%r\r\n", &hdr->val);
+ break;
+ }
+
+ if (!msg_param_exists(&msg->via.params, "rport", &rp)){
+ err |= mbuf_write_pl_skip(mb, &hdr->val, &rp);
+ err |= mbuf_printf(mb, ";rport=%u",
+ sa_port(&msg->src));
+ rport = true;
+ }
+ else
+ err |= mbuf_write_pl(mb, &hdr->val);
+
+ if (rport || !sa_cmp(&msg->src, &msg->via.addr,
+ SA_ADDR))
+ err |= mbuf_printf(mb, ";received=%j",
+ &msg->src);
+
+ err |= mbuf_write_str(mb, "\r\n");
+ break;
+
+ case SIP_HDR_TO:
+ err |= mbuf_printf(mb, "%r: %r", &hdr->name,
+ &hdr->val);
+ if (!pl_isset(&msg->to.tag) && scode > 100)
+ err |= mbuf_printf(mb, ";tag=%016llx",
+ msg->tag);
+ err |= mbuf_write_str(mb, "\r\n");
+ break;
+
+ case SIP_HDR_RECORD_ROUTE:
+ if (!rec_route)
+ break;
+
+ /*@fallthrough@*/
+
+ case SIP_HDR_FROM:
+ case SIP_HDR_CALL_ID:
+ case SIP_HDR_CSEQ:
+ err |= mbuf_printf(mb, "%r: %r\r\n",
+ &hdr->name, &hdr->val);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (sip->software)
+ err |= mbuf_printf(mb, "Server: %s\r\n", sip->software);
+
+ if (fmt)
+ err |= mbuf_vprintf(mb, fmt, ap);
+ else
+ err |= mbuf_printf(mb, "Content-Length: 0\r\n\r\n");
+
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ sip_reply_addr(&dst, msg, rport);
+
+ if (trans) {
+ err = sip_strans_reply(stp, sip, msg, &dst, scode, mb);
+ }
+ else {
+ err = sip_send(sip, msg->sock, msg->tp, &dst, mb);
+ }
+
+ out:
+ if (err && stp)
+ *stp = mem_deref(*stp);
+
+ if (!err && mbp)
+ *mbp = mb;
+ else
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Formatted reply using Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction (optional)
+ * @param mbp Pointer to allocated SIP message buffer (optional)
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param rec_route True to copy Record-Route headers
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param fmt Additional formatted SIP headers and body, otherwise NULL
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_treplyf(struct sip_strans **stp, struct mbuf **mbp, struct sip *sip,
+ const struct sip_msg *msg, bool rec_route, uint16_t scode,
+ const char *reason, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = vreplyf(stp, mbp, true, sip, msg, rec_route, scode, reason,
+ fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Reply using Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction (optional)
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_treply(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, uint16_t scode, const char *reason)
+{
+ return sip_treplyf(stp, NULL, sip, msg, false, scode, reason, NULL);
+}
+
+
+/**
+ * Stateless formatted reply
+ *
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param fmt Additional formatted SIP headers and body, otherwise NULL
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_replyf(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+ const char *reason, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = vreplyf(NULL, NULL, false, sip, msg, false, scode, reason,
+ fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Stateless reply
+ *
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_reply(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+ const char *reason)
+{
+ return sip_replyf(sip, msg, scode, reason, NULL);
+}
+
+
+/**
+ * Get the reply address from a SIP message
+ *
+ * @param addr Network address, set on return
+ * @param msg SIP message
+ * @param rport Rport value
+ */
+void sip_reply_addr(struct sa *addr, const struct sip_msg *msg, bool rport)
+{
+ uint16_t port;
+ struct pl pl;
+
+ if (!addr || !msg)
+ return;
+
+ port = sa_port(&msg->via.addr);
+ *addr = msg->src;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ if (!msg_param_decode(&msg->via.params, "maddr", &pl)) {
+ (void)sa_set(addr, &pl,sip_transp_port(msg->tp, port));
+ break;
+ }
+
+ if (rport)
+ break;
+
+ /*@fallthrough@*/
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ sa_set_port(addr, sip_transp_port(msg->tp, port));
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/src/sip/request.c b/src/sip/request.c
new file mode 100644
index 0000000..aca2935
--- /dev/null
+++ b/src/sip/request.c
@@ -0,0 +1,936 @@
+/**
+ * @file sip/request.c SIP 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_fmt.h>
+#include <re_dns.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+struct sip_request {
+ struct le le;
+ struct list cachel;
+ struct list addrl;
+ struct list srvl;
+ struct sip_request **reqp;
+ struct sip_ctrans *ct;
+ struct dns_query *dnsq;
+ struct dns_query *dnsq2;
+ struct sip *sip;
+ char *met;
+ char *uri;
+ char *host;
+ struct mbuf *mb;
+ sip_send_h *sendh;
+ sip_resp_h *resph;
+ void *arg;
+ size_t sortkey;
+ enum sip_transp tp;
+ bool tp_selected;
+ bool stateful;
+ bool canceled;
+ bool provrecv;
+ uint16_t port;
+};
+
+
+static int request_next(struct sip_request *req);
+static bool rr_append_handler(struct dnsrr *rr, void *arg);
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg);
+static int srv_lookup(struct sip_request *req, const char *domain);
+static int addr_lookup(struct sip_request *req, const char *name);
+
+
+static int str_ldup(char **dst, const char *src, int len)
+{
+ struct pl pl;
+
+ pl.p = src;
+ pl.l = len < 0 ? str_len(src) : (size_t)len;
+
+ return pl_strdup(dst, &pl);
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_request *req = arg;
+
+ if (req->reqp && req->stateful) {
+ /* user does deref before request has completed */
+ *req->reqp = NULL;
+ req->reqp = NULL;
+ req->sendh = NULL;
+ req->resph = NULL;
+ sip_request_cancel(mem_ref(req));
+ return;
+ }
+
+ list_flush(&req->cachel);
+ list_flush(&req->addrl);
+ list_flush(&req->srvl);
+ list_unlink(&req->le);
+ mem_deref(req->dnsq);
+ mem_deref(req->dnsq2);
+ mem_deref(req->ct);
+ mem_deref(req->met);
+ mem_deref(req->uri);
+ mem_deref(req->host);
+ mem_deref(req->mb);
+}
+
+
+static void terminate(struct sip_request *req, int err,
+ const struct sip_msg *msg)
+{
+ if (req->reqp) {
+ *req->reqp = NULL;
+ req->reqp = NULL;
+ }
+
+ list_unlink(&req->le);
+ req->sendh = NULL;
+
+ if (req->resph) {
+ req->resph(err, msg, req->arg);
+ req->resph = NULL;
+ }
+}
+
+
+static bool close_handler(struct le *le, void *arg)
+{
+ struct sip_request *req = le->data;
+ (void)arg;
+
+ req->dnsq = mem_deref(req->dnsq);
+ req->dnsq2 = mem_deref(req->dnsq2);
+ req->ct = mem_deref(req->ct);
+
+ terminate(req, ECONNABORTED, NULL);
+ mem_deref(req);
+
+ return false;
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sip_request *req = arg;
+
+ if (msg && msg->scode < 200) {
+ if (!req->provrecv) {
+ req->provrecv = true;
+ if (req->canceled)
+ (void)sip_ctrans_cancel(req->ct);
+ }
+
+ if (req->resph)
+ req->resph(err, msg, req->arg);
+
+ return;
+ }
+
+ req->ct = NULL;
+
+ if (!req->canceled && (err || msg->scode == 503) &&
+ (req->addrl.head || req->srvl.head)) {
+
+ err = request_next(req);
+ if (!err)
+ return;
+ }
+
+ terminate(req, err, msg);
+ mem_deref(req);
+}
+
+
+static int request(struct sip_request *req, enum sip_transp tp,
+ const struct sa *dst)
+{
+ struct mbuf *mb = NULL;
+ char *branch = NULL;
+ int err = ENOMEM;
+ struct sa laddr;
+
+ req->provrecv = false;
+
+ branch = mem_alloc(24, NULL);
+ mb = mbuf_alloc(1024);
+
+ if (!branch || !mb)
+ goto out;
+
+ (void)re_snprintf(branch, 24, "z9hG4bK%016llx", rand_u64());
+
+ err = sip_transp_laddr(req->sip, &laddr, tp, dst);
+ if (err)
+ goto out;
+
+ err = mbuf_printf(mb, "%s %s SIP/2.0\r\n", req->met, req->uri);
+ err |= mbuf_printf(mb, "Via: SIP/2.0/%s %J;branch=%s;rport\r\n",
+ sip_transp_name(tp), &laddr, branch);
+ err |= req->sendh ? req->sendh(tp, &laddr, dst, mb, req->arg) : 0;
+ err |= mbuf_write_mem(mb, mbuf_buf(req->mb), mbuf_get_left(req->mb));
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ if (!req->stateful)
+ err = sip_send(req->sip, NULL, tp, dst, mb);
+ else
+ err = sip_ctrans_request(&req->ct, req->sip, tp, dst, req->met,
+ branch, mb, response_handler, req);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(branch);
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static int request_next(struct sip_request *req)
+{
+ struct dnsrr *rr;
+ struct sa dst;
+ int err;
+
+ again:
+ rr = list_ledata(req->addrl.head);
+ if (!rr) {
+ rr = list_ledata(req->srvl.head);
+ if (!rr)
+ return ENOENT;
+
+ req->port = rr->rdata.srv.port;
+
+ dns_rrlist_apply2(&req->cachel, rr->rdata.srv.target,
+ DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
+ true, rr_append_handler, &req->addrl);
+
+ list_unlink(&rr->le);
+
+ if (req->addrl.head) {
+ dns_rrlist_sort_addr(&req->addrl, req->sortkey);
+ mem_deref(rr);
+ goto again;
+ }
+
+ err = addr_lookup(req, rr->rdata.srv.target);
+ mem_deref(rr);
+
+ return err;
+ }
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ sa_set_in(&dst, rr->rdata.a.addr, req->port);
+ break;
+
+ case DNS_TYPE_AAAA:
+ sa_set_in6(&dst, rr->rdata.aaaa.addr, req->port);
+ break;
+
+ default:
+ return EINVAL;
+ }
+
+ list_unlink(&rr->le);
+ mem_deref(rr);
+
+ err = request(req, req->tp, &dst);
+ if (err) {
+ if (req->addrl.head || req->srvl.head)
+ goto again;
+ }
+ else if (!req->stateful) {
+ req->resph = NULL;
+ terminate(req, 0, NULL);
+ mem_deref(req);
+ }
+
+ return err;
+}
+
+
+static bool transp_next(struct sip *sip, enum sip_transp *tp)
+{
+ enum sip_transp i;
+
+ for (i=(enum sip_transp)(*tp+1); i<SIP_TRANSPC; i++) {
+
+ if (!sip_transp_supported(sip, i, AF_UNSPEC))
+ continue;
+
+ *tp = i;
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool transp_next_srv(struct sip *sip, enum sip_transp *tp)
+{
+ enum sip_transp i;
+
+ for (i=(enum sip_transp)(*tp-1); i>SIP_TRANSP_NONE; i--) {
+
+ if (!sip_transp_supported(sip, i, AF_UNSPEC))
+ continue;
+
+ *tp = i;
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool rr_append_handler(struct dnsrr *rr, void *arg)
+{
+ struct list *lst = arg;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ case DNS_TYPE_AAAA:
+ case DNS_TYPE_SRV:
+ if (rr->le.list)
+ break;
+
+ list_append(lst, &rr->le, mem_ref(rr));
+ break;
+ }
+
+ return false;
+}
+
+
+static bool rr_cache_handler(struct dnsrr *rr, void *arg)
+{
+ struct sip_request *req = arg;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ if (!sip_transp_supported(req->sip, req->tp, AF_INET))
+ break;
+
+ list_unlink(&rr->le_priv);
+ list_append(&req->cachel, &rr->le_priv, rr);
+ break;
+
+#ifdef HAVE_INET6
+ case DNS_TYPE_AAAA:
+ if (!sip_transp_supported(req->sip, req->tp, AF_INET6))
+ break;
+
+ list_unlink(&rr->le_priv);
+ list_append(&req->cachel, &rr->le_priv, rr);
+ break;
+#endif
+
+ case DNS_TYPE_CNAME:
+ list_unlink(&rr->le_priv);
+ list_append(&req->cachel, &rr->le_priv, rr);
+ break;
+ }
+
+ return false;
+}
+
+
+static bool rr_naptr_handler(struct dnsrr *rr, void *arg)
+{
+ struct sip_request *req = arg;
+ enum sip_transp tp;
+
+ if (rr->type != DNS_TYPE_NAPTR)
+ return false;
+
+ if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2U"))
+ tp = SIP_TRANSP_UDP;
+ else if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2T"))
+ tp = SIP_TRANSP_TCP;
+ else if (!str_casecmp(rr->rdata.naptr.services, "SIPS+D2T"))
+ tp = SIP_TRANSP_TLS;
+ else
+ return false;
+
+ if (!sip_transp_supported(req->sip, tp, AF_UNSPEC))
+ return false;
+
+ req->tp = tp;
+ req->tp_selected = true;
+
+ return true;
+}
+
+
+static void naptr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct sip_request *req = arg;
+ struct dnsrr *rr;
+ (void)hdr;
+ (void)authl;
+
+ dns_rrlist_sort(ansl, DNS_TYPE_NAPTR, req->sortkey);
+
+ rr = dns_rrlist_apply(ansl, NULL, DNS_TYPE_NAPTR, DNS_CLASS_IN, false,
+ rr_naptr_handler, req);
+ if (!rr) {
+ req->tp = SIP_TRANSPC;
+ if (!transp_next_srv(req->sip, &req->tp)) {
+ err = EPROTONOSUPPORT;
+ goto fail;
+ }
+
+ err = srv_lookup(req, req->host);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ dns_rrlist_apply(addl, rr->rdata.naptr.replace, DNS_TYPE_SRV,
+ DNS_CLASS_IN, true, rr_append_handler, &req->srvl);
+
+ if (!req->srvl.head) {
+ err = dnsc_query(&req->dnsq, req->sip->dnsc,
+ rr->rdata.naptr.replace, DNS_TYPE_SRV,
+ DNS_CLASS_IN, true, srv_handler, req);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
+
+ dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
+ rr_cache_handler, req);
+
+ err = request_next(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ terminate(req, err, NULL);
+ mem_deref(req);
+}
+
+
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct sip_request *req = arg;
+ (void)hdr;
+ (void)authl;
+
+ dns_rrlist_apply(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false,
+ rr_append_handler, &req->srvl);
+
+ if (!req->srvl.head) {
+ if (!req->tp_selected) {
+ if (transp_next_srv(req->sip, &req->tp)) {
+
+ err = srv_lookup(req, req->host);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ req->tp = SIP_TRANSP_NONE;
+ if (!transp_next(req->sip, &req->tp)) {
+ err = EPROTONOSUPPORT;
+ goto fail;
+ }
+ }
+
+ req->port = sip_transp_port(req->tp, 0);
+
+ err = addr_lookup(req, req->host);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
+
+ dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
+ rr_cache_handler, req);
+
+ err = request_next(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ terminate(req, err, NULL);
+ mem_deref(req);
+}
+
+
+static void addr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct sip_request *req = arg;
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ dns_rrlist_apply2(ansl, NULL, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
+ false, rr_append_handler, &req->addrl);
+
+ /* wait for other (A/AAAA) query to complete */
+ if (req->dnsq || req->dnsq2)
+ return;
+
+ if (!req->addrl.head && !req->srvl.head) {
+ err = err ? err : EDESTADDRREQ;
+ goto fail;
+ }
+
+ dns_rrlist_sort_addr(&req->addrl, req->sortkey);
+
+ err = request_next(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ terminate(req, err, NULL);
+ mem_deref(req);
+}
+
+
+static int srv_lookup(struct sip_request *req, const char *domain)
+{
+ char name[256];
+
+ if (re_snprintf(name, sizeof(name), "%s.%s",
+ sip_transp_srvid(req->tp), domain) < 0)
+ return ENOMEM;
+
+ return dnsc_query(&req->dnsq, req->sip->dnsc, name, DNS_TYPE_SRV,
+ DNS_CLASS_IN, true, srv_handler, req);
+}
+
+
+static int addr_lookup(struct sip_request *req, const char *name)
+{
+ int err;
+
+ if (sip_transp_supported(req->sip, req->tp, AF_INET)) {
+
+ err = dnsc_query(&req->dnsq, req->sip->dnsc, name,
+ DNS_TYPE_A, DNS_CLASS_IN, true,
+ addr_handler, req);
+ if (err)
+ return err;
+ }
+
+#ifdef HAVE_INET6
+ if (sip_transp_supported(req->sip, req->tp, AF_INET6)) {
+
+ err = dnsc_query(&req->dnsq2, req->sip->dnsc, name,
+ DNS_TYPE_AAAA, DNS_CLASS_IN, true,
+ addr_handler, req);
+ if (err)
+ return err;
+ }
+#endif
+
+ if (!req->dnsq && !req->dnsq2)
+ return EPROTONOSUPPORT;
+
+ return 0;
+}
+
+
+/**
+ * Send a SIP request
+ *
+ * @param reqp Pointer to allocated SIP request object
+ * @param sip SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met SIP Method string
+ * @param metl Length of SIP Method string
+ * @param uri Request URI
+ * @param uril Length of Request URI string
+ * @param route Next hop route URI
+ * @param mb Buffer containing SIP request
+ * @param sortkey Key for DNS record sorting
+ * @param sendh Send handler
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, int metl, const char *uri, int uril,
+ const struct uri *route, struct mbuf *mb, size_t sortkey,
+ sip_send_h *sendh, sip_resp_h *resph, void *arg)
+{
+ struct sip_request *req;
+ struct sa dst;
+ struct pl pl;
+ int err;
+
+ if (!sip || !met || !uri || !route || !mb)
+ return EINVAL;
+
+ if (pl_strcasecmp(&route->scheme, "sip"))
+ return ENOSYS;
+
+ req = mem_zalloc(sizeof(*req), destructor);
+ if (!req)
+ return ENOMEM;
+
+ list_append(&sip->reql, &req->le, req);
+
+ err = str_ldup(&req->met, met, metl);
+ if (err)
+ goto out;
+
+ err = str_ldup(&req->uri, uri, uril);
+ if (err)
+ goto out;
+
+ if (msg_param_decode(&route->params, "maddr", &pl))
+ pl = route->host;
+
+ err = pl_strdup(&req->host, &pl);
+ if (err)
+ goto out;
+
+ req->stateful = stateful;
+ req->sortkey = sortkey;
+ req->mb = mem_ref(mb);
+ req->sip = sip;
+ req->sendh = sendh;
+ req->resph = resph;
+ req->arg = arg;
+
+ if (!msg_param_decode(&route->params, "transport", &pl)) {
+
+ if (!pl_strcasecmp(&pl, "udp"))
+ req->tp = SIP_TRANSP_UDP;
+ else if (!pl_strcasecmp(&pl, "tcp"))
+ req->tp = SIP_TRANSP_TCP;
+ else if (!pl_strcasecmp(&pl, "tls"))
+ req->tp = SIP_TRANSP_TLS;
+ else {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ if (!sip_transp_supported(sip, req->tp, AF_UNSPEC)) {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ req->tp_selected = true;
+ }
+ else {
+ req->tp = SIP_TRANSP_NONE;
+ if (!transp_next(sip, &req->tp)) {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ req->tp_selected = false;
+ }
+
+ if (!sa_set_str(&dst, req->host,
+ sip_transp_port(req->tp, route->port))) {
+
+ err = request(req, req->tp, &dst);
+ if (!req->stateful) {
+ mem_deref(req);
+ return err;
+ }
+ }
+ else if (route->port) {
+
+ req->port = sip_transp_port(req->tp, route->port);
+ err = addr_lookup(req, req->host);
+ }
+ else if (req->tp_selected) {
+
+ err = srv_lookup(req, req->host);
+ }
+ else {
+ err = dnsc_query(&req->dnsq, sip->dnsc, req->host,
+ DNS_TYPE_NAPTR, DNS_CLASS_IN, true,
+ naptr_handler, req);
+ }
+
+ out:
+ if (err)
+ mem_deref(req);
+ else if (reqp) {
+ req->reqp = reqp;
+ *reqp = req;
+ }
+
+ return err;
+}
+
+
+/**
+ * Send a SIP request with formatted arguments
+ *
+ * @param reqp Pointer to allocated SIP request object
+ * @param sip SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met Null-terminated SIP Method string
+ * @param uri Null-terminated Request URI string
+ * @param route Next hop route URI (optional)
+ * @param auth SIP authentication state
+ * @param sendh Send handler
+ * @param resph Response handler
+ * @param arg Handler argument
+ * @param fmt Formatted SIP headers and body
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, const char *uri, const struct uri *route,
+ struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+ void *arg, const char *fmt, ...)
+{
+ struct uri lroute;
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!sip || !met || !uri || !fmt)
+ return EINVAL;
+
+ if (!route) {
+ struct pl uripl;
+
+ pl_set_str(&uripl, uri);
+
+ err = uri_decode(&lroute, &uripl);
+ if (err)
+ return err;
+
+ route = &lroute;
+ }
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+
+ if (auth)
+ err |= sip_auth_encode(mb, auth, met, uri);
+
+ if (err)
+ goto out;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sip_request(reqp, sip, stateful, met, -1, uri, -1, route, mb,
+ (size_t)arg, sendh, resph, arg);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send a SIP dialog request with formatted arguments
+ *
+ * @param reqp Pointer to allocated SIP request object
+ * @param sip SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met Null-terminated SIP Method string
+ * @param dlg SIP Dialog state
+ * @param cseq CSeq number
+ * @param auth SIP authentication state
+ * @param sendh Send handler
+ * @param resph Response handler
+ * @param arg Handler argument
+ * @param fmt Formatted SIP headers and body
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, struct sip_dialog *dlg, uint32_t cseq,
+ struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+ void *arg, const char *fmt, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!sip || !met || !dlg || !fmt)
+ return EINVAL;
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+
+ if (auth)
+ err |= sip_auth_encode(mb, auth, met, sip_dialog_uri(dlg));
+
+ err |= sip_dialog_encode(mb, dlg, cseq, met);
+
+ if (sip->software)
+ err |= mbuf_printf(mb, "User-Agent: %s\r\n", sip->software);
+
+ if (err)
+ goto out;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sip_request(reqp, sip, stateful, met, -1, sip_dialog_uri(dlg),
+ -1, sip_dialog_route(dlg), mb, sip_dialog_hash(dlg),
+ sendh, resph, arg);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Cancel a pending SIP Request
+ *
+ * @param req SIP Request
+ */
+void sip_request_cancel(struct sip_request *req)
+{
+ if (!req || req->canceled)
+ return;
+
+ req->canceled = true;
+
+ if (!req->provrecv)
+ return;
+
+ (void)sip_ctrans_cancel(req->ct);
+}
+
+
+void sip_request_close(struct sip *sip)
+{
+ if (!sip)
+ return;
+
+ list_apply(&sip->reql, true, close_handler, NULL);
+}
+
+
+/**
+ * Check if a SIP request loops
+ *
+ * @param ls Loop state
+ * @param scode Status code from SIP response
+ *
+ * @return True if loops, otherwise false
+ */
+bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode)
+{
+ bool loop = false;
+
+ if (!ls)
+ return false;
+
+ if (scode < 200) {
+ return false;
+ }
+ else if (scode < 300) {
+ ls->failc = 0;
+ }
+ else if (scode < 400) {
+ loop = (++ls->failc >= 16);
+ }
+ else {
+ switch (scode) {
+
+ default:
+ if (ls->last_scode == scode)
+ loop = true;
+ /*@fallthrough@*/
+ case 401:
+ case 407:
+ case 491:
+ if (++ls->failc >= 16)
+ loop = true;
+ break;
+ }
+ }
+
+ ls->last_scode = scode;
+
+ return loop;
+}
+
+
+/**
+ * Reset the loop state
+ *
+ * @param ls Loop state
+ */
+void sip_loopstate_reset(struct sip_loopstate *ls)
+{
+ if (!ls)
+ return;
+
+ ls->last_scode = 0;
+ ls->failc = 0;
+}
diff --git a/src/sip/sip.c b/src/sip/sip.c
new file mode 100644
index 0000000..651628c
--- /dev/null
+++ b/src/sip/sip.c
@@ -0,0 +1,238 @@
+/**
+ * @file sip.c SIP 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_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static void destructor(void *arg)
+{
+ struct sip *sip = arg;
+
+ if (sip->closing) {
+ sip->closing = false;
+ mem_ref(sip);
+ if (sip->exith)
+ sip->exith(sip->arg);
+ return;
+ }
+
+ sip_request_close(sip);
+ sip_request_close(sip);
+
+ hash_flush(sip->ht_ctrans);
+ mem_deref(sip->ht_ctrans);
+
+ hash_flush(sip->ht_strans);
+ hash_clear(sip->ht_strans_mrg);
+ mem_deref(sip->ht_strans);
+ mem_deref(sip->ht_strans_mrg);
+
+ hash_flush(sip->ht_conn);
+ mem_deref(sip->ht_conn);
+
+ hash_flush(sip->ht_udpconn);
+ mem_deref(sip->ht_udpconn);
+
+ list_flush(&sip->transpl);
+ list_flush(&sip->lsnrl);
+
+ mem_deref(sip->software);
+ mem_deref(sip->dnsc);
+ mem_deref(sip->stun);
+}
+
+
+static void lsnr_destructor(void *arg)
+{
+ struct sip_lsnr *lsnr = arg;
+
+ if (lsnr->lsnrp)
+ *lsnr->lsnrp = NULL;
+
+ list_unlink(&lsnr->le);
+}
+
+
+/**
+ * Allocate a SIP stack instance
+ *
+ * @param sipp Pointer to allocated SIP stack
+ * @param dnsc DNS Client (optional)
+ * @param ctsz Size of client transactions hashtable (power of 2)
+ * @param stsz Size of server transactions hashtable (power of 2)
+ * @param tcsz Size of SIP transport hashtable (power of 2)
+ * @param software Software identifier
+ * @param exith SIP-stack exit handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_alloc(struct sip **sipp, struct dnsc *dnsc, uint32_t ctsz,
+ uint32_t stsz, uint32_t tcsz, const char *software,
+ sip_exit_h *exith, void *arg)
+{
+ struct sip *sip;
+ int err;
+
+ if (!sipp)
+ return EINVAL;
+
+ sip = mem_zalloc(sizeof(*sip), destructor);
+ if (!sip)
+ return ENOMEM;
+
+ err = sip_transp_init(sip, tcsz);
+ if (err)
+ goto out;
+
+ err = sip_ctrans_init(sip, ctsz);
+ if (err)
+ goto out;
+
+ err = sip_strans_init(sip, stsz);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sip->ht_udpconn, tcsz);
+ if (err)
+ goto out;
+
+ err = stun_alloc(&sip->stun, NULL, NULL, NULL);
+ if (err)
+ goto out;
+
+ if (software) {
+ err = str_dup(&sip->software, software);
+ if (err)
+ goto out;
+ }
+
+ sip->dnsc = mem_ref(dnsc);
+ sip->exith = exith;
+ sip->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sip);
+ else
+ *sipp = sip;
+
+ return err;
+}
+
+
+/**
+ * Close the SIP stack instance
+ *
+ * @param sip SIP stack instance
+ * @param force Don't wait for transactions to complete
+ */
+void sip_close(struct sip *sip, bool force)
+{
+ if (!sip)
+ return;
+
+ if (force) {
+ sip_request_close(sip);
+ sip_request_close(sip);
+ }
+ else if (!sip->closing) {
+ sip->closing = true;
+ mem_deref(sip);
+ }
+}
+
+
+/**
+ * Send a SIP message
+ *
+ * @param sip SIP stack instance
+ * @param sock Optional socket to send from
+ * @param tp SIP transport
+ * @param dst Destination network address
+ * @param mb Buffer containing SIP message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_send(struct sip *sip, void *sock, enum sip_transp tp,
+ const struct sa *dst, struct mbuf *mb)
+{
+ return sip_transp_send(NULL, sip, sock, tp, dst, mb, NULL, NULL);
+}
+
+
+/**
+ * Listen for incoming SIP Requests and SIP Responses
+ *
+ * @param lsnrp Pointer to allocated listener
+ * @param sip SIP stack instance
+ * @param req True for Request, false for Response
+ * @param msgh SIP message handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_listen(struct sip_lsnr **lsnrp, struct sip *sip, bool req,
+ sip_msg_h *msgh, void *arg)
+{
+ struct sip_lsnr *lsnr;
+
+ if (!sip || !msgh)
+ return EINVAL;
+
+ lsnr = mem_zalloc(sizeof(*lsnr), lsnr_destructor);
+ if (!lsnr)
+ return ENOMEM;
+
+ list_append(&sip->lsnrl, &lsnr->le, lsnr);
+
+ lsnr->msgh = msgh;
+ lsnr->arg = arg;
+ lsnr->req = req;
+
+ if (lsnrp) {
+ lsnr->lsnrp = lsnrp;
+ *lsnrp = lsnr;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Print debug information about the SIP stack
+ *
+ * @param pf Print function for debug output
+ * @param sip SIP stack instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ if (!sip)
+ return 0;
+
+ err = sip_transp_debug(pf, sip);
+ err |= sip_ctrans_debug(pf, sip);
+ err |= sip_strans_debug(pf, sip);
+
+ return err;
+}
diff --git a/src/sip/sip.h b/src/sip/sip.h
new file mode 100644
index 0000000..912c69b
--- /dev/null
+++ b/src/sip/sip.h
@@ -0,0 +1,101 @@
+/**
+ * @file sip.h SIP Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct sip {
+ struct list transpl;
+ struct list lsnrl;
+ struct list reql;
+ struct hash *ht_ctrans;
+ struct hash *ht_strans;
+ struct hash *ht_strans_mrg;
+ struct hash *ht_conn;
+ struct hash *ht_udpconn;
+ struct dnsc *dnsc;
+ struct stun *stun;
+ char *software;
+ sip_exit_h *exith;
+ void *arg;
+ bool closing;
+};
+
+
+struct sip_lsnr {
+ struct le le;
+ struct sip_lsnr **lsnrp;
+ sip_msg_h *msgh;
+ void *arg;
+ bool req;
+};
+
+
+struct sip_keepalive {
+ struct le le;
+ struct sip_keepalive **kap;
+ sip_keepalive_h *kah;
+ void *arg;
+};
+
+
+/* request */
+void sip_request_close(struct sip *sip);
+
+
+/* ctrans */
+struct sip_ctrans;
+
+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);
+int sip_ctrans_cancel(struct sip_ctrans *ct);
+int sip_ctrans_init(struct sip *sip, uint32_t sz);
+int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* strans */
+int sip_strans_init(struct sip *sip, uint32_t sz);
+int sip_strans_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* transp */
+struct sip_connqent;
+
+typedef void(sip_transp_h)(int err, void *arg);
+
+int sip_transp_init(struct sip *sip, uint32_t sz);
+int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock,
+ enum sip_transp tp, const struct sa *dst, struct mbuf *mb,
+ sip_transp_h *transph, void *arg);
+bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af);
+const char *sip_transp_srvid(enum sip_transp tp);
+bool sip_transp_reliable(enum sip_transp tp);
+int sip_transp_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* auth */
+int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met,
+ const char *uri);
+
+
+/* dialog */
+int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq,
+ const char *met);
+const char *sip_dialog_uri(const struct sip_dialog *dlg);
+const struct uri *sip_dialog_route(const struct sip_dialog *dlg);
+uint32_t sip_dialog_hash(const struct sip_dialog *dlg);
+
+
+/* keepalive */
+struct sip_conn;
+
+void sip_keepalive_signal(struct list *kal, int err);
+uint64_t sip_keepalive_wait(uint32_t interval);
+int sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn,
+ uint32_t interval);
+int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
+ struct udp_sock *us, const struct sa *paddr,
+ uint32_t interval);
diff --git a/src/sip/strans.c b/src/sip/strans.c
new file mode 100644
index 0000000..13a875c
--- /dev/null
+++ b/src/sip/strans.c
@@ -0,0 +1,455 @@
+/**
+ * @file strans.c SIP Server Transaction
+ *
+ * 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_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum state {
+ TRYING,
+ PROCEEDING,
+ ACCEPTED,
+ COMPLETED,
+ CONFIRMED,
+};
+
+
+struct sip_strans {
+ struct le he;
+ struct le he_mrg;
+ struct tmr tmr;
+ struct tmr tmrg;
+ struct sa dst;
+ struct sip *sip;
+ struct sip_msg *msg;
+ struct mbuf *mb;
+ sip_cancel_h *cancelh;
+ void *arg;
+ enum state state;
+ uint32_t txc;
+ bool invite;
+};
+
+
+static void destructor(void *arg)
+{
+ struct sip_strans *st = arg;
+
+ hash_unlink(&st->he);
+ hash_unlink(&st->he_mrg);
+ tmr_cancel(&st->tmr);
+ tmr_cancel(&st->tmrg);
+ mem_deref(st->msg);
+ mem_deref(st->mb);
+}
+
+
+static bool strans_cmp(const struct sip_msg *msg1, const struct sip_msg *msg2)
+{
+ if (pl_cmp(&msg1->via.branch, &msg2->via.branch))
+ return false;
+
+ if (pl_cmp(&msg1->via.sentby, &msg2->via.sentby))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!strans_cmp(st->msg, msg))
+ return false;
+
+ if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_ack_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!strans_cmp(st->msg, msg))
+ return false;
+
+ if (pl_strcmp(&st->msg->cseq.met, "INVITE"))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_cancel_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!strans_cmp(st->msg, msg))
+ return false;
+
+ if (!pl_strcmp(&st->msg->cseq.met, "CANCEL"))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_merge_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met))
+ return false;
+
+ if (st->msg->cseq.num != msg->cseq.num)
+ return false;
+
+ if (pl_cmp(&st->msg->callid, &msg->callid))
+ return false;
+
+ if (pl_cmp(&st->msg->from.tag, &msg->from.tag))
+ return false;
+
+ if (pl_cmp(&st->msg->ruri, &msg->ruri))
+ return false;
+
+ return true;
+}
+
+
+static void dummy_handler(void *arg)
+{
+ (void)arg;
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sip_strans *st = arg;
+
+ mem_deref(st);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+ struct sip_strans *st = arg;
+
+ (void)sip_send(st->sip, st->msg->sock, st->msg->tp, &st->dst,
+ st->mb);
+
+ st->txc++;
+ tmr_start(&st->tmrg, MIN(SIP_T1<<st->txc, SIP_T2), retransmit_handler,
+ st);
+}
+
+
+static bool ack_handler(struct sip *sip, const struct sip_msg *msg)
+{
+ struct sip_strans *st;
+
+ st = list_ledata(hash_lookup(sip->ht_strans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_ack_handler, (void *)msg));
+ if (!st)
+ return false;
+
+ switch (st->state) {
+
+ case ACCEPTED:
+ /* make sure ACKs for 2xx are passed to TU */
+ return false;
+
+ case COMPLETED:
+ if (sip_transp_reliable(st->msg->tp)) {
+ mem_deref(st);
+ break;
+ }
+
+ tmr_start(&st->tmr, SIP_T4, tmr_handler, st);
+ tmr_cancel(&st->tmrg);
+ st->state = CONFIRMED;
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static bool cancel_handler(struct sip *sip, const struct sip_msg *msg)
+{
+ struct sip_strans *st;
+
+ st = list_ledata(hash_lookup(sip->ht_strans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_cancel_handler, (void *)msg));
+ if (!st)
+ return false;
+
+ ((struct sip_msg *)msg)->tag = st->msg->tag;
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ switch (st->state) {
+
+ case TRYING:
+ case PROCEEDING:
+ st->cancelh(st->arg);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sip_strans *st;
+ struct sip *sip = arg;
+
+ if (!pl_strcmp(&msg->met, "ACK"))
+ return ack_handler(sip, msg);
+
+ st = list_ledata(hash_lookup(sip->ht_strans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_handler, (void *)msg));
+ if (st) {
+ switch (st->state) {
+
+ case PROCEEDING:
+ case COMPLETED:
+ (void)sip_send(st->sip, st->msg->sock, st->msg->tp,
+ &st->dst, st->mb);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+ }
+ else if (!pl_isset(&msg->to.tag)) {
+
+ st = list_ledata(hash_lookup(sip->ht_strans_mrg,
+ hash_joaat_pl(&msg->callid),
+ cmp_merge_handler, (void *)msg));
+ if (st) {
+ (void)sip_reply(sip, msg, 482, "Loop Detected");
+ return true;
+ }
+ }
+
+ if (!pl_strcmp(&msg->met, "CANCEL"))
+ return cancel_handler(sip, msg);
+
+ return false;
+}
+
+
+/**
+ * Allocate a SIP Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param cancelh Cancel handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_strans_alloc(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, sip_cancel_h *cancelh,
+ void *arg)
+{
+ struct sip_strans *st;
+
+ if (!stp || !sip || !msg)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ hash_append(sip->ht_strans, hash_joaat_pl(&msg->via.branch),
+ &st->he, st);
+
+ hash_append(sip->ht_strans_mrg, hash_joaat_pl(&msg->callid),
+ &st->he_mrg, st);
+
+ st->invite = !pl_strcmp(&msg->met, "INVITE");
+ st->msg = mem_ref((void *)msg);
+ st->state = TRYING;
+ st->cancelh = cancelh ? cancelh : dummy_handler;
+ st->arg = arg;
+ st->sip = sip;
+
+ *stp = st;
+
+ return 0;
+}
+
+
+/**
+ * Reply using a SIP Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param dst Destination network address
+ * @param scode Response status code
+ * @param mb Buffer containing SIP response
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_strans_reply(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, const struct sa *dst,
+ uint16_t scode, struct mbuf *mb)
+{
+ struct sip_strans *st = NULL;
+ int err;
+
+ if (!sip || !mb || !dst || (scode < 200 && !stp))
+ return EINVAL;
+
+ if (stp)
+ st = *stp;
+
+ if (!st) {
+ err = sip_strans_alloc(&st, sip, msg, NULL, NULL);
+ if (err)
+ return err;
+ }
+
+ mem_deref(st->mb);
+ st->mb = mem_ref(mb);
+ st->dst = *dst;
+
+ err = sip_send(sip, st->msg->sock, st->msg->tp, dst, mb);
+
+ if (stp)
+ *stp = (err || scode >= 200) ? NULL : st;
+
+ if (err) {
+ mem_deref(st);
+ return err;
+ }
+
+ if (st->invite) {
+ if (scode < 200) {
+ st->state = PROCEEDING;
+ }
+ else if (scode < 300) {
+ tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st);
+ st->state = ACCEPTED;
+ }
+ else {
+ tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st);
+ st->state = COMPLETED;
+
+ if (!sip_transp_reliable(st->msg->tp))
+ tmr_start(&st->tmrg, SIP_T1,
+ retransmit_handler, st);
+ }
+ }
+ else {
+ if (scode < 200) {
+ st->state = PROCEEDING;
+ }
+ else {
+ if (!sip_transp_reliable(st->msg->tp)) {
+ tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler,
+ st);
+ st->state = COMPLETED;
+ }
+ else {
+ mem_deref(st);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+int sip_strans_init(struct sip *sip, uint32_t sz)
+{
+ int err;
+
+ err = sip_listen(NULL, sip, true, request_handler, sip);
+ if (err)
+ return err;
+
+ err = hash_alloc(&sip->ht_strans_mrg, sz);
+ if (err)
+ return err;
+
+ return hash_alloc(&sip->ht_strans, sz);
+}
+
+
+static const char *statename(enum state state)
+{
+ switch (state) {
+
+ case TRYING: return "TRYING";
+ case PROCEEDING: return "PROCEEDING";
+ case ACCEPTED: return "ACCEPTED";
+ case COMPLETED: return "COMPLETED";
+ case CONFIRMED: return "CONFIRMED";
+ default: return "???";
+ }
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ struct re_printf *pf = arg;
+
+ (void)re_hprintf(pf, " %-10r %-10s %2llus (%r)\n",
+ &st->msg->met,
+ statename(st->state),
+ tmr_get_expire(&st->tmr)/1000,
+ &st->msg->via.branch);
+
+ return false;
+}
+
+
+int sip_strans_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ err = re_hprintf(pf, "server transactions:\n");
+ hash_apply(sip->ht_strans, debug_handler, pf);
+
+ return err;
+}
diff --git a/src/sip/transp.c b/src/sip/transp.c
new file mode 100644
index 0000000..aed33bb
--- /dev/null
+++ b/src/sip/transp.c
@@ -0,0 +1,985 @@
+/**
+ * @file sip/transp.c SIP Transport
+ *
+ * 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_stun.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ TCP_ACCEPT_TIMEOUT = 32,
+ TCP_IDLE_TIMEOUT = 900,
+ TCP_KEEPALIVE_TIMEOUT = 10,
+ TCP_KEEPALIVE_INTVAL = 120,
+ TCP_BUFSIZE_MAX = 65536,
+};
+
+
+struct sip_transport {
+ struct le le;
+ struct sa laddr;
+ struct sip *sip;
+ struct tls *tls;
+ void *sock;
+ enum sip_transp tp;
+};
+
+
+struct sip_conn {
+ struct le he;
+ struct list ql;
+ struct list kal;
+ struct tmr tmr;
+ struct tmr tmr_ka;
+ struct sa laddr;
+ struct sa paddr;
+ struct tls_conn *sc;
+ struct tcp_conn *tc;
+ struct mbuf *mb;
+ struct sip *sip;
+ uint32_t ka_interval;
+ bool established;
+};
+
+
+struct sip_connqent {
+ struct le le;
+ struct mbuf *mb;
+ struct sip_connqent **qentp;
+ sip_transp_h *transph;
+ void *arg;
+};
+
+
+static uint8_t crlfcrlf[4] = {0x0d, 0x0a, 0x0d, 0x0a};
+
+
+static void internal_transport_handler(int err, void *arg)
+{
+ (void)err;
+ (void)arg;
+}
+
+
+static void transp_destructor(void *arg)
+{
+ struct sip_transport *transp = arg;
+
+ if (transp->tp == SIP_TRANSP_UDP)
+ udp_handler_set(transp->sock, NULL, NULL);
+
+ list_unlink(&transp->le);
+ mem_deref(transp->sock);
+ mem_deref(transp->tls);
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct sip_conn *conn = arg;
+
+ tmr_cancel(&conn->tmr_ka);
+ tmr_cancel(&conn->tmr);
+ list_flush(&conn->kal);
+ list_flush(&conn->ql);
+ hash_unlink(&conn->he);
+ mem_deref(conn->sc);
+ mem_deref(conn->tc);
+ mem_deref(conn->mb);
+}
+
+
+static void qent_destructor(void *arg)
+{
+ struct sip_connqent *qent = arg;
+
+ if (qent->qentp)
+ *qent->qentp = NULL;
+
+ list_unlink(&qent->le);
+ mem_deref(qent->mb);
+}
+
+
+static const struct sip_transport *transp_find(struct sip *sip,
+ enum sip_transp tp,
+ int af, const struct sa *dst)
+{
+ struct le *le;
+ (void)dst;
+
+ for (le = sip->transpl.head; le; le = le->next) {
+
+ const struct sip_transport *transp = le->data;
+
+ if (transp->tp != tp)
+ continue;
+
+ if (af != AF_UNSPEC && sa_af(&transp->laddr) != af)
+ continue;
+
+ return transp;
+ }
+
+ return NULL;
+}
+
+
+static struct sip_conn *conn_find(struct sip *sip, const struct sa *paddr,
+ bool secure)
+{
+ struct le *le;
+
+ le = list_head(hash_list(sip->ht_conn, sa_hash(paddr, SA_ALL)));
+
+ for (; le; le = le->next) {
+
+ struct sip_conn *conn = le->data;
+
+ if (!secure != (conn->sc == NULL))
+ continue;
+
+ if (!sa_cmp(&conn->paddr, paddr, SA_ALL))
+ continue;
+
+ return conn;
+ }
+
+ return NULL;
+}
+
+
+static void conn_close(struct sip_conn *conn, int err)
+{
+ struct le *le;
+
+ conn->sc = mem_deref(conn->sc);
+ conn->tc = mem_deref(conn->tc);
+ tmr_cancel(&conn->tmr_ka);
+ tmr_cancel(&conn->tmr);
+ hash_unlink(&conn->he);
+
+ le = list_head(&conn->ql);
+
+ while (le) {
+
+ struct sip_connqent *qent = le->data;
+ le = le->next;
+
+ if (qent->qentp) {
+ *qent->qentp = NULL;
+ qent->qentp = NULL;
+ }
+
+ qent->transph(err, qent->arg);
+ list_unlink(&qent->le);
+ mem_deref(qent);
+ }
+
+ sip_keepalive_signal(&conn->kal, err);
+}
+
+
+static void conn_tmr_handler(void *arg)
+{
+ struct sip_conn *conn = arg;
+
+ conn_close(conn, ETIMEDOUT);
+ mem_deref(conn);
+}
+
+
+static void conn_keepalive_handler(void *arg)
+{
+ struct sip_conn *conn = arg;
+ struct mbuf mb;
+ int err;
+
+ mb.buf = crlfcrlf;
+ mb.size = sizeof(crlfcrlf);
+ mb.pos = 0;
+ mb.end = 4;
+
+ err = tcp_send(conn->tc, &mb);
+ if (err) {
+ conn_close(conn, err);
+ mem_deref(conn);
+ return;
+ }
+
+ tmr_start(&conn->tmr, TCP_KEEPALIVE_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+ tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval),
+ conn_keepalive_handler, conn);
+}
+
+
+static void sip_recv(struct sip *sip, const struct sip_msg *msg)
+{
+ struct le *le = sip->lsnrl.head;
+
+ while (le) {
+ struct sip_lsnr *lsnr = le->data;
+
+ le = le->next;
+
+ if (msg->req != lsnr->req)
+ continue;
+
+ if (lsnr->msgh(msg, lsnr->arg))
+ return;
+ }
+
+ if (msg->req) {
+ (void)re_fprintf(stderr, "unhandeled request from %J: %r %r\n",
+ &msg->src, &msg->met, &msg->ruri);
+
+ if (!pl_strcmp(&msg->met, "CANCEL"))
+ (void)sip_reply(sip, msg,
+ 481, "Transaction Does Not Exist");
+ else
+ (void)sip_reply(sip, msg,
+ 501, "Not Implemented");
+ }
+ else {
+ (void)re_fprintf(stderr, "unhandeled response from %J:"
+ " %u %r (%r)\n", &msg->src,
+ msg->scode, &msg->reason, &msg->cseq.met);
+ }
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct sip_transport *transp = arg;
+ struct stun_unknown_attr ua;
+ struct stun_msg *stun_msg;
+ struct sip_msg *msg;
+ int err;
+
+ if (mb->end <= 4)
+ return;
+
+ if (!stun_msg_decode(&stun_msg, mb, &ua)) {
+
+ if (stun_msg_method(stun_msg) == STUN_METHOD_BINDING) {
+
+ switch (stun_msg_class(stun_msg)) {
+
+ case STUN_CLASS_REQUEST:
+ (void)stun_reply(IPPROTO_UDP, transp->sock,
+ src, 0, stun_msg,
+ NULL, 0, false, 2,
+ STUN_ATTR_XOR_MAPPED_ADDR,
+ src,
+ STUN_ATTR_SOFTWARE,
+ transp->sip->software);
+ break;
+
+ default:
+ (void)stun_ctrans_recv(transp->sip->stun,
+ stun_msg, &ua);
+ break;
+ }
+ }
+
+ mem_deref(stun_msg);
+
+ return;
+ }
+
+ err = sip_msg_decode(&msg, mb);
+ if (err) {
+ (void)re_fprintf(stderr, "sip: msg decode err: %m\n", err);
+ return;
+ }
+
+ msg->sock = mem_ref(transp->sock);
+ msg->src = *src;
+ msg->dst = transp->laddr;
+ msg->tp = SIP_TRANSP_UDP;
+
+ sip_recv(transp->sip, msg);
+
+ mem_deref(msg);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct sip_conn *conn = arg;
+ size_t pos;
+ int err = 0;
+
+ if (conn->mb) {
+ pos = conn->mb->pos;
+
+ conn->mb->pos = conn->mb->end;
+
+ err = mbuf_write_mem(conn->mb, mbuf_buf(mb),mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ conn->mb->pos = pos;
+
+ if (mbuf_get_left(conn->mb) > TCP_BUFSIZE_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+ }
+ else {
+ conn->mb = mem_ref(mb);
+ }
+
+ for (;;) {
+ struct sip_msg *msg;
+ uint32_t clen;
+ size_t end;
+
+ if (mbuf_get_left(conn->mb) < 2)
+ break;
+
+ if (!memcmp(mbuf_buf(conn->mb), "\r\n", 2)) {
+
+ tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+
+ conn->mb->pos += 2;
+
+ if (mbuf_get_left(conn->mb) >= 2 &&
+ !memcmp(mbuf_buf(conn->mb), "\r\n", 2)) {
+
+ struct mbuf mbr;
+
+ conn->mb->pos += 2;
+
+ mbr.buf = crlfcrlf;
+ mbr.size = sizeof(crlfcrlf);
+ mbr.pos = 0;
+ mbr.end = 2;
+
+ err = tcp_send(conn->tc, &mbr);
+ if (err)
+ break;
+ }
+
+ if (mbuf_get_left(conn->mb))
+ continue;
+
+ conn->mb = mem_deref(conn->mb);
+ break;
+ }
+
+ pos = conn->mb->pos;
+
+ err = sip_msg_decode(&msg, conn->mb);
+ if (err) {
+ if (err == ENODATA)
+ err = 0;
+ break;
+ }
+
+ if (!msg->clen.p) {
+ mem_deref(msg);
+ err = EBADMSG;
+ break;
+ }
+
+ clen = pl_u32(&msg->clen);
+
+ if (mbuf_get_left(conn->mb) < clen) {
+ conn->mb->pos = pos;
+ mem_deref(msg);
+ break;
+ }
+
+ tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+
+ end = conn->mb->end;
+
+ msg->mb->end = msg->mb->pos + clen;
+ msg->sock = mem_ref(conn);
+ msg->src = conn->paddr;
+ msg->dst = conn->laddr;
+ msg->tp = conn->sc ? SIP_TRANSP_TLS : SIP_TRANSP_TCP;
+
+ sip_recv(conn->sip, msg);
+ mem_deref(msg);
+
+ if (end <= conn->mb->end) {
+ conn->mb = mem_deref(conn->mb);
+ break;
+ }
+
+ mb = mbuf_alloc(end - conn->mb->end);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ (void)mbuf_write_mem(mb, &conn->mb->buf[conn->mb->end],
+ end - conn->mb->end);
+
+ mb->pos = 0;
+
+ mem_deref(conn->mb);
+ conn->mb = mb;
+ }
+
+ out:
+ if (err) {
+ conn_close(conn, err);
+ mem_deref(conn);
+ }
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct sip_conn *conn = arg;
+ struct le *le;
+ int err;
+
+#ifdef WIN32
+ tcp_conn_local_get(conn->tc, &conn->laddr);
+#endif
+
+ conn->established = true;
+
+ le = list_head(&conn->ql);
+
+ while (le) {
+
+ struct sip_connqent *qent = le->data;
+ le = le->next;
+
+ if (qent->qentp) {
+ *qent->qentp = NULL;
+ qent->qentp = NULL;
+ }
+
+ err = tcp_send(conn->tc, qent->mb);
+ if (err)
+ qent->transph(err, qent->arg);
+
+ list_unlink(&qent->le);
+ mem_deref(qent);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct sip_conn *conn = arg;
+
+ conn_close(conn, err ? err : ECONNRESET);
+ mem_deref(conn);
+}
+
+
+static void tcp_connect_handler(const struct sa *paddr, void *arg)
+{
+ struct sip_transport *transp = arg;
+ struct sip_conn *conn;
+ int err;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ hash_append(transp->sip->ht_conn, sa_hash(paddr, SA_ALL),
+ &conn->he, conn);
+
+ conn->paddr = *paddr;
+ conn->sip = transp->sip;
+
+ err = tcp_accept(&conn->tc, transp->sock, tcp_estab_handler,
+ tcp_recv_handler, tcp_close_handler, conn);
+ if (err)
+ goto out;
+
+ err = tcp_conn_local_get(conn->tc, &conn->laddr);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ if (transp->tls) {
+ err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0);
+ if (err)
+ goto out;
+ }
+#endif
+
+ tmr_start(&conn->tmr, TCP_ACCEPT_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+
+ out:
+ if (err) {
+ tcp_reject(transp->sock);
+ mem_deref(conn);
+ }
+}
+
+
+static int conn_send(struct sip_connqent **qentp, struct sip *sip, bool secure,
+ const struct sa *dst, struct mbuf *mb,
+ sip_transp_h *transph, void *arg)
+{
+ struct sip_conn *conn, *new_conn = NULL;
+ struct sip_connqent *qent;
+ int err = 0;
+
+ conn = conn_find(sip, dst, secure);
+ if (conn) {
+ if (!conn->established)
+ goto enqueue;
+
+ return tcp_send(conn->tc, mb);
+ }
+
+ new_conn = conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ hash_append(sip->ht_conn, sa_hash(dst, SA_ALL), &conn->he, conn);
+ conn->paddr = *dst;
+ conn->sip = sip;
+
+ err = tcp_connect(&conn->tc, dst, tcp_estab_handler, tcp_recv_handler,
+ tcp_close_handler, conn);
+ if (err)
+ goto out;
+
+ err = tcp_conn_local_get(conn->tc, &conn->laddr);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ if (secure) {
+ const struct sip_transport *transp;
+
+ transp = transp_find(sip, SIP_TRANSP_TLS, sa_af(dst), dst);
+ if (!transp || !transp->tls) {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0);
+ if (err)
+ goto out;
+ }
+#endif
+
+ tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn);
+
+ enqueue:
+ qent = mem_zalloc(sizeof(*qent), qent_destructor);
+ if (!qent) {
+ err = ENOMEM;
+ goto out;
+
+ }
+
+ list_append(&conn->ql, &qent->le, qent);
+ qent->mb = mem_ref(mb);
+ qent->transph = transph ? transph : internal_transport_handler;
+ qent->arg = arg;
+
+ if (qentp) {
+ qent->qentp = qentp;
+ *qentp = qent;
+ }
+
+ out:
+ if (err)
+ mem_deref(new_conn);
+
+ return err;
+}
+
+
+int sip_transp_init(struct sip *sip, uint32_t sz)
+{
+ return hash_alloc(&sip->ht_conn, sz);
+}
+
+
+/**
+ * Add a SIP transport
+ *
+ * @param sip SIP stack instance
+ * @param tp SIP Transport
+ * @param laddr Local network address
+ * @param ... Optional transport parameters such as TLS context
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_transp_add(struct sip *sip, enum sip_transp tp,
+ const struct sa *laddr, ...)
+{
+ struct sip_transport *transp;
+ struct tls *tls;
+ va_list ap;
+ int err;
+
+ if (!sip || !laddr || !sa_isset(laddr, SA_ADDR))
+ return EINVAL;
+
+ transp = mem_zalloc(sizeof(*transp), transp_destructor);
+ if (!transp)
+ return ENOMEM;
+
+ list_append(&sip->transpl, &transp->le, transp);
+ transp->sip = sip;
+ transp->tp = tp;
+
+ va_start(ap, laddr);
+
+ switch (tp) {
+
+ case SIP_TRANSP_UDP:
+ err = udp_listen((struct udp_sock **)&transp->sock, laddr,
+ udp_recv_handler, transp);
+ if (err)
+ break;
+
+ err = udp_local_get(transp->sock, &transp->laddr);
+ break;
+
+ case SIP_TRANSP_TLS:
+ tls = va_arg(ap, struct tls *);
+ if (!tls) {
+ err = EINVAL;
+ break;
+ }
+
+ transp->tls = mem_ref(tls);
+
+ /*@fallthrough@*/
+
+ case SIP_TRANSP_TCP:
+ err = tcp_listen((struct tcp_sock **)&transp->sock, laddr,
+ tcp_connect_handler, transp);
+ if (err)
+ break;
+
+ err = tcp_sock_local_get(transp->sock, &transp->laddr);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ va_end(ap);
+
+ if (err)
+ mem_deref(transp);
+
+ return err;
+}
+
+
+/**
+ * Flush all transports of a SIP stack instance
+ *
+ * @param sip SIP stack instance
+ */
+void sip_transp_flush(struct sip *sip)
+{
+ if (!sip)
+ return;
+
+ hash_flush(sip->ht_conn);
+ list_flush(&sip->transpl);
+}
+
+
+int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock,
+ enum sip_transp tp, const struct sa *dst, struct mbuf *mb,
+ sip_transp_h *transph, void *arg)
+{
+ const struct sip_transport *transp;
+ struct sip_conn *conn;
+ bool secure = false;
+ int err;
+
+ if (!sip || !dst || !mb)
+ return EINVAL;
+
+ switch (tp) {
+
+ case SIP_TRANSP_UDP:
+ if (!sock) {
+ transp = transp_find(sip, tp, sa_af(dst), dst);
+ if (!transp)
+ return EPROTONOSUPPORT;
+
+ sock = transp->sock;
+ }
+
+ err = udp_send(sock, dst, mb);
+ break;
+
+ case SIP_TRANSP_TLS:
+ secure = true;
+ /*@fallthrough@*/
+
+ case SIP_TRANSP_TCP:
+ conn = sock;
+
+ if (conn && conn->tc)
+ err = tcp_send(conn->tc, mb);
+ else
+ err = conn_send(qentp, sip, secure, dst, mb,
+ transph, arg);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ return err;
+}
+
+
+int sip_transp_laddr(struct sip *sip, struct sa *laddr, enum sip_transp tp,
+ const struct sa *dst)
+{
+ const struct sip_transport *transp;
+
+ if (!sip || !laddr)
+ return EINVAL;
+
+ transp = transp_find(sip, tp, sa_af(dst), dst);
+ if (!transp)
+ return EPROTONOSUPPORT;
+
+ *laddr = transp->laddr;
+
+ return 0;
+}
+
+
+bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af)
+{
+ if (!sip)
+ return false;
+
+ return transp_find(sip, tp, af, NULL) != NULL;
+}
+
+
+/**
+ * Check if network address is part of SIP transports
+ *
+ * @param sip SIP stack instance
+ * @param tp SIP transport
+ * @param laddr Local network address to check
+ *
+ * @return True if part of SIP transports, otherwise false
+ */
+bool sip_transp_isladdr(const struct sip *sip, enum sip_transp tp,
+ const struct sa *laddr)
+{
+ struct le *le;
+
+ if (!sip || !laddr)
+ return false;
+
+ for (le=sip->transpl.head; le; le=le->next) {
+
+ const struct sip_transport *transp = le->data;
+
+ if (tp != SIP_TRANSP_NONE && transp->tp != tp)
+ continue;
+
+ if (!sa_cmp(&transp->laddr, laddr, SA_ALL))
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the name of a given SIP Transport
+ *
+ * @param tp SIP Transport
+ *
+ * @return Name of the corresponding SIP Transport
+ */
+const char *sip_transp_name(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "UDP";
+ case SIP_TRANSP_TCP: return "TCP";
+ case SIP_TRANSP_TLS: return "TLS";
+ default: return "???";
+ }
+}
+
+
+const char *sip_transp_srvid(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "_sip._udp";
+ case SIP_TRANSP_TCP: return "_sip._tcp";
+ case SIP_TRANSP_TLS: return "_sips._tcp";
+ default: return "???";
+ }
+}
+
+
+/**
+ * Get the transport parameters for a given SIP Transport
+ *
+ * @param tp SIP Transport
+ *
+ * @return Transport parameters of the corresponding SIP Transport
+ */
+const char *sip_transp_param(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "";
+ case SIP_TRANSP_TCP: return ";transport=tcp";
+ case SIP_TRANSP_TLS: return ";transport=tls";
+ default: return "";
+ }
+}
+
+
+bool sip_transp_reliable(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return false;
+ case SIP_TRANSP_TCP: return true;
+ case SIP_TRANSP_TLS: return true;
+ default: return false;
+ }
+}
+
+
+/**
+ * Get the default port number for a given SIP Transport
+ *
+ * @param tp SIP Transport
+ * @param port Port number
+ *
+ * @return Corresponding port number
+ */
+uint16_t sip_transp_port(enum sip_transp tp, uint16_t port)
+{
+ if (port)
+ return port;
+
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return SIP_PORT;
+ case SIP_TRANSP_TCP: return SIP_PORT;
+ case SIP_TRANSP_TLS: return SIP_PORT_TLS;
+ default: return 0;
+ }
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ const struct sip_transport *transp = le->data;
+ struct re_printf *pf = arg;
+
+ (void)re_hprintf(pf, " %J (%s)\n",
+ &transp->laddr,
+ sip_transp_name(transp->tp));
+
+ return false;
+}
+
+
+int sip_transp_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ err = re_hprintf(pf, "transports:\n");
+ list_apply(&sip->transpl, true, debug_handler, pf);
+
+ return err;
+}
+
+
+/**
+ * Get the TCP Connection from a SIP Message
+ *
+ * @param msg SIP Message
+ *
+ * @return TCP Connection if reliable transport, otherwise NULL
+ */
+struct tcp_conn *sip_msg_tcpconn(const struct sip_msg *msg)
+{
+ if (!msg || !msg->sock)
+ return NULL;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ return ((struct sip_conn *)msg->sock)->tc;
+
+ default:
+ return NULL;
+ }
+}
+
+
+int sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn,
+ uint32_t interval)
+{
+ if (!ka || !conn)
+ return EINVAL;
+
+ if (!conn->tc || !conn->established)
+ return ENOTCONN;
+
+ list_append(&conn->kal, &ka->le, ka);
+
+ if (!tmr_isrunning(&conn->tmr_ka)) {
+
+ interval = MAX(interval ? interval : TCP_KEEPALIVE_INTVAL,
+ TCP_KEEPALIVE_TIMEOUT * 2);
+
+ conn->ka_interval = interval;
+
+ tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval),
+ conn_keepalive_handler, conn);
+ }
+
+ return 0;
+}
diff --git a/src/sip/via.c b/src/sip/via.c
new file mode 100644
index 0000000..76e518a
--- /dev/null
+++ b/src/sip/via.c
@@ -0,0 +1,77 @@
+/**
+ * @file via.c SIP Via decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+static int decode_hostport(const struct pl *hostport, struct pl *host,
+ struct pl *port)
+{
+ /* Try IPv6 first */
+ if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*",
+ host, NULL, port))
+ return 0;
+
+ /* Then non-IPv6 host */
+ return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*",
+ host, NULL, port);
+}
+
+
+/**
+ * Decode a pointer-length string into a SIP Via header
+ *
+ * @param via SIP Via header
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_via_decode(struct sip_via *via, const struct pl *pl)
+{
+ struct pl transp, host, port;
+ int err;
+
+ if (!via || !pl)
+ return EINVAL;
+
+ err = re_regex(pl->p, pl->l,
+ "SIP[ \t\r\n]*/[ \t\r\n]*2.0[ \t\r\n]*/[ \t\r\n]*"
+ "[A-Z]+[ \t\r\n]*[^; \t\r\n]+[ \t\r\n]*[^]*",
+ NULL, NULL, NULL, NULL, &transp,
+ NULL, &via->sentby, NULL, &via->params);
+ if (err)
+ return err;
+
+ if (!pl_strcmp(&transp, "TCP"))
+ via->tp = SIP_TRANSP_TCP;
+ else if (!pl_strcmp(&transp, "TLS"))
+ via->tp = SIP_TRANSP_TLS;
+ else if (!pl_strcmp(&transp, "UDP"))
+ via->tp = SIP_TRANSP_UDP;
+ else
+ via->tp = SIP_TRANSP_NONE;
+
+ err = decode_hostport(&via->sentby, &host, &port);
+ if (err)
+ return err;
+
+ sa_init(&via->addr, AF_INET);
+
+ (void)sa_set(&via->addr, &host, 0);
+
+ if (pl_isset(&port))
+ sa_set_port(&via->addr, pl_u32(&port));
+
+ via->val = *pl;
+
+ return msg_param_decode(&via->params, "branch", &via->branch);
+}