| /* |
| Copyright (c) 2013 250bpm s.r.o. All rights reserved. |
| Copyright (c) 2014-2016 Jack R. Dunaway. All rights reserved. |
| Copyright 2016 Garrett D'Amore <garrett@damore.org> |
| |
| 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/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 0x08 |
| #define NN_WS_OPCODE_PING 0x09 |
| #define NN_WS_OPCODE_PONG 0x0A |
| #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 void 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_acknowledge_close_handshake (struct nn_sws *self); |
| |
| void nn_sws_init (struct nn_sws *self, int src, |
| struct nn_ep *ep, 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, ep); |
| 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, uint8_t msg_type) |
| { |
| /* 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; |
| |
| self->msg_type = msg_type; |
| |
| /* 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); |
| } |
| |
| /* Given a buffer location, this function determines whether the leading |
| octets form a valid UTF-8 code point. */ |
| static 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_MSG_TYPE) |
| break; |
| cmsg = NN_CMSG_NXTHDR (&msghdr, cmsg); |
| } |
| } |
| |
| /* If the header does not specify an opcode, take default from option. */ |
| if (cmsg) |
| sws->outhdr [0] = *(uint8_t *) NN_CMSG_DATA (cmsg); |
| else |
| sws->outhdr [0] = sws->msg_type; |
| |
| /* 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; |
| |
| return 0; |
| } |
| |
| static int nn_sws_recv (struct nn_pipebase *self, struct nn_msg *msg) |
| { |
| struct nn_sws *sws; |
| struct nn_list_item *it; |
| struct msg_chunk *ch; |
| struct nn_cmsghdr *cmsg; |
| uint8_t opcode_hdr; |
| uint8_t opcode; |
| size_t cmsgsz; |
| size_t pos; |
| |
| sws = nn_cont (self, struct nn_sws, pipebase); |
| |
| nn_assert_state (sws, NN_SWS_STATE_ACTIVE); |
| |
| switch (sws->instate) { |
| case NN_SWS_INSTATE_RECVD_CHUNKED: |
| |
| /* Relay opcode to the user in order to interpret payload. */ |
| opcode_hdr = sws->inmsg_hdr; |
| |
| /* 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_assert (opcode_hdr & NN_SWS_FRAME_BITMASK_FIN); |
| opcode_hdr &= ~NN_SWS_FRAME_BITMASK_FIN; |
| |
| /* The library is expected to have failed any connections with other |
| opcodes; these are the only two opcodes that can be chunked. */ |
| opcode = opcode_hdr & NN_SWS_FRAME_BITMASK_OPCODE; |
| nn_assert (opcode == NN_WS_OPCODE_BINARY || |
| opcode == NN_WS_OPCODE_TEXT); |
| |
| nn_msg_init (msg, sws->inmsg_total_size); |
| |
| 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: |
| |
| /* Relay opcode to the user in order to interpret payload. */ |
| opcode_hdr = sws->inhdr [0]; |
| |
| /* 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_assert (opcode_hdr & NN_SWS_FRAME_BITMASK_FIN); |
| opcode_hdr &= ~NN_SWS_FRAME_BITMASK_FIN; |
| |
| /* The library is expected to have failed any connections with other |
| opcodes; these are the only two control opcodes delivered. */ |
| opcode = opcode_hdr & NN_SWS_FRAME_BITMASK_OPCODE; |
| nn_assert (opcode == NN_WS_OPCODE_PING || |
| opcode == NN_WS_OPCODE_PONG); |
| |
| nn_msg_init (msg, sws->inmsg_current_chunk_len); |
| |
| memcpy (((uint8_t*) nn_chunkref_data (&msg->body)), |
| sws->inmsg_control, sws->inmsg_current_chunk_len); |
| |
| 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_MSG_TYPE; |
| 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; |
| int code_point_len; |
| size_t len; |
| |
| len = 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. */ |
| nn_assert (len >= (size_t) code_point_len); |
| len -= code_point_len; |
| pos += code_point_len; |
| 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_acknowledge_close_handshake (struct nn_sws *self) |
| { |
| uint8_t *pos; |
| uint16_t close_code; |
| int code_point_len; |
| size_t len; |
| |
| len = self->inmsg_current_chunk_len; |
| pos = self->inmsg_current_chunk_buf; |
| |
| /* Peer did not provide a Close Code, so choose our own here. */ |
| if (len == 0) { |
| nn_sws_fail_conn (self, NN_SWS_CLOSE_NORMAL, ""); |
| return; |
| } |
| |
| /* If the payload is not even long enough for the required 2-octet |
| Close Code, the connection should have already been failed. */ |
| nn_assert (len >= NN_SWS_CLOSE_CODE_LEN); |
| len -= NN_SWS_CLOSE_CODE_LEN; |
| pos += 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) { |
| /* Valid code point found; continue validating. */ |
| nn_assert (len >= (size_t) code_point_len); |
| 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 || |
| (close_code >= 3000 && close_code <= 3999) || |
| (close_code >= 4000 && close_code <= 4999)) { |
| /* Repeat close code, per RFC 6455 7.4.1 and 7.4.2 */ |
| nn_sws_fail_conn (self, (int) close_code, ""); |
| } |
| else { |
| nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_PROTO, |
| "Unrecognized close code."); |
| } |
| |
| return; |
| } |
| |
| static void 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; |
| struct nn_iovec iov; |
| |
| nn_assert_state (self, NN_SWS_STATE_ACTIVE); |
| |
| /* Stop user send/recv actions. */ |
| self->instate = NN_SWS_INSTATE_CLOSED; |
| nn_pipebase_stop (&self->pipebase); |
| |
| /* 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] = (char)(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] = (char)payload_len; |
| |
| self->fail_msg_len = NN_SWS_FRAME_SIZE_INITIAL; |
| |
| switch (self->mode) { |
| case NN_WS_SERVER: |
| self->fail_msg [1] |= NN_SWS_FRAME_BITMASK_NOT_MASKED; |
| break; |
| case 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; |
| break; |
| default: |
| /* 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); |
| self->fail_msg_len += 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); |
| } |
| |
| |
| if (self->outstate == NN_SWS_OUTSTATE_IDLE) { |
| iov.iov_base = self->fail_msg; |
| iov.iov_len = self->fail_msg_len; |
| nn_usock_send (self->usock, &iov, 1); |
| self->outstate = NN_SWS_OUTSTATE_SENDING; |
| self->state = NN_SWS_STATE_CLOSING_CONNECTION; |
| } else { |
| self->state = NN_SWS_STATE_DONE; |
| nn_fsm_raise (&self->fsm, &self->done, NN_SWS_RETURN_CLOSE_HANDSHAKE); |
| } |
| |
| return; |
| } |
| |
| 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; |
| int opt; |
| size_t opt_sz = sizeof (opt); |
| |
| 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; |
| nn_sws_acknowledge_close_handshake (sws); |
| 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 be within these bounds. */ |
| nn_assert (0 < sws->payload_ctl && |
| sws->payload_ctl <= NN_SWS_PAYLOAD_MAX_LENGTH); |
| |
| 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_total_size += sws->inmsg_current_chunk_len; |
| /* Protect non-control messages against the |
| NN_RCVMAXSIZE threshold; control messages already |
| have a small pre-allocated buffer, and therefore |
| are not subject to this limit. */ |
| nn_pipebase_getopt (&sws->pipebase, NN_SOL_SOCKET, |
| NN_RCVMAXSIZE, &opt, &opt_sz); |
| if (opt >= 0 && sws->inmsg_total_size > (size_t) opt) { |
| nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_TOOBIG, |
| "Message larger than application allows."); |
| return; |
| } |
| sws->inmsg_chunks++; |
| 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; |
| } |
| 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) { |
| if (sws->opcode == NN_WS_OPCODE_CLOSE) { |
| nn_sws_acknowledge_close_handshake (sws); |
| } |
| else { |
| sws->instate = (sws->is_control_frame ? |
| NN_SWS_INSTATE_RECVD_CONTROL : |
| NN_SWS_INSTATE_RECVD_CHUNKED); |
| nn_pipebase_received (&sws->pipebase); |
| } |
| } |
| 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_total_size += sws->inmsg_current_chunk_len; |
| /* Protect non-control messages against the |
| NN_RCVMAXSIZE threshold; control messages already |
| have a small pre-allocated buffer, and therefore |
| are not subject to this limit. */ |
| nn_pipebase_getopt (&sws->pipebase, NN_SOL_SOCKET, |
| NN_RCVMAXSIZE, &opt, &opt_sz); |
| if (opt >= 0 && sws->inmsg_total_size > (size_t) opt) { |
| nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_TOOBIG, |
| "Message size exceeds limit."); |
| return; |
| } |
| sws->inmsg_chunks++; |
| 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: |
| nn_sws_acknowledge_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); |
| } |
| } |