1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-23 00:00:06 +01:00
libwebsockets/lib/roles/ws/client-ws.c
Andy Green 498a4e2bd7 sul: all timed objects use a single pt sul list
wsi timeout, wsi hrtimer, sequencer timeout and vh-protocol timer
all now participate on a single sorted us list.

The whole idea of polling wakes is thrown out, poll waits ignore the
timeout field and always use infinite timeouts.

Introduce a public api that can schedule its own callback from the event
loop with us resolution (usually ms is all the platform can do).

Upgrade timeouts and sequencer timeouts to also be able to use us resolution.

Introduce a prepared fakewsi in the pt, so we don't have to allocate
one on the heap when we need it.

Directly handle vh-protocol timer if LWS_MAX_SMP == 1
2019-08-09 10:12:09 +01:00

685 lines
16 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation:
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <core/private.h>
/*
* In-place str to lower case
*/
static void
strtolower(char *s)
{
while (*s) {
#ifdef LWS_PLAT_OPTEE
int tolower_optee(int c);
*s = tolower_optee((int)*s);
#else
*s = tolower((int)*s);
#endif
s++;
}
}
int
lws_create_client_ws_object(const struct lws_client_connect_info *i,
struct lws *wsi)
{
int v = SPEC_LATEST_SUPPORTED;
/* allocate the ws struct for the wsi */
wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct");
if (!wsi->ws) {
lwsl_notice("OOM\n");
return 1;
}
/* -1 means just use latest supported */
if (i->ietf_version_or_minus_one != -1 &&
i->ietf_version_or_minus_one)
v = i->ietf_version_or_minus_one;
wsi->ws->ietf_spec_revision = v;
return 0;
}
#if !defined(LWS_NO_CLIENT)
int
lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
{
unsigned char *bufin = *buf;
if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) &&
(lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) &&
(lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) &&
!lwsi_role_client(wsi))
return 0;
lwsl_debug("%s: hs client feels it has %d in\n", __func__, (int)len);
while (len) {
/*
* we were accepting input but now we stopped doing so
*/
if (lws_is_flowcontrolled(wsi)) {
lwsl_debug("%s: caching %ld\n", __func__, (long)len);
/*
* Since we cached the remaining available input, we
* can say we "consumed" it.
*
* But what about the case where the available input
* came out of the rxflow cache already? If we are
* effectively "putting it back in the cache", we have
* to place it at the cache head, not the tail as usual.
*/
if (lws_rxflow_cache(wsi, *buf, 0, (int)len) ==
LWSRXFC_TRIMMED) {
/*
* we dealt with it by trimming the existing
* rxflow cache HEAD to account for what we used.
*
* indicate we didn't use anything to the caller
* so he doesn't do any consumed processing
*/
lwsl_info("%s: trimming inside rxflow cache\n",
__func__);
*buf = bufin;
} else
*buf += len;
return 0;
}
#if !defined(LWS_WITHOUT_EXTENSIONS)
if (wsi->ws->rx_draining_ext) {
int m;
lwsl_info("%s: draining ext\n", __func__);
if (lwsi_role_client(wsi))
m = lws_ws_client_rx_sm(wsi, 0);
else
m = lws_ws_rx_sm(wsi, 0, 0);
if (m < 0)
return -1;
continue;
}
#endif
/*
* caller will account for buflist usage by studying what
* happened to *buf
*/
if (lws_ws_client_rx_sm(wsi, *(*buf)++)) {
lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n",
__func__, (int)len);
return -1;
}
len--;
}
// lwsl_notice("%s: finished with %ld\n", __func__, (long)len);
return 0;
}
#endif
char *
lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1)
{
char buf[128], hash[20], key_b64[40];
int n;
#if !defined(LWS_WITHOUT_EXTENSIONS)
const struct lws_extension *ext;
int ext_count = 0;
#endif
/*
* create the random key
*/
n = lws_get_random(wsi->context, hash, 16);
if (n != 16) {
lwsl_err("Unable to read from random dev %s\n",
SYSTEM_RANDOM_FILEPATH);
return NULL;
}
lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64));
p += sprintf(p, "Upgrade: websocket\x0d\x0a"
"Connection: %sUpgrade\x0d\x0a"
"Sec-WebSocket-Key: ", conn1);
strcpy(p, key_b64);
p += strlen(key_b64);
p += sprintf(p, "\x0d\x0a");
if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS))
p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a",
lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_SENT_PROTOCOLS));
/* tell the server what extensions we could support */
#if !defined(LWS_WITHOUT_EXTENSIONS)
ext = wsi->vhost->ws.extensions;
while (ext && ext->callback) {
n = wsi->vhost->protocols[0].callback(wsi,
LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
wsi->user_space, (char *)ext->name, 0);
/*
* zero return from callback means go ahead and allow
* the extension, it's what we get if the callback is
* unhandled
*/
if (n) {
ext++;
continue;
}
/* apply it */
if (ext_count)
*p++ = ',';
else
p += sprintf(p, "Sec-WebSocket-Extensions: ");
p += sprintf(p, "%s", ext->client_offer);
ext_count++;
ext++;
}
if (ext_count)
p += sprintf(p, "\x0d\x0a");
#endif
if (wsi->ws->ietf_spec_revision)
p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a",
wsi->ws->ietf_spec_revision);
/* prepare the expected server accept response */
key_b64[39] = '\0'; /* enforce composed length below buf sizeof */
n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
key_b64);
lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash);
lws_b64_encode_string(hash, 20,
wsi->http.ah->initial_handshake_hash_base64,
sizeof(wsi->http.ah->initial_handshake_hash_base64));
return p;
}
int
lws_client_ws_upgrade(struct lws *wsi, const char **cce)
{
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
struct lws_context *context = wsi->context;
struct lws_tokenize ts;
int n, len, okay = 0;
lws_tokenize_elem e;
char *p, buf[64];
const char *pc;
#if !defined(LWS_WITHOUT_EXTENSIONS)
char *sb = (char *)&pt->serv_buf[0];
const struct lws_ext_options *opts;
const struct lws_extension *ext;
char ext_name[128];
const char *c, *a;
int more = 1;
char ignore;
#endif
if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */
lwsl_warn("%s: client ws-over-h2 upgrade not supported yet\n",
__func__);
*cce = "HS: h2 / ws upgrade unsupported";
goto bail3;
}
if (wsi->http.ah->http_response == 401) {
lwsl_warn(
"lws_client_handshake: got bad HTTP response '%d'\n",
wsi->http.ah->http_response);
*cce = "HS: ws upgrade unauthorized";
goto bail3;
}
if (wsi->http.ah->http_response != 101) {
lwsl_warn(
"lws_client_handshake: got bad HTTP response '%d'\n",
wsi->http.ah->http_response);
*cce = "HS: ws upgrade response not 101";
goto bail3;
}
if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
lwsl_info("no ACCEPT\n");
*cce = "HS: ACCEPT missing";
goto bail3;
}
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
if (!p) {
lwsl_info("no UPGRADE\n");
*cce = "HS: UPGRADE missing";
goto bail3;
}
strtolower(p);
if (strcmp(p, "websocket")) {
lwsl_warn(
"lws_client_handshake: got bad Upgrade header '%s'\n", p);
*cce = "HS: Upgrade to something other than websocket";
goto bail3;
}
/* connection: must have "upgrade" */
lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
LWS_TOKENIZE_F_MINUS_NONTERM);
ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION);
if (ts.len <= 0) /* won't fit, or absent */
goto bad_conn_format;
do {
e = lws_tokenize(&ts);
switch (e) {
case LWS_TOKZE_TOKEN:
if (!strncasecmp(ts.token, "upgrade", ts.token_len))
e = LWS_TOKZE_ENDED;
break;
case LWS_TOKZE_DELIMITER:
break;
default: /* includes ENDED found by the tokenizer itself */
bad_conn_format:
lwsl_info("%s: malfored connection '%s'\n",
__func__, buf);
*cce = "HS: UPGRADE malformed";
goto bail3;
}
} while (e > 0);
pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
if (!pc) {
lwsl_parser("lws_client_int_s_hs: no protocol list\n");
} else
lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc);
/*
* confirm the protocol the server wants to talk was in the list
* of protocols we offered
*/
len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
if (!len) {
lwsl_info("%s: WSI_TOKEN_PROTOCOL is null\n", __func__);
/*
* no protocol name to work from, if we don't already have one
* default to first protocol
*/
if (wsi->protocol) {
p = (char *)wsi->protocol->name;
goto identify_protocol;
}
/* no choice but to use the default protocol */
n = 0;
wsi->protocol = &wsi->vhost->protocols[0];
goto check_extensions;
}
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
len = (int)strlen(p);
while (pc && *pc && !okay) {
if (!strncmp(pc, p, len) &&
(pc[len] == ',' || pc[len] == '\0')) {
okay = 1;
continue;
}
while (*pc && *pc++ != ',')
;
while (*pc == ' ')
pc++;
}
if (!okay) {
lwsl_info("%s: got bad protocol %s\n", __func__, p);
*cce = "HS: PROTOCOL malformed";
goto bail2;
}
identify_protocol:
#if defined(LWS_WITH_HTTP_PROXY)
lws_strncpy(wsi->ws->actual_protocol, p,
sizeof(wsi->ws->actual_protocol));
#endif
/*
* identify the selected protocol struct and set it
*/
n = 0;
/* keep client connection pre-bound protocol */
if (!lwsi_role_client(wsi))
wsi->protocol = NULL;
while (wsi->vhost->protocols[n].callback) {
if (!wsi->protocol &&
strcmp(p, wsi->vhost->protocols[n].name) == 0) {
wsi->protocol = &wsi->vhost->protocols[n];
break;
}
n++;
}
if (!wsi->vhost->protocols[n].callback) { /* no match */
/* if server, that's already fatal */
if (!lwsi_role_client(wsi)) {
lwsl_info("%s: fail protocol %s\n", __func__, p);
*cce = "HS: Cannot match protocol";
goto bail2;
}
/* for client, find the index of our pre-bound protocol */
n = 0;
while (wsi->vhost->protocols[n].callback) {
if (wsi->protocol && strcmp(wsi->protocol->name,
wsi->vhost->protocols[n].name) == 0) {
wsi->protocol = &wsi->vhost->protocols[n];
break;
}
n++;
}
if (!wsi->vhost->protocols[n].callback) {
if (wsi->protocol)
lwsl_err("Failed to match protocol %s\n",
wsi->protocol->name);
else
lwsl_err("No protocol on client\n");
*cce = "ws protocol no match";
goto bail2;
}
}
lwsl_debug("Selected protocol %s\n", wsi->protocol->name);
check_extensions:
/*
* stitch protocol choice into the vh protocol linked list
* We always insert ourselves at the start of the list
*
* X <-> B
* X <-> pAn <-> pB
*/
lws_same_vh_protocol_insert(wsi, n);
#if !defined(LWS_WITHOUT_EXTENSIONS)
/* instantiate the accepted extensions */
if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
lwsl_ext("no client extensions allowed by server\n");
goto check_accept;
}
/*
* break down the list of server accepted extensions
* and go through matching them or identifying bogons
*/
if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size,
WSI_TOKEN_EXTENSIONS) < 0) {
lwsl_warn("ext list from server failed to copy\n");
*cce = "HS: EXT: list too big";
goto bail2;
}
c = sb;
n = 0;
ignore = 0;
a = NULL;
while (more) {
if (*c && (*c != ',' && *c != '\t')) {
if (*c == ';') {
ignore = 1;
if (!a)
a = c + 1;
}
if (ignore || *c == ' ') {
c++;
continue;
}
ext_name[n] = *c++;
if (n < (int)sizeof(ext_name) - 1)
n++;
continue;
}
ext_name[n] = '\0';
ignore = 0;
if (!*c)
more = 0;
else {
c++;
if (!n)
continue;
}
/* check we actually support it */
lwsl_notice("checking client ext %s\n", ext_name);
n = 0;
ext = wsi->vhost->ws.extensions;
while (ext && ext->callback) {
if (strcmp(ext_name, ext->name)) {
ext++;
continue;
}
n = 1;
lwsl_notice("instantiating client ext %s\n", ext_name);
/* instantiate the extension on this conn */
wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext;
/* allow him to construct his ext instance */
if (ext->callback(lws_get_context(wsi), ext, wsi,
LWS_EXT_CB_CLIENT_CONSTRUCT,
(void *)&wsi->ws->act_ext_user[
wsi->ws->count_act_ext],
(void *)&opts, 0)) {
lwsl_info(" ext %s failed construction\n",
ext_name);
ext++;
continue;
}
/*
* allow the user code to override ext defaults if it
* wants to
*/
ext_name[0] = '\0';
if (user_callback_handle_rxflow(wsi->protocol->callback,
wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
(char *)ext->name, ext_name,
sizeof(ext_name))) {
*cce = "HS: EXT: failed setting defaults";
goto bail2;
}
if (ext_name[0] &&
lws_ext_parse_options(ext, wsi,
wsi->ws->act_ext_user[
wsi->ws->count_act_ext],
opts, ext_name,
(int)strlen(ext_name))) {
lwsl_err("%s: unable to parse user defaults '%s'",
__func__, ext_name);
*cce = "HS: EXT: failed parsing defaults";
goto bail2;
}
/*
* give the extension the server options
*/
if (a && lws_ext_parse_options(ext, wsi,
wsi->ws->act_ext_user[
wsi->ws->count_act_ext],
opts, a, lws_ptr_diff(c, a))) {
lwsl_err("%s: unable to parse remote def '%s'",
__func__, a);
*cce = "HS: EXT: failed parsing options";
goto bail2;
}
if (ext->callback(lws_get_context(wsi), ext, wsi,
LWS_EXT_CB_OPTION_CONFIRM,
wsi->ws->act_ext_user[wsi->ws->count_act_ext],
NULL, 0)) {
lwsl_err("%s: ext %s rejects server options %s",
__func__, ext->name, a);
*cce = "HS: EXT: Rejects server options";
goto bail2;
}
wsi->ws->count_act_ext++;
ext++;
}
if (n == 0) {
lwsl_warn("Unknown ext '%s'!\n", ext_name);
*cce = "HS: EXT: unknown ext";
goto bail2;
}
a = NULL;
n = 0;
}
check_accept:
#endif
/*
* Confirm his accept token is the one we precomputed
*/
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
if (strcmp(p, wsi->http.ah->initial_handshake_hash_base64)) {
lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p,
wsi->http.ah->initial_handshake_hash_base64);
*cce = "HS: Accept hash wrong";
goto bail2;
}
/* allocate the per-connection user memory (if any) */
if (lws_ensure_user_space(wsi)) {
lwsl_err("Problem allocating wsi user mem\n");
*cce = "HS: OOM";
goto bail2;
}
/*
* we seem to be good to go, give client last chance to check
* headers and OK it
*/
if (wsi->protocol->callback(wsi,
LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
wsi->user_space, NULL, 0)) {
*cce = "HS: Rejected by filter cb";
goto bail2;
}
/* clear his proxy connection timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
/* free up his parsing allocations */
lws_header_table_detach(wsi, 0);
lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED,
&role_ops_ws);
if (wsi->context->ws_ping_pong_interval && !wsi->http2_substream ) {
wsi->sul_ping.cb = lws_sul_wsping_cb;
__lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping,
(lws_usec_t)wsi->context->ws_ping_pong_interval *
LWS_USEC_PER_SEC);
}
wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
/*
* create the frame buffer for this connection according to the
* size mentioned in the protocol definition. If 0 there, then
* use a big default for compatibility
*/
n = (int)wsi->protocol->rx_buffer_size;
if (!n)
n = context->pt_serv_buf_size;
n += LWS_PRE;
wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */,
"client frame buffer");
if (!wsi->ws->rx_ubuf) {
lwsl_err("Out of Mem allocating rx buffer %d\n", n);
*cce = "HS: OOM";
goto bail2;
}
wsi->ws->rx_ubuf_alloc = n;
lwsl_info("Allocating client RX buffer %d\n", n);
#if !defined(LWS_WITH_ESP32)
if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
(const char *)&n, sizeof n)) {
lwsl_warn("Failed to set SNDBUF to %d", n);
*cce = "HS: SO_SNDBUF failed";
goto bail3;
}
#endif
lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name);
/* call him back to inform him he is up */
if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
wsi->user_space, NULL, 0)) {
*cce = "HS: Rejected at CLIENT_ESTABLISHED";
goto bail3;
}
return 0;
bail3:
return 3;
bail2:
return 2;
}