Coverage Report

Created: 2025-07-11 06:25

/src/h2o/lib/http2/http2_debug_state.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2016 DeNA Co., Ltd., Ichito Nagata
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
 * IN THE SOFTWARE.
21
 */
22
#include <inttypes.h>
23
#include "h2o.h"
24
#include "h2o/http2.h"
25
#include "h2o/http2_internal.h"
26
27
static const char debug_state_string_open[] = "OPEN";
28
static const char debug_state_string_half_closed_remote[] = "HALF_CLOSED_REMOTE";
29
static const char debug_state_string_reserved_local[] = "RESERVED_LOCAL";
30
31
static const char *get_debug_state_string(h2o_http2_stream_t *stream)
32
0
{
33
0
    if (h2o_http2_stream_is_push(stream->stream_id)) {
34
0
        switch (stream->state) {
35
0
        case H2O_HTTP2_STREAM_STATE_RECV_HEADERS:
36
0
        case H2O_HTTP2_STREAM_STATE_RECV_BODY:
37
0
        case H2O_HTTP2_STREAM_STATE_REQ_PENDING:
38
0
            return debug_state_string_reserved_local;
39
0
        case H2O_HTTP2_STREAM_STATE_SEND_HEADERS:
40
0
        case H2O_HTTP2_STREAM_STATE_SEND_BODY:
41
0
        case H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL:
42
0
            return debug_state_string_half_closed_remote;
43
0
        case H2O_HTTP2_STREAM_STATE_IDLE:
44
0
        case H2O_HTTP2_STREAM_STATE_END_STREAM:
45
0
            return NULL;
46
0
        }
47
0
    } else {
48
0
        switch (stream->state) {
49
0
        case H2O_HTTP2_STREAM_STATE_RECV_HEADERS:
50
0
        case H2O_HTTP2_STREAM_STATE_RECV_BODY:
51
0
            return debug_state_string_open;
52
0
        case H2O_HTTP2_STREAM_STATE_REQ_PENDING:
53
0
        case H2O_HTTP2_STREAM_STATE_SEND_HEADERS:
54
0
        case H2O_HTTP2_STREAM_STATE_SEND_BODY:
55
0
        case H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL:
56
0
            return debug_state_string_half_closed_remote;
57
0
        case H2O_HTTP2_STREAM_STATE_IDLE:
58
0
        case H2O_HTTP2_STREAM_STATE_END_STREAM:
59
0
            return NULL;
60
0
        }
61
0
    }
62
0
    return NULL;
63
0
}
64
65
__attribute__((format(printf, 3, 4))) static void append_chunk(h2o_mem_pool_t *pool, h2o_iovec_vector_t *chunks, const char *fmt,
66
                                                               ...)
67
0
{
68
0
    va_list args;
69
70
0
    va_start(args, fmt);
71
0
    int size = vsnprintf(NULL, 0, fmt, args);
72
0
    va_end(args);
73
74
0
    assert(size > 0);
75
76
0
    h2o_iovec_t v;
77
0
    v.base = h2o_mem_alloc_pool(pool, char, size + 1);
78
79
0
    va_start(args, fmt);
80
0
    v.len = vsnprintf(v.base, size + 1, fmt, args);
81
0
    va_end(args);
82
83
0
    h2o_vector_reserve(pool, chunks, chunks->size + 1);
84
0
    chunks->entries[chunks->size++] = v;
85
0
}
86
87
static void append_header_table_chunks(h2o_mem_pool_t *pool, h2o_iovec_vector_t *chunks, h2o_hpack_header_table_t *header_table)
88
0
{
89
0
    int i;
90
0
    for (i = 0; i < header_table->num_entries; i++) {
91
0
        h2o_hpack_header_table_entry_t *entry = h2o_hpack_header_table_get(header_table, i);
92
0
        append_chunk(pool, chunks,
93
0
                     "\n"
94
0
                     "      [ \"%.*s\", \"%.*s\" ],",
95
0
                     (int)entry->name->len, entry->name->base, (int)entry->value->len, entry->value->base);
96
0
    }
97
98
0
    if (i > 0) {
99
        // remove the last commna
100
0
        --chunks->entries[chunks->size - 1].len;
101
0
    }
102
0
}
103
104
h2o_http2_debug_state_t *h2o_http2_get_debug_state(h2o_req_t *req, int hpack_enabled)
105
0
{
106
0
    h2o_http2_conn_t *conn = (h2o_http2_conn_t *)req->conn;
107
0
    h2o_http2_debug_state_t *state = h2o_mem_alloc_pool(&req->pool, *state, 1);
108
0
    *state = (h2o_http2_debug_state_t){{NULL}};
109
110
0
    state->conn_flow_in = h2o_http2_window_get_avail(&conn->_input_window);
111
0
    state->conn_flow_out = h2o_http2_window_get_avail(&conn->_write.window);
112
113
0
    append_chunk(&req->pool, &state->json,
114
0
                 "{\n"
115
0
                 "  \"version\": \"draft-01\",\n"
116
0
                 "  \"settings\": {\n"
117
0
                 "    \"SETTINGS_HEADER_TABLE_SIZE\": %" PRIu32 ",\n"
118
0
                 "    \"SETTINGS_ENABLE_PUSH\": %" PRIu32 ",\n"
119
0
                 "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %" PRIu32 ",\n"
120
0
                 "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %" PRIu32 ",\n"
121
0
                 "    \"SETTINGS_MAX_FRAME_SIZE\": %" PRIu32 "\n"
122
0
                 "  },\n"
123
0
                 "  \"peerSettings\": {\n"
124
0
                 "    \"SETTINGS_HEADER_TABLE_SIZE\": %" PRIu32 ",\n"
125
0
                 "    \"SETTINGS_ENABLE_PUSH\": %" PRIu32 ",\n"
126
0
                 "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %" PRIu32 ",\n"
127
0
                 "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %" PRIu32 ",\n"
128
0
                 "    \"SETTINGS_MAX_FRAME_SIZE\": %" PRIu32 "\n"
129
0
                 "  },\n"
130
0
                 "  \"connFlowIn\": %zd,\n"
131
0
                 "  \"connFlowOut\": %zd,\n"
132
0
                 "  \"streams\": {",
133
0
                 H2O_HTTP2_SETTINGS_HOST_HEADER_TABLE_SIZE, H2O_HTTP2_SETTINGS_HOST_ENABLE_PUSH,
134
0
                 conn->super.ctx->globalconf->http2.max_streams, H2O_HTTP2_SETTINGS_HOST_STREAM_INITIAL_WINDOW_SIZE,
135
0
                 H2O_HTTP2_SETTINGS_HOST_MAX_FRAME_SIZE, conn->peer_settings.header_table_size, conn->peer_settings.enable_push,
136
0
                 conn->peer_settings.max_concurrent_streams, conn->peer_settings.initial_window_size,
137
0
                 conn->peer_settings.max_frame_size, h2o_http2_window_get_avail(&conn->_input_window),
138
0
                 h2o_http2_window_get_avail(&conn->_write.window));
139
140
    /* encode streams */
141
0
    {
142
0
        h2o_http2_stream_t *stream;
143
0
        kh_foreach_value(conn->streams, stream, {
144
0
            const char *state_string = get_debug_state_string(stream);
145
0
            if (state_string == NULL)
146
0
                continue;
147
148
0
            append_chunk(&req->pool, &state->json,
149
0
                         "\n"
150
0
                         "    \"%" PRIu32 "\": {\n"
151
0
                         "      \"state\": \"%s\",\n"
152
0
                         "      \"flowIn\": %zd,\n"
153
0
                         "      \"flowOut\": %zd,\n"
154
0
                         "      \"dataIn\": %zu,\n"
155
0
                         "      \"dataOut\": %" PRIu64 ",\n"
156
0
                         "      \"created\": %" PRIu64 "\n"
157
0
                         "    },",
158
0
                         stream->stream_id, state_string, h2o_http2_window_get_avail(&stream->input_window.window),
159
0
                         h2o_http2_window_get_avail(&stream->output_window), stream->req.req_body_bytes_received,
160
0
                         stream->req.bytes_sent, (uint64_t)stream->req.timestamps.request_begin_at.tv_sec);
161
0
        });
162
163
0
        if (conn->streams->size > 0) {
164
            // remove the last commna
165
0
            --state->json.entries[state->json.size - 1].len;
166
0
        }
167
0
    }
168
169
0
    append_chunk(&req->pool, &state->json,
170
0
                 "\n"
171
0
                 "  }");
172
173
0
    if (hpack_enabled) {
174
        /* encode inbound header table */
175
0
        append_chunk(&req->pool, &state->json,
176
0
                     ",\n"
177
0
                     "  \"hpack\": {\n"
178
0
                     "    \"inboundTableSize\": %zd,\n"
179
0
                     "    \"inboundDynamicHeaderTable\": [",
180
0
                     conn->_input_header_table.num_entries);
181
0
        append_header_table_chunks(&req->pool, &state->json, &conn->_input_header_table);
182
183
        /* encode outbound header table */
184
0
        append_chunk(&req->pool, &state->json,
185
0
                     "\n"
186
0
                     "    ],\n"
187
0
                     "    \"outboundTableSize\": %zd,\n"
188
0
                     "    \"outboundDynamicHeaderTable\": [",
189
0
                     conn->_output_header_table.num_entries);
190
0
        append_header_table_chunks(&req->pool, &state->json, &conn->_output_header_table);
191
192
0
        append_chunk(&req->pool, &state->json,
193
0
                     "\n"
194
0
                     "    ]\n"
195
0
                     "  }");
196
0
    }
197
198
0
    append_chunk(&req->pool, &state->json,
199
0
                 "\n"
200
0
                 "}\n");
201
202
0
    return state;
203
0
}