You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1554 lines
58 KiB
1554 lines
58 KiB
/*
|
|
Copyright (c) 2013 250bpm s.r.o. All rights reserved.
|
|
Copyright (c) 2014 Wirebird Labs LLC. All rights reserved.
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom
|
|
the Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included
|
|
in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "sws.h"
|
|
#include "../../ws.h"
|
|
#include "../../nn.h"
|
|
|
|
#include "../../utils/alloc.h"
|
|
#include "../../utils/err.h"
|
|
#include "../../utils/cont.h"
|
|
#include "../../utils/fast.h"
|
|
#include "../../utils/wire.h"
|
|
#include "../../utils/int.h"
|
|
#include "../../utils/attr.h"
|
|
#include "../../utils/random.h"
|
|
|
|
/* States of the object as a whole. */
|
|
#define NN_SWS_STATE_IDLE 1
|
|
#define NN_SWS_STATE_HANDSHAKE 2
|
|
#define NN_SWS_STATE_STOPPING_HANDSHAKE 3
|
|
#define NN_SWS_STATE_ACTIVE 4
|
|
#define NN_SWS_STATE_CLOSING_CONNECTION 5
|
|
#define NN_SWS_STATE_BROKEN_CONNECTION 6
|
|
#define NN_SWS_STATE_DONE 7
|
|
#define NN_SWS_STATE_STOPPING 8
|
|
|
|
/* Possible states of the inbound part of the object. */
|
|
#define NN_SWS_INSTATE_RECV_HDR 1
|
|
#define NN_SWS_INSTATE_RECV_HDREXT 2
|
|
#define NN_SWS_INSTATE_RECV_PAYLOAD 3
|
|
#define NN_SWS_INSTATE_RECVD_CHUNKED 4
|
|
#define NN_SWS_INSTATE_RECVD_CONTROL 5
|
|
#define NN_SWS_INSTATE_FAILING 6
|
|
#define NN_SWS_INSTATE_CLOSED 7
|
|
|
|
/* Possible states of the outbound part of the object. */
|
|
#define NN_SWS_OUTSTATE_IDLE 1
|
|
#define NN_SWS_OUTSTATE_SENDING 2
|
|
|
|
/* Subordinate srcptr objects. */
|
|
#define NN_SWS_SRC_USOCK 1
|
|
#define NN_SWS_SRC_HANDSHAKE 2
|
|
|
|
/* WebSocket opcode constants as per RFC 6455 5.2. */
|
|
#define NN_WS_OPCODE_FRAGMENT 0x00
|
|
#define NN_WS_OPCODE_TEXT NN_WS_MSG_TYPE_TEXT
|
|
#define NN_WS_OPCODE_BINARY NN_WS_MSG_TYPE_BINARY
|
|
#define NN_WS_OPCODE_UNUSED3 0x03
|
|
#define NN_WS_OPCODE_UNUSED4 0x04
|
|
#define NN_WS_OPCODE_UNUSED5 0x05
|
|
#define NN_WS_OPCODE_UNUSED6 0x06
|
|
#define NN_WS_OPCODE_UNUSED7 0x07
|
|
#define NN_WS_OPCODE_CLOSE NN_WS_MSG_TYPE_CLOSE
|
|
#define NN_WS_OPCODE_PING NN_WS_MSG_TYPE_PING
|
|
#define NN_WS_OPCODE_PONG NN_WS_MSG_TYPE_PONG
|
|
#define NN_WS_OPCODE_UNUSEDB 0x0B
|
|
#define NN_WS_OPCODE_UNUSEDC 0x0C
|
|
#define NN_WS_OPCODE_UNUSEDD 0x0D
|
|
#define NN_WS_OPCODE_UNUSEDE 0x0E
|
|
#define NN_WS_OPCODE_UNUSEDF 0x0F
|
|
|
|
/* WebSocket protocol header bit masks as per RFC 6455. */
|
|
#define NN_SWS_FRAME_BITMASK_MASKED 0x80
|
|
#define NN_SWS_FRAME_BITMASK_NOT_MASKED 0x00
|
|
#define NN_SWS_FRAME_BITMASK_LENGTH 0x7F
|
|
|
|
/* WebSocket Close Status Codes (1004-1006 and 1015 are reserved). */
|
|
#define NN_SWS_CLOSE_NORMAL 1000
|
|
#define NN_SWS_CLOSE_GOING_AWAY 1001
|
|
#define NN_SWS_CLOSE_ERR_PROTO 1002
|
|
#define NN_SWS_CLOSE_ERR_WUT 1003
|
|
#define NN_SWS_CLOSE_ERR_INVALID_FRAME 1007
|
|
#define NN_SWS_CLOSE_ERR_POLICY 1008
|
|
#define NN_SWS_CLOSE_ERR_TOOBIG 1009
|
|
#define NN_SWS_CLOSE_ERR_EXTENSION 1010
|
|
#define NN_SWS_CLOSE_ERR_SERVER 1011
|
|
|
|
/* UTF-8 validation. */
|
|
#define NN_SWS_UTF8_INVALID -2
|
|
#define NN_SWS_UTF8_FRAGMENT -1
|
|
|
|
/* Stream is a special type of pipe. Implementation of the virtual pipe API. */
|
|
static int nn_sws_send (struct nn_pipebase *self, struct nn_msg *msg);
|
|
static int nn_sws_recv (struct nn_pipebase *self, struct nn_msg *msg);
|
|
const struct nn_pipebase_vfptr nn_sws_pipebase_vfptr = {
|
|
nn_sws_send,
|
|
nn_sws_recv
|
|
};
|
|
|
|
/* Private functions. */
|
|
static void nn_sws_handler (struct nn_fsm *self, int src, int type,
|
|
void *srcptr);
|
|
static void nn_sws_shutdown (struct nn_fsm *self, int src, int type,
|
|
void *srcptr);
|
|
|
|
/* Ceases further I/O on the underlying socket and prepares to send a
|
|
close handshake on the next receive. */
|
|
static int nn_sws_fail_conn (struct nn_sws *self, int code, char *reason);
|
|
|
|
/* Start receiving new message chunk. */
|
|
static int nn_sws_recv_hdr (struct nn_sws *self);
|
|
|
|
/* Mask or unmask message payload. */
|
|
static void nn_sws_mask_payload (uint8_t *payload, size_t payload_len,
|
|
const uint8_t *mask, size_t mask_len, int *mask_start_pos);
|
|
|
|
/* Validates incoming text chunks for UTF-8 compliance as per RFC 3629. */
|
|
static void nn_sws_validate_utf8_chunk (struct nn_sws *self);
|
|
|
|
/* Ensures that Close frames received from peer conform to
|
|
RFC 6455 section 7. */
|
|
static void nn_sws_validate_close_handshake (struct nn_sws *self);
|
|
|
|
void nn_sws_init (struct nn_sws *self, int src,
|
|
struct nn_epbase *epbase, struct nn_fsm *owner)
|
|
{
|
|
nn_fsm_init (&self->fsm, nn_sws_handler, nn_sws_shutdown,
|
|
src, self, owner);
|
|
self->state = NN_SWS_STATE_IDLE;
|
|
nn_ws_handshake_init (&self->handshaker,
|
|
NN_SWS_SRC_HANDSHAKE, &self->fsm);
|
|
self->usock = NULL;
|
|
self->usock_owner.src = -1;
|
|
self->usock_owner.fsm = NULL;
|
|
nn_pipebase_init (&self->pipebase, &nn_sws_pipebase_vfptr, epbase);
|
|
self->instate = -1;
|
|
nn_list_init (&self->inmsg_array);
|
|
self->outstate = -1;
|
|
nn_msg_init (&self->outmsg, 0);
|
|
|
|
self->continuing = 0;
|
|
|
|
memset (self->utf8_code_pt_fragment, 0,
|
|
NN_SWS_UTF8_MAX_CODEPOINT_LEN);
|
|
self->utf8_code_pt_fragment_len = 0;
|
|
|
|
self->pings_sent = 0;
|
|
self->pongs_sent = 0;
|
|
self->pings_received = 0;
|
|
self->pongs_received = 0;
|
|
|
|
nn_fsm_event_init (&self->done);
|
|
}
|
|
|
|
void nn_sws_term (struct nn_sws *self)
|
|
{
|
|
nn_assert_state (self, NN_SWS_STATE_IDLE);
|
|
|
|
nn_fsm_event_term (&self->done);
|
|
nn_msg_term (&self->outmsg);
|
|
nn_msg_array_term (&self->inmsg_array);
|
|
nn_pipebase_term (&self->pipebase);
|
|
nn_ws_handshake_term (&self->handshaker);
|
|
nn_fsm_term (&self->fsm);
|
|
}
|
|
|
|
int nn_sws_isidle (struct nn_sws *self)
|
|
{
|
|
return nn_fsm_isidle (&self->fsm);
|
|
}
|
|
|
|
void nn_sws_start (struct nn_sws *self, struct nn_usock *usock, int mode,
|
|
const char *resource, const char *host)
|
|
{
|
|
/* Take ownership of the underlying socket. */
|
|
nn_assert (self->usock == NULL && self->usock_owner.fsm == NULL);
|
|
self->usock_owner.src = NN_SWS_SRC_USOCK;
|
|
self->usock_owner.fsm = &self->fsm;
|
|
nn_usock_swap_owner (usock, &self->usock_owner);
|
|
self->usock = usock;
|
|
self->mode = mode;
|
|
self->resource = resource;
|
|
self->remote_host = host;
|
|
|
|
/* Launch the state machine. */
|
|
nn_fsm_start (&self->fsm);
|
|
}
|
|
|
|
void nn_sws_stop (struct nn_sws *self)
|
|
{
|
|
nn_fsm_stop (&self->fsm);
|
|
}
|
|
|
|
void *nn_msg_chunk_new (size_t size, struct nn_list *msg_array)
|
|
{
|
|
struct msg_chunk *self;
|
|
|
|
self = nn_alloc (sizeof (struct msg_chunk), "msg_chunk");
|
|
alloc_assert (self);
|
|
|
|
nn_chunkref_init (&self->chunk, size);
|
|
nn_list_item_init (&self->item);
|
|
|
|
nn_list_insert (msg_array, &self->item, nn_list_end (msg_array));
|
|
|
|
return nn_chunkref_data (&self->chunk);
|
|
}
|
|
|
|
void nn_msg_chunk_term (struct msg_chunk *it, struct nn_list *msg_array)
|
|
{
|
|
nn_chunkref_term (&it->chunk);
|
|
nn_list_erase (msg_array, &it->item);
|
|
nn_list_item_term (&it->item);
|
|
nn_free (it);
|
|
}
|
|
|
|
void nn_msg_array_term (struct nn_list *msg_array)
|
|
{
|
|
struct nn_list_item *it;
|
|
struct msg_chunk *ch;
|
|
|
|
while (!nn_list_empty (msg_array)) {
|
|
it = nn_list_begin (msg_array);
|
|
ch = nn_cont (it, struct msg_chunk, item);
|
|
nn_msg_chunk_term (ch, msg_array);
|
|
}
|
|
|
|
nn_list_term (msg_array);
|
|
}
|
|
|
|
int nn_utf8_code_point (const uint8_t *buffer, size_t len)
|
|
{
|
|
/* The lack of information is considered neither valid nor invalid. */
|
|
if (!buffer || !len)
|
|
return NN_SWS_UTF8_FRAGMENT;
|
|
|
|
/* RFC 3629 section 4 UTF8-1. */
|
|
if (buffer [0] <= 0x7F)
|
|
return 1;
|
|
|
|
/* 0xC2, or 11000001, is the smallest conceivable multi-octet code
|
|
point that is not an illegal overlong encoding. */
|
|
if (buffer [0] < 0xC2)
|
|
return NN_SWS_UTF8_INVALID;
|
|
|
|
/* Largest 2-octet code point starts with 0xDF (11011111). */
|
|
if (buffer [0] <= 0xDF) {
|
|
if (len < 2)
|
|
return NN_SWS_UTF8_FRAGMENT;
|
|
/* Ensure continuation byte in form of 10xxxxxx */
|
|
else if ((buffer [1] & 0xC0) != 0x80)
|
|
return NN_SWS_UTF8_INVALID;
|
|
else
|
|
return 2;
|
|
}
|
|
|
|
/* RFC 3629 section 4 UTF8-3, where 0xEF is 11101111. */
|
|
if (buffer [0] <= 0xEF) {
|
|
/* Fragment. */
|
|
if (len < 2)
|
|
return NN_SWS_UTF8_FRAGMENT;
|
|
/* Illegal overlong sequence detection. */
|
|
else if (buffer [0] == 0xE0 && (buffer [1] < 0xA0 || buffer [1] == 0x80))
|
|
return NN_SWS_UTF8_INVALID;
|
|
/* Illegal UTF-16 surrogate pair half U+D800 through U+DFFF. */
|
|
else if (buffer [0] == 0xED && buffer [1] >= 0xA0)
|
|
return NN_SWS_UTF8_INVALID;
|
|
/* Fragment. */
|
|
else if (len < 3)
|
|
return NN_SWS_UTF8_FRAGMENT;
|
|
/* Ensure continuation bytes 2 and 3 in form of 10xxxxxx */
|
|
else if ((buffer [1] & 0xC0) != 0x80 || (buffer [2] & 0xC0) != 0x80)
|
|
return NN_SWS_UTF8_INVALID;
|
|
else
|
|
return 3;
|
|
}
|
|
|
|
/* RFC 3629 section 4 UTF8-4, where 0xF4 is 11110100. Why
|
|
not 11110111 to follow the pattern? Because UTF-8 encoding
|
|
stops at 0x10FFFF as per RFC 3629. */
|
|
if (buffer [0] <= 0xF4) {
|
|
/* Fragment. */
|
|
if (len < 2)
|
|
return NN_SWS_UTF8_FRAGMENT;
|
|
/* Illegal overlong sequence detection. */
|
|
else if (buffer [0] == 0xF0 && buffer [1] < 0x90)
|
|
return NN_SWS_UTF8_INVALID;
|
|
/* Illegal code point greater than U+10FFFF. */
|
|
else if (buffer [0] == 0xF4 && buffer [1] >= 0x90)
|
|
return NN_SWS_UTF8_INVALID;
|
|
/* Fragment. */
|
|
else if (len < 4)
|
|
return NN_SWS_UTF8_FRAGMENT;
|
|
/* Ensure continuation bytes 2, 3, and 4 in form of 10xxxxxx */
|
|
else if ((buffer [1] & 0xC0) != 0x80 ||
|
|
(buffer [2] & 0xC0) != 0x80 ||
|
|
(buffer [3] & 0xC0) != 0x80)
|
|
return NN_SWS_UTF8_INVALID;
|
|
else
|
|
return 4;
|
|
}
|
|
|
|
/* UTF-8 encoding stops at U+10FFFF and only defines up to 4-octet
|
|
code point sequences. */
|
|
if (buffer [0] >= 0xF5)
|
|
return NN_SWS_UTF8_INVALID;
|
|
|
|
/* Algorithm error; a case above should have been satisfied. */
|
|
nn_assert (0);
|
|
}
|
|
|
|
static void nn_sws_mask_payload (uint8_t *payload, size_t payload_len,
|
|
const uint8_t *mask, size_t mask_len, int *mask_start_pos)
|
|
{
|
|
unsigned i;
|
|
|
|
if (mask_start_pos) {
|
|
for (i = 0; i < payload_len; i++) {
|
|
payload [i] ^= mask [(i + *mask_start_pos) % mask_len];
|
|
}
|
|
|
|
*mask_start_pos = (i + *mask_start_pos) % mask_len;
|
|
|
|
return;
|
|
}
|
|
else {
|
|
for (i = 0; i < payload_len; i++) {
|
|
payload [i] ^= mask [i % mask_len];
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int nn_sws_recv_hdr (struct nn_sws *self)
|
|
{
|
|
if (!self->continuing) {
|
|
nn_assert (nn_list_empty (&self->inmsg_array));
|
|
|
|
self->inmsg_current_chunk_buf = NULL;
|
|
self->inmsg_chunks = 0;
|
|
self->inmsg_current_chunk_len = 0;
|
|
self->inmsg_total_size = 0;
|
|
}
|
|
|
|
memset (self->inmsg_control, 0, NN_SWS_PAYLOAD_MAX_LENGTH);
|
|
memset (self->inhdr, 0, NN_SWS_FRAME_MAX_HDR_LEN);
|
|
self->instate = NN_SWS_INSTATE_RECV_HDR;
|
|
nn_usock_recv (self->usock, self->inhdr, NN_SWS_FRAME_SIZE_INITIAL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nn_sws_send (struct nn_pipebase *self, struct nn_msg *msg)
|
|
{
|
|
struct nn_sws *sws;
|
|
struct nn_iovec iov [3];
|
|
int mask_pos;
|
|
size_t nn_msg_size;
|
|
size_t hdr_len;
|
|
struct nn_cmsghdr *cmsg;
|
|
struct nn_msghdr msghdr;
|
|
uint8_t rand_mask [NN_SWS_FRAME_SIZE_MASK];
|
|
|
|
sws = nn_cont (self, struct nn_sws, pipebase);
|
|
|
|
nn_assert_state (sws, NN_SWS_STATE_ACTIVE);
|
|
nn_assert (sws->outstate == NN_SWS_OUTSTATE_IDLE);
|
|
|
|
/* Move the message to the local storage. */
|
|
nn_msg_term (&sws->outmsg);
|
|
nn_msg_mv (&sws->outmsg, msg);
|
|
|
|
memset (sws->outhdr, 0, sizeof (sws->outhdr));
|
|
|
|
hdr_len = NN_SWS_FRAME_SIZE_INITIAL;
|
|
|
|
cmsg = NULL;
|
|
msghdr.msg_iov = NULL;
|
|
msghdr.msg_iovlen = 0;
|
|
msghdr.msg_controllen = nn_chunkref_size (&sws->outmsg.hdrs);
|
|
|
|
/* If the outgoing message has specified an opcode and control framing in
|
|
its header, properly frame it as per RFC 6455 5.2. */
|
|
if (msghdr.msg_controllen > 0) {
|
|
msghdr.msg_control = nn_chunkref_data (&sws->outmsg.hdrs);
|
|
cmsg = NN_CMSG_FIRSTHDR (&msghdr);
|
|
while (cmsg) {
|
|
if (cmsg->cmsg_level == NN_WS && cmsg->cmsg_type == NN_WS_HDR_OPCODE)
|
|
break;
|
|
cmsg = NN_CMSG_NXTHDR (&msghdr, cmsg);
|
|
}
|
|
}
|
|
|
|
/* If the header does not specify an opcode, assume default. */
|
|
if (cmsg)
|
|
sws->outhdr [0] = *(uint8_t *) NN_CMSG_DATA (cmsg);
|
|
else
|
|
sws->outhdr [0] = NN_WS_OPCODE_BINARY;
|
|
|
|
/* For now, enforce that outgoing messages are the final frame. */
|
|
sws->outhdr [0] |= NN_SWS_FRAME_BITMASK_FIN;
|
|
|
|
nn_msg_size = nn_chunkref_size (&sws->outmsg.sphdr) +
|
|
nn_chunkref_size (&sws->outmsg.body);
|
|
|
|
/* Framing WebSocket payload size in network byte order (big endian). */
|
|
if (nn_msg_size <= NN_SWS_PAYLOAD_MAX_LENGTH) {
|
|
sws->outhdr [1] |= (uint8_t) nn_msg_size;
|
|
hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_0;
|
|
}
|
|
else if (nn_msg_size <= NN_SWS_PAYLOAD_MAX_LENGTH_16) {
|
|
sws->outhdr [1] |= NN_SWS_PAYLOAD_FRAME_16;
|
|
nn_puts (&sws->outhdr [hdr_len], (uint16_t) nn_msg_size);
|
|
hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_16;
|
|
}
|
|
else {
|
|
sws->outhdr [1] |= NN_SWS_PAYLOAD_FRAME_63;
|
|
nn_putll (&sws->outhdr [hdr_len], (uint64_t) nn_msg_size);
|
|
hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_63;
|
|
}
|
|
|
|
if (sws->mode == NN_WS_CLIENT) {
|
|
sws->outhdr [1] |= NN_SWS_FRAME_BITMASK_MASKED;
|
|
|
|
/* Generate 32-bit mask as per RFC 6455 5.3. */
|
|
nn_random_generate (rand_mask, NN_SWS_FRAME_SIZE_MASK);
|
|
|
|
memcpy (&sws->outhdr [hdr_len], rand_mask, NN_SWS_FRAME_SIZE_MASK);
|
|
hdr_len += NN_SWS_FRAME_SIZE_MASK;
|
|
|
|
/* Mask payload, beginning with header and moving to body. */
|
|
mask_pos = 0;
|
|
|
|
nn_sws_mask_payload (nn_chunkref_data (&sws->outmsg.sphdr),
|
|
nn_chunkref_size (&sws->outmsg.sphdr),
|
|
rand_mask, NN_SWS_FRAME_SIZE_MASK, &mask_pos);
|
|
|
|
nn_sws_mask_payload (nn_chunkref_data (&sws->outmsg.body),
|
|
nn_chunkref_size (&sws->outmsg.body),
|
|
rand_mask, NN_SWS_FRAME_SIZE_MASK, &mask_pos);
|
|
|
|
}
|
|
else if (sws->mode == NN_WS_SERVER) {
|
|
sws->outhdr [1] |= NN_SWS_FRAME_BITMASK_NOT_MASKED;
|
|
}
|
|
else {
|
|
/* Developer error; sws object was not constructed properly. */
|
|
nn_assert (0);
|
|
}
|
|
|
|
/* Start async sending. */
|
|
iov [0].iov_base = sws->outhdr;
|
|
iov [0].iov_len = hdr_len;
|
|
iov [1].iov_base = nn_chunkref_data (&sws->outmsg.sphdr);
|
|
iov [1].iov_len = nn_chunkref_size (&sws->outmsg.sphdr);
|
|
iov [2].iov_base = nn_chunkref_data (&sws->outmsg.body);
|
|
iov [2].iov_len = nn_chunkref_size (&sws->outmsg.body);
|
|
nn_usock_send (sws->usock, iov, 3);
|
|
|
|
sws->outstate = NN_SWS_OUTSTATE_SENDING;
|
|
|
|
/* If a Close handshake was just sent, it's time to shut down. */
|
|
if ((sws->outhdr [0] & NN_SWS_FRAME_BITMASK_OPCODE) ==
|
|
NN_WS_OPCODE_CLOSE) {
|
|
nn_pipebase_stop (&sws->pipebase);
|
|
sws->state = NN_SWS_STATE_CLOSING_CONNECTION;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nn_sws_recv (struct nn_pipebase *self, struct nn_msg *msg)
|
|
{
|
|
struct nn_sws *sws;
|
|
struct nn_iovec iov [1];
|
|
struct nn_list_item *it;
|
|
struct msg_chunk *ch;
|
|
struct nn_cmsghdr *cmsg;
|
|
uint8_t opcode_hdr;
|
|
size_t cmsgsz;
|
|
int pos;
|
|
|
|
sws = nn_cont (self, struct nn_sws, pipebase);
|
|
|
|
nn_assert_state (sws, NN_SWS_STATE_ACTIVE);
|
|
|
|
switch (sws->instate) {
|
|
case NN_SWS_INSTATE_FAILING:
|
|
|
|
/* Prevent further send/recv operations on this connection. */
|
|
nn_pipebase_stop (self);
|
|
sws->instate = NN_SWS_INSTATE_CLOSED;
|
|
|
|
/* Inform user this connection has been failed. */
|
|
nn_msg_init (msg, 0);
|
|
|
|
opcode_hdr = NN_WS_MSG_TYPE_GONE | NN_SWS_FRAME_BITMASK_FIN;
|
|
|
|
iov [0].iov_base = sws->fail_msg;
|
|
iov [0].iov_len = sws->fail_msg_len;
|
|
|
|
/* TODO: Consider queueing and unconditionally sending close
|
|
handshake rather than skipping it. */
|
|
/* RFC 6455 7.1.7 - try to send helpful Closing Handshake only if
|
|
the socket is not currently sending. If it's still busy sending,
|
|
forcibly close this connection, since it's not readily deterministic
|
|
how much time that action could take to complete, or if the peer is
|
|
even healthy enough to receive. Rationale: try to be nice, but be
|
|
mindful of self-preservation! */
|
|
if (sws->outstate == NN_SWS_OUTSTATE_IDLE) {
|
|
nn_usock_send (sws->usock, iov, 1);
|
|
sws->outstate = NN_SWS_OUTSTATE_SENDING;
|
|
sws->state = NN_SWS_STATE_CLOSING_CONNECTION;
|
|
}
|
|
else {
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done,
|
|
NN_SWS_RETURN_CLOSE_HANDSHAKE);
|
|
}
|
|
break;
|
|
|
|
case NN_SWS_INSTATE_RECVD_CHUNKED:
|
|
|
|
/* This library should not deliver fragmented messages to the application,
|
|
so it's expected that this is the final frame. */
|
|
nn_assert (sws->is_final_frame);
|
|
|
|
nn_msg_init (msg, sws->inmsg_total_size);
|
|
|
|
/* Relay opcode to the user in order to interpret payload. */
|
|
opcode_hdr = sws->inmsg_hdr;
|
|
|
|
pos = 0;
|
|
|
|
/* Reassemble incoming message scatter array. */
|
|
while (!nn_list_empty (&sws->inmsg_array)) {
|
|
it = nn_list_begin (&sws->inmsg_array);
|
|
ch = nn_cont (it, struct msg_chunk, item);
|
|
memcpy (((uint8_t*) nn_chunkref_data (&msg->body)) + pos,
|
|
nn_chunkref_data (&ch->chunk),
|
|
nn_chunkref_size (&ch->chunk));
|
|
pos += nn_chunkref_size (&ch->chunk);
|
|
nn_msg_chunk_term (ch, &sws->inmsg_array);
|
|
}
|
|
|
|
nn_assert (pos == sws->inmsg_total_size);
|
|
nn_assert (nn_list_empty (&sws->inmsg_array));
|
|
|
|
/* No longer collecting scatter array of incoming msg chunks. */
|
|
sws->continuing = 0;
|
|
|
|
nn_sws_recv_hdr (sws);
|
|
|
|
break;
|
|
|
|
case NN_SWS_INSTATE_RECVD_CONTROL:
|
|
|
|
/* This library should not deliver fragmented messages to the user, so
|
|
it's expected that this is the final frame. */
|
|
nn_assert (sws->is_final_frame);
|
|
|
|
nn_msg_init (msg, sws->inmsg_current_chunk_len);
|
|
|
|
/* Relay opcode to the user in order to interpret payload. */
|
|
opcode_hdr = sws->inhdr [0];
|
|
|
|
memcpy (((uint8_t*) nn_chunkref_data (&msg->body)),
|
|
sws->inmsg_control, sws->inmsg_current_chunk_len);
|
|
|
|
/* If a closing handshake was just transferred to the application,
|
|
discontinue continual, async receives. */
|
|
if (sws->opcode == NN_WS_OPCODE_CLOSE) {
|
|
sws->instate = NN_SWS_INSTATE_CLOSED;
|
|
}
|
|
else {
|
|
nn_sws_recv_hdr (sws);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
/* Unexpected state. */
|
|
nn_assert (0);
|
|
break;
|
|
}
|
|
|
|
/* Allocate and populate WebSocket-specific control headers. */
|
|
cmsgsz = NN_CMSG_SPACE (sizeof (opcode_hdr));
|
|
nn_chunkref_init (&msg->hdrs, cmsgsz);
|
|
cmsg = nn_chunkref_data (&msg->hdrs);
|
|
cmsg->cmsg_level = NN_WS;
|
|
cmsg->cmsg_type = NN_WS_HDR_OPCODE;
|
|
cmsg->cmsg_len = cmsgsz;
|
|
memcpy (NN_CMSG_DATA (cmsg), &opcode_hdr, sizeof (opcode_hdr));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nn_sws_validate_utf8_chunk (struct nn_sws *self)
|
|
{
|
|
uint8_t *pos; int32_t code_point_len,len;
|
|
|
|
len = (int32_t)self->inmsg_current_chunk_len;
|
|
pos = self->inmsg_current_chunk_buf;
|
|
|
|
/* For chunked transfers, it's possible that a previous chunk was cut
|
|
intra-code point. That partially-validated code point is reassembled
|
|
with the beginning of the current chunk and checked. */
|
|
if (self->utf8_code_pt_fragment_len) {
|
|
|
|
nn_assert (self->utf8_code_pt_fragment_len <
|
|
NN_SWS_UTF8_MAX_CODEPOINT_LEN);
|
|
|
|
/* Keep adding octets from fresh buffer to previous code point
|
|
fragment to check for validity. */
|
|
while (len > 0) {
|
|
self->utf8_code_pt_fragment [self->utf8_code_pt_fragment_len] = *pos;
|
|
self->utf8_code_pt_fragment_len++;
|
|
pos++;
|
|
len--;
|
|
|
|
code_point_len = nn_utf8_code_point (self->utf8_code_pt_fragment,
|
|
self->utf8_code_pt_fragment_len);
|
|
|
|
if (code_point_len > 0) {
|
|
/* Valid code point found; continue validating. */
|
|
break;
|
|
}
|
|
else if (code_point_len == NN_SWS_UTF8_INVALID) {
|
|
nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_INVALID_FRAME,
|
|
"Invalid UTF-8 code point split on previous frame.");
|
|
return;
|
|
}
|
|
else if (code_point_len == NN_SWS_UTF8_FRAGMENT) {
|
|
if (self->is_final_frame) {
|
|
nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_INVALID_FRAME,
|
|
"Truncated UTF-8 payload with invalid code point.");
|
|
return;
|
|
}
|
|
else {
|
|
/* This chunk is well-formed; now recv the next chunk. */
|
|
nn_sws_recv_hdr (self);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->utf8_code_pt_fragment_len >= NN_SWS_UTF8_MAX_CODEPOINT_LEN)
|
|
nn_assert (0);
|
|
|
|
while (len > 0) {
|
|
|
|
code_point_len = nn_utf8_code_point (pos, len);
|
|
|
|
if (code_point_len > 0) {
|
|
/* Valid code point found; continue validating. */
|
|
pos += code_point_len;
|
|
len -= code_point_len;
|
|
nn_assert (len >= 0);
|
|
continue;
|
|
}
|
|
else if (code_point_len == NN_SWS_UTF8_INVALID) {
|
|
self->utf8_code_pt_fragment_len = 0;
|
|
memset (self->utf8_code_pt_fragment, 0,
|
|
NN_SWS_UTF8_MAX_CODEPOINT_LEN);
|
|
nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_INVALID_FRAME,
|
|
"Invalid UTF-8 code point in payload.");
|
|
return;
|
|
}
|
|
else if (code_point_len == NN_SWS_UTF8_FRAGMENT) {
|
|
nn_assert (len < NN_SWS_UTF8_MAX_CODEPOINT_LEN);
|
|
self->utf8_code_pt_fragment_len = len;
|
|
memcpy (self->utf8_code_pt_fragment, pos, len);
|
|
if (self->is_final_frame) {
|
|
nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_INVALID_FRAME,
|
|
"Truncated UTF-8 payload with invalid code point.");
|
|
}
|
|
else {
|
|
/* Previous frame ended in the middle of a code point;
|
|
receive more. */
|
|
nn_sws_recv_hdr (self);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Entire buffer is well-formed. */
|
|
nn_assert (len == 0);
|
|
|
|
self->utf8_code_pt_fragment_len = 0;
|
|
memset (self->utf8_code_pt_fragment, 0, NN_SWS_UTF8_MAX_CODEPOINT_LEN);
|
|
|
|
if (self->is_final_frame) {
|
|
self->instate = NN_SWS_INSTATE_RECVD_CHUNKED;
|
|
nn_pipebase_received (&self->pipebase);
|
|
}
|
|
else {
|
|
nn_sws_recv_hdr (self);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void nn_sws_validate_close_handshake (struct nn_sws *self)
|
|
{
|
|
uint8_t *pos; uint16_t close_code; int32_t code_point_len,len;
|
|
|
|
len = (int32_t)(self->inmsg_current_chunk_len - NN_SWS_CLOSE_CODE_LEN);
|
|
pos = self->inmsg_current_chunk_buf + NN_SWS_CLOSE_CODE_LEN;
|
|
|
|
/* As per RFC 6455 7.1.6, the Close Reason following the Close Code
|
|
must be well-formed UTF-8. */
|
|
while (len > 0) {
|
|
code_point_len = nn_utf8_code_point (pos, len);
|
|
|
|
if (code_point_len > 0) {
|
|
len -= code_point_len;
|
|
pos += code_point_len;
|
|
continue;
|
|
}
|
|
else {
|
|
/* RFC 6455 7.1.6 */
|
|
nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Invalid UTF-8 sent as Close Reason.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Entire Close Reason is well-formed UTF-8 (or empty) */
|
|
nn_assert (len == 0);
|
|
|
|
close_code = nn_gets (self->inmsg_current_chunk_buf);
|
|
|
|
if (close_code == NN_SWS_CLOSE_NORMAL ||
|
|
close_code == NN_SWS_CLOSE_GOING_AWAY ||
|
|
close_code == NN_SWS_CLOSE_ERR_PROTO ||
|
|
close_code == NN_SWS_CLOSE_ERR_WUT ||
|
|
close_code == NN_SWS_CLOSE_ERR_INVALID_FRAME ||
|
|
close_code == NN_SWS_CLOSE_ERR_POLICY ||
|
|
close_code == NN_SWS_CLOSE_ERR_TOOBIG ||
|
|
close_code == NN_SWS_CLOSE_ERR_EXTENSION ||
|
|
close_code == NN_SWS_CLOSE_ERR_SERVER) {
|
|
/* RFC 6455 7.4.1 */
|
|
self->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&self->pipebase);
|
|
}
|
|
else if (close_code >= 3000 && close_code <= 3999) {
|
|
/* RFC 6455 7.4.2 */
|
|
self->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&self->pipebase);
|
|
}
|
|
else if (close_code >= 4000 && close_code <= 4999) {
|
|
/* RFC 6455 7.4.2 */
|
|
self->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&self->pipebase);
|
|
}
|
|
else {
|
|
nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Unrecognized close code.");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static int nn_sws_fail_conn (struct nn_sws *self, int code, char *reason)
|
|
{
|
|
size_t reason_len;
|
|
size_t payload_len;
|
|
uint8_t rand_mask [NN_SWS_FRAME_SIZE_MASK];
|
|
uint8_t *payload_pos;
|
|
|
|
nn_assert_state (self, NN_SWS_STATE_ACTIVE);
|
|
|
|
/* Destroy any remnant incoming message fragments. */
|
|
nn_msg_array_term (&self->inmsg_array);
|
|
|
|
reason_len = strlen (reason);
|
|
|
|
payload_len = reason_len + NN_SWS_CLOSE_CODE_LEN;
|
|
|
|
/* Ensure text is short enough to also include code and framing. */
|
|
nn_assert (payload_len <= NN_SWS_PAYLOAD_MAX_LENGTH);
|
|
|
|
/* RFC 6455 section 5.5.1. */
|
|
self->fail_msg [0] = NN_SWS_FRAME_BITMASK_FIN | NN_WS_OPCODE_CLOSE;
|
|
|
|
/* Size of the payload, which is the status code plus the reason. */
|
|
self->fail_msg [1] = payload_len;
|
|
|
|
self->fail_msg_len = NN_SWS_FRAME_SIZE_INITIAL;
|
|
|
|
if (self->mode == NN_WS_SERVER) {
|
|
self->fail_msg [1] |= NN_SWS_FRAME_BITMASK_NOT_MASKED;
|
|
}
|
|
else if (self->mode == NN_WS_CLIENT) {
|
|
self->fail_msg [1] |= NN_SWS_FRAME_BITMASK_MASKED;
|
|
|
|
/* Generate 32-bit mask as per RFC 6455 5.3. */
|
|
nn_random_generate (rand_mask, NN_SWS_FRAME_SIZE_MASK);
|
|
|
|
memcpy (&self->fail_msg [NN_SWS_FRAME_SIZE_INITIAL],
|
|
rand_mask, NN_SWS_FRAME_SIZE_MASK);
|
|
|
|
self->fail_msg_len += NN_SWS_FRAME_SIZE_MASK;
|
|
}
|
|
else {
|
|
/* Developer error. */
|
|
nn_assert (0);
|
|
}
|
|
|
|
payload_pos = (uint8_t*) (&self->fail_msg [self->fail_msg_len]);
|
|
|
|
/* Copy Status Code in network order (big-endian). */
|
|
nn_puts (payload_pos, (uint16_t) code);
|
|
self->fail_msg_len += NN_SWS_CLOSE_CODE_LEN;
|
|
|
|
/* Copy Close Reason immediately following the code. */
|
|
memcpy (payload_pos + NN_SWS_CLOSE_CODE_LEN, reason, reason_len);
|
|
|
|
/* If this is a client, apply mask. */
|
|
if (self->mode == NN_WS_CLIENT) {
|
|
nn_sws_mask_payload (payload_pos, payload_len,
|
|
rand_mask, NN_SWS_FRAME_SIZE_MASK, NULL);
|
|
}
|
|
|
|
self->fail_msg_len += payload_len;
|
|
|
|
self->instate = NN_SWS_INSTATE_FAILING;
|
|
|
|
/* On the next recv, the connection will be failed. Why defer
|
|
until the next recv? Semantically, until then, this incoming
|
|
message has not been interpreted, so it's not until then that
|
|
it could be failed. This type of pre-processing is necessary
|
|
to early fail chunked transfers. */
|
|
nn_pipebase_received (&self->pipebase);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nn_sws_shutdown (struct nn_fsm *self, int src, int type,
|
|
NN_UNUSED void *srcptr)
|
|
{
|
|
struct nn_sws *sws;
|
|
|
|
sws = nn_cont (self, struct nn_sws, fsm);
|
|
|
|
if (nn_slow (src == NN_FSM_ACTION && type == NN_FSM_STOP)) {
|
|
/* TODO: Consider sending a close code here? */
|
|
nn_pipebase_stop (&sws->pipebase);
|
|
nn_ws_handshake_stop (&sws->handshaker);
|
|
sws->state = NN_SWS_STATE_STOPPING;
|
|
}
|
|
if (nn_slow (sws->state == NN_SWS_STATE_STOPPING)) {
|
|
if (nn_ws_handshake_isidle (&sws->handshaker)) {
|
|
nn_usock_swap_owner (sws->usock, &sws->usock_owner);
|
|
sws->usock = NULL;
|
|
sws->usock_owner.src = -1;
|
|
sws->usock_owner.fsm = NULL;
|
|
sws->state = NN_SWS_STATE_IDLE;
|
|
nn_fsm_stopped (&sws->fsm, NN_SWS_RETURN_STOPPED);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
nn_fsm_bad_state (sws->state, src, type);
|
|
}
|
|
|
|
static void nn_sws_handler (struct nn_fsm *self, int src, int type,
|
|
NN_UNUSED void *srcptr)
|
|
{
|
|
struct nn_sws *sws;
|
|
int rc;
|
|
|
|
sws = nn_cont (self, struct nn_sws, fsm);
|
|
|
|
switch (sws->state) {
|
|
|
|
/******************************************************************************/
|
|
/* IDLE state. */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_IDLE:
|
|
switch (src) {
|
|
|
|
case NN_FSM_ACTION:
|
|
switch (type) {
|
|
case NN_FSM_START:
|
|
nn_ws_handshake_start (&sws->handshaker, sws->usock,
|
|
&sws->pipebase, sws->mode, sws->resource, sws->remote_host);
|
|
sws->state = NN_SWS_STATE_HANDSHAKE;
|
|
return;
|
|
default:
|
|
nn_fsm_bad_action (sws->state, src, type);
|
|
}
|
|
|
|
default:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* HANDSHAKE state. */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_HANDSHAKE:
|
|
switch (src) {
|
|
|
|
case NN_SWS_SRC_HANDSHAKE:
|
|
switch (type) {
|
|
case NN_WS_HANDSHAKE_OK:
|
|
|
|
/* Before moving to the active state stop the handshake
|
|
state machine. */
|
|
nn_ws_handshake_stop (&sws->handshaker);
|
|
sws->state = NN_SWS_STATE_STOPPING_HANDSHAKE;
|
|
return;
|
|
|
|
case NN_WS_HANDSHAKE_ERROR:
|
|
|
|
/* Raise the error and move directly to the DONE state.
|
|
ws_handshake object will be stopped later on. */
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done,
|
|
NN_SWS_RETURN_CLOSE_HANDSHAKE);
|
|
return;
|
|
|
|
default:
|
|
nn_fsm_bad_action (sws->state, src, type);
|
|
}
|
|
|
|
default:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* STOPPING_HANDSHAKE state. */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_STOPPING_HANDSHAKE:
|
|
switch (src) {
|
|
|
|
case NN_SWS_SRC_HANDSHAKE:
|
|
switch (type) {
|
|
case NN_WS_HANDSHAKE_STOPPED:
|
|
|
|
/* Start the pipe. */
|
|
rc = nn_pipebase_start (&sws->pipebase);
|
|
if (nn_slow (rc < 0)) {
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR);
|
|
return;
|
|
}
|
|
|
|
/* Start receiving a message in asynchronous manner. */
|
|
nn_sws_recv_hdr (sws);
|
|
|
|
/* Mark the pipe as available for sending. */
|
|
sws->outstate = NN_SWS_OUTSTATE_IDLE;
|
|
|
|
sws->state = NN_SWS_STATE_ACTIVE;
|
|
return;
|
|
|
|
default:
|
|
nn_fsm_bad_action (sws->state, src, type);
|
|
}
|
|
|
|
default:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* ACTIVE state. */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_ACTIVE:
|
|
switch (src) {
|
|
|
|
case NN_SWS_SRC_USOCK:
|
|
switch (type) {
|
|
case NN_USOCK_SENT:
|
|
|
|
/* The message is now fully sent. */
|
|
nn_assert (sws->outstate == NN_SWS_OUTSTATE_SENDING);
|
|
sws->outstate = NN_SWS_OUTSTATE_IDLE;
|
|
nn_msg_term (&sws->outmsg);
|
|
nn_msg_init (&sws->outmsg, 0);
|
|
nn_pipebase_sent (&sws->pipebase);
|
|
return;
|
|
|
|
case NN_USOCK_RECEIVED:
|
|
|
|
switch (sws->instate) {
|
|
case NN_SWS_INSTATE_RECV_HDR:
|
|
|
|
/* Require RSV1, RSV2, and RSV3 bits to be unset for
|
|
x-nanomsg protocol as per RFC 6455 section 5.2. */
|
|
if (sws->inhdr [0] & NN_SWS_FRAME_BITMASK_RSV1 ||
|
|
sws->inhdr [0] & NN_SWS_FRAME_BITMASK_RSV2 ||
|
|
sws->inhdr [0] & NN_SWS_FRAME_BITMASK_RSV3) {
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"RSV1, RSV2, and RSV3 must be unset.");
|
|
return;
|
|
}
|
|
|
|
sws->is_final_frame = sws->inhdr [0] &
|
|
NN_SWS_FRAME_BITMASK_FIN;
|
|
sws->masked = sws->inhdr [1] &
|
|
NN_SWS_FRAME_BITMASK_MASKED;
|
|
|
|
switch (sws->mode) {
|
|
case NN_WS_SERVER:
|
|
/* Require mask bit to be set from client. */
|
|
if (sws->masked) {
|
|
/* Continue receiving header for this frame. */
|
|
sws->ext_hdr_len = NN_SWS_FRAME_SIZE_MASK;
|
|
break;
|
|
}
|
|
else {
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Server expects MASK bit to be set.");
|
|
return;
|
|
}
|
|
case NN_WS_CLIENT:
|
|
/* Require mask bit to be unset from server. */
|
|
if (sws->masked) {
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Client expects MASK bit to be unset.");
|
|
return;
|
|
}
|
|
else {
|
|
/* Continue receiving header for this frame. */
|
|
sws->ext_hdr_len = 0;
|
|
break;
|
|
}
|
|
default:
|
|
/* Only two modes of this endpoint are expected. */
|
|
nn_assert (0);
|
|
return;
|
|
}
|
|
|
|
sws->opcode = sws->inhdr [0] &
|
|
NN_SWS_FRAME_BITMASK_OPCODE;
|
|
sws->payload_ctl = sws->inhdr [1] &
|
|
NN_SWS_FRAME_BITMASK_LENGTH;
|
|
|
|
/* Prevent unexpected continuation frame. */
|
|
if (!sws->continuing &&
|
|
sws->opcode == NN_WS_OPCODE_FRAGMENT) {
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"No message to continue.");
|
|
return;
|
|
}
|
|
|
|
/* Preserve initial message opcode and RSV bits in case
|
|
this is a fragmented message. */
|
|
if (!sws->continuing)
|
|
sws->inmsg_hdr = sws->inhdr [0] |
|
|
NN_SWS_FRAME_BITMASK_FIN;
|
|
|
|
if (sws->payload_ctl <= NN_SWS_PAYLOAD_MAX_LENGTH) {
|
|
sws->ext_hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_0;
|
|
}
|
|
else if (sws->payload_ctl == NN_SWS_PAYLOAD_FRAME_16) {
|
|
sws->ext_hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_16;
|
|
}
|
|
else if (sws->payload_ctl == NN_SWS_PAYLOAD_FRAME_63) {
|
|
sws->ext_hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_63;
|
|
}
|
|
else {
|
|
/* Developer error parsing/handling length. */
|
|
nn_assert (0);
|
|
return;
|
|
}
|
|
|
|
switch (sws->opcode) {
|
|
|
|
case NN_WS_OPCODE_TEXT:
|
|
/* Fall thru; TEXT and BINARY handled alike. */
|
|
case NN_WS_OPCODE_BINARY:
|
|
|
|
sws->is_control_frame = 0;
|
|
|
|
if (sws->continuing) {
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Expected continuation frame opcode.");
|
|
return;
|
|
}
|
|
|
|
if (!sws->is_final_frame)
|
|
sws->continuing = 1;
|
|
|
|
if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) {
|
|
/* Only a remote server could send a 2-byte msg;
|
|
sanity-check that this endpoint is a client. */
|
|
nn_assert (sws->mode == NN_WS_CLIENT);
|
|
|
|
sws->inmsg_current_chunk_len = 0;
|
|
|
|
if (sws->continuing) {
|
|
/* This frame was empty, but continue
|
|
next frame in fragmented sequence. */
|
|
nn_sws_recv_hdr (sws);
|
|
return;
|
|
}
|
|
else {
|
|
/* Special case when there is no payload,
|
|
mask, or additional frames. */
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
}
|
|
}
|
|
/* Continue to receive extended header+payload. */
|
|
break;
|
|
|
|
case NN_WS_OPCODE_FRAGMENT:
|
|
|
|
sws->is_control_frame = 0;
|
|
sws->continuing = !sws->is_final_frame;
|
|
|
|
if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) {
|
|
/* Only a remote server could send a 2-byte msg;
|
|
sanity-check that this endpoint is a client. */
|
|
nn_assert (sws->mode == NN_WS_CLIENT);
|
|
|
|
sws->inmsg_current_chunk_len = 0;
|
|
|
|
if (sws->continuing) {
|
|
/* This frame was empty, but continue
|
|
next frame in fragmented sequence. */
|
|
nn_sws_recv_hdr (sws);
|
|
return;
|
|
}
|
|
else {
|
|
/* Special case when there is no payload,
|
|
mask, or additional frames. */
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
}
|
|
}
|
|
/* Continue to receive extended header+payload. */
|
|
break;
|
|
|
|
case NN_WS_OPCODE_PING:
|
|
sws->is_control_frame = 1;
|
|
sws->pings_received++;
|
|
if (sws->payload_ctl > NN_SWS_PAYLOAD_MAX_LENGTH) {
|
|
/* As per RFC 6455 section 5.4, large payloads on
|
|
control frames is not allowed, and on receipt the
|
|
endpoint MUST close connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Control frame payload exceeds allowable length.");
|
|
return;
|
|
}
|
|
if (!sws->is_final_frame) {
|
|
/* As per RFC 6455 section 5.4, fragmentation of
|
|
control frames is not allowed; on receipt the
|
|
endpoint MUST close connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Cannot fragment control message (FIN=0).");
|
|
return;
|
|
}
|
|
|
|
if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) {
|
|
/* Special case when there is no payload,
|
|
mask, or additional frames. */
|
|
sws->inmsg_current_chunk_len = 0;
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
}
|
|
/* Continue to receive extended header+payload. */
|
|
break;
|
|
|
|
case NN_WS_OPCODE_PONG:
|
|
sws->is_control_frame = 1;
|
|
sws->pongs_received++;
|
|
if (sws->payload_ctl > NN_SWS_PAYLOAD_MAX_LENGTH) {
|
|
/* As per RFC 6455 section 5.4, large payloads on
|
|
control frames is not allowed, and on receipt the
|
|
endpoint MUST close connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Control frame payload exceeds allowable length.");
|
|
return;
|
|
}
|
|
if (!sws->is_final_frame) {
|
|
/* As per RFC 6455 section 5.4, fragmentation of
|
|
control frames is not allowed; on receipt the
|
|
endpoint MUST close connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Cannot fragment control message (FIN=0).");
|
|
return;
|
|
}
|
|
|
|
if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) {
|
|
/* Special case when there is no payload,
|
|
mask, or additional frames. */
|
|
sws->inmsg_current_chunk_len = 0;
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
}
|
|
/* Continue to receive extended header+payload. */
|
|
break;
|
|
|
|
case NN_WS_OPCODE_CLOSE:
|
|
/* RFC 6455 section 5.5.1. */
|
|
sws->is_control_frame = 1;
|
|
if (!sws->is_final_frame) {
|
|
/* As per RFC 6455 section 5.4, fragmentation of
|
|
control frames is not allowed; on receipt the
|
|
endpoint MUST close connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Cannot fragment control message (FIN=0).");
|
|
return;
|
|
}
|
|
|
|
if (sws->payload_ctl > NN_SWS_PAYLOAD_MAX_LENGTH) {
|
|
/* As per RFC 6455 section 5.4, large payloads on
|
|
control frames is not allowed, and on receipt the
|
|
endpoint MUST close connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Control frame payload exceeds allowable length.");
|
|
return;
|
|
}
|
|
|
|
if (sws->payload_ctl == 1) {
|
|
/* As per RFC 6455 section 5.5.1, if a payload is
|
|
to accompany a close frame, the first two bytes
|
|
MUST be the close code. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Expected 2byte close code.");
|
|
return;
|
|
}
|
|
|
|
if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) {
|
|
/* Special case when there is no payload,
|
|
mask, or additional frames. */
|
|
sws->inmsg_current_chunk_len = 0;
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
}
|
|
/* Continue to receive extended header+payload. */
|
|
break;
|
|
|
|
default:
|
|
/* Client sent an invalid opcode; as per RFC 6455
|
|
section 10.7, close connection with code. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Invalid opcode.");
|
|
return;
|
|
|
|
}
|
|
|
|
if (sws->ext_hdr_len == 0) {
|
|
/* Only a remote server could send a 2-byte msg;
|
|
sanity-check that this endpoint is a client. */
|
|
nn_assert (sws->mode == NN_WS_CLIENT);
|
|
|
|
/* In the case of no additional header, the payload
|
|
is known to not exceed this threshold. */
|
|
nn_assert (sws->payload_ctl <= NN_SWS_PAYLOAD_MAX_LENGTH);
|
|
|
|
/* In the case of no additional header, the payload
|
|
is known to not exceed this threshold. */
|
|
nn_assert (sws->payload_ctl > 0);
|
|
|
|
sws->instate = NN_SWS_INSTATE_RECV_PAYLOAD;
|
|
sws->inmsg_current_chunk_len = sws->payload_ctl;
|
|
|
|
|
|
/* Use scatter/gather array for application messages,
|
|
and a fixed-width buffer for control messages. This
|
|
is convenient since control messages can be
|
|
interspersed between chunked application msgs. */
|
|
if (sws->is_control_frame) {
|
|
sws->inmsg_current_chunk_buf = sws->inmsg_control;
|
|
}
|
|
else {
|
|
sws->inmsg_chunks++;
|
|
sws->inmsg_total_size += sws->inmsg_current_chunk_len;
|
|
sws->inmsg_current_chunk_buf =
|
|
nn_msg_chunk_new (sws->inmsg_current_chunk_len,
|
|
&sws->inmsg_array);
|
|
}
|
|
|
|
nn_usock_recv (sws->usock, sws->inmsg_current_chunk_buf,
|
|
sws->inmsg_current_chunk_len, NULL);
|
|
return;
|
|
}
|
|
else {
|
|
/* Continue receiving the rest of the header frame. */
|
|
sws->instate = NN_SWS_INSTATE_RECV_HDREXT;
|
|
nn_usock_recv (sws->usock,
|
|
sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL,
|
|
sws->ext_hdr_len,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
case NN_SWS_INSTATE_RECV_HDREXT:
|
|
nn_assert (sws->ext_hdr_len > 0);
|
|
|
|
if (sws->payload_ctl <= NN_SWS_PAYLOAD_MAX_LENGTH) {
|
|
sws->inmsg_current_chunk_len = sws->payload_ctl;
|
|
if (sws->masked) {
|
|
sws->mask = sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL;
|
|
}
|
|
else {
|
|
sws->mask = NULL;
|
|
}
|
|
}
|
|
else if (sws->payload_ctl == NN_SWS_PAYLOAD_FRAME_16) {
|
|
sws->inmsg_current_chunk_len =
|
|
nn_gets (sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL);
|
|
if (sws->masked) {
|
|
sws->mask = sws->inhdr +
|
|
NN_SWS_FRAME_SIZE_INITIAL +
|
|
NN_SWS_FRAME_SIZE_PAYLOAD_16;
|
|
}
|
|
else {
|
|
sws->mask = NULL;
|
|
}
|
|
}
|
|
else if (sws->payload_ctl == NN_SWS_PAYLOAD_FRAME_63) {
|
|
sws->inmsg_current_chunk_len =
|
|
(size_t) nn_getll (sws->inhdr +
|
|
NN_SWS_FRAME_SIZE_INITIAL);
|
|
if (sws->masked) {
|
|
sws->mask = sws->inhdr +
|
|
NN_SWS_FRAME_SIZE_INITIAL +
|
|
NN_SWS_FRAME_SIZE_PAYLOAD_63;
|
|
}
|
|
else {
|
|
sws->mask = NULL;
|
|
}
|
|
}
|
|
else {
|
|
/* Client sent invalid data; as per RFC 6455,
|
|
server closes the connection immediately. */
|
|
nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO,
|
|
"Invalid payload length.");
|
|
return;
|
|
}
|
|
|
|
/* Handle zero-length message bodies. */
|
|
if (sws->inmsg_current_chunk_len == 0)
|
|
{
|
|
if (sws->is_final_frame) {
|
|
sws->instate = (sws->is_control_frame ?
|
|
NN_SWS_INSTATE_RECVD_CONTROL :
|
|
NN_SWS_INSTATE_RECVD_CHUNKED);
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
}
|
|
else {
|
|
nn_sws_recv_hdr (sws);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nn_assert (sws->inmsg_current_chunk_len > 0);
|
|
|
|
/* Use scatter/gather array for application messages,
|
|
and a fixed-width buffer for control messages. This
|
|
is convenient since control messages can be
|
|
interspersed between chunked application msgs. */
|
|
if (sws->is_control_frame) {
|
|
sws->inmsg_current_chunk_buf = sws->inmsg_control;
|
|
}
|
|
else {
|
|
sws->inmsg_chunks++;
|
|
sws->inmsg_total_size += sws->inmsg_current_chunk_len;
|
|
sws->inmsg_current_chunk_buf =
|
|
nn_msg_chunk_new (sws->inmsg_current_chunk_len,
|
|
&sws->inmsg_array);
|
|
}
|
|
|
|
sws->instate = NN_SWS_INSTATE_RECV_PAYLOAD;
|
|
nn_usock_recv (sws->usock, sws->inmsg_current_chunk_buf,
|
|
sws->inmsg_current_chunk_len, NULL);
|
|
return;
|
|
|
|
case NN_SWS_INSTATE_RECV_PAYLOAD:
|
|
|
|
/* Unmask if necessary. */
|
|
if (sws->masked) {
|
|
nn_sws_mask_payload (sws->inmsg_current_chunk_buf,
|
|
sws->inmsg_current_chunk_len, sws->mask,
|
|
NN_SWS_FRAME_SIZE_MASK, NULL);
|
|
}
|
|
|
|
switch (sws->opcode) {
|
|
|
|
case NN_WS_OPCODE_TEXT:
|
|
nn_sws_validate_utf8_chunk (sws);
|
|
return;
|
|
|
|
case NN_WS_OPCODE_BINARY:
|
|
if (sws->is_final_frame) {
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
}
|
|
else {
|
|
nn_sws_recv_hdr (sws);
|
|
}
|
|
return;
|
|
|
|
case NN_WS_OPCODE_FRAGMENT:
|
|
/* Must check original opcode to see if this fragment
|
|
needs UTF-8 validation. */
|
|
if ((sws->inmsg_hdr & NN_SWS_FRAME_BITMASK_OPCODE) ==
|
|
NN_WS_OPCODE_TEXT) {
|
|
nn_sws_validate_utf8_chunk (sws);
|
|
}
|
|
else if (sws->is_final_frame) {
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
}
|
|
else {
|
|
nn_sws_recv_hdr (sws);
|
|
}
|
|
return;
|
|
|
|
case NN_WS_OPCODE_PING:
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
|
|
case NN_WS_OPCODE_PONG:
|
|
sws->instate = NN_SWS_INSTATE_RECVD_CONTROL;
|
|
nn_pipebase_received (&sws->pipebase);
|
|
return;
|
|
|
|
case NN_WS_OPCODE_CLOSE:
|
|
/* If the payload is not even long enough for the
|
|
required 2-octet Close Code, the connection
|
|
should have been failed upstream. */
|
|
nn_assert (sws->inmsg_current_chunk_len >=
|
|
NN_SWS_CLOSE_CODE_LEN);
|
|
|
|
nn_sws_validate_close_handshake (sws);
|
|
return;
|
|
|
|
default:
|
|
/* This should have been prevented upstream. */
|
|
nn_assert (0);
|
|
return;
|
|
}
|
|
|
|
default:
|
|
nn_fsm_error ("Unexpected socket instate",
|
|
sws->state, src, type);
|
|
}
|
|
|
|
case NN_USOCK_SHUTDOWN:
|
|
nn_pipebase_stop (&sws->pipebase);
|
|
sws->state = NN_SWS_STATE_BROKEN_CONNECTION;
|
|
return;
|
|
|
|
case NN_USOCK_ERROR:
|
|
nn_pipebase_stop (&sws->pipebase);
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR);
|
|
return;
|
|
|
|
default:
|
|
nn_fsm_bad_action (sws->state, src, type);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* CLOSING_CONNECTION state. */
|
|
/* Wait for acknowledgement closing handshake was successfully sent. */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_CLOSING_CONNECTION:
|
|
switch (src) {
|
|
|
|
case NN_SWS_SRC_USOCK:
|
|
switch (type) {
|
|
case NN_USOCK_SENT:
|
|
/* Wait for acknowledgement closing handshake was sent
|
|
to peer. */
|
|
nn_assert (sws->outstate == NN_SWS_OUTSTATE_SENDING);
|
|
sws->outstate = NN_SWS_OUTSTATE_IDLE;
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done,
|
|
NN_SWS_RETURN_CLOSE_HANDSHAKE);
|
|
return;
|
|
case NN_USOCK_SHUTDOWN:
|
|
return;
|
|
case NN_USOCK_ERROR:
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR);
|
|
return;
|
|
default:
|
|
nn_fsm_bad_action (sws->state, src, type);
|
|
}
|
|
|
|
default:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* SHUTTING_DOWN state. */
|
|
/* The underlying connection is closed. We are just waiting that underlying */
|
|
/* usock being closed */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_BROKEN_CONNECTION:
|
|
switch (src) {
|
|
|
|
case NN_SWS_SRC_USOCK:
|
|
switch (type) {
|
|
case NN_USOCK_ERROR:
|
|
sws->state = NN_SWS_STATE_DONE;
|
|
nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR);
|
|
return;
|
|
default:
|
|
nn_fsm_bad_action (sws->state, src, type);
|
|
}
|
|
|
|
default:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* DONE state. */
|
|
/* The underlying connection is closed. There's nothing that can be done in */
|
|
/* this state except stopping the object. */
|
|
/******************************************************************************/
|
|
case NN_SWS_STATE_DONE:
|
|
nn_fsm_bad_source (sws->state, src, type);
|
|
|
|
/******************************************************************************/
|
|
/* Invalid state. */
|
|
/******************************************************************************/
|
|
default:
|
|
nn_fsm_bad_state (sws->state, src, type);
|
|
}
|
|
}
|
|
|