diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index e27bbfa00..d41a91a76 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -44,8 +44,9 @@ enum lws_client_connect_ssl_connection_flags { LCCSCF_H2_QUIRK_OVERFLOWS_TXCR = (1 << 6), LCCSCF_H2_AUTH_BEARER = (1 << 7), LCCSCF_H2_HEXIFY_AUTH_TOKEN = (1 << 8), - LCCSCF_HTTP_MULTIPART_MIME = (1 << 9), - LCCSCF_HTTP_X_WWW_FORM_URLENCODED = (1 << 10), + LCCSCF_H2_MANUAL_RXFLOW = (1 << 9), + LCCSCF_HTTP_MULTIPART_MIME = (1 << 10), + LCCSCF_HTTP_X_WWW_FORM_URLENCODED = (1 << 11), LCCSCF_PIPELINE = (1 << 16), /**< Serialize / pipeline multiple client connections @@ -145,6 +146,11 @@ struct lws_client_connect_info { * Currently only the idle parts are applied to the connection. */ + int manual_initial_tx_credit; + /**< if LCCSCF_H2_MANUAL_REFLOW is set, this becomes the initial tx + * credit for the stream. + */ + uint8_t sys_tls_client_cert; /**< 0 means no client cert. 1+ means apply lws_system client cert 0+ * to the client connection. diff --git a/include/libwebsockets/lws-http.h b/include/libwebsockets/lws-http.h index 17df7bac0..d19e5aa6f 100644 --- a/include/libwebsockets/lws-http.h +++ b/include/libwebsockets/lws-http.h @@ -337,6 +337,19 @@ struct lws_token_limits { unsigned short token_limit[WSI_TOKEN_COUNT]; /**< max chars for this token */ }; +enum lws_h2_settings { + H2SET_HEADER_TABLE_SIZE = 1, + H2SET_ENABLE_PUSH, + H2SET_MAX_CONCURRENT_STREAMS, + H2SET_INITIAL_WINDOW_SIZE, + H2SET_MAX_FRAME_SIZE, + H2SET_MAX_HEADER_LIST_SIZE, + H2SET_RESERVED7, + H2SET_ENABLE_CONNECT_PROTOCOL, /* defined in mcmanus-httpbis-h2-ws-02 */ + + H2SET_COUNT /* always last */ +}; + /** * lws_token_to_string() - returns a textual representation of a hdr token index * @@ -802,5 +815,50 @@ lws_http_compression_apply(struct lws *wsi, const char *name, */ LWS_VISIBLE LWS_EXTERN int lws_http_is_redirected_to_get(struct lws *wsi); + +/** + * lws_h2_update_peer_txcredit() - manually update stream peer tx credit + * + * \param wsi: the h2 child stream whose peer credit to change + * \param sid: the stream ID, or LWS_H2_STREAM_SID for the wsi stream ID + * \param bump: signed change to confer upon peer tx credit for sid + * + * In conjunction with LCCSCF_H2_MANUAL_RXFLOW flag, allows the user code to + * selectively starve the remote peer of the ability to send us data on a client + * connection. + * + * Normally lws sends an initial window size for the peer to send to it of 0, + * but during the header phase it sends a WINDOW_UPDATE to increase the amount + * available. LCCSCF_H2_MANUAL_RXFLOW restricts this initial increase in tx + * credit for the stream, before it has been asked to send us anything, to the + * amount specified in the client info .manual_initial_tx_credit member, and + * this api can be called to send the other side permission to send us up to + * \p bump additional bytes. + * + * The nwsi tx credit is updated automatically for exactly what was sent to us + * on a stream with LCCSCF_H2_MANUAL_RXFLOW flag, but the stream's own tx credit + * must be handled manually by user code via this api. + * + * Returns 0 for success or nonzero for failure. + */ +#define LWS_H2_STREAM_SID -1 +LWS_VISIBLE LWS_EXTERN int +lws_h2_update_peer_txcredit(struct lws *wsi, int sid, int bump); + + +/** + * lws_h2_get_peer_txcredit_estimate() - return peer tx credit estimate + * + * \param wsi: the h2 child stream whose peer credit estimate to return + * + * Returns the estimated amount of tx credit at the peer, in other words the + * number of bytes the peer is authorized to send to us. + * + * It's an 'estimate' because we don't know how much is already in flight + * towards us and actually already used. + */ +LWS_VISIBLE LWS_EXTERN int +lws_h2_get_peer_txcredit_estimate(struct lws *wsi); + ///@} diff --git a/lib/core-net/connect.c b/lib/core-net/connect.c index 99bff8b71..8046f774d 100644 --- a/lib/core-net/connect.c +++ b/lib/core-net/connect.c @@ -145,6 +145,10 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) wsi->ocport = wsi->c_port = i->port; wsi->sys_tls_client_cert = i->sys_tls_client_cert; +#if defined(LWS_ROLE_H2) + wsi->h2.manual_initial_tx_credit = (int32_t)i->manual_initial_tx_credit; +#endif + wsi->protocol = &wsi->vhost->protocols[0]; wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE); diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index 66b35c3c6..0e8d72bcd 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -436,7 +436,7 @@ lws_get_peer_write_allowance(struct lws *wsi) { if (!wsi->role_ops->tx_credit) return -1; - return wsi->role_ops->tx_credit(wsi); + return wsi->role_ops->tx_credit(wsi, LWSTXCR_US_TO_PEER); } LWS_VISIBLE void @@ -1074,10 +1074,10 @@ lws_wsi_mux_dump_waiting_children(struct lws *wsi) wsi = wsi->mux.child_list; while (wsi) { - lwsl_info(" %c %p %s %s\n", + lwsl_info(" %c %p: sid %u: %s %s\n", wsi->mux.requested_POLLOUT ? '*' : ' ', - wsi, wsi->role_ops->name, wsi->protocol ? - wsi->protocol->name : "noprotocol"); + wsi, wsi->mux.my_sid, wsi->role_ops->name, + wsi->protocol ? wsi->protocol->name : "noprotocol"); wsi = wsi->mux.sibling_list; } diff --git a/lib/plat/freertos/freertos-init.c b/lib/plat/freertos/freertos-init.c index 0dc74f710..d99966232 100644 --- a/lib/plat/freertos/freertos-init.c +++ b/lib/plat/freertos/freertos-init.c @@ -61,7 +61,7 @@ const struct http2_settings lws_h2_defaults_esp32 = { { /* H2SET_HEADER_TABLE_SIZE */ 512, /* H2SET_ENABLE_PUSH */ 0, /* H2SET_MAX_CONCURRENT_STREAMS */ 8, - /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_INITIAL_WINDOW_SIZE */ 0, /* H2SET_MAX_FRAME_SIZE */ 16384, /* H2SET_MAX_HEADER_LIST_SIZE */ 512, /* H2SET_RESERVED7 */ 0, diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 4525b969a..0fc403351 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -133,7 +133,8 @@ lws_h2_new_pps(enum lws_h2_protocol_send_type type) void lws_h2_init(struct lws *wsi) { - wsi->h2.h2n->set = wsi->vhost->h2.set; + wsi->h2.h2n->our_set = wsi->vhost->h2.set; + wsi->h2.h2n->peer_set = lws_h2_defaults; } void @@ -149,6 +150,59 @@ lws_h2_state(struct lws *wsi, enum lws_h2_states s) wsi->h2.h2_state = (uint8_t)s; } +#if defined(_DEBUG) +static void +lws_h2_describe_txcredit(struct lws *wsi, const char *at) +{ + lwsl_info("%s: %p: %s: sid %d: peer-to-us: %d, us-to-peer: %d\n", + __func__, wsi, at, wsi->mux.my_sid, wsi->h2.peer_tx_cr_est, + wsi->h2.tx_cr); +} +#else +#define lws_h2_describe_txcredit(x, y) { (void)x; } +#endif + +int +lws_h2_update_peer_txcredit(struct lws *wsi, int sid, int bump) +{ + struct lws_h2_protocol_send *pps; + + assert(wsi); + + if (sid == -1) + sid = wsi->mux.my_sid; + + pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); + if (!pps) + return 1; + + pps->u.update_window.sid = sid; + pps->u.update_window.credit = bump; + wsi->h2.peer_tx_cr_est += bump; + + lws_h2_describe_txcredit(wsi, __func__); + + lws_pps_schedule(wsi, pps); + + return 0; +} + +int +lws_h2_get_peer_txcredit_estimate(struct lws *wsi) +{ + lws_h2_describe_txcredit(wsi, __func__); + return (int)wsi->h2.peer_tx_cr_est; +} + +static int +lws_h2_update_peer_txcredit_thresh(struct lws *wsi, int sid, int threshold, int bump) +{ + if (wsi->h2.peer_tx_cr_est > threshold) + return 0; + + return lws_h2_update_peer_txcredit(wsi, sid, bump); +} + struct lws * lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, unsigned int sid) @@ -174,7 +228,7 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, /* no more children allowed by parent */ if (parent_wsi->mux.child_count + 1 > - parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) { + parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) { lwsl_notice("reached concurrent stream limit\n"); return NULL; } @@ -191,9 +245,9 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, wsi->mux_substream = 1; wsi->seen_nonpseudoheader = 0; - wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE]; + wsi->h2.tx_cr = nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE]; wsi->h2.peer_tx_cr_est = - nwsi->vhost->h2.set.s[H2SET_INITIAL_WINDOW_SIZE]; + nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE]; lwsi_set_state(wsi, LRS_ESTABLISHED); lwsi_set_role(wsi, lwsi_role(parent_wsi)); @@ -209,10 +263,11 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi, /* get the ball rolling */ lws_validity_confirmed(wsi); - 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); + lwsl_info("%s: %p new ch %p, sid %d, usersp=%p\n", __func__, + parent_wsi, wsi, sid, wsi->user_space); + + lws_h2_describe_txcredit(nwsi, __func__); + lws_h2_describe_txcredit(wsi, __func__); return wsi; @@ -239,7 +294,7 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi) /* no more children allowed by parent */ if (parent_wsi->mux.child_count + 1 > - parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) { + parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) { lwsl_notice("reached concurrent stream limit\n"); return NULL; } @@ -254,9 +309,11 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi) lws_wsi_mux_insert(wsi, parent_wsi, wsi->mux.my_sid); - wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE]; + wsi->h2.tx_cr = nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE]; wsi->h2.peer_tx_cr_est = - nwsi->vhost->h2.set.s[H2SET_INITIAL_WINDOW_SIZE]; + nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE]; + + lws_h2_describe_txcredit(wsi, __func__); if (lws_ensure_user_space(wsi)) goto bail1; @@ -384,7 +441,7 @@ lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason) int lws_h2_settings(struct lws *wsi, struct http2_settings *settings, - unsigned char *buf, int len) + unsigned char *buf, int len) { struct lws *nwsi = lws_get_network_wsi(wsi); unsigned int a, b; @@ -587,10 +644,10 @@ 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]; + *buf++ = wsi->h2.h2n->our_set.s[n] >> 24; + *buf++ = wsi->h2.h2n->our_set.s[n] >> 16; + *buf++ = wsi->h2.h2n->our_set.s[n] >> 8; + *buf = wsi->h2.h2n->our_set.s[n]; } /* we get called on the network connection */ @@ -630,9 +687,9 @@ int lws_h2_do_pps_send(struct lws *wsi) * then we must inform the peer */ for (n = 1; n < H2SET_COUNT; n++) - if (h2n->set.s[n] != lws_h2_defaults.s[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->set.s[n]); + (unsigned int)wsi->h2.h2n->our_set.s[n]); lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]); m += sizeof(h2n->one_setting); } @@ -645,6 +702,27 @@ int lws_h2_do_pps_send(struct lws *wsi) } break; + case LWS_H2_PPS_SETTINGS_INITIAL_UPDATE_WINDOW: + q = &set[LWS_PRE]; + *q++ = H2SET_INITIAL_WINDOW_SIZE >> 8; + *q++ = H2SET_INITIAL_WINDOW_SIZE; + *q++ = pps->u.update_window.credit >> 24; + *q++ = pps->u.update_window.credit >> 16; + *q++ = pps->u.update_window.credit >> 8; + *q = 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, @@ -654,6 +732,7 @@ int lws_h2_do_pps_send(struct lws *wsi) lwsl_err("ack tells %d\n", 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); @@ -677,7 +756,7 @@ int lws_h2_do_pps_send(struct lws *wsi) lwsl_info("%s: inherited headers %p\n", __func__, h2n->swsi->http.ah); h2n->swsi->h2.tx_cr = - h2n->set.s[H2SET_INITIAL_WINDOW_SIZE]; + h2n->our_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; @@ -843,7 +922,7 @@ lws_h2_parse_frame_header(struct lws *wsi) if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) return 0; - if (h2n->length > h2n->set.s[H2SET_MAX_FRAME_SIZE]) { + if (h2n->length > h2n->our_set.s[H2SET_MAX_FRAME_SIZE]) { /* * peer sent us something bigger than we told * it we would allow @@ -1102,13 +1181,18 @@ lws_h2_parse_frame_header(struct lws *wsi) if (!h2n->swsi) { /* no more children allowed by parent */ if (wsi->mux.child_count + 1 > - wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) { + 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 + */ + h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, h2n->sid); if (!h2n->swsi) { @@ -1118,22 +1202,13 @@ lws_h2_parse_frame_header(struct lws *wsi) return 1; } - pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); - if (!pps) - goto cleanup_wsi; - pps->u.update_window.sid = h2n->sid; - pps->u.update_window.credit = 4 * 65536; - h2n->swsi->h2.peer_tx_cr_est += - pps->u.update_window.credit; - lws_pps_schedule(wsi, pps); + h2n->swsi->h2.initialized = 1; - pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); - if (!pps) + if (lws_h2_update_peer_txcredit(h2n->swsi, h2n->swsi->mux.my_sid, 4 * 65536)) + goto cleanup_wsi; + + if (lws_h2_update_peer_txcredit(wsi, 0, 4 * 65536)) goto cleanup_wsi; - pps->u.update_window.sid = 0; - pps->u.update_window.credit = 4 * 65536; - wsi->h2.peer_tx_cr_est += pps->u.update_window.credit; - lws_pps_schedule(wsi, pps); } /* @@ -1265,14 +1340,6 @@ lws_h2_parse_end_of_frame(struct lws *wsi) 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 & ~(1u << 31)) == h2n->sid) { lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "depends on own sid"); return 0; @@ -1327,6 +1394,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi) wsi->user_space_externally_allocated; h2n->swsi->opaque_user_data = wsi->opaque_user_data; wsi->opaque_user_data = NULL; + h2n->swsi->h2.manual_initial_tx_credit = + wsi->h2.manual_initial_tx_credit; wsi->user_space = NULL; @@ -1337,11 +1406,20 @@ lws_h2_parse_end_of_frame(struct lws *wsi) lwsl_info("%s: MIGRATING nwsi %p: swsi %p\n", __func__, wsi, h2n->swsi); 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->peer_set.s[H2SET_INITIAL_WINDOW_SIZE]; + lwsl_info("%s: initial tx credit on conn %p: %d\n", + __func__, h2n->swsi, h2n->swsi->h2.tx_cr); h2n->swsi->h2.initialized = 1; + /* set our initial window size */ + if (!wsi->h2.initialized) { + wsi->h2.tx_cr = h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE]; + lwsl_info("%s: initial tx credit for us to " + "write on master %p: %d\n", __func__, + wsi, wsi->h2.tx_cr); + wsi->h2.initialized = 1; + } + lws_callback_on_writable(h2n->swsi); if (!wsi->h2_acked_settings || @@ -1653,6 +1731,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi) n = eff_wsi->h2.tx_cr; eff_wsi->h2.tx_cr += h2n->hpack_e_dep; + lws_h2_describe_txcredit(eff_wsi, "WINDOW_UPDATE in"); + if (n <= 0 && eff_wsi->h2.tx_cr <= 0) /* it helps, but won't change sendability for anyone */ break; @@ -1815,7 +1895,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, h2n->one_setting[n] = c; if (n != LWS_H2_SETTINGS_LEN - 1) break; - lws_h2_settings(wsi, &h2n->set, + lws_h2_settings(wsi, &h2n->peer_set, h2n->one_setting, LWS_H2_SETTINGS_LEN); break; @@ -1920,13 +2000,17 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, if (!h2n->swsi->protocol) { lwsl_err("%s: swsi %pdoesn't have protocol\n", __func__, h2n->swsi); m = 1; - } else + } else { + h2n->swsi->h2.peer_tx_cr_est -= n; + wsi->h2.peer_tx_cr_est -= n; + lws_h2_describe_txcredit(h2n->swsi, __func__); m = user_callback_handle_rxflow( h2n->swsi->protocol->callback, h2n->swsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, h2n->swsi->user_space, in - 1, n); + } in += n - 1; h2n->inside += n; @@ -1998,38 +2082,42 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, in += n - 1; h2n->inside += n; h2n->count += n - 1; + h2n->swsi->h2.peer_tx_cr_est -= n; + wsi->h2.peer_tx_cr_est -= n; } do_windows: - /* account for both network and stream wsi windows */ - wsi->h2.peer_tx_cr_est -= n; - h2n->swsi->h2.peer_tx_cr_est -= n; +#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 + */ - // lwsl_notice(" peer_tx_cr_est %d, parent %d\n", - // h2n->swsi->h2.peer_tx_cr_est, wsi->h2.peer_tx_cr_est); + m = n; //(2 * h2n->length) + 65536; - if (h2n->swsi->h2.peer_tx_cr_est < (int)(2 * h2n->length) + 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 = (2 * h2n->length + 65536); - h2n->swsi->h2.peer_tx_cr_est += pps->u.update_window.credit; - lws_pps_schedule(wsi, pps); + /* update both the stream and nwsi */ + + lws_h2_update_peer_txcredit_thresh(h2n->swsi, + h2n->sid, m, m); + lws_h2_update_peer_txcredit_thresh(wsi, 0, m, m); } - if (wsi->h2.peer_tx_cr_est < (int)(2 * h2n->length) + 65536) { - 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 = (2 * h2n->length + 65536); - wsi->h2.peer_tx_cr_est += pps->u.update_window.credit; - lws_pps_schedule(wsi, pps); +#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, 0, n); } - - // lwsl_notice("%s: count %d len %d\n", __func__, (int)h2n->count, (int)h2n->length); - +#endif break; case LWS_H2_FRAME_TYPE_PRIORITY: @@ -2174,7 +2262,6 @@ lws_h2_client_handshake(struct lws *wsi) char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD), *uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI); struct lws *nwsi = lws_get_network_wsi(wsi); - struct lws_h2_protocol_send *pps; int n, m; /* * The identifier of a newly established stream MUST be numerically @@ -2192,22 +2279,6 @@ lws_h2_client_handshake(struct lws *wsi) lwsl_info("%s: CLIENT_WAITING_TO_SEND_HEADERS: pollout (sid %d)\n", __func__, wsi->mux.my_sid); - pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW); - if (!pps) - return 1; - pps->u.update_window.sid = sid; - pps->u.update_window.credit = 4 * 65536; - wsi->h2.peer_tx_cr_est += pps->u.update_window.credit; - 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 = 4 * 65536; - wsi->h2.peer_tx_cr_est += pps->u.update_window.credit; - lws_pps_schedule(wsi, pps); - p = start = buf = pt->serv_buf + LWS_PRE; end = start + (wsi->context->pt_serv_buf_size / 2) - LWS_PRE - 1; @@ -2302,6 +2373,22 @@ lws_h2_client_handshake(struct lws *wsi) 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->h2.manual_initial_tx_credit; + + if (lws_h2_update_peer_txcredit(wsi, sid, n)) + return 1; + + if (lws_h2_update_peer_txcredit(nwsi, 0, n)) + return 1; + lws_h2_state(wsi, LWS_H2_STATE_OPEN); lwsi_set_state(wsi, LRS_ESTABLISHED); diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index 7e6a02195..ee9380271 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -73,7 +73,10 @@ const struct http2_settings lws_h2_stock_settings = { { */ /* H2SET_ENABLE_PUSH */ 0, /* H2SET_MAX_CONCURRENT_STREAMS */ 24, - /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_INITIAL_WINDOW_SIZE */ 0, + /*< This is managed by explicit WINDOW_UPDATE. Because otherwise no + * way to precisely control it when we do want to. + */ /* H2SET_MAX_FRAME_SIZE */ 16384, /* H2SET_MAX_HEADER_LIST_SIZE */ 4096, /*< This advisory setting informs a peer of the maximum size of @@ -338,7 +341,7 @@ int rops_handle_POLLOUT_h2(struct lws *wsi) return LWS_HP_RET_USER_SERVICE; /* - * Priority 2: H2 protocol packets + * Priority 1: H2 protocol packets */ if ((wsi->upgraded_to_http2 #if defined(LWS_WITH_CLIENT) @@ -364,7 +367,7 @@ int rops_handle_POLLOUT_h2(struct lws *wsi) return LWS_HP_RET_BAIL_OK; /* leave POLLOUT active */ } - /* Priority 4: if we are closing, not allowed to send more data frags + /* Priority 2: if we are closing, not allowed to send more data frags * which means user callback or tx ext flush banned now */ if (lwsi_state(wsi) == LRS_RETURNED_CLOSE) @@ -567,9 +570,19 @@ rops_pt_init_destroy_h2(struct lws_context *context, static lws_fileofs_t -rops_tx_credit_h2(struct lws *wsi) +rops_tx_credit_h2(struct lws *wsi, char peer_to_us) { - return lws_h2_tx_cr_get(wsi); + struct lws *nwsi = lws_get_network_wsi(wsi); + int n; + + if (peer_to_us == LWSTXCR_US_TO_PEER) + return lws_h2_tx_cr_get(wsi); + + n = wsi->h2.peer_tx_cr_est; + if (n > nwsi->h2.peer_tx_cr_est) + n = nwsi->h2.peer_tx_cr_est; + + return n; } static int @@ -710,12 +723,16 @@ rops_callback_on_writable_h2(struct lws *wsi) * Delay waiting for our POLLOUT until peer indicates he has * space for more using tx window command in http2 layer */ - lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi, - wsi->h2.tx_cr); + + lwsl_info("%s: %p: skint (%d)\n", __func__, wsi, wsi->h2.tx_cr); + wsi->h2.skint = 1; return 0; } + if (wsi->h2.skint) + lwsl_info("%s: %p: unskint (%d)\n", __func__, wsi, wsi->h2.tx_cr); + wsi->h2.skint = 0; #if defined(LWS_WITH_CLIENT) network_wsi = lws_get_network_wsi(wsi); @@ -804,10 +821,10 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi) wsi = lws_get_network_wsi(wsi); wsi->mux.requested_POLLOUT = 0; - if (!wsi->h2.initialized) { - lwsl_info("pollout on uninitialized http2 conn\n"); - return 0; - } +// if (!wsi->h2.initialized) { +// lwsl_info("pollout on uninitialized http2 conn\n"); +// return 0; +// } lws_wsi_mux_dump_waiting_children(wsi); @@ -961,6 +978,32 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi) if (lwsi_state(w) == LRS_ISSUING_FILE) { + /* is this for DATA or for control messages? */ + if (!lws_h2_tx_cr_get(w)) { + /* + * Other side is not able to cope with us sending any DATA, + * so no matter if we have POLLOUT on our side if it's + * DATA we want to send. + * + * Delay waiting for our POLLOUT until peer indicates he has + * space for more using tx window command in http2 layer + */ + + lwsl_info("%s: %p: skint (%d)\n", __func__, + w, w->h2.tx_cr); + + w->h2.skint = 1; + + wa = &wsi->mux.child_list; + goto next_child; + } + + if (w->h2.skint) + lwsl_info("%s: %p: unskint (%d)\n", __func__, + w, w->h2.tx_cr); + + wsi->h2.skint = 0; + ((volatile struct lws *)w)->leave_pollout_active = 0; /* >0 == completion, <0 == error @@ -1147,7 +1190,7 @@ rops_alpn_negotiated_h2(struct lws *wsi, const char *alpn) /* HTTP2 union */ lws_hpack_dynamic_size(wsi, - wsi->h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); + wsi->h2.h2n->our_set.s[H2SET_HEADER_TABLE_SIZE]); wsi->h2.tx_cr = 65535; lwsl_info("%s: wsi %p: configured for h2\n", __func__, wsi); diff --git a/lib/roles/h2/private-lib-roles-h2.h b/lib/roles/h2/private-lib-roles-h2.h index 82c624bfc..5e90a2a28 100644 --- a/lib/roles/h2/private-lib-roles-h2.h +++ b/lib/roles/h2/private-lib-roles-h2.h @@ -25,19 +25,6 @@ extern struct lws_role_ops role_ops_h2; #define lwsi_role_h2(wsi) (wsi->role_ops == &role_ops_h2) -enum lws_h2_settings { - H2SET_HEADER_TABLE_SIZE = 1, - H2SET_ENABLE_PUSH, - H2SET_MAX_CONCURRENT_STREAMS, - H2SET_INITIAL_WINDOW_SIZE, - H2SET_MAX_FRAME_SIZE, - H2SET_MAX_HEADER_LIST_SIZE, - H2SET_RESERVED7, - H2SET_ENABLE_CONNECT_PROTOCOL, /* defined in mcmanus-httpbis-h2-ws-02 */ - - H2SET_COUNT /* always last */ -}; - struct http2_settings { uint32_t s[H2SET_COUNT]; }; @@ -216,6 +203,7 @@ enum lws_h2_protocol_send_type { LWS_H2_PPS_GOAWAY, LWS_H2_PPS_RST_STREAM, LWS_H2_PPS_UPDATE_WINDOW, + LWS_H2_PPS_SETTINGS_INITIAL_UPDATE_WINDOW }; struct lws_h2_protocol_send { @@ -261,7 +249,8 @@ struct lws_h2_ghost_sid { * fills it but it belongs to the logical child. */ struct lws_h2_netconn { - struct http2_settings set; + struct http2_settings our_set; + struct http2_settings peer_set; struct hpack_dynamic_table hpack_dyn_table; uint8_t ping_payload[8]; uint8_t one_setting[LWS_H2_SETTINGS_LEN]; @@ -322,6 +311,8 @@ struct _lws_h2_related { int tx_cr; int peer_tx_cr_est; + int32_t manual_initial_tx_credit; + uint8_t END_STREAM:1; uint8_t END_HEADERS:1; uint8_t send_END_STREAM:1; @@ -329,6 +320,7 @@ struct _lws_h2_related { uint8_t GOING_AWAY; uint8_t skint:1; uint8_t initialized:1; + uint8_t told_initial:1; uint8_t h2_state; /* RFC7540 state of the connection */ }; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index 53ee8eacf..548820727 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -2132,9 +2132,9 @@ upgrade_h2c: /* HTTP2 union */ - lws_h2_settings(wsi, &wsi->h2.h2n->set, (unsigned char *)tbuf, n); + lws_h2_settings(wsi, &wsi->h2.h2n->peer_set, (unsigned char *)tbuf, n); - lws_hpack_dynamic_size(wsi, wsi->h2.h2n->set.s[ + lws_hpack_dynamic_size(wsi, wsi->h2.h2n->peer_set.s[ H2SET_HEADER_TABLE_SIZE]); strcpy(tbuf, "HTTP/1.1 101 Switching Protocols\x0d\x0a" @@ -2781,7 +2781,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) poss = wsi->protocol->tx_packet_size; if (wsi->role_ops->tx_credit) { - lws_filepos_t txc = wsi->role_ops->tx_credit(wsi); + lws_filepos_t txc = wsi->role_ops->tx_credit(wsi, LWSTXCR_US_TO_PEER); if (!txc) { lwsl_info("%s: came here with no tx credit\n", diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index 9d09596ef..c4219902b 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -200,7 +200,9 @@ struct lws_role_ops { /* do effective callback on writeable */ int (*callback_on_writable)(struct lws *wsi); /* connection-specific tx credit in bytes */ - lws_fileofs_t (*tx_credit)(struct lws *wsi); +#define LWSTXCR_US_TO_PEER 0 +#define LWSTXCR_PEER_TO_US 1 + lws_fileofs_t (*tx_credit)(struct lws *wsi, char peer_to_us); /* role-specific write formatting */ int (*write_role_protocol)(struct lws *wsi, unsigned char *buf, size_t len, enum lws_write_protocol *wp); diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-h2-rxflow/CMakeLists.txt new file mode 100644 index 000000000..0289f114d --- /dev/null +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-http-client-h2-rxflow) +set(SRCS minimal-http-client.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_H2 1 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/README.md b/minimal-examples/http-client/minimal-http-client-h2-rxflow/README.md new file mode 100644 index 000000000..1593d262b --- /dev/null +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/README.md @@ -0,0 +1,53 @@ +# lws minimal http client-h2-rxflow + +The application reads from a server with tightly controlled and rate-limited +receive flow control using h2 tx credit. + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-l| Connect to https://localhost:7681 and accept selfsigned cert +--server |set server name to connect to +--path |URL path to access on server +-k|Apply tls option LCCSCF_ALLOW_INSECURE +-j|Apply tls option LCCSCF_ALLOW_SELFSIGNED +-m|Apply tls option LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK +-e|Apply tls option LCCSCF_ALLOW_EXPIRED +-v|Connection validity use 3s / 10s instead of default 5m / 5m10s +--nossl| disable ssl connection +-f |Indicate we will manually manage tx credit and set a new connection-specific initial tx credit + +RX is constrained to 1024 bytes every 250ms + +``` + $ ./lws-minimal-http-client-h2-rxflow --server phys.org --path "/" -f 1024 +[2019/12/26 13:32:59:6801] U: LWS minimal http client [-d] [-l] [--h1] +[2019/12/26 13:33:00:5087] N: system_notify_cb: manual peer tx credit 1024 +[2019/12/26 13:33:01:7390] U: Connected to 72.251.236.55, http response: 200 +[2019/12/26 13:33:01:7441] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:01:0855] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:02:3367] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:02:5858] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:02:8384] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:02:0886] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +... +[2019/12/26 13:33:46:1152] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:47:3650] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:47:6150] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:47:8666] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:47:1154] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:48:3656] U: RECEIVE_CLIENT_HTTP_READ: read 1024 +[2019/12/26 13:33:48:6157] U: RECEIVE_CLIENT_HTTP_READ: read 380 +[2019/12/26 13:33:48:6219] U: LWS_CALLBACK_COMPLETED_CLIENT_HTTP +[2019/12/26 13:33:48:7050] U: Completed: OK + +``` + diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c new file mode 100644 index 000000000..3013ee149 --- /dev/null +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c @@ -0,0 +1,302 @@ +/* + * lws-minimal-http-client-h2-rxflow + * + * Written in 2010-2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates the a minimal http client using lws. + * + * It visits https://warmcat.com/ and receives the html page there. You + * can dump the page data by changing the #if 0 below. + */ + +#include +#include +#include + +static int interrupted, bad = 1, status, each = 1024; +static struct lws *client_wsi; + +static const lws_retry_bo_t retry = { + .secs_since_valid_ping = 3, + .secs_since_valid_hangup = 10, +}; + +struct pss { + lws_sorted_usec_list_t sul; + struct lws *wsi; +}; + +/* + * Once we're established, we ask the server for another 1KB every 250ms + * until we have it all. + */ + +static void +drain_cb(lws_sorted_usec_list_t *sul) +{ + struct pss *pss = lws_container_of(sul, struct pss, sul); + + lws_h2_update_peer_txcredit(pss->wsi, LWS_H2_STREAM_SID, each); + + lws_sul_schedule(lws_get_context(pss->wsi), 0, &pss->sul, drain_cb, + 250 * LWS_US_PER_MS); +} + + +static int +callback_http(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct pss *pss = (struct pss *)user; + + switch (reason) { + + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", + in ? (char *)in : "(null)"); + interrupted = 1; + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + { + char buf[128]; + + lws_get_peer_simple(wsi, buf, sizeof(buf)); + status = lws_http_client_http_response(wsi); + + lwsl_user("Connected to %s, http response: %d\n", + buf, status); + } + pss->wsi = wsi; + lws_sul_schedule(lws_get_context(wsi), 0, &pss->sul, drain_cb, + 250 * LWS_US_PER_MS); + break; + + /* chunks of chunked content, with header removed */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); + +#if 0 /* enable to dump the html */ + { + const char *p = in; + + while (len--) + if (*p < 0x7f) + putchar(*p++); + else + putchar('.'); + } +#endif + return 0; /* don't passthru */ + + /* uninterpreted http content */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + { + char buffer[1024 + LWS_PRE]; + char *px = buffer + LWS_PRE; + int lenx = sizeof(buffer) - LWS_PRE; + + if (lws_http_client_read(wsi, &px, &lenx) < 0) + return -1; + } + return 0; /* don't passthru */ + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); + interrupted = 1; + bad = status != 200; + lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ + break; + + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + interrupted = 1; + bad = status != 200; + lws_sul_schedule(lws_get_context(wsi), 0, &pss->sul, NULL, + LWS_SET_TIMER_USEC_CANCEL); + lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ + break; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +static const struct lws_protocols protocols[] = { + { + "http", + callback_http, + sizeof(struct pss), + 0, + }, + { NULL, NULL, 0, 0 } +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +struct args { + int argc; + const char **argv; +}; + +static int +system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = mgr->parent; + struct lws_client_connect_info i; + struct args *a = lws_context_user(context); + const char *p; + + if (current != LWS_SYSTATE_OPERATIONAL || target != LWS_SYSTATE_OPERATIONAL) + return 0; + + lwsl_info("%s: operational\n", __func__); + + memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + i.context = context; + if (!lws_cmdline_option(a->argc, a->argv, "-n")) + i.ssl_connection = LCCSCF_USE_SSL; + + if (lws_cmdline_option(a->argc, a->argv, "-l")) { + i.port = 7681; + i.address = "localhost"; + i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; + } else { + i.port = 443; + i.address = "warmcat.com"; + } + + if (lws_cmdline_option(a->argc, a->argv, "--nossl")) + i.ssl_connection = 0; + + i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | + LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; + + i.alpn = "h2"; + if (lws_cmdline_option(a->argc, a->argv, "--h1")) + i.alpn = "http/1.1"; + + if ((p = lws_cmdline_option(a->argc, a->argv, "-p"))) + i.port = atoi(p); + + if (lws_cmdline_option(a->argc, a->argv, "-j")) + i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; + + if (lws_cmdline_option(a->argc, a->argv, "-k")) + i.ssl_connection |= LCCSCF_ALLOW_INSECURE; + + if (lws_cmdline_option(a->argc, a->argv, "-m")) + i.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; + + if (lws_cmdline_option(a->argc, a->argv, "-e")) + i.ssl_connection |= LCCSCF_ALLOW_EXPIRED; + + if ((p = lws_cmdline_option(a->argc, a->argv, "-f"))) { + i.ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW; + i.manual_initial_tx_credit = atoi(p); + lwsl_notice("%s: manual peer tx credit %d\n", __func__, + i.manual_initial_tx_credit); + } + + if ((p = lws_cmdline_option(a->argc, a->argv, "--each"))) + each = atoi(p); + + /* the default validity check is 5m / 5m10s... -v = 3s / 10s */ + + if (lws_cmdline_option(a->argc, a->argv, "-v")) + i.retry_and_idle_policy = &retry; + + if ((p = lws_cmdline_option(a->argc, a->argv, "--server"))) + i.address = p; + + if ((p = lws_cmdline_option(a->argc, a->argv, "--path"))) + i.path = p; + else + i.path = "/"; + + i.host = i.address; + i.origin = i.address; + i.method = "GET"; + + i.protocol = protocols[0].name; + i.pwsi = &client_wsi; + + return !lws_client_connect_via_info(&i); +} + +int main(int argc, const char **argv) +{ + lws_state_notify_link_t notifier = { {}, system_notify_cb, "app" }; + lws_state_notify_link_t *na[] = { ¬ifier, NULL }; + struct lws_context_creation_info info; + struct lws_context *context; + struct args args; + int n = 0; + // uint8_t memcert[4096]; + + args.argc = argc; + args.argv = argv; + + signal(SIGINT, sigint_handler); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS minimal http client [-d] [-l] [--h1]\n"); + + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + info.user = &args; + info.register_notifier_list = na; + + /* + * since we know this lws context is only ever going to be used with + * one client wsis / fds / sockets at a time, let lws know it doesn't + * have to use the default allocations for fd tables up to ulimit -n. + * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we + * will use. + */ + info.fd_limit_per_thread = 1 + 1 + 1; + +#if defined(LWS_WITH_MBEDTLS) + /* + * OpenSSL uses the system trust store. mbedTLS has to be told which + * CA to trust explicitly. + */ + info.client_ssl_ca_filepath = "./warmcat.com.cer"; +#endif +#if 0 + n = open("./warmcat.com.cer", O_RDONLY); + if (n >= 0) { + info.client_ssl_ca_mem_len = read(n, memcert, sizeof(memcert)); + info.client_ssl_ca_mem = memcert; + close(n); + n = 0; + memcert[info.client_ssl_ca_mem_len++] = '\0'; + } +#endif + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/selftest.sh b/minimal-examples/http-client/minimal-http-client-h2-rxflow/selftest.sh new file mode 100755 index 000000000..c065b444a --- /dev/null +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/selftest.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# $1: path to minimal example binaries... +# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1 +# that will be ./bin from your build dir +# +# $2: path for logs and results. The results will go +# in a subdir named after the directory this script +# is in +# +# $3: offset for test index count +# +# $4: total test count +# +# $5: path to ./minimal-examples dir in lws +# +# Test return code 0: OK, 254: timed out, other: error indication + +. $5/selftests-library.sh + +COUNT_TESTS=4 + +dotest $1 $2 warmcat +dotest $1 $2 warmcat-h1 --h1 + +spawn "" $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls +dotest $1 $2 localhost -l +spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls +dotest $1 $2 localhost-h1 -l --h1 + +kill $SPID 2>/dev/null +wait $SPID 2>/dev/null +exit $FAILS diff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client-h2-rxflow/warmcat.com.cer new file mode 100644 index 000000000..550393df7 --- /dev/null +++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/warmcat.com.cer @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIFUDCCBDigAwIBAgISA4mJfIm3iCGbU9+o8YQa+4nUMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA5MDcwNzA5MjNaFw0x +OTEyMDYwNzA5MjNaMBYxFDASBgNVBAMTC3dhcm1jYXQuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwnEoH9JW3GvpadpxHGZPb5wv1Q6KfAIMWtdq +YCOfotFxaYULuzHVxmrTTgmEqJr+eBqUBkXKmGuRR/9UipOmTu5j02qFyWHotFdF +ZGyp//8z+Rle9Qt1nL68oNIZLDtWkybh5x00b1uo4eyEszXUaa0aLqKP3lH7Q4jI +aSVARZ8snrJR640Gp3ByudvNTYkGz469bpWzRC/8wSNtzzY02DvHs1GxQx9tMXw+ +BbtUxeP7lpYFKEFBjgZaIKLv+4g8ItJIuO7gMSzG2JfpQHxdhrlhxpx7dsaMUcyM +nnYXysNL5JG3KEMhkxbtdpCaEQ8jLSPbl/rnF/+mgce+lSjMuQIDAQABo4ICYjCC +Al4wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSI9ai12zLFeNTEDHKI9Ghkqcpa2TAf +BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw +LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw +LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv +MBYGA1UdEQQPMA2CC3dhcm1jYXQuY29tMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG +CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5 +cHQub3JnMIIBBgYKKwYBBAHWeQIEAgSB9wSB9ADyAHcAY/Lbzeg7zCzPC3KEJ1dr +M6SNYXePvXWmOLHHaFRL2I0AAAFtCsVHHAAABAMASDBGAiEAy0q1cR4VwPL3iviL +cBWN67kjJRXk+DwhodmeoM3kb3gCIQC2soAHFs0Umo+0RNdFrL41+hMuidh2cXbb +Ovc6nh5tOQB3AOJpS64m6OlACeiGG7Y7g9Q+5/50iPukjyiTAZ3d8dv+AAABbQrF +R48AAAQDAEgwRgIhANqKQm4t9by263CJ7/DLOaZCjtcK29KgJjPwhv08UMn1AiEA +h35nGTASR8/E7xz+56ZUleqD7U1ABFgWZRZskIzsFO8wDQYJKoZIhvcNAQELBQAD +ggEBADDJBVbKe2LPHmi8k2vxErB3Y0Ty+3gwgPEXKYtEvQ7tos89eE+QmOXAzH5J +GwRarFf7kzmKeJv04tMebiEtshpap47oJfxCxfrtpja8hP8Cdu/v/Ae6eEzu3yet +0N08GJdxQKfgCFaoGUptbaF2RCIZS12SVcX4TPpdP+xaiZdmIx4dGM6tReQ8+y8B +10b4Hi2+d/zW0W1z6+FAemU6yleWriJDUik5oas9XZF5LAAMDb/WgF5eIB6P9CUG +LuAO8lWlk9nBgXvMLTxZ74SJb17H4kFEIrIjvABNshz5gBW8xw9nfr5YIfANtwEj +BDsq06Df3UORYVs/j3T97gPAEZ4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index 03b032a4c..98fdcd164 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -42,7 +42,6 @@ struct cliuser { static int interrupted, completed, failed, numbered, stagger_idx; static struct lws *client_wsi[COUNT]; -static struct cliuser cliuser[COUNT]; static lws_sorted_usec_list_t sul_stagger; static struct lws_client_connect_info i; struct lws_context *context; @@ -51,7 +50,7 @@ static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - struct cliuser *u = (struct cliuser *)cliuser; + int idx = (int)(long)lws_get_opaque_user_data(wsi); switch (reason) { @@ -64,7 +63,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)"); - client_wsi[u->index] = NULL; + client_wsi[idx] = NULL; failed++; if (++completed == COUNT) { lwsl_err("Done: failed: %d\n", failed); @@ -74,8 +73,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, /* chunks of chunked content, with header removed */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: - lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n", - u->index, (int)len); + lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n", idx, (int)len); #if 0 /* enable to dump the html */ { const char *p = in; @@ -103,8 +101,8 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %p: idx %d\n", - wsi, u->index); - client_wsi[u->index] = NULL; + wsi, idx); + client_wsi[idx] = NULL; if (++completed == COUNT) { if (!failed) lwsl_user("Done: all OK\n"); @@ -117,13 +115,14 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: - if (u && client_wsi[u->index]) { + lwsl_info("%s: closed: %p\n", __func__, client_wsi[idx]); + if (client_wsi[idx]) { /* * If it completed normally, it will have been set to * NULL then already. So we are dealing with an * abnormal, failing, close */ - client_wsi[u->index] = NULL; + client_wsi[idx] = NULL; failed++; if (++completed == COUNT) { lwsl_err("Done: failed: %d\n", failed); @@ -194,8 +193,7 @@ lws_try_client_connection(struct lws_client_connect_info *i, int m) i->path = "/"; i->pwsi = &client_wsi[m]; - cliuser[m].index = m; - i->userdata = &cliuser[m]; + i->opaque_user_data = (void *)(long)m; if (!lws_client_connect_via_info(i)) { failed++; @@ -290,7 +288,9 @@ int main(int argc, const char **argv) } i.context = context; - i.ssl_connection = LCCSCF_USE_SSL; + i.ssl_connection = LCCSCF_USE_SSL | + LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | + LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; /* enables h1 or h2 connection sharing */ if (lws_cmdline_option(argc, argv, "-p")) diff --git a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c index 399e302cc..3b8535924 100644 --- a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c @@ -194,7 +194,10 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, if (lws_cmdline_option(a->argc, a->argv, "--nossl")) i.ssl_connection = 0; + i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | + LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; + i.alpn = "h2"; if (lws_cmdline_option(a->argc, a->argv, "--h1")) i.alpn = "http/1.1"; @@ -218,6 +221,13 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, if (lws_cmdline_option(a->argc, a->argv, "-e")) i.ssl_connection |= LCCSCF_ALLOW_EXPIRED; + if ((p = lws_cmdline_option(a->argc, a->argv, "-f"))) { + i.ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW; + i.manual_initial_tx_credit = atoi(p); + lwsl_notice("%s: manual peer tx credit %d\n", __func__, + i.manual_initial_tx_credit); + } + /* the default validity check is 5m / 5m10s... -v = 3s / 10s */ if (lws_cmdline_option(a->argc, a->argv, "-v"))