blob: ddf5f258f7f3d6e78690881dc7d860177305b6ca [file] [log] [blame]
/*
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 "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 "../../utils/strcasestr.h"
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#define CRLF "\r\n"
/*****************************************************************************/
/*** 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 (*token) != tolower (*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 (*expected) != tolower (*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;
size_t 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 (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))
{
const char *conn = NULL;
size_t conn_len = 0;
if (nn_ws_match_token ("Host:", &pos, 1, 0)) {
rc = nn_ws_match_value (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 (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 (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 (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 (CRLF, &pos, 1, 1, &conn, &conn_len);
/* The values here can be comma delimited, or they can be
listed as separate Connection headers. We only care about
the presence of the Upgrade header, and we're willing to
assume well-formedness. This crummy parse may let clients
send us a malformed header that we ought to reject, but
we'll just cite Postel's law here if anyone asks. */
self->conn = nn_strcasestr (conn, "upgrade");
if (self->conn != NULL) {
self->conn_len = strlen ("upgrade");
}
}
else if (nn_ws_match_token ("Sec-WebSocket-Version:",
&pos, 1, 0) == NN_WS_HANDSHAKE_MATCH) {
rc = nn_ws_match_value (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 (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 (CRLF, &pos, 1, 1,
&self->extensions, &self->extensions_len);
}
else if (nn_ws_match_token (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 (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 (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 (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 (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 (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 (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 (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 (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 (CRLF, &pos, 1, 1,
&self->extensions, &self->extensions_len);
}
else if (nn_ws_match_token (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 (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));
nn_assert (rc >=0);
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 (rc >= 0);
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;
}