http2 window update
authorAndy Green <andy.green@linaro.org>
Wed, 29 Oct 2014 01:39:08 +0000 (09:39 +0800)
committerAndy Green <andy.green@linaro.org>
Sat, 1 Nov 2014 22:54:09 +0000 (06:54 +0800)
Signed-off-by: Andy Green <andy.green@linaro.org>
lib/http2.c
lib/libwebsockets.c
lib/libwebsockets.h
lib/pollfd.c
lib/private-libwebsockets.h
lib/server.c
lib/ssl-http2.c

index ab08b13..24d5ba4 100644 (file)
@@ -74,6 +74,7 @@ lws_create_server_child_wsi(struct libwebsocket_context *context, struct libwebs
        parent_wsi->u.http2.child_count++;
        
        wsi->u.http2.my_priority = 16;
+       wsi->u.http2.tx_credit = 65535;
        
        wsi->state = WSI_STATE_HTTP2_ESTABLISHED;
        wsi->mode = parent_wsi->mode;
@@ -156,7 +157,13 @@ int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigne
        *p++ = sid;
        
        lwsl_info("%s: %p (eff %p). type %d, flags 0x%x, sid=%d, len=%d\n",
-                 __func__, wsi, wsi_eff, type, flags, sid, len);
+                 __func__, wsi, wsi_eff, type, flags, sid, len, wsi->u.http2.tx_credit);
+       
+       if (type == LWS_HTTP2_FRAME_TYPE_DATA) {
+               if (wsi->u.http2.tx_credit < len)
+                       lwsl_err("%s: %p: sending payload len %d but tx_credit only %d!\n", len, wsi->u.http2.tx_credit);
+               wsi->u.http2.tx_credit -= len;
+       }
 
        n = lws_issue_raw(wsi_eff, &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH], len + LWS_HTTP2_FRAME_HEADER_LENGTH);
        if (n >= LWS_HTTP2_FRAME_HEADER_LENGTH)
@@ -195,6 +202,7 @@ lws_http2_parser(struct libwebsocket_context *context,
                        lwsl_info("http2: %p: established\n", wsi);
                        wsi->state = WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS;
                        wsi->u.http2.count = 0;
+                       wsi->u.http2.tx_credit = 65535;
                        
                        /* 
                         * we must send a settings frame -- empty one is OK...
@@ -255,6 +263,8 @@ lws_http2_parser(struct libwebsocket_context *context,
                                }
                                break;
                        case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE:
+                               wsi->u.http2.hpack_e_dep <<= 8;
+                               wsi->u.http2.hpack_e_dep |= c;
                                break;
                        }
                        if (wsi->u.http2.count != wsi->u.http2.length)
@@ -264,6 +274,7 @@ lws_http2_parser(struct libwebsocket_context *context,
 
                        wsi->u.http2.frame_state = 0;
                        wsi->u.http2.count = 0;
+                       swsi = wsi->u.http2.stream_wsi;
                        /* set our initial window size */
                        if (!wsi->u.http2.initialized) {
                                wsi->u.http2.tx_credit = wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE];
@@ -274,7 +285,7 @@ lws_http2_parser(struct libwebsocket_context *context,
                        case LWS_HTTP2_FRAME_TYPE_HEADERS:
                                /* service the http request itself */
                                lwsl_info("servicing initial http request, wsi=%p, stream wsi=%p\n", wsi, wsi->u.http2.stream_wsi);
-                               n = lws_http_action(context, wsi->u.http2.stream_wsi);
+                               n = lws_http_action(context, swsi);
                                lwsl_info("  action result %d\n", n);
                                break;
                        case LWS_HTTP2_FRAME_TYPE_PING:
@@ -283,6 +294,17 @@ lws_http2_parser(struct libwebsocket_context *context,
                                        lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_PONG);
                                }
                                break;
+                       case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE:
+                               wsi->u.http2.hpack_e_dep &= ~(1 << 31);
+                               if ((long long)swsi->u.http2.tx_credit + (unsigned long long)wsi->u.http2.hpack_e_dep > (~(1 << 31)))
+                                       return 1; /* actually need to close swsi not the whole show */
+                               swsi->u.http2.tx_credit += wsi->u.http2.hpack_e_dep;
+                               if (swsi->u.http2.waiting_tx_credit && swsi->u.http2.tx_credit > 0) {
+                                       lwsl_info("%s: %p: waiting_tx_credit -> wait on writeable\n", __func__, wsi);
+                                       swsi->u.http2.waiting_tx_credit = 0;
+                                       libwebsocket_callback_on_writable(context, swsi);
+                               }       
+                               break;
                        }
                        break;
                }
@@ -307,7 +329,6 @@ lws_http2_parser(struct libwebsocket_context *context,
                case 8:
                        wsi->u.http2.stream_id <<= 8;
                        wsi->u.http2.stream_id |= c;
-                       wsi->u.http2.stream_wsi = wsi;
                        break;
                }
                if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { /* frame header complete */
@@ -315,6 +336,9 @@ lws_http2_parser(struct libwebsocket_context *context,
                                  wsi->u.http2.type, wsi->u.http2.flags, wsi->u.http2.stream_id, wsi->u.http2.length);
                        wsi->u.http2.count = 0;
                        
+                       wsi->u.http2.stream_wsi = wsi;
+                       if (wsi->u.http2.stream_id)
+                               wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id);
                        
                        switch (wsi->u.http2.type) {
                        case LWS_HTTP2_FRAME_TYPE_SETTINGS:
@@ -342,7 +366,6 @@ lws_http2_parser(struct libwebsocket_context *context,
                                lwsl_info("LWS_HTTP2_FRAME_TYPE_HEADERS: stream_id = %d\n", wsi->u.http2.stream_id);
                                if (!wsi->u.http2.stream_id)
                                        return 1;
-                               wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id);
                                if (!wsi->u.http2.stream_wsi)
                                        wsi->u.http2.stream_wsi = lws_create_server_child_wsi(context, wsi, wsi->u.http2.stream_id);
                                                                
@@ -374,6 +397,10 @@ update_end_headers:
                                                swsi->u.http2.hpack = HPKS_TYPE;
                                lwsl_info("initial hpack state %d\n", swsi->u.http2.hpack);
                                break;
+                       case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE:
+                               if (wsi->u.http2.length != 4)
+                                       return 1;
+                               break;
                        }
                        if (wsi->u.http2.length == 0)
                                wsi->u.http2.frame_state = 0;
@@ -391,6 +418,8 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc
        struct libwebsocket *swsi;
        int n, m = 0;
 
+       lwsl_debug("%s: %p: %d\n", __func__, wsi, wsi->pps);
+       
        switch (wsi->pps) {
        case LWS_PPS_HTTP2_MY_SETTINGS:
                for (n = 1; n < LWS_HTTP2_SETTINGS__COUNT; n++)
index 5b36516..5cf78eb 100644 (file)
@@ -821,7 +821,7 @@ void lws_set_protocol_write_pending(struct libwebsocket_context *context,
                                    struct libwebsocket *wsi,
                                    enum lws_pending_protocol_send pend)
 {
-               lwsl_err("setting pps %d\n", pend);
+       lwsl_info("setting pps %d\n", pend);
        
        if (wsi->pps)
                lwsl_err("pps overwrite\n");
@@ -829,3 +829,20 @@ void lws_set_protocol_write_pending(struct libwebsocket_context *context,
        libwebsocket_rx_flow_control(wsi, 0);
        libwebsocket_callback_on_writable(context, wsi);
 }
+
+LWS_VISIBLE size_t
+lws_get_peer_write_allowance(struct libwebsocket *wsi)
+{
+#ifdef LWS_USE_HTTP2
+       /* only if we are using HTTP2 on this connection */
+       if (wsi->mode != LWS_CONNMODE_HTTP2_SERVING)
+               return -1;
+       /* user is only interested in how much he can send, or that he can't  */
+       if (wsi->u.http2.tx_credit <= 0)
+               return 0;
+       
+       return wsi->u.http2.tx_credit;
+#else
+       return -1;
+#endif
+}
index 0ed5592..ca382ce 100644 (file)
@@ -153,9 +153,11 @@ LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len);
 
 #define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_ARG
 
-    /* the struct libwebsocket_protocols has the id field present */
+/* the struct libwebsocket_protocols has the id field present */
 #define LWS_FEATURE_PROTOCOLS_HAS_ID_FIELD
 
+/* you can call lws_get_peer_write_allowance */
+#define LWS_FEATURE_PROTOCOLS_HAS_PEER_WRITE_ALLOWANCE
 
 enum libwebsocket_context_options {
        LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT = 2,
@@ -1226,6 +1228,25 @@ libwebsocket_rx_flow_allow_all_protocol(
 LWS_VISIBLE LWS_EXTERN size_t
 libwebsockets_remaining_packet_payload(struct libwebsocket *wsi);
 
+/*
+ * if the protocol does not have any guidence, returns -1.  Currently only
+ * http2 connections get send window information from this API.  But your code
+ * should use it so it can work properly with any protocol.
+ * 
+ * If nonzero return is the amount of payload data the peer or intermediary has
+ * reported it has buffer space for.  That has NO relationship with the amount
+ * of buffer space your OS can accept on this connection for a write action.
+ * 
+ * This number represents the maximum you could send to the peer or intermediary
+ * on this connection right now without it complaining.
+ * 
+ * lws manages accounting for send window updates and payload writes
+ * automatically, so this number reflects the situation at the peer or
+ * intermediary dynamically.
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_get_peer_write_allowance(struct libwebsocket *wsi);
+
 LWS_VISIBLE LWS_EXTERN struct libwebsocket *
 libwebsocket_client_connect(struct libwebsocket_context *clients,
                              const char *address,
index 63cdfc3..10fd810 100644 (file)
@@ -207,6 +207,19 @@ libwebsocket_callback_on_writable(struct libwebsocket_context *context,
                return 1;
        }
        
+       if (wsi->u.http2.tx_credit <= 0) {
+               /*
+                * other side is not able to cope with us sending
+                * anything so no matter if we have POLLOUT on our side.
+                * 
+                * Delay waiting for our POLLOUT until peer indicates he has
+                * space for more using tx window command in http2 layer
+                */
+               lwsl_info("%s: %p: waiting_tx_credit (%d)\n", __func__, wsi, wsi->u.http2.tx_credit);
+               wsi->u.http2.waiting_tx_credit = 1;
+               return 0;
+       }
+       
        network_wsi = lws_http2_get_network_wsi(wsi);
        already = network_wsi->u.http2.requested_POLLOUT;
        
index ac7e7c3..da9c7a1 100755 (executable)
@@ -708,6 +708,7 @@ struct _lws_http2_related {
        unsigned int send_END_STREAM:1;
        unsigned int GOING_AWAY;
        unsigned int requested_POLLOUT:1;
+       unsigned int waiting_tx_credit:1;
 
        /* hpack */
        enum http2_hpack_state hpack;
@@ -720,7 +721,8 @@ struct _lws_http2_related {
        unsigned int huff:1;
        unsigned int value:1;
        
-       unsigned int tx_credit;
+       /* negative credit is mandated by the spec */
+       int tx_credit;
        unsigned int my_stream_id;
        unsigned int child_count;
        int my_priority;
index 1c6afd2..3e7e471 100644 (file)
@@ -426,9 +426,9 @@ upgrade_h2c:
                       "HTTP/1.1 101 Switching Protocols\x0d\x0a"
                      "Connection: Upgrade\x0d\x0a"
                      "Upgrade: h2c\x0d\x0a\x0d\x0a");
-               n = lws_issue_raw(wsi, (unsigned char *)wsi->protocol->name,
-                                       strlen(wsi->protocol->name));
-               if (n != strlen(wsi->protocol->name)) {
+               n = lws_issue_raw(wsi, (unsigned char *)protocol_list,
+                                       strlen(protocol_list));
+               if (n != strlen(protocol_list)) {
                        lwsl_debug("http2 switch: ERROR writing to socket\n");
                        return 1;
                }
index 134231f..832d915 100644 (file)
@@ -140,8 +140,6 @@ void lws_http2_configure_if_upgraded(struct libwebsocket *wsi)
 
        ah = wsi->u.hdr.ah;
 
-       wsi->mode = LWS_CONNMODE_HTTP2_SERVING;
-
        /* union transition */
        memset(&wsi->u, 0, sizeof(wsi->u));