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/websock/mod.mk b/src/websock/mod.mk
new file mode 100644
index 0000000..d9667a7
--- /dev/null
+++ b/src/websock/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += websock/websock.c
diff --git a/src/websock/websock.c b/src/websock/websock.c
new file mode 100644
index 0000000..e78147757
--- /dev/null
+++ b/src/websock/websock.c
@@ -0,0 +1,729 @@
+/**
+ * @file websock.c Implementation of The WebSocket Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#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_tmr.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_msg.h>
+#include <re_http.h>
+#include <re_base64.h>
+#include <re_sha.h>
+#include <re_sys.h>
+#include <re_websock.h>
+
+
+enum {
+ TIMEOUT_CLOSE = 10000,
+ BUFSIZE_MAX = 131072,
+};
+
+enum websock_state {
+ ACCEPTING = 0,
+ CONNECTING,
+ OPEN,
+ CLOSING,
+ CLOSED,
+};
+
+struct websock {
+ websock_shutdown_h *shuth;
+ void *arg;
+ bool shutdown;
+};
+
+struct websock_conn {
+ struct tmr tmr;
+ struct sa peer;
+ char nonce[24];
+ struct websock *sock;
+ struct tcp_conn *tc;
+ struct tls_conn *sc;
+ struct mbuf *mb;
+ struct http_req *req;
+ websock_estab_h *estabh;
+ websock_recv_h *recvh;
+ websock_close_h *closeh;
+ void *arg;
+ enum websock_state state;
+ unsigned kaint;
+ bool active;
+};
+
+
+static const char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+
+static void timeout_handler(void *arg);
+
+
+static void dummy_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb,
+ void *arg)
+{
+ (void)hdr;
+ (void)mb;
+ (void)arg;
+}
+
+
+static void internal_close_handler(int err, void *arg)
+{
+ struct websock_conn *conn = arg;
+ (void)err;
+
+ mem_deref(conn);
+}
+
+
+static void sock_destructor(void *arg)
+{
+ struct websock *sock = arg;
+
+ if (sock->shutdown) {
+ sock->shutdown = false;
+ mem_ref(sock);
+ if (sock->shuth)
+ sock->shuth(sock->arg);
+ return;
+ }
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ if (conn->state == OPEN)
+ (void)websock_close(conn, WEBSOCK_GOING_AWAY, "Going Away");
+
+ if (conn->state == CLOSING) {
+
+ conn->recvh = dummy_recv_handler;
+ conn->closeh = internal_close_handler;
+ conn->arg = conn;
+
+ tmr_start(&conn->tmr, TIMEOUT_CLOSE, timeout_handler, conn);
+
+ /* important: the hack below depends on this */
+ mem_ref(conn);
+ return;
+ }
+
+ tmr_cancel(&conn->tmr);
+ mem_deref(conn->sc);
+ mem_deref(conn->tc);
+ mem_deref(conn->mb);
+ mem_deref(conn->req);
+ mem_deref(conn->sock);
+}
+
+
+static void conn_close(struct websock_conn *conn, int err)
+{
+ tmr_cancel(&conn->tmr);
+ conn->sc = mem_deref(conn->sc);
+ conn->tc = mem_deref(conn->tc);
+ conn->state = CLOSED;
+
+ conn->closeh(err, conn->arg);
+}
+
+
+static void timeout_handler(void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ conn_close(conn, ETIMEDOUT);
+}
+
+
+static void keepalive_handler(void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
+
+ (void)websock_send(conn, WEBSOCK_PING, NULL);
+}
+
+
+static enum websock_scode websock_err2scode(int err)
+{
+ switch (err) {
+
+ case EOVERFLOW: return WEBSOCK_MESSAGE_TOO_BIG;
+ case EPROTO: return WEBSOCK_PROTOCOL_ERROR;
+ case EBADMSG: return WEBSOCK_PROTOCOL_ERROR;
+ default: return WEBSOCK_INTERNAL_ERROR;
+ }
+}
+
+
+static int websock_decode(struct websock_hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v, *p;
+ size_t i;
+
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ v = mbuf_read_u8(mb);
+ hdr->fin = v>>7 & 0x1;
+ hdr->rsv1 = v>>6 & 0x1;
+ hdr->rsv2 = v>>5 & 0x1;
+ hdr->rsv3 = v>>4 & 0x1;
+ hdr->opcode = v & 0x0f;
+
+ v = mbuf_read_u8(mb);
+ hdr->mask = v>>7 & 0x1;
+ hdr->len = v & 0x7f;
+
+ if (hdr->len == 126) {
+
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ hdr->len = ntohs(mbuf_read_u16(mb));
+ }
+ else if (hdr->len == 127) {
+
+ if (mbuf_get_left(mb) < 8)
+ return ENODATA;
+
+ hdr->len = sys_ntohll(mbuf_read_u64(mb));
+ }
+
+ if (hdr->mask) {
+
+ if (mbuf_get_left(mb) < (4 + hdr->len))
+ return ENODATA;
+
+ hdr->mkey[0] = mbuf_read_u8(mb);
+ hdr->mkey[1] = mbuf_read_u8(mb);
+ hdr->mkey[2] = mbuf_read_u8(mb);
+ hdr->mkey[3] = mbuf_read_u8(mb);
+
+ for (i=0, p=mbuf_buf(mb); i<hdr->len; i++)
+ p[i] = p[i] ^ hdr->mkey[i%4];
+ }
+ else {
+ if (mbuf_get_left(mb) < hdr->len)
+ return ENODATA;
+ }
+
+ return 0;
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+ struct websock_conn *conn = arg;
+ int err = 0;
+
+ if (conn->mb) {
+
+ const size_t len = mbuf_get_left(mb), pos = conn->mb->pos;
+
+ if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+
+ conn->mb->pos = conn->mb->end;
+
+ err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len);
+ if (err)
+ goto out;
+
+ conn->mb->pos = pos;
+ }
+ else {
+ conn->mb = mem_ref(mb);
+ }
+
+ while (conn->mb) {
+
+ struct websock_hdr hdr;
+ size_t pos, end;
+
+ pos = conn->mb->pos;
+
+ err = websock_decode(&hdr, conn->mb);
+ if (err) {
+ if (err == ENODATA) {
+ conn->mb->pos = pos;
+ err = 0;
+ break;
+ }
+
+ goto out;
+ }
+
+ if (conn->active == hdr.mask) {
+ err = EPROTO;
+ goto out;
+ }
+
+ if (hdr.rsv1 || hdr.rsv2 || hdr.rsv3) {
+ err = EPROTO;
+ goto out;
+ }
+
+ mb = conn->mb;
+
+ end = mb->end;
+ mb->end = mb->pos + (size_t)hdr.len;
+
+ if (end > mb->end) {
+ struct mbuf *mbn = mbuf_alloc(end - mb->end);
+ if (!mbn) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ (void)mbuf_write_mem(mbn, mb->buf + mb->end,
+ end - mb->end);
+ mbn->pos = 0;
+
+ conn->mb = mbn;
+ }
+ else {
+ conn->mb = NULL;
+ }
+
+ switch (hdr.opcode) {
+
+ case WEBSOCK_CONT:
+ case WEBSOCK_TEXT:
+ case WEBSOCK_BIN:
+ mem_ref(conn);
+ conn->recvh(&hdr, mb, conn->arg);
+
+ if (mem_nrefs(conn) == 1) {
+
+ if (conn->state == OPEN)
+ (void)websock_close(conn,
+ WEBSOCK_GOING_AWAY,
+ "Going Away");
+
+ /*
+ * This is a hack. We enforce CLOSING
+ * state so we know the connection will
+ * continue to live.
+ */
+ conn->state = CLOSING;
+ }
+ mem_deref(conn);
+ break;
+
+ case WEBSOCK_CLOSE:
+ if (conn->state == OPEN)
+ (void)websock_send(conn, WEBSOCK_CLOSE, "%b",
+ mbuf_buf(mb), mbuf_get_left(mb));
+ conn_close(conn, 0);
+ mem_deref(mb);
+ return;
+
+ case WEBSOCK_PING:
+ (void)websock_send(conn, WEBSOCK_PONG, "%b",
+ mbuf_buf(mb), mbuf_get_left(mb));
+ break;
+
+ case WEBSOCK_PONG:
+ break;
+
+ default:
+ mem_deref(mb);
+ err = EPROTO;
+ goto out;
+ }
+
+ mem_deref(mb);
+ }
+
+ out:
+ if (err) {
+ (void)websock_close(conn, websock_err2scode(err), NULL);
+ conn_close(conn, err);
+ }
+}
+
+
+static void close_handler(int err, void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ conn_close(conn, err);
+}
+
+
+static int accept_print(struct re_printf *pf, const struct pl *key)
+{
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA_CTX ctx;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, key->p, key->l);
+ SHA1_Update(&ctx, magic, sizeof(magic)-1);
+ SHA1_Final(digest, &ctx);
+
+ return base64_print(pf, digest, sizeof(digest));
+}
+
+
+static void http_resp_handler(int err, const struct http_msg *msg, void *arg)
+{
+ struct websock_conn *conn = arg;
+ const struct http_hdr *hdr;
+ struct pl key;
+ char buf[32];
+
+ if (err || msg->scode != 101)
+ goto fail;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
+ goto fail;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
+ goto fail;
+
+ hdr = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_ACCEPT);
+ if (!hdr)
+ goto fail;
+
+ key.p = conn->nonce;
+ key.l = sizeof(conn->nonce);
+
+ if (re_snprintf(buf, sizeof(buf), "%H", accept_print, &key) < 0)
+ goto fail;
+
+ if (pl_strcmp(&hdr->val, buf))
+ goto fail;
+
+ /* here we are ok */
+
+ conn->state = OPEN;
+ (void)tcp_conn_peer_get(conn->tc, &conn->peer);
+
+ if (conn->kaint)
+ tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
+
+ conn->estabh(conn->arg);
+ return;
+
+ fail:
+ conn_close(conn, err ? err : EPROTO);
+}
+
+
+static void http_conn_handler(struct tcp_conn *tc, struct tls_conn *sc,
+ void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ conn->tc = mem_ref(tc);
+ conn->sc = mem_ref(sc);
+
+ tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
+}
+
+
+int websock_connect(struct websock_conn **connp, struct websock *sock,
+ struct http_cli *cli, const char *uri, unsigned kaint,
+ websock_estab_h *estabh, websock_recv_h *recvh,
+ websock_close_h *closeh, void *arg,
+ const char *fmt, ...)
+{
+ struct websock_conn *conn;
+ uint8_t nonce[16];
+ va_list ap;
+ size_t len;
+ int err;
+
+ if (!connp || !sock || !cli || !uri || !estabh || !recvh || !closeh)
+ return EINVAL;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ /* The nonce MUST be selected randomly for each connection */
+ rand_bytes(nonce, sizeof(nonce));
+
+ len = sizeof(conn->nonce);
+
+ err = base64_encode(nonce, sizeof(nonce), conn->nonce, &len);
+ if (err)
+ goto out;
+
+ conn->sock = mem_ref(sock);
+ conn->kaint = kaint;
+ conn->estabh = estabh;
+ conn->recvh = recvh;
+ conn->closeh = closeh;
+ conn->arg = arg;
+ conn->state = CONNECTING;
+ conn->active = true;
+
+ /* Protocol Handshake */
+ va_start(ap, fmt);
+ err = http_request(&conn->req, cli, "GET", uri,
+ http_resp_handler, NULL, conn,
+ "Upgrade: websocket\r\n"
+ "Connection: upgrade\r\n"
+ "Sec-WebSocket-Key: %b\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "%v"
+ "\r\n",
+ conn->nonce, sizeof(conn->nonce),
+ fmt, &ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ http_req_set_conn_handler(conn->req, http_conn_handler);
+
+ out:
+ if (err)
+ mem_deref(conn);
+ else
+ *connp = conn;
+
+ return err;
+}
+
+
+int websock_accept(struct websock_conn **connp, struct websock *sock,
+ struct http_conn *htconn, const struct http_msg *msg,
+ unsigned kaint, websock_recv_h *recvh,
+ websock_close_h *closeh, void *arg)
+{
+ const struct http_hdr *key;
+ struct websock_conn *conn;
+ int err;
+
+ if (!connp || !sock || !htconn || !msg || !recvh || !closeh)
+ return EINVAL;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
+ return EBADMSG;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
+ return EBADMSG;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_SEC_WEBSOCKET_VERSION, "13"))
+ return EBADMSG;
+
+ key = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_KEY);
+ if (!key)
+ return EBADMSG;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ err = http_reply(htconn, 101, "Switching Protocols",
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: %H\r\n"
+ "\r\n",
+ accept_print, &key->val);
+ if (err)
+ goto out;
+
+ sa_cpy(&conn->peer, http_conn_peer(htconn));
+ conn->sock = mem_ref(sock);
+ conn->tc = mem_ref(http_conn_tcp(htconn));
+ conn->sc = mem_ref(http_conn_tls(htconn));
+ conn->kaint = kaint;
+ conn->recvh = recvh;
+ conn->closeh = closeh;
+ conn->arg = arg;
+ conn->state = OPEN;
+ conn->active = false;
+
+ tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
+ http_conn_close(htconn);
+
+ if (conn->kaint)
+ tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
+
+ out:
+ if (err)
+ mem_deref(conn);
+ else
+ *connp = conn;
+
+ return err;
+}
+
+
+static int websock_encode(struct mbuf *mb, bool fin,
+ enum websock_opcode opcode, bool mask, size_t len)
+{
+ int err;
+
+ err = mbuf_write_u8(mb, (fin<<7) | (opcode & 0x0f));
+
+ if (len > 0xffff) {
+ err |= mbuf_write_u8(mb, (mask<<7) | 127);
+ err |= mbuf_write_u64(mb, sys_htonll(len));
+ }
+ else if (len > 125) {
+ err |= mbuf_write_u8(mb, (mask<<7) | 126);
+ err |= mbuf_write_u16(mb, htons(len));
+ }
+ else {
+ err |= mbuf_write_u8(mb, (mask<<7) | len);
+ }
+
+ if (mask) {
+ uint8_t mkey[4];
+ uint8_t *p;
+ size_t i;
+
+ rand_bytes(mkey, sizeof(mkey));
+
+ err |= mbuf_write_mem(mb, mkey, sizeof(mkey));
+
+ for (i=0, p=mbuf_buf(mb); i<len; i++)
+ p[i] = p[i] ^ mkey[i%4];
+ }
+
+ return err;
+}
+
+
+static int websock_vsend(struct websock_conn *conn, enum websock_opcode opcode,
+ enum websock_scode scode, const char *fmt, va_list ap)
+{
+ const size_t hsz = conn->active ? 14 : 10;
+ size_t len, start;
+ struct mbuf *mb;
+ int err = 0;
+
+ if (conn->state != OPEN)
+ return ENOTCONN;
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = hsz;
+
+ if (scode)
+ err |= mbuf_write_u16(mb, htons(scode));
+ if (fmt)
+ err |= mbuf_vprintf(mb, fmt, ap);
+ if (err)
+ goto out;
+
+ len = mb->pos - hsz;
+
+ if (len > 0xffff)
+ start = mb->pos = 0;
+ else if (len > 125)
+ start = mb->pos = 6;
+ else
+ start = mb->pos = 8;
+
+ err = websock_encode(mb, true, opcode, conn->active, len);
+ if (err)
+ goto out;
+
+ mb->pos = start;
+
+ err = tcp_send(conn->tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!conn)
+ return EINVAL;
+
+ va_start(ap, fmt);
+ err = websock_vsend(conn, opcode, 0, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+int websock_close(struct websock_conn *conn, enum websock_scode scode,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!conn)
+ return EINVAL;
+
+ if (!scode)
+ fmt = NULL;
+
+ va_start(ap, fmt);
+ err = websock_vsend(conn, WEBSOCK_CLOSE, scode, fmt, ap);
+ va_end(ap);
+
+ if (!err)
+ conn->state = CLOSING;
+
+ return err;
+}
+
+
+const struct sa *websock_peer(const struct websock_conn *conn)
+{
+ return conn ? &conn->peer : NULL;
+}
+
+
+int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth, void *arg)
+{
+ struct websock *sock;
+
+ if (!sockp)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), sock_destructor);
+ if (!sock)
+ return ENOMEM;
+
+ sock->shuth = shuth;
+ sock->arg = arg;
+
+ *sockp = sock;
+
+ return 0;
+}
+
+
+void websock_shutdown(struct websock *sock)
+{
+ if (!sock || sock->shutdown)
+ return;
+
+ sock->shutdown = true;
+ mem_deref(sock);
+}