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.
1364 lines
51 KiB
1364 lines
51 KiB
9 years ago
|
/*
|
||
|
Copyright (c) 2013 250bpm s.r.o. All rights reserved.
|
||
|
Copyright (c) 2014 Wirebird Labs LLC. All rights reserved.
|
||
|
Copyright 2015 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 "ws_handshake.h"
|
||
|
#include "sha1.h"
|
||
|
|
||
|
#include "../../aio/timer.h"
|
||
|
|
||
|
#include "../../core/sock.h"
|
||
|
|
||
|
#include "../utils/base64.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"
|
||
|
|
||
|
#include <stddef.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/*** BEGIN undesirable dependency *******************************************/
|
||
|
/*****************************************************************************/
|
||
|
/* TODO: A transport should be SP agnostic; alas, these includes are */
|
||
|
/* required for the map. Ideally, this map would live in another */
|
||
|
/* abstraction layer; perhaps a "registry" of Scalability Protocols? */
|
||
|
/*****************************************************************************/
|
||
|
#include "../../pair.h"
|
||
|
#include "../../reqrep.h"
|
||
|
#include "../../pubsub.h"
|
||
|
#include "../../survey.h"
|
||
|
#include "../../pipeline.h"
|
||
|
#include "../../bus.h"
|
||
|
|
||
|
static const struct nn_ws_sp_map NN_WS_HANDSHAKE_SP_MAP[] = {
|
||
|
{ NN_PAIR, NN_PAIR, "pair.sp.nanomsg.org" },
|
||
|
{ NN_REQ, NN_REP, "req.sp.nanomsg.org" },
|
||
|
{ NN_REP, NN_REQ, "rep.sp.nanomsg.org" },
|
||
|
{ NN_PUB, NN_SUB, "pub.sp.nanomsg.org" },
|
||
|
{ NN_SUB, NN_PUB, "sub.sp.nanomsg.org" },
|
||
|
{ NN_SURVEYOR, NN_RESPONDENT, "surveyor.sp.nanomsg.org" },
|
||
|
{ NN_RESPONDENT, NN_SURVEYOR, "respondent.sp.nanomsg.org" },
|
||
|
{ NN_PUSH, NN_PULL, "push.sp.nanomsg.org" },
|
||
|
{ NN_PULL, NN_PUSH, "pull.sp.nanomsg.org" },
|
||
|
{ NN_BUS, NN_BUS, "bus.sp.nanomsg.org" }
|
||
|
};
|
||
|
|
||
|
const size_t NN_WS_HANDSHAKE_SP_MAP_LEN = sizeof (NN_WS_HANDSHAKE_SP_MAP) /
|
||
|
sizeof (NN_WS_HANDSHAKE_SP_MAP [0]);
|
||
|
/*****************************************************************************/
|
||
|
/*** END undesirable dependency *********************************************/
|
||
|
/*****************************************************************************/
|
||
|
|
||
|
/* State machine finite states. */
|
||
|
#define NN_WS_HANDSHAKE_STATE_IDLE 1
|
||
|
#define NN_WS_HANDSHAKE_STATE_SERVER_RECV 2
|
||
|
#define NN_WS_HANDSHAKE_STATE_SERVER_REPLY 3
|
||
|
#define NN_WS_HANDSHAKE_STATE_CLIENT_SEND 4
|
||
|
#define NN_WS_HANDSHAKE_STATE_CLIENT_RECV 5
|
||
|
#define NN_WS_HANDSHAKE_STATE_HANDSHAKE_SENT 6
|
||
|
#define NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR 7
|
||
|
#define NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_DONE 8
|
||
|
#define NN_WS_HANDSHAKE_STATE_DONE 9
|
||
|
#define NN_WS_HANDSHAKE_STATE_STOPPING 10
|
||
|
|
||
|
/* Subordinate srcptr objects. */
|
||
|
#define NN_WS_HANDSHAKE_SRC_USOCK 1
|
||
|
#define NN_WS_HANDSHAKE_SRC_TIMER 2
|
||
|
|
||
|
/* Time allowed to complete handshake. */
|
||
|
#define NN_WS_HANDSHAKE_TIMEOUT 5000
|
||
|
|
||
|
/* Possible return codes internal to the parsing operations. */
|
||
|
#define NN_WS_HANDSHAKE_NOMATCH 0
|
||
|
#define NN_WS_HANDSHAKE_MATCH 1
|
||
|
|
||
|
/* Possible return codes from parsing opening handshake from peer. */
|
||
|
#define NN_WS_HANDSHAKE_VALID 0
|
||
|
#define NN_WS_HANDSHAKE_RECV_MORE 1
|
||
|
#define NN_WS_HANDSHAKE_INVALID -1
|
||
|
|
||
|
/* Possible handshake responses to send to client when acting as server. */
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_NULL -1
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_OK 0
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_TOO_BIG 1
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_UNUSED2 2
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_WSPROTO 3
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_WSVERSION 4
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_NNPROTO 5
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_NOTPEER 6
|
||
|
#define NN_WS_HANDSHAKE_RESPONSE_UNKNOWNTYPE 7
|
||
|
|
||
|
/* Private functions. */
|
||
|
static void nn_ws_handshake_handler (struct nn_fsm *self, int src, int type,
|
||
|
void *srcptr);
|
||
|
static void nn_ws_handshake_shutdown (struct nn_fsm *self, int src, int type,
|
||
|
void *srcptr);
|
||
|
static void nn_ws_handshake_leave (struct nn_ws_handshake *self, int rc);
|
||
|
|
||
|
/* WebSocket protocol support functions. */
|
||
|
static int nn_ws_handshake_parse_client_opening (struct nn_ws_handshake *self);
|
||
|
static void nn_ws_handshake_server_reply (struct nn_ws_handshake *self);
|
||
|
static void nn_ws_handshake_client_request (struct nn_ws_handshake *self);
|
||
|
static int nn_ws_handshake_parse_server_response (struct nn_ws_handshake *self);
|
||
|
static int nn_ws_handshake_hash_key (const char *key, size_t key_len,
|
||
|
char *hashed, size_t hashed_len);
|
||
|
|
||
|
/* String parsing support functions. */
|
||
|
|
||
|
/* Scans for reference token against subject string, optionally ignoring
|
||
|
case sensitivity and/or leading spaces in subject. On match, advances
|
||
|
the subject pointer to the next non-ignored character past match. Both
|
||
|
strings must be NULL terminated to avoid undefined behavior. Returns
|
||
|
NN_WS_HANDSHAKE_MATCH on match; else, NN_WS_HANDSHAKE_NOMATCH. */
|
||
|
static int nn_ws_match_token (const char* token, const char **subj,
|
||
|
int case_insensitive, int ignore_leading_sp);
|
||
|
|
||
|
/* Scans subject string for termination sequence, optionally ignoring
|
||
|
leading and/or trailing spaces in subject. On match, advances
|
||
|
the subject pointer to the next character past match. Both
|
||
|
strings must be NULL terminated to avoid undefined behavior. If the
|
||
|
match succeeds, values are stored into *addr and *len. */
|
||
|
static int nn_ws_match_value (const char* termseq, const char **subj,
|
||
|
int ignore_leading_sp, int ignore_trailing_sp, const char **addr,
|
||
|
size_t* const len);
|
||
|
|
||
|
/* Compares subject octet stream to expected value, optionally ignoring
|
||
|
case sensitivity. Returns non-zero on success, zero on failure. */
|
||
|
static int nn_ws_validate_value (const char* expected, const char *subj,
|
||
|
size_t subj_len, int case_insensitive);
|
||
|
|
||
|
void nn_ws_handshake_init (struct nn_ws_handshake *self, int src,
|
||
|
struct nn_fsm *owner)
|
||
|
{
|
||
|
nn_fsm_init (&self->fsm, nn_ws_handshake_handler, nn_ws_handshake_shutdown,
|
||
|
src, self, owner);
|
||
|
self->state = NN_WS_HANDSHAKE_STATE_IDLE;
|
||
|
nn_timer_init (&self->timer, NN_WS_HANDSHAKE_SRC_TIMER, &self->fsm);
|
||
|
nn_fsm_event_init (&self->done);
|
||
|
self->timeout = NN_WS_HANDSHAKE_TIMEOUT;
|
||
|
self->usock = NULL;
|
||
|
self->usock_owner.src = -1;
|
||
|
self->usock_owner.fsm = NULL;
|
||
|
self->pipebase = NULL;
|
||
|
}
|
||
|
|
||
|
void nn_ws_handshake_term (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
nn_assert_state (self, NN_WS_HANDSHAKE_STATE_IDLE);
|
||
|
|
||
|
nn_fsm_event_term (&self->done);
|
||
|
nn_timer_term (&self->timer);
|
||
|
nn_fsm_term (&self->fsm);
|
||
|
}
|
||
|
|
||
|
int nn_ws_handshake_isidle (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
return nn_fsm_isidle (&self->fsm);
|
||
|
}
|
||
|
|
||
|
void nn_ws_handshake_start (struct nn_ws_handshake *self,
|
||
|
struct nn_usock *usock, struct nn_pipebase *pipebase,
|
||
|
int mode, const char *resource, const char *host)
|
||
|
{
|
||
|
/* It's expected this resource has been allocated during intial connect. */
|
||
|
if (mode == NN_WS_CLIENT)
|
||
|
nn_assert (strlen (resource) >= 1);
|
||
|
|
||
|
/* Take ownership of the underlying socket. */
|
||
|
nn_assert (self->usock == NULL && self->usock_owner.fsm == NULL);
|
||
|
self->usock_owner.src = NN_WS_HANDSHAKE_SRC_USOCK;
|
||
|
self->usock_owner.fsm = &self->fsm;
|
||
|
nn_usock_swap_owner (usock, &self->usock_owner);
|
||
|
self->usock = usock;
|
||
|
self->pipebase = pipebase;
|
||
|
self->mode = mode;
|
||
|
self->resource = resource;
|
||
|
self->remote_host = host;
|
||
|
|
||
|
memset (self->opening_hs, 0, sizeof (self->opening_hs));
|
||
|
memset (self->response, 0, sizeof (self->response));
|
||
|
|
||
|
self->recv_pos = 0;
|
||
|
self->retries = 0;
|
||
|
|
||
|
/* Calculate the absolute minimum length possible for a valid opening
|
||
|
handshake. This is an optimization since we must poll for the
|
||
|
remainder of the opening handshake in small byte chunks. */
|
||
|
switch (self->mode) {
|
||
|
case NN_WS_SERVER:
|
||
|
self->recv_len = strlen (
|
||
|
"GET x HTTP/1.1\r\n"
|
||
|
"Upgrade: websocket\r\n"
|
||
|
"Connection: Upgrade\r\n"
|
||
|
"Host: x\r\n"
|
||
|
"Origin: x\r\n"
|
||
|
"Sec-WebSocket-Key: xxxxxxxxxxxxxxxxxxxxxxxx\r\n"
|
||
|
"Sec-WebSocket-Version: xx\r\n\r\n");
|
||
|
break;
|
||
|
case NN_WS_CLIENT:
|
||
|
/* Shortest conceiveable response from server is a terse status. */
|
||
|
self->recv_len = strlen ("HTTP/1.1 xxx\r\n\r\n");
|
||
|
break;
|
||
|
default:
|
||
|
/* Developer error; unexpected mode. */
|
||
|
nn_assert (0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Launch the state machine. */
|
||
|
nn_fsm_start (&self->fsm);
|
||
|
}
|
||
|
|
||
|
void nn_ws_handshake_stop (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
nn_fsm_stop (&self->fsm);
|
||
|
}
|
||
|
|
||
|
static void nn_ws_handshake_shutdown (struct nn_fsm *self, int src, int type,
|
||
|
NN_UNUSED void *srcptr)
|
||
|
{
|
||
|
struct nn_ws_handshake *handshaker;
|
||
|
|
||
|
handshaker = nn_cont (self, struct nn_ws_handshake, fsm);
|
||
|
|
||
|
if (nn_slow (src == NN_FSM_ACTION && type == NN_FSM_STOP)) {
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING;
|
||
|
}
|
||
|
if (nn_slow (handshaker->state == NN_WS_HANDSHAKE_STATE_STOPPING)) {
|
||
|
if (!nn_timer_isidle (&handshaker->timer))
|
||
|
return;
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_IDLE;
|
||
|
nn_fsm_stopped (&handshaker->fsm, NN_WS_HANDSHAKE_STOPPED);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
nn_fsm_bad_state (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
static int nn_ws_match_token (const char* token, const char **subj,
|
||
|
int case_insensitive, int ignore_leading_sp)
|
||
|
{
|
||
|
const char *pos;
|
||
|
|
||
|
nn_assert (token && *subj);
|
||
|
|
||
|
pos = *subj;
|
||
|
|
||
|
if (ignore_leading_sp) {
|
||
|
while (*pos == '\x20' && *pos) {
|
||
|
pos++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (case_insensitive) {
|
||
|
while (*token && *pos) {
|
||
|
if (tolower ((uint32_t)*token) != tolower ((uint32_t)*pos))
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
token++;
|
||
|
pos++;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
while (*token && *pos) {
|
||
|
if (*token != *pos)
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
token++;
|
||
|
pos++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Encountered end of subject before matching completed. */
|
||
|
if (!*pos && *token)
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
|
||
|
/* Entire token has been matched. */
|
||
|
nn_assert (!*token);
|
||
|
|
||
|
/* On success, advance subject position. */
|
||
|
*subj = pos;
|
||
|
|
||
|
return NN_WS_HANDSHAKE_MATCH;
|
||
|
}
|
||
|
|
||
|
static int nn_ws_match_value (const char* termseq, const char **subj,
|
||
|
int ignore_leading_sp, int ignore_trailing_sp, const char **addr,
|
||
|
size_t* const len)
|
||
|
{
|
||
|
const char *start;
|
||
|
const char *end;
|
||
|
|
||
|
nn_assert (termseq && *subj);
|
||
|
|
||
|
start = *subj;
|
||
|
if (addr)
|
||
|
*addr = NULL;
|
||
|
if (len)
|
||
|
*len = 0;
|
||
|
|
||
|
/* Find first occurence of termination sequence. */
|
||
|
end = strstr (start, termseq);
|
||
|
|
||
|
/* Was a termination sequence found? */
|
||
|
if (end) {
|
||
|
*subj = end + strlen (termseq);
|
||
|
}
|
||
|
else {
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
}
|
||
|
|
||
|
if (ignore_leading_sp) {
|
||
|
while (*start == '\x20' && start < end) {
|
||
|
start++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (addr)
|
||
|
*addr = start;
|
||
|
|
||
|
/* In this special case, the value was "found", but is just empty or
|
||
|
ignored space. */
|
||
|
if (start == end)
|
||
|
return NN_WS_HANDSHAKE_MATCH;
|
||
|
|
||
|
if (ignore_trailing_sp) {
|
||
|
while (*(end - 1) == '\x20' && start < end) {
|
||
|
end--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (len)
|
||
|
*len = end - start;
|
||
|
|
||
|
return NN_WS_HANDSHAKE_MATCH;
|
||
|
}
|
||
|
|
||
|
static int nn_ws_validate_value (const char* expected, const char *subj,
|
||
|
size_t subj_len, int case_insensitive)
|
||
|
{
|
||
|
if (strlen (expected) != subj_len)
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
|
||
|
if (case_insensitive) {
|
||
|
while (*expected && *subj) {
|
||
|
if (tolower ((uint32_t)*expected) != tolower ((uint32_t)*subj))
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
expected++;
|
||
|
subj++;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
while (*expected && *subj) {
|
||
|
if (*expected != *subj)
|
||
|
return NN_WS_HANDSHAKE_NOMATCH;
|
||
|
expected++;
|
||
|
subj++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NN_WS_HANDSHAKE_MATCH;
|
||
|
}
|
||
|
|
||
|
static void nn_ws_handshake_handler (struct nn_fsm *self, int src, int type,
|
||
|
NN_UNUSED void *srcptr)
|
||
|
{
|
||
|
struct nn_ws_handshake *handshaker;
|
||
|
|
||
|
unsigned i;
|
||
|
|
||
|
handshaker = nn_cont (self, struct nn_ws_handshake, fsm);
|
||
|
|
||
|
switch (handshaker->state) {
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* IDLE state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_IDLE:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_FSM_ACTION:
|
||
|
switch (type) {
|
||
|
case NN_FSM_START:
|
||
|
nn_assert (handshaker->recv_pos == 0);
|
||
|
nn_assert (handshaker->recv_len >= NN_WS_HANDSHAKE_TERMSEQ_LEN);
|
||
|
|
||
|
nn_timer_start (&handshaker->timer, handshaker->timeout);
|
||
|
|
||
|
switch (handshaker->mode) {
|
||
|
case NN_WS_CLIENT:
|
||
|
/* Send opening handshake to server. */
|
||
|
nn_assert (handshaker->recv_len <=
|
||
|
sizeof (handshaker->response));
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_CLIENT_SEND;
|
||
|
nn_ws_handshake_client_request (handshaker);
|
||
|
return;
|
||
|
case NN_WS_SERVER:
|
||
|
/* Begin receiving opening handshake from client. */
|
||
|
nn_assert (handshaker->recv_len <=
|
||
|
sizeof (handshaker->opening_hs));
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_SERVER_RECV;
|
||
|
nn_usock_recv (handshaker->usock, handshaker->opening_hs,
|
||
|
handshaker->recv_len, NULL);
|
||
|
return;
|
||
|
default:
|
||
|
/* Unexpected mode. */
|
||
|
nn_assert (0);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* SERVER_RECV state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_SERVER_RECV:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
switch (type) {
|
||
|
case NN_USOCK_RECEIVED:
|
||
|
/* Parse bytes received thus far. */
|
||
|
switch (nn_ws_handshake_parse_client_opening (handshaker)) {
|
||
|
case NN_WS_HANDSHAKE_INVALID:
|
||
|
/* Opening handshake parsed successfully but does not
|
||
|
contain valid values. Respond failure to client. */
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_SERVER_REPLY;
|
||
|
nn_ws_handshake_server_reply (handshaker);
|
||
|
return;
|
||
|
case NN_WS_HANDSHAKE_VALID:
|
||
|
/* Opening handshake parsed successfully, and is valid.
|
||
|
Respond success to client. */
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_SERVER_REPLY;
|
||
|
nn_ws_handshake_server_reply (handshaker);
|
||
|
return;
|
||
|
case NN_WS_HANDSHAKE_RECV_MORE:
|
||
|
/* Not enough bytes have been received to determine
|
||
|
validity; remain in the receive state, and retrieve
|
||
|
more bytes from client. */
|
||
|
handshaker->recv_pos += handshaker->recv_len;
|
||
|
|
||
|
/* Validate the previous recv operation. */
|
||
|
nn_assert (handshaker->recv_pos <
|
||
|
sizeof (handshaker->opening_hs));
|
||
|
|
||
|
/* Ensure we can back-track at least the length of the
|
||
|
termination sequence to determine how many bytes to
|
||
|
receive on the next retry. This is an assertion, not
|
||
|
a conditional, since under no condition is it
|
||
|
necessary to initially receive so few bytes. */
|
||
|
nn_assert (handshaker->recv_pos >=
|
||
|
(int) NN_WS_HANDSHAKE_TERMSEQ_LEN);
|
||
|
|
||
|
/* We only compare if we have at least one byte to
|
||
|
compare against. When i drops to zero, it means
|
||
|
we don't have any bytes to match against, and it is
|
||
|
automatically true. */
|
||
|
for (i = NN_WS_HANDSHAKE_TERMSEQ_LEN; i > 0; i--) {
|
||
|
if (memcmp (NN_WS_HANDSHAKE_TERMSEQ,
|
||
|
handshaker->opening_hs + handshaker->recv_pos - i,
|
||
|
i) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
nn_assert (i < NN_WS_HANDSHAKE_TERMSEQ_LEN);
|
||
|
|
||
|
handshaker->recv_len = NN_WS_HANDSHAKE_TERMSEQ_LEN - i;
|
||
|
|
||
|
/* In the unlikely case the client would overflow what we
|
||
|
assumed was a sufficiently-large buffer to receive the
|
||
|
handshake, we fail the client. */
|
||
|
if (handshaker->recv_len + handshaker->recv_pos >
|
||
|
sizeof (handshaker->opening_hs)) {
|
||
|
handshaker->response_code =
|
||
|
NN_WS_HANDSHAKE_RESPONSE_TOO_BIG;
|
||
|
handshaker->state =
|
||
|
NN_WS_HANDSHAKE_STATE_SERVER_REPLY;
|
||
|
nn_ws_handshake_server_reply (handshaker);
|
||
|
}
|
||
|
else {
|
||
|
handshaker->retries++;
|
||
|
nn_usock_recv (handshaker->usock,
|
||
|
handshaker->opening_hs + handshaker->recv_pos,
|
||
|
handshaker->recv_len, NULL);
|
||
|
}
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_error ("Unexpected handshake result",
|
||
|
handshaker->state, src, type);
|
||
|
}
|
||
|
return;
|
||
|
case NN_USOCK_SHUTDOWN:
|
||
|
/* Ignore it and wait for ERROR event. */
|
||
|
return;
|
||
|
case NN_USOCK_ERROR:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_TIMEOUT:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* SERVER_REPLY state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_SERVER_REPLY:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
switch (type) {
|
||
|
case NN_USOCK_SENT:
|
||
|
/* As per RFC 6455 4.2.2, the handshake is now complete
|
||
|
and the connection is immediately ready for send/recv. */
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_DONE;
|
||
|
case NN_USOCK_SHUTDOWN:
|
||
|
/* Ignore it and wait for ERROR event. */
|
||
|
return;
|
||
|
case NN_USOCK_ERROR:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_TIMEOUT:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* CLIENT_SEND state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_CLIENT_SEND:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
switch (type) {
|
||
|
case NN_USOCK_SENT:
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_CLIENT_RECV;
|
||
|
nn_usock_recv (handshaker->usock, handshaker->response,
|
||
|
handshaker->recv_len, NULL);
|
||
|
return;
|
||
|
case NN_USOCK_SHUTDOWN:
|
||
|
/* Ignore it and wait for ERROR event. */
|
||
|
return;
|
||
|
case NN_USOCK_ERROR:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_TIMEOUT:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* CLIENT_RECV state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_CLIENT_RECV:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
switch (type) {
|
||
|
case NN_USOCK_RECEIVED:
|
||
|
/* Parse bytes received thus far. */
|
||
|
switch (nn_ws_handshake_parse_server_response (handshaker)) {
|
||
|
case NN_WS_HANDSHAKE_INVALID:
|
||
|
/* Opening handshake parsed successfully but does not
|
||
|
contain valid values. Fail connection. */
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state =
|
||
|
NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
case NN_WS_HANDSHAKE_VALID:
|
||
|
/* As per RFC 6455 4.2.2, the handshake is now complete
|
||
|
and the connection is immediately ready for send/recv. */
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_DONE;
|
||
|
return;
|
||
|
case NN_WS_HANDSHAKE_RECV_MORE:
|
||
|
/* Not enough bytes have been received to determine
|
||
|
validity; remain in the receive state, and retrieve
|
||
|
more bytes from client. */
|
||
|
handshaker->recv_pos += handshaker->recv_len;
|
||
|
|
||
|
/* Validate the previous recv operation. */
|
||
|
nn_assert (handshaker->recv_pos <
|
||
|
sizeof (handshaker->response));
|
||
|
|
||
|
/* Ensure we can back-track at least the length of the
|
||
|
termination sequence to determine how many bytes to
|
||
|
receive on the next retry. This is an assertion, not
|
||
|
a conditional, since under no condition is it
|
||
|
necessary to initially receive so few bytes. */
|
||
|
nn_assert (handshaker->recv_pos >=
|
||
|
(int) NN_WS_HANDSHAKE_TERMSEQ_LEN);
|
||
|
|
||
|
/* If i goes to 0, it no need to compare. */
|
||
|
for (i = NN_WS_HANDSHAKE_TERMSEQ_LEN; i > 0; i--) {
|
||
|
if (memcmp (NN_WS_HANDSHAKE_TERMSEQ,
|
||
|
handshaker->response + handshaker->recv_pos - i,
|
||
|
i) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
nn_assert (i < NN_WS_HANDSHAKE_TERMSEQ_LEN);
|
||
|
|
||
|
handshaker->recv_len = NN_WS_HANDSHAKE_TERMSEQ_LEN - i;
|
||
|
|
||
|
/* In the unlikely case the client would overflow what we
|
||
|
assumed was a sufficiently-large buffer to receive the
|
||
|
handshake, we fail the connection. */
|
||
|
if (handshaker->recv_len + handshaker->recv_pos >
|
||
|
sizeof (handshaker->response)) {
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state =
|
||
|
NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
}
|
||
|
else {
|
||
|
handshaker->retries++;
|
||
|
nn_usock_recv (handshaker->usock,
|
||
|
handshaker->response + handshaker->recv_pos,
|
||
|
handshaker->recv_len, NULL);
|
||
|
}
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_error ("Unexpected handshake result",
|
||
|
handshaker->state, src, type);
|
||
|
}
|
||
|
return;
|
||
|
case NN_USOCK_SHUTDOWN:
|
||
|
/* Ignore it and wait for ERROR event. */
|
||
|
return;
|
||
|
case NN_USOCK_ERROR:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_TIMEOUT:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* HANDSHAKE_SENT state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_HANDSHAKE_SENT:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
switch (type) {
|
||
|
case NN_USOCK_SENT:
|
||
|
/* As per RFC 6455 4.2.2, the handshake is now complete
|
||
|
and the connection is immediately ready for send/recv. */
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_DONE;
|
||
|
return;
|
||
|
case NN_USOCK_SHUTDOWN:
|
||
|
/* Ignore it and wait for ERROR event. */
|
||
|
return;
|
||
|
case NN_USOCK_ERROR:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_TIMEOUT:
|
||
|
nn_timer_stop (&handshaker->timer);
|
||
|
handshaker->state = NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR;
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* STOPPING_TIMER_ERROR state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_ERROR:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
/* Ignore. The only circumstance the client would send bytes is
|
||
|
to notify the server it is closing the connection. Wait for the
|
||
|
socket to eventually error. */
|
||
|
return;
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_STOPPED:
|
||
|
nn_ws_handshake_leave (handshaker, NN_WS_HANDSHAKE_ERROR);
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* STOPPING_TIMER_DONE state. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_STOPPING_TIMER_DONE:
|
||
|
switch (src) {
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_USOCK:
|
||
|
/* Ignore. The only circumstance the client would send bytes is
|
||
|
to notify the server it is closing the connection. Wait for the
|
||
|
socket to eventually error. */
|
||
|
return;
|
||
|
|
||
|
case NN_WS_HANDSHAKE_SRC_TIMER:
|
||
|
switch (type) {
|
||
|
case NN_TIMER_STOPPED:
|
||
|
nn_ws_handshake_leave (handshaker, NN_WS_HANDSHAKE_OK);
|
||
|
return;
|
||
|
default:
|
||
|
nn_fsm_bad_action (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* DONE state. */
|
||
|
/* The header exchange was either done successfully of failed. There's */
|
||
|
/* nothing that can be done in this state except stopping the object. */
|
||
|
/******************************************************************************/
|
||
|
case NN_WS_HANDSHAKE_STATE_DONE:
|
||
|
nn_fsm_bad_source (handshaker->state, src, type);
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Invalid state. */
|
||
|
/******************************************************************************/
|
||
|
default:
|
||
|
nn_fsm_bad_state (handshaker->state, src, type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* State machine actions. */
|
||
|
/******************************************************************************/
|
||
|
|
||
|
static void nn_ws_handshake_leave (struct nn_ws_handshake *self, int rc)
|
||
|
{
|
||
|
nn_usock_swap_owner (self->usock, &self->usock_owner);
|
||
|
self->usock = NULL;
|
||
|
self->usock_owner.src = -1;
|
||
|
self->usock_owner.fsm = NULL;
|
||
|
self->state = NN_WS_HANDSHAKE_STATE_DONE;
|
||
|
nn_fsm_raise (&self->fsm, &self->done, rc);
|
||
|
}
|
||
|
|
||
|
static int nn_ws_handshake_parse_client_opening (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
/* As per RFC 6455 section 1.7, this parser is not intended to be a
|
||
|
general-purpose parser for arbitrary HTTP headers. As with the design
|
||
|
philosophy of nanomsg, application-specific exchanges are better
|
||
|
reserved for accepted connections, not as fields within these
|
||
|
headers. */
|
||
|
|
||
|
int rc;
|
||
|
const char *pos;
|
||
|
unsigned i;
|
||
|
|
||
|
/* Guarantee that a NULL terminator exists to enable treating this
|
||
|
recv buffer like a string. */
|
||
|
nn_assert (memchr (self->opening_hs, '\0', sizeof (self->opening_hs)));
|
||
|
|
||
|
/* Having found the NULL terminator, from this point forward string
|
||
|
functions may be used. */
|
||
|
nn_assert (strlen (self->opening_hs) < sizeof (self->opening_hs));
|
||
|
|
||
|
pos = self->opening_hs;
|
||
|
|
||
|
/* Is the opening handshake from the client fully received? */
|
||
|
if (!strstr (pos, NN_WS_HANDSHAKE_TERMSEQ))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
self->host = NULL;
|
||
|
self->origin = NULL;
|
||
|
self->key = NULL;
|
||
|
self->upgrade = NULL;
|
||
|
self->conn = NULL;
|
||
|
self->version = NULL;
|
||
|
self->protocol = NULL;
|
||
|
self->uri = NULL;
|
||
|
|
||
|
self->host_len = 0;
|
||
|
self->origin_len = 0;
|
||
|
self->key_len = 0;
|
||
|
self->upgrade_len = 0;
|
||
|
self->conn_len = 0;
|
||
|
self->version_len = 0;
|
||
|
self->protocol_len = 0;
|
||
|
self->uri_len = 0;
|
||
|
|
||
|
/* This function, if generating a return value that triggers
|
||
|
a response to the client, should replace this sentinel value
|
||
|
with a proper response code. */
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_NULL;
|
||
|
|
||
|
/* RFC 7230 3.1.1 Request Line: HTTP Method
|
||
|
Note requirement of one space and case sensitivity. */
|
||
|
if (!nn_ws_match_token ("GET\x20", &pos, 0, 0))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
/* RFC 7230 3.1.1 Request Line: Requested Resource. */
|
||
|
if (!nn_ws_match_value ("\x20", &pos, 0, 0, &self->uri, &self->uri_len))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
/* RFC 7230 3.1.1 Request Line: HTTP version. Note case sensitivity. */
|
||
|
if (!nn_ws_match_token ("HTTP/1.1", &pos, 0, 0))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
if (!nn_ws_match_token (NN_WS_HANDSHAKE_CRLF, &pos, 0, 0))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
/* It's expected the current position is now at the first
|
||
|
header field. Match them one by one. */
|
||
|
while (strlen (pos))
|
||
|
{
|
||
|
if (nn_ws_match_token ("Host:", &pos, 1, 0)) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->host, &self->host_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Origin:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->origin, &self->origin_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Key:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->key, &self->key_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Upgrade:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->upgrade, &self->upgrade_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Connection:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->conn, &self->conn_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Version:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->version, &self->version_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Protocol:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->protocol, &self->protocol_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Extensions:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->extensions, &self->extensions_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token (NN_WS_HANDSHAKE_CRLF,
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
/* Exit loop since all headers are parsed. */
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
/* Skip unknown headers. */
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
NULL, NULL);
|
||
|
}
|
||
|
|
||
|
if (rc != NN_WS_HANDSHAKE_MATCH)
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
}
|
||
|
|
||
|
/* Validate the opening handshake is now fully parsed. Additionally,
|
||
|
as per RFC 6455 section 4.1, the client should not send additional data
|
||
|
after the opening handshake, so this assertion validates upstream recv
|
||
|
logic prevented this case. */
|
||
|
nn_assert (strlen (pos) == 0);
|
||
|
|
||
|
/* TODO: protocol expectations below this point are hard-coded here as
|
||
|
an initial design decision. Perhaps in the future these values should
|
||
|
be settable via compile time (or run-time socket) options? */
|
||
|
|
||
|
/* These header fields are required as per RFC 6455 section 4.1. */
|
||
|
if (!self->host || !self->upgrade || !self->conn ||
|
||
|
!self->key || !self->version) {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_WSPROTO;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
|
||
|
/* RFC 6455 section 4.2.1.6 (version December 2011). */
|
||
|
if (nn_ws_validate_value ("13", self->version,
|
||
|
self->version_len, 1) != NN_WS_HANDSHAKE_MATCH) {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_WSVERSION;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
|
||
|
/* RFC 6455 section 4.2.1.3 (version December 2011). */
|
||
|
if (nn_ws_validate_value ("websocket", self->upgrade,
|
||
|
self->upgrade_len, 1) != NN_WS_HANDSHAKE_MATCH) {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_WSPROTO;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
|
||
|
/* RFC 6455 section 4.2.1.4 (version December 2011). */
|
||
|
if (nn_ws_validate_value ("Upgrade", self->conn,
|
||
|
self->conn_len, 1) != NN_WS_HANDSHAKE_MATCH) {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_WSPROTO;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
|
||
|
/* At this point, client meets RFC 6455 compliance for opening handshake.
|
||
|
Now it's time to check nanomsg-imposed required handshake values. */
|
||
|
if (self->protocol) {
|
||
|
/* Ensure the client SP is a compatible socket type. */
|
||
|
for (i = 0; i < NN_WS_HANDSHAKE_SP_MAP_LEN; i++) {
|
||
|
if (nn_ws_validate_value (NN_WS_HANDSHAKE_SP_MAP [i].ws_sp,
|
||
|
self->protocol, self->protocol_len, 1)) {
|
||
|
|
||
|
if (self->pipebase->sock->socktype->protocol ==
|
||
|
NN_WS_HANDSHAKE_SP_MAP [i].server) {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_OK;
|
||
|
return NN_WS_HANDSHAKE_VALID;
|
||
|
}
|
||
|
else {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_NOTPEER;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_UNKNOWNTYPE;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
else {
|
||
|
/* Be permissive and generous here, assuming that if a protocol is
|
||
|
not explicitly declared, PAIR is presumed. This enables
|
||
|
interoperability with non-nanomsg remote peers, nominally by
|
||
|
making the local socket PAIR type. For any other local
|
||
|
socket type, we expect connection to be rejected as
|
||
|
incompatible if the header is not specified. */
|
||
|
|
||
|
if (nn_pipebase_ispeer (self->pipebase, NN_PAIR)) {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_OK;
|
||
|
return NN_WS_HANDSHAKE_VALID;
|
||
|
}
|
||
|
else {
|
||
|
self->response_code = NN_WS_HANDSHAKE_RESPONSE_NOTPEER;
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int nn_ws_handshake_parse_server_response (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
/* As per RFC 6455 section 1.7, this parser is not intended to be a
|
||
|
general-purpose parser for arbitrary HTTP headers. As with the design
|
||
|
philosophy of nanomsg, application-specific exchanges are better
|
||
|
reserved for accepted connections, not as fields within these
|
||
|
headers. */
|
||
|
|
||
|
int rc;
|
||
|
const char *pos;
|
||
|
|
||
|
/* Guarantee that a NULL terminator exists to enable treating this
|
||
|
recv buffer like a string. The lack of such would indicate a failure
|
||
|
upstream to catch a buffer overflow. */
|
||
|
nn_assert (memchr (self->response, '\0', sizeof (self->response)));
|
||
|
|
||
|
/* Having found the NULL terminator, from this point forward string
|
||
|
functions may be used. */
|
||
|
nn_assert (strlen (self->response) < sizeof (self->response));
|
||
|
|
||
|
pos = self->response;
|
||
|
|
||
|
/* Is the response from the server fully received? */
|
||
|
if (!strstr (pos, NN_WS_HANDSHAKE_TERMSEQ))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
self->status_code = NULL;
|
||
|
self->reason_phrase = NULL;
|
||
|
self->server = NULL;
|
||
|
self->accept_key = NULL;
|
||
|
self->upgrade = NULL;
|
||
|
self->conn = NULL;
|
||
|
self->version = NULL;
|
||
|
self->protocol = NULL;
|
||
|
|
||
|
self->status_code_len = 0;
|
||
|
self->reason_phrase_len = 0;
|
||
|
self->server_len = 0;
|
||
|
self->accept_key_len = 0;
|
||
|
self->upgrade_len = 0;
|
||
|
self->conn_len = 0;
|
||
|
self->version_len = 0;
|
||
|
self->protocol_len = 0;
|
||
|
|
||
|
/* RFC 7230 3.1.2 Status Line: HTTP Version. */
|
||
|
if (!nn_ws_match_token ("HTTP/1.1\x20", &pos, 0, 0))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
/* RFC 7230 3.1.2 Status Line: Status Code. */
|
||
|
if (!nn_ws_match_value ("\x20", &pos, 0, 0, &self->status_code,
|
||
|
&self->status_code_len))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
/* RFC 7230 3.1.2 Status Line: Reason Phrase. */
|
||
|
if (!nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 0, 0,
|
||
|
&self->reason_phrase, &self->reason_phrase_len))
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
|
||
|
/* It's expected the current position is now at the first
|
||
|
header field. Match them one by one. */
|
||
|
while (strlen (pos))
|
||
|
{
|
||
|
if (nn_ws_match_token ("Server:", &pos, 1, 0)) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->server, &self->server_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Accept:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->accept_key, &self->accept_key_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Upgrade:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->upgrade, &self->upgrade_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Connection:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->conn, &self->conn_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Version-Server:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->version, &self->version_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Protocol-Server:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->protocol, &self->protocol_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token ("Sec-WebSocket-Extensions:",
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
&self->extensions, &self->extensions_len);
|
||
|
}
|
||
|
else if (nn_ws_match_token (NN_WS_HANDSHAKE_CRLF,
|
||
|
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
|
||
|
/* Exit loop since all headers are parsed. */
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
/* Skip unknown headers. */
|
||
|
rc = nn_ws_match_value (NN_WS_HANDSHAKE_CRLF, &pos, 1, 1,
|
||
|
NULL, NULL);
|
||
|
}
|
||
|
|
||
|
if (rc != NN_WS_HANDSHAKE_MATCH)
|
||
|
return NN_WS_HANDSHAKE_RECV_MORE;
|
||
|
}
|
||
|
|
||
|
/* Validate the opening handshake is now fully parsed. Additionally,
|
||
|
as per RFC 6455 section 4.1, the client should not send additional data
|
||
|
after the opening handshake, so this assertion validates upstream recv
|
||
|
logic prevented this case. */
|
||
|
nn_assert (strlen (pos) == 0);
|
||
|
|
||
|
/* TODO: protocol expectations below this point are hard-coded here as
|
||
|
an initial design decision. Perhaps in the future these values should
|
||
|
be settable via compile time (or run-time socket) options? */
|
||
|
|
||
|
/* These header fields are required as per RFC 6455 4.2.2. */
|
||
|
if (!self->status_code || !self->upgrade || !self->conn ||
|
||
|
!self->accept_key)
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
|
||
|
/* TODO: Currently, we only handle a successful connection upgrade.
|
||
|
Anything else is treated as a failed connection.
|
||
|
Consider handling other scenarios like 3xx redirects. */
|
||
|
if (nn_ws_validate_value ("101", self->status_code,
|
||
|
self->status_code_len, 1) != NN_WS_HANDSHAKE_MATCH)
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
|
||
|
/* RFC 6455 section 4.2.2.5.2 (version December 2011). */
|
||
|
if (nn_ws_validate_value ("websocket", self->upgrade,
|
||
|
self->upgrade_len, 1) != NN_WS_HANDSHAKE_MATCH)
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
|
||
|
/* RFC 6455 section 4.2.2.5.3 (version December 2011). */
|
||
|
if (nn_ws_validate_value ("Upgrade", self->conn,
|
||
|
self->conn_len, 1) != NN_WS_HANDSHAKE_MATCH)
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
|
||
|
/* RFC 6455 section 4.2.2.5.4 (version December 2011). */
|
||
|
if (nn_ws_validate_value (self->expected_accept_key, self->accept_key,
|
||
|
self->accept_key_len, 1) != NN_WS_HANDSHAKE_MATCH)
|
||
|
return NN_WS_HANDSHAKE_INVALID;
|
||
|
|
||
|
/* Server response meets RFC 6455 compliance for opening handshake. */
|
||
|
return NN_WS_HANDSHAKE_VALID;
|
||
|
}
|
||
|
|
||
|
static void nn_ws_handshake_client_request (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
struct nn_iovec open_request;
|
||
|
size_t encoded_key_len;
|
||
|
int rc;
|
||
|
unsigned i;
|
||
|
|
||
|
/* Generate random 16-byte key as per RFC 6455 4.1 */
|
||
|
uint8_t rand_key [16];
|
||
|
|
||
|
/* Known length required to base64 encode above random key plus
|
||
|
string NULL terminator. */
|
||
|
char encoded_key [24 + 1];
|
||
|
|
||
|
nn_random_generate (rand_key, sizeof (rand_key));
|
||
|
|
||
|
rc = nn_base64_encode (rand_key, sizeof (rand_key),
|
||
|
encoded_key, sizeof (encoded_key));
|
||
|
|
||
|
encoded_key_len = strlen (encoded_key);
|
||
|
|
||
|
nn_assert (encoded_key_len == sizeof (encoded_key) - 1);
|
||
|
|
||
|
/* Pre-calculated expected Accept Key value as per
|
||
|
RFC 6455 section 4.2.2.5.4 (version December 2011). */
|
||
|
rc = nn_ws_handshake_hash_key (encoded_key, encoded_key_len,
|
||
|
self->expected_accept_key, sizeof (self->expected_accept_key));
|
||
|
|
||
|
nn_assert (rc == NN_WS_HANDSHAKE_ACCEPT_KEY_LEN);
|
||
|
|
||
|
/* Lookup SP header value. */
|
||
|
for (i = 0; i < NN_WS_HANDSHAKE_SP_MAP_LEN; i++) {
|
||
|
if (NN_WS_HANDSHAKE_SP_MAP [i].client ==
|
||
|
self->pipebase->sock->socktype->protocol) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Guarantee that the socket type was found in the map. */
|
||
|
nn_assert (i < NN_WS_HANDSHAKE_SP_MAP_LEN);
|
||
|
|
||
|
sprintf (self->opening_hs,
|
||
|
"GET %s HTTP/1.1\r\n"
|
||
|
"Host: %s\r\n"
|
||
|
"Upgrade: websocket\r\n"
|
||
|
"Connection: Upgrade\r\n"
|
||
|
"Sec-WebSocket-Key: %s\r\n"
|
||
|
"Sec-WebSocket-Version: 13\r\n"
|
||
|
"Sec-WebSocket-Protocol: %s\r\n\r\n",
|
||
|
self->resource, self->remote_host, encoded_key,
|
||
|
NN_WS_HANDSHAKE_SP_MAP[i].ws_sp);
|
||
|
|
||
|
open_request.iov_len = strlen (self->opening_hs);
|
||
|
open_request.iov_base = self->opening_hs;
|
||
|
|
||
|
nn_usock_send (self->usock, &open_request, 1);
|
||
|
}
|
||
|
|
||
|
static void nn_ws_handshake_server_reply (struct nn_ws_handshake *self)
|
||
|
{
|
||
|
struct nn_iovec response;
|
||
|
char *code;
|
||
|
char *version;
|
||
|
char *protocol;
|
||
|
int rc;
|
||
|
|
||
|
/* Allow room for NULL terminator. */
|
||
|
char accept_key [NN_WS_HANDSHAKE_ACCEPT_KEY_LEN + 1];
|
||
|
|
||
|
memset (self->response, 0, sizeof (self->response));
|
||
|
|
||
|
if (self->response_code == NN_WS_HANDSHAKE_RESPONSE_OK) {
|
||
|
/* Upgrade connection as per RFC 6455 section 4.2.2. */
|
||
|
|
||
|
rc = nn_ws_handshake_hash_key (self->key, self->key_len,
|
||
|
accept_key, sizeof (accept_key));
|
||
|
|
||
|
nn_assert (strlen (accept_key) == NN_WS_HANDSHAKE_ACCEPT_KEY_LEN);
|
||
|
|
||
|
protocol = nn_alloc (self->protocol_len + 1, "WebSocket protocol");
|
||
|
alloc_assert (protocol);
|
||
|
strncpy (protocol, self->protocol, self->protocol_len);
|
||
|
protocol [self->protocol_len] = '\0';
|
||
|
|
||
|
sprintf (self->response,
|
||
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
||
|
"Upgrade: websocket\r\n"
|
||
|
"Connection: Upgrade\r\n"
|
||
|
"Sec-WebSocket-Accept: %s\r\n"
|
||
|
"Sec-WebSocket-Protocol: %s\r\n\r\n",
|
||
|
accept_key, protocol);
|
||
|
|
||
|
nn_free (protocol);
|
||
|
}
|
||
|
else {
|
||
|
/* Fail the connection with a helpful hint. */
|
||
|
switch (self->response_code) {
|
||
|
case NN_WS_HANDSHAKE_RESPONSE_TOO_BIG:
|
||
|
code = "400 Opening Handshake Too Long";
|
||
|
break;
|
||
|
case NN_WS_HANDSHAKE_RESPONSE_WSPROTO:
|
||
|
code = "400 Cannot Have Body";
|
||
|
break;
|
||
|
case NN_WS_HANDSHAKE_RESPONSE_WSVERSION:
|
||
|
code = "400 Unsupported WebSocket Version";
|
||
|
break;
|
||
|
case NN_WS_HANDSHAKE_RESPONSE_NNPROTO:
|
||
|
code = "400 Missing nanomsg Required Headers";
|
||
|
break;
|
||
|
case NN_WS_HANDSHAKE_RESPONSE_NOTPEER:
|
||
|
code = "400 Incompatible Socket Type";
|
||
|
break;
|
||
|
case NN_WS_HANDSHAKE_RESPONSE_UNKNOWNTYPE:
|
||
|
code = "400 Unrecognized Socket Type";
|
||
|
break;
|
||
|
default:
|
||
|
/* Unexpected failure response. */
|
||
|
nn_assert (0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
version = nn_alloc (self->version_len + 1, "WebSocket version");
|
||
|
alloc_assert (version);
|
||
|
strncpy (version, self->version, self->version_len);
|
||
|
version [self->version_len] = '\0';
|
||
|
|
||
|
/* Fail connection as per RFC 6455 4.4. */
|
||
|
sprintf (self->response,
|
||
|
"HTTP/1.1 %s\r\n"
|
||
|
"Sec-WebSocket-Version: %s\r\n",
|
||
|
code, version);
|
||
|
|
||
|
nn_free (version);
|
||
|
}
|
||
|
|
||
|
response.iov_len = strlen (self->response);
|
||
|
response.iov_base = &self->response;
|
||
|
|
||
|
nn_usock_send (self->usock, &response, 1);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static int nn_ws_handshake_hash_key (const char *key, size_t key_len,
|
||
|
char *hashed, size_t hashed_len)
|
||
|
{
|
||
|
int rc;
|
||
|
unsigned i;
|
||
|
struct nn_sha1 hash;
|
||
|
|
||
|
nn_sha1_init (&hash);
|
||
|
|
||
|
for (i = 0; i < key_len; i++)
|
||
|
nn_sha1_hashbyte (&hash, key [i]);
|
||
|
|
||
|
for (i = 0; i < strlen (NN_WS_HANDSHAKE_MAGIC_GUID); i++)
|
||
|
nn_sha1_hashbyte (&hash, NN_WS_HANDSHAKE_MAGIC_GUID [i]);
|
||
|
|
||
|
rc = nn_base64_encode (nn_sha1_result (&hash),
|
||
|
sizeof (hash.state), hashed, hashed_len);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|