http2 track content length add END_STREAM
authorAndy Green <andy.green@linaro.org>
Sat, 18 Oct 2014 04:23:05 +0000 (12:23 +0800)
committerAndy Green <andy.green@linaro.org>
Sat, 18 Oct 2014 04:23:05 +0000 (12:23 +0800)
Signed-off-by: Andy Green <andy.green@linaro.org>
lib/hpack.c
lib/http2.c
lib/libwebsockets.h
lib/output.c
lib/private-libwebsockets.h
lib/server.c
test-server/test-server.c

index bd4f462..4b2a1a8 100644 (file)
@@ -238,25 +238,96 @@ static void lws_dump_header(struct libwebsocket *wsi, int hdr)
        char s[200];
        int len = lws_hdr_copy(wsi, s, sizeof(s) - 1, hdr);
        s[len] = '\0';
-       lwsl_info("  hdr tok %d '%s'\n", hdr, s);
+       lwsl_info("  hdr tok %d (%s) = '%s'\n", hdr, lws_token_to_string(hdr), s);
 }
 
-static int lws_token_from_index(struct libwebsocket *wsi, int index)
+static int lws_token_from_index(struct libwebsocket *wsi, int index, char **arg, int *len)
 {
+       struct hpack_dynamic_table *dyn;
+       
+       /* dynamic table only belongs to network wsi */
+       
+       wsi = lws_http2_get_network_wsi(wsi);
+       
+       dyn = wsi->u.http2.hpack_dyn_table;
+
        if (index < ARRAY_SIZE(static_token))
                return static_token[index];
+
+       if (!dyn)
+               return 0;
+       
+       index -= ARRAY_SIZE(static_token);
+       if (index >= dyn->num_entries)
+               return 0;
+       
+       if (arg && len) {
+               *arg = dyn->args + dyn->entries[index].arg_offset;
+               *len = dyn->entries[index].arg_len;
+       }
+       
+       return dyn->entries[index].token;
+}
+
+static int lws_hpack_add_dynamic_header(struct libwebsocket *wsi, int token, char *arg, int len)
+{
+       struct hpack_dynamic_table *dyn;
+       int ret = 1;
+       
+       wsi = lws_http2_get_network_wsi(wsi);
+       dyn = wsi->u.http2.hpack_dyn_table;
+
+       if (!dyn) {
+               dyn = malloc(sizeof(*dyn));
+               if (!dyn)
+                       return 1;
+               memset(dyn, 0, sizeof(*dyn));
+               wsi->u.http2.hpack_dyn_table = dyn;
+               
+               dyn->args = malloc(1024);
+               if (!dyn->args)
+                       goto bail1;
+               dyn->args_length = 1024;
+               dyn->entries = malloc(sizeof(dyn->entries[0]) * 20);
+               if (!dyn->entries)
+                       goto bail2;
+               dyn->num_entries = 20;
+       }
        
-       // dynamic indexes
+       if (dyn->next == dyn->num_entries)
+               return 1;
+       
+       if (dyn->args_length - dyn->pos < len)
+               return 1;
+       
+       dyn->entries[dyn->next].token = token;
+       dyn->entries[dyn->next].arg_offset = dyn->pos;
+       if (len)
+               memcpy(dyn->args + dyn->pos, arg, len);
+       dyn->entries[dyn->next].arg_len = len;
+       
+       lwsl_info("%s: added dynamic hdr %d, token %d (%s), len %d\n", __func__, dyn->next, token, lws_token_to_string(token), len);
+       
+       dyn->pos += len;
+       dyn->next++;
        
        return 0;
+       
+bail2:
+       free(dyn->args);
+bail1:
+       free(dyn);
+       wsi->u.http2.hpack_dyn_table = NULL;
+               
+       return ret;
 }
 
-static int lws_add_indexed_hdr(struct libwebsocket *wsi, int idx)
+static int lws_write_indexed_hdr(struct libwebsocket *wsi, int idx)
 {
        const char *p;
-       int tok = lws_token_from_index(wsi, idx);
+       int tok = lws_token_from_index(wsi, idx, NULL, 0);
 
-       lwsl_info("adding indexed hdr %d (tok %d)\n", idx, tok);
+       lwsl_info("writing indexed hdr %d (tok %d '%s')\n", idx, tok, lws_token_to_string(tok));
 
        if (lws_frag_start(wsi, tok))
                return 1;
@@ -283,6 +354,28 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
        int n;
 
        switch (wsi->u.http2.hpack) {
+       case HPKS_OPT_PADDING:
+               wsi->u.http2.padding = c;
+               lwsl_info("padding %d\n", c);
+               if (wsi->u.http2.flags & LWS_HTTP2_FLAG_PRIORITY) {
+                       wsi->u.http2.hpack = HKPS_OPT_E_DEPENDENCY;
+                       wsi->u.http2.hpack_m = 4;
+               } else
+                       wsi->u.http2.hpack = HPKS_TYPE;
+               break;
+       case HKPS_OPT_E_DEPENDENCY:
+               wsi->u.http2.hpack_e_dep <<= 8;
+               wsi->u.http2.hpack_e_dep |= c;
+               if (! --wsi->u.http2.hpack_m) {
+                       lwsl_info("hpack_e_dep = 0x%x\n", wsi->u.http2.hpack_e_dep);
+                       wsi->u.http2.hpack = HKPS_OPT_WEIGHT;
+               }
+               break;
+       case HKPS_OPT_WEIGHT:
+               /* weight */
+               wsi->u.http2.hpack = HPKS_TYPE;
+               break;
+                       
        case HPKS_TYPE:
                if (c & 0x80) { /* indexed header field only */
                        /* just a possibly-extended integer */
@@ -294,7 +387,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
                                wsi->u.http2.hpack = HPKS_IDX_EXT;
                                break;
                        }
-                       if (lws_add_indexed_hdr(wsi, c & 0x7f))
+                       if (lws_write_indexed_hdr(wsi, c & 0x7f))
                                return 1;
                        /* stay at same state */
                        break;
@@ -374,7 +467,7 @@ int lws_hpack_interpret(struct libwebsocket_context *context,
                if (!(c & 0x80)) {
                        switch (wsi->u.http2.hpack_type) {
                        case HPKT_INDEXED_HDR_7:
-                               if (lws_add_indexed_hdr(wsi, wsi->u.http2.hpack_len))
+                               if (lws_write_indexed_hdr(wsi, wsi->u.http2.hpack_len))
                                        return 1;
                                wsi->u.http2.hpack = HPKS_TYPE;
                                break;
@@ -396,7 +489,7 @@ pre_data:
                        if (wsi->u.http2.value) {
                                if (lws_frag_start(wsi,
                                        lws_token_from_index(wsi,
-                                               wsi->u.http2.header_index)))
+                                               wsi->u.http2.header_index, NULL, NULL)))
                                        return 1;
                        } else
                                wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
@@ -448,14 +541,27 @@ pre_data:
                        }
                }
                if (--wsi->u.http2.hpack_len == 0) {
+                       
+                       switch (wsi->u.http2.hpack_type) {
+                       case HPKT_LITERAL_HDR_VALUE_INCR:
+                       case HPKT_INDEXED_HDR_6_VALUE_INCR: // !!!
+                               if (lws_hpack_add_dynamic_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index, NULL, NULL), NULL, 0))
+                                       return 1;
+                               break;
+                       default:
+                               break;
+                       }
+                       
                        n = 8;
                        if (wsi->u.http2.value) {
                                if (lws_frag_end(wsi))
                                        return 1;
 
-                               lws_dump_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index));
-
-                               wsi->u.http2.hpack = HPKS_TYPE;
+                               lws_dump_header(wsi, lws_token_from_index(wsi, wsi->u.http2.header_index, NULL, NULL));
+                               if (wsi->u.http2.count + wsi->u.http2.padding == wsi->u.http2.length)
+                                       wsi->u.http2.hpack = HKPS_OPT_DISCARD_PADDING;
+                               else
+                                       wsi->u.http2.hpack = HPKS_TYPE;
                        } else { /* name */
                                if (wsi->u.hdr.parser_state < WSI_TOKEN_COUNT)
                                        
@@ -464,6 +570,11 @@ pre_data:
                        }
                }
                break;
+       case HKPS_OPT_DISCARD_PADDING:
+               lwsl_info("eating padding %x\n", c);
+               if (! --wsi->u.http2.padding)
+                       wsi->u.http2.hpack = HPKS_TYPE;
+               break;
        }
        
        return 0;
index 0846651..ae96237 100644 (file)
@@ -128,14 +128,19 @@ lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned c
        return 0;
 }
 
+struct libwebsocket *lws_http2_get_network_wsi(struct libwebsocket *wsi)
+{
+       while (wsi->u.http2.parent_wsi)
+               wsi = wsi->u.http2.parent_wsi;
+       
+       return wsi;
+}
+
 int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigned int sid, unsigned int len, unsigned char *buf)
 {
-       struct libwebsocket *wsi_eff = wsi;
+       struct libwebsocket *wsi_eff = lws_http2_get_network_wsi(wsi);
        unsigned char *p = &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH];
        int n;
-       
-       while (wsi_eff->u.http2.parent_wsi)
-               wsi_eff = wsi_eff->u.http2.parent_wsi;
 
        *p++ = len >> 16;
        *p++ = len >> 8;
@@ -174,6 +179,7 @@ int
 lws_http2_parser(struct libwebsocket_context *context,
                     struct libwebsocket *wsi, unsigned char c)
 {
+       struct libwebsocket *swsi;
        int n;
        //dstruct libwebsocket *wsi_new;
 
@@ -200,6 +206,8 @@ lws_http2_parser(struct libwebsocket_context *context,
        case WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS:
        case WSI_STATE_HTTP2_ESTABLISHED:
                if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { // payload
+                       wsi->u.http2.count++;
+                       wsi->u.http2.stream_wsi->u.http2.count = wsi->u.http2.count;
                        /* applies to wsi->u.http2.stream_wsi which may be wsi*/
                        switch(wsi->u.http2.type) {
                        case LWS_HTTP2_FRAME_TYPE_SETTINGS:
@@ -217,7 +225,6 @@ lws_http2_parser(struct libwebsocket_context *context,
                                        return 1;
                                break;
                        }
-                       wsi->u.http2.count++;
                        if (wsi->u.http2.count != wsi->u.http2.length)
                                break;
                        
@@ -294,15 +301,34 @@ lws_http2_parser(struct libwebsocket_context *context,
                                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);
-
-                               if (!wsi->u.http2.stream_wsi)
-                                       return 1;
-                               
+                                                               
                                /* END_STREAM means after servicing this, close the stream */
                                wsi->u.http2.END_STREAM = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_STREAM);
+                               lwsl_info("%s: headers END_STREAM = %d\n",__func__, wsi->u.http2.END_STREAM);
 update_end_headers:
                                /* no END_HEADERS means CONTINUATION must come */
                                wsi->u.http2.END_HEADERS = !!(wsi->u.http2.flags & LWS_HTTP2_FLAG_END_HEADERS);
+                               
+                               swsi = wsi->u.http2.stream_wsi;
+                               if (!swsi)
+                                       return 1;
+
+                               
+                               /* prepare the hpack parser at the right start */
+                               
+                               swsi->u.http2.flags = wsi->u.http2.flags;
+                               swsi->u.http2.length = wsi->u.http2.length;
+                               swsi->u.http2.END_STREAM = wsi->u.http2.END_STREAM;
+
+                               if (swsi->u.http2.flags & LWS_HTTP2_FLAG_PADDED)
+                                       swsi->u.http2.hpack = HPKS_OPT_PADDING;
+                               else
+                                       if (swsi->u.http2.flags & LWS_HTTP2_FLAG_PRIORITY) {
+                                               swsi->u.http2.hpack = HKPS_OPT_E_DEPENDENCY;
+                                               swsi->u.http2.hpack_m = 4;
+                                       } else
+                                               swsi->u.http2.hpack = HPKS_TYPE;
+                               lwsl_info("initial hpack state %d\n", swsi->u.http2.hpack);
                                break;
                        }
                        if (wsi->u.http2.length == 0)
index 444d716..0ed5592 100644 (file)
@@ -265,6 +265,9 @@ enum libwebsocket_write_protocol {
        LWS_WRITE_PING,
        LWS_WRITE_PONG,
 
+       /* Same as write_http but we know this write ends the transaction */
+       LWS_WRITE_HTTP_FINAL,
+       
        /* HTTP2 */
 
        LWS_WRITE_HTTP_HEADERS,
@@ -1082,6 +1085,11 @@ lws_add_http_header_by_token(struct libwebsocket_context *context,
                            int length,
                            unsigned char **p,
                            unsigned char *end);
+LWS_VISIBLE LWS_EXTERN int lws_add_http_header_content_length(struct libwebsocket_context *context,
+                           struct libwebsocket *wsi,
+                           unsigned long content_length,
+                           unsigned char **p,
+                           unsigned char *end);
 LWS_VISIBLE LWS_EXTERN int
 lws_add_http_header_status(struct libwebsocket_context *context,
                            struct libwebsocket *wsi,
index 7c5cb53..aebec9c 100644 (file)
@@ -264,7 +264,9 @@ LWS_VISIBLE int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf,
                return 0;
        }
 
-       if (protocol == LWS_WRITE_HTTP || protocol == LWS_WRITE_HTTP_HEADERS)
+       if (protocol == LWS_WRITE_HTTP ||
+           protocol == LWS_WRITE_HTTP_FINAL ||
+           protocol == LWS_WRITE_HTTP_HEADERS)
                goto send_raw;
 
        /* websocket protocol, either binary or text */
@@ -431,6 +433,7 @@ send_raw:
        case LWS_WRITE_CLOSE:
 /*             lwsl_hexdump(&buf[-pre], len + post); */
        case LWS_WRITE_HTTP:
+       case LWS_WRITE_HTTP_FINAL:
        case LWS_WRITE_HTTP_HEADERS:
        case LWS_WRITE_PONG:
        case LWS_WRITE_PING:
@@ -443,6 +446,21 @@ send_raw:
                                n = LWS_HTTP2_FRAME_TYPE_HEADERS;
                                flags = LWS_HTTP2_FLAG_END_HEADERS;
                        }
+                       
+                       if ((protocol == LWS_WRITE_HTTP || protocol == LWS_WRITE_HTTP_FINAL) && wsi->u.http.content_length) {
+                               wsi->u.http.content_remain -= len;
+                               lwsl_info("%s: content_remain = %lu\n", __func__, wsi->u.http.content_remain);
+                               if (!wsi->u.http.content_remain) {
+                                       lwsl_info("%s: selecting final write mode\n", __func__);
+                                       protocol = LWS_WRITE_HTTP_FINAL;
+                               }
+                       }
+                       
+                       if (protocol == LWS_WRITE_HTTP_FINAL && wsi->u.http2.END_STREAM) {
+                               lwsl_info("%s: setting END_STREAM\n", __func__);
+                               flags |= LWS_HTTP2_FLAG_END_STREAM;
+                       }
+
                        return lws_http2_frame_write(wsi, n, flags, wsi->u.http2.my_stream_id, len, buf);
                }
 #endif
@@ -519,12 +537,12 @@ LWS_VISIBLE int libwebsockets_serve_http_file_fragment(
                if (n < 0)
                        return -1; /* caller will close */
                if (n) {
+                       wsi->u.http.filepos += n;
                        m = libwebsocket_write(wsi, context->service_buffer, n,
-                                                               LWS_WRITE_HTTP);
+                                              wsi->u.http.filepos == wsi->u.http.filelen ? LWS_WRITE_HTTP_FINAL : LWS_WRITE_HTTP);
                        if (m < 0)
                                return -1;
 
-                       wsi->u.http.filepos += m;
                        if (m != n)
                                /* adjust for what was not sent */
                                compatible_file_seek_cur(wsi->u.http.fd, m - n);
index e0a6680..4631bfe 100755 (executable)
@@ -626,6 +626,13 @@ struct http2_settings {
 };
 
 enum http2_hpack_state {
+       
+       /* optional before first header block */
+       HPKS_OPT_PADDING,
+       HKPS_OPT_E_DEPENDENCY,
+       HKPS_OPT_WEIGHT,
+       
+       /* header block */
        HPKS_TYPE,
        
        HPKS_IDX_EXT,
@@ -634,6 +641,9 @@ enum http2_hpack_state {
        HPKS_HLEN_EXT,
 
        HPKS_DATA,
+       
+       /* optional after last header block */
+       HKPS_OPT_DISCARD_PADDING,
 };
 
 enum http2_hpack_type {
@@ -645,6 +655,21 @@ enum http2_hpack_type {
        HPKT_SIZE_5
 };
 
+struct hpack_dt_entry {
+       int token; /* additions that don't map to a token are ignored */
+       int arg_offset;
+       int arg_len;
+};
+
+struct hpack_dynamic_table {
+       struct hpack_dt_entry *entries;
+       char *args;
+       int pos;
+       int next;
+       int num_entries;
+       int args_length;
+};
+
 struct _lws_http2_related {
        /* 
         * having this first lets us also re-use all HTTP union code
@@ -659,6 +684,8 @@ struct _lws_http2_related {
        struct libwebsocket *parent_wsi;
        struct libwebsocket *next_child_wsi;
 
+       struct hpack_dynamic_table *hpack_dyn_table;
+       
        unsigned int count;
        
        /* frame */
@@ -668,6 +695,7 @@ struct _lws_http2_related {
        unsigned char type;
        unsigned char flags;
        unsigned char frame_state;
+       unsigned char padding;
        
        unsigned int END_STREAM:1;
        unsigned int END_HEADERS:1;
@@ -679,6 +707,7 @@ struct _lws_http2_related {
        unsigned int hpack_len;
        unsigned short hpack_pos;
        unsigned char hpack_m;
+       unsigned int hpack_e_dep;
        unsigned int huff:1;
        unsigned int value:1;
        
@@ -923,6 +952,7 @@ user_callback_handle_rxflow(callback_function,
                         enum libwebsocket_callback_reasons reason, void *user,
                                                          void *in, size_t len);
 #ifdef LWS_USE_HTTP2
+LWS_EXTERN struct libwebsocket *lws_http2_get_network_wsi(struct libwebsocket *wsi);
 LWS_EXTERN int
 lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned char *buf, int len);
 LWS_EXTERN void lws_http2_init(struct http2_settings *settings);
index 6b9b84a..f02e5de 100644 (file)
@@ -943,6 +943,24 @@ int lws_add_http_header_by_token(struct libwebsocket_context *context,
        return lws_add_http_header_by_name(context, wsi, name, value, length, p, end);
 }
 
+int lws_add_http_header_content_length(struct libwebsocket_context *context,
+                           struct libwebsocket *wsi,
+                           unsigned long content_length,
+                           unsigned char **p,
+                           unsigned char *end)
+{
+       char b[24];
+       int n;
+
+       n = sprintf(b, "%lu", content_length);
+       if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b, n, p, end))
+               return 1;
+       wsi->u.http.content_length = content_length;
+       wsi->u.http.content_remain = content_length;
+
+       return 0;
+}
+
 static const char *err400[] = {
        "Bad Request",
        "Unauthorized",
@@ -1069,9 +1087,7 @@ LWS_VISIBLE int libwebsockets_serve_http_file(
        unsigned char *p = response;
        unsigned char *end = p + sizeof(context->service_buffer) -
                                        LWS_SEND_BUFFER_PRE_PADDING;
-       unsigned char clen[10];
        int ret = 0;
-       int n;
 
        wsi->u.http.fd = lws_plat_open_file(file, &wsi->u.http.filelen);
 
@@ -1088,8 +1104,7 @@ LWS_VISIBLE int libwebsockets_serve_http_file(
                return -1;
        if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, strlen(content_type), &p, end))
                return -1;
-       n = sprintf((char *)clen, "%lu", (unsigned long)wsi->u.http.filelen);
-       if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, clen, n, &p, end))
+       if (lws_add_http_header_content_length(context, wsi, wsi->u.http.filelen, &p, end))
                return -1;
 
        if (other_headers) {
index 421eca4..04f0b34 100644 (file)
@@ -234,8 +234,7 @@ static int callback_http(struct libwebsocket_context *context,
                                return 1;
                        if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end))
                                return 1;
-                       n = sprintf(b64, "%u", (unsigned int)stat_buf.st_size);
-                       if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b64, n, &p, end))
+                       if (lws_add_http_header_content_length(context, wsi,stat_buf.st_size, &p, end))
                                return 1;
                        if (lws_finalize_http_header(context, wsi, &p, end))
                                return 1;
@@ -356,8 +355,10 @@ static int callback_http(struct libwebsocket_context *context,
                        /* sent it all, close conn */
                        if (n == 0)
                                goto flush_bail;
+
                        /*
                         * To support HTTP2, must take care about preamble space
+                        * and identify when we send the last frame
                         */
                        m = libwebsocket_write(wsi,
                                               buffer + LWS_SEND_BUFFER_PRE_PADDING,
@@ -365,6 +366,9 @@ static int callback_http(struct libwebsocket_context *context,
                        if (m < 0)
                                /* write failed, close conn */
                                goto bail;
+                       /*
+                        * http2 won't do this
+                        */
                        if (m != n)
                                /* partial write, adjust */
                                lseek(pss->fd, m - n, SEEK_CUR);
@@ -378,6 +382,7 @@ static int callback_http(struct libwebsocket_context *context,
                                break;
 
                } while (!lws_send_pipe_choked(wsi));
+               
                libwebsocket_callback_on_writable(context, wsi);
                break;
 flush_bail: