mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-24 01:39:33 +00:00
2922 lines
77 KiB
C
2922 lines
77 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* 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 "private-lib-core.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",
|
|
"reserved",
|
|
"H2SET_ENABLE_CONNECT_PROTOCOL"
|
|
};
|
|
|
|
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
|
|
|
|
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;
|
|
}
|
|
|
|
void lws_h2_init(struct lws *wsi)
|
|
{
|
|
wsi->h2.h2n->our_set = wsi->a.vhost->h2.set;
|
|
wsi->h2.h2n->peer_set = lws_h2_defaults;
|
|
}
|
|
|
|
void
|
|
lws_h2_state(struct lws *wsi, enum lws_h2_states s)
|
|
{
|
|
if (!wsi)
|
|
return;
|
|
lwsl_info("%s: %s: state %s -> %s\n", __func__, lws_wsi_tag(wsi),
|
|
h2_state_names[wsi->h2.h2_state],
|
|
h2_state_names[s]);
|
|
|
|
(void)h2_state_names;
|
|
wsi->h2.h2_state = (uint8_t)s;
|
|
}
|
|
|
|
int
|
|
lws_h2_update_peer_txcredit(struct lws *wsi, unsigned int sid, int bump)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
struct lws_h2_protocol_send *pps;
|
|
|
|
assert(wsi);
|
|
|
|
if (!bump)
|
|
return 0;
|
|
|
|
if (sid == (unsigned int)-1)
|
|
sid = wsi->mux.my_sid;
|
|
|
|
lwsl_info("%s: sid %d: bump %d -> %d\n", __func__, sid, bump,
|
|
(int)wsi->txc.peer_tx_cr_est + bump);
|
|
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
|
|
if (!pps)
|
|
return 1;
|
|
|
|
pps->u.update_window.sid = (unsigned int)sid;
|
|
pps->u.update_window.credit = (unsigned int)bump;
|
|
wsi->txc.peer_tx_cr_est += bump;
|
|
|
|
lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);
|
|
|
|
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 = (unsigned int)bump;
|
|
nwsi->txc.peer_tx_cr_est += bump;
|
|
|
|
lws_wsi_txc_describe(&nwsi->txc, __func__, nwsi->mux.my_sid);
|
|
|
|
lws_pps_schedule(nwsi, pps);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_h2_get_peer_txcredit_estimate(struct lws *wsi)
|
|
{
|
|
lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);
|
|
return (int)wsi->txc.peer_tx_cr_est;
|
|
}
|
|
|
|
static int
|
|
lws_h2_update_peer_txcredit_thresh(struct lws *wsi, unsigned int sid, int threshold, int bump)
|
|
{
|
|
if (wsi->txc.peer_tx_cr_est > threshold)
|
|
return 0;
|
|
|
|
return lws_h2_update_peer_txcredit(wsi, sid, bump);
|
|
}
|
|
|
|
/* cx + vh lock */
|
|
|
|
static struct lws *
|
|
__lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
|
|
unsigned int sid)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(parent_wsi);
|
|
struct lws_h2_netconn *h2n = nwsi->h2.h2n;
|
|
char tmp[50], tmp1[50];
|
|
unsigned int n, b = 0;
|
|
struct lws *wsi;
|
|
const char *p;
|
|
|
|
lws_context_assert_lock_held(vh->context);
|
|
lws_vhost_assert_lock_held(vh);
|
|
|
|
/*
|
|
* 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) {
|
|
lwsl_info("%s: tried to open lower sid %d (%d)\n", __func__,
|
|
sid, (int)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->mux.child_count + 1 >
|
|
parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
|
|
lwsl_notice("reached concurrent stream limit\n");
|
|
return NULL;
|
|
}
|
|
|
|
n = 0;
|
|
p = &parent_wsi->lc.gutag[1];
|
|
do {
|
|
if (*p == '|') {
|
|
b++;
|
|
if (b == 3)
|
|
continue;
|
|
}
|
|
tmp1[n++] = *p++;
|
|
} while (b < 3 && n < sizeof(tmp1) - 2);
|
|
tmp1[n] = '\0';
|
|
lws_snprintf(tmp, sizeof(tmp), "h2_sid%u_(%s)", sid, tmp1);
|
|
wsi = lws_create_new_server_wsi(vh, parent_wsi->tsi, LWSLCG_WSI_MUX, tmp);
|
|
if (!wsi) {
|
|
lwsl_notice("new server wsi failed (%s)\n", lws_vh_tag(vh));
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(LWS_WITH_SERVER)
|
|
if (lwsi_role_server(parent_wsi)) {
|
|
lws_metrics_caliper_bind(wsi->cal_conn, wsi->a.context->mth_srv);
|
|
}
|
|
#endif
|
|
|
|
h2n->highest_sid_opened = sid;
|
|
|
|
lws_wsi_mux_insert(wsi, parent_wsi, sid);
|
|
if (sid >= h2n->highest_sid)
|
|
h2n->highest_sid = sid + 2;
|
|
|
|
wsi->mux_substream = 1;
|
|
wsi->seen_nonpseudoheader = 0;
|
|
|
|
wsi->txc.tx_cr = (int32_t)nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
wsi->txc.peer_tx_cr_est =
|
|
(int32_t)nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
|
|
lwsi_set_state(wsi, LRS_ESTABLISHED);
|
|
lwsi_set_role(wsi, lwsi_role(parent_wsi));
|
|
|
|
wsi->a.protocol = &vh->protocols[0];
|
|
if (lws_ensure_user_space(wsi))
|
|
goto bail1;
|
|
|
|
#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS)
|
|
if (lws_adopt_ss_server_accept(wsi))
|
|
goto bail1;
|
|
#endif
|
|
|
|
/* get the ball rolling */
|
|
lws_validity_confirmed(wsi);
|
|
|
|
lwsl_info("%s: %s new ch %s, sid %d, usersp=%p\n", __func__,
|
|
lws_wsi_tag(parent_wsi), lws_wsi_tag(wsi), sid, wsi->user_space);
|
|
|
|
lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);
|
|
lws_wsi_txc_describe(&nwsi->txc, __func__, 0);
|
|
|
|
return wsi;
|
|
|
|
bail1:
|
|
/* undo the insert */
|
|
parent_wsi->mux.child_list = wsi->mux.sibling_list;
|
|
parent_wsi->mux.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_vhost_unbind_wsi(wsi);
|
|
lws_free(wsi);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct lws *
|
|
lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)
|
|
{
|
|
struct lws *nwsi = lws_get_network_wsi(parent_wsi);
|
|
|
|
/* no more children allowed by parent */
|
|
if (parent_wsi->mux.child_count + 1 >
|
|
parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
|
|
lwsl_notice("reached concurrent stream limit\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* sid is set just before issuing the headers, ensuring monoticity */
|
|
|
|
wsi->seen_nonpseudoheader = 0;
|
|
#if defined(LWS_WITH_CLIENT)
|
|
wsi->client_mux_substream = 1;
|
|
#endif
|
|
wsi->h2.initialized = 1;
|
|
|
|
#if 0
|
|
/* only assign sid at header send time when we know it */
|
|
if (!wsi->mux.my_sid) {
|
|
wsi->mux.my_sid = nwsi->h2.h2n->highest_sid;
|
|
nwsi->h2.h2n->highest_sid += 2;
|
|
}
|
|
#endif
|
|
|
|
lwsl_info("%s: binding wsi %s to sid %d (next %d)\n", __func__,
|
|
lws_wsi_tag(wsi), (int)wsi->mux.my_sid, (int)nwsi->h2.h2n->highest_sid);
|
|
|
|
lws_wsi_mux_insert(wsi, parent_wsi, wsi->mux.my_sid);
|
|
|
|
wsi->txc.tx_cr = (int32_t)nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
wsi->txc.peer_tx_cr_est = (int32_t)
|
|
nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
|
|
lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);
|
|
|
|
if (lws_ensure_user_space(wsi))
|
|
goto bail1;
|
|
|
|
lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS,
|
|
&role_ops_h2);
|
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
return wsi;
|
|
|
|
bail1:
|
|
/* undo the insert */
|
|
parent_wsi->mux.child_list = wsi->mux.sibling_list;
|
|
parent_wsi->mux.child_count--;
|
|
|
|
if (wsi->user_space)
|
|
lws_free_set_NULL(wsi->user_space);
|
|
wsi->a.protocol->callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
|
|
lws_free(wsi);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
lws_h2_issue_preface(struct lws *wsi)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
|
|
if (!h2n) {
|
|
lwsl_warn("%s: no valid h2n\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
if (h2n->sent_preface)
|
|
return 1;
|
|
|
|
lwsl_debug("%s: %s: fd %d\n", __func__, lws_wsi_tag(wsi), (int)wsi->desc.sockfd);
|
|
|
|
if (lws_issue_raw(wsi, (uint8_t *)preface, strlen(preface)) !=
|
|
(int)strlen(preface))
|
|
return 1;
|
|
|
|
h2n->sent_preface = 1;
|
|
|
|
lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS,
|
|
&role_ops_h2);
|
|
|
|
h2n->count = 0;
|
|
wsi->txc.tx_cr = 65535;
|
|
|
|
/*
|
|
* we must send a settings frame
|
|
*/
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS);
|
|
if (!pps)
|
|
return 1;
|
|
lws_pps_schedule(wsi, pps);
|
|
lwsl_info("%s: h2 client sending settings\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
if (!h2n) {
|
|
lwsl_warn("%s: null h2n\n", __func__);
|
|
lws_free(pps);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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: %s: ERR 0x%x, '%s'\n", __func__, lws_wsi_tag(wsi), (int)err, reason);
|
|
|
|
pps->u.ga.err = err;
|
|
pps->u.ga.highest_sid = h2n->highest_sid;
|
|
lws_strncpy(pps->u.ga.str, reason, sizeof(pps->u.ga.str));
|
|
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)
|
|
return 0;
|
|
|
|
if (!wsi->h2_stream_carries_ws && 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, sid %d, REASON '%s'\n", __func__,
|
|
(int)err, wsi->mux.my_sid, reason);
|
|
|
|
pps->u.rs.sid = wsi->mux.my_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 = (unsigned int)((buf[0] << 8) | buf[1]);
|
|
if (!a || a >= H2SET_COUNT)
|
|
goto skip;
|
|
b = (unsigned int)(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,
|
|
"Initial Window beyond max");
|
|
return 1;
|
|
}
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
|
|
if (
|
|
#else
|
|
if (wsi->flags & LCCSCF_H2_QUIRK_OVERFLOWS_TXCR &&
|
|
#endif
|
|
b == 0x7fffffff) {
|
|
b >>= 4;
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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->mux.child_list) {
|
|
lwsl_info("%s: adi child tc cr %d +%d -> %d",
|
|
__func__, (int)w->txc.tx_cr,
|
|
b - (unsigned int)settings->s[a],
|
|
(int)(w->txc.tx_cr + (int)b -
|
|
(int)settings->s[a]));
|
|
w->txc.tx_cr += (int)b - (int)settings->s[a];
|
|
if (w->txc.tx_cr > 0 &&
|
|
w->txc.tx_cr <=
|
|
(int32_t)(b - settings->s[a]))
|
|
|
|
lws_callback_on_writable(w);
|
|
} lws_end_foreach_ll(w, mux.sibling_list);
|
|
|
|
break;
|
|
case H2SET_MAX_FRAME_SIZE:
|
|
if (b < wsi->a.vhost->h2.set.s[H2SET_MAX_FRAME_SIZE]) {
|
|
lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Frame size < initial");
|
|
return 1;
|
|
}
|
|
if (b > 0x00ffffff) {
|
|
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->txc.tx_cr;
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
|
|
if (!wsi->mux_substream && !nwsi->upgraded_to_http2)
|
|
return ~0x80000000;
|
|
|
|
lwsl_info ("%s: %s: own tx credit %d: nwsi credit %d\n",
|
|
__func__, lws_wsi_tag(wsi), c, (int)nwsi->txc.tx_cr);
|
|
|
|
if (nwsi->txc.tx_cr < c)
|
|
c = nwsi->txc.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->txc.tx_cr -= consumed;
|
|
|
|
if (nwsi != wsi)
|
|
nwsi->txc.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;
|
|
|
|
//if (wsi->h2_stream_carries_ws)
|
|
// lwsl_hexdump_level(LLL_NOTICE, buf, len);
|
|
|
|
*p++ = (uint8_t)(len >> 16);
|
|
*p++ = (uint8_t)(len >> 8);
|
|
*p++ = (uint8_t)len;
|
|
*p++ = (uint8_t)type;
|
|
*p++ = (uint8_t)flags;
|
|
*p++ = (uint8_t)(sid >> 24);
|
|
*p++ = (uint8_t)(sid >> 16);
|
|
*p++ = (uint8_t)(sid >> 8);
|
|
*p++ = (uint8_t)sid;
|
|
|
|
lwsl_debug("%s: %s (eff %s). typ %d, fl 0x%x, sid=%d, len=%d, "
|
|
"txcr=%d, nwsi->txcr=%d\n", __func__, lws_wsi_tag(wsi),
|
|
lws_wsi_tag(nwsi), type, flags,
|
|
sid, len, (int)wsi->txc.tx_cr, (int)nwsi->txc.tx_cr);
|
|
|
|
if (type == LWS_H2_FRAME_TYPE_DATA) {
|
|
if (wsi->txc.tx_cr < (int)len)
|
|
|
|
lwsl_info("%s: %s: sending payload len %d"
|
|
" but tx_cr only %d!\n", __func__,
|
|
lws_wsi_tag(wsi), len, (int)wsi->txc.tx_cr);
|
|
lws_h2_tx_cr_consume(wsi, (int)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++ = (uint8_t)(n >> 8);
|
|
*buf++ = (uint8_t)n;
|
|
*buf++ = (uint8_t)(wsi->h2.h2n->our_set.s[n] >> 24);
|
|
*buf++ = (uint8_t)(wsi->h2.h2n->our_set.s[n] >> 16);
|
|
*buf++ = (uint8_t)(wsi->h2.h2n->our_set.s[n] >> 8);
|
|
*buf = (uint8_t)wsi->h2.h2n->our_set.s[n];
|
|
}
|
|
|
|
/* we get called on the network connection */
|
|
|
|
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, flags = 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: %s: %d\n", __func__, lws_wsi_tag(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 peer
|
|
*/
|
|
for (n = 1; n < H2SET_COUNT; n++)
|
|
if (h2n->our_set.s[n] != lws_h2_defaults.s[n]) {
|
|
lwsl_debug("sending SETTING %d 0x%x\n", n,
|
|
(unsigned int)
|
|
wsi->h2.h2n->our_set.s[n]);
|
|
|
|
lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]);
|
|
m += (int)sizeof(h2n->one_setting);
|
|
}
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
|
|
flags, LWS_H2_STREAM_ID_MASTER, (unsigned int)m,
|
|
&set[LWS_PRE]);
|
|
if (n != m) {
|
|
lwsl_info("send %d %d\n", n, m);
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_PPS_SETTINGS_INITIAL_UPDATE_WINDOW:
|
|
q = &set[LWS_PRE];
|
|
*q++ = (uint8_t)(H2SET_INITIAL_WINDOW_SIZE >> 8);
|
|
*q++ = (uint8_t)(H2SET_INITIAL_WINDOW_SIZE);
|
|
*q++ = (uint8_t)(pps->u.update_window.credit >> 24);
|
|
*q++ = (uint8_t)(pps->u.update_window.credit >> 16);
|
|
*q++ = (uint8_t)(pps->u.update_window.credit >> 8);
|
|
*q = (uint8_t)(pps->u.update_window.credit);
|
|
|
|
lwsl_debug("%s: resetting initial window to %d\n", __func__,
|
|
(int)pps->u.update_window.credit);
|
|
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
|
|
flags, LWS_H2_STREAM_ID_MASTER, 6,
|
|
&set[LWS_PRE]);
|
|
if (n != 6) {
|
|
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("%s: writing settings ack frame failed %d\n", __func__, n);
|
|
goto bail;
|
|
}
|
|
wsi->h2_acked_settings = 0;
|
|
/* this is the end of the preface dance then? */
|
|
if (lwsi_state(wsi) == LRS_H2_AWAIT_SETTINGS) {
|
|
lwsi_set_state(wsi, LRS_ESTABLISHED);
|
|
#if defined(LWS_WITH_FILE_OPS)
|
|
wsi->http.fop_fd = NULL;
|
|
#endif
|
|
if (lws_is_ssl(lws_get_network_wsi(wsi)))
|
|
break;
|
|
|
|
if (wsi->a.vhost->options &
|
|
LWS_SERVER_OPTION_H2_PRIOR_KNOWLEDGE)
|
|
break;
|
|
|
|
/*
|
|
* we need to treat the headers from the upgrade as the
|
|
* first job. So these need to get shifted to sid 1.
|
|
*/
|
|
|
|
lws_context_lock(wsi->a.context, "h2 mig");
|
|
lws_vhost_lock(wsi->a.vhost);
|
|
|
|
h2n->swsi = __lws_wsi_server_new(wsi->a.vhost, wsi, 1);
|
|
|
|
lws_vhost_unlock(wsi->a.vhost);
|
|
lws_context_unlock(wsi->a.context);
|
|
|
|
if (!h2n->swsi)
|
|
goto bail;
|
|
|
|
/* pass on the initial headers to SID 1 */
|
|
h2n->swsi->http.ah = wsi->http.ah;
|
|
wsi->http.ah = NULL;
|
|
|
|
lwsl_info("%s: inherited headers %p\n", __func__,
|
|
h2n->swsi->http.ah);
|
|
h2n->swsi->txc.tx_cr = (int32_t)
|
|
h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
lwsl_info("initial tx credit on %s: %d\n",
|
|
lws_wsi_tag(h2n->swsi),
|
|
(int)h2n->swsi->txc.tx_cr);
|
|
h2n->swsi->h2.initialized = 1;
|
|
/* demanded by HTTP2 */
|
|
h2n->swsi->h2.END_STREAM = 1;
|
|
lwsl_info("servicing initial http request\n");
|
|
|
|
#if defined(LWS_WITH_SERVER)
|
|
if (lws_http_action(h2n->swsi))
|
|
goto bail;
|
|
#endif
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* h2 only has PING... ACK = 0 = ping, ACK = 1 = pong
|
|
*/
|
|
|
|
case LWS_H2_PPS_PING:
|
|
case LWS_H2_PPS_PONG:
|
|
if (pps->type == LWS_H2_PPS_PING)
|
|
lwsl_info("sending PING\n");
|
|
else {
|
|
lwsl_info("sending PONG\n");
|
|
flags = LWS_H2_FLAG_SETTINGS_ACK;
|
|
}
|
|
|
|
memcpy(&set[LWS_PRE], pps->u.ping.ping_payload, 8);
|
|
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_PING, flags,
|
|
LWS_H2_STREAM_ID_MASTER, 8,
|
|
&set[LWS_PRE]);
|
|
if (n != 8)
|
|
goto bail;
|
|
|
|
break;
|
|
|
|
case LWS_H2_PPS_GOAWAY:
|
|
lwsl_info("LWS_H2_PPS_GOAWAY\n");
|
|
*p++ = (uint8_t)(pps->u.ga.highest_sid >> 24);
|
|
*p++ = (uint8_t)(pps->u.ga.highest_sid >> 16);
|
|
*p++ = (uint8_t)(pps->u.ga.highest_sid >> 8);
|
|
*p++ = (uint8_t)(pps->u.ga.highest_sid);
|
|
*p++ = (uint8_t)(pps->u.ga.err >> 24);
|
|
*p++ = (uint8_t)(pps->u.ga.err >> 16);
|
|
*p++ = (uint8_t)(pps->u.ga.err >> 8);
|
|
*p++ = (uint8_t)(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,
|
|
(unsigned int)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++ = (uint8_t)(pps->u.rs.err >> 24);
|
|
*p++ = (uint8_t)(pps->u.rs.err >> 16);
|
|
*p++ = (uint8_t)(pps->u.rs.err >> 8);
|
|
*p++ = (uint8_t)(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_wsi_mux_from_id(wsi, pps->u.rs.sid);
|
|
if (cwsi) {
|
|
lwsl_debug("%s: closing cwsi %s %s %s (wsi %s)\n",
|
|
__func__, lws_wsi_tag(cwsi),
|
|
cwsi->role_ops->name,
|
|
cwsi->a.protocol->name, lws_wsi_tag(wsi));
|
|
lws_close_free_wsi(cwsi, 0, "reset stream");
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_PPS_UPDATE_WINDOW:
|
|
lwsl_info("Issuing LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n",
|
|
(int)pps->u.update_window.sid,
|
|
(int)pps->u.update_window.credit);
|
|
*p++ = (uint8_t)((pps->u.update_window.credit >> 24) & 0x7f); /* 31b */
|
|
*p++ = (uint8_t)(pps->u.update_window.credit >> 16);
|
|
*p++ = (uint8_t)(pps->u.update_window.credit >> 8);
|
|
*p++ = (uint8_t)(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;
|
|
}
|
|
|
|
static int
|
|
lws_h2_parse_end_of_frame(struct lws *wsi);
|
|
|
|
/*
|
|
* The frame header part has just completely arrived.
|
|
* Perform actions for header 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)) {
|
|
char pes[32];
|
|
lws_snprintf(pes, sizeof(pes), "Even Stream ID 0x%x", (unsigned int)h2n->sid);
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, pes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* let the network wsi live a bit longer if subs are active */
|
|
|
|
if (!wsi->immortal_substream_count)
|
|
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
|
|
wsi->a.vhost->keepalive_timeout ?
|
|
wsi->a.vhost->keepalive_timeout : 31);
|
|
|
|
if (h2n->sid)
|
|
h2n->swsi = lws_wsi_mux_from_id(wsi, h2n->sid);
|
|
|
|
lwsl_debug("%s (%s): fr hdr: typ 0x%x, fla 0x%x, sid 0x%x, len 0x%x\n",
|
|
lws_wsi_tag(wsi), lws_wsi_tag(h2n->swsi), h2n->type,
|
|
h2n->flags, (unsigned int)h2n->sid, (unsigned int)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) {
|
|
lwsl_info("%s: ignoring unknown frame type %d (len %d)\n", __func__, h2n->type, (unsigned int)h2n->length);
|
|
/* we MUST ignore frames we don't understand */
|
|
h2n->type = LWS_H2_FRAME_TYPE_COUNT;
|
|
}
|
|
|
|
/*
|
|
* Even if we have decided to logically ignore this frame, we must
|
|
* consume the correct "frame length" amount of data to retain sync
|
|
*/
|
|
|
|
if (h2n->length > h2n->our_set.s[H2SET_MAX_FRAME_SIZE]) {
|
|
/*
|
|
* peer sent us something bigger than we told
|
|
* it we would allow
|
|
*/
|
|
lwsl_info("%s: received oversize frame %d\n", __func__,
|
|
(unsigned int)h2n->length);
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Peer ignored our frame size setting");
|
|
return 1;
|
|
}
|
|
|
|
if (h2n->swsi)
|
|
lwsl_info("%s: %s, State: %s, received cmd %d\n",
|
|
__func__, lws_wsi_tag(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) {
|
|
if (h2n->sid <= h2n->highest_sid_opened
|
|
#if defined(LWS_WITH_CLIENT)
|
|
&& wsi->client_h2_alpn
|
|
#endif
|
|
) {
|
|
if (h2n->flags & LWS_H2_FLAG_END_STREAM)
|
|
lwsl_notice("%s: stragging EOS\n", __func__);
|
|
else {
|
|
lwsl_wsi_notice(wsi, "ignoring straggling "
|
|
"DATA (flags 0x%x, length %d)",
|
|
h2n->flags, (int)h2n->length);
|
|
/* ie, IGNORE */
|
|
h2n->type = LWS_H2_FRAME_TYPE_COUNT;
|
|
}
|
|
} else {
|
|
lwsl_info("%s: received %d bytes data for unknown sid %d, highest known %d\n",
|
|
__func__, (int)h2n->length, (int)h2n->sid, (int)h2n->highest_sid_opened);
|
|
|
|
// if (h2n->sid > h2n->highest_sid_opened) {
|
|
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: %s, No child for sid %d, rxcmd %d\n",
|
|
__func__, lws_wsi_tag(h2n->swsi), (unsigned int)h2n->sid, h2n->type);
|
|
lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
|
|
"Data for nonexistent sid");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (h2n->swsi && h2n->sid && h2n->type != LWS_H2_FRAME_TYPE_COUNT &&
|
|
!(http2_rx_validity[h2n->swsi->h2.h2_state] & (1 << h2n->type))) {
|
|
lwsl_info("%s: %s, State: %s, ILLEGAL cmdrx %d (OK 0x%x)\n",
|
|
__func__, lws_wsi_tag(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, (unsigned int)n, "invalid rx for state");
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (h2n->cont_exp && h2n->type != LWS_H2_FRAME_TYPE_COUNT &&
|
|
(h2n->cont_exp_sid != h2n->sid ||
|
|
h2n->type != LWS_H2_FRAME_TYPE_CONTINUATION)) {
|
|
lwsl_info("%s: expected cont on sid %u (got %d on sid %u)\n",
|
|
__func__, (unsigned int)h2n->cont_exp_sid, h2n->type,
|
|
(unsigned int)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, (unsigned int)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) {
|
|
lwsl_info("DATA: 0 sid\n");
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "DATA 0 sid");
|
|
break;
|
|
}
|
|
lwsl_info("Frame header DATA: sid %u, flags 0x%x, len %u\n",
|
|
(unsigned int)h2n->sid, h2n->flags,
|
|
(unsigned int)h2n->length);
|
|
|
|
if (!h2n->swsi) {
|
|
lwsl_notice("DATA: NULL swsi\n");
|
|
break;
|
|
}
|
|
|
|
lwsl_info("DATA rx on state %d\n", h2n->swsi->h2.h2_state);
|
|
|
|
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;
|
|
}
|
|
|
|
if (h2n->length == 0)
|
|
lws_h2_parse_end_of_frame(wsi);
|
|
|
|
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:
|
|
lwsl_debug("LWS_H2_FRAME_TYPE_GOAWAY received\n");
|
|
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 % 6) {
|
|
lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
|
|
"Settings length error");
|
|
break;
|
|
}
|
|
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
|
|
return 0;
|
|
|
|
if (wsi->upgraded_to_http2 &&
|
|
#if defined(LWS_WITH_CLIENT)
|
|
(!(wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM) ||
|
|
#else
|
|
(
|
|
#endif
|
|
!wsi->h2_acked_settings)) {
|
|
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
|
|
if (!pps)
|
|
return 1;
|
|
lws_pps_schedule(wsi, pps);
|
|
wsi->h2_acked_settings = 1;
|
|
}
|
|
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 = %u %d %d\n",
|
|
(unsigned int)h2n->sid, (int)h2n->cont_exp,
|
|
(int)h2n->cont_exp_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 = %u\n",
|
|
(unsigned int)h2n->sid);
|
|
if (!h2n->sid) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "sid 0");
|
|
return 1;
|
|
}
|
|
|
|
if (h2n->swsi && !h2n->swsi->h2.END_STREAM &&
|
|
h2n->swsi->h2.END_HEADERS &&
|
|
!(h2n->flags & LWS_H2_FLAG_END_STREAM)) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"extra HEADERS together");
|
|
return 1;
|
|
}
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
if (wsi->client_h2_alpn) {
|
|
if (h2n->sid) {
|
|
h2n->swsi = lws_wsi_mux_from_id(wsi, h2n->sid);
|
|
lwsl_info("HEADERS: nwsi %s: sid %u mapped "
|
|
"to wsi %s\n", lws_wsi_tag(wsi),
|
|
(unsigned int)h2n->sid,
|
|
lws_wsi_tag(h2n->swsi));
|
|
if (!h2n->swsi)
|
|
break;
|
|
}
|
|
goto update_end_headers;
|
|
}
|
|
#endif
|
|
|
|
if (!h2n->swsi) {
|
|
/* no more children allowed by parent */
|
|
if (wsi->mux.child_count + 1 >
|
|
wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Another stream not allowed");
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The peer has sent us a HEADERS implying the creation
|
|
* of a new stream
|
|
*/
|
|
|
|
lws_context_lock(wsi->a.context, "h2 new str");
|
|
lws_vhost_lock(wsi->a.vhost);
|
|
|
|
h2n->swsi = __lws_wsi_server_new(wsi->a.vhost, wsi,
|
|
h2n->sid);
|
|
|
|
lws_vhost_unlock(wsi->a.vhost);
|
|
lws_context_unlock(wsi->a.context);
|
|
|
|
if (!h2n->swsi) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"OOM");
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (h2n->sid >= h2n->highest_sid)
|
|
h2n->highest_sid = h2n->sid + 2;
|
|
|
|
h2n->swsi->h2.initialized = 1;
|
|
|
|
if (lws_h2_update_peer_txcredit(h2n->swsi,
|
|
h2n->swsi->mux.my_sid, 4 * 65536))
|
|
goto cleanup_wsi;
|
|
}
|
|
|
|
/*
|
|
* ah needs attaching to child wsi, even though
|
|
* we only fill it from network wsi
|
|
*/
|
|
if (!h2n->swsi->http.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->mux.child_list) {
|
|
if (w->mux.my_sid < h2n->sid &&
|
|
w->h2.h2_state == LWS_H2_STATE_IDLE)
|
|
lws_close_free_wsi(w, 0, "h2 sid close");
|
|
assert(w->mux.sibling_list != w);
|
|
} lws_end_foreach_ll(w, mux.sibling_list);
|
|
|
|
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:
|
|
if (lws_check_opt(h2n->swsi->a.vhost->options,
|
|
LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL)) {
|
|
|
|
/*
|
|
* We don't directly timeout streams that enter the
|
|
* half-closed remote state, allowing immortal long
|
|
* poll
|
|
*/
|
|
lws_mux_mark_immortal(h2n->swsi);
|
|
lwsl_info("%s: %s: h2 stream entering long poll\n",
|
|
__func__, lws_wsi_tag(h2n->swsi));
|
|
|
|
} else {
|
|
h2n->swsi->h2.END_STREAM =
|
|
!!(h2n->flags & LWS_H2_FLAG_END_STREAM);
|
|
lwsl_debug("%s: hdr END_STREAM = %d\n",__func__,
|
|
h2n->swsi->h2.END_STREAM);
|
|
}
|
|
|
|
/* no END_HEADERS means CONTINUATION must come */
|
|
h2n->swsi->h2.END_HEADERS =
|
|
!!(h2n->flags & LWS_H2_FLAG_END_HEADERS);
|
|
lwsl_info("%s: %s: END_HEADERS %d\n", __func__, lws_wsi_tag(h2n->swsi),
|
|
h2n->swsi->h2.END_HEADERS);
|
|
if (h2n->swsi->h2.END_HEADERS)
|
|
h2n->cont_exp = 0;
|
|
lwsl_debug("END_HEADERS %d\n", h2n->swsi->h2.END_HEADERS);
|
|
break;
|
|
|
|
cleanup_wsi:
|
|
|
|
return 1;
|
|
|
|
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;
|
|
case LWS_H2_FRAME_TYPE_COUNT:
|
|
if (h2n->length == 0)
|
|
lws_h2_parse_end_of_frame(wsi);
|
|
else
|
|
lwsl_debug("%s: going on to deal with unknown frame remaining len %d\n", __func__, (unsigned int)h2n->length);
|
|
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;
|
|
}
|
|
|
|
static const char * const method_names[] = {
|
|
"GET", "POST",
|
|
#if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
|
|
"OPTIONS", "PUT", "PATCH", "DELETE",
|
|
#endif
|
|
"CONNECT", "HEAD"
|
|
};
|
|
static unsigned char method_index[] = {
|
|
WSI_TOKEN_GET_URI,
|
|
WSI_TOKEN_POST_URI,
|
|
#if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
|
|
WSI_TOKEN_OPTIONS_URI,
|
|
WSI_TOKEN_PUT_URI,
|
|
WSI_TOKEN_PATCH_URI,
|
|
WSI_TOKEN_DELETE_URI,
|
|
#endif
|
|
WSI_TOKEN_CONNECT,
|
|
WSI_TOKEN_HEAD_URI,
|
|
};
|
|
|
|
/*
|
|
* The last byte of the whole frame has been handled.
|
|
* Perform actions for frame completion.
|
|
*
|
|
* This is the crunch time for parsing that may have occured on a network
|
|
* wsi with a pending partial send... we may call lws_http_action() to send
|
|
* a response, conflicting with the partial.
|
|
*
|
|
* So in that case we change the wsi state and do the lws_http_action() in the
|
|
* WRITABLE handler as a priority.
|
|
*/
|
|
static int
|
|
lws_h2_parse_end_of_frame(struct lws *wsi)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws *eff_wsi = wsi;
|
|
const char *p;
|
|
int n;
|
|
|
|
h2n->frame_state = 0;
|
|
h2n->count = 0;
|
|
|
|
if (h2n->sid)
|
|
h2n->swsi = lws_wsi_mux_from_id(wsi, h2n->sid);
|
|
|
|
if (h2n->sid > h2n->highest_sid)
|
|
h2n->highest_sid = h2n->sid;
|
|
|
|
if (h2n->collected_priority && (h2n->dep & ~(1u << 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_SETTINGS:
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
if (wsi->client_h2_alpn && !wsi->client_mux_migrated &&
|
|
!(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) {
|
|
struct lws_h2_protocol_send *pps;
|
|
|
|
/* migrate original client ask on to substream 1 */
|
|
#if defined(LWS_WITH_FILE_OPS)
|
|
wsi->http.fop_fd = NULL;
|
|
#endif
|
|
lwsl_info("%s: migrating\n", __func__);
|
|
wsi->client_mux_migrated = 1;
|
|
/*
|
|
* we need to treat the headers from the upgrade as the
|
|
* first job. So these need to get shifted to sid 1.
|
|
*/
|
|
lws_context_lock(wsi->a.context, "h2 mig");
|
|
lws_vhost_lock(wsi->a.vhost);
|
|
|
|
h2n->swsi = __lws_wsi_server_new(wsi->a.vhost, wsi, 1);
|
|
|
|
lws_vhost_unlock(wsi->a.vhost);
|
|
lws_context_unlock(wsi->a.context);
|
|
|
|
if (!h2n->swsi)
|
|
return 1;
|
|
h2n->sid = 1;
|
|
|
|
assert(lws_wsi_mux_from_id(wsi, 1) == h2n->swsi);
|
|
|
|
// lws_role_transition(wsi, LWSIFR_CLIENT,
|
|
// LRS_H2_WAITING_TO_SEND_HEADERS,
|
|
// &role_ops_h2);
|
|
|
|
lws_role_transition(h2n->swsi, LWSIFR_CLIENT,
|
|
LRS_H2_WAITING_TO_SEND_HEADERS,
|
|
&role_ops_h2);
|
|
|
|
/* pass on the initial headers to SID 1 */
|
|
h2n->swsi->http.ah = wsi->http.ah;
|
|
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
|
|
lws_fi_import(&h2n->swsi->fic, &wsi->fic);
|
|
#endif
|
|
h2n->swsi->client_mux_substream = 1;
|
|
h2n->swsi->client_h2_alpn = 1;
|
|
#if defined(LWS_WITH_CLIENT)
|
|
h2n->swsi->flags = wsi->flags;
|
|
#if defined(LWS_WITH_CONMON)
|
|
/* sid1 needs to represent the connection experience
|
|
* ... we take over responsibility for the DNS list
|
|
* copy as well
|
|
*/
|
|
h2n->swsi->conmon = wsi->conmon;
|
|
h2n->swsi->conmon_datum = wsi->conmon_datum;
|
|
h2n->swsi->sa46_peer = wsi->sa46_peer;
|
|
wsi->conmon.dns_results_copy = NULL;
|
|
#endif
|
|
#endif /* CLIENT */
|
|
|
|
#if defined(LWS_WITH_SECURE_STREAMS)
|
|
if (wsi->for_ss) {
|
|
lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);
|
|
|
|
h2n->swsi->for_ss = 1;
|
|
wsi->for_ss = 0;
|
|
|
|
if (h->wsi == wsi)
|
|
h->wsi = h2n->swsi;
|
|
}
|
|
#endif
|
|
|
|
h2n->swsi->a.protocol = wsi->a.protocol;
|
|
if (h2n->swsi->user_space &&
|
|
!h2n->swsi->user_space_externally_allocated)
|
|
lws_free(h2n->swsi->user_space);
|
|
h2n->swsi->user_space = wsi->user_space;
|
|
h2n->swsi->user_space_externally_allocated =
|
|
wsi->user_space_externally_allocated;
|
|
h2n->swsi->a.opaque_user_data = wsi->a.opaque_user_data;
|
|
wsi->a.opaque_user_data = NULL;
|
|
h2n->swsi->txc.manual_initial_tx_credit =
|
|
wsi->txc.manual_initial_tx_credit;
|
|
|
|
#if defined(LWS_WITH_TLS)
|
|
lws_strncpy(h2n->swsi->alpn, wsi->alpn,
|
|
sizeof(wsi->alpn));
|
|
#endif
|
|
|
|
wsi->user_space = NULL;
|
|
|
|
if (h2n->swsi->http.ah)
|
|
h2n->swsi->http.ah->wsi = h2n->swsi;
|
|
wsi->http.ah = NULL;
|
|
|
|
lwsl_info("%s: MIGRATING nwsi %s -> swsi %s\n", __func__,
|
|
lws_wsi_tag(wsi), lws_wsi_tag(h2n->swsi));
|
|
h2n->swsi->txc.tx_cr = (int32_t)
|
|
h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
lwsl_info("%s: initial tx credit on %s: %d\n",
|
|
__func__, lws_wsi_tag(h2n->swsi),
|
|
(int)h2n->swsi->txc.tx_cr);
|
|
h2n->swsi->h2.initialized = 1;
|
|
|
|
/* set our initial window size */
|
|
if (!wsi->h2.initialized) {
|
|
wsi->txc.tx_cr = (int32_t)
|
|
h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
|
|
|
|
lwsl_info("%s: initial tx credit for us to "
|
|
"write on nwsi %s: %d\n", __func__,
|
|
lws_wsi_tag(wsi), (int)wsi->txc.tx_cr);
|
|
wsi->h2.initialized = 1;
|
|
}
|
|
|
|
lws_callback_on_writable(h2n->swsi);
|
|
|
|
if (!wsi->h2_acked_settings ||
|
|
!(wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM)
|
|
) {
|
|
pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
|
|
if (!pps)
|
|
return 1;
|
|
lws_pps_schedule(wsi, pps);
|
|
lwsl_info("%s: SETTINGS ack PPS\n", __func__);
|
|
wsi->h2_acked_settings = 1;
|
|
}
|
|
|
|
/* also attach any queued guys */
|
|
|
|
lws_wsi_mux_apply_queue(wsi);
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_CONTINUATION:
|
|
case LWS_H2_FRAME_TYPE_HEADERS:
|
|
|
|
if (!h2n->swsi)
|
|
break;
|
|
|
|
/* 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("withholding http action for continuation\n");
|
|
h2n->cont_exp_sid = h2n->sid;
|
|
h2n->cont_exp = 1;
|
|
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 %u)\n",
|
|
h2n->hpack, h2n->type,
|
|
(unsigned int)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, %s, h2n->swsi=%s\n", lws_wsi_tag(wsi),
|
|
lws_wsi_tag(h2n->swsi));
|
|
h2n->swsi->hdr_parsing_completed = 1;
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
if (h2n->swsi->client_mux_substream &&
|
|
lws_client_interpret_server_handshake(h2n->swsi)) {
|
|
/*
|
|
* This is more complicated than it looks, one exit from
|
|
* interpret_server_handshake() is to do a close that
|
|
* turns into a redirect.
|
|
*
|
|
* In that case, the wsi survives having being reset
|
|
* and detached from any h2 identity. We need to get
|
|
* our parents out from touching it any more
|
|
*/
|
|
lwsl_info("%s: cli int serv hs closed, or redir\n", __func__);
|
|
return 2;
|
|
}
|
|
#endif
|
|
|
|
if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
|
|
const char *simp = lws_hdr_simple_ptr(h2n->swsi,
|
|
WSI_TOKEN_HTTP_CONTENT_LENGTH);
|
|
|
|
if (!simp) /* coverity */
|
|
return 1;
|
|
h2n->swsi->http.rx_content_length = (unsigned long long)atoll(simp);
|
|
h2n->swsi->http.rx_content_remain =
|
|
h2n->swsi->http.rx_content_length;
|
|
h2n->swsi->http.content_length_given = 1;
|
|
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((enum lws_token_indexes)n);
|
|
if (!c) {
|
|
n++;
|
|
continue;
|
|
}
|
|
|
|
len = lws_hdr_total_length(h2n->swsi, (enum lws_token_indexes)n);
|
|
if (!len || len > (int)sizeof(buf) - 1) {
|
|
n++;
|
|
continue;
|
|
}
|
|
|
|
if (lws_hdr_copy(h2n->swsi, buf, sizeof buf,
|
|
(enum lws_token_indexes)n) < 0) {
|
|
lwsl_info(" %s !oversize!\n",
|
|
(char *)c);
|
|
} else {
|
|
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_IDLE:
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_OPEN);
|
|
break;
|
|
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)
|
|
/*
|
|
* action the END_STREAM
|
|
*/
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
|
|
break;
|
|
}
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
|
|
/*
|
|
* If we already had the END_STREAM along with the END_HEADERS,
|
|
* we have already transitioned to STATE_CLOSED and we are not
|
|
* going to be doing anything further on this stream.
|
|
*
|
|
* In that case handle the transaction completion and
|
|
* finalize the stream for the peer
|
|
*/
|
|
|
|
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED &&
|
|
h2n->swsi->client_mux_substream) {
|
|
|
|
lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
|
|
"client done");
|
|
|
|
if (lws_http_transaction_completed_client(h2n->swsi))
|
|
lwsl_debug("tx completed returned close\n");
|
|
break;
|
|
}
|
|
|
|
if (h2n->swsi->client_mux_substream) {
|
|
lwsl_info("%s: %s: headers: client path (h2 state %s)\n",
|
|
__func__, lws_wsi_tag(wsi),
|
|
h2_state_names[h2n->swsi->h2.h2_state]);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
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 ||
|
|
!lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_TE) ||
|
|
strncmp(lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_TE),
|
|
"trailers", (unsigned int)n)) {
|
|
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
|
|
"Illegal transfer-encoding");
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
|
|
lws_http_compression_validate(h2n->swsi);
|
|
#endif
|
|
|
|
p = lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD);
|
|
/*
|
|
* duplicate :path into the individual method uri header
|
|
* index, so that it looks the same as h1 in the ah
|
|
*/
|
|
for (n = 0; n < (int)LWS_ARRAY_SIZE(method_names); n++)
|
|
if (p && !strcasecmp(p, method_names[n])) {
|
|
h2n->swsi->http.ah->frag_index[method_index[n]] =
|
|
h2n->swsi->http.ah->frag_index[
|
|
WSI_TOKEN_HTTP_COLON_PATH];
|
|
break;
|
|
}
|
|
|
|
{
|
|
lwsl_debug("%s: setting DEF_ACT from 0x%x\n", __func__,
|
|
(unsigned int)h2n->swsi->wsistate);
|
|
lwsi_set_state(h2n->swsi, LRS_DEFERRING_ACTION);
|
|
lws_callback_on_writable(h2n->swsi);
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_DATA:
|
|
lwsl_info("%s: DATA flags 0x%x\n", __func__, h2n->flags);
|
|
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);
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
/*
|
|
* client... remote END_STREAM implies we weren't going to
|
|
* send anything else anyway.
|
|
*/
|
|
|
|
if (h2n->swsi->client_mux_substream &&
|
|
(h2n->flags & LWS_H2_FLAG_END_STREAM)) {
|
|
lwsl_info("%s: %s: DATA: end stream\n",
|
|
__func__, lws_wsi_tag(h2n->swsi));
|
|
|
|
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN) {
|
|
lws_h2_state(h2n->swsi,
|
|
LWS_H2_STATE_HALF_CLOSED_REMOTE);
|
|
// lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
|
|
// "client done");
|
|
|
|
// if (lws_http_transaction_completed_client(h2n->swsi))
|
|
// lwsl_debug("tx completed returned close\n");
|
|
}
|
|
|
|
//if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
|
|
{
|
|
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
|
|
|
|
lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
|
|
"client done");
|
|
|
|
if (lws_http_transaction_completed_client(h2n->swsi))
|
|
lwsl_debug("tx completed returned close\n");
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_PING:
|
|
if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)
|
|
lws_validity_confirmed(wsi);
|
|
else {
|
|
/* they're sending us a ping request */
|
|
struct lws_h2_protocol_send *pps =
|
|
lws_h2_new_pps(LWS_H2_PPS_PONG);
|
|
if (!pps)
|
|
return 1;
|
|
|
|
lwsl_info("rx ping, preparing pong\n");
|
|
|
|
memcpy(pps->u.ping.ping_payload, h2n->ping_payload, 8);
|
|
lws_pps_schedule(wsi, pps);
|
|
}
|
|
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
|
|
/*
|
|
* We only have an unsigned 31-bit (positive) increment possible
|
|
*/
|
|
h2n->hpack_e_dep &= ~(1u << 31);
|
|
lwsl_info("WINDOW_UPDATE: sid %u %u (0x%x)\n",
|
|
(unsigned int)h2n->sid,
|
|
(unsigned int)h2n->hpack_e_dep,
|
|
(unsigned int)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 (eff_wsi->a.vhost->options &
|
|
LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW &&
|
|
(uint64_t)eff_wsi->txc.tx_cr + (uint64_t)h2n->hpack_e_dep >
|
|
(uint64_t)0x7fffffff)
|
|
h2n->hpack_e_dep = (uint32_t)(0x7fffffff - eff_wsi->txc.tx_cr);
|
|
|
|
if ((uint64_t)eff_wsi->txc.tx_cr + (uint64_t)h2n->hpack_e_dep >
|
|
(uint64_t)0x7fffffff) {
|
|
lwsl_warn("%s: WINDOW_UPDATE 0x%llx + 0x%llx = 0x%llx, too high\n",
|
|
__func__, (unsigned long long)eff_wsi->txc.tx_cr,
|
|
(unsigned long long)h2n->hpack_e_dep,
|
|
(unsigned long long)eff_wsi->txc.tx_cr + (unsigned long long)h2n->hpack_e_dep);
|
|
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->txc.tx_cr;
|
|
eff_wsi->txc.tx_cr += (int32_t)h2n->hpack_e_dep;
|
|
|
|
lws_wsi_txc_report_manual_txcr_in(eff_wsi,
|
|
(int32_t)h2n->hpack_e_dep);
|
|
|
|
lws_wsi_txc_describe(&eff_wsi->txc, "WINDOW_UPDATE in",
|
|
eff_wsi->mux.my_sid);
|
|
|
|
if (n <= 0 && eff_wsi->txc.tx_cr <= 0)
|
|
/* it helps, but won't change sendability for anyone */
|
|
break;
|
|
|
|
/*
|
|
* It may have changed sendability (depends on SID 0 tx credit
|
|
* too)... for us and any children waiting on us... reassess
|
|
* blockage for all children first
|
|
*/
|
|
lws_start_foreach_ll(struct lws *, w, wsi->mux.child_list) {
|
|
lws_callback_on_writable(w);
|
|
} lws_end_foreach_ll(w, mux.sibling_list);
|
|
|
|
if (eff_wsi->txc.skint &&
|
|
!lws_wsi_txc_check_skint(&eff_wsi->txc,
|
|
lws_h2_tx_cr_get(eff_wsi)))
|
|
/*
|
|
* This one became un-skint, schedule a writeable
|
|
* callback
|
|
*/
|
|
lws_callback_on_writable(eff_wsi);
|
|
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_GOAWAY:
|
|
lwsl_notice("GOAWAY: last sid %u, error 0x%08X, string '%s'\n",
|
|
(unsigned int)h2n->goaway_last_sid,
|
|
(unsigned int)h2n->goaway_err, h2n->goaway_str);
|
|
|
|
return 1;
|
|
|
|
case LWS_H2_FRAME_TYPE_RST_STREAM:
|
|
lwsl_info("LWS_H2_FRAME_TYPE_RST_STREAM: sid %u: reason 0x%x\n",
|
|
(unsigned int)h2n->sid,
|
|
(unsigned int)h2n->hpack_e_dep);
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This may want to send something on the network wsi, which may be in the
|
|
* middle of a partial send. PPS sends are OK because they are queued to
|
|
* go through the WRITABLE handler already.
|
|
*
|
|
* The read parser for the network wsi has no choice but to parse its stream
|
|
* anyway, because otherwise it will not be able to get tx credit window
|
|
* messages.
|
|
*
|
|
* Therefore if we will send non-PPS, ie, lws_http_action() for a stream
|
|
* wsi, we must change its state and handle it as a priority in the
|
|
* POLLOUT handler instead of writing it here.
|
|
*
|
|
* About closing... for the main network wsi, it should return nonzero to
|
|
* close it all. If it needs to close an swsi, it can do it here.
|
|
*/
|
|
int
|
|
lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t _inlen,
|
|
lws_filepos_t *inused)
|
|
{
|
|
struct lws_h2_netconn *h2n = wsi->h2.h2n;
|
|
struct lws_h2_protocol_send *pps;
|
|
unsigned char c, *oldin = in, *iend = in + (size_t)_inlen;
|
|
int n, m;
|
|
|
|
if (!h2n)
|
|
goto fail;
|
|
|
|
while (in < iend) {
|
|
|
|
c = *in++;
|
|
|
|
switch (lwsi_state(wsi)) {
|
|
case LRS_H2_AWAIT_PREFACE:
|
|
if (preface[h2n->count++] != c)
|
|
goto fail;
|
|
|
|
if (preface[h2n->count])
|
|
break;
|
|
|
|
lwsl_info("http2: %s: established\n", lws_wsi_tag(wsi));
|
|
lwsi_set_state(wsi, LRS_H2_AWAIT_SETTINGS);
|
|
lws_validity_confirmed(wsi);
|
|
h2n->count = 0;
|
|
wsi->txc.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)
|
|
goto fail;
|
|
lws_pps_schedule(wsi, pps);
|
|
break;
|
|
|
|
case LRS_H2_WAITING_TO_SEND_HEADERS:
|
|
case LRS_ESTABLISHED:
|
|
case LRS_H2_AWAIT_SETTINGS:
|
|
|
|
if (h2n->frame_state != LWS_H2_FRAME_HEADER_LENGTH)
|
|
goto try_frame_start;
|
|
|
|
/*
|
|
* post-header, preamble / payload / padding part
|
|
*/
|
|
h2n->count++;
|
|
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) { /* IGNORING FRAME */
|
|
//lwsl_debug("%s: consuming for ignored %u %u\n", __func__, (unsigned int)h2n->count, (unsigned int)h2n->length);
|
|
goto frame_end;
|
|
}
|
|
|
|
|
|
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",
|
|
(unsigned int)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 = (int)(h2n->count - 1u - h2n->preamble) %
|
|
LWS_H2_SETTINGS_LEN;
|
|
h2n->one_setting[n] = c;
|
|
if (n != LWS_H2_SETTINGS_LEN - 1)
|
|
break;
|
|
lws_h2_settings(wsi, &h2n->peer_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__);
|
|
goto fail;
|
|
}
|
|
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] = (char)c;
|
|
h2n->goaway_str[
|
|
sizeof(h2n->goaway_str) - 1] = '\0';
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_DATA:
|
|
|
|
// lwsl_info("%s: LWS_H2_FRAME_TYPE_DATA: fl 0x%x\n",
|
|
// __func__, h2n->flags);
|
|
|
|
/*
|
|
* let the network wsi live a bit longer if
|
|
* subs are active... our frame may take a long
|
|
* time to chew through
|
|
*/
|
|
if (!wsi->immortal_substream_count)
|
|
lws_set_timeout(wsi,
|
|
PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
|
|
wsi->a.vhost->keepalive_timeout ?
|
|
wsi->a.vhost->keepalive_timeout : 31);
|
|
|
|
if (!h2n->swsi)
|
|
break;
|
|
|
|
if (lws_buflist_next_segment_len(
|
|
&h2n->swsi->buflist, NULL))
|
|
lwsl_info("%s: substream has pending\n",
|
|
__func__);
|
|
|
|
if (lwsi_role_http(h2n->swsi) &&
|
|
lwsi_state(h2n->swsi) == LRS_ESTABLISHED) {
|
|
lwsi_set_state(h2n->swsi, LRS_BODY);
|
|
lwsl_info("%s: %s to LRS_BODY\n",
|
|
__func__, lws_wsi_tag(h2n->swsi));
|
|
}
|
|
|
|
/*
|
|
* in + length may cover multiple frames, we
|
|
* can only consider the length of the DATA
|
|
* in front of us
|
|
*/
|
|
|
|
if (lws_hdr_total_length(h2n->swsi,
|
|
WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
|
|
h2n->swsi->http.rx_content_length &&
|
|
h2n->swsi->http.rx_content_remain <
|
|
h2n->length - h2n->inside && /* last */
|
|
h2n->inside < h2n->length) {
|
|
|
|
lwsl_warn("%s: rx.cl: %lu, rx.content_remain: %lu, buf left: %lu, "
|
|
"h2->inside: %lu, h2->length: %lu\n", __func__,
|
|
(unsigned long)h2n->swsi->http.rx_content_length,
|
|
(unsigned long)h2n->swsi->http.rx_content_remain,
|
|
(unsigned long)(lws_ptr_diff_size_t(iend, in) + 1),
|
|
(unsigned long)h2n->inside, (unsigned long)h2n->length);
|
|
|
|
/* unread data in frame */
|
|
lws_h2_goaway(wsi,
|
|
H2_ERR_PROTOCOL_ERROR,
|
|
"More rx than content_length told");
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We operate on a frame. The RX we have at
|
|
* hand may exceed the current frame.
|
|
*/
|
|
|
|
n = (int)lws_ptr_diff_size_t(iend, in) + 1;
|
|
if (n > (int)(h2n->length - h2n->count + 1)) {
|
|
if (h2n->count > h2n->length)
|
|
goto close_swsi_and_return;
|
|
n = (int)(h2n->length - h2n->count) + 1;
|
|
lwsl_debug("---- restricting len to %d "
|
|
"\n", n);
|
|
}
|
|
#if defined(LWS_WITH_CLIENT)
|
|
if (h2n->swsi->client_mux_substream) {
|
|
if (!h2n->swsi->a.protocol) {
|
|
lwsl_err("%s: %p doesn't have protocol\n",
|
|
__func__, lws_wsi_tag(h2n->swsi));
|
|
m = 1;
|
|
} else {
|
|
h2n->swsi->txc.peer_tx_cr_est -= n;
|
|
wsi->txc.peer_tx_cr_est -= n;
|
|
lws_wsi_txc_describe(&h2n->swsi->txc,
|
|
__func__,
|
|
h2n->swsi->mux.my_sid);
|
|
m = user_callback_handle_rxflow(
|
|
h2n->swsi->a.protocol->callback,
|
|
h2n->swsi,
|
|
LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
|
|
h2n->swsi->user_space,
|
|
in - 1, (unsigned int)n);
|
|
}
|
|
|
|
in += n - 1;
|
|
h2n->inside += (unsigned int)n;
|
|
h2n->count += (unsigned int)n - 1;
|
|
|
|
if (m) {
|
|
lwsl_info("RECEIVE_CLIENT_HTTP "
|
|
"closed it\n");
|
|
goto close_swsi_and_return;
|
|
}
|
|
|
|
goto do_windows;
|
|
}
|
|
#endif
|
|
if (lwsi_state(h2n->swsi) == LRS_DEFERRING_ACTION) {
|
|
m = lws_buflist_append_segment(
|
|
&h2n->swsi->buflist, in - 1, (unsigned int)n);
|
|
if (m < 0)
|
|
return -1;
|
|
|
|
/*
|
|
* Since we're in an open-ended
|
|
* DEFERRING_ACTION, don't add this swsi
|
|
* to the pt list of wsi holding buflist
|
|
* content yet, we are not in a position
|
|
* to consume it until we get out of
|
|
* DEFERRING_ACTION.
|
|
*/
|
|
|
|
in += n - 1;
|
|
h2n->inside += (unsigned int)n;
|
|
h2n->count += (unsigned int)n - 1;
|
|
|
|
lwsl_debug("%s: deferred %d\n", __func__, n);
|
|
goto do_windows;
|
|
}
|
|
|
|
h2n->swsi->outer_will_close = 1;
|
|
/*
|
|
* choose the length for this go so that we end at
|
|
* the frame boundary, in the case there is already
|
|
* more waiting leave it for next time around
|
|
*/
|
|
|
|
n = lws_read_h1(h2n->swsi, in - 1, (unsigned int)n);
|
|
// lwsl_notice("%s: lws_read_h1 %d\n", __func__, n);
|
|
h2n->swsi->outer_will_close = 0;
|
|
/*
|
|
* can return 0 in POST body with
|
|
* content len exhausted somehow.
|
|
*/
|
|
if (n < 0 ||
|
|
(!n && h2n->swsi->http.content_length_given && !lws_buflist_next_segment_len(
|
|
&wsi->buflist, NULL))) {
|
|
lwsl_info("%s: lws_read_h1 told %d %u / %u\n",
|
|
__func__, n,
|
|
(unsigned int)h2n->count,
|
|
(unsigned int)h2n->length);
|
|
in += h2n->length - h2n->count;
|
|
h2n->inside = h2n->length;
|
|
h2n->count = h2n->length - 1;
|
|
|
|
//if (n < 0)
|
|
// goto already_closed_swsi;
|
|
goto close_swsi_and_return;
|
|
}
|
|
|
|
lwsl_info("%s: lws_read_h1 telling %d %u / %u\n",
|
|
__func__, n,
|
|
(unsigned int)h2n->count,
|
|
(unsigned int)h2n->length);
|
|
|
|
in += (unsigned int)n - 1;
|
|
h2n->inside += (unsigned int)n;
|
|
h2n->count += (unsigned int)n - 1;
|
|
|
|
h2n->swsi->txc.peer_tx_cr_est -= n;
|
|
wsi->txc.peer_tx_cr_est -= n;
|
|
|
|
do_windows:
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
if (!(h2n->swsi->flags & LCCSCF_H2_MANUAL_RXFLOW))
|
|
#endif
|
|
{
|
|
/*
|
|
* The default behaviour is we just keep
|
|
* cranking the other side's tx credit
|
|
* back up, for simple bulk transfer as
|
|
* fast as we can take it
|
|
*/
|
|
|
|
m = n + 65536;
|
|
|
|
/* update both the stream and nwsi */
|
|
|
|
lws_h2_update_peer_txcredit_thresh(h2n->swsi,
|
|
h2n->sid, m, m);
|
|
}
|
|
#if defined(LWS_WITH_CLIENT)
|
|
else {
|
|
/*
|
|
* If he's handling it himself, only
|
|
* repair the nwsi credit but allow the
|
|
* stream credit to run down until the
|
|
* user code deals with it
|
|
*/
|
|
lws_h2_update_peer_txcredit(wsi, (unsigned int)0, n);
|
|
h2n->swsi->txc.manual = 1;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case LWS_H2_FRAME_TYPE_PRIORITY:
|
|
if (h2n->count <= 4) {
|
|
h2n->dep <<= 8;
|
|
h2n->dep |= c;
|
|
break;
|
|
}
|
|
h2n->weight_temp = c;
|
|
lwsl_info("PRIORITY: dep 0x%x, weight 0x%02X\n",
|
|
(unsigned int)h2n->dep, h2n->weight_temp);
|
|
|
|
if ((h2n->dep & ~(1u << 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:
|
|
h2n->hpack_e_dep <<= 8;
|
|
h2n->hpack_e_dep |= c;
|
|
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 */
|
|
//lwsl_debug("%s: consuming for ignored %u %u\n", __func__, (unsigned int)h2n->count, (unsigned int)h2n->length);
|
|
h2n->count++;
|
|
break;
|
|
|
|
default:
|
|
lwsl_notice("%s: unhandled frame type %d\n",
|
|
__func__, h2n->type);
|
|
|
|
goto fail;
|
|
}
|
|
|
|
frame_end:
|
|
if (h2n->count > h2n->length) {
|
|
lwsl_notice("%s: count > length %u %u (type %d)\n",
|
|
__func__, (unsigned int)h2n->count,
|
|
(unsigned int)h2n->length, h2n->type);
|
|
|
|
} else
|
|
if (h2n->count != h2n->length)
|
|
break;
|
|
|
|
/*
|
|
* end of frame just happened
|
|
*/
|
|
n = lws_h2_parse_end_of_frame(wsi);
|
|
if (n == 2) {
|
|
*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);
|
|
|
|
return 2;
|
|
}
|
|
if (n)
|
|
goto fail;
|
|
|
|
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 &&
|
|
lws_h2_parse_frame_header(wsi))
|
|
goto fail;
|
|
break;
|
|
|
|
default:
|
|
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) { /* IGNORING FRAME */
|
|
//lwsl_debug("%s: consuming for ignored %u %u\n", __func__, (unsigned int)h2n->count, (unsigned int)h2n->length);
|
|
h2n->count++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);
|
|
|
|
return 0;
|
|
|
|
close_swsi_and_return:
|
|
|
|
lws_close_free_wsi(h2n->swsi, 0, "close_swsi_and_return");
|
|
h2n->swsi = NULL;
|
|
h2n->frame_state = 0;
|
|
h2n->count = 0;
|
|
|
|
// already_closed_swsi:
|
|
*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);
|
|
|
|
return 2;
|
|
|
|
fail:
|
|
*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);
|
|
|
|
return 1;
|
|
}
|
|
|
|
#if defined(LWS_WITH_CLIENT)
|
|
int
|
|
lws_h2_client_handshake(struct lws *wsi)
|
|
{
|
|
struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
|
|
uint8_t *buf, *start, *p, *p1, *end;
|
|
char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD),
|
|
*uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), *simp;
|
|
struct lws *nwsi = lws_get_network_wsi(wsi);
|
|
const char *path = "/";
|
|
int n, m;
|
|
/*
|
|
* 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.
|
|
*/
|
|
unsigned int sid = nwsi->h2.h2n->highest_sid_opened + 2;
|
|
|
|
lwsl_debug("%s\n", __func__);
|
|
|
|
/*
|
|
* We MUST allocate our sid here at the point we're about to send the
|
|
* stream open. It's because we don't know the order in which multiple
|
|
* open streams will send their headers... in h2, sending the headers
|
|
* is the point the stream is opened. The peer requires that we only
|
|
* open streams in ascending sid order
|
|
*/
|
|
|
|
wsi->mux.my_sid = nwsi->h2.h2n->highest_sid_opened = sid;
|
|
lwsl_info("%s: %s: assigning SID %d at header send\n", __func__,
|
|
lws_wsi_tag(wsi), sid);
|
|
|
|
|
|
lwsl_info("%s: CLIENT_WAITING_TO_SEND_HEADERS: pollout (sid %d)\n",
|
|
__func__, wsi->mux.my_sid);
|
|
|
|
p = start = buf = pt->serv_buf + LWS_PRE;
|
|
end = start + (wsi->a.context->pt_serv_buf_size / 2) - LWS_PRE - 1;
|
|
|
|
/* it's time for us to send our client stream headers */
|
|
|
|
if (!meth)
|
|
meth = "GET";
|
|
|
|
/* h2 pseudoheaders must be in a bunch at the start */
|
|
|
|
if (lws_add_http_header_by_token(wsi,
|
|
WSI_TOKEN_HTTP_COLON_METHOD,
|
|
(unsigned char *)meth,
|
|
(int)strlen(meth), &p, end))
|
|
goto fail_length;
|
|
|
|
if (lws_add_http_header_by_token(wsi,
|
|
WSI_TOKEN_HTTP_COLON_SCHEME,
|
|
(unsigned char *)"https", 5,
|
|
&p, end))
|
|
goto fail_length;
|
|
|
|
|
|
n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI);
|
|
if (n)
|
|
path = uri;
|
|
else
|
|
if (wsi->stash && wsi->stash->cis[CIS_PATH]) {
|
|
path = wsi->stash->cis[CIS_PATH];
|
|
n = (int)strlen(path);
|
|
} else
|
|
n = 1;
|
|
|
|
if (n > 1 && path[0] == '/' && path[1] == '/') {
|
|
path++;
|
|
n--;
|
|
}
|
|
|
|
if (n && lws_add_http_header_by_token(wsi,
|
|
WSI_TOKEN_HTTP_COLON_PATH,
|
|
(unsigned char *)path, n, &p, end))
|
|
goto fail_length;
|
|
|
|
n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_HOST);
|
|
simp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST);
|
|
if (!n && wsi->stash && wsi->stash->cis[CIS_ADDRESS]) {
|
|
n = (int)strlen(wsi->stash->cis[CIS_ADDRESS]);
|
|
simp = wsi->stash->cis[CIS_ADDRESS];
|
|
}
|
|
|
|
// n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_ORIGIN);
|
|
// simp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN);
|
|
#if 0
|
|
if (n && simp && lws_add_http_header_by_token(wsi,
|
|
WSI_TOKEN_HTTP_COLON_AUTHORITY,
|
|
(unsigned char *)simp, n, &p, end))
|
|
goto fail_length;
|
|
#endif
|
|
|
|
|
|
if (/*!wsi->client_h2_alpn && */n && simp &&
|
|
lws_add_http_header_by_token(wsi, WSI_TOKEN_HOST,
|
|
(unsigned char *)simp, n, &p, end))
|
|
goto fail_length;
|
|
|
|
|
|
if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME) {
|
|
p1 = lws_http_multipart_headers(wsi, p);
|
|
if (!p1)
|
|
goto fail_length;
|
|
p = p1;
|
|
}
|
|
|
|
if (wsi->flags & LCCSCF_HTTP_X_WWW_FORM_URLENCODED) {
|
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
|
|
(unsigned char *)"application/x-www-form-urlencoded",
|
|
33, &p, end))
|
|
goto fail_length;
|
|
lws_client_http_body_pending(wsi, 1);
|
|
}
|
|
|
|
/* give userland a chance to append, eg, cookies */
|
|
|
|
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
|
|
if (wsi->flags & LCCSCF_CACHE_COOKIES)
|
|
lws_cookie_send_cookies(wsi, (char **)&p, (char *)end);
|
|
#endif
|
|
|
|
if (wsi->a.protocol->callback(wsi,
|
|
LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
|
|
wsi->user_space, &p, lws_ptr_diff_size_t(end, p) - 12))
|
|
goto fail_length;
|
|
|
|
if (lws_finalize_http_header(wsi, &p, end))
|
|
goto fail_length;
|
|
|
|
m = LWS_WRITE_HTTP_HEADERS;
|
|
#if defined(LWS_WITH_CLIENT)
|
|
/* below is not needed in spec, indeed it destroys the long poll
|
|
* feature, but required by nghttp2 */
|
|
if ((wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM) &&
|
|
!(wsi->client_http_body_pending || lws_has_buffered_out(wsi)))
|
|
m |= LWS_WRITE_H2_STREAM_END;
|
|
#endif
|
|
|
|
// lwsl_hexdump_notice(start, p - start);
|
|
|
|
n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), (enum lws_write_protocol)m);
|
|
|
|
if (n != lws_ptr_diff(p, start)) {
|
|
lwsl_err("_write returned %d from %ld\n", n,
|
|
(long)(p - start));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Normally let's charge up the peer tx credit a bit. But if
|
|
* MANUAL_REFLOW is set, just set it to the initial credit given in
|
|
* the client create info
|
|
*/
|
|
|
|
n = 4 * 65536;
|
|
if (wsi->flags & LCCSCF_H2_MANUAL_RXFLOW) {
|
|
n = wsi->txc.manual_initial_tx_credit;
|
|
wsi->txc.manual = 1;
|
|
}
|
|
|
|
if (lws_h2_update_peer_txcredit(wsi, wsi->mux.my_sid, n))
|
|
return 1;
|
|
|
|
lws_h2_state(wsi, LWS_H2_STATE_OPEN);
|
|
lwsi_set_state(wsi, LRS_ESTABLISHED);
|
|
|
|
if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME)
|
|
lws_callback_on_writable(wsi);
|
|
|
|
return 0;
|
|
|
|
fail_length:
|
|
lwsl_err("Client hdrs too long: incr context info.pt_serv_buf_size\n");
|
|
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(LWS_ROLE_WS) && defined(LWS_WITH_SERVER)
|
|
int
|
|
lws_h2_ws_handshake(struct lws *wsi)
|
|
{
|
|
uint8_t buf[LWS_PRE + 2048], *p = buf + LWS_PRE, *start = p,
|
|
*end = &buf[sizeof(buf) - 1];
|
|
const struct lws_http_mount *hit;
|
|
const char * uri_ptr;
|
|
size_t m;
|
|
int n;
|
|
|
|
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
|
|
return -1;
|
|
|
|
if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) > 64)
|
|
return -1;
|
|
|
|
if (wsi->proxied_ws_parent && wsi->child_list) {
|
|
if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) {
|
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
|
|
(uint8_t *)lws_hdr_simple_ptr(wsi,
|
|
WSI_TOKEN_PROTOCOL),
|
|
(int)strlen(lws_hdr_simple_ptr(wsi,
|
|
WSI_TOKEN_PROTOCOL)),
|
|
&p, end))
|
|
return -1;
|
|
}
|
|
} else {
|
|
|
|
/* we can only return the protocol header if:
|
|
* - one came in, and ... */
|
|
if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
|
|
/* - it is not an empty string */
|
|
wsi->a.protocol->name && wsi->a.protocol->name[0]) {
|
|
|
|
#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SERVER)
|
|
|
|
/*
|
|
* This is the h2 version of server-ws.c understanding that it
|
|
* did the ws upgrade on a ss server object, therefore it needs
|
|
* to pass back to the peer the policy ws-protocol name, not
|
|
* the generic ss-ws.c protocol name
|
|
*/
|
|
|
|
if (wsi->a.vhost && wsi->a.vhost->ss_handle &&
|
|
wsi->a.vhost->ss_handle->policy->u.http.u.ws.subprotocol) {
|
|
lws_ss_handle_t *h =
|
|
(lws_ss_handle_t *)wsi->a.opaque_user_data;
|
|
|
|
lwsl_notice("%s: Server SS %s .wsi %s switching to ws protocol\n",
|
|
__func__, lws_ss_tag(h), lws_wsi_tag(h->wsi));
|
|
|
|
wsi->a.protocol = &protocol_secstream_ws;
|
|
|
|
/*
|
|
* inform the SS user code that this has done a one-way
|
|
* upgrade to some other protocol... it will likely
|
|
* want to treat subsequent payloads differently
|
|
*/
|
|
|
|
lws_ss_event_helper(h, LWSSSCS_SERVER_UPGRADE);
|
|
|
|
lws_mux_mark_immortal(wsi);
|
|
|
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
|
|
(unsigned char *)wsi->a.vhost->ss_handle->policy->
|
|
u.http.u.ws.subprotocol,
|
|
(int)strlen(wsi->a.vhost->ss_handle->policy->
|
|
u.http.u.ws.subprotocol), &p, end))
|
|
return -1;
|
|
} else
|
|
#endif
|
|
|
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
|
|
(unsigned char *)wsi->a.protocol->name,
|
|
(int)strlen(wsi->a.protocol->name), &p, end))
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#if !defined(LWS_WITHOUT_EXTENSIONS)
|
|
/*
|
|
* Figure out which extensions the client has that we want to
|
|
* enable on this connection, and give him back the list.
|
|
*
|
|
* Give him a limited write bugdet
|
|
*/
|
|
if (lws_extension_server_handshake(wsi, (char **)&p, 192))
|
|
return -1;
|
|
#endif
|
|
|
|
if (lws_finalize_http_header(wsi, &p, end))
|
|
return -1;
|
|
|
|
m = lws_ptr_diff_size_t(p, start);
|
|
// lwsl_hexdump_notice(start, m);
|
|
n = lws_write(wsi, start, m, LWS_WRITE_HTTP_HEADERS);
|
|
if (n != (int)m) {
|
|
lwsl_err("_write returned %d from %d\n", n, (int)m);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* alright clean up, set our state to generic ws established, the
|
|
* mode / state of the nwsi will get the h2 processing done.
|
|
*/
|
|
|
|
lwsi_set_state(wsi, LRS_ESTABLISHED);
|
|
wsi->lws_rx_parse_state = 0; // ==LWS_RXPS_NEW;
|
|
|
|
uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH);
|
|
n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
|
|
hit = lws_find_mount(wsi, uri_ptr, n);
|
|
|
|
if (hit && hit->cgienv &&
|
|
wsi->a.protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, wsi->user_space,
|
|
(void *)hit->cgienv, 0))
|
|
return 1;
|
|
|
|
lws_validity_confirmed(wsi);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
|
|
{
|
|
unsigned char *oldbuf = buf;
|
|
|
|
// lwsl_notice("%s: h2 path: wsistate 0x%x len %d\n", __func__,
|
|
// wsi->wsistate, (int)len);
|
|
|
|
/*
|
|
* wsi here is always the network connection wsi, not a stream
|
|
* wsi. Once we unpicked the framing we will find the right
|
|
* swsi and make it the target of the frame.
|
|
*
|
|
* If it's ws over h2, the nwsi will get us here to do the h2
|
|
* processing, and that will call us back with the swsi +
|
|
* ESTABLISHED state for the inner payload, handled in a later
|
|
* case.
|
|
*/
|
|
while (len) {
|
|
lws_filepos_t body_chunk_len = 0;
|
|
int m;
|
|
|
|
/*
|
|
* we were accepting input but now we stopped doing so
|
|
*/
|
|
if (lws_is_flowcontrolled(wsi)) {
|
|
lws_rxflow_cache(wsi, buf, 0, (size_t)len);
|
|
buf += len;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* lws_h2_parser() may send something; when it gets the
|
|
* whole frame, it will want to perform some action
|
|
* involving a reply. But we may be in a partial send
|
|
* situation on the network wsi...
|
|
*
|
|
* Even though we may be in a partial send and unable to
|
|
* send anything new, we still have to parse the network
|
|
* wsi in order to gain tx credit to send, which is
|
|
* potentially necessary to clear the old partial send.
|
|
*
|
|
* ALL network wsi-specific frames are sent by PPS
|
|
* already, these are sent as a priority on the writable
|
|
* handler, and so respect partial sends. The only
|
|
* problem is when a stream wsi wants to send an, eg,
|
|
* reply headers frame in response to the parsing
|
|
* we will do now... the *stream wsi* must stall in a
|
|
* different state until it is able to do so from a
|
|
* priority on the WRITABLE callback, same way that
|
|
* file transfers operate.
|
|
*/
|
|
|
|
m = lws_h2_parser(wsi, buf, len, &body_chunk_len);
|
|
if (m && m != 2) {
|
|
lwsl_debug("%s: http2_parser bail: %d\n", __func__, m);
|
|
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
|
|
"lws_read_h2 bail");
|
|
|
|
return -1;
|
|
}
|
|
if (m == 2) {
|
|
/* swsi has been closed */
|
|
buf += body_chunk_len;
|
|
break;
|
|
}
|
|
|
|
buf += body_chunk_len;
|
|
len -= body_chunk_len;
|
|
}
|
|
|
|
return lws_ptr_diff(buf, oldbuf);
|
|
}
|
|
|
|
int
|
|
lws_h2_client_stream_long_poll_rxonly(struct lws *wsi)
|
|
{
|
|
|
|
if (!wsi->mux_substream)
|
|
return 1;
|
|
|
|
/*
|
|
* Elect to send an empty DATA with END_STREAM, to force the stream
|
|
* into HALF_CLOSED LOCAL
|
|
*/
|
|
wsi->h2.long_poll = 1;
|
|
wsi->h2.send_END_STREAM = 1;
|
|
|
|
// lws_header_table_detach(wsi, 0);
|
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
return 0;
|
|
}
|