websock: added WebSocket client/server module
This commit is contained in:
parent
38f4370a6c
commit
9891c74872
8 changed files with 832 additions and 3 deletions
2
Makefile
2
Makefile
|
@ -18,7 +18,7 @@ include $(MK)
|
|||
|
||||
# List of modules
|
||||
MODULES += sip sipevent sipreg sipsess
|
||||
MODULES += uri http httpauth msg
|
||||
MODULES += uri http httpauth msg websock
|
||||
MODULES += stun turn ice
|
||||
MODULES += natbd
|
||||
MODULES += rtp sdp jbuf telev
|
||||
|
|
|
@ -61,6 +61,7 @@ Modules:
|
|||
* turn testing Obtaining Relay Addresses from STUN (TURN)
|
||||
* udp testing UDP transport
|
||||
* uri testing Generic URI library
|
||||
* websock unstable WebSocket Client and Server
|
||||
|
||||
legend:
|
||||
"stable" - Code complete; Stable code and stable API
|
||||
|
@ -111,6 +112,7 @@ Features:
|
|||
* RFC 5780 - NAT Behaviour Discovery Using STUN
|
||||
* RFC 6026 - Correct Transaction Handling for 2xx Resp. to SIP INVITE Requests
|
||||
* RFC 6156 - TURN Extension for IPv6
|
||||
* RFC 6455 - The WebSocket Protocol
|
||||
* Symmetric RTP
|
||||
* ITU-T G.711 Appendix I and Appendix II
|
||||
* draft-ietf-bfcpbis-rfc4582bis-08
|
||||
|
|
|
@ -55,6 +55,7 @@ extern "C" {
|
|||
#include "re_tls.h"
|
||||
#include "re_turn.h"
|
||||
#include "re_udp.h"
|
||||
#include "re_websock.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -122,6 +122,8 @@ int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc);
|
|||
int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
|
||||
const char *uri, http_resp_h *resph, http_data_h *datah,
|
||||
void *arg, const char *fmt, ...);
|
||||
struct tcp_conn *http_req_tcp(struct http_req *req);
|
||||
struct tls_conn *http_req_tls(struct http_req *req);
|
||||
|
||||
|
||||
/* Server */
|
||||
|
|
74
include/re_websock.h
Normal file
74
include/re_websock.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* @file re_websock.h The WebSocket Protocol
|
||||
*
|
||||
* Copyright (C) 2010 Creytiv.com
|
||||
*/
|
||||
|
||||
|
||||
enum {
|
||||
WEBSOCK_VERSION = 13,
|
||||
};
|
||||
|
||||
enum websock_opcode {
|
||||
/* Data frames */
|
||||
WEBSOCK_CONT = 0x0,
|
||||
WEBSOCK_TEXT = 0x1,
|
||||
WEBSOCK_BIN = 0x2,
|
||||
/* Control frames */
|
||||
WEBSOCK_CLOSE = 0x8,
|
||||
WEBSOCK_PING = 0x9,
|
||||
WEBSOCK_PONG = 0xa,
|
||||
};
|
||||
|
||||
enum websock_scode {
|
||||
WEBSOCK_NORMAL_CLOSURE = 1000,
|
||||
WEBSOCK_GOING_AWAY = 1001,
|
||||
WEBSOCK_PROTOCOL_ERROR = 1002,
|
||||
WEBSOCK_UNSUPPORTED_DATA = 1003,
|
||||
WEBSOCK_INVALID_PAYLOAD = 1007,
|
||||
WEBSOCK_POLICY_VIOLATION = 1008,
|
||||
WEBSOCK_MESSAGE_TOO_BIG = 1009,
|
||||
WEBSOCK_EXTENSION_ERROR = 1010,
|
||||
WEBSOCK_INTERNAL_ERROR = 1011,
|
||||
};
|
||||
|
||||
struct websock_hdr {
|
||||
unsigned fin:1;
|
||||
unsigned rsv1:1;
|
||||
unsigned rsv2:1;
|
||||
unsigned rsv3:1;
|
||||
unsigned opcode:4;
|
||||
unsigned mask:1;
|
||||
uint64_t len;
|
||||
uint8_t mkey[4];
|
||||
};
|
||||
|
||||
struct websock;
|
||||
struct websock_conn;
|
||||
|
||||
typedef void (websock_estab_h)(void *arg);
|
||||
typedef void (websock_recv_h)(const struct websock_hdr *hdr, struct mbuf *mb,
|
||||
void *arg);
|
||||
typedef void (websock_close_h)(int err, void *arg);
|
||||
|
||||
|
||||
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, ...);
|
||||
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);
|
||||
int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
|
||||
const char *fmt, ...);
|
||||
int websock_close(struct websock_conn *conn, enum websock_scode scode,
|
||||
const char *fmt, ...);
|
||||
const struct sa *websock_peer(const struct websock_conn *conn);
|
||||
|
||||
typedef void (websock_shutdown_h)(void *arg);
|
||||
|
||||
int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth,
|
||||
void *arg);
|
||||
void websock_shutdown(struct websock *sock);
|
|
@ -338,12 +338,14 @@ int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
|
|||
&scheme, &host, NULL, &port, &path) || scheme.p != uri)
|
||||
return EINVAL;
|
||||
|
||||
if (!pl_strcasecmp(&scheme, "http")) {
|
||||
if (!pl_strcasecmp(&scheme, "http") ||
|
||||
!pl_strcasecmp(&scheme, "ws")) {
|
||||
secure = false;
|
||||
defport = 80;
|
||||
}
|
||||
#ifdef USE_TLS
|
||||
else if (!pl_strcasecmp(&scheme, "https")) {
|
||||
else if (!pl_strcasecmp(&scheme, "https") ||
|
||||
!pl_strcasecmp(&scheme, "wss")) {
|
||||
secure = true;
|
||||
defport = 443;
|
||||
}
|
||||
|
@ -455,3 +457,15 @@ int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc)
|
|||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
struct tcp_conn *http_req_tcp(struct http_req *req)
|
||||
{
|
||||
return req ? req->tc : NULL;
|
||||
}
|
||||
|
||||
|
||||
struct tls_conn *http_req_tls(struct http_req *req)
|
||||
{
|
||||
return req ? req->sc : NULL;
|
||||
}
|
||||
|
|
7
src/websock/mod.mk
Normal file
7
src/websock/mod.mk
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# mod.mk
|
||||
#
|
||||
# Copyright (C) 2010 Creytiv.com
|
||||
#
|
||||
|
||||
SRCS += websock/websock.c
|
729
src/websock/websock.c
Normal file
729
src/websock/websock.c
Normal file
|
@ -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_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;
|
||||
conn->tc = mem_ref(http_req_tcp(conn->req));
|
||||
conn->sc = mem_ref(http_req_tls(conn->req));
|
||||
(void)tcp_conn_peer_get(conn->tc, &conn->peer);
|
||||
|
||||
tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
|
||||
conn->req = mem_deref(conn->req);
|
||||
|
||||
if (conn->kaint)
|
||||
tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
|
||||
|
||||
conn->estabh(conn->arg);
|
||||
return;
|
||||
|
||||
fail:
|
||||
conn_close(conn, err ? err : EPROTO);
|
||||
}
|
||||
|
||||
|
||||
/* dummy HTTP data handler, this must be here so that HTTP client
|
||||
* is not closing the underlying TCP-connection (which we need ..)
|
||||
*/
|
||||
static void http_data_handler(struct mbuf *mb, void *arg)
|
||||
{
|
||||
(void)mb;
|
||||
(void)arg;
|
||||
}
|
||||
|
||||
|
||||
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, http_data_handler, 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;
|
||||
|
||||
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);
|
||||
}
|
Loading…
Add table
Reference in a new issue