mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-23 00:00:06 +01:00

The union used to make a lot of sense to save space between mutually exclusive modes. But the fact the http2 struct contains the http1 struct as well as it appearing in the union means the http1 struct belongs outside the union. This patch - eliminates the union - puts the http_related struct directly in struct lws - removes http_related from h2 - puts h2 directly in struct lws if enabled for build - changes ws to be a pointer, allocated if we upgrade to ws (the ws part contains a 135 byte char array for ping / close) Again all of this is entirely private / internal and doesn't affect any apis.
1582 lines
40 KiB
C
1582 lines
40 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010-2017 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 "private-libwebsockets.h"
|
|
|
|
/*
|
|
* bitmap of control messages that are valid to receive for each http2 state
|
|
*/
|
|
|
|
static const uint16_t http2_rx_validity[] = {
|
|
/* LWS_H2S_IDLE */
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
|
|
// (1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE)| /* ignore */
|
|
(1 << LWS_H2_FRAME_TYPE_HEADERS) |
|
|
(1 << LWS_H2_FRAME_TYPE_CONTINUATION),
|
|
/* LWS_H2S_RESERVED_LOCAL */
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
|
|
(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE),
|
|
/* LWS_H2S_RESERVED_REMOTE */
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_HEADERS) |
|
|
(1 << LWS_H2_FRAME_TYPE_CONTINUATION) |
|
|
(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY),
|
|
/* LWS_H2S_OPEN */
|
|
(1 << LWS_H2_FRAME_TYPE_DATA) |
|
|
(1 << LWS_H2_FRAME_TYPE_HEADERS) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
|
|
(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_PUSH_PROMISE) |
|
|
(1 << LWS_H2_FRAME_TYPE_PING) |
|
|
(1 << LWS_H2_FRAME_TYPE_GOAWAY) |
|
|
(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
|
|
(1 << LWS_H2_FRAME_TYPE_CONTINUATION),
|
|
/* LWS_H2S_HALF_CLOSED_REMOTE */
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
|
|
(1 << LWS_H2_FRAME_TYPE_RST_STREAM),
|
|
/* LWS_H2S_HALF_CLOSED_LOCAL */
|
|
(1 << LWS_H2_FRAME_TYPE_DATA) |
|
|
(1 << LWS_H2_FRAME_TYPE_HEADERS) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
|
|
(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_PUSH_PROMISE) |
|
|
(1 << LWS_H2_FRAME_TYPE_PING) |
|
|
(1 << LWS_H2_FRAME_TYPE_GOAWAY) |
|
|
(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
|
|
(1 << LWS_H2_FRAME_TYPE_CONTINUATION),
|
|
/* LWS_H2S_CLOSED */
|
|
(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
|
|
(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
|
|
(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
|
|
(1 << LWS_H2_FRAME_TYPE_RST_STREAM),
|
|
};
|
|
|
|
static const char *preface = "PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a";
|
|
|
|
static const char * const h2_state_names[] = {
|
|
"LWS_H2S_IDLE",
|
|
"LWS_H2S_RESERVED_LOCAL",
|
|
"LWS_H2S_RESERVED_REMOTE",
|
|
"LWS_H2S_OPEN",
|
|
"LWS_H2S_HALF_CLOSED_REMOTE",
|
|
"LWS_H2S_HALF_CLOSED_LOCAL",
|
|
"LWS_H2S_CLOSED",
|
|
};
|
|
|
|
#if 0
|
|
static const char * const h2_setting_names[] = {
|
|
"",
|
|
"H2SET_HEADER_TABLE_SIZE",
|
|
"H2SET_ENABLE_PUSH",
|
|
"H2SET_MAX_CONCURRENT_STREAMS",
|
|
"H2SET_INITIAL_WINDOW_SIZE",
|
|
"H2SET_MAX_FRAME_SIZE",
|
|
"H2SET_MAX_HEADER_LIST_SIZE",
|
|
};
|
|
|
|
void
|
|
lws_h2_dump_settings(struct http2_settings *set)
|
|
{
|
|
int n;
|
|
|
|
for (n = 1; n < H2SET_COUNT; n++)
|
|
lwsl_notice(" %30s: %10d\n", h2_setting_names[n], set->s[n]);
|
|
}
|
|
#else
|
|
void
|
|
lws_h2_dump_settings(struct http2_settings *set)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
void lws_h2_init(struct lws *wsi)
|
|
{
|
|
wsi->h2.h2n->set = wsi->vhost->set;
|
|
}
|
|
|
|
static void
|
|
lws_h2_state(struct lws *wsi, enum lws_h2_states s)
|
|
{
|
|
if (!wsi)
|
|
return;
|
|
lwsl_info("%s: wsi %p: state %s -> %s\n", __func__, wsi,
|
|
h2_state_names[wsi->h2.h2_state],
|
|
h2_state_names[s]);
|
|
|
|
(void)h2_state_names;
|
|
wsi->h2.h2_state = (uint8_t)s;
|
|
}
|
|
|
|
struct lws *
|
|
lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
|
|
unsigned int sid)
|
|
{
|
|
struct lws *wsi;
|
|
struct lws *nwsi = lws_get_network_wsi(parent_wsi);
|
|
struct lws_h2_netconn *h2n = nwsi->h2.h2n;
|
|
|
|
/*
|
|
* The identifier of a newly established stream MUST be numerically
|
|
* greater than all streams that the initiating endpoint has opened or
|
|
* reserved. This governs streams that are opened using a HEADERS frame
|
|
* and streams that are reserved using PUSH_PROMISE. An endpoint that
|
|
* receives an unexpected stream identifier MUST respond with a
|
|
* connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
|
*/
|
|
if (sid <= h2n->highest_sid_opened) {
|
|
lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, "Bad sid");
|
|
return NULL;
|
|
}
|
|
|
|
/* no more children allowed by parent */
|
|
if (parent_wsi->h2.child_count + 1 >
|
|
parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
|
|
lwsl_notice("reached concurrent stream limit\n");
|
|
return NULL;
|
|
}
|
|
wsi = lws_create_new_server_wsi(vh);
|
|
if (!wsi) {
|
|
lwsl_notice("new server wsi failed (vh %p)\n", vh);
|
|
return NULL;
|
|
}
|
|
|
|
h2n->highest_sid_opened = sid;
|
|
wsi->h2.my_sid = sid;
|
|
wsi->http2_substream = 1;
|
|
wsi->seen_nonpseudoheader = 0;
|
|
|
|
wsi->h2.parent_wsi = parent_wsi;
|
|
/* new guy's sibling is whoever was the first child before */
|
|
wsi->h2.sibling_list = parent_wsi->h2.child_list;
|
|
/* first child is now the new guy */
|
|
parent_wsi->h2.child_list = wsi;
|
|
parent_wsi->h2.child_count++;
|
|
|
|
wsi->h2.my_priority = 16;
|
|
wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
wsi->h2.peer_tx_cr_est = nwsi->vhost->set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
|
|
wsi->state = LWSS_HTTP2_ESTABLISHED;
|
|
wsi->mode = parent_wsi->mode;
|
|
|
|
wsi->protocol = &vh->protocols[0];
|
|
if (lws_ensure_user_space(wsi))
|
|
goto bail1;
|
|
|
|
wsi->vhost->conn_stats.h2_subs++;
|
|
|
|
lwsl_info("%s: %p new ch %p, sid %d, usersp=%p, tx cr %d, "
|
|
"peer_credit %d (nwsi tx_cr %d)\n",
|
|
__func__, parent_wsi, wsi, sid, wsi->user_space,
|
|
wsi->h2.tx_cr, wsi->h2.peer_tx_cr_est, nwsi->h2.tx_cr);
|
|
|
|
return wsi;
|
|
|
|
bail1:
|
|
/* undo the insert */
|
|
parent_wsi->h2.child_list = wsi->h2.sibling_list;
|
|
parent_wsi->h2.child_count--;
|
|
|
|
if (wsi->user_space)
|
|
lws_free_set_NULL(wsi->user_space);
|
|
vh->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
|
|
lws_free(wsi);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct lws *
|
|
lws_h2_wsi_from_id(struct lws *parent_wsi, unsigned int sid)
|
|
{
|
|
lws_start_foreach_ll(struct lws *, wsi, parent_wsi->h2.child_list) {
|
|
if (wsi->h2.my_sid == sid)
|
|
return wsi;
|
|
} lws_end_foreach_ll(wsi, h2.sibling_list);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int lws_remove_server_child_wsi(struct lws_context *context, struct lws *wsi)
|
|
{
|
|
lws_start_foreach_llp(struct lws **, w, wsi->h2.child_list) {
|
|
if (*w == wsi) {
|
|
*w = wsi->h2.sibling_list;
|
|
(wsi->h2.parent_wsi)->h2.child_count--;
|
|
return 0;
|
|
}
|
|
} lws_end_foreach_llp(w, h2.sibling_list);
|
|
|
|
lwsl_err("%s: can't find %p\n", __func__, wsi);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pps)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
struct lws_h2_netconn *h2n = nwsi->h2.h2n;
|
|
|
|
pps->next = h2n->pps;
|
|
h2n->pps = pps;
|
|
lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_DISABLE |
|
|
LWS_RXFLOW_REASON_H2_PPS_PENDING);
|
|
lws_callback_on_writable(wsi);
|
|
}
|
|
|
|
static struct lws_h2_protocol_send *
|
|
lws_h2_new_pps(enum lws_h2_protocol_send_type type)
|
|
{
|
|
struct lws_h2_protocol_send *pps = lws_malloc(sizeof(*pps), "pps");
|
|
|
|
if (pps)
|
|
pps->type = type;
|
|
|
|
return pps;
|
|
}
|
|
|
|
int
|
|
lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
|
|
return 0;
|
|
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_GOAWAY);
|
|
if (!pps)
|
|
return 1;
|
|
|
|
lwsl_info("%s: %p: ERR 0x%x, '%s'\n", __func__, wsi, err, reason);
|
|
|
|
pps->u.ga.err = err;
|
|
pps->u.ga.highest_sid = h2n->highest_sid;
|
|
strncpy(pps->u.ga.str, reason, sizeof(pps->u.ga.str) - 1);
|
|
pps->u.ga.str[sizeof(pps->u.ga.str) - 1] = '\0';
|
|
lws_pps_schedule(wsi, pps);
|
|
|
|
h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
struct lws_h2_netconn *h2n = nwsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
|
|
return 0;
|
|
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_RST_STREAM);
|
|
if (!pps)
|
|
return 1;
|
|
|
|
lwsl_info("%s: RST_STREAM 0x%x, REASON '%s'\n", __func__, err, reason);
|
|
|
|
pps->u.rs.sid = h2n->sid;
|
|
pps->u.rs.err = err;
|
|
lws_pps_schedule(wsi, pps);
|
|
|
|
h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */
|
|
lws_h2_state(wsi, LWS_H2_STATE_CLOSED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_h2_settings(struct lws *wsi, struct http2_settings *settings,
|
|
unsigned char *buf, int len)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
unsigned int a, b;
|
|
|
|
if (!len)
|
|
return 0;
|
|
|
|
if (len < LWS_H2_SETTINGS_LEN)
|
|
return 1;
|
|
|
|
while (len >= LWS_H2_SETTINGS_LEN) {
|
|
a = (buf[0] << 8) | buf[1];
|
|
if (!a || a >= H2SET_COUNT)
|
|
goto skip;
|
|
b = buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5];
|
|
|
|
switch (a) {
|
|
case H2SET_HEADER_TABLE_SIZE:
|
|
break;
|
|
case H2SET_ENABLE_PUSH:
|
|
if (b > 1) {
|
|
lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
|
|
"ENABLE_PUSH invalid arg");
|
|
return 1;
|
|
}
|
|
break;
|
|
case H2SET_MAX_CONCURRENT_STREAMS:
|
|
break;
|
|
case H2SET_INITIAL_WINDOW_SIZE:
|
|
if (b > 0x7fffffff) {
|
|
lws_h2_goaway(nwsi, H2_ERR_FLOW_CONTROL_ERROR,
|
|
"Inital Window beyond max");
|
|
return 1;
|
|
}
|
|
/*
|
|
* In addition to changing the flow-control window for
|
|
* streams that are not yet active, a SETTINGS frame
|
|
* can alter the initial flow-control window size for
|
|
* streams with active flow-control windows (that is,
|
|
* streams in the "open" or "half-closed (remote)"
|
|
* state). When the value of
|
|
* SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver
|
|
* MUST adjust the size of all stream flow-control
|
|
* windows that it maintains by the difference between
|
|
* the new value and the old value.
|
|
*/
|
|
|
|
lws_start_foreach_ll(struct lws *, w,
|
|
nwsi->h2.child_list) {
|
|
lwsl_info("%s: adi child tc cr %d +%d -> %d",
|
|
__func__,
|
|
w->h2.tx_cr, b - settings->s[a],
|
|
w->h2.tx_cr + b - settings->s[a]);
|
|
w->h2.tx_cr += b - settings->s[a];
|
|
if (w->h2.tx_cr > 0 &&
|
|
w->h2.tx_cr <= (int32_t)(b - settings->s[a]))
|
|
lws_callback_on_writable(w);
|
|
} lws_end_foreach_ll(w, h2.sibling_list);
|
|
|
|
break;
|
|
case H2SET_MAX_FRAME_SIZE:
|
|
if (b < wsi->vhost->set.s[H2SET_MAX_FRAME_SIZE]) {
|
|
lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Frame size < initial");
|
|
return 1;
|
|
}
|
|
if (b > 0x007fffff) {
|
|
lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Settings Frame size above max");
|
|
return 1;
|
|
}
|
|
break;
|
|
case H2SET_MAX_HEADER_LIST_SIZE:
|
|
break;
|
|
}
|
|
settings->s[a] = b;
|
|
lwsl_info("http2 settings %d <- 0x%x\n", a, b);
|
|
skip:
|
|
len -= LWS_H2_SETTINGS_LEN;
|
|
buf += LWS_H2_SETTINGS_LEN;
|
|
}
|
|
|
|
if (len)
|
|
return 1;
|
|
|
|
lws_h2_dump_settings(settings);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* RFC7640 Sect 6.9
|
|
*
|
|
* The WINDOW_UPDATE frame can be specific to a stream or to the entire
|
|
* connection. In the former case, the frame's stream identifier
|
|
* indicates the affected stream; in the latter, the value "0" indicates
|
|
* that the entire connection is the subject of the frame.
|
|
*
|
|
* ...
|
|
*
|
|
* Two flow-control windows are applicable: the stream flow-control
|
|
* window and the connection flow-control window. The sender MUST NOT
|
|
* send a flow-controlled frame with a length that exceeds the space
|
|
* available in either of the flow-control windows advertised by the
|
|
* receiver. Frames with zero length with the END_STREAM flag set (that
|
|
* is, an empty DATA frame) MAY be sent if there is no available space
|
|
* in either flow-control window.
|
|
*/
|
|
|
|
int
|
|
lws_h2_tx_cr_get(struct lws *wsi)
|
|
{
|
|
int c = wsi->h2.tx_cr;
|
|
struct lws *nwsi;
|
|
|
|
if (!wsi->http2_substream && !wsi->upgraded_to_http2)
|
|
return ~0x80000000;
|
|
|
|
nwsi = lws_get_network_wsi(wsi);
|
|
|
|
lwsl_info ("%s: %p: own tx credit %d: nwsi credit %d\n",
|
|
__func__, wsi, c, nwsi->h2.tx_cr);
|
|
|
|
if (nwsi->h2.tx_cr < c)
|
|
c = nwsi->h2.tx_cr;
|
|
|
|
if (c < 0)
|
|
return 0;
|
|
|
|
return c;
|
|
}
|
|
|
|
void
|
|
lws_h2_tx_cr_consume(struct lws *wsi, int consumed)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
|
|
wsi->h2.tx_cr -= consumed;
|
|
|
|
if (nwsi != wsi)
|
|
nwsi->h2.tx_cr -= consumed;
|
|
}
|
|
|
|
int lws_h2_frame_write(struct lws *wsi, int type, int flags,
|
|
unsigned int sid, unsigned int len, unsigned char *buf)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
unsigned char *p = &buf[-LWS_H2_FRAME_HEADER_LENGTH];
|
|
int n;
|
|
|
|
*p++ = len >> 16;
|
|
*p++ = len >> 8;
|
|
*p++ = len;
|
|
*p++ = type;
|
|
*p++ = flags;
|
|
*p++ = sid >> 24;
|
|
*p++ = sid >> 16;
|
|
*p++ = sid >> 8;
|
|
*p++ = sid;
|
|
|
|
lwsl_debug("%s: %p (eff %p). typ %d, fl 0x%x, sid=%d, len=%d, "
|
|
"txcr=%d, nwsi->txcr=%d\n", __func__, wsi, nwsi, type, flags,
|
|
sid, len, wsi->h2.tx_cr, nwsi->h2.tx_cr);
|
|
|
|
if (type == LWS_H2_FRAME_TYPE_DATA) {
|
|
if (wsi->h2.tx_cr < (int)len)
|
|
lwsl_err("%s: %p: sending payload len %d"
|
|
" but tx_cr only %d!\n", __func__, wsi,
|
|
len, wsi->h2.tx_cr);
|
|
lws_h2_tx_cr_consume(wsi, len);
|
|
}
|
|
|
|
n = lws_issue_raw(nwsi, &buf[-LWS_H2_FRAME_HEADER_LENGTH],
|
|
len + LWS_H2_FRAME_HEADER_LENGTH);
|
|
if (n < 0)
|
|
return n;
|
|
|
|
if (n >= LWS_H2_FRAME_HEADER_LENGTH)
|
|
return n - LWS_H2_FRAME_HEADER_LENGTH;
|
|
|
|
return n;
|
|
}
|
|
|
|
static void lws_h2_set_bin(struct lws *wsi, int n, unsigned char *buf)
|
|
{
|
|
*buf++ = n >> 8;
|
|
*buf++ = n;
|
|
*buf++ = wsi->h2.h2n->set.s[n] >> 24;
|
|
*buf++ = wsi->h2.h2n->set.s[n] >> 16;
|
|
*buf++ = wsi->h2.h2n->set.s[n] >> 8;
|
|
*buf = wsi->h2.h2n->set.s[n];
|
|
}
|
|
|
|
int lws_h2_do_pps_send(struct lws *wsi)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps = NULL;
|
|
struct lws *cwsi;
|
|
uint8_t set[LWS_PRE + 64], *p = &set[LWS_PRE], *q;
|
|
int n, m = 0;
|
|
|
|
if (!h2n)
|
|
return 1;
|
|
|
|
/* get the oldest pps */
|
|
|
|
lws_start_foreach_llp(struct lws_h2_protocol_send **, pps1, h2n->pps) {
|
|
if ((*pps1)->next == NULL) { /* we are the oldest in the list */
|
|
pps = *pps1; /* remove us from the list */
|
|
*pps1 = NULL;
|
|
continue;
|
|
}
|
|
} lws_end_foreach_llp(pps1, next);
|
|
|
|
if (!pps)
|
|
return 1;
|
|
|
|
lwsl_info("%s: %p: %d\n", __func__, wsi, pps->type);
|
|
|
|
switch (pps->type) {
|
|
|
|
case LWS_H2_PPS_MY_SETTINGS:
|
|
/*
|
|
* if any of our settings varies from h2 "default defaults"
|
|
* then we must inform the perr
|
|
*/
|
|
for (n = 1; n < H2SET_COUNT; n++)
|
|
if (h2n->set.s[n] != lws_h2_defaults.s[n]) {
|
|
lwsl_debug("sending SETTING %d 0x%x\n", n,
|
|
wsi->h2.h2n->set.s[n]);
|
|
lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]);
|
|
m += sizeof(h2n->one_setting);
|
|
}
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
|
|
0, LWS_H2_STREAM_ID_MASTER, m,
|
|
&set[LWS_PRE]);
|
|
if (n != m) {
|
|
lwsl_info("send %d %d\n", n, m);
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_PPS_ACK_SETTINGS:
|
|
/* send ack ... always empty */
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS, 1,
|
|
LWS_H2_STREAM_ID_MASTER, 0, &set[LWS_PRE]);
|
|
if (n) {
|
|
lwsl_err("ack tells %d\n", n);
|
|
goto bail;
|
|
}
|
|
/* this is the end of the preface dance then? */
|
|
if (wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS) {
|
|
wsi->state = LWSS_HTTP2_ESTABLISHED;
|
|
wsi->http.fop_fd = NULL;
|
|
if (lws_is_ssl(lws_get_network_wsi(wsi)))
|
|
break;
|
|
/*
|
|
* we need to treat the headers from the upgrade as the
|
|
* first job. So these need to get shifted to sid 1.
|
|
*/
|
|
h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, 1);
|
|
if (!h2n->swsi)
|
|
goto bail;
|
|
|
|
/* pass on the initial headers to SID 1 */
|
|
h2n->swsi->ah = wsi->ah;
|
|
wsi->ah = NULL;
|
|
|
|
lwsl_info("%s: inherited headers %p\n", __func__,
|
|
h2n->swsi->ah);
|
|
h2n->swsi->h2.tx_cr =
|
|
h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
lwsl_info("initial tx credit on conn %p: %d\n",
|
|
h2n->swsi, h2n->swsi->h2.tx_cr);
|
|
h2n->swsi->h2.initialized = 1;
|
|
/* demanded by HTTP2 */
|
|
h2n->swsi->h2.END_STREAM = 1;
|
|
lwsl_info("servicing initial http request\n");
|
|
|
|
wsi->vhost->conn_stats.h2_trans++;
|
|
|
|
if (lws_http_action(h2n->swsi))
|
|
goto bail;
|
|
|
|
break;
|
|
}
|
|
break;
|
|
case LWS_H2_PPS_PONG:
|
|
lwsl_debug("sending PONG\n");
|
|
memcpy(&set[LWS_PRE], pps->u.ping.ping_payload, 8);
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_PING,
|
|
LWS_H2_FLAG_SETTINGS_ACK,
|
|
LWS_H2_STREAM_ID_MASTER, 8,
|
|
&set[LWS_PRE]);
|
|
if (n != 8) {
|
|
lwsl_info("send %d %d\n", n, m);
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_PPS_GOAWAY:
|
|
lwsl_info("LWS_H2_PPS_GOAWAY\n");
|
|
*p++ = pps->u.ga.highest_sid >> 24;
|
|
*p++ = pps->u.ga.highest_sid >> 16;
|
|
*p++ = pps->u.ga.highest_sid >> 8;
|
|
*p++ = pps->u.ga.highest_sid;
|
|
*p++ = pps->u.ga.err >> 24;
|
|
*p++ = pps->u.ga.err >> 16;
|
|
*p++ = pps->u.ga.err >> 8;
|
|
*p++ = pps->u.ga.err;
|
|
q = (unsigned char *)pps->u.ga.str;
|
|
n = 0;
|
|
while (*q && n++ < (int)sizeof(pps->u.ga.str))
|
|
*p++ = *q++;
|
|
h2n->we_told_goaway = 1;
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_GOAWAY, 0,
|
|
LWS_H2_STREAM_ID_MASTER,
|
|
lws_ptr_diff(p, &set[LWS_PRE]),
|
|
&set[LWS_PRE]);
|
|
if (n != 4) {
|
|
lwsl_info("send %d %d\n", n, m);
|
|
goto bail;
|
|
}
|
|
goto bail;
|
|
|
|
case LWS_H2_PPS_RST_STREAM:
|
|
lwsl_info("LWS_H2_PPS_RST_STREAM\n");
|
|
*p++ = pps->u.rs.err >> 24;
|
|
*p++ = pps->u.rs.err >> 16;
|
|
*p++ = pps->u.rs.err >> 8;
|
|
*p++ = pps->u.rs.err;
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_RST_STREAM,
|
|
0, pps->u.rs.sid, 4, &set[LWS_PRE]);
|
|
if (n != 4) {
|
|
lwsl_info("send %d %d\n", n, m);
|
|
goto bail;
|
|
}
|
|
cwsi = lws_h2_wsi_from_id(wsi, pps->u.rs.sid);
|
|
if (cwsi)
|
|
lws_close_free_wsi(cwsi, 0);
|
|
break;
|
|
|
|
case LWS_H2_PPS_UPDATE_WINDOW:
|
|
lwsl_notice("LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n",
|
|
pps->u.update_window.sid,
|
|
pps->u.update_window.credit);
|
|
*p++ = pps->u.update_window.credit >> 24;
|
|
*p++ = pps->u.update_window.credit >> 16;
|
|
*p++ = pps->u.update_window.credit >> 8;
|
|
*p++ = pps->u.update_window.credit;
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_WINDOW_UPDATE,
|
|
0, pps->u.update_window.sid, 4,
|
|
&set[LWS_PRE]);
|
|
if (n != 4) {
|
|
lwsl_info("send %d %d\n", n, m);
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
lws_free(pps);
|
|
|
|
return 0;
|
|
|
|
bail:
|
|
lws_free(pps);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The frame header part has just completely arrived.
|
|
* Perform actions for frame completion.
|
|
*/
|
|
static int
|
|
lws_h2_parse_frame_header(struct lws *wsi)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
int n;
|
|
|
|
/*
|
|
* We just got the frame header
|
|
*/
|
|
h2n->count = 0;
|
|
h2n->swsi = wsi;
|
|
/* b31 is a reserved bit */
|
|
h2n->sid = h2n->sid & 0x7fffffff;
|
|
|
|
if (h2n->sid && !(h2n->sid & 1)) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "Even Stream ID");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* let the network wsi live a bit longer if subs are active */
|
|
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 10);
|
|
|
|
/* let the network wsi live a bit longer if subs are active */
|
|
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 10);
|
|
|
|
if (h2n->sid)
|
|
h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid);
|
|
|
|
lwsl_info("%p (%p): fr hdr: typ 0x%x, flags 0x%x, sid 0x%x, len 0x%x\n",
|
|
wsi, h2n->swsi, h2n->type, h2n->flags, h2n->sid,
|
|
h2n->length);
|
|
|
|
if (h2n->we_told_goaway && h2n->sid > h2n->highest_sid)
|
|
h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */
|
|
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
|
|
return 0;
|
|
|
|
if (h2n->length > h2n->set.s[H2SET_MAX_FRAME_SIZE]) {
|
|
/*
|
|
* peer sent us something bigger than we told
|
|
* it we would allow
|
|
*/
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Peer ignored our frame size setting");
|
|
return 0;
|
|
}
|
|
|
|
if (h2n->swsi)
|
|
lwsl_info("%s: wsi %p, State: %s, received cmd %d\n",
|
|
__func__, h2n->swsi,
|
|
h2_state_names[h2n->swsi->h2.h2_state], h2n->type);
|
|
else {
|
|
/* if it's data, either way no swsi means CLOSED state */
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_DATA) {
|
|
lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
|
|
"Data for nonexistent sid");
|
|
return 0;
|
|
}
|
|
/* if the sid is credible, treat as wsi for it closed */
|
|
if (h2n->sid > h2n->highest_sid_opened &&
|
|
h2n->type != LWS_H2_FRAME_TYPE_HEADERS &&
|
|
h2n->type != LWS_H2_FRAME_TYPE_PRIORITY) {
|
|
/* if not credible, reject it */
|
|
lwsl_info("%s: wsi %p, No child for sid %d, rx cmd %d\n",
|
|
__func__, h2n->swsi, h2n->sid, h2n->type);
|
|
lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
|
|
"Data for nonexistent sid");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (h2n->swsi && h2n->sid &&
|
|
!(http2_rx_validity[h2n->swsi->h2.h2_state] & (1 << h2n->type))) {
|
|
lwsl_info("%s: wsi %p, State: %s, ILLEGAL cmdrx %d (OK 0x%x)\n",
|
|
__func__, h2n->swsi,
|
|
h2_state_names[h2n->swsi->h2.h2_state], h2n->type,
|
|
http2_rx_validity[h2n->swsi->h2.h2_state]);
|
|
|
|
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED ||
|
|
h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE)
|
|
n = H2_ERR_STREAM_CLOSED;
|
|
else
|
|
n = H2_ERR_PROTOCOL_ERROR;
|
|
lws_h2_goaway(wsi, n, "invalid rx for state");
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (h2n->cont_exp && (h2n->cont_exp_sid != h2n->sid ||
|
|
h2n->type != LWS_H2_FRAME_TYPE_CONTINUATION)) {
|
|
lwsl_info("%s: expected cont on sid %d (got %d on sid %d)\n",
|
|
__func__, h2n->cont_exp_sid, h2n->type, h2n->sid);
|
|
h2n->cont_exp = 0;
|
|
if (h2n->cont_exp_headers)
|
|
n = H2_ERR_COMPRESSION_ERROR;
|
|
else
|
|
n = H2_ERR_PROTOCOL_ERROR;
|
|
lws_h2_goaway(wsi, n, "Continuation hdrs State");
|
|
|
|
return 0;
|
|
}
|
|
|
|
switch (h2n->type) {
|
|
case LWS_H2_FRAME_TYPE_DATA:
|
|
lwsl_info("seen incoming LWS_H2_FRAME_TYPE_DATA start\n");
|
|
if (!h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "DATA 0 sid");
|
|
break;
|
|
}
|
|
lwsl_info("Frame header DATA: sid %d\n", h2n->sid);
|
|
|
|
if (!h2n->swsi)
|
|
break;
|
|
|
|
h2n->swsi->h2.peer_tx_cr_est -= h2n->length;
|
|
lwsl_debug(" peer_tx_cr_est %d\n",
|
|
h2n->swsi->h2.peer_tx_cr_est);
|
|
if (h2n->swsi->h2.peer_tx_cr_est < 32768) {
|
|
h2n->swsi->h2.peer_tx_cr_est += 65536;
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
|
|
if (!pps)
|
|
return 1;
|
|
pps->u.update_window.sid = h2n->sid;
|
|
pps->u.update_window.credit = 65536;
|
|
lws_pps_schedule(wsi, pps);
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
|
|
if (!pps)
|
|
return 1;
|
|
pps->u.update_window.sid = 0;
|
|
pps->u.update_window.credit = 65536;
|
|
lws_pps_schedule(wsi, pps);
|
|
}
|
|
|
|
if (
|
|
h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE ||
|
|
h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED) {
|
|
lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, "conn closed");
|
|
break;
|
|
}
|
|
break;
|
|
case LWS_H2_FRAME_TYPE_PRIORITY:
|
|
lwsl_info("LWS_H2_FRAME_TYPE_PRIORITY complete frame\n");
|
|
if (!h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Priority has 0 sid");
|
|
break;
|
|
}
|
|
if (h2n->length != 5) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Priority has length other than 5");
|
|
break;
|
|
}
|
|
break;
|
|
case LWS_H2_FRAME_TYPE_PUSH_PROMISE:
|
|
lwsl_info("LWS_H2_FRAME_TYPE_PUSH_PROMISE complete frame\n");
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "Server only");
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_GOAWAY:
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_RST_STREAM:
|
|
if (!h2n->sid)
|
|
return 1;
|
|
if (!h2n->swsi) {
|
|
if (h2n->sid <= h2n->highest_sid_opened)
|
|
break;
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"crazy sid on RST_STREAM");
|
|
return 1;
|
|
}
|
|
if (h2n->length != 4) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"RST_STREAM can only be length 4");
|
|
break;
|
|
}
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_SETTINGS:
|
|
lwsl_info("LWS_H2_FRAME_TYPE_SETTINGS complete frame\n");
|
|
/* nonzero sid on settings is illegal */
|
|
if (h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Settings has nonzero sid");
|
|
break;
|
|
}
|
|
|
|
if (!(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) {
|
|
if ((!h2n->length) || h2n->length % 6) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Settings length error");
|
|
break;
|
|
}
|
|
lwsl_info("scheduled settings ack PPS\n");
|
|
/* non-ACK coming in means we must ACK it */
|
|
|
|
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
|
|
return 0;
|
|
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
|
|
if (!pps)
|
|
return 1;
|
|
lws_pps_schedule(wsi, pps);
|
|
break;
|
|
}
|
|
/* came to us with ACK set... not allowed to have payload */
|
|
|
|
if (h2n->length) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Settings with ACK not allowed payload");
|
|
break;
|
|
}
|
|
break;
|
|
case LWS_H2_FRAME_TYPE_PING:
|
|
if (h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Ping has nonzero sid");
|
|
break;
|
|
}
|
|
if (h2n->length != 8) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Ping payload can only be 8");
|
|
break;
|
|
}
|
|
break;
|
|
case LWS_H2_FRAME_TYPE_CONTINUATION:
|
|
lwsl_info("LWS_H2_FRAME_TYPE_CONTINUATION: sid = %d\n",
|
|
h2n->sid);
|
|
|
|
if (!h2n->cont_exp ||
|
|
h2n->cont_exp_sid != h2n->sid ||
|
|
!h2n->sid ||
|
|
!h2n->swsi) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"unexpected CONTINUATION");
|
|
break;
|
|
}
|
|
if (h2n->swsi->h2.END_HEADERS) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"END_HEADERS already seen");
|
|
break;
|
|
}
|
|
/* END_STREAM is in HEADERS, skip resetting it */
|
|
goto update_end_headers;
|
|
|
|
case LWS_H2_FRAME_TYPE_HEADERS:
|
|
lwsl_info("HEADERS: frame header: sid = %d\n", h2n->sid);
|
|
if (!h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "sid 0");
|
|
return 1;
|
|
}
|
|
|
|
if (!h2n->swsi) {
|
|
/* no more children allowed by parent */
|
|
if (wsi->h2.child_count + 1 >
|
|
wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Another stream not allowed");
|
|
|
|
return 1;
|
|
}
|
|
|
|
h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi,
|
|
h2n->sid);
|
|
if (!h2n->swsi) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "OOM");
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ah needs attaching to child wsi, even though
|
|
* we only fill it from network wsi
|
|
*/
|
|
if (!h2n->swsi->ah)
|
|
if (lws_header_table_attach(h2n->swsi, 0)) {
|
|
lwsl_err("%s: Failed to get ah\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The first use of a new stream identifier implicitly closes
|
|
* all streams in the "idle" state that might have been
|
|
* initiated by that peer with a lower-valued stream identifier.
|
|
*
|
|
* For example, if a client sends a HEADERS frame on stream 7
|
|
* without ever sending a frame on stream 5, then stream 5
|
|
* transitions to the "closed" state when the first frame for
|
|
* stream 7 is sent or received.
|
|
*/
|
|
lws_start_foreach_ll(struct lws *, w, wsi->h2.child_list) {
|
|
if (w->h2.my_sid < h2n->sid &&
|
|
w->h2.h2_state == LWS_H2_STATE_IDLE)
|
|
lws_close_free_wsi(w, 0);
|
|
} lws_end_foreach_ll(w, h2.sibling_list);
|
|
|
|
|
|
/* END_STREAM means after servicing this, close the stream */
|
|
h2n->swsi->h2.END_STREAM =
|
|
!!(h2n->flags & LWS_H2_FLAG_END_STREAM);
|
|
lwsl_info("%s: hdr END_STREAM = %d\n",__func__,
|
|
h2n->swsi->h2.END_STREAM);
|
|
|
|
h2n->cont_exp = !(h2n->flags & LWS_H2_FLAG_END_HEADERS);
|
|
h2n->cont_exp_sid = h2n->sid;
|
|
h2n->cont_exp_headers = 1;
|
|
lws_header_table_reset(h2n->swsi, 0);
|
|
|
|
update_end_headers:
|
|
/* no END_HEADERS means CONTINUATION must come */
|
|
h2n->swsi->h2.END_HEADERS =
|
|
!!(h2n->flags & LWS_H2_FLAG_END_HEADERS);
|
|
if (h2n->swsi->h2.END_HEADERS)
|
|
h2n->cont_exp = 0;
|
|
lwsl_debug("END_HEADERS %d\n", h2n->swsi->h2.END_HEADERS);
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
|
|
if (h2n->length != 4) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"window update frame not 4");
|
|
break;
|
|
}
|
|
lwsl_info("LWS_H2_FRAME_TYPE_WINDOW_UPDATE\n");
|
|
break;
|
|
default:
|
|
lwsl_info("%s: ILLEGAL FRAME TYPE %d\n", __func__, h2n->type);
|
|
h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */
|
|
break;
|
|
}
|
|
if (h2n->length == 0)
|
|
h2n->frame_state = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The last byte of the whole frame has been handled.
|
|
* Perform actions for frame completion.
|
|
*/
|
|
static int
|
|
lws_h2_parse_end_of_frame(struct lws *wsi)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
struct lws *eff_wsi = wsi;
|
|
const char *p;
|
|
int n;
|
|
|
|
h2n->frame_state = 0;
|
|
h2n->count = 0;
|
|
|
|
if (h2n->sid)
|
|
h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid);
|
|
|
|
if (h2n->sid > h2n->highest_sid)
|
|
h2n->highest_sid = h2n->sid;
|
|
|
|
/* set our initial window size */
|
|
if (!wsi->h2.initialized) {
|
|
wsi->h2.tx_cr = h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
lwsl_info("initial tx credit on master %p: %d\n", wsi,
|
|
wsi->h2.tx_cr);
|
|
wsi->h2.initialized = 1;
|
|
}
|
|
|
|
if (h2n->collected_priority && (h2n->dep & ~(1 << 31)) == h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "depends on own sid");
|
|
return 0;
|
|
}
|
|
|
|
switch (h2n->type) {
|
|
case LWS_H2_FRAME_TYPE_CONTINUATION:
|
|
case LWS_H2_FRAME_TYPE_HEADERS:
|
|
|
|
/* service the http request itself */
|
|
|
|
if (h2n->last_action_dyntable_resize) {
|
|
lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR,
|
|
"dyntable resize last in headers");
|
|
break;
|
|
}
|
|
|
|
if (!h2n->swsi->h2.END_HEADERS) {
|
|
/* we are not finished yet */
|
|
lwsl_info("witholding http action for continuation\n");
|
|
break;
|
|
}
|
|
|
|
/* confirm the hpack stream state is reasonable for finishing */
|
|
|
|
if (h2n->hpack != HPKS_TYPE) {
|
|
/* hpack incomplete */
|
|
lwsl_info("hpack incomplete %d (type %d, len %d)\n",
|
|
h2n->hpack, h2n->type, h2n->hpack_len);
|
|
lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR,
|
|
"hpack incomplete");
|
|
break;
|
|
}
|
|
|
|
/* this is the last part of HEADERS */
|
|
switch (h2n->swsi->h2.h2_state) {
|
|
case LWS_H2_STATE_IDLE:
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_OPEN);
|
|
break;
|
|
case LWS_H2_STATE_RESERVED_REMOTE:
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_LOCAL);
|
|
break;
|
|
}
|
|
|
|
lwsl_info("http req, wsi=%p, h2n->swsi=%p\n", wsi, h2n->swsi);
|
|
h2n->swsi->hdr_parsing_completed = 1;
|
|
|
|
if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
|
|
h2n->swsi->http.rx_content_length = atoll(
|
|
lws_hdr_simple_ptr(h2n->swsi,
|
|
WSI_TOKEN_HTTP_CONTENT_LENGTH));
|
|
h2n->swsi->http.rx_content_remain =
|
|
h2n->swsi->http.rx_content_length;
|
|
lwsl_info("setting rx_content_length %lld\n",
|
|
(long long)h2n->swsi->http.rx_content_length);
|
|
}
|
|
|
|
{
|
|
int n = 0, len;
|
|
char buf[256];
|
|
const unsigned char *c;
|
|
|
|
do {
|
|
c = lws_token_to_string(n);
|
|
if (!c) {
|
|
n++;
|
|
continue;
|
|
}
|
|
|
|
len = lws_hdr_total_length(h2n->swsi, n);
|
|
if (!len || len > (int)sizeof(buf) - 1) {
|
|
n++;
|
|
continue;
|
|
}
|
|
|
|
lws_hdr_copy(h2n->swsi, buf, sizeof buf, n);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
|
|
lwsl_info(" %s = %s\n", (char *)c, buf);
|
|
n++;
|
|
} while (c);
|
|
}
|
|
|
|
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE ||
|
|
h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED) {
|
|
lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
|
|
"Banning service on CLOSED_REMOTE");
|
|
break;
|
|
}
|
|
|
|
switch (h2n->swsi->h2.h2_state) {
|
|
case LWS_H2_STATE_OPEN:
|
|
if (h2n->swsi->h2.END_STREAM)
|
|
lws_h2_state(h2n->swsi,
|
|
LWS_H2_STATE_HALF_CLOSED_REMOTE);
|
|
break;
|
|
case LWS_H2_STATE_HALF_CLOSED_LOCAL:
|
|
if (h2n->swsi->h2.END_STREAM)
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
|
|
break;
|
|
}
|
|
|
|
if (!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_PATH) ||
|
|
!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD) ||
|
|
!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_SCHEME) ||
|
|
lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_STATUS) ||
|
|
lws_hdr_extant(h2n->swsi, WSI_TOKEN_CONNECTION)) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Pseudoheader checks");
|
|
break;
|
|
}
|
|
|
|
if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_TE)) {
|
|
n = lws_hdr_total_length(h2n->swsi, WSI_TOKEN_TE);
|
|
|
|
if (n != 8 ||
|
|
strncmp(lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_TE),
|
|
"trailers", n)) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Illegal transfer-encoding");
|
|
break;
|
|
}
|
|
}
|
|
|
|
p = lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD);
|
|
if (!strcmp(p, "POST"))
|
|
h2n->swsi->ah->frag_index[WSI_TOKEN_POST_URI] =
|
|
h2n->swsi->ah->frag_index[WSI_TOKEN_HTTP_COLON_PATH];
|
|
|
|
wsi->vhost->conn_stats.h2_trans++;
|
|
|
|
lwsl_info(" action start...\n");
|
|
n = lws_http_action(h2n->swsi);
|
|
lwsl_info(" action result %d "
|
|
"(wsi->http.rx_content_remain %lld)\n",
|
|
n, h2n->swsi->http.rx_content_remain);
|
|
|
|
/*
|
|
* Commonly we only managed to start a larger transfer that will
|
|
* complete asynchronously. In those cases we will hear about
|
|
* END_STREAM going out in the POLLOUT handler.
|
|
*/
|
|
if (n || h2n->swsi->h2.send_END_STREAM) {
|
|
lws_close_free_wsi(h2n->swsi, 0);
|
|
h2n->swsi = NULL;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_DATA:
|
|
if (!h2n->swsi)
|
|
break;
|
|
|
|
if (lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
|
|
h2n->swsi->h2.END_STREAM &&
|
|
h2n->swsi->http.rx_content_length &&
|
|
h2n->swsi->http.rx_content_remain) {
|
|
lws_h2_rst_stream(h2n->swsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Not enough rx content");
|
|
break;
|
|
}
|
|
|
|
if (h2n->swsi->h2.END_STREAM &&
|
|
h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN)
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_REMOTE);
|
|
|
|
if (h2n->swsi->h2.END_STREAM &&
|
|
h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_PING:
|
|
if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack
|
|
} else {/* they're sending us a ping request */
|
|
lwsl_info("rx ping, preparing pong\n");
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_PONG);
|
|
if (!pps)
|
|
return 1;
|
|
memcpy(pps->u.ping.ping_payload, h2n->ping_payload, 8);
|
|
lws_pps_schedule(wsi, pps);
|
|
}
|
|
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
|
|
h2n->hpack_e_dep &= ~(1 << 31);
|
|
lwsl_info("WINDOW_UPDATE: sid %d %u (0x%x)\n", h2n->sid,
|
|
h2n->hpack_e_dep, h2n->hpack_e_dep);
|
|
|
|
if (h2n->sid)
|
|
eff_wsi = h2n->swsi;
|
|
|
|
if (!eff_wsi) {
|
|
if (h2n->sid > h2n->highest_sid_opened)
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"alien sid");
|
|
break; /* ignore */
|
|
}
|
|
|
|
if ((uint64_t)eff_wsi->h2.tx_cr + (uint64_t)h2n->hpack_e_dep >
|
|
(uint64_t)0x7fffffff) {
|
|
if (h2n->sid)
|
|
lws_h2_rst_stream(h2n->swsi,
|
|
H2_ERR_FLOW_CONTROL_ERROR,
|
|
"Flow control exceeded max");
|
|
else
|
|
lws_h2_goaway(wsi, H2_ERR_FLOW_CONTROL_ERROR,
|
|
"Flow control exceeded max");
|
|
break;
|
|
}
|
|
|
|
if (!h2n->hpack_e_dep) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Zero length window update");
|
|
break;
|
|
}
|
|
n = eff_wsi->h2.tx_cr;
|
|
eff_wsi->h2.tx_cr += h2n->hpack_e_dep;
|
|
|
|
if (n <= 0 && eff_wsi->h2.tx_cr <= 0)
|
|
/* it helps, but won't change sendability for anyone */
|
|
break;
|
|
|
|
/*
|
|
* It did change sendability... for us and any children waiting
|
|
* on us... reassess blockage for all children first
|
|
*/
|
|
lws_start_foreach_ll(struct lws *, w, wsi->h2.child_list) {
|
|
lws_callback_on_writable(w);
|
|
} lws_end_foreach_ll(w, h2.sibling_list);
|
|
|
|
if (eff_wsi->h2.skint && lws_h2_tx_cr_get(eff_wsi)) {
|
|
lwsl_info("%s: %p: skint\n", __func__, wsi);
|
|
eff_wsi->h2.skint = 0;
|
|
lws_callback_on_writable(eff_wsi);
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_GOAWAY:
|
|
lwsl_info("GOAWAY: last sid %d, error 0x%08X, string '%s'\n",
|
|
h2n->goaway_last_sid, h2n->goaway_err,
|
|
h2n->goaway_str);
|
|
wsi->h2.GOING_AWAY = 1;
|
|
|
|
return 1;
|
|
|
|
case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_h2_parser(struct lws *wsi, unsigned char c)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
int n;
|
|
|
|
if (!h2n)
|
|
return 1;
|
|
|
|
switch (wsi->state) {
|
|
case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
|
|
if (preface[h2n->count++] != c)
|
|
return 1;
|
|
|
|
if (preface[h2n->count])
|
|
break;
|
|
|
|
lwsl_info("http2: %p: established\n", wsi);
|
|
wsi->state = LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS;
|
|
h2n->count = 0;
|
|
wsi->h2.tx_cr = 65535;
|
|
|
|
/*
|
|
* we must send a settings frame -- empty one is OK...
|
|
* that must be the first thing sent by server
|
|
* and the peer must send a SETTINGS with ACK flag...
|
|
*/
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS);
|
|
if (!pps)
|
|
return 1;
|
|
lws_pps_schedule(wsi, pps);
|
|
break;
|
|
|
|
case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
|
|
case LWSS_HTTP2_ESTABLISHED:
|
|
if (h2n->frame_state != LWS_H2_FRAME_HEADER_LENGTH)
|
|
goto try_frame_start;
|
|
|
|
/*
|
|
* post-header, preamble / payload / padding part
|
|
*/
|
|
h2n->count++;
|
|
|
|
if (h2n->flags & LWS_H2_FLAG_PADDED && !h2n->pad_length) {
|
|
/*
|
|
* Get the padding count... actual padding is
|
|
* at the end of the frame.
|
|
*/
|
|
h2n->padding = c;
|
|
h2n->pad_length = 1;
|
|
h2n->preamble++;
|
|
|
|
if (h2n->padding > h2n->length - 1)
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"execssive padding");
|
|
break; /* we consumed this */
|
|
}
|
|
|
|
if (h2n->flags & LWS_H2_FLAG_PRIORITY &&
|
|
!h2n->collected_priority) {
|
|
/* going to be 5 preamble bytes */
|
|
|
|
lwsl_debug("PRIORITY FLAG: 0x%x\n", c);
|
|
|
|
if (h2n->preamble++ - h2n->pad_length < 4) {
|
|
h2n->dep = ((h2n->dep) << 8) | c;
|
|
break; /* we consumed this */
|
|
}
|
|
h2n->weight_temp = c;
|
|
h2n->collected_priority = 1;
|
|
lwsl_debug("PRI FL: dep 0x%x, weight 0x%02X\n",
|
|
h2n->dep, h2n->weight_temp);
|
|
break; /* we consumed this */
|
|
}
|
|
if (h2n->padding && h2n->count > (h2n->length - h2n->padding)) {
|
|
if (c) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"nonzero padding");
|
|
break;
|
|
}
|
|
goto frame_end;
|
|
}
|
|
|
|
/* applies to wsi->h2.swsi which may be wsi */
|
|
switch(h2n->type) {
|
|
|
|
case LWS_H2_FRAME_TYPE_SETTINGS:
|
|
n = (h2n->count - 1 - h2n->preamble) %
|
|
LWS_H2_SETTINGS_LEN;
|
|
h2n->one_setting[n] = c;
|
|
if (n != LWS_H2_SETTINGS_LEN - 1)
|
|
break;
|
|
lws_h2_settings(wsi, &h2n->set, h2n->one_setting,
|
|
LWS_H2_SETTINGS_LEN);
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_CONTINUATION:
|
|
case LWS_H2_FRAME_TYPE_HEADERS:
|
|
if (!h2n->swsi)
|
|
break;
|
|
if (lws_hpack_interpret(h2n->swsi, c)) {
|
|
lwsl_info("%s: hpack failed\n", __func__);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_GOAWAY:
|
|
switch (h2n->inside++) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
h2n->goaway_last_sid <<= 8;
|
|
h2n->goaway_last_sid |= c;
|
|
h2n->goaway_str[0] = '\0';
|
|
break;
|
|
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
h2n->goaway_err <<= 8;
|
|
h2n->goaway_err |= c;
|
|
break;
|
|
|
|
default:
|
|
if (h2n->inside - 9 <
|
|
sizeof(h2n->goaway_str) - 1)
|
|
h2n->goaway_str[h2n->inside - 9] = c;
|
|
h2n->goaway_str[sizeof(h2n->goaway_str) - 1] = '\0';
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_DATA:
|
|
//lwsl_notice("incoming LWS_H2_FRAME_TYPE_DATA content\n");
|
|
if (!h2n->swsi) {
|
|
//lwsl_notice("data sid %d has no swsi\n", h2n->sid);
|
|
break;
|
|
}
|
|
|
|
h2n->swsi->state = LWSS_HTTP_BODY;
|
|
h2n->inside++;
|
|
if (lws_hdr_total_length(h2n->swsi,
|
|
WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
|
|
h2n->swsi->http.rx_content_length &&
|
|
h2n->swsi->http.rx_content_remain == 1 && /* last */
|
|
h2n->inside < h2n->length) { /* unread data in frame */
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"More rx than content_length told");
|
|
break;
|
|
}
|
|
|
|
n = lws_read(h2n->swsi, &c, 1);
|
|
if (n < 0) {
|
|
// lws_h2_rst_stream(wsi, LWS_H2_PPS_RST_STREAM,
|
|
// "post body done");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_PRIORITY:
|
|
if (h2n->count <= 4) {
|
|
h2n->dep <<= 8;
|
|
h2n->dep |= c;
|
|
} else {
|
|
h2n->weight_temp = c;
|
|
lwsl_info("PRIORITY: dep 0x%x, weight 0x%02X\n",
|
|
h2n->dep, h2n->weight_temp);
|
|
|
|
if ((h2n->dep & ~(1 << 31)) == h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"cant depend on own sid");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_RST_STREAM:
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_PUSH_PROMISE:
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_PING:
|
|
if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack
|
|
} else { /* they're sending us a ping request */
|
|
if (h2n->count > 8)
|
|
return 1;
|
|
h2n->ping_payload[h2n->count - 1] = c;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
|
|
h2n->hpack_e_dep <<= 8;
|
|
h2n->hpack_e_dep |= c;
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */
|
|
break;
|
|
|
|
default:
|
|
lwsl_notice("%s: unhandled frame type %d\n",
|
|
__func__, h2n->type);
|
|
|
|
return 1;
|
|
}
|
|
|
|
frame_end:
|
|
if (h2n->count != h2n->length)
|
|
break;
|
|
|
|
/*
|
|
* end of frame just happened
|
|
*/
|
|
if (lws_h2_parse_end_of_frame(wsi))
|
|
return 1;
|
|
break;
|
|
|
|
try_frame_start:
|
|
if (h2n->frame_state <= 8) {
|
|
|
|
switch (h2n->frame_state++) {
|
|
case 0:
|
|
h2n->pad_length = 0;
|
|
h2n->collected_priority = 0;
|
|
h2n->padding = 0;
|
|
h2n->preamble = 0;
|
|
h2n->length = c;
|
|
h2n->inside = 0;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
h2n->length <<= 8;
|
|
h2n->length |= c;
|
|
break;
|
|
case 3:
|
|
h2n->type = c;
|
|
break;
|
|
case 4:
|
|
h2n->flags = c;
|
|
break;
|
|
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
h2n->sid <<= 8;
|
|
h2n->sid |= c;
|
|
break;
|
|
}
|
|
}
|
|
if (h2n->frame_state == LWS_H2_FRAME_HEADER_LENGTH)
|
|
if (lws_h2_parse_frame_header(wsi))
|
|
return 1;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|