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/sipevent/notify.c b/src/sipevent/notify.c
new file mode 100644
index 0000000..7a7a87d
--- /dev/null
+++ b/src/sipevent/notify.c
@@ -0,0 +1,488 @@
+/**
+ * @file notify.c SIP Event Notify
+ *
+ * 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_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+static int notify_request(struct sipnot *not, bool reset_ls);
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+static bool terminate(struct sipnot *not, enum sipevent_reason reason)
+{
+ not->terminated = true;
+ not->reason = reason;
+ not->closeh = internal_close_handler;
+
+ if (not->req) {
+ mem_ref(not);
+ return true;
+ }
+
+ if (not->subscribed && !notify_request(not, true)) {
+ mem_ref(not);
+ return true;
+ }
+
+ return false;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sipnot *not = arg;
+
+ tmr_cancel(¬->tmr);
+
+ if (!not->terminated) {
+
+ if (terminate(not, SIPEVENT_DEACTIVATED))
+ return;
+ }
+
+ hash_unlink(¬->he);
+ mem_deref(not->req);
+ mem_deref(not->dlg);
+ mem_deref(not->auth);
+ mem_deref(not->mb);
+ mem_deref(not->event);
+ mem_deref(not->id);
+ mem_deref(not->cuser);
+ mem_deref(not->hdrs);
+ mem_deref(not->ctype);
+ mem_deref(not->sock);
+ mem_deref(not->sip);
+}
+
+
+static void sipnot_terminate(struct sipnot *not, int err,
+ const struct sip_msg *msg,
+ enum sipevent_reason reason)
+{
+ sipnot_close_h *closeh;
+ void *arg;
+
+ closeh = not->closeh;
+ arg = not->arg;
+
+ tmr_cancel(¬->tmr);
+ (void)terminate(not, reason);
+
+ closeh(err, msg, arg);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipnot *not = arg;
+
+ if (not->terminated)
+ return;
+
+ sipnot_terminate(not, ETIMEDOUT, NULL, SIPEVENT_TIMEOUT);
+}
+
+
+void sipnot_refresh(struct sipnot *not, uint32_t expires)
+{
+ not->expires = min(expires, not->expires_max);
+
+ tmr_start(¬->tmr, not->expires * 1000, tmr_handler, not);
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sipnot *not = arg;
+
+ if (err) {
+ if (err == ETIMEDOUT)
+ not->subscribed = false;
+ goto out;
+ }
+
+ if (sip_request_loops(¬->ls, msg->scode)) {
+ not->subscribed = false;
+ goto out;
+ }
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+
+ (void)sip_dialog_update(not->dlg, msg);
+ }
+ else {
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(not->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = notify_request(not, false);
+ if (err)
+ break;
+
+ return;
+ }
+
+ not->subscribed = false;
+ }
+
+ out:
+ if (not->termsent) {
+ mem_deref(not);
+ }
+ else if (not->terminated) {
+ if (!not->subscribed || notify_request(not, true))
+ mem_deref(not);
+ }
+ else if (!not->subscribed) {
+ sipnot_terminate(not, err, msg, -1);
+ }
+ else if (not->notify_pending) {
+ (void)notify_request(not, true);
+ }
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sip_contact contact;
+ struct sipnot *not = arg;
+ (void)dst;
+
+ sip_contact_set(&contact, not->cuser, src, tp);
+
+ return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static int print_event(struct re_printf *pf, const struct sipnot *not)
+{
+ if (not->id)
+ return re_hprintf(pf, "%s;id=%s", not->event, not->id);
+ else
+ return re_hprintf(pf, "%s", not->event);
+}
+
+
+static int print_substate(struct re_printf *pf, const struct sipnot *not)
+{
+ int err;
+
+ if (not->terminated) {
+
+ err = re_hprintf(pf, "terminated;reason=%s",
+ sipevent_reason_name(not->reason));
+
+ if (not->retry_after)
+ err |= re_hprintf(pf, ";retry-after=%u",
+ not->retry_after);
+ }
+ else {
+ uint32_t expires;
+
+ expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000);
+
+ err = re_hprintf(pf, "%s;expires=%u",
+ sipevent_substate_name(not->substate),
+ expires);
+ }
+
+ return err;
+}
+
+
+static int print_content(struct re_printf *pf, const struct sipnot *not)
+{
+ if (!not->mb)
+ return re_hprintf(pf,
+ "Content-Length: 0\r\n"
+ "\r\n");
+ else
+ return re_hprintf(pf,
+ "Content-Type: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ not->ctype,
+ mbuf_get_left(not->mb),
+ mbuf_buf(not->mb),
+ mbuf_get_left(not->mb));
+}
+
+
+static int notify_request(struct sipnot *not, bool reset_ls)
+{
+ if (reset_ls)
+ sip_loopstate_reset(¬->ls);
+
+ if (not->terminated)
+ not->termsent = true;
+
+ not->notify_pending = false;
+
+ return sip_drequestf(¬->req, not->sip, true, "NOTIFY",
+ not->dlg, 0, not->auth,
+ send_handler, response_handler, not,
+ "Event: %H\r\n"
+ "Subscription-State: %H\r\n"
+ "%s"
+ "%H",
+ print_event, not,
+ print_substate, not,
+ not->hdrs,
+ print_content, not);
+}
+
+
+int sipnot_notify(struct sipnot *not)
+{
+ if (not->expires == 0) {
+ return 0;
+ }
+
+ if (not->req) {
+ not->notify_pending = true;
+ return 0;
+ }
+
+ return notify_request(not, true);
+}
+
+
+int sipnot_reply(struct sipnot *not, const struct sip_msg *msg,
+ uint16_t scode, const char *reason)
+{
+ struct sip_contact contact;
+ uint32_t expires;
+
+ expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000);
+
+ sip_contact_set(&contact, not->cuser, &msg->dst, msg->tp);
+
+ return sip_treplyf(NULL, NULL, not->sip, msg, true, scode, reason,
+ "%H"
+ "Expires: %u\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ sip_contact_print, &contact,
+ expires);
+}
+
+
+int sipevent_accept(struct sipnot **notp, struct sipevent_sock *sock,
+ const struct sip_msg *msg, struct sip_dialog *dlg,
+ const struct sipevent_event *event,
+ uint16_t scode, const char *reason, uint32_t expires_min,
+ uint32_t expires_dfl, uint32_t expires_max,
+ const char *cuser, const char *ctype,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipnot_close_h *closeh, void *arg, const char *fmt, ...)
+{
+ struct sipnot *not;
+ uint32_t expires;
+ int err;
+
+ if (!notp || !sock || !msg || !scode || !reason || !expires_dfl ||
+ !expires_max || !cuser || !ctype || expires_dfl < expires_min)
+ return EINVAL;
+
+ not = mem_zalloc(sizeof(*not), destructor);
+ if (!not)
+ return ENOMEM;
+
+ if (!pl_strcmp(&msg->met, "REFER")) {
+
+ err = str_dup(¬->event, "refer");
+ if (err)
+ goto out;
+
+ err = re_sdprintf(¬->id, "%u", msg->cseq.num);
+ if (err)
+ goto out;
+ }
+ else {
+ if (!event) {
+ err = EINVAL;
+ goto out;
+ }
+
+ err = pl_strdup(¬->event, &event->event);
+ if (err)
+ goto out;
+
+ if (pl_isset(&event->id)) {
+
+ err = pl_strdup(¬->id, &event->id);
+ if (err)
+ goto out;
+ }
+ }
+
+ if (dlg) {
+ not->dlg = mem_ref(dlg);
+ }
+ else {
+ err = sip_dialog_accept(¬->dlg, msg);
+ if (err)
+ goto out;
+ }
+
+ hash_append(sock->ht_not,
+ hash_joaat_str(sip_dialog_callid(not->dlg)),
+ ¬->he, not);
+
+ err = sip_auth_alloc(¬->auth, authh, aarg, aref);
+ if (err)
+ goto out;
+
+ err = str_dup(¬->cuser, cuser);
+ if (err)
+ goto out;
+
+ err = str_dup(¬->ctype, ctype);
+ if (err)
+ goto out;
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = re_vsdprintf(¬->hdrs, fmt, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+ }
+
+ not->expires_min = expires_min;
+ not->expires_dfl = expires_dfl;
+ not->expires_max = expires_max;
+ not->substate = SIPEVENT_PENDING;
+ not->sock = mem_ref(sock);
+ not->sip = mem_ref(sock->sip);
+ not->closeh = closeh ? closeh : internal_close_handler;
+ not->arg = arg;
+
+ if (pl_isset(&msg->expires))
+ expires = pl_u32(&msg->expires);
+ else
+ expires = not->expires_dfl;
+
+ sipnot_refresh(not, expires);
+
+ err = sipnot_reply(not, msg, scode, reason);
+ if (err)
+ goto out;
+
+ not->subscribed = true;
+
+ out:
+ if (err)
+ mem_deref(not);
+ else
+ *notp = not;
+
+ return err;
+}
+
+
+int sipevent_notify(struct sipnot *not, struct mbuf *mb,
+ enum sipevent_subst state, enum sipevent_reason reason,
+ uint32_t retry_after)
+{
+ if (!not || not->terminated)
+ return EINVAL;
+
+ if (mb || state != SIPEVENT_TERMINATED) {
+ mem_deref(not->mb);
+ not->mb = mem_ref(mb);
+ }
+
+ switch (state) {
+
+ case SIPEVENT_ACTIVE:
+ case SIPEVENT_PENDING:
+ not->substate = state;
+ return sipnot_notify(not);
+
+ case SIPEVENT_TERMINATED:
+ tmr_cancel(¬->tmr);
+ not->retry_after = retry_after;
+ (void)terminate(not, reason);
+ return 0;
+
+ default:
+ return EINVAL;
+ }
+}
+
+
+int sipevent_notifyf(struct sipnot *not, struct mbuf **mbp,
+ enum sipevent_subst state, enum sipevent_reason reason,
+ uint32_t retry_after, const char *fmt, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!not || not->terminated || !fmt)
+ return EINVAL;
+
+ if (mbp && *mbp)
+ return sipevent_notify(not, *mbp, state, reason, retry_after);
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sipevent_notify(not, mb, state, reason, retry_after);
+ if (err)
+ goto out;
+
+ out:
+ if (err || !mbp)
+ mem_deref(mb);
+ else
+ *mbp = mb;
+
+ return err;
+}