├── .ci └── setup.sh ├── .gitattributes ├── .gitignore ├── .luacheckrc ├── .travis.yml ├── LICENSE ├── Makefile ├── README.markdown ├── lib └── resty │ ├── http2.lua │ └── http2 │ ├── error.lua │ ├── frame.lua │ ├── hpack.lua │ ├── huff_decode.lua │ ├── huff_encode.lua │ ├── protocol.lua │ ├── stream.lua │ └── util.lua ├── rockspec └── lua-resty-http2-1.0-0.rockspec ├── t ├── 00-sanity.t ├── 01-window-update.t ├── 02-exceptions.t ├── 03-frame.t └── unit │ ├── test_hpack.lua │ └── test_huffman.lua └── util ├── example.lua ├── lua-nginx-module-0.10.13-ssl-alpn.patch ├── lua-releng ├── mkhuffdectbl └── protocol.markdown /.ci/setup.sh: -------------------------------------------------------------------------------- 1 | OPENRESTY_DOWNLOAD_DIR=$DOWNLOAD_CACHE/openresty-$V_OPENRESTY 2 | LUAROCKS_DOWNLOAD_DIR=$DOWNLOAD_CACHE/luarocks-$V_LUAROCKS 3 | 4 | mkdir -p $OPENRESTY_DOWNLOAD_DIR $LUAROCKS_DOWNLOAD_DIR 5 | 6 | if [ ! $(ls -A $OPENRESTY_DOWNLOAD_DIR) ]; then 7 | pushd $DOWNLOAD_CACHE 8 | wget https://openresty.org/download/openresty-$V_OPENRESTY.tar.gz 9 | tar xzf openresty-$V_OPENRESTY.tar.gz 10 | popd 11 | fi 12 | 13 | if [ ! $(ls -A $LUAROCKS_DOWNLOAD_DIR) ]; then 14 | pushd $DOWNLOAD_CACHE 15 | wget http://luarocks.github.io/luarocks/releases/luarocks-$V_LUAROCKS.tar.gz 16 | tar xzf luarocks-$V_LUAROCKS.tar.gz 17 | popd 18 | fi 19 | 20 | OPENRESTY_INSTALL_DIR=$INSTALL_CACHE/openresty-$V_OPENRESTY 21 | LUAROCKS_INSTALL_DIR=$INSTALL_CACHE/luarocks-$V_LUAROCKS 22 | 23 | mkdir -p $OPENRESTY_INSTALL_DIR $LUAROCKS_INSTALL_DIR 24 | 25 | if [ ! "$(ls -A $OPENRESTY_INSTALL_DIR)" ]; then 26 | pushd $OPENRESTY_DOWNLOAD_DIR 27 | ./configure \ 28 | --prefix=$OPENRESTY_INSTALL_DIR \ 29 | --with-http_v2_module \ 30 | &> build.log || (cat build.log && exit 1) 31 | 32 | make &> build.log || (cat build.log && exit 1) 33 | make install &> build.log || (cat build.log && exit 1) 34 | popd 35 | fi 36 | 37 | if [ ! "$(ls -A $LUAROCKS_INSTALL_DIR)" ]; then 38 | pushd $LUAROCKS_DOWNLOAD_DIR 39 | ./configure \ 40 | --prefix=$LUAROCKS_INSTALL_DIR \ 41 | --lua-suffix=jit \ 42 | --with-lua=$OPENRESTY_INSTALL_DIR/luajit \ 43 | --with-lua-include=$OPENRESTY_INSTALL_DIR/luajit/include/luajit-2.1 \ 44 | &> build.log || (cat build.log && exit 1) 45 | 46 | make build &> build.log || (cat build.log && exit 1) 47 | make install &> build.log || (cat build.log && exit 1) 48 | popd 49 | fi 50 | 51 | export PATH=$PATH:$OPENRESTY_INSTALL_DIR/nginx/sbin:$OPENRESTY_INSTALL_DIR/bin:$LUAROCKS_INSTALL_DIR/bin 52 | 53 | eval `luarocks path` 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | t/servroot/* 43 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "ngx_lua" 2 | unused_args = true 3 | redefined = true 4 | max_line_length = 80 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 3 | - 5.10 4 | cache: 5 | - apt: true 6 | - ccache: true 7 | - 8 | services: 9 | - redis-server 10 | env: 11 | global: 12 | - V_OPENRESTY=1.13.6.1 13 | - V_LUAROCKS=3.0.1 14 | - DOWNLOAD_CACHE=$HOME/download_cache 15 | - INSTALL_CACHE=$HOME/install_cache 16 | install: 17 | - cpanm -v --notest --sudo Test::Nginx 18 | before_script: 19 | - sudo apt-get update -q 20 | - sudo apt-get install libreadline-dev libncurses5-dev libpcre3-dev libssl-dev -y 21 | - sudo apt-get install make build-essential lua5.1 -y 22 | - source .ci/setup.sh 23 | - luarocks install luacheck 24 | script: 25 | - OPENRESTY_INSTALL_DIR=$INSTALL_CACHE/openresty-$V_OPENRESTY make test 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Alex Zhang 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_INSTALL_DIR ?= /usr/local/openresty 2 | 3 | .PHONY: all luacheck test install 4 | 5 | all: ; 6 | 7 | luacheck: 8 | luacheck --std ngx_lua lib/resty/http2.lua lib/resty/http2/*.lua 9 | @echo "" 10 | 11 | luareleng: 12 | util/lua-releng 13 | @echo "" 14 | 15 | test: luareleng luacheck 16 | @echo -n "resty t/unit/test_hpack.lua ...... " 17 | @resty t/unit/test_hpack.lua 18 | @echo "ok" 19 | @resty t/unit/test_huffman.lua 20 | @echo -n "resty t/unit/test_hufman.lua ...... " 21 | @resty t/unit/test_hpack.lua 22 | @echo -e "ok\n" 23 | 24 | sudo cp lib/resty/*.lua $(OPENRESTY_INSTALL_DIR)/lualib/resty 25 | sudo cp -r lib/resty/http2/ $(OPENRESTY_INSTALL_DIR)/lualib/resty/ 26 | prove -I../test-nginx/lib -r -s t/ 27 | -------------------------------------------------------------------------------- /lib/resty/http2.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local h2_protocol = require "resty.http2.protocol" 4 | local h2_frame = require "resty.http2.frame" 5 | local h2_error = require "resty.http2.error" 6 | local h2_stream = require "resty.http2.stream" 7 | local util = require "resty.http2.util" 8 | 9 | local is_func = util.is_func 10 | local debug_log = util.debug_log 11 | local new_tab = util.new_tab 12 | local new_buffer = util.new_buffer 13 | local sub = string.sub 14 | local min = math.min 15 | local pairs = pairs 16 | local setmetatable = setmetatable 17 | 18 | local _M = { _VERSION = "0.1" } 19 | local mt = { __index = _M } 20 | local session_pool = new_tab(0, 4) 21 | 22 | 23 | local function get_data_wrapper(data) 24 | local pos = 1 25 | 26 | return function(max_frame_size) 27 | if is_func(data) then 28 | local part = data(max_frame_size) 29 | return part, part == "" 30 | end 31 | 32 | local data_size = #data 33 | if data_size - pos + 1 <= max_frame_size then 34 | if pos == 1 then 35 | return data, true 36 | end 37 | 38 | return sub(data, pos), true 39 | end 40 | 41 | local part = sub(data, pos, pos + max_frame_size - 1) 42 | pos = pos + max_frame_size 43 | 44 | return part, false 45 | end 46 | end 47 | 48 | 49 | local function send_data(stream, session, part, last) 50 | local ok 51 | local err 52 | local flush_err 53 | 54 | ok, err = stream:submit_data(part, nil, last) 55 | if not ok then 56 | session:close(h2_error.INTERNAL_ERROR) 57 | ok, flush_err = session:flush_queue() 58 | if not ok then 59 | return nil, flush_err 60 | end 61 | 62 | return nil, err 63 | end 64 | 65 | -- we always flush the frame queue, 66 | -- since a single DATA frame maybe large enough 67 | 68 | ok, flush_err = session:flush_queue() 69 | if not ok then 70 | return nil, flush_err 71 | end 72 | 73 | return true 74 | end 75 | 76 | 77 | local function handle_frame(self, session, stream) 78 | local frame, err = session:recv_frame() 79 | if not frame then 80 | debug_log("failed to receive a frame: ", err) 81 | return nil, err 82 | end 83 | 84 | local typ = frame.header.type 85 | if typ == h2_frame.RST_STREAM_FRAME or typ == h2_frame.GOAWAY_FRAME then 86 | session:close() 87 | 88 | local ok, flush_err = session:flush_queue() 89 | if not ok then 90 | return nil, flush_err 91 | end 92 | 93 | if typ == h2_frame.RST_STREAM_FRAME then 94 | return nil, "stream reset" 95 | else 96 | if frame.error_code == h2_error.NO_ERROR then 97 | return true 98 | end 99 | 100 | return nil, "connection went away" 101 | end 102 | end 103 | 104 | if typ == h2_frame.SETTINGS_FRAME and not frame.header.flag_ack then 105 | -- response to the server's SETTINGS frame 106 | local settings_frame = h2_frame.settings.new(h2_frame.FLAG_ACK, nil) 107 | session.ack_peer_settings = true 108 | session:frame_queue(settings_frame) 109 | return session:flush_queue() 110 | end 111 | 112 | local end_stream = frame.header.flag_end_stream 113 | if end_stream and frame.header.id == stream.sid then 114 | stream.done = true 115 | end 116 | 117 | local headers = typ == h2_frame.HEADERS_FRAME or h2_frame.CONTINUATION_FRAME 118 | if headers and frame.header.flag_end_headers then 119 | if self.headers_read then 120 | self.cached_trailers = frame.block_frags 121 | else 122 | self.headers_read = true 123 | self.cached_headers = frame.block_frags 124 | end 125 | 126 | return true 127 | end 128 | 129 | if typ == h2_frame.DATA_FRAME then 130 | local buf = new_buffer(frame.payload) 131 | if not self.cached_body then 132 | self.last_body = buf 133 | self.cached_body = self.last_body 134 | else 135 | self.last_body.next = buf 136 | self.last_body = buf 137 | end 138 | 139 | if session.output_queue_size > 0 then 140 | -- flush the WINDOW_UPDATE frame as soon as possible 141 | local ok, flush_err = session:flush_queue() 142 | if not ok then 143 | debug_log("failed to flush frames: ", flush_err) 144 | return nil, err 145 | end 146 | end 147 | end 148 | 149 | return true 150 | end 151 | 152 | 153 | function _M.new(opts) 154 | local recv = opts.recv 155 | local send = opts.send 156 | local ctx = opts.ctx 157 | local preread_size = opts.preread_size 158 | local max_concurrent_stream = opts.max_concurrent_stream 159 | local max_frame_size = opts.max_frame_size 160 | local key = opts.key 161 | 162 | if max_frame_size and 163 | (max_frame_size > h2_frame.MAX_FRAME_SIZE or 164 | max_frame_size < h2_frame.DEFAULT_FRAME_SIZE) 165 | then 166 | return nil, "incorrect max_frame_size value" 167 | end 168 | 169 | local session 170 | local err 171 | local ok 172 | 173 | if key and session_pool[key] then 174 | session = session_pool[key] 175 | session_pool[key] = nil 176 | ok, err = session:attach(recv, send, ctx) 177 | if not ok then 178 | return nil, err 179 | end 180 | 181 | else 182 | session, err = h2_protocol.session(recv, send, ctx, preread_size, 183 | max_concurrent_stream, 184 | max_frame_size) 185 | if not session then 186 | return nil, err 187 | end 188 | end 189 | 190 | local client = { 191 | session = session, 192 | headers_read = false, 193 | cached_headers = nil, 194 | cached_trailers = nil, 195 | cached_body = nil, 196 | last_body = nil, 197 | } 198 | 199 | return setmetatable(client, mt) 200 | end 201 | 202 | 203 | function _M:keepalive(key) 204 | local session = self.session 205 | 206 | if session.goaway_sent or session.goaway_received or session.fatal then 207 | return 208 | end 209 | 210 | session:detach() 211 | session_pool[key] = session 212 | end 213 | 214 | 215 | function _M:close(code) 216 | local session = self.session 217 | session:close(code) 218 | return session:flush_queue() 219 | end 220 | 221 | 222 | function _M:acknowledge_settings() 223 | local session = self.session 224 | while not session.ack_peer_settings do 225 | local ok, err = handle_frame(self, session) 226 | if not ok then 227 | return nil, err 228 | end 229 | end 230 | 231 | return true 232 | end 233 | 234 | 235 | function _M:send_request(headers, body) 236 | local session = self.session 237 | local stream, err = session:submit_request(headers, body == nil, nil, nil) 238 | if not stream then 239 | debug_log("failed to submit_request: ", err) 240 | return nil, err 241 | end 242 | 243 | local ok, flush_err = session:flush_queue() 244 | if not ok then 245 | debug_log("failed to flush frames: ", err) 246 | return nil, flush_err 247 | end 248 | 249 | if not body then 250 | return stream 251 | end 252 | 253 | local get_data = get_data_wrapper(body) 254 | 255 | while true do 256 | local size = min(session.send_window, stream.send_window) 257 | if size > session.max_frame_size then 258 | size = session.max_frame_size 259 | end 260 | 261 | if size > 0 then 262 | local part, last 263 | part, last, err = get_data(size) 264 | if not part then 265 | debug_log("connection will be closed since ", 266 | "DATA frame cannot be generated correctly") 267 | 268 | session:close(h2_error.INTERNAL_ERROR) 269 | ok, flush_err = session:flush_queue() 270 | if not ok then 271 | return nil, flush_err 272 | end 273 | 274 | return nil, err 275 | end 276 | 277 | ok, err = send_data(stream, session, part, last) 278 | if not ok then 279 | return nil, err 280 | end 281 | 282 | if last then 283 | break 284 | end 285 | else 286 | -- cannot continue sending body, waits the WINDOW_UPDATE firstly 287 | ok, err = handle_frame(self, session, stream) 288 | if not ok then 289 | return nil, err 290 | end 291 | end 292 | end 293 | 294 | return stream 295 | end 296 | 297 | 298 | function _M:read_headers(stream) 299 | local session = stream.session 300 | 301 | while not self.cached_headers do 302 | local ok, err = handle_frame(self, session, stream) 303 | if not ok then 304 | return nil, err 305 | end 306 | end 307 | 308 | local headers = self.cached_headers 309 | self.cached_headers = nil 310 | 311 | for name in pairs(h2_stream.IS_CONNECTION_SPEC_HEADERS) do 312 | headers[name] = nil 313 | end 314 | 315 | return headers 316 | end 317 | 318 | 319 | function _M:read_body(stream) 320 | if stream.done then 321 | return "" 322 | end 323 | 324 | local session = stream.session 325 | 326 | while not self.cached_body do 327 | local ok, err = handle_frame(self, session, stream) 328 | if not ok then 329 | return nil, err 330 | end 331 | 332 | if self.cached_trailers then 333 | -- we have read a header frame for trailer headers, now it's time 334 | -- to return. 335 | return 336 | end 337 | end 338 | 339 | local body = self.cached_body.data 340 | self.cached_body = self.cached_body.next 341 | 342 | return body 343 | end 344 | 345 | 346 | function _M:request(headers, body, on_headers_reach, on_data_reach, 347 | on_trailers_reach) 348 | local ack, err = self:acknowledge_settings() 349 | if not ack then 350 | return nil, err 351 | end 352 | 353 | if not headers then 354 | return nil, "empty headers" 355 | end 356 | 357 | if not is_func(on_headers_reach) then 358 | return nil, "invalid on_headers_reach callback" 359 | end 360 | 361 | if not is_func(on_data_reach) then 362 | return nil, "invalid on_data_reach callback" 363 | end 364 | 365 | local stream 366 | local resp_headers 367 | local data 368 | local session = self.session 369 | 370 | stream, err = self:send_request(headers, body) 371 | if not stream then 372 | return nil, err 373 | end 374 | 375 | resp_headers, err = self:read_headers(stream) 376 | if not resp_headers then 377 | return nil, err 378 | end 379 | 380 | local ctx = session.ctx 381 | 382 | if on_headers_reach(ctx, resp_headers) then 383 | -- abort the session 384 | return self:close(h2_error.INTERNAL_ERROR) 385 | end 386 | 387 | if stream.done then 388 | return true 389 | end 390 | 391 | while true do 392 | data, err = self:read_body(stream) 393 | if not data then 394 | if self.cached_trailers then 395 | -- we have met trailer headers 396 | break 397 | end 398 | 399 | return nil, err 400 | end 401 | 402 | if on_data_reach(ctx, data) then 403 | -- abort 404 | return self:close(h2_error.INTERNAL_ERROR) 405 | end 406 | 407 | if stream.done then 408 | break 409 | end 410 | end 411 | 412 | if self.cached_trailers then 413 | local trailers = self.cached_trailers 414 | self.cached_trailers = nil 415 | if on_data_reach and on_trailers_reach(ctx, trailers) then 416 | return self:close(h2_error.INTERNAL_ERROR) 417 | end 418 | 419 | if not session.done then 420 | -- unexpected extra data after trailer headers 421 | return self:close(h2_stream.PROTOCOL_ERROR) 422 | end 423 | end 424 | 425 | return true 426 | end 427 | 428 | 429 | return _M 430 | -------------------------------------------------------------------------------- /lib/resty/http2/error.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local _M = { _VERSION = "0.1" } 4 | 5 | local NO_ERROR = 0x0 6 | local PROTOCOL_ERROR = 0x1 7 | local INTERNAL_ERROR = 0x2 8 | local FLOW_CONTROL_ERROR = 0x3 9 | local SETTINGS_TIMEOUT = 0x4 10 | local STREAM_CLOSED = 0x5 11 | local FRAME_SIZE_ERROR = 0x6 12 | local REFUSED_STREAM = 0x7 13 | local CANCEL = 0x8 14 | local COMPRESSION_ERROR = 0x9 15 | local CONNECT_ERROR = 0xa 16 | local ENHANCE_YOUR_CALM = 0xb 17 | local INADEQUATE_SECURITY = 0xc 18 | local HTTP_1_1_REQUIRED = 0xd 19 | 20 | -- we use negative codes to represent the some stream-level errors 21 | local STREAM_PROTOCOL_ERROR = -PROTOCOL_ERROR 22 | local STREAM_FLOW_CONTROL_ERROR = -FLOW_CONTROL_ERROR 23 | local STREAM_FRAME_SIZE_ERROR = -FRAME_SIZE_ERROR 24 | 25 | local error_map = { 26 | [NO_ERROR] = "no error", 27 | [PROTOCOL_ERROR] = "protocol error", 28 | [INTERNAL_ERROR] = "internal error", 29 | [FLOW_CONTROL_ERROR] = "flow control error", 30 | [SETTINGS_TIMEOUT] = "settings timeout", 31 | [STREAM_CLOSED] = "stream closed", 32 | [FRAME_SIZE_ERROR] = "frame size error", 33 | [REFUSED_STREAM] = "refused stream", 34 | [CANCEL] = "cancel", 35 | [COMPRESSION_ERROR] = "compression error", 36 | [CONNECT_ERROR] = "connect error", 37 | [ENHANCE_YOUR_CALM] = "enhanced your calm", 38 | [INADEQUATE_SECURITY] = "inadequate security", 39 | [HTTP_1_1_REQUIRED] = "http/1.1 required", 40 | 41 | [STREAM_FRAME_SIZE_ERROR] = "frame size error (stream level)", 42 | [STREAM_PROTOCOL_ERROR] = "protocol error (stream level)", 43 | [STREAM_FLOW_CONTROL_ERROR] = "flow control error (stream level)", 44 | } 45 | 46 | 47 | function _M.strerror(code) 48 | return error_map[code] or "unknown error" 49 | end 50 | 51 | 52 | function _M.is_stream_error(code) 53 | return code < 0 or code == REFUSED_STREAM or code == STREAM_CLOSED 54 | end 55 | 56 | 57 | _M.NO_ERROR = NO_ERROR 58 | _M.PROTOCOL_ERROR = PROTOCOL_ERROR 59 | _M.INTERNAL_ERROR = INTERNAL_ERROR 60 | _M.FLOW_CONTROL_ERROR = FLOW_CONTROL_ERROR 61 | _M.SETTINGS_TIMEOUT = SETTINGS_TIMEOUT 62 | _M.STREAM_CLOSED = STREAM_CLOSED 63 | _M.FRAME_SIZE_ERROR = FRAME_SIZE_ERROR 64 | _M.REFUSED_STREAM = REFUSED_STREAM 65 | _M.CANCEL = CANCEL 66 | _M.COMPRESSION_ERROR = COMPRESSION_ERROR 67 | _M.CONNECT_ERROR = CONNECT_ERROR 68 | _M.ENHANCE_YOUR_CALM = ENHANCE_YOUR_CALM 69 | _M.INADEQUATE_SECURITY = INADEQUATE_SECURITY 70 | _M.HTTP_1_1_REQUIRED = HTTP_1_1_REQUIRED 71 | 72 | _M.STREAM_PROTOCOL = STREAM_PROTOCOL_ERROR 73 | _M.STREAM_FLOW_CONTROL_ERROR = STREAM_FLOW_CONTROL_ERROR 74 | 75 | 76 | return _M 77 | -------------------------------------------------------------------------------- /lib/resty/http2/frame.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local bit = require "bit" 4 | local util = require "resty.http2.util" 5 | local h2_stream = require "resty.http2.stream" 6 | local h2_error = require "resty.http2.error" 7 | 8 | local bor = bit.bor 9 | local band = bit.band 10 | local blshift = bit.lshift 11 | local brshift = bit.rshift 12 | local char = string.char 13 | local byte = string.byte 14 | local sub = string.sub 15 | local new_tab = util.new_tab 16 | local pack_u16 = util.pack_u16 17 | local unpack_u16 = util.unpack_u16 18 | local pack_u32 = util.pack_u32 19 | local unpack_u32 = util.unpack_u32 20 | local new_buffer = util.new_buffer 21 | local debug_log = util.debug_log 22 | 23 | local MAX_WINDOW = h2_stream.MAX_WINDOW 24 | local HEADER_SIZE = 9 25 | local DEFAULT_FRAME_SIZE = 16384 26 | local MAX_FRAME_SIZE = 16777215 27 | 28 | local FLAG_NONE = 0x0 29 | local FLAG_ACK = 0x1 30 | local FLAG_END_STREAM = 0x1 31 | local FLAG_END_HEADERS = 0x4 32 | local FLAG_PADDED = 0x8 33 | local FLAG_PRIORITY = 0x20 34 | 35 | local DATA_FRAME = 0x0 36 | local HEADERS_FRAME = 0x1 37 | local PRIORITY_FRAME = 0x2 38 | local RST_STREAM_FRAME = 0x3 39 | local SETTINGS_FRAME = 0x4 40 | local PUSH_PROMISE_FRAME = 0x5 41 | local PING_FRAME = 0x6 42 | local GOAWAY_FRAME = 0x7 43 | local WINDOW_UPDATE_FRAME = 0x8 44 | local CONTINUATION_FRAME = 0x9 45 | 46 | local SETTINGS_ENABLE_PUSH = 0x2 47 | local SETTINGS_MAX_CONCURRENT_STREAMS = 0x3 48 | local SETTINGS_INITIAL_WINDOW_SIZE = 0x4 49 | local SETTINGS_MAX_FRAME_SIZE = 0x5 50 | 51 | 52 | local _M = { 53 | _VERSION = "0.1", 54 | } 55 | 56 | local header = {} -- frame header 57 | local priority = {} -- priority frame 58 | local settings = {} -- settings frame 59 | local ping = {} -- ping frame 60 | local goaway = {} -- goaway frame 61 | local window_update = {} -- window_update frame 62 | local headers = {} -- headers frame 63 | local continuation = {} -- continuation frame 64 | local rst_stream = {} -- rst_stream frame 65 | local data = {} -- data frame 66 | local push_promise = {} -- push_promise frame 67 | 68 | 69 | function header.new(length, typ, flags, id) 70 | local flag_ack = band(flags, FLAG_ACK) ~= 0 71 | local flag_end_stream = band(flags, FLAG_END_STREAM) ~= 0 72 | local flag_end_headers = band(flags, FLAG_END_HEADERS) ~= 0 73 | local flag_padded = band(flags, FLAG_PADDED) ~= 0 74 | local flag_priority = band(flags, FLAG_PRIORITY) ~= 0 75 | 76 | return { 77 | length = length, 78 | type = typ, 79 | flags = flags, 80 | id = id, 81 | flag_ack = flag_ack, 82 | flag_end_stream = flag_end_stream, 83 | flag_end_headers = flag_end_headers, 84 | flag_padded = flag_padded, 85 | flag_priority = flag_priority, 86 | } 87 | end 88 | 89 | 90 | function header.pack(hd, dst) 91 | -- length (24) 92 | local length = hd.length 93 | for i = 16, 0, -8 do 94 | dst[#dst + 1] = char(band(brshift(length, i), 0xff)) 95 | end 96 | 97 | dst[#dst + 1] = char(band(hd.type, 0xff)) 98 | dst[#dst + 1] = char(band(hd.flags, 0xff)) 99 | 100 | pack_u32(hd.id, dst) 101 | end 102 | 103 | 104 | function header.unpack(src) 105 | local b1, b2, b3 = byte(src, 1, 3) 106 | local length = bor(bor(blshift(b1, 16), blshift(b2, 8)), b3) 107 | 108 | local typ = byte(src, 4) 109 | local flags = byte(src, 5) 110 | 111 | local id = unpack_u32(byte(src, 6, 9)) 112 | 113 | return header.new(length, typ, flags, id) 114 | end 115 | 116 | 117 | function priority.pack(pf, dst) 118 | header.pack(pf.header, dst) 119 | 120 | local depend = pf.depend 121 | local excl = pf.excl 122 | local weight = pf.weight 123 | 124 | if excl then 125 | depend = bor(depend, blshift(1, 31)) 126 | end 127 | 128 | pack_u32(depend, dst) 129 | 130 | dst[#dst + 1] = char(weight) 131 | end 132 | 133 | 134 | function priority.unpack(pf, src, stream) 135 | local sid = stream.sid 136 | if sid == 0x0 then 137 | debug_log("server sent PRIORITY frame with incorrect ", 138 | "stream idenitifier: 0x0") 139 | return nil, h2_error.PROTOCOL_ERROR 140 | end 141 | 142 | local payload_length = pf.header.length 143 | if payload_length ~= 5 then 144 | debug_log("server sent PRIORITY frame with incorrect payload length: ", 145 | payload_length) 146 | return nil, h2_error.STREAM_FRAME_SIZE_ERROR 147 | end 148 | 149 | local b1, b2, b3, b4, b5 = byte(src, 1, 5) 150 | 151 | if b1 > 127 then 152 | b1 = b1 - 127 153 | pf.excl = 1 154 | 155 | else 156 | pf.excl = 0 157 | end 158 | 159 | local depend = unpack_u32(b1, b2, b3, b4) 160 | local weight = b5 161 | 162 | if depend == sid then 163 | debug_log("server sent PRIORITY frame with incorrect ", 164 | "dependent stream: ", depend) 165 | return nil, h2_error.PROTOCOL_ERROR 166 | end 167 | 168 | local session = stream.session 169 | 170 | local depend_stream = session.stream_map[depend] 171 | if not depend_stream then -- not in the dependency tree 172 | depend_stream = h2_stream.new(sid, h2_stream.DEFAULT_WEIGHT, session) 173 | end 174 | 175 | pf.weight = weight 176 | pf.depend = depend 177 | 178 | stream:set_dependency(depend_stream, pf.excl) 179 | 180 | return true 181 | end 182 | 183 | 184 | function rst_stream.pack(rf, dst) 185 | header.pack(rf.header, dst) 186 | pack_u32(rf.error_code, dst) 187 | end 188 | 189 | 190 | function rst_stream.unpack(rf, src, stream) 191 | local sid = rf.header.id 192 | if sid == 0x0 then 193 | debug_log("server sent RST_STREAM frame with ", 194 | "incorrect stream identifier: 0x0") 195 | return nil, h2_error.PROTOCOL_ERROR 196 | end 197 | 198 | local peer_state = stream.peer_state 199 | if peer_state == h2_stream.STATE_IDLE then 200 | debug_log("server sent RST_STREAM frame for stream: ", sid, 201 | " with invalid state") 202 | return nil, h2_error.PROTOCOL_ERROR 203 | end 204 | 205 | local length = rf.header.length 206 | if length ~= 4 then 207 | debug_log("server sent RST_STREAM frame with incorrect payload length ", 208 | length) 209 | return nil, h2_error.PROTOCOL_ERROR 210 | end 211 | 212 | rf.error_code = unpack_u32(byte(src, 1, 4)) 213 | 214 | stream.state = h2_stream.STATE_CLOSED 215 | stream.peer_state = h2_stream.STATE_CLOSED 216 | 217 | local session = stream.session 218 | session.closed_streams = session.closed_streams + 1 219 | 220 | return true 221 | end 222 | 223 | 224 | function rst_stream.new(error_code, sid) 225 | local hd = header.new(4, RST_STREAM_FRAME, FLAG_NONE, sid) 226 | 227 | return { 228 | header = hd, 229 | error_code = error_code, 230 | next = nil, 231 | } 232 | end 233 | 234 | 235 | function settings.pack(sf, dst) 236 | header.pack(sf.header, dst) 237 | 238 | local item = sf.item 239 | if item then 240 | for i = 1, #item do 241 | pack_u16(item[i].id, dst) 242 | pack_u32(item[i].value, dst) 243 | end 244 | end 245 | end 246 | 247 | 248 | function settings.unpack(sf, src, stream) 249 | local ack = sf.header.flag_ack 250 | local payload_length = sf.header.length 251 | 252 | if ack and payload_length > 0 then 253 | debug_log("server sent SETTINGS frame with ACK flag ", 254 | "and non-empty payloads") 255 | return nil, h2_error.FRAME_SIZE_ERROR 256 | end 257 | 258 | local sid = sf.header.id 259 | if sid ~= 0x0 then 260 | debug_log("server sent SETTINGS frame with incorrect ", 261 | "stream identifier: ", sid) 262 | return nil, h2_error.PROTOCOL_ERROR 263 | end 264 | 265 | if payload_length % 6 ~= 0 then 266 | debug_log("server sent SETTINGS frame with incorrect payload length: ", 267 | payload_length) 268 | return nil, h2_error.FRAME_SIZE_ERROR 269 | end 270 | 271 | local session = stream.session 272 | local count = payload_length / 6 273 | sf.item = new_tab(count, 0) 274 | local offset = 0 275 | 276 | for i = 1, count do 277 | local id = unpack_u16(byte(src, offset + 1, offset + 2)) 278 | local value = unpack_u32(byte(src, offset + 3, offset + 6)) 279 | 280 | sf.item[i] = { id = id, value = value } 281 | 282 | offset = offset + 6 283 | 284 | -- TODO handle SETTINGS_HEADER_TABLE_SIZE 285 | -- and SETTINGS_MAX_HEADER_LIST_SIZE 286 | 287 | if id == SETTINGS_INITIAL_WINDOW_SIZE then 288 | if value > MAX_WINDOW then 289 | debug_log("server sent SETTINGS frame with improper ", 290 | "SETTINGS_INITIAL_WINDOW_SIZE value: ", value) 291 | return nil, h2_error.FRAME_SIZE_ERROR 292 | end 293 | 294 | local init_window = session.init_window 295 | if init_window ~= value then 296 | session.init_window = value 297 | if not session:adjust_window(value - init_window) then 298 | return nil, h2_error.INTERNAL_ERROR 299 | end 300 | end 301 | 302 | elseif id == SETTINGS_MAX_CONCURRENT_STREAMS then 303 | session.max_stream = value 304 | 305 | elseif id == SETTINGS_ENABLE_PUSH then 306 | -- this setting makes no sense for client side, 307 | -- we just check the value 308 | if value > 1 then 309 | debug_log("server sent SETTINGS frame with improper ", 310 | "SETTINGS_MAX_CONCURRENT_STREAMS value: ", value) 311 | return nil, h2_error.PROTOCOL_ERROR 312 | end 313 | 314 | elseif id == SETTINGS_MAX_FRAME_SIZE then 315 | if value > MAX_FRAME_SIZE or value < DEFAULT_FRAME_SIZE then 316 | debug_log("server sent SETTINGS frame with improper ", 317 | "SETTINGS_MAX_FRAME_SIZE value: ", value) 318 | return nil, h2_error.PROTOCOL_ERROR 319 | end 320 | 321 | session.max_frame_size = value 322 | end 323 | end 324 | 325 | return true 326 | end 327 | 328 | 329 | function settings.new(flags, payload) 330 | local length = 0 331 | if payload then 332 | length = 6 * #payload 333 | end 334 | 335 | local hd = header.new(length, SETTINGS_FRAME, flags, 0) 336 | 337 | return { 338 | header = hd, 339 | item = payload, 340 | next = nil, 341 | } 342 | end 343 | 344 | 345 | function ping.pack(pf, dst) 346 | header.pack(pf.header, dst) 347 | pack_u32(pf.opaque_data_hi, dst) 348 | pack_u32(pf.opaque_data_lo, dst) 349 | end 350 | 351 | 352 | function ping.unpack(pf, src, stream) 353 | local sid = stream.sid 354 | if sid ~= 0x0 then 355 | debug_log("server sent PING frame with incorrect stream identifier: ", 356 | sid) 357 | return nil, h2_error.PROTOCOL_ERROR 358 | end 359 | 360 | local payload_length = pf.header.length 361 | if payload_length ~= 8 then 362 | debug_log("server sent PING frame with incorrect payload length: ", 363 | payload_length) 364 | return nil ,h2_error.FRAME_SIZE_ERROR 365 | end 366 | 367 | pf.opaque_data_hi = unpack_u32(byte(src, 1, 4)) 368 | pf.opaque_data_lo = unpack_u32(byte(src, 5, 8)) 369 | 370 | return true 371 | end 372 | 373 | 374 | function goaway.pack(gf, dst) 375 | header.pack(gf.header, dst) 376 | pack_u32(gf.last_stream_id, dst) 377 | pack_u32(gf.error_code, dst) 378 | 379 | if gf.debug_data then 380 | dst[#dst + 1] = gf.debug_data 381 | end 382 | end 383 | 384 | 385 | function goaway.unpack(gf, src, stream) 386 | local payload_length = gf.header.length 387 | if payload_length < 4 then 388 | debug_log("server sent GOAWAY frame with incorrect payload length: ", 389 | payload_length) 390 | return nil, h2_error.FRAME_SIZE_ERROR 391 | end 392 | 393 | local sid = stream.sid 394 | if sid ~= 0x0 then 395 | debug_log("server sent GOAWAY frame with incorrect stream identifier: ", 396 | sid) 397 | return nil, h2_error.PROTOCOL_ERROR 398 | end 399 | 400 | local session = stream.session 401 | local last_stream_id = unpack_u32(byte(src, 1, 4)) 402 | local error_code = unpack_u32(byte(src, 5, 8)) 403 | 404 | session.goaway_received = true 405 | session.last_stream_id = last_stream_id 406 | gf.last_stream_id = last_stream_id 407 | gf.error_code = error_code 408 | 409 | debug_log("server sent GOAWAY frame with last stream id: ", last_stream_id, 410 | ", error_code: ", error_code) 411 | 412 | if payload_length > 8 then 413 | gf.debug_data = sub(src, 9, payload_length - 8) 414 | end 415 | 416 | return true 417 | end 418 | 419 | 420 | function goaway.new(last_sid, error_code, debug_data) 421 | local debug_data_len = debug_data and #debug_data or 0 422 | local hd = header.new(8 + debug_data_len, GOAWAY_FRAME, FLAG_NONE, 0) 423 | 424 | return { 425 | header = hd, 426 | last_stream_id = last_sid, 427 | error_code = error_code, 428 | debug_data = debug_data, 429 | next = nil, 430 | } 431 | end 432 | 433 | 434 | function window_update.pack(wf, dst) 435 | header.pack(wf.header, dst) 436 | pack_u32(wf.window_size_increment, dst) 437 | end 438 | 439 | 440 | function window_update.unpack(wf, src, stream) 441 | local payload_length = wf.header.length 442 | if payload_length ~= 4 then 443 | debug_log("server sent WINDOW_UPDATE frame with ", 444 | "incorrect payload length: ", payload_length) 445 | return nil, h2_error.FRAME_SIZE_ERROR 446 | end 447 | 448 | local sid = stream.sid 449 | local incr = band(unpack_u32(byte(src, 1, 4)), 0x7fffffff) 450 | 451 | if incr == 0 then 452 | if sid == 0x0 then 453 | debug_log("server sent WINDOW_UPDATE frame for the ", 454 | "whole connection with invalid window increment: 0") 455 | return nil, h2_error.PROTOCOL_ERROR 456 | end 457 | 458 | debug_log("server sent WINDOW_UPDATE frame for stream ", sid, 459 | " with invalid window increment: 0") 460 | return nil, h2_error.STREAM_PROTOCOL_ERROR 461 | end 462 | 463 | wf.window_size_increment = incr 464 | 465 | local send_window 466 | if sid == 0x0 then 467 | send_window = stream.session.send_window 468 | else 469 | send_window = stream.send_window 470 | end 471 | 472 | if stream.state == h2_stream.STATE_IDLE then 473 | return true 474 | end 475 | 476 | if incr > MAX_WINDOW - send_window then 477 | if sid == 0 then 478 | debug_log("server volatiled connection flow control: ", 479 | "received WINDOW_UPDATE frame with window increment: ", 480 | incr, ", which is not allowed for window ", send_window) 481 | return nil, h2_error.FLOW_CONTROL_ERROR 482 | end 483 | 484 | debug_log("server volatiled flow control for stream ", sid, 485 | " received WINDOW_UPDATE frame with window increment: ", incr, 486 | " which is not allowed for window ", send_window) 487 | return nil, h2_error.STREAM_FLOW_CONTROL_ERROR 488 | end 489 | 490 | if sid ~= 0x0 then 491 | stream.send_window = send_window + incr 492 | if stream.send_window > 0 and stream.exhausted then 493 | stream.exhausted = false 494 | end 495 | else 496 | stream.session.send_window = send_window + incr 497 | end 498 | 499 | return true 500 | end 501 | 502 | 503 | function window_update.new(sid, window) 504 | local hd = header.new(4, WINDOW_UPDATE_FRAME, FLAG_NONE, sid) 505 | return { 506 | header = hd, 507 | window_size_increment = window, 508 | next = nil, 509 | } 510 | end 511 | 512 | 513 | function headers.pack(hf, dst) 514 | header.pack(hf.header, dst) 515 | 516 | local flag_padded = hf.header.flag_padded 517 | local flag_priority = hf.header.flag_priority 518 | 519 | if flag_padded then 520 | local pad_length = #hf.pad 521 | dst[#dst + 1] = char(pad_length) 522 | end 523 | 524 | if flag_priority then 525 | local depend = hf.depend 526 | local excl = hf.excl 527 | 528 | if excl then 529 | depend = bor(depend, 0x7fffffff) 530 | end 531 | 532 | pack_u32(depend, dst) 533 | dst[#dst + 1] = char(hf.weight) 534 | end 535 | 536 | dst[#dst + 1] = hf.block_frags 537 | 538 | if flag_padded then 539 | dst[#dst + 1] = hf.pad 540 | end 541 | end 542 | 543 | 544 | function headers.unpack(hf, src, stream) 545 | local hd = hf.header 546 | 547 | local flag_padded = hd.FLAG_PADDED 548 | local flag_priority = hd.FLAG_PRIORITY 549 | 550 | local length = hd.length 551 | local offset = 1 552 | local size = 0 553 | local depend 554 | local weight 555 | local excl = false 556 | 557 | local sid = stream.sid 558 | local session = stream.session 559 | local next_stream_id = session.next_stream_id 560 | 561 | if sid % 2 == 0 or sid >= next_stream_id then 562 | debug_log("server sent HEADERS frame with incorrect identifier", sid) 563 | return nil, h2_error.PROTOCOL_ERROR 564 | end 565 | 566 | local state = stream.state 567 | local peer_state = stream.peer_state 568 | 569 | if peer_state ~= h2_stream.STATE_OPEN and 570 | peer_state ~= h2_stream.STATE_IDLE and 571 | peer_state ~= h2_stream.STATE_REVERSED_LOCAL and 572 | peer_state ~= h2_stream.STATE_HALF_CLOSED_REMOTE 573 | then 574 | debug_log("server sent HEADERS frame for stream ", sid, 575 | " with invalid state") 576 | return nil, h2_error.PROTOCOL_ERROR 577 | end 578 | 579 | if flag_padded then 580 | size = 1 581 | end 582 | 583 | if flag_priority then 584 | -- stream dependency (32) + weight (8) 585 | size = size + 5 586 | end 587 | 588 | if size >= length then 589 | debug_log("server sent HEADERS frame with incorrect length ", length) 590 | return nil, h2_error.FRAME_SIZE_ERROR 591 | end 592 | 593 | length = length - size 594 | 595 | if flag_padded then 596 | local pad_length = band(byte(src, 1, 1), 0xff) 597 | if length < pad_length then 598 | debug_log("server sent padded HEADERS frame with ", 599 | "incorrect length: ", length, ", padding: ", pad_length) 600 | return nil, h2_error.FRAME_SIZE_ERROR 601 | end 602 | 603 | offset = 2 604 | length = length - pad_length 605 | end 606 | 607 | if flag_priority then 608 | depend = unpack_u32(byte(src, offset + 1, offset + 4)) 609 | if band(brshift(depend, 31), 1) then 610 | excl = true 611 | end 612 | 613 | depend = band(depend, 0x7fffffff) 614 | 615 | weight = band(byte(src, offset + 5, offset + 5), 0xff) 616 | offset = offset + 5 617 | 618 | debug_log("HEADERS frame sid: ", sid, " depends on ", depend, 619 | " excl: ", excl, "weight: ", weight) 620 | 621 | if depend == sid then 622 | debug_log("server sent HEADERS frame for stream ", sid, 623 | " with incorrect dependency") 624 | return nil, h2_error.STREAM_PROTOCOL_ERROR 625 | end 626 | 627 | stream.weight = weight 628 | 629 | local depend_stream = session.stream_map[depend] 630 | if not depend_stream then 631 | -- not in the dependency tree 632 | depend_stream = h2_stream.new(depend, h2_stream.DEFAULT_WEIGHT, 633 | session) 634 | end 635 | 636 | stream:set_dependency(depend_stream, excl) 637 | end 638 | 639 | if hd.FLAG_END_STREAM then 640 | if state == h2_stream.STATE_OPEN then 641 | stream.state = h2_stream.STATE_HALF_CLOSED_REMOTE 642 | 643 | elseif state == h2_stream.STATE_IDLE then 644 | stream.state = h2_stream.STATE_OPEN 645 | session.idle_streams = session.idle_streams - 1 646 | 647 | else 648 | session.closed_streams = session.closed_streams + 1 649 | stream.state = h2_stream.STATE_CLOSED 650 | end 651 | 652 | if peer_state == h2_stream.STATE_OPEN then 653 | stream.peer_state = h2_stream.HALF_CLOSED_LOCAL 654 | 655 | elseif state == h2_stream.STATE_IDLE then 656 | stream.peer_state = h2_stream.STATE_OPEN 657 | 658 | else 659 | stream.peer_state = h2_stream.STATE_CLOSED 660 | end 661 | end 662 | 663 | if length > 0 then 664 | local buffer = new_buffer(src, offset, offset + length) 665 | local cached = session.hpack.cached 666 | if not cached then 667 | session.hpack.cached = buffer 668 | session.hpack.last_cache = buffer 669 | else 670 | session.hpack.last_cache.next = buffer 671 | session.hpack.last_cache = buffer 672 | end 673 | end 674 | 675 | -- just skip the incompleting decode operation 676 | -- if we don't receive the whole headers (it's rare), 677 | -- that makes the hpack codes simple. :) 678 | if hd.flag_end_headers then 679 | -- XXX don't have a good way to estimate a proper size 680 | hf.block_frags = new_tab(0, 8) 681 | return session.hpack:decode(hf.block_frags) 682 | end 683 | 684 | debug_log("server sent large headers which cannot be ", 685 | "fitted in a single HEADERS frame") 686 | 687 | session.incomplete_headers = true 688 | session.current_sid = sid 689 | return true 690 | end 691 | 692 | 693 | function headers.new(frags, pri, pad, end_stream, end_headers, sid) 694 | local payload_length = #frags 695 | local flags = FLAG_NONE 696 | 697 | if end_stream then 698 | flags = bor(flags, FLAG_END_STREAM) 699 | end 700 | 701 | if pri then 702 | flags = bor(flags, FLAG_PRIORITY) 703 | end 704 | 705 | if end_headers then 706 | flags = bor(flags, FLAG_END_HEADERS) 707 | end 708 | 709 | -- basically we don't use this but still we should respect it 710 | if pad then 711 | flags = bor(flags, FLAG_PADDED) 712 | payload_length = payload_length + #pad 713 | end 714 | 715 | local hd = header.new(payload_length, HEADERS_FRAME, flags, sid) 716 | 717 | return { 718 | header = hd, 719 | depend = pri and pri.sid, 720 | weight = pri and pri.weight, 721 | excl = pri and pri.excl, 722 | block_frags = frags, 723 | pad = pad, 724 | next = nil, 725 | } 726 | end 727 | 728 | 729 | function continuation.pack(cf, dst) 730 | header.pack(cf.header, dst) 731 | dst[#dst + 1] = cf.block_frags 732 | end 733 | 734 | 735 | function continuation.unpack(cf, src, stream) 736 | local session = stream.session 737 | 738 | if not session.incomplete_headers then 739 | debug_log("server sent unexpected CONTINUATION frame") 740 | return nil, h2_error.PROTOCOL_ERROR 741 | end 742 | 743 | if #src > 0 then 744 | local buffer = new_buffer(src, 1, #src + 1) 745 | session.hpack.last_cache.next = buffer 746 | session.hpack.last_cache = buffer 747 | end 748 | 749 | if cf.header.flag_end_headers then 750 | session.incomplete_headers = false 751 | session.current_sid = -1 752 | 753 | -- XXX don't have a good way to estimate a proper size 754 | cf.block_frags = new_tab(0, 4) 755 | return session.hpack:decode(cf.block_frags) 756 | end 757 | 758 | return true 759 | end 760 | 761 | 762 | function continuation.new(frags, end_headers, sid) 763 | local payload_length = #frags 764 | local flags = FLAG_NONE 765 | 766 | if end_headers then 767 | flags = bor(flags, FLAG_END_HEADERS) 768 | end 769 | 770 | local hd = header.new(payload_length, CONTINUATION_FRAME, flags, sid) 771 | 772 | return { 773 | header = hd, 774 | block_frags = frags, 775 | next = nil, 776 | } 777 | end 778 | 779 | 780 | function data.pack(df, dst) 781 | header.pack(df.header, dst) 782 | 783 | local flag_padded = df.header.FLAG_PADDED 784 | if flag_padded then 785 | local length = #df.pad 786 | dst[#dst + 1] = char(length) 787 | end 788 | 789 | dst[#dst + 1] = df.payload 790 | 791 | if flag_padded then 792 | dst[#dst + 1] = df.pad 793 | end 794 | end 795 | 796 | 797 | function data.unpack(df, src, stream) 798 | local sid = stream.sid 799 | 800 | if sid == 0x0 then 801 | debug_log("server sent DATA frame with incorrect identifier ", sid) 802 | return nil, h2_error.PROTOCOL_ERROR 803 | end 804 | 805 | local state = stream.state 806 | local peer_state = stream.peer_state 807 | 808 | if peer_state ~= h2_stream.STATE_OPEN and 809 | peer_state ~= h2_stream.STATE_HALF_CLOSED_REMOTE 810 | then 811 | debug_log("server sent DATA frame for stream ", sid, 812 | " with invalid state") 813 | return nil, h2_error.STREAM_CLOSED 814 | end 815 | 816 | local hd = df.header 817 | local flag_padded = hd.FLAG_PADDED 818 | local flag_end_stream = hd.FLAG_END_STREAM 819 | local length = hd.length 820 | local session = stream.session 821 | 822 | if flag_padded then 823 | if length == 0 then 824 | debug_log("server sent padded DATA frame with incorrect length: 0") 825 | return nil, h2_error.FRAME_SIZE_ERROR 826 | end 827 | 828 | local pad_length = band(byte(src, 1, 1), 0xff) 829 | 830 | if pad_length >= length then 831 | debug_log("server sent padded DATA frame with incorrect length: ", 832 | length, ", padding: ", pad_length) 833 | return nil, h2_error.PROTOCOL_ERROR 834 | end 835 | 836 | df.payload = sub(src, 2, 1 + length - pad_length) 837 | else 838 | df.payload = src 839 | end 840 | 841 | if flag_end_stream then 842 | if state == h2_stream.STATE_OPEN then 843 | stream.state = h2_stream.STATE_HALF_CLOSED_REMOTE 844 | else 845 | session.closed_streams = session.closed_streams + 1 846 | stream.state = h2_stream.STATE_CLOSED 847 | end 848 | 849 | if peer_state == h2_stream.STATE_OPEN then 850 | stream.peer_state = h2_stream.STATE_HALF_CLOSED_LOCAL 851 | else 852 | stream.peer_state = h2_stream.STATE_CLOSED 853 | end 854 | end 855 | 856 | local recv_window = session.recv_window 857 | if length > recv_window then 858 | return nil, h2_error.FLOW_CONTROL_ERROR 859 | end 860 | 861 | recv_window = recv_window - length 862 | if recv_window * 4 < MAX_WINDOW then 863 | if not session:submit_window_update(MAX_WINDOW - recv_window) then 864 | return nil, h2_error.INTERNAL_ERROR 865 | end 866 | 867 | recv_window = MAX_WINDOW 868 | end 869 | 870 | session.recv_window = recv_window 871 | 872 | local init_window = stream.init_window 873 | recv_window = stream.recv_window 874 | if length > recv_window then 875 | return nil, h2_error.STREAM_FLOW_CONTROL_ERROR 876 | end 877 | 878 | recv_window = recv_window - length 879 | if recv_window * 4 < init_window then 880 | if not stream:submit_window_update(init_window - recv_window) then 881 | return nil, h2_error.INTERNAL_ERROR 882 | end 883 | 884 | recv_window = init_window 885 | end 886 | 887 | stream.recv_window = recv_window 888 | 889 | return true 890 | end 891 | 892 | 893 | function data.new(payload, pad, last, sid) 894 | local flags = FLAG_NONE 895 | if last then 896 | flags = bor(flags, FLAG_END_STREAM) 897 | end 898 | 899 | if pad then 900 | flags = bor(flags, FLAG_PADDED) 901 | end 902 | 903 | local pad_length = pad and #pad or 0 904 | 905 | local hd = header.new(#payload + pad_length, DATA_FRAME, flags, sid) 906 | 907 | return { 908 | header = hd, 909 | pad = pad, 910 | payload = payload, 911 | next = nil, 912 | } 913 | end 914 | 915 | 916 | -- XXX just prohibits the PUSH_PROMISE frame, 917 | -- maybe it will be supported in the future 918 | function push_promise.unpack() 919 | debug_log("server sent PUSH_PROMISE frame with ", 920 | "ignoring SETTINGS_ENABLE_PUSH setting") 921 | return nil, h2_error.PROTOCOL_ERROR 922 | end 923 | 924 | 925 | _M.header = header 926 | _M.priority = priority 927 | _M.settings = settings 928 | _M.ping = ping 929 | _M.goaway = goaway 930 | _M.window_update = window_update 931 | _M.headers = headers 932 | _M.continuation = continuation 933 | _M.rst_stream = rst_stream 934 | _M.data = data 935 | _M.push_promise = push_promise 936 | 937 | _M.FLAG_NONE = FLAG_NONE 938 | _M.FLAG_ACK = FLAG_ACK 939 | _M.FLAG_END_STREAM = FLAG_END_STREAM 940 | _M.FLAG_END_HEADERS = FLAG_END_HEADERS 941 | _M.FLAG_PADDED = FLAG_PADDED 942 | _M.FLAG_PRIORITY = FLAG_PRIORITY 943 | 944 | _M.HEADER_SIZE = HEADER_SIZE 945 | 946 | _M.DATA_FRAME = DATA_FRAME 947 | _M.HEADERS_FRAME = HEADERS_FRAME 948 | _M.PRIORITY_FRAME = PRIORITY_FRAME 949 | _M.RST_STREAM_FRAME = RST_STREAM_FRAME 950 | _M.SETTINGS_FRAME = SETTINGS_FRAME 951 | _M.PUSH_PROMISE_FRAME = PUSH_PROMISE_FRAME 952 | _M.PING_FRAME = PING_FRAME 953 | _M.GOAWAY_FRAME = GOAWAY_FRAME 954 | _M.WINDOW_UPDATE_FRAME = WINDOW_UPDATE_FRAME 955 | _M.CONTINUATION_FRAME = CONTINUATION_FRAME 956 | 957 | _M.MAX_FRAME_SIZE = MAX_FRAME_SIZE 958 | _M.DEFAULT_FRAME_SIZE = DEFAULT_FRAME_SIZE 959 | _M.MAX_FRAME_ID = 0x9 960 | 961 | _M.SETTINGS_ENABLE_PUSH = SETTINGS_ENABLE_PUSH 962 | _M.SETTINGS_MAX_CONCURRENT_STREAMS = SETTINGS_MAX_CONCURRENT_STREAMS 963 | _M.SETTINGS_INITIAL_WINDOW_SIZE = SETTINGS_INITIAL_WINDOW_SIZE 964 | _M.SETTINGS_MAX_FRAME_SIZE = SETTINGS_MAX_FRAME_SIZE 965 | 966 | _M.pack = { 967 | [DATA_FRAME] = data.pack, 968 | [HEADERS_FRAME] = headers.pack, 969 | [PRIORITY_FRAME] = priority.pack, 970 | [RST_STREAM_FRAME] = rst_stream.pack, 971 | [SETTINGS_FRAME] = settings.pack, 972 | [PING_FRAME] = ping.pack, 973 | [GOAWAY_FRAME] = goaway.pack, 974 | [WINDOW_UPDATE_FRAME] = window_update.pack, 975 | [CONTINUATION_FRAME] = continuation.pack, 976 | } 977 | 978 | _M.unpack = { 979 | [DATA_FRAME] = data.unpack, 980 | [HEADERS_FRAME] = headers.unpack, 981 | [PRIORITY_FRAME] = priority.unpack, 982 | [RST_STREAM_FRAME] = rst_stream.unpack, 983 | [SETTINGS_FRAME] = settings.unpack, 984 | [PING_FRAME] = ping.unpack, 985 | [GOAWAY_FRAME] = goaway.unpack, 986 | [WINDOW_UPDATE_FRAME] = window_update.unpack, 987 | [CONTINUATION_FRAME] = continuation.unpack, 988 | [PUSH_PROMISE_FRAME] = push_promise.unpack, 989 | } 990 | 991 | _M.sizeof = { 992 | [DATA_FRAME] = 4, 993 | [HEADERS_FRAME] = 7, 994 | [PRIORITY_FRAME] = 5, 995 | [RST_STREAM_FRAME] = 3, 996 | [SETTINGS_FRAME] = 4, 997 | [PING_FRAME] = 4, 998 | [GOAWAY_FRAME] = 5, 999 | [WINDOW_UPDATE_FRAME] = 3, 1000 | [CONTINUATION_FRAME] = 3, 1001 | [PUSH_PROMISE_FRAME] = 1, 1002 | } 1003 | 1004 | 1005 | return _M 1006 | -------------------------------------------------------------------------------- /lib/resty/http2/hpack.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local bit = require "bit" 4 | local util = require "resty.http2.util" 5 | local huffenc = require "resty.http2.huff_encode" 6 | local huffdec = require "resty.http2.huff_decode" 7 | local h2_error = require "resty.http2.error" 8 | 9 | local bor = bit.bor 10 | local brshift = bit.rshift 11 | local blshift = bit.lshift 12 | local band = bit.band 13 | local char = string.char 14 | local sub = string.sub 15 | local byte = string.byte 16 | local concat = table.concat 17 | local setmetatable = setmetatable 18 | local new_tab = util.new_tab 19 | local clear_tab = util.clear_tab 20 | local debug_log = util.debug_log 21 | local is_str = util.is_str 22 | 23 | local _M = { _VERSION = "0.1" } 24 | local mt = { __index = _M } 25 | 26 | local MAX_TABLE_SIZE = 4096 27 | local ENTRY_SLOTS = 64 28 | local ENCODE_HUFF = 0x80 29 | local ENCODE_RAW = 0x0 30 | 31 | local HPACK_INDEXED = 0 32 | local HPACK_INCR_INDEXING = 1 33 | local HPACK_WITHOUT_INDEXING = 2 34 | local HPACK_NEVER_INDEXED = 3 35 | 36 | local huff_data 37 | local huff_data_len = 0 38 | 39 | local hpack_static_table = { 40 | { name = ":authority", value = "" }, 41 | { name = ":method", value = "GET" }, 42 | { name = ":method", value = "POST" }, 43 | { name = ":path", value = "/" }, 44 | { name = ":path", value = "/index.html" }, 45 | { name = ":scheme", value = "http" }, 46 | { name = ":scheme", value = "https" }, 47 | { name = ":status", value = "200" }, 48 | { name = ":status", value = "204" }, 49 | { name = ":status", value = "206" }, 50 | { name = ":status", value = "304" }, 51 | { name = ":status", value = "400" }, 52 | { name = ":status", value = "404" }, 53 | { name = ":status", value = "500" }, 54 | { name = "accept-charset", value = "" }, 55 | { name = "accept-encoding", value = "gzip, deflate" }, 56 | { name = "accept-language", value = "" }, 57 | { name = "accept-ranges", value = "" }, 58 | { name = "accept", value = "" }, 59 | { name = "access-control-allow-origin", value = "" }, 60 | { name = "age", value = "" }, 61 | { name = "allow", value = "" }, 62 | { name = "authorization", value = "" }, 63 | { name = "cache-control", value = "" }, 64 | { name = "content-disposition", value = "" }, 65 | { name = "content-encoding", value = "" }, 66 | { name = "content-language", value = "" }, 67 | { name = "content-length", value = "" }, 68 | { name = "content-location", value = "" }, 69 | { name = "content-range", value = "" }, 70 | { name = "content-type", value = "" }, 71 | { name = "cookie", value = "" }, 72 | { name = "date", value = "" }, 73 | { name = "etag", value = "" }, 74 | { name = "expect", value = "" }, 75 | { name = "expires", value = "" }, 76 | { name = "from", value = "" }, 77 | { name = "host", value = "" }, 78 | { name = "if-match", value = "" }, 79 | { name = "if-modified-since", value = "" }, 80 | { name = "if-none-match", value = "" }, 81 | { name = "if-range", value = "" }, 82 | { name = "if-unmodified-since", value = "" }, 83 | { name = "last-modified", value = "" }, 84 | { name = "link", value = "" }, 85 | { name = "location", value = "" }, 86 | { name = "max-forwards", value = "" }, 87 | { name = "proxy-authenticate", value = "" }, 88 | { name = "proxy-authorization", value = "" }, 89 | { name = "range", value = "" }, 90 | { name = "referer", value = "" }, 91 | { name = "refresh", value = "" }, 92 | { name = "retry-after", value = "" }, 93 | { name = "server", value = "" }, 94 | { name = "set-cookie", value = "" }, 95 | { name = "strict-transport-security", value = "" }, 96 | { name = "transfer-encoding", value = "" }, 97 | { name = "user-agent", value = "" }, 98 | { name = "vary", value = "" }, 99 | { name = "via", value = "" }, 100 | { name = "www-authenticate", value = "" }, 101 | } 102 | 103 | -- use two pointers mimic the dynamic table's borders, 104 | -- back is the insert point and front is always the drop point. 105 | -- 106 | -- case 1 (back >= front) : 107 | -- 108 | -- ....^--------------------^..... 109 | -- front back 110 | -- 111 | -- number of entries = back - front + 1 112 | -- 113 | -- ith entry = entries[back - i + 1] 114 | -- 115 | -- case 2 (back < front) : 116 | -- 117 | -- ----^....................^----- 118 | -- back front 119 | -- 120 | -- number of entries = slots - (front - back - 1) 121 | -- 122 | -- ith entry = 123 | -- * entries[back - i + 1] (when i <= back) 124 | -- * entries[slots - (i - back) + 1] (when i > back) 125 | -- 126 | -- when all the slots are occupied, a larger slots table will be 127 | -- allocated, all the current data will be moved to there. 128 | 129 | 130 | local function table_account(hpack, size) 131 | size = size + 32 132 | local dynamic = hpack.dynamic 133 | local free = dynamic.free 134 | 135 | if free >= size then 136 | dynamic.free = free - size 137 | return true 138 | end 139 | 140 | if size > dynamic.size then 141 | dynamic.front = 1 142 | dynamic.back = 0 143 | dynamic.free = dynamic.size 144 | return false 145 | end 146 | 147 | local front = dynamic.front 148 | local slots = dynamic.slots 149 | 150 | while size > free do 151 | -- evict this entry 152 | local entry = dynamic.entries[front] 153 | 154 | front = front + 1 155 | if front > slots then 156 | front = front - slots 157 | end 158 | 159 | free = free + 32 + entry.len 160 | end 161 | 162 | dynamic.free = free - size 163 | 164 | -- all entries are evicted 165 | dynamic.front = front 166 | 167 | return true 168 | end 169 | 170 | 171 | local function parse_int(self, current, prefix) 172 | local value = band(current, prefix) 173 | if value ~= prefix then 174 | return value 175 | end 176 | 177 | local count = 0 178 | local shift = 0 179 | 180 | while true do 181 | if self.buffer.pos == self.buffer.last then 182 | self.buffer = self.buffer.next 183 | if not self.buffer then 184 | debug_log("server sent header block with incorrect length") 185 | return 186 | end 187 | end 188 | 189 | local pos = self.buffer.pos 190 | local b = band(byte(self.buffer.data, pos, pos), 0xff) 191 | self.buffer.pos = pos + 1 192 | 193 | value = blshift(band(b, 0x7f), shift) + value 194 | 195 | if b < 128 then 196 | return value 197 | end 198 | 199 | count = count + 1 200 | shift = shift + 7 201 | 202 | -- length is too large 203 | if count > 4 then 204 | debug_log("server sent header block with too long length") 205 | return 206 | end 207 | end 208 | end 209 | 210 | 211 | local function parse_raw(self, length) 212 | local data = new_tab(2, 0) 213 | while length > 0 do 214 | if not self.buffer then 215 | break 216 | end 217 | 218 | local pos = self.buffer.pos 219 | local last = self.buffer.last 220 | local size = last - pos 221 | if size >= length then 222 | data[#data + 1] = sub(self.buffer.data, pos, pos + length - 1) 223 | self.buffer.pos = pos + length 224 | length = 0 225 | break 226 | end 227 | 228 | -- size < length 229 | if pos == 1 then 230 | data[#data + 1] = self.buffer.data 231 | else 232 | data[#data + 1] = sub(self.buffer.data, pos, last - 1) 233 | end 234 | 235 | length = length - size 236 | self.buffer = self.buffer.next 237 | end 238 | 239 | if length > 0 then 240 | debug_log("server sent incomplet header block") 241 | return 242 | end 243 | 244 | return concat(data) 245 | end 246 | 247 | 248 | local function parse_huff(hpack, length) 249 | if huff_data_len < length then 250 | huff_data = new_tab(length, 0) 251 | huff_data_len = length 252 | else 253 | clear_tab(huff_data) 254 | end 255 | 256 | while true do 257 | if not hpack.buffer then 258 | break 259 | end 260 | 261 | local pos = hpack.buffer.pos 262 | local last = hpack.buffer.last 263 | local size = last - pos 264 | if size >= length then 265 | local src = sub(hpack.buffer.data, pos, pos + length - 1) 266 | hpack.buffer.pos = pos + length 267 | 268 | local ok, err = hpack.decode_state:decode(src, huff_data, true) 269 | if not ok then 270 | debug_log("hpack huffman decoding error: ", err) 271 | return 272 | end 273 | 274 | hpack.decode_state:reset() 275 | 276 | return concat(huff_data) 277 | end 278 | 279 | local src 280 | if pos == 1 then 281 | src = hpack.buffer.data 282 | else 283 | src = sub(hpack.buffer.data, pos, pos + size - 1) 284 | end 285 | 286 | hpack.buffer = hpack.buffer.next 287 | length = length - size 288 | 289 | local ok, err = hpack.decode_state:decode(src, huff_data, false) 290 | if not ok then 291 | debug_log("hpack huffman decoding error: ", err) 292 | return 293 | end 294 | end 295 | 296 | if length > 0 then 297 | debug_log("server sent incomplete header block") 298 | return 299 | end 300 | 301 | return concat(huff_data) 302 | end 303 | 304 | 305 | local function parse_value(hpack) 306 | if hpack.buffer.pos == hpack.buffer.last then 307 | hpack.buffer = hpack.buffer.next 308 | if not hpack.buffer then 309 | debug_log("server sent incomplete header block") 310 | return 311 | end 312 | end 313 | 314 | local pos = hpack.buffer.pos 315 | local ch = band(byte(hpack.buffer.data, pos, pos), 0xff) 316 | hpack.buffer.pos = pos + 1 317 | 318 | local huff = ch >= 128 319 | local value = parse_int(hpack, ch, 127) 320 | if not value then 321 | return 322 | end 323 | 324 | debug_log("string length: ", value, " huff: ", huff) 325 | 326 | if not huff then 327 | return parse_raw(hpack, value) 328 | end 329 | 330 | return parse_huff(hpack, value) 331 | end 332 | 333 | 334 | local function write_length(preface, prefix, value, dst) 335 | if value < prefix then 336 | dst[#dst + 1] = char(bor(preface, value)) 337 | return 338 | end 339 | 340 | dst[#dst + 1] = char(bor(preface, prefix)) 341 | value = value - prefix 342 | 343 | while value >= 128 do 344 | dst[#dst + 1] = char(band(value, 0x7f) + 128) 345 | value = brshift(value, 7) 346 | end 347 | 348 | dst[#dst + 1] = char(value) 349 | end 350 | 351 | 352 | local function append_header(dst, name, value) 353 | local old = dst[name] 354 | if not old then 355 | dst[name] = value 356 | return 357 | end 358 | 359 | if is_str(old) then 360 | dst[name] = { old, value } 361 | return 362 | end 363 | 364 | old[#old + 1] = value 365 | end 366 | 367 | 368 | function _M.new(size) 369 | size = size or MAX_TABLE_SIZE 370 | 371 | -- linked list cannot be indexed fastly. 372 | -- We use two pointers to index a solidify table. 373 | local dynamic = { 374 | free = size, 375 | size = size, 376 | entries = new_tab(ENTRY_SLOTS, 0), 377 | slots = ENTRY_SLOTS, 378 | front = 1, -- pop from the front 379 | back = 0, -- push from the back 380 | } 381 | 382 | return setmetatable({ 383 | static = hpack_static_table, 384 | dynamic = dynamic, 385 | cached = nil, 386 | buffer = nil, 387 | last_cache = nil, 388 | decode_state = huffdec.new_state(), 389 | }, mt) 390 | end 391 | 392 | 393 | function _M:insert_entry(header_name, header_value) 394 | local dynamic = self.dynamic 395 | 396 | local cost = #header_name + #header_value 397 | 398 | if not table_account(self, cost) then 399 | return false 400 | end 401 | 402 | -- TODO reuse these entries to reduce the GC overheads. 403 | local entry = { 404 | name = header_name, 405 | value = header_value, 406 | len = cost, 407 | } 408 | 409 | local back = dynamic.back 410 | local front = dynamic.front 411 | local slots = dynamic.slots 412 | 413 | if back == 0 then 414 | goto insert 415 | end 416 | 417 | if (back == slots and front == 1) or back == front - 1 then 418 | -- enlarge the capacity 419 | -- TODO use luatablepool to recycle this tables. 420 | local entries = dynamic.entries 421 | local new_entries = new_tab(slots + 64, 0) 422 | 423 | -- the first case 424 | if back == slots and front == 1 then 425 | for i = 1, slots do 426 | new_entries[i] = entries[i] 427 | end 428 | 429 | else 430 | for i = front, slots do 431 | new_entries[i - front + 1] = entries[i] 432 | end 433 | 434 | -- back == front - 1 435 | for i = 1, back do 436 | new_entries[slots - front + 1 + i] = entries[i] 437 | end 438 | 439 | dynamic.back = slots 440 | dynamic.front = 1 441 | end 442 | 443 | dynamic.entries = new_entries 444 | dynamic.slots = slots + 64 445 | end 446 | 447 | ::insert:: 448 | 449 | back = back + 1 450 | if back > dynamic.slots then 451 | back = back - dynamic.slots 452 | end 453 | 454 | dynamic.back = back 455 | dynamic.entries[back] = entry 456 | 457 | return true 458 | end 459 | 460 | 461 | function _M:resize(new_size) 462 | if new_size > MAX_TABLE_SIZE then 463 | debug_log("server sent header block with too long size update value") 464 | return 465 | end 466 | 467 | local dynamic = self.dynamic 468 | local front = dynamic.front 469 | local slots = dynamic.slots 470 | local cost = dynamic.size - dynamic.free 471 | 472 | while cost > new_size do 473 | local entry = dynamic.entries[front] 474 | front = front + 1 475 | if front > slots then 476 | front = front - slots 477 | end 478 | 479 | cost = cost - entry.len - 32 480 | end 481 | 482 | dynamic.front = front 483 | dynamic.size = new_size 484 | dynamic.free = new_size - cost 485 | 486 | return true 487 | end 488 | 489 | 490 | function _M:decode(dst) 491 | local buffer = self.cached 492 | if not buffer then 493 | return true 494 | end 495 | 496 | self.cached = nil 497 | self.buffer = buffer 498 | 499 | local index_type 500 | local size_update = false 501 | local prefix 502 | 503 | while true do 504 | local pos = self.buffer.pos 505 | if pos == self.buffer.last then 506 | break 507 | end 508 | 509 | local ch = band(byte(self.buffer.data, pos), 0xff) 510 | self.buffer.pos = pos + 1 511 | 512 | if ch >= 128 then -- indexed header field 513 | prefix = 127 514 | index_type = HPACK_INDEXED 515 | 516 | elseif ch >= 64 then -- literal header field with incremental indexing 517 | prefix = 63 518 | index_type = HPACK_INCR_INDEXING 519 | 520 | elseif ch >= 32 then -- dynamic table size update 521 | prefix = 31 522 | size_update = true 523 | 524 | elseif ch >= 16 then -- literal header field never indexed 525 | prefix = 15 526 | index_type = HPACK_NEVER_INDEXED 527 | 528 | else 529 | prefix = 15 530 | index_type = HPACK_WITHOUT_INDEXING 531 | end 532 | 533 | local value = parse_int(self, ch, prefix) 534 | if not value then 535 | return nil, h2_error.COMPRESSION_ERROR 536 | end 537 | 538 | if index_type == HPACK_INDEXED then 539 | local entry = self:get_indexed_header(value) 540 | if not entry then 541 | return nil, h2_error.COMPRESSION_ERROR 542 | end 543 | 544 | append_header(dst, entry.name, entry.value) 545 | 546 | elseif size_update then 547 | size_update = false 548 | 549 | if not self:resize(value) then 550 | return nil, h2_error.COMPRESSION_ERROR 551 | end 552 | 553 | else 554 | local header_name 555 | local header_value 556 | 557 | if value > 0 then 558 | local entry = self:get_indexed_header(value) 559 | if not entry then 560 | return nil, h2_error.COMPRESSION_ERROR 561 | end 562 | 563 | header_name = entry.name 564 | 565 | else 566 | header_name = parse_value(self) 567 | if not header_name then 568 | return nil, h2_error.COMPRESSION_ERROR 569 | end 570 | end 571 | 572 | header_value = parse_value(self) 573 | if not header_value then 574 | return nil, h2_error.COMPRESSION_ERROR 575 | end 576 | 577 | append_header(dst, header_name, header_value) 578 | 579 | if index_type == HPACK_INCR_INDEXING then 580 | self:insert_entry(header_name, header_value) 581 | end 582 | end 583 | end 584 | 585 | return true 586 | end 587 | 588 | 589 | function _M:get_indexed_header(raw_index) 590 | if raw_index <= 0 then 591 | debug_log("server sent invalid hpack table index ", raw_index) 592 | return 593 | end 594 | 595 | local static = self.static 596 | if raw_index <= #static then 597 | return static[raw_index] 598 | end 599 | 600 | local dynamic = self.dynamic 601 | local front = dynamic.front 602 | local back = dynamic.back 603 | 604 | if back == 0 then 605 | debug_log("server sent invalid hpack table index ", raw_index) 606 | return 607 | end 608 | 609 | local index = raw_index - #self.static 610 | 611 | if back >= front then 612 | if back - front + 1 < index then 613 | debug_log("server sent invalid hpack table index ", raw_index) 614 | return 615 | end 616 | 617 | return dynamic.entries[back - index + 1] 618 | end 619 | 620 | local count = back + dynamic.slots - front + 1 621 | if count < index then 622 | debug_log("server sent invalid hpack table index ", raw_index) 623 | return 624 | end 625 | 626 | if index <= back then 627 | return dynamic.entries[back - index + 1] 628 | end 629 | 630 | local slots = dynamic.slots 631 | return dynamic.entries[slots - (index - back) + 1] 632 | end 633 | 634 | 635 | -- literal header field with incremental indexing 636 | function _M.encode(src, dst, lower) 637 | local tmp = huffenc.encode(src, lower) 638 | 639 | if tmp then -- encode to huffman codes is a better idea 640 | write_length(ENCODE_HUFF, 127, #tmp, dst) 641 | dst[#dst + 1] = concat(tmp) 642 | return 643 | end 644 | 645 | write_length(ENCODE_RAW, 127, #src, dst) 646 | if lower then 647 | dst[#dst + 1] = src:lower() 648 | else 649 | dst[#dst + 1] = src 650 | end 651 | end 652 | 653 | 654 | function _M.indexed(index) 655 | return char(128 + index) 656 | end 657 | 658 | 659 | function _M.incr_indexed(index) 660 | return char(64 + index) 661 | end 662 | 663 | 664 | _M.MAX_TABLE_SIZE = MAX_TABLE_SIZE 665 | 666 | _M.COMMON_REQUEST_HEADERS_INDEX = { 667 | [":authority"] = 1, 668 | ["accept-charset"] = 15, 669 | ["accept-language"] = 17, 670 | ["accept-ranges"] = 18, 671 | ["accept"] = 19, 672 | ["authorization"] = 23, 673 | ["cache-control"] = 24, 674 | ["cookie"] = 32, 675 | ["expect"] = 35, 676 | ["host"] = 38, 677 | ["if-match"] = 39, 678 | ["if-modified-since"] = 40, 679 | ["if-none-match"] = 41, 680 | ["if-range"] = 42, 681 | ["if-unmodified-since"] = 43, 682 | ["range"] = 50, 683 | ["referer"] = 51, 684 | ["user-agent"] = 58, 685 | ["via"] = 60, 686 | 687 | [":method"] = { ["GET"] = 2, ["POST"] = 3 }, 688 | [":path"] = { ["/"] = 4, ["/index.html"] = 5 }, 689 | [":scheme"] = { ["http"] = 6, ["https"] = 7 }, 690 | ["accept-encoding"] = { ["gzip, deflate"] = 16 }, 691 | } 692 | 693 | 694 | return _M 695 | -------------------------------------------------------------------------------- /lib/resty/http2/huff_encode.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local util = require "resty.http2.util" 4 | local bit = require "bit" 5 | 6 | local new_tab = util.new_tab 7 | local clear_tab = util.clear_tab 8 | local bor = bit.bor 9 | local band = bit.band 10 | local blshift = bit.lshift 11 | local brshift = bit.rshift 12 | local str_byte = string.byte 13 | local str_char = string.char 14 | 15 | local BUFBITS = 32 16 | local BUFBYTES = 4 17 | local dst 18 | local dstcap = 0 19 | 20 | local _M = { _VERSION = "0.1" } 21 | 22 | 23 | local huff_encode_table = { 24 | {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, 25 | {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, 26 | {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28}, 27 | {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28}, 28 | {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28}, 29 | {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28}, 30 | {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28}, 31 | {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28}, 32 | {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12}, 33 | {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11}, 34 | {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11}, 35 | {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6}, 36 | {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6}, 37 | {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6}, 38 | {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8}, 39 | {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10}, 40 | {0x00001ffa, 13}, {0x00000021, 6}, {0x0000005d, 7}, {0x0000005e, 7}, 41 | {0x0000005f, 7}, {0x00000060, 7}, {0x00000061, 7}, {0x00000062, 7}, 42 | {0x00000063, 7}, {0x00000064, 7}, {0x00000065, 7}, {0x00000066, 7}, 43 | {0x00000067, 7}, {0x00000068, 7}, {0x00000069, 7}, {0x0000006a, 7}, 44 | {0x0000006b, 7}, {0x0000006c, 7}, {0x0000006d, 7}, {0x0000006e, 7}, 45 | {0x0000006f, 7}, {0x00000070, 7}, {0x00000071, 7}, {0x00000072, 7}, 46 | {0x000000fc, 8}, {0x00000073, 7}, {0x000000fd, 8}, {0x00001ffb, 13}, 47 | {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6}, 48 | {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, 49 | {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, 50 | {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, 51 | {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, 52 | {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, 53 | {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, 54 | {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15}, 55 | {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28}, 56 | {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20}, 57 | {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23}, 58 | {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23}, 59 | {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23}, 60 | {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23}, 61 | {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23}, 62 | {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23}, 63 | {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24}, 64 | {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22}, 65 | {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21}, 66 | {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24}, 67 | {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23}, 68 | {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21}, 69 | {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23}, 70 | {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22}, 71 | {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23}, 72 | {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19}, 73 | {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25}, 74 | {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27}, 75 | {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25}, 76 | {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27}, 77 | {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24}, 78 | {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26}, 79 | {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27}, 80 | {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21}, 81 | {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23}, 82 | {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25}, 83 | {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23}, 84 | {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26}, 85 | {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27}, 86 | {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27}, 87 | {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26}, 88 | } 89 | 90 | 91 | -- same as above, but embeds lowercase transformation 92 | local huff_encode_table_lc = { 93 | {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, 94 | {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, 95 | {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28}, 96 | {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28}, 97 | {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28}, 98 | {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28}, 99 | {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28}, 100 | {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28}, 101 | {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12}, 102 | {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11}, 103 | {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11}, 104 | {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6}, 105 | {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6}, 106 | {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6}, 107 | {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8}, 108 | {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10}, 109 | {0x00001ffa, 13}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, 110 | {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, 111 | {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, 112 | {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, 113 | {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, 114 | {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, 115 | {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00001ffb, 13}, 116 | {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6}, 117 | {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, 118 | {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, 119 | {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, 120 | {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, 121 | {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, 122 | {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, 123 | {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15}, 124 | {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28}, 125 | {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20}, 126 | {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23}, 127 | {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23}, 128 | {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23}, 129 | {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23}, 130 | {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23}, 131 | {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23}, 132 | {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24}, 133 | {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22}, 134 | {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21}, 135 | {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24}, 136 | {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23}, 137 | {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21}, 138 | {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23}, 139 | {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22}, 140 | {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23}, 141 | {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19}, 142 | {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25}, 143 | {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27}, 144 | {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25}, 145 | {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27}, 146 | {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24}, 147 | {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26}, 148 | {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27}, 149 | {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21}, 150 | {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23}, 151 | {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25}, 152 | {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23}, 153 | {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26}, 154 | {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27}, 155 | {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27}, 156 | {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26} 157 | } 158 | 159 | 160 | -- try to encode src with huffman codes, 161 | -- the shared table dst will be returned to callers, 162 | -- callers should save data to their own space. 163 | function _M.encode(src, lower) 164 | local srclen = #src 165 | 166 | if dstcap < srclen then 167 | dst = new_tab(srclen, 0) 168 | dstcap = srclen 169 | else 170 | clear_tab(dst) 171 | end 172 | 173 | local buf = 0 174 | local rest = BUFBITS 175 | local table = lower and huff_encode_table_lc or huff_encode_table 176 | 177 | for i = 1, srclen do 178 | local byte = str_byte(src, i) 179 | local code = table[byte + 1][1] 180 | local len = table[byte + 1][2] 181 | 182 | if rest >= len then 183 | buf = bor(buf, blshift(code, rest - len)) 184 | rest = rest - len 185 | 186 | else 187 | -- do more harm than good, mimic from nginx 188 | if srclen <= #dst + BUFBYTES then 189 | return 190 | end 191 | 192 | buf = bor(buf, brshift(code, len - rest)) 193 | 194 | for n = BUFBITS - 8, 0, -8 do 195 | dst[#dst + 1] = str_char(band(brshift(buf, n), 0xff)) 196 | end 197 | 198 | buf = blshift(code, BUFBITS - len + rest) 199 | rest = BUFBITS - len + rest 200 | end 201 | end 202 | 203 | if rest == BUFBITS then 204 | return dst 205 | end 206 | 207 | if rest == 0 then 208 | if #dst + BUFBYTES >= srclen then 209 | return 210 | end 211 | 212 | for n = BUFBITS - 8, 0, -8 do 213 | dst[#dst + 1] = str_char(band(brshift(buf, n), 0xff)) 214 | end 215 | 216 | return dst 217 | end 218 | 219 | -- padding with 1 220 | buf = bor(buf, brshift(-1, BUFBITS - rest)) 221 | 222 | local bytes = util.align(BUFBITS - rest, 8) / 8 223 | 224 | -- do more harm than good, mimic from nginx 225 | if #dst + bytes >= srclen then 226 | return 227 | end 228 | 229 | for i = 1, bytes do 230 | local offset = BUFBITS - 8 * i 231 | dst[#dst + 1] = str_char(band(brshift(buf, offset), 0xff)) 232 | end 233 | 234 | return dst 235 | end 236 | 237 | 238 | return _M 239 | -------------------------------------------------------------------------------- /lib/resty/http2/protocol.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local util = require "resty.http2.util" 4 | local h2_frame = require "resty.http2.frame" 5 | local h2_stream = require "resty.http2.stream" 6 | local h2_error = require "resty.http2.error" 7 | local h2_hpack = require "resty.http2.hpack" 8 | 9 | local pairs = pairs 10 | local new_tab = util.new_tab 11 | local clear_tab = util.clear_tab 12 | local debug_log = util.debug_log 13 | 14 | local MAX_STREAMS_SETTING = h2_frame.SETTINGS_MAX_CONCURRENT_STREAMS 15 | local INIT_WINDOW_SIZE_SETTING = h2_frame.SETTINGS_INITIAL_WINDOW_SIZE 16 | local MAX_FRAME_SIZE_SETTING = h2_frame.SETTINGS_MAX_FRAME_SIZE 17 | local ENABLE_PUSH_SETTING = h2_frame.SETTINGS_ENABLE_PUSH 18 | local MAX_STREAM_ID = 0x7fffffff 19 | local DEFAULT_WINDOW_SIZE = 65535 20 | local DEFAULT_MAX_STREAMS = 128 21 | local DEFAULT_MAX_FRAME_SIZE = h2_frame.MAX_FRAME_SIZE 22 | local HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" 23 | 24 | local send_buffer 25 | local send_buffer_size = 0 26 | local _M = { _VERSION = "0.1" } 27 | local mt = { __index = _M } 28 | 29 | 30 | local function handle_error(self, stream, error_code) 31 | if h2_error.is_stream_error(error_code) then 32 | stream:rst(error_code) 33 | else 34 | self:close(error_code) 35 | end 36 | 37 | -- we flush the frame queue actively, 38 | -- since callers cannot distinguish protocol error and network error 39 | local ok, err = self:flush_queue() 40 | if not ok then 41 | debug_log("failed to flush frame queue: ", err) 42 | end 43 | 44 | return nil, h2_error.strerror(error_code) 45 | end 46 | 47 | 48 | -- send the default settings and advertise the window size 49 | -- for the whole connection 50 | local function init(self) 51 | local preread_size = self.preread_size 52 | local max_frame_size = self.max_frame_size 53 | local max_streams = self.max_streams 54 | 55 | local payload = { 56 | { id = MAX_STREAMS_SETTING, value = max_streams }, 57 | { id = INIT_WINDOW_SIZE_SETTING, value = preread_size }, 58 | { id = MAX_FRAME_SIZE_SETTING, value = max_frame_size }, 59 | { id = ENABLE_PUSH_SETTING, value = 0 }, 60 | } 61 | 62 | local sf, err = h2_frame.settings.new(h2_frame.FLAG_NONE, payload) 63 | 64 | if not sf then 65 | return nil, err 66 | end 67 | 68 | local incr = h2_stream.MAX_WINDOW - DEFAULT_WINDOW_SIZE 69 | 70 | local wf 71 | wf, err = h2_frame.window_update.new(0x0, incr) 72 | if not wf then 73 | return nil, err 74 | end 75 | 76 | self.recv_window = h2_stream.MAX_WINDOW 77 | 78 | self:frame_queue(sf) 79 | self:frame_queue(wf) 80 | 81 | return self:flush_queue() 82 | end 83 | 84 | 85 | -- create a new http2 session 86 | function _M.session(recv, send, ctx, preread_size, max_streams, max_frame_size) 87 | if not recv then 88 | return nil, "empty read handler" 89 | end 90 | 91 | if not send then 92 | return nil, "empty write handler" 93 | end 94 | 95 | local _, err = send(ctx, HTTP2_PREFACE) 96 | if err then 97 | return nil, err 98 | end 99 | 100 | local session = { 101 | send_window = DEFAULT_WINDOW_SIZE, 102 | recv_window = DEFAULT_WINDOW_SIZE, 103 | init_window = DEFAULT_WINDOW_SIZE, 104 | preread_size = preread_size or DEFAULT_WINDOW_SIZE, 105 | max_streams = max_streams or DEFAULT_MAX_STREAMS, 106 | max_frame_size = max_frame_size or DEFAULT_MAX_FRAME_SIZE, 107 | 108 | recv = recv, -- handler for reading data 109 | send = send, -- handler for writing data 110 | ctx = ctx, 111 | 112 | last_stream_id = 0x0, 113 | next_stream_id = 0x3, -- 0x1 is used for the HTTP/1.1 upgrade 114 | 115 | stream_map = new_tab(4, 1), 116 | total_streams = 0, 117 | idle_streams = 0, 118 | closed_streams = 0, 119 | 120 | goaway_sent = false, 121 | goaway_received = false, 122 | incomplete_headers = false, 123 | 124 | current_sid = nil, 125 | 126 | fatal = false, 127 | ack_peer_settings = false, 128 | 129 | output_queue = nil, 130 | output_queue_size = 0, 131 | last_frame = nil, -- last frame in the output queue 132 | 133 | root = nil, 134 | 135 | hpack = h2_hpack.new(), 136 | } 137 | 138 | session = setmetatable(session, mt) 139 | 140 | session.root = h2_stream.new_root(session) 141 | session.stream_map[0] = session.root 142 | 143 | local ok 144 | ok, err = init(session) 145 | if not ok then 146 | debug_log("failed to send SETTINGS frame: ", err) 147 | return nil, err 148 | end 149 | 150 | return session 151 | end 152 | 153 | 154 | function _M:adjust_window(delta) 155 | local max_window = h2_stream.MAX_WINDOW 156 | for sid, stream in pairs(self.stream_map) do 157 | if sid ~= 0x0 then 158 | local send_window = stream.send_window 159 | if delta > 0 and send_window > max_window - delta then 160 | stream:rst(h2_error.FLOW_CONTROL_ERROR) 161 | return 162 | end 163 | 164 | stream.send_window = send_window + delta 165 | if stream.send_window > 0 and stream.exhausted then 166 | stream.exhausted = false 167 | 168 | elseif stream.send_window < 0 then 169 | stream.exhausted = true 170 | end 171 | end 172 | end 173 | 174 | return true 175 | end 176 | 177 | 178 | function _M:frame_queue(frame) 179 | local output_queue = self.output_queue 180 | local last_frame = self.last_frame 181 | local queue_size = self.output_queue_size 182 | 183 | if not output_queue then 184 | self.output_queue = frame 185 | self.last_frame = frame 186 | self.output_queue_size = 1 187 | return 188 | end 189 | 190 | last_frame.next = frame 191 | self.last_frame = frame 192 | self.output_queue_size = queue_size + 1 193 | end 194 | 195 | 196 | function _M:flush_queue() 197 | local frame = self.output_queue 198 | if not frame then 199 | return true 200 | end 201 | 202 | local size = self.output_queue_size 203 | if send_buffer_size < size then 204 | send_buffer_size = size 205 | send_buffer = new_tab(size, 0) 206 | else 207 | clear_tab(send_buffer) 208 | end 209 | 210 | while frame do 211 | local frame_type = frame.header.type 212 | h2_frame.pack[frame_type](frame, send_buffer) 213 | frame = frame.next 214 | end 215 | 216 | self.output_queue_size = size 217 | self.output_queue = frame 218 | 219 | local _, err = self.send(self.ctx, send_buffer) 220 | if err then 221 | self.fatal = true 222 | return nil, err 223 | end 224 | 225 | return true 226 | end 227 | 228 | 229 | -- submit a request 230 | function _M:submit_request(headers, no_body, priority, pad) 231 | if not self.ack_peer_settings then 232 | return nil, "peer's settings aren't acknowledged yet" 233 | end 234 | 235 | if #headers == 0 then 236 | return nil, "empty headers" 237 | end 238 | 239 | local sid = self.next_stream_id 240 | if sid > MAX_STREAM_ID then 241 | return nil, "stream id overflow" 242 | end 243 | 244 | local total = self.total_streams 245 | if total == self.max_streams then 246 | return nil, h2_error.STRERAM_OVERFLOW 247 | end 248 | 249 | if self.goaway_sent or self.goaway_received then 250 | return nil, "goaway frame was received or sent" 251 | end 252 | 253 | self.next_stream_id = sid + 2 -- odd number 254 | 255 | -- TODO custom stream weight 256 | local stream, err = h2_stream.new(sid, h2_stream.DEFAULT_WEIGHT, self) 257 | if not stream then 258 | return nil, err 259 | end 260 | 261 | local ok 262 | ok, err = stream:submit_headers(headers, no_body, priority, pad) 263 | if not ok then 264 | return nil, err 265 | end 266 | 267 | self.total_streams = total + 1 268 | self.idle_streams = self.idle_streams + 1 269 | 270 | return stream 271 | end 272 | 273 | 274 | function _M:submit_window_update(incr) 275 | return self.root:submit_window_update(incr) 276 | end 277 | 278 | 279 | -- all the frame payload will be read, thereby a proper preread_size is needed . 280 | -- note WINDOW_UPDATE, RST_STREAM or GOAWAY frame will be sent automatically 281 | -- (if necessary). 282 | function _M:recv_frame() 283 | local ctx = self.ctx 284 | local recv = self.recv 285 | 286 | local incomplete_headers = self.incomplete_headers 287 | local frame 288 | local ok 289 | 290 | while true do 291 | local bytes, err = recv(ctx, h2_frame.HEADER_SIZE) 292 | if err then 293 | self.fatal = true 294 | return nil, err 295 | end 296 | 297 | local hd = h2_frame.header.unpack(bytes) 298 | local typ = hd.type 299 | 300 | bytes, err = recv(ctx, hd.length) -- read the payload 301 | if err then 302 | self.fatal = true 303 | return nil, err 304 | end 305 | 306 | if typ <= h2_frame.MAX_FRAME_ID then 307 | local sid = hd.id 308 | local stream = self.stream_map[sid] 309 | if sid > 0x0 and not stream then 310 | -- idle stream 311 | if typ ~= h2_frame.HEADERS_FRAME and 312 | typ ~= h2_frame.PUSH_PROMISE_FREAM 313 | then 314 | return handle_error(self, nil, h2_error.PROTOCOL_ERROR) 315 | end 316 | 317 | stream = h2_stream.new(sid, h2_stream.DEFAULT_WEIGHT, self) 318 | end 319 | 320 | if incomplete_headers then 321 | local current_sid = self.current_sid 322 | if typ ~= h2_frame.CONTINUATION_FRAME or current_sid ~= sid then 323 | return handle_error(self, stream, h2_error.PROTOCOL_ERROR) 324 | end 325 | end 326 | 327 | frame = new_tab(0, h2_frame.sizeof[typ]) 328 | frame.header = hd 329 | 330 | ok, err = h2_frame.unpack[typ](frame, bytes, stream) 331 | if not ok then 332 | return handle_error(self, stream, err) 333 | end 334 | 335 | return frame 336 | 337 | elseif incomplete_headers then 338 | return handle_error(self, nil, h2_error.PROTOCOL_ERROR) 339 | end 340 | end 341 | end 342 | 343 | 344 | function _M:close(code, debug_data) 345 | if self.goaway_sent then 346 | return 347 | end 348 | 349 | code = code or h2_error.NO_ERROR 350 | 351 | debug_log("GOAWAY frame with code ", code, " and last_stream_id: ", 352 | MAX_STREAM_ID, " will be sent") 353 | 354 | local frame = h2_frame.goaway.new(MAX_STREAM_ID, code, debug_data) 355 | self:frame_queue(frame) 356 | 357 | self.goaway_sent = true 358 | end 359 | 360 | 361 | function _M:detach() 362 | self.recv = nil 363 | self.send = nil 364 | self.ctx = nil 365 | self.current_sid = nil 366 | end 367 | 368 | 369 | function _M:attach(recv, send, ctx) 370 | if not recv then 371 | return nil, "empty read handler" 372 | end 373 | 374 | if not send then 375 | return nil, "empty write handler" 376 | end 377 | 378 | self.recv = recv 379 | self.send = send 380 | self.ctx = ctx 381 | 382 | return true 383 | end 384 | 385 | 386 | return _M 387 | -------------------------------------------------------------------------------- /lib/resty/http2/stream.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local util = require "resty.http2.util" 4 | local hpack = require "resty.http2.hpack" 5 | local h2_error = require "resty.http2.error" 6 | local h2_frame 7 | 8 | local new_tab = util.new_tab 9 | local clear_tab = util.clear_tab 10 | local children_update 11 | local pairs = pairs 12 | local is_num = util.is_num 13 | local is_tab = util.is_tab 14 | local lower = string.lower 15 | local sub = string.sub 16 | local concat = table.concat 17 | 18 | local buffer 19 | local buffer_len = 0 20 | 21 | local STATE_IDLE = 0 22 | local STATE_OPEN = 1 23 | local STATE_CLOSED = 3 24 | local STATE_HALF_CLOSED_LOCAL = 4 25 | local STATE_HALF_CLOSED_REMOTE = 5 26 | local STATE_RESERVED_LOCAL = 6 27 | local STATE_RESERVED_REMOTE = 7 28 | 29 | local MAX_WINDOW = 0x7fffffff 30 | 31 | local _M = { 32 | _VERSION = "0.1", 33 | 34 | MAX_WEIGHT = 256, 35 | DEFAULT_WEIGHT = 16, 36 | } 37 | 38 | local mt = { __index = _M } 39 | 40 | local IS_CONNECTION_SPEC_HEADERS = { 41 | ["connection"] = true, 42 | ["keep-alive"] = true, 43 | ["proxy-connection"] = true, 44 | ["upgrade"] = true, 45 | ["transfer-encoding"] = true, 46 | } 47 | 48 | 49 | children_update = function(node) 50 | if not node then 51 | return 52 | end 53 | 54 | local child = node.child 55 | if not child then 56 | return 57 | end 58 | 59 | local max_weight = _M.MAX_WEIGHT 60 | local rank = node.rank 61 | local rel_weight = node.rel_weight 62 | 63 | while true do 64 | child.rank = rank + 1 65 | child.rel_weight = rel_weight / max_weight * child.weight 66 | 67 | children_update(child) 68 | 69 | child = child.next_sibling 70 | end 71 | end 72 | 73 | 74 | -- let depend as current stream's parent 75 | function _M:set_dependency(depend, excl) 76 | local stream = self 77 | local root = stream.session.root 78 | 79 | if not depend then 80 | depend = root 81 | excl = false 82 | end 83 | 84 | local child 85 | local max_weight = _M.MAX_WEIGHT 86 | 87 | if depend == root then 88 | stream.rel_weight = stream.weight / max_weight 89 | stream.rank = 1 90 | child = depend.child 91 | 92 | else 93 | -- check whether stream is an ancestor of depend 94 | while true do 95 | local node = depend.parent 96 | if node == root or node.rank < stream.rank then 97 | break 98 | end 99 | 100 | if node == stream then 101 | -- firstly take depend out of it's "old parent" 102 | local last_node = depend.last_sibling 103 | local next_node = depend.next_sibling 104 | 105 | if last_node then 106 | last_node.next_sibling = next_node 107 | end 108 | 109 | if next_node then 110 | next_node.last_sibling = last_node 111 | end 112 | 113 | -- now stream.parent will be the "new parent" of depend 114 | local parent = stream.parent 115 | local first_child = parent.child 116 | 117 | first_child.last_sibling = depend 118 | depend.last_sibling = nil 119 | depend.next_sibling = first_child 120 | depend.parent = parent 121 | parent.child = depend 122 | 123 | if parent == root then 124 | depend.rank = 1 125 | depend.rel_weight = depend.weight / max_weight 126 | else 127 | local weight = depend.weight 128 | depend.rank = parent.rank + 1 129 | depend.rel_weight = parent.rel_weight / max_weight * weight 130 | end 131 | 132 | if not excl then 133 | children_update(depend) 134 | end 135 | 136 | break 137 | end 138 | end 139 | 140 | stream.rank = depend.rank + 1 141 | stream.rel_weight = depend.rel_weight / max_weight * stream.weight 142 | child = depend.child 143 | end 144 | 145 | if excl and child then 146 | -- stream should be the sole direct child of depend 147 | local c = child 148 | local last 149 | 150 | while true do 151 | c.parent = stream 152 | if not c.next_sibling then 153 | last = c -- the last sibling 154 | break 155 | end 156 | 157 | c = c.next_sibling 158 | end 159 | 160 | last.next_sibling = stream.child 161 | stream.child.last_sibling = last 162 | 163 | stream.child = child 164 | depend.child = stream 165 | end 166 | 167 | local last_node = stream.last_sibling 168 | local next_node = stream.next_sibling 169 | 170 | if last_node then 171 | last_node.next_sibling = next_node 172 | end 173 | 174 | if next_node then 175 | next_node.last_sibling = last_node 176 | end 177 | 178 | stream.parent = depend 179 | 180 | children_update(stream) 181 | end 182 | 183 | 184 | -- serialize headers and create a headers frame, 185 | -- note this function does not check the HTTP protocol semantics, 186 | -- callers should check this in the higher land. 187 | function _M:submit_headers(headers, end_stream, priority, pad) 188 | local state = self.state 189 | if state ~= STATE_IDLE 190 | and state ~= STATE_OPEN 191 | and state ~= STATE_RESERVED_LOCAL 192 | and state ~= STATE_HALF_CLOSED_REMOTE 193 | then 194 | return nil, "invalid stream state" 195 | end 196 | 197 | local sid = self.sid 198 | 199 | if sid == 0 then 200 | return nil, "cannot submit headers to the whole connection" 201 | end 202 | 203 | if sid % 2 == 0 then 204 | return nil, "peer-initiated stream" 205 | end 206 | 207 | if self.end_headers then 208 | return nil, "end headers" 209 | end 210 | 211 | local headers_count = #headers 212 | if buffer_len < headers_count then 213 | buffer = new_tab(headers_count, 0) 214 | buffer_len = headers_count 215 | else 216 | clear_tab(buffer) 217 | end 218 | 219 | for i = 1, #headers do 220 | local name = lower(headers[i].name) 221 | local value = headers[i].value 222 | 223 | if IS_CONNECTION_SPEC_HEADERS[name] then 224 | goto continue 225 | end 226 | 227 | local index = hpack.COMMON_REQUEST_HEADERS_INDEX[name] 228 | if is_num(index) then 229 | buffer[#buffer + 1] = hpack.incr_indexed(index) 230 | hpack.encode(value, buffer, false) 231 | goto continue 232 | end 233 | 234 | if is_tab(index) then 235 | for v_name, v_index in pairs(index) do 236 | if value == v_name then 237 | buffer[#buffer + 1] = hpack.indexed(v_index) 238 | goto continue 239 | end 240 | end 241 | end 242 | 243 | buffer[#buffer + 1] = hpack.incr_indexed(0) 244 | hpack.encode(name, buffer, true) 245 | hpack.encode(value, buffer, false) 246 | 247 | ::continue:: 248 | end 249 | 250 | local fragment = concat(buffer) 251 | local max_frame_size = self.session.max_frame_size 252 | local payload = fragment 253 | local continue = false 254 | local first_piece_size = max_frame_size 255 | if priority then 256 | first_piece_size = first_piece_size - 5 257 | end 258 | 259 | if pad then 260 | first_piece_size = first_piece_size - #pad - 1 261 | end 262 | 263 | if #fragment > first_piece_size then 264 | payload = sub(fragment, 1, first_piece_size) 265 | continue = true 266 | end 267 | 268 | local frame, err = h2_frame.headers.new(payload, priority, pad, 269 | end_stream, not continue, sid) 270 | if not frame then 271 | return nil, err 272 | end 273 | 274 | self.session:frame_queue(frame) 275 | 276 | if continue then 277 | local pos = first_piece_size + 1 278 | repeat 279 | if pos + max_frame_size - 1 < #fragment then 280 | payload = sub(fragment, pos, pos + max_frame_size - 1) 281 | else 282 | payload = sub(fragment, pos) 283 | continue = false 284 | end 285 | 286 | frame, err = h2_frame.continuation.new(payload, not continue, sid) 287 | if not frame then 288 | return nil, err 289 | end 290 | 291 | self.session:frame_queue(frame) 292 | until not continue 293 | end 294 | 295 | -- FIXME we change the stream state just when the frame was queued, 296 | -- maybe it is improper and shoule be postponed 297 | -- until the frame was really sent. 298 | if state == STATE_IDLE then 299 | self.state = end_stream and STATE_HALF_CLOSED_LOCAL or STATE_OPEN 300 | 301 | elseif state == STATE_RESERVED_LOCAL then 302 | self.state = end_stream and STATE_CLOSED or STATE_HALF_CLOSED_REMOTE 303 | end 304 | 305 | local peer_state = self.peer_state 306 | if peer_state == STATE_IDLE then 307 | self.peer_state = end_stream and STATE_HALF_CLOSED_REMOTE or STATE_OPEN 308 | 309 | elseif peer_state == STATE_RESERVED_REMOTE then 310 | self.peer_state = end_stream and STATE_CLOSED or STATE_HALF_CLOSED_LOCAL 311 | end 312 | 313 | self.end_headers = true 314 | 315 | return true 316 | end 317 | 318 | 319 | function _M:submit_data(data, pad, last) 320 | local state = self.state 321 | if state ~= STATE_OPEN and state ~= STATE_HALF_CLOSED_REMOTE then 322 | return nil, "invalid stream state" 323 | end 324 | 325 | if self.exhausted then 326 | return nil, "stream send window exhausted" 327 | end 328 | 329 | local frame = h2_frame.data.new(data, pad, last, self.sid) 330 | 331 | local length = frame.header.length 332 | 333 | local self_send_window = self.send_window 334 | local session_send_window = self.session.send_window 335 | 336 | if length > self_send_window then 337 | return nil, "stream send window is not enough" 338 | end 339 | 340 | if length > session_send_window then 341 | return nil, "connection send window is not enough" 342 | end 343 | 344 | if last then 345 | if state == STATE_OPEN then 346 | self.state = STATE_HALF_CLOSED_LOCAL 347 | else 348 | self.state = STATE_CLOSED 349 | end 350 | 351 | local peer_state = self.peer_state 352 | if peer_state == STATE_OPEN then 353 | self.peer_state = STATE_HALF_CLOSED_REMOTE 354 | else 355 | self.peer_state = STATE_CLOSED 356 | end 357 | end 358 | 359 | self.session.send_window = session_send_window - length 360 | self.send_window = self_send_window - length 361 | 362 | self.session:frame_queue(frame) 363 | 364 | return true 365 | end 366 | 367 | 368 | function _M:rst(code) 369 | code = code or h2_error.protocol.NO_ERROR 370 | local state = self.state 371 | if state == STATE_IDLE or state == STATE_CLOSED then 372 | return 373 | end 374 | 375 | local session = self.session 376 | 377 | local frame = h2_frame.rst.new(code, self.sid) 378 | 379 | session:frame_queue(frame) 380 | 381 | -- FIXME we change the stream state just when the frame was queued, 382 | -- maybe it is improper and shoule be postponed 383 | -- until the frame was reall sent. 384 | self.state = STATE_CLOSED 385 | self.peer_state = STATE_CLOSED 386 | 387 | session.closed_streams = session.closed_streams + 1 388 | end 389 | 390 | 391 | function _M:submit_window_update(incr) 392 | local state = self.state 393 | if state == STATE_IDLE or state == STATE_CLOSED then 394 | return nil, "invalid stream state" 395 | end 396 | 397 | local frame, err = h2_frame.window_update.new(self.sid, incr) 398 | if not frame then 399 | return nil, err 400 | end 401 | 402 | frame.window_size_increment = incr 403 | 404 | self.session:frame_queue(frame) 405 | return true 406 | end 407 | 408 | 409 | function _M.new(sid, weight, session) 410 | weight = weight or _M.DEFAULT_WEIGHT 411 | 412 | local stream_map = session.stream_map 413 | local stream = stream_map[sid] 414 | 415 | local init_window = session.init_window 416 | 417 | if not stream then 418 | stream = { 419 | sid = sid, 420 | state = _M.STATE_IDLE, 421 | peer_state = _M.STATE_IDLE, 422 | data = new_tab(1, 0), 423 | parent = session.root, 424 | next_sibling = nil, 425 | last_sibling = nil, 426 | child = nil, -- the first child 427 | weight = weight, 428 | rel_weight = weight / _M.MAX_WEIGHT, 429 | rank = -1, 430 | opaque_data = nil, -- user private data 431 | session = session, -- the session 432 | init_window = init_window, 433 | send_window = init_window, 434 | recv_window = session.preread_size, 435 | exhausted = false, 436 | done = false, 437 | } 438 | 439 | session.total_streams = session.total_streams + 1 440 | session.idle_streams = session.idle_streams + 1 441 | 442 | stream_map[sid] = stream 443 | 444 | else 445 | -- since the stream dependencies, the stream maybe created early 446 | stream.init_window = init_window 447 | stream.data = new_tab(1, 0) 448 | stream.init_window = init_window 449 | stream.send_window = init_window 450 | stream.recv_window = session.preread_size 451 | stream.exhausted = false 452 | 453 | if weight ~= stream.weight then 454 | stream.weight = weight 455 | children_update(stream) 456 | end 457 | end 458 | 459 | return setmetatable(stream, mt) 460 | end 461 | 462 | 463 | function _M.new_root(session) 464 | -- XXX this is a work around way to solve 465 | -- the mutal requision of frame.lua nad stream.lua, 466 | if not h2_frame then 467 | h2_frame = require "resty.http2.frame" 468 | end 469 | 470 | local root = { 471 | sid = 0x0, 472 | rank = 0, 473 | child = nil, 474 | parent = nil, 475 | state = STATE_OPEN, 476 | session = session, 477 | } 478 | 479 | root.parent = root 480 | 481 | return setmetatable(root, mt) 482 | end 483 | 484 | 485 | _M.STATE_IDLE = STATE_IDLE 486 | _M.STATE_OPEN = STATE_OPEN 487 | _M.STATE_CLOSED = STATE_CLOSED 488 | _M.STATE_HALF_CLOSED_LOCAL = STATE_HALF_CLOSED_LOCAL 489 | _M.STATE_HALF_CLOSED_REMOTE = STATE_HALF_CLOSED_REMOTE 490 | _M.STATE_RESERVED_LOCAL = STATE_RESERVED_LOCAL 491 | _M.STATE_RESERVED_REMOTE = STATE_RESERVED_REMOTE 492 | 493 | _M.MAX_WINDOW = MAX_WINDOW 494 | 495 | _M.IS_CONNECTION_SPEC_HEADERS = IS_CONNECTION_SPEC_HEADERS 496 | 497 | 498 | return _M 499 | -------------------------------------------------------------------------------- /lib/resty/http2/util.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | local bit = require "bit" 4 | 5 | local pairs = pairs 6 | local bor = bit.bor 7 | local band = bit.band 8 | local bnot = bit.bnot 9 | local blshift = bit.lshift 10 | local brshift = bit.rshift 11 | local char = string.char 12 | local type = type 13 | local ngx_log = ngx.log 14 | local DEBUG = ngx.DEBUG 15 | local debug_log 16 | 17 | local _M = { _VERSION = "0.1" } 18 | 19 | 20 | if ngx.config.debug then 21 | debug_log = function(...) ngx_log(DEBUG, ...) end 22 | else 23 | debug_log = function() end 24 | end 25 | _M.debug_log = debug_log 26 | 27 | 28 | local ok, new_tab = pcall(require, "table.new") 29 | if not ok then 30 | new_tab = function() return {} end 31 | end 32 | _M.new_tab = new_tab 33 | 34 | local clear_tab 35 | ok, clear_tab = pcall(require, "table.clear") 36 | if not ok then 37 | clear_tab = function(tab) 38 | for k, _ in pairs(tab) do 39 | tab[k] = nil 40 | end 41 | end 42 | end 43 | _M.clear_tab = clear_tab 44 | 45 | 46 | function _M.align(value, base) 47 | return band(value + base - 1, bnot(base - 1)) 48 | end 49 | 50 | 51 | function _M.pack_u16(u, dst) 52 | local len = #dst 53 | dst[len + 1] = char(band(brshift(u, 8), 0xff)) 54 | dst[len + 2] = char(band(u, 0xff)) 55 | end 56 | 57 | 58 | function _M.unpack_u16(b1, b2) 59 | return bor(blshift(b1, 8), b2) 60 | end 61 | 62 | 63 | function _M.pack_u32(u, dst) 64 | local len = #dst 65 | dst[len + 1] = char(band(brshift(u, 24), 0xff)) 66 | dst[len + 2] = char(band(brshift(u, 16), 0xff)) 67 | dst[len + 3] = char(band(brshift(u, 8), 0xff)) 68 | dst[len + 4] = char(band(u, 0xff)) 69 | end 70 | 71 | 72 | function _M.unpack_u32(b1, b2, b3, b4) 73 | local mid1 = bor(blshift(b1, 8), b2) 74 | local mid2 = bor(blshift(b3, 8), b4) 75 | return bor(blshift(mid1, 16), mid2) 76 | end 77 | 78 | 79 | function _M.is_num(num) 80 | return type(num) == "number" 81 | end 82 | 83 | 84 | function _M.is_tab(tab) 85 | return type(tab) == "table" 86 | end 87 | 88 | 89 | function _M.is_func(func) 90 | return type(func) == "function" 91 | end 92 | 93 | 94 | function _M.is_str(str) 95 | return type(str) == "string" 96 | end 97 | 98 | 99 | -- TODO recycle the buffer struct if necessary 100 | function _M.new_buffer(data, pos, last) 101 | return { 102 | data = data, 103 | pos = pos, 104 | last = last, 105 | next = nil, 106 | } 107 | end 108 | 109 | 110 | return _M 111 | -------------------------------------------------------------------------------- /rockspec/lua-resty-http2-1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-http2" 2 | version = "1.0-0" 3 | 4 | source = { 5 | url = "git://github.com/tokers/lua-resty-http2", 6 | tag = "v1.0", 7 | } 8 | 9 | description = { 10 | summary = "The HTTP/2 Protocol (Client Side) Implementation for OpenResty.", 11 | homepage = "https://github.com/tokers/lua-resty-http2", 12 | license = "2-clause BSD", 13 | maintainer = "Alex Zhang ", 14 | } 15 | 16 | dependencies = { 17 | "lua >= 5.1", 18 | } 19 | 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | ["resty.http2"] = "lib/resty/http2.lua", 24 | ["resty.http2.error"] = "lib/resty/http2/error.lua", 25 | ["resty.http2.frame"] = "lib/resty/http2/frame.lua", 26 | ["resty.http2.hpack"] = "lib/resty/http2/hpack.lua", 27 | ["resty.http2.huff_decode"] = "lib/resty/http2/huff_decode.lua", 28 | ["resty.http2.huff_encode"] = "lib/resty/http2/huff_encode.lua", 29 | ["resty.http2.protocol"] = "lib/resty/http2/protocol.lua", 30 | ["resty.http2.stream"] = "lib/resty/http2/stream.lua", 31 | ["resty.http2.util"] = "lib/resty/http2/util.lua", 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /t/00-sanity.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | our $http_config = << 'EOC'; 4 | lua_package_path "lib/?.lua;;"; 5 | 6 | server { 7 | listen 8083 http2; 8 | location = /t1 { 9 | return 200; 10 | } 11 | 12 | location = /t2 { 13 | return 200 "hello world"; 14 | } 15 | 16 | location = /t3 { 17 | http2_chunk_size 256; 18 | content_by_lua_block { 19 | local data = {} 20 | for i = 48, 120 do 21 | data[i - 47] = string.char(i) 22 | end 23 | 24 | for i = 1, 50 do 25 | ngx.print(data) 26 | ngx.flush(true) 27 | end 28 | } 29 | } 30 | 31 | location = /t4 { 32 | return 200; 33 | header_filter_by_lua_block { 34 | local cookie = {} 35 | for i = 1, 20000 do 36 | cookie[i] = string.char(math.random(48, 97)) 37 | end 38 | 39 | ngx.header["Cookie"] = table.concat(cookie) 40 | } 41 | } 42 | 43 | location = /t5 { 44 | return 200; 45 | add_header test-header 1; 46 | add_header test-header 2; 47 | add_header test-header 3; 48 | } 49 | 50 | location = /t6 { 51 | return 200; 52 | add_header Keep-Alive 100; 53 | } 54 | } 55 | EOC 56 | 57 | repeat_each(3); 58 | plan tests => repeat_each() * blocks() * 3; 59 | no_long_string(); 60 | run_tests(); 61 | 62 | __DATA__ 63 | 64 | === TEST 1: GET request and zero Content-Length 65 | 66 | --- http_config eval: $::http_config 67 | --- config 68 | location = /t { 69 | content_by_lua_block { 70 | local http2 = require "resty.http2" 71 | local headers = { 72 | { name = ":authority", value = "test.com" }, 73 | { name = ":method", value = "GET" }, 74 | { name = ":path", value = "/t1" }, 75 | { name = ":scheme", value = "http" }, 76 | { name = "accept-encoding", value = "deflate, gzip" }, 77 | } 78 | 79 | local on_headers_reach = function(ctx, headers) 80 | assert(headers[":status"] == "200") 81 | local length = headers["content-length"] 82 | assert(not length or length == "0") 83 | 84 | return true 85 | end 86 | 87 | local on_data_reach = function(ctx, data) 88 | error("unexpected data") 89 | end 90 | 91 | local sock = ngx.socket.tcp() 92 | local ok, err = sock:connect("127.0.0.1", 8083) 93 | if not ok then 94 | ngx.log(ngx.ERR, err) 95 | return 96 | end 97 | 98 | local client, err = http2.new { 99 | ctx = sock, 100 | recv = sock.receive, 101 | send = sock.send, 102 | } 103 | 104 | if not client then 105 | ngx.log(ngx.ERR, err) 106 | return 107 | end 108 | 109 | local ok, err = client:request(headers, nil, on_headers_reach, 110 | on_data_reach) 111 | if not ok then 112 | ngx.log(ngx.ERR, err) 113 | return 114 | end 115 | 116 | local ok, err = sock:close() 117 | if not ok then 118 | ngx.log(ngx.ERR, err) 119 | return 120 | end 121 | 122 | ngx.print("OK") 123 | } 124 | } 125 | 126 | --- request 127 | GET /t 128 | 129 | --- response_body: OK 130 | --- no_error_log 131 | [error] 132 | 133 | 134 | 135 | === TEST 2: GET request with response body 136 | 137 | --- http_config eval: $::http_config 138 | --- config 139 | location = /t { 140 | content_by_lua_block { 141 | local http2 = require "resty.http2" 142 | local headers = { 143 | { name = ":authority", value = "test.com" }, 144 | { name = ":method", value = "GET" }, 145 | { name = ":path", value = "/t2" }, 146 | { name = ":scheme", value = "http" }, 147 | { name = "accept-encoding", value = "deflate, gzip" }, 148 | } 149 | 150 | local on_headers_reach = function(ctx, headers) 151 | assert(headers[":status"] == "200") 152 | local length = headers["content-length"] 153 | assert(not length or length == "11") 154 | end 155 | 156 | local on_data_reach = function(ctx, data) 157 | assert(data == "hello world") 158 | end 159 | 160 | local sock = ngx.socket.tcp() 161 | local ok, err = sock:connect("127.0.0.1", 8083) 162 | if not ok then 163 | ngx.log(ngx.ERR, err) 164 | return 165 | end 166 | 167 | local client, err = http2.new { 168 | ctx = sock, 169 | recv = sock.receive, 170 | send = sock.send, 171 | } 172 | 173 | if not client then 174 | ngx.log(ngx.ERR, err) 175 | return 176 | end 177 | 178 | local ok, err = client:request(headers, nil, on_headers_reach, 179 | on_data_reach) 180 | if not ok then 181 | ngx.log(ngx.ERR, err) 182 | return 183 | end 184 | 185 | local ok, err = sock:close() 186 | if not ok then 187 | ngx.log(ngx.ERR, err) 188 | return 189 | end 190 | 191 | ngx.print("OK") 192 | } 193 | } 194 | 195 | --- request 196 | GET /t 197 | 198 | --- response_body: OK 199 | --- no_error_log 200 | [error] 201 | 202 | 203 | 204 | === TEST 3: POST request with request body 205 | 206 | --- http_config eval: $::http_config 207 | --- config 208 | location = /t { 209 | content_by_lua_block { 210 | local http2 = require "resty.http2" 211 | local headers = { 212 | { name = ":authority", value = "test.com" }, 213 | { name = ":method", value = "GET" }, 214 | { name = ":path", value = "/t1" }, 215 | { name = ":scheme", value = "http" }, 216 | { name = "accept-encoding", value = "deflate, gzip" }, 217 | { name = "content-length", value = "11" }, 218 | } 219 | 220 | local on_headers_reach = function(ctx, headers) 221 | assert(headers[":status"] == "200") 222 | local length = headers["content-length"] 223 | assert(not length or length == "0") 224 | end 225 | 226 | local on_data_reach = function(ctx, data) 227 | if #data > 0 then 228 | error("unexpected DATA frame") 229 | end 230 | end 231 | 232 | local sock = ngx.socket.tcp() 233 | local ok, err = sock:connect("127.0.0.1", 8083) 234 | if not ok then 235 | ngx.log(ngx.ERR, err) 236 | return 237 | end 238 | 239 | local client, err = http2.new { 240 | ctx = sock, 241 | recv = sock.receive, 242 | send = sock.send, 243 | } 244 | 245 | if not client then 246 | ngx.log(ngx.ERR, err) 247 | return 248 | end 249 | 250 | local ok, err = client:request(headers, "hello world", 251 | on_headers_reach, on_data_reach) 252 | if not ok then 253 | ngx.log(ngx.ERR, err) 254 | return 255 | end 256 | 257 | local ok, err = sock:close() 258 | if not ok then 259 | ngx.log(ngx.ERR, err) 260 | return 261 | end 262 | 263 | ngx.print("OK") 264 | } 265 | } 266 | 267 | --- request 268 | GET /t 269 | 270 | --- response_body: OK 271 | --- no_error_log 272 | [error] 273 | 274 | 275 | 276 | === TEST 4: GET request with bulk response body 277 | 278 | --- http_config eval: $::http_config 279 | --- config 280 | location = /t { 281 | content_by_lua_block { 282 | local http2 = require "resty.http2" 283 | local headers = { 284 | { name = ":authority", value = "test.com" }, 285 | { name = ":method", value = "GET" }, 286 | { name = ":path", value = "/t3" }, 287 | { name = ":scheme", value = "http" }, 288 | } 289 | 290 | local on_headers_reach = function(ctx, headers) 291 | assert(headers[":status"] == "200") 292 | local length = headers["content-length"] 293 | assert(not length or length == "3650") 294 | end 295 | 296 | local data_frame_count = 0 297 | local on_data_reach = function(ctx, data) 298 | data_frame_count = data_frame_count + 1 299 | end 300 | 301 | local sock = ngx.socket.tcp() 302 | local ok, err = sock:connect("127.0.0.1", 8083) 303 | if not ok then 304 | ngx.log(ngx.ERR, err) 305 | return 306 | end 307 | 308 | local client, err = http2.new { 309 | ctx = sock, 310 | recv = sock.receive, 311 | send = sock.send, 312 | } 313 | 314 | if not client then 315 | ngx.log(ngx.ERR, err) 316 | return 317 | end 318 | 319 | local ok, err = client:request(headers, nil, on_headers_reach, 320 | on_data_reach) 321 | if not ok then 322 | ngx.log(ngx.ERR, err) 323 | return 324 | end 325 | 326 | local ok, err = sock:close() 327 | if not ok then 328 | ngx.log(ngx.ERR, err) 329 | return 330 | end 331 | 332 | assert(data_frame_count == 51) 333 | 334 | ngx.print("OK") 335 | } 336 | } 337 | 338 | --- request 339 | GET /t 340 | 341 | --- response_body: OK 342 | --- no_error_log 343 | [error] 344 | 345 | 346 | 347 | === TEST 5: HEAD request 348 | 349 | --- http_config eval: $::http_config 350 | --- config 351 | location = /t { 352 | content_by_lua_block { 353 | local http2 = require "resty.http2" 354 | local headers = { 355 | { name = ":authority", value = "test.com" }, 356 | { name = ":method", value = "HEAD" }, 357 | { name = ":path", value = "/t1" }, 358 | { name = ":scheme", value = "http" }, 359 | } 360 | 361 | local on_headers_reach = function(ctx, headers) 362 | assert(headers[":status"] == "200") 363 | local length = headers["content-length"] 364 | assert(not length or length == "0") 365 | end 366 | 367 | local on_data_reach = function(ctx, data) 368 | error("unexpected DATA frame") 369 | end 370 | 371 | local sock = ngx.socket.tcp() 372 | local ok, err = sock:connect("127.0.0.1", 8083) 373 | if not ok then 374 | ngx.log(ngx.ERR, err) 375 | return 376 | end 377 | 378 | local client, err = http2.new { 379 | ctx = sock, 380 | recv = sock.receive, 381 | send = sock.send, 382 | } 383 | 384 | if not client then 385 | ngx.log(ngx.ERR, err) 386 | return 387 | end 388 | 389 | local ok, err = client:request(headers, nil, on_headers_reach, 390 | on_data_reach) 391 | if not ok then 392 | ngx.log(ngx.ERR, err) 393 | return 394 | end 395 | 396 | local ok, err = sock:close() 397 | if not ok then 398 | ngx.log(ngx.ERR, err) 399 | return 400 | end 401 | 402 | ngx.print("OK") 403 | } 404 | } 405 | 406 | --- request 407 | GET /t 408 | 409 | --- response_body: OK 410 | --- no_error_log 411 | [error] 412 | 413 | 414 | 415 | === TEST 6: keepalive 416 | 417 | --- http_config eval: $::http_config 418 | --- config 419 | location = /t { 420 | content_by_lua_block { 421 | local http2 = require "resty.http2" 422 | local headers = { 423 | { name = ":authority", value = "test.com" }, 424 | { name = ":method", value = "GET" }, 425 | { name = ":path", value = "/t3" }, 426 | { name = ":scheme", value = "http" }, 427 | } 428 | 429 | local on_headers_reach = function(ctx, headers) 430 | assert(headers[":status"] == "200") 431 | local length = headers["content-length"] 432 | assert(not length) 433 | end 434 | 435 | local data_length = 0 436 | 437 | local on_data_reach = function(ctx, data) 438 | data_length = data_length + #data 439 | end 440 | 441 | local sock = ngx.socket.tcp() 442 | local ok, err = sock:connect("127.0.0.1", 8083, {pool = "h2"}) 443 | if not ok then 444 | ngx.log(ngx.ERR, err) 445 | return 446 | end 447 | 448 | local client, err = http2.new { 449 | ctx = sock, 450 | recv = sock.receive, 451 | send = sock.send, 452 | } 453 | 454 | if not client then 455 | ngx.log(ngx.ERR, err) 456 | return 457 | end 458 | 459 | local ok, err = client:request(headers, nil, on_headers_reach, 460 | on_data_reach) 461 | if not ok then 462 | ngx.log(ngx.ERR, err) 463 | return 464 | end 465 | 466 | local ok, err = sock:setkeepalive(nil, 1) 467 | if not ok then 468 | ngx.log(ngx.ERR, err) 469 | return 470 | end 471 | 472 | assert(data_length == 3650) 473 | 474 | ngx.print("OK1") 475 | 476 | client:keepalive("key") 477 | 478 | data_length = 0 479 | 480 | sock = ngx.socket.tcp() 481 | ok, err = sock:connect("127.0.0.1", 8083, {pool = "h2"}) 482 | if not ok then 483 | ngx.log(ngx.ERR, err) 484 | return 485 | end 486 | 487 | local reuse_times = sock:getreusedtimes() 488 | assert(reuse_times == 1) 489 | 490 | client, err = http2.new { 491 | ctx = sock, 492 | recv = sock.receive, 493 | send = sock.send, 494 | key = "key", 495 | } 496 | 497 | if not client then 498 | ngx.log(ngx.ERR, err) 499 | return 500 | end 501 | 502 | ok, err = client:request(headers, nil, on_headers_reach, on_data_reach) 503 | if not ok then 504 | ngx.log(ngx.ERR, err) 505 | return 506 | end 507 | 508 | assert(data_length == 3650) 509 | 510 | ok, err = sock:close() 511 | if not ok then 512 | ngx.log(ngx.ERR, err) 513 | end 514 | 515 | ngx.print("OK2") 516 | } 517 | } 518 | 519 | --- request 520 | GET /t 521 | 522 | --- response_body: OK1OK2 523 | --- no_error_log 524 | [error] 525 | 526 | 527 | 528 | === TEST 7: large response headers 529 | 530 | --- http_config eval: $::http_config 531 | --- config 532 | location = /t { 533 | content_by_lua_block { 534 | local http2 = require "resty.http2" 535 | local headers = { 536 | { name = ":authority", value = "test.com" }, 537 | { name = ":method", value = "GET" }, 538 | { name = ":path", value = "/t4" }, 539 | { name = ":scheme", value = "http" }, 540 | { name = "accept-encoding", value = "deflate, gzip" }, 541 | } 542 | 543 | local on_headers_reach = function(ctx, headers) 544 | assert(#headers["cookie"] == 20000) 545 | end 546 | 547 | local on_data_reach = function(ctx, data) 548 | if #data > 0 then 549 | error("unexpected DATA frame") 550 | end 551 | end 552 | 553 | local sock = ngx.socket.tcp() 554 | local ok, err = sock:connect("127.0.0.1", 8083) 555 | if not ok then 556 | ngx.log(ngx.ERR, err) 557 | return 558 | end 559 | 560 | local client, err = http2.new { 561 | ctx = sock, 562 | recv = sock.receive, 563 | send = sock.send, 564 | preread_size = 1024, 565 | } 566 | 567 | if not client then 568 | ngx.log(ngx.ERR, err) 569 | return 570 | end 571 | 572 | local ok, err = client:request(headers, nil, on_headers_reach, 573 | on_data_reach) 574 | 575 | if not ok then 576 | ngx.log(ngx.ERR, err) 577 | return 578 | end 579 | 580 | local ok, err = sock:close() 581 | if not ok then 582 | ngx.log(ngx.ERR, err) 583 | return 584 | end 585 | 586 | ngx.print("OK") 587 | } 588 | } 589 | 590 | --- request 591 | GET /t 592 | 593 | --- response_body: OK 594 | --- no_error_log 595 | [error] 596 | 597 | 598 | === TEST 8: duplicate response headers 599 | 600 | --- http_config eval: $::http_config 601 | --- config 602 | location = /t { 603 | content_by_lua_block { 604 | local http2 = require "resty.http2" 605 | local headers = { 606 | { name = ":authority", value = "test.com" }, 607 | { name = ":method", value = "GET" }, 608 | { name = ":path", value = "/t5" }, 609 | { name = ":scheme", value = "http" }, 610 | } 611 | 612 | local sock = ngx.socket.tcp() 613 | local ok, err = sock:connect("127.0.0.1", 8083) 614 | if not ok then 615 | ngx.log(ngx.ERR, err) 616 | return 617 | end 618 | 619 | local client, err = http2.new { 620 | ctx = sock, 621 | recv = sock.receive, 622 | send = sock.send, 623 | preread_size = 1024, 624 | } 625 | 626 | if not client then 627 | ngx.log(ngx.ERR, err) 628 | return 629 | end 630 | 631 | local ok, err = client:acknowledge_settings() 632 | if not ok then 633 | ngx.log(ngx.ERR, err) 634 | return 635 | end 636 | 637 | local stream, err = client:send_request(headers) 638 | if not stream then 639 | ngx.log(ngx.ERR, err) 640 | return 641 | end 642 | 643 | local headers, err = client:read_headers(stream) 644 | if not headers then 645 | ngx.log(ngx.ERR, err) 646 | return 647 | end 648 | 649 | assert(type(headers["test-header"] == "table")) 650 | local th = headers["test-header"] 651 | 652 | assert(th[1] == "1") 653 | assert(th[2] == "2") 654 | assert(th[3] == "3") 655 | 656 | local ok, err = client:close() 657 | if not ok then 658 | ngx.log(ngx.ERR, err) 659 | return 660 | end 661 | 662 | local ok, err = sock:close() 663 | if not ok then 664 | ngx.log(ngx.ERR, err) 665 | return 666 | end 667 | 668 | ngx.print("OK") 669 | } 670 | } 671 | 672 | --- request 673 | GET /t 674 | 675 | --- response_body: OK 676 | --- no_error_log 677 | [error] 678 | 679 | 680 | 681 | === TEST 9: peer sent connection specific headers 682 | 683 | --- http_config eval: $::http_config 684 | --- config 685 | location = /t { 686 | content_by_lua_block { 687 | local http2 = require "resty.http2" 688 | local headers = { 689 | { name = ":authority", value = "test.com" }, 690 | { name = ":method", value = "GET" }, 691 | { name = ":path", value = "/t6" }, 692 | { name = ":scheme", value = "http" }, 693 | } 694 | 695 | local sock = ngx.socket.tcp() 696 | local ok, err = sock:connect("127.0.0.1", 8083) 697 | if not ok then 698 | ngx.log(ngx.ERR, err) 699 | return 700 | end 701 | 702 | local client, err = http2.new { 703 | ctx = sock, 704 | recv = sock.receive, 705 | send = sock.send, 706 | preread_size = 1024, 707 | } 708 | 709 | if not client then 710 | ngx.log(ngx.ERR, err) 711 | return 712 | end 713 | 714 | local ok, err = client:acknowledge_settings() 715 | if not ok then 716 | ngx.log(ngx.ERR, err) 717 | return 718 | end 719 | 720 | local stream, err = client:send_request(headers) 721 | if not stream then 722 | ngx.log(ngx.ERR, err) 723 | return 724 | end 725 | 726 | local headers, err = client:read_headers(stream) 727 | if not headers then 728 | ngx.log(ngx.ERR, err) 729 | return 730 | end 731 | 732 | assert(headers["keep-alive"] == nil) 733 | 734 | local ok, err = client:close() 735 | if not ok then 736 | ngx.log(ngx.ERR, err) 737 | return 738 | end 739 | 740 | local ok, err = sock:close() 741 | if not ok then 742 | ngx.log(ngx.ERR, err) 743 | return 744 | end 745 | 746 | ngx.print("OK") 747 | } 748 | } 749 | 750 | --- request 751 | GET /t 752 | 753 | --- response_body: OK 754 | --- no_error_log 755 | [error] 756 | -------------------------------------------------------------------------------- /t/01-window-update.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | our $http_config = << 'EOC'; 4 | lua_package_path "lib/?.lua;;"; 5 | 6 | server { 7 | listen 8083 http2; 8 | http2_body_preread_size 256; 9 | 10 | location = /t1 { 11 | lua_need_request_body on; 12 | content_by_lua_block { 13 | ngx.status = 200 14 | return ngx.exit(200) 15 | } 16 | } 17 | 18 | location = /t2 { 19 | content_by_lua_block { 20 | local data = {} 21 | for i = 1, 1024 do 22 | data[i] = string.char(math.random(97, 122)) 23 | end 24 | 25 | data = table.concat(data) 26 | 27 | for i = 1, 1024 do 28 | ngx.print(data) 29 | 30 | if i % 10 == 0 then 31 | ngx.sleep(0.001) 32 | end 33 | end 34 | } 35 | } 36 | } 37 | EOC 38 | 39 | 40 | repeat_each(3); 41 | plan tests => repeat_each() * blocks() * 3; 42 | no_long_string(); 43 | run_tests(); 44 | 45 | __DATA__ 46 | 47 | === TEST 1: POST request with bulk request body (server sends WINDOW_UPDATE) 48 | 49 | --- http_config eval: $::http_config 50 | --- config 51 | location = /t { 52 | content_by_lua_block { 53 | local http2 = require "resty.http2" 54 | local headers = { 55 | { name = ":authority", value = "test.com" }, 56 | { name = ":method", value = "GET" }, 57 | { name = ":path", value = "/t1" }, 58 | { name = ":scheme", value = "http" }, 59 | { name = "accept-encoding", value = "deflate, gzip" }, 60 | { name = "content-length", value = "2048" }, 61 | } 62 | 63 | local t = {} 64 | for i = 1, 2048 do 65 | t[i] = string.char(math.random(48, 120)) 66 | end 67 | 68 | local data = table.concat(t) 69 | 70 | local on_headers_reach = function(ctx, headers) 71 | assert(headers[":status"] == "200") 72 | local length = headers["content-length"] 73 | assert(not length or length == "0") 74 | 75 | return true 76 | end 77 | 78 | local on_data_reach = function(ctx, data) 79 | error("unexpected data") 80 | end 81 | 82 | local sock = ngx.socket.tcp() 83 | local ok, err = sock:connect("127.0.0.1", 8083) 84 | if not ok then 85 | ngx.log(ngx.ERR, err) 86 | return 87 | end 88 | 89 | local client, err = http2.new { 90 | ctx = sock, 91 | recv = sock.receive, 92 | send = sock.send, 93 | preread_size = 1024, 94 | } 95 | 96 | if not client then 97 | ngx.log(ngx.ERR, err) 98 | return 99 | end 100 | 101 | local ok, err = client:request(headers, data, on_headers_reach, 102 | on_data_reach) 103 | if not ok then 104 | ngx.log(ngx.ERR, err) 105 | return 106 | end 107 | 108 | local ok, err = sock:close() 109 | if not ok then 110 | ngx.log(ngx.ERR, err) 111 | return 112 | end 113 | 114 | ngx.print("OK") 115 | } 116 | } 117 | 118 | --- request 119 | GET /t 120 | 121 | --- response_body: OK 122 | --- no_error_log 123 | [error] 124 | 125 | 126 | 127 | === TEST 2: GET request with bulk response body (client sends WINDOW_UPDATE) 128 | 129 | --- http_config eval: $::http_config 130 | --- config 131 | location = /t { 132 | content_by_lua_block { 133 | local http2 = require "resty.http2" 134 | local headers = { 135 | { name = ":authority", value = "test.com" }, 136 | { name = ":method", value = "GET" }, 137 | { name = ":path", value = "/t2" }, 138 | { name = ":scheme", value = "http" }, 139 | { name = "accept-encoding", value = "deflate, gzip" }, 140 | } 141 | 142 | local on_headers_reach = function(ctx, headers) 143 | assert(headers[":status"] == "200") 144 | local length = headers["content-length"] 145 | assert(not length) 146 | end 147 | 148 | local data_length = 0 149 | 150 | local on_data_reach = function(ctx, data) 151 | data_length = data_length + #data 152 | end 153 | 154 | local sock = ngx.socket.tcp() 155 | local ok, err = sock:connect("127.0.0.1", 8083) 156 | if not ok then 157 | ngx.log(ngx.ERR, err) 158 | return 159 | end 160 | 161 | local client, err = http2.new { 162 | ctx = sock, 163 | recv = sock.receive, 164 | send = sock.send, 165 | preread_size = 128, 166 | } 167 | 168 | if not client then 169 | ngx.log(ngx.ERR, err) 170 | return 171 | end 172 | 173 | local ok, err = client:request(headers, nil, on_headers_reach, 174 | on_data_reach) 175 | if not ok then 176 | ngx.log(ngx.ERR, err) 177 | return 178 | end 179 | 180 | local ok, err = sock:close() 181 | if not ok then 182 | ngx.log(ngx.ERR, err) 183 | return 184 | end 185 | 186 | assert(data_length == 1024 * 1024) 187 | 188 | ngx.print("OK") 189 | } 190 | } 191 | 192 | --- request 193 | GET /t 194 | 195 | --- response_body: OK 196 | --- no_error_log 197 | [error] 198 | -------------------------------------------------------------------------------- /t/02-exceptions.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | our $http_config = << 'EOC'; 4 | lua_package_path "lib/?.lua;;"; 5 | 6 | server { 7 | listen 8083 http2; 8 | http2_body_preread_size 256; 9 | http2_max_header_size 19k; 10 | 11 | location = /t1 { 12 | lua_need_request_body on; 13 | content_by_lua_block { 14 | ngx.status = 200 15 | return ngx.exit(200) 16 | } 17 | } 18 | } 19 | EOC 20 | 21 | 22 | repeat_each(3); 23 | plan tests => repeat_each() * (blocks() * 3 + 1); 24 | no_long_string(); 25 | run_tests(); 26 | 27 | __DATA__ 28 | 29 | === TEST 1: abnormal request headers 30 | 31 | --- http_config eval: $::http_config 32 | --- config 33 | location = /t { 34 | content_by_lua_block { 35 | local http2 = require "resty.http2" 36 | local headers = { 37 | { name = ":authority", value = "test.com" }, 38 | { name = ":method", value = "get" }, 39 | { name = ":path", value = "/t1" }, 40 | { name = ":scheme", value = "http" }, 41 | { name = "accept-encoding", value = "deflate, gzip" }, 42 | { name = "content-length", value = "2048" }, 43 | } 44 | 45 | local t = {} 46 | for i = 1, 2048 do 47 | t[i] = string.char(math.random(48, 120)) 48 | end 49 | 50 | local data = table.concat(t) 51 | 52 | local on_headers_reach = function(ctx, headers) 53 | end 54 | 55 | local on_data_reach = function(ctx, data) 56 | end 57 | 58 | local sock = ngx.socket.tcp() 59 | local ok, err = sock:connect("127.0.0.1", 8083) 60 | if not ok then 61 | ngx.log(ngx.ERR, err) 62 | return 63 | end 64 | 65 | local client, err = http2.new { 66 | ctx = sock, 67 | recv = sock.receive, 68 | send = sock.send, 69 | preread_size = 1024, 70 | } 71 | 72 | if not client then 73 | ngx.log(ngx.ERR, err) 74 | return 75 | end 76 | 77 | local ok, err = client:request(headers, data, on_headers_reach, 78 | on_data_reach) 79 | assert(ok == nil) 80 | ngx.print(err) 81 | 82 | local ok, err = sock:close() 83 | if not ok then 84 | ngx.log(ngx.ERR, err) 85 | return 86 | end 87 | } 88 | } 89 | 90 | --- request 91 | GET /t 92 | 93 | --- response_body: stream reset 94 | --- grep_error_log: client sent invalid method: "get" 95 | --- grep_error_log_out 96 | client sent invalid method: "get" 97 | --- no_error_log 98 | [error] 99 | 100 | 101 | 102 | === TEST 2: client sent too large headers 103 | 104 | --- http_config eval: $::http_config 105 | --- config 106 | location = /t { 107 | content_by_lua_block { 108 | local http2 = require "resty.http2" 109 | 110 | local cookie = {} 111 | local client, err 112 | 113 | for i = 1, 20000 do 114 | cookie[i] = string.char(math.random(48, 97)) 115 | end 116 | 117 | cookie = table.concat(cookie) 118 | 119 | local headers = { 120 | { name = ":authority", value = "test.com" }, 121 | { name = ":method", value = "GET" }, 122 | { name = ":path", value = "/t2" }, 123 | { name = ":scheme", value = "http" }, 124 | { name = "accept-encoding", value = "deflate, gzip" }, 125 | { name = "cookie", value = cookie }, 126 | } 127 | 128 | local on_headers_reach = function(ctx, headers) 129 | error("unexpected HEADERS frame") 130 | end 131 | 132 | local on_data_reach = function(ctx, data) 133 | error("unexpected DATA frame") 134 | end 135 | 136 | local sock = ngx.socket.tcp() 137 | local ok, err = sock:connect("127.0.0.1", 8083) 138 | if not ok then 139 | ngx.log(ngx.ERR, err) 140 | return 141 | end 142 | 143 | local client, err = http2.new { 144 | ctx = sock, 145 | recv = sock.receive, 146 | send = sock.send, 147 | } 148 | 149 | if not client then 150 | ngx.log(ngx.ERR, err) 151 | return 152 | end 153 | 154 | local ok, err = client:request(headers, nil, on_headers_reach, 155 | on_data_reach) 156 | assert(ok == nil) 157 | assert(err == "connection went away") 158 | ngx.print("OK") 159 | } 160 | } 161 | 162 | --- timeout: 10 163 | --- request 164 | GET /t 165 | 166 | --- response_body: OK 167 | --- no_error_log 168 | [error] 169 | -------------------------------------------------------------------------------- /t/03-frame.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | our $http_config = << 'EOC'; 4 | lua_package_path "lib/?.lua;;"; 5 | 6 | server { 7 | listen 8083 http2; 8 | http2_max_field_size 64k; 9 | http2_max_header_size 64k; 10 | location = /t1 { 11 | return 200; 12 | 13 | header_filter_by_lua_block { 14 | local cookie = {} 15 | local client, err 16 | 17 | for i = 1, 50000 do 18 | cookie[i] = string.char(math.random(48, 97)) 19 | end 20 | 21 | cookie = table.concat(cookie) 22 | 23 | ngx.header["Cookie"] = cookie 24 | } 25 | } 26 | 27 | location = /t2 { 28 | return 200; 29 | 30 | header_filter_by_lua_block { 31 | ngx.header["Cookie"] = ngx.var.http_cookie 32 | } 33 | } 34 | 35 | location = /t3 { 36 | add_trailer header1 value1; 37 | add_trailer header2 value2; 38 | add_header trailer "header1,header2"; 39 | 40 | content_by_lua_block { 41 | ngx.say("hello world") 42 | } 43 | } 44 | } 45 | EOC 46 | 47 | 48 | repeat_each(3); 49 | plan tests => repeat_each() * blocks() * 3; 50 | no_long_string(); 51 | run_tests(); 52 | 53 | __DATA__ 54 | 55 | === TEST 1: CONTINUATION frame 56 | 57 | --- http_config eval: $::http_config 58 | --- config 59 | location = /t { 60 | content_by_lua_block { 61 | local http2 = require "resty.http2" 62 | local headers = { 63 | { name = ":authority", value = "test.com" }, 64 | { name = ":method", value = "GET" }, 65 | { name = ":path", value = "/t1" }, 66 | { name = ":scheme", value = "http" }, 67 | { name = "accept-encoding", value = "deflate, gzip" }, 68 | } 69 | 70 | local on_headers_reach = function(ctx, headers) 71 | assert(#headers["cookie"] == 50000) 72 | end 73 | 74 | local on_data_reach = function(ctx, data) 75 | if #data > 0 then 76 | error("unexpected DATA frame") 77 | end 78 | end 79 | 80 | local sock = ngx.socket.tcp() 81 | local ok, err = sock:connect("127.0.0.1", 8083) 82 | if not ok then 83 | ngx.log(ngx.ERR, err) 84 | return 85 | end 86 | 87 | local client, err = http2.new { 88 | ctx = sock, 89 | recv = sock.receive, 90 | send = sock.send, 91 | preread_size = 1024, 92 | max_frame_size = 16385, 93 | } 94 | 95 | if not client then 96 | ngx.log(ngx.ERR, err) 97 | return 98 | end 99 | 100 | local ok, err = client:request(headers, nil, on_headers_reach, 101 | on_data_reach) 102 | 103 | if not ok then 104 | ngx.log(ngx.ERR, err) 105 | return 106 | end 107 | 108 | local ok, err = sock:close() 109 | if not ok then 110 | ngx.log(ngx.ERR, err) 111 | return 112 | end 113 | 114 | ngx.print("OK") 115 | } 116 | } 117 | 118 | --- request 119 | GET /t 120 | 121 | --- response_body: OK 122 | --- no_error_log 123 | [error] 124 | 125 | 126 | === TEST 2: client sent CONTINUATION frame 127 | 128 | --- http_config eval: $::http_config 129 | --- config 130 | location = /t { 131 | content_by_lua_block { 132 | local http2 = require "resty.http2" 133 | 134 | local cookie = {} 135 | local client, err 136 | 137 | for i = 1, 20000 do 138 | cookie[i] = string.char(math.random(48, 97)) 139 | end 140 | 141 | cookie = table.concat(cookie) 142 | 143 | local headers = { 144 | { name = ":authority", value = "test.com" }, 145 | { name = ":method", value = "GET" }, 146 | { name = ":path", value = "/t2" }, 147 | { name = ":scheme", value = "http" }, 148 | { name = "accept-encoding", value = "deflate, gzip" }, 149 | { name = "cookie", value = cookie }, 150 | } 151 | 152 | local on_headers_reach = function(ctx, headers) 153 | assert(headers["cookie"] == cookie) 154 | end 155 | 156 | local on_data_reach = function(ctx, data) 157 | if #data > 0 then 158 | error("unexpected DATA frame") 159 | end 160 | end 161 | 162 | local sock = ngx.socket.tcp() 163 | local ok, err = sock:connect("127.0.0.1", 8083) 164 | if not ok then 165 | ngx.log(ngx.ERR, err) 166 | return 167 | end 168 | 169 | client, err = http2.new { 170 | ctx = sock, 171 | recv = sock.receive, 172 | send = sock.send, 173 | preread_size = 1024, 174 | } 175 | 176 | if not client then 177 | ngx.log(ngx.ERR, err) 178 | return 179 | end 180 | 181 | client.session.max_frame_size = 16384 182 | 183 | local ok, err = client:request(headers, nil, on_headers_reach, 184 | on_data_reach) 185 | 186 | if not ok then 187 | ngx.log(ngx.ERR, err) 188 | return 189 | end 190 | 191 | local ok, err = sock:close() 192 | if not ok then 193 | ngx.log(ngx.ERR, err) 194 | return 195 | end 196 | 197 | ngx.print("OK") 198 | } 199 | } 200 | 201 | --- request 202 | GET /t 203 | 204 | --- response_body: OK 205 | --- no_error_log 206 | [error] 207 | 208 | 209 | 210 | === TEST 3: request with PING frame 211 | 212 | --- http_config eval: $::http_config 213 | --- config 214 | location = /t { 215 | content_by_lua_block { 216 | local http2 = require "resty.http2" 217 | local h2_frame = require "resty.http2.frame" 218 | 219 | local headers = { 220 | { name = ":authority", value = "test.com" }, 221 | { name = ":method", value = "GET" }, 222 | { name = ":path", value = "/t2" }, 223 | { name = ":scheme", value = "http" }, 224 | } 225 | 226 | local on_headers_reach = function() 227 | end 228 | 229 | local on_data_reach = function(ctx, data) 230 | if #data > 0 then 231 | error("unexpected DATA frame") 232 | end 233 | end 234 | 235 | local sock = ngx.socket.tcp() 236 | local ok, err = sock:connect("127.0.0.1", 8083) 237 | if not ok then 238 | ngx.log(ngx.ERR, err) 239 | return 240 | end 241 | 242 | client, err = http2.new { 243 | ctx = sock, 244 | recv = sock.receive, 245 | send = sock.send, 246 | preread_size = 1024, 247 | } 248 | 249 | if not client then 250 | ngx.log(ngx.ERR, err) 251 | return 252 | end 253 | 254 | local ok, err = client:request(headers, nil, on_headers_reach, 255 | on_data_reach) 256 | if not ok then 257 | ngx.log(ngx.ERR, err) 258 | return 259 | end 260 | 261 | local session = client.session 262 | local hd = h2_frame.header.new(8, h2_frame.PING_FRAME, 0, 0) 263 | 264 | local frame = { 265 | next = nil, 266 | header = hd, 267 | opaque_data_hi = 1234, 268 | opaque_data_lo = 5678, 269 | } 270 | 271 | session:frame_queue(frame) 272 | local ok, err = session:flush_queue() 273 | if not ok then 274 | ngx.log(ngx.ERR, err) 275 | return 276 | end 277 | 278 | local frame, err = session:recv_frame() 279 | if not frame then 280 | ngx.log(ngx.ERR, err) 281 | return 282 | end 283 | 284 | assert(frame.header.type == h2_frame.PING_FRAME) 285 | assert(frame.header.length == 8) 286 | assert(frame.header.flag_ack == true) 287 | assert(frame.opaque_data_hi == 1234) 288 | assert(frame.opaque_data_lo == 5678) 289 | 290 | local ok, err = sock:close() 291 | if not ok then 292 | ngx.log(ngx.ERR, err) 293 | return 294 | end 295 | 296 | ngx.print("OK") 297 | } 298 | } 299 | 300 | --- request 301 | GET /t 302 | 303 | --- response_body: OK 304 | --- no_error_log 305 | [error] 306 | 307 | 308 | === TEST 4: trailers frame 309 | 310 | --- http_config eval: $::http_config 311 | --- config 312 | location = /t { 313 | content_by_lua_block { 314 | local http2 = require "resty.http2" 315 | local h2_frame = require "resty.http2.frame" 316 | 317 | local headers = { 318 | { name = ":authority", value = "test.com" }, 319 | { name = ":method", value = "GET" }, 320 | { name = ":path", value = "/t3" }, 321 | { name = ":scheme", value = "http" }, 322 | } 323 | 324 | local on_headers_reach = function(ctx, headers) 325 | if headers["trailer"] ~= "header1,header2" then 326 | error("unexpected trailer header value") 327 | end 328 | end 329 | 330 | local on_data_reach = function(ctx, data) 331 | if data ~= "hello world\n" then 332 | error("unexpected data value") 333 | end 334 | end 335 | 336 | local on_trailers_reach = function(ctx, trailers) 337 | if trailers["header1"] ~= "value1" 338 | or trailers["header2"] ~= "value2" 339 | then 340 | error("unexpected trailers") 341 | end 342 | end 343 | 344 | local sock = ngx.socket.tcp() 345 | local ok, err = sock:connect("127.0.0.1", 8083) 346 | if not ok then 347 | ngx.log(ngx.ERR, err) 348 | return 349 | end 350 | 351 | client, err = http2.new { 352 | ctx = sock, 353 | recv = sock.receive, 354 | send = sock.send, 355 | preread_size = 1024, 356 | } 357 | 358 | if not client then 359 | ngx.log(ngx.ERR, err) 360 | return 361 | end 362 | 363 | local ok, err = client:request(headers, nil, on_headers_reach, 364 | on_data_reach, on_trailers_reach) 365 | if not ok then 366 | ngx.log(ngx.ERR, err) 367 | return 368 | end 369 | 370 | local ok, err = sock:close() 371 | if not ok then 372 | ngx.log(ngx.ERR, err) 373 | return 374 | end 375 | 376 | ngx.print("OK") 377 | } 378 | } 379 | 380 | --- request 381 | GET /t 382 | 383 | --- response_body: OK 384 | --- no_error_log 385 | [error] 386 | -------------------------------------------------------------------------------- /t/unit/test_hpack.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | package.path = "./lib/?.lua;;" 4 | 5 | local hpack = require "resty.http2.hpack" 6 | 7 | local assert = assert 8 | local char = string.char 9 | local rep = string.rep 10 | 11 | local hstate = hpack.new(hpack.MAX_TABLE_SIZE) 12 | 13 | assert(char(128 + 55) == hpack.indexed(55), "bad indexed value") 14 | assert(char(64 + 13) == hpack.incr_indexed(13), "bad incr indexed value") 15 | 16 | local headers = { 17 | { name = "header_A", value = "value_A" }, 18 | { name = "header_B", value = "value_B" }, 19 | { name = "header_C", value = "value_C" }, 20 | { name = "header_D", value = "value_D" }, 21 | { name = "header_E", value = "value_E" }, 22 | { name = "header_F", value = "value_F" }, 23 | } 24 | 25 | for i = 1, #headers do 26 | local name = headers[i].name 27 | local value = headers[i].value 28 | 29 | assert(hstate:insert_entry(name, value), "failed to insert entry") 30 | end 31 | 32 | local dynamic = hstate.dynamic 33 | 34 | assert(dynamic.front == 1, "bad front value " .. dynamic.front) 35 | assert(dynamic.back == 6, "bad back value " .. dynamic.back) 36 | assert(dynamic.free == 4096 - 47 * 6, "bad free value " .. dynamic.free) 37 | 38 | assert(hstate:get_indexed_header(0) == nil) 39 | local entry = hstate:get_indexed_header(2) 40 | assert(entry.name == ":method") 41 | assert(entry.value == "GET") 42 | 43 | entry = hstate:get_indexed_header(63) 44 | assert(entry.name == "header_E") 45 | assert(entry.value == "value_E") 46 | 47 | assert(hstate:get_indexed_header(70) == nil) 48 | 49 | -- resize the dynamic table, only two entries can be retained 50 | hstate:resize(100) 51 | 52 | assert(dynamic.front == 5, "bad front value " .. dynamic.front) 53 | assert(dynamic.back == 6, "bad back value " .. dynamic.back) 54 | assert(dynamic.size == 100, "bad size value " .. dynamic.size) 55 | 56 | local name = "header_G" 57 | local value = "value_G" 58 | 59 | -- evict a entry 60 | assert(hstate:insert_entry(name, value), "failed to insert entry") 61 | assert(dynamic.front == 6, "bad front value " .. dynamic.front) 62 | assert(dynamic.back == 7, "bad back value " .. dynamic.back) 63 | 64 | -- more evictions 65 | for i = 1, 57 do 66 | local name = "header_" .. i 67 | local value = "value_" .. i 68 | assert(hstate:insert_entry(name, value), "failed to insert entry") 69 | end 70 | 71 | assert(dynamic.front == 63, "bad front value " .. dynamic.front) 72 | assert(dynamic.back == 64, "bad back value " .. dynamic.back) 73 | 74 | local name = "header_H" 75 | local value = "value_H" 76 | 77 | -- pointers go back 78 | assert(hstate:insert_entry(name, value), "failed to insert entry") 79 | assert(dynamic.front == 64, "bad front value " .. dynamic.front) 80 | assert(dynamic.back == 1, "bad back value " .. dynamic.back) 81 | 82 | entry = hstate:get_indexed_header(63) 83 | assert(entry.name == "header_57") 84 | assert(entry.value == "value_57") 85 | 86 | entry = hstate:get_indexed_header(62) 87 | assert(entry.name == "header_H") 88 | assert(entry.value == "value_H") 89 | assert(hstate:get_indexed_header(64) == nil) 90 | 91 | local name = rep("HEADER_I", 5) 92 | local value = rep("HEADER_I", 5) 93 | 94 | -- too large, all items should be evicted 95 | assert(hstate:insert_entry(name, value) == false, "should be failed!") 96 | assert(dynamic.front == 1, "bad front value " .. dynamic.front) 97 | assert(dynamic.back == 0, "bad back value " .. dynamic.back) 98 | 99 | local name = rep("HEADER_I", 4) 100 | local value = rep("HEADER_I", 4) 101 | 102 | assert(hstate:insert_entry(name, value), "failed to insert entry") 103 | assert(dynamic.front == 1, "bad front value " .. dynamic.front) 104 | assert(dynamic.back == 1, "bad back value " .. dynamic.back) 105 | 106 | hstate:resize(3400) 107 | assert(dynamic.front == 1, "bad front value " .. dynamic.front) 108 | assert(dynamic.back == 1, "bad back value " .. dynamic.back) 109 | assert(dynamic.free == 3400 - #name - #value - 32, "incorrect free size") 110 | 111 | local free = dynamic.free 112 | for i = 1, 63 do 113 | local name = "header_" .. i 114 | local value = "value_" .. i 115 | free = free - #name - #value - 32 116 | assert(hstate:insert_entry(name, value), "failed to insert entry") 117 | end 118 | 119 | assert(dynamic.front == 1, "bad front value " .. dynamic.front) 120 | assert(dynamic.back == 64, "bad back value " .. dynamic.back) 121 | assert(dynamic.free == free, "incorrect free size") 122 | 123 | local name = "header_" .. 64 124 | local value = "value_" .. 64 125 | assert(hstate:insert_entry(name, value), "failed to insert entry") 126 | 127 | assert(dynamic.front == 1, "bad front value " .. dynamic.front) 128 | assert(dynamic.back == 65, "bad back value " .. dynamic.back) 129 | -------------------------------------------------------------------------------- /t/unit/test_huffman.lua: -------------------------------------------------------------------------------- 1 | -- Copyright Alex Zhang (tokers) 2 | 3 | package.path = "./lib/?.lua;;" 4 | 5 | local huffenc = require "resty.http2.huff_encode" 6 | local huffdec = require "resty.http2.huff_decode" 7 | 8 | local assert = assert 9 | local concat = table.concat 10 | local char = string.char 11 | 12 | local function test(str, lower) 13 | local out2 = {} 14 | local dec_state = huffdec.new_state() 15 | local out1 = huffenc.encode(str, lower) 16 | if not out1 then 17 | return 18 | end 19 | 20 | local data = concat(out1) 21 | 22 | local ok, err = dec_state:decode(data, out2, true) 23 | 24 | if lower then 25 | str = str:lower() 26 | end 27 | 28 | assert(ok, err) 29 | assert(str == concat(out2), "bad decoded result, \"" .. str .. 30 | "\" is expected but got \"" .. concat(out2) .. 31 | "\"") 32 | end 33 | 34 | 35 | test("hello,", false) 36 | test("hello, world", true) 37 | test("hello, 你好世界", false) 38 | test("HELLO, 你好世界", true) 39 | test("abcABC", true) 40 | test("!@#~$%^&*()_+{}:\"01234567890abcdefghijklmnopgrstuvwxyzABCDEFGHIJKLMNOPGRSTUVWXYZ", false) 41 | test("!@#~$%^&*()_+{}:\"01234567890abcdefghijklmnopgrstuvwxyzABCDEFGHIJKLMNOPGRSTUVWXYZ", true) 42 | 43 | local mid = {} 44 | for i = 0, 255 do 45 | mid[#mid + 1] = char(i) 46 | end 47 | 48 | test(concat(mid), false) 49 | test(concat(mid), true) 50 | -------------------------------------------------------------------------------- /util/example.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Alex Zhang 2 | 3 | package.path = "./lib/?.lua;;" 4 | 5 | local http2 = require "resty.http2" 6 | 7 | local arg = arg 8 | local exit = os.exit 9 | local error = error 10 | local print = print 11 | local pairs = pairs 12 | 13 | local host = arg[1] 14 | local port = tonumber(arg[2]) 15 | 16 | if not host or not port then 17 | error("invalid host or port") 18 | end 19 | 20 | local sock = ngx.socket.tcp() 21 | 22 | local ok, err = sock:connect(host, port) 23 | if not ok then 24 | print("failed to connect ", host, ":", port, ": ", err) 25 | exit(1) 26 | end 27 | 28 | local headers = { 29 | { name = ":authority", value = "tokers.com" }, 30 | { name = ":method", value = "GET" }, 31 | { name = ":path", value = "/index.html" }, 32 | { name = ":scheme", value = "http" }, 33 | { name = "accept-encoding", value = "gzip" }, 34 | { name = "user-agent", value = "example/client" }, 35 | } 36 | 37 | 38 | local on_headers_reach = function(ctx, headers) 39 | print("received HEADERS frame:") 40 | for k, v in pairs(headers) do 41 | print(k, ": ", v) 42 | end 43 | end 44 | 45 | local on_data_reach = function(ctx, data) 46 | print("received DATA frame:") 47 | print(data) 48 | end 49 | 50 | local on_trailers_reach = function(ctx, data) 51 | print("received HEADERS frame for trailer headers:") 52 | for k, v in pairs(headers) do 53 | print(k, ": ", v) 54 | end 55 | end 56 | 57 | local opts = { 58 | ctx = sock, 59 | recv = sock.receive, 60 | send = sock.send, 61 | preread_size = 1024, 62 | max_concurrent_stream = 100, 63 | } 64 | 65 | local client, err = http2.new(opts) 66 | if not client then 67 | print("failed to create HTTP/2 client: ", err) 68 | exit(1) 69 | end 70 | 71 | local ok, err = client:request(headers, nil, on_headers_reach, 72 | on_data_reach, on_trailers_reach) 73 | if not ok then 74 | print("client:request() failed: ", err) 75 | exit(1) 76 | end 77 | 78 | 79 | sock:close() 80 | -------------------------------------------------------------------------------- /util/lua-nginx-module-0.10.13-ssl-alpn.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c 2 | index 0717c46d..27735639 100644 3 | --- a/src/ngx_http_lua_socket_tcp.c 4 | +++ b/src/ngx_http_lua_socket_tcp.c 5 | @@ -1276,8 +1276,8 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) 6 | [,send_status_req] */ 7 | 8 | n = lua_gettop(L); 9 | - if (n < 1 || n > 5) { 10 | - return luaL_error(L, "ngx.socket sslhandshake: expecting 1 ~ 5 " 11 | + if (n < 1 || n > 6) { 12 | + return luaL_error(L, "ngx.socket sslhandshake: expecting 1 ~ 6 " 13 | "arguments (including the object), but seen %d", n); 14 | } 15 | 16 | @@ -1421,6 +1421,44 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) 17 | return luaL_error(L, "no OCSP support"); 18 | #endif 19 | } 20 | + 21 | + if (n >= 6) { 22 | +#if (defined TLSEXT_TYPE_application_layer_protocol_negotiation) 23 | + size_t len; 24 | + const char *data; 25 | + unsigned char *list; 26 | + 27 | + data = lua_tolstring(L, 6, &len); 28 | + if (len > 255) { 29 | + lua_pushnil(L); 30 | + lua_pushliteral(L, "too large next protocol list"); 31 | + return 2; 32 | + } 33 | + 34 | + list = ngx_palloc(r->pool, len + 1); 35 | + if (list == NULL) { 36 | + lua_pushnil(L); 37 | + lua_pushliteral(L, "no memory"); 38 | + return 2; 39 | + } 40 | + 41 | + list[0] = (char) len; 42 | + ngx_memcpy(list + 1, data, len); 43 | + 44 | + if (SSL_set_alpn_protos(c->ssl->connection, 45 | + (const unsigned char *) list, 46 | + (unsigned int) (len + 1))) 47 | + { 48 | + lua_pushnil(L); 49 | + lua_pushliteral(L, "SSL_set_alpn_protos failed"); 50 | + return 2; 51 | + } 52 | + 53 | + u->ssl_ext_upgrade = 1; 54 | +#else 55 | + return luaL_error(L, "no ALPN or NPN support"); 56 | +#endif 57 | + } 58 | } 59 | } 60 | } 61 | @@ -1649,6 +1687,19 @@ ngx_http_lua_ssl_handshake_retval_handler(ngx_http_request_t *r, 62 | lua_pushlightuserdata(L, &ngx_http_lua_ssl_session_metatable_key); 63 | lua_rawget(L, LUA_REGISTRYINDEX); 64 | lua_setmetatable(L, -2); 65 | + 66 | + if (u->ssl_ext_upgrade) { 67 | +#if (defined TLSEXT_TYPE_application_layer_protocol_negotiation) 68 | + unsigned int len; 69 | + const unsigned char *data; 70 | + 71 | + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); 72 | + 73 | + lua_pushlstring(L, (const char *) data, (size_t) len); 74 | + 75 | + return 2; 76 | +#endif 77 | + } 78 | } 79 | 80 | return 1; 81 | diff --git a/src/ngx_http_lua_socket_tcp.h b/src/ngx_http_lua_socket_tcp.h 82 | index dbdee41c..eb6fc434 100644 83 | --- a/src/ngx_http_lua_socket_tcp.h 84 | +++ b/src/ngx_http_lua_socket_tcp.h 85 | @@ -107,6 +107,7 @@ struct ngx_http_lua_socket_tcp_upstream_s { 86 | #if (NGX_HTTP_SSL) 87 | unsigned ssl_verify:1; 88 | unsigned ssl_session_reuse:1; 89 | + unsigned ssl_ext_upgrade:1; 90 | #endif 91 | }; 92 | 93 | -------------------------------------------------------------------------------- /util/lua-releng: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | sub file_contains ($$); 7 | 8 | my $version; 9 | for my $file (map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/*.lua lib/*/*/*/*/*.lua }) { 10 | # Check the sanity of each .lua file 11 | open my $in, $file or 12 | die "ERROR: Can't open $file for reading: $!\n"; 13 | my $found_ver; 14 | while (<$in>) { 15 | my ($ver, $skipping); 16 | if (/(?x) (?:_VERSION) \s* = .*? ([\d\.]*\d+) (.*? SKIP)?/) { 17 | my $orig_ver = $ver = $1; 18 | $found_ver = 1; 19 | # $skipping = $2; 20 | $ver =~ s{^(\d+)\.(\d{3})(\d{3})$}{join '.', int($1), int($2), int($3)}e; 21 | warn "$file: $orig_ver ($ver)\n"; 22 | } elsif (/(?x) (?:_VERSION) \s* = \s* ([a-zA-Z_]\S*)/) { 23 | warn "$file: $1\n"; 24 | $found_ver = 1; 25 | last; 26 | } 27 | if ($ver and $version and !$skipping) { 28 | if ($version ne $ver) { 29 | # die "$file: $ver != $version\n"; 30 | } 31 | } elsif ($ver and !$version) { 32 | $version = $ver; 33 | } 34 | } 35 | if (!$found_ver) { 36 | warn "WARNING: No \"_VERSION\" or \"version\" field found in `$file`.\n"; 37 | } 38 | close $in; 39 | print "Checking use of Lua global variables in file $file ...\n"; 40 | my $output = `luac -p -l $file | grep ETGLOBAL | grep -vE 'require|type|tostring|error|ngx\$|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|select|rawset|rawget|debug'`; 41 | if ($output) { 42 | print $output; 43 | exit 1; 44 | } 45 | #file_contains($file, "attempt to write to undeclared variable"); 46 | system("grep -H -n -E --color '.{120}' $file"); 47 | } 48 | 49 | sub file_contains ($$) { 50 | my ($file, $regex) = @_; 51 | open my $in, $file 52 | or die "Cannot open $file fo reading: $!\n"; 53 | my $content = do { local $/; <$in> }; 54 | close $in; 55 | #print "$content"; 56 | return scalar ($content =~ /$regex/); 57 | } 58 | 59 | if (-d 't') { 60 | for my $file (map glob, qw{ t/*.t t/*/*.t t/*/*/*.t }) { 61 | system(qq{grep -H -n --color -E '\\--- ?(ONLY|LAST)' $file}); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /util/mkhuffdectbl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env resty 2 | 3 | -- Copright Alex Zhang (tokers) 4 | 5 | local bit = require "bit" 6 | 7 | local bor = bit.bor 8 | local band = bit.band 9 | local brshift = bit.rshift 10 | local blshift = bit.lshift 11 | local rep = string.rep 12 | local format = string.format 13 | local insert = table.insert 14 | local setmetatable = setmetatable 15 | 16 | local EOS = 256 17 | local huffnode = {} 18 | local hufftree = {} 19 | local mt = { __index = hufftree } 20 | 21 | -- http://http2.github.io/http2-spec/compression.html#rfc.section.B 22 | local huff_encode_table = { 23 | {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, 24 | {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, 25 | {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28}, 26 | {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28}, 27 | {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28}, 28 | {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28}, 29 | {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28}, 30 | {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28}, 31 | {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12}, 32 | {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11}, 33 | {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11}, 34 | {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6}, 35 | {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6}, 36 | {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6}, 37 | {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8}, 38 | {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10}, 39 | {0x00001ffa, 13}, {0x00000021, 6}, {0x0000005d, 7}, {0x0000005e, 7}, 40 | {0x0000005f, 7}, {0x00000060, 7}, {0x00000061, 7}, {0x00000062, 7}, 41 | {0x00000063, 7}, {0x00000064, 7}, {0x00000065, 7}, {0x00000066, 7}, 42 | {0x00000067, 7}, {0x00000068, 7}, {0x00000069, 7}, {0x0000006a, 7}, 43 | {0x0000006b, 7}, {0x0000006c, 7}, {0x0000006d, 7}, {0x0000006e, 7}, 44 | {0x0000006f, 7}, {0x00000070, 7}, {0x00000071, 7}, {0x00000072, 7}, 45 | {0x000000fc, 8}, {0x00000073, 7}, {0x000000fd, 8}, {0x00001ffb, 13}, 46 | {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6}, 47 | {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, 48 | {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, 49 | {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, 50 | {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, 51 | {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, 52 | {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, 53 | {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15}, 54 | {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28}, 55 | {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20}, 56 | {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23}, 57 | {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23}, 58 | {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23}, 59 | {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23}, 60 | {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23}, 61 | {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23}, 62 | {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24}, 63 | {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22}, 64 | {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21}, 65 | {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24}, 66 | {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23}, 67 | {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21}, 68 | {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23}, 69 | {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22}, 70 | {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23}, 71 | {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19}, 72 | {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25}, 73 | {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27}, 74 | {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25}, 75 | {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27}, 76 | {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24}, 77 | {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26}, 78 | {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27}, 79 | {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21}, 80 | {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23}, 81 | {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25}, 82 | {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23}, 83 | {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26}, 84 | {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27}, 85 | {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27}, 86 | {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26}, 87 | 88 | -- EOS 89 | {0x3fffffff, 30}, 90 | } 91 | 92 | 93 | function huffnode.new() 94 | return { 95 | left = nil, 96 | right = nil, 97 | id = nil, 98 | symbol = nil, 99 | ending = false, 100 | shift = {}, 101 | } 102 | end 103 | 104 | 105 | function hufftree:insert(symbol, code, len) 106 | if self.root == nil then 107 | self.root = huffnode:new() 108 | end 109 | 110 | local node = self.root 111 | 112 | for i = len - 1, 0, -1 do 113 | local child 114 | if band(brshift(code, i), 0x1) ~= 0 then 115 | if node.right == nil then 116 | node.right = huffnode:new() 117 | end 118 | 119 | child = node.right 120 | else 121 | if node.left == nil then 122 | node.left = huffnode:new() 123 | end 124 | 125 | child = node.left 126 | end 127 | 128 | node = child 129 | end 130 | 131 | -- reach the leaf node 132 | node.symbol = symbol 133 | end 134 | 135 | 136 | function hufftree:assign_id() 137 | local root = self.root 138 | local traverse 139 | 140 | traverse = function(node, prefix, step) 141 | if node.symbol then 142 | node.id = -1 -- ansign a negative id for marking the leaf nodes 143 | return 144 | end 145 | 146 | node.id = self.id 147 | self.id = self.id + 1 148 | 149 | -- the padding path, padding length must be small than 8 150 | if step <= 7 and prefix == blshift(1, step) - 1 then 151 | node.ending = true 152 | end 153 | 154 | traverse(node.left, blshift(prefix, 1), step + 1) 155 | traverse(node.right, bor(blshift(prefix, 1), 1), step + 1) 156 | end 157 | 158 | traverse(root, 0, 0) 159 | end 160 | 161 | 162 | function hufftree:build_step_state() 163 | -- take 4 steps, so there are 16 kinds of possibility 164 | local root = self.root 165 | local iterate 166 | local start 167 | 168 | iterate = function(left, node, from, symbol) 169 | if left == 0 then 170 | if symbol == EOS then -- the EOS state, ofcourse it is invalid 171 | symbol = nil 172 | node = nil 173 | end 174 | 175 | -- the state transition: 176 | -- from the "from" node, takes 4 steps: 177 | -- * reached the "node" node, 178 | -- * got the symbol on the way 179 | insert(from.shift, { symbol, node }) 180 | return 181 | end 182 | 183 | -- encounter the leaf node, go back to the root 184 | if node.symbol then 185 | node = root 186 | end 187 | 188 | iterate(left - 1, node.left, from, node.left.symbol or symbol) 189 | iterate(left - 1, node.right, from, node.right.symbol or symbol) 190 | end 191 | 192 | start = function(node) 193 | if not node then 194 | return 195 | end 196 | 197 | iterate(4, node, node, node.symbol) 198 | start(node.left) 199 | start(node.right) 200 | end 201 | 202 | start(root) 203 | end 204 | 205 | 206 | function hufftree:echo() 207 | local iterate 208 | 209 | print("local huff_decode_codes = {") 210 | 211 | iterate = function(node) 212 | if node.symbol then 213 | return 214 | end 215 | 216 | print(" { -- " .. node.id) 217 | 218 | local process = function(shift) 219 | local symbol = shift[1] 220 | local state = shift[2] 221 | local ending = false 222 | 223 | if not symbol then 224 | symbol = "nil" 225 | else 226 | symbol = format("0x%02x", symbol) 227 | end 228 | 229 | local id 230 | local fail = "false" 231 | 232 | if not state then 233 | id = 0 234 | fail = "true" -- an invalid state 235 | else 236 | id = state.id 237 | 238 | if id == -1 then 239 | id = 0 240 | ending = true 241 | elseif state.ending then 242 | ending = true 243 | end 244 | end 245 | 246 | ending = ending and "true" or "false" 247 | 248 | return format("{0x%02x, %s, %s, %s}", id, fail, symbol, ending) 249 | end 250 | 251 | local shift = node.shift 252 | 253 | for i = 1, #shift, 2 do 254 | local part1 = process(shift[i]) 255 | local part2 = process(shift[i + 1]) 256 | print(format("%s%s, %s,", rep(" ", 8), part1, part2)) 257 | end 258 | 259 | print(" },") 260 | 261 | iterate(node.left) 262 | iterate(node.right) 263 | end 264 | 265 | iterate(self.root) 266 | 267 | print("}") 268 | end 269 | 270 | 271 | function hufftree.new() 272 | return setmetatable({ root = nil, id = 0 }, mt) 273 | end 274 | 275 | 276 | local function gen() 277 | -- build the huffman tree 278 | local tree = hufftree.new() 279 | tree.root = huffnode:new() 280 | 281 | for i = 1, #huff_encode_table do 282 | local code = huff_encode_table[i][1] 283 | local len = huff_encode_table[i][2] 284 | tree:insert(i - 1, code, len) 285 | end 286 | 287 | -- assign an identity for each non-leaf node 288 | tree:assign_id() 289 | 290 | -- build the state 291 | tree:build_step_state() 292 | 293 | -- print the result 294 | tree:echo() 295 | end 296 | 297 | gen() 298 | 299 | -- vi: ft=lua 300 | -------------------------------------------------------------------------------- /util/protocol.markdown: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [FRAME](#frame) 4 | * [FRAME HEADER](#frame-header) 5 | * [PRIORITY](#priority-frame) 6 | * [RST](#rst) 7 | * [SETTINGS](#settings) 8 | * [PING](#ping) 9 | * [GOAWAY](#goaway) 10 | * [WINDOW_UPDATE](#window_update) 11 | * [HEADERS](#headers) 12 | * [RST_STREAM](#rst_stream) 13 | * [DATA](#data) 14 | * [STREAM](#stream) 15 | * [STREAM STATE](#stream-state) 16 | * [HPACK](#hpack) 17 | * [Literal Header Field with Incremental Indexing](#literal-header-field-with-incremental-indexing) 18 | * [Literal Header Field with Incremental Indexing](#literal-header-field-with-incremental-indexing) 19 | * [Indexed Name](#indexed-name) 20 | * [New Name](#new-name) 21 | * [Literal Header Field without Indexing](#literal-header-field-without-indexing) 22 | * [Indexed Name](#indexed-name) 23 | * [New Name](#new-name) 24 | * [Literal Header Field Never Indexed](#literal-header-field-never-indexed) 25 | * [Indexed Name](#indexed-name) 26 | * [New Name](#new-name) 27 | * [Dynamic Table Size Update](#dynamic-table-size-update) 28 | 29 | # FRAME 30 | 31 | ## FRAME HEADER 32 | 33 | ``` 34 | +-----------------------------------------------+ 35 | | Length (24) | 36 | +---------------+---------------+---------------+ 37 | | Type (8) | Flags (8) | 38 | +-+-------------+---------------+---------------+ 39 | |R| Stream Identifier (31) | 40 | +=+=============================================+ 41 | | Frame Payload (0...) ... 42 | +-----------------------------------------------+ 43 | ``` 44 | 45 | ## PRIORITY 46 | 47 | ``` 48 | +-+-------------------------------------------------------------+ 49 | |E| Stream Dependency (31) | 50 | +-+-------------+-----------------------------------------------+ 51 | | Weight (8) | 52 | +-+-------------+ 53 | ``` 54 | 55 | ## RST 56 | ``` 57 | +---------------------------------------------------------------+ 58 | | Error Code (32) | 59 | +---------------------------------------------------------------+ 60 | ``` 61 | 62 | ## SETTINGS 63 | 64 | ``` 65 | +-------------------------------+ 66 | | Identifier (16) | 67 | +-------------------------------+-------------------------------+ 68 | | Value (32) | 69 | +---------------------------------------------------------------+ 70 | ``` 71 | 72 | ## PING 73 | 74 | ``` 75 | +---------------------------------------------------------------+ 76 | | | 77 | | Opaque Data (64) | 78 | | | 79 | +---------------------------------------------------------------+ 80 | ``` 81 | 82 | ## GOAWAY 83 | 84 | ``` 85 | +-+-------------------------------------------------------------+ 86 | |R| Last-Stream-ID (31) | 87 | +-+-------------------------------------------------------------+ 88 | | Error Code (32) | 89 | +---------------------------------------------------------------+ 90 | | Additional Debug Data (*) | 91 | +---------------------------------------------------------------+ 92 | ``` 93 | 94 | ## WINDOW_UPDATE 95 | 96 | ``` 97 | +-+-------------------------------------------------------------+ 98 | |R| Window Size Increment (31) | 99 | +-+-------------------------------------------------------------+ 100 | ``` 101 | 102 | ## HEADERS 103 | 104 | ``` 105 | +---------------+ 106 | |Pad Length? (8)| 107 | +-+-------------+-----------------------------------------------+ 108 | |E| Stream Dependency? (31) | 109 | +-+-------------+-----------------------------------------------+ 110 | | Weight? (8) | 111 | +-+-------------+-----------------------------------------------+ 112 | | Header Block Fragment (*) ... 113 | +---------------------------------------------------------------+ 114 | | Padding (*) ... 115 | +---------------------------------------------------------------+ 116 | ``` 117 | 118 | ## DATA 119 | 120 | ``` 121 | +---------------+ 122 | |Pad Length? (8)| 123 | +---------------+-----------------------------------------------+ 124 | | Data (*) ... 125 | +---------------------------------------------------------------+ 126 | | Padding (*) ... 127 | +---------------------------------------------------------------+ 128 | ``` 129 | 130 | ## RST_STREAM 131 | 132 | ``` 133 | +---------------------------------------------------------------+ 134 | | Error Code (32) | 135 | +---------------------------------------------------------------+ 136 | ``` 137 | 138 | # STREAM 139 | 140 | ## STREAM STATE 141 | 142 | ``` 143 | +--------+ 144 | send PP | | recv PP 145 | ,---------| idle |---------. 146 | / | | \ 147 | v +--------+ v 148 | +----------+ | +----------+ 149 | | | | send H / | | 150 | ,-------| reserved | | recv H | reserved |------. 151 | | | (local) | | | (remote) | | 152 | | +----------+ v +----------+ | 153 | | | +--------+ | | 154 | | | recv ES | | send ES | | 155 | | send H | ,-------| open |-------. | recv H | 156 | | | / | | \ | | 157 | | v v +--------+ v v | 158 | | +----------+ | +----------+ | 159 | | | half | | | half | | 160 | | | closed | | send R / | closed | | 161 | | | (remote) | | recv R | (local) | | 162 | | +----------+ | +----------+ | 163 | | | | | | 164 | | | send ES / | recv ES / | | 165 | | | send R / v send R / | | 166 | | | recv R +--------+ recv R | | 167 | |send R /‘--------------->| |<-------------’ send R /| 168 | |recv R | closed | recv R | 169 | ‘------------------------>| |<-----------------------’ 170 | +--------+ 171 | ``` 172 | 173 | * send: endpoint sends this frame 174 | * recv: endpoint receives this frame 175 | * H: HEADERS frame (with implied CONTINUATIONs) 176 | * PP: PUSH_PROMISE frame (with implied CONTINUATIONs) 177 | * ES: END_STREAM flag 178 | * R: RST_STREAM frame 179 | 180 | # HPACK 181 | 182 | ## Indexed Header Field Representation 183 | 184 | ``` 185 | 0 1 2 3 4 5 6 7 186 | +---+---+---+---+---+---+---+---+ 187 | | 1 | Index (7+) | 188 | +---+---------------------------+ 189 | ``` 190 | 191 | ## Literal Header Field with Incremental Indexing 192 | 193 | ### Indexed Name 194 | 195 | ``` 196 | 0 1 2 3 4 5 6 7 197 | +---+---+---+---+---+---+---+---+ 198 | | 0 | 1 | Index (6+) | 199 | +---+---+-----------------------+ 200 | | H | Value Length (7+) | 201 | +---+---------------------------+ 202 | | Value String (Length octets) | 203 | +-------------------------------+ 204 | ``` 205 | 206 | ### New Name 207 | 208 | ``` 209 | 0 1 2 3 4 5 6 7 210 | +---+---+---+---+---+---+---+---+ 211 | | 0 | 1 | 0 | 212 | +---+---+-----------------------+ 213 | | H | Name Length (7+) | 214 | +---+---------------------------+ 215 | | Name String (Length octets) | 216 | +---+---------------------------+ 217 | | H | Value Length (7+) | 218 | +---+---------------------------+ 219 | | Value String (Length octets) | 220 | +-------------------------------+ 221 | ``` 222 | 223 | ## Literal Header Field without Indexing 224 | 225 | ### Indexed Name 226 | 227 | ``` 228 | 0 1 2 3 4 5 6 7 229 | +---+---+---+---+---+---+---+---+ 230 | | 0 | 0 | 0 | 0 | Index (4+) | 231 | +---+---+-----------------------+ 232 | | H | Value Length (7+) | 233 | +---+---------------------------+ 234 | | Value String (Length octets) | 235 | +-------------------------------+ 236 | ``` 237 | 238 | ### New Name 239 | 240 | ``` 241 | 0 1 2 3 4 5 6 7 242 | +---+---+---+---+---+---+---+---+ 243 | | 0 | 0 | 0 | 0 | 0 | 244 | +---+---+-----------------------+ 245 | | H | Name Length (7+) | 246 | +---+---------------------------+ 247 | | Name String (Length octets) | 248 | +---+---------------------------+ 249 | | H | Value Length (7+) | 250 | +---+---------------------------+ 251 | | Value String (Length octets) | 252 | +-------------------------------+ 253 | ``` 254 | 255 | ## Literal Header Field Never Indexed 256 | 257 | ### Indexed Name 258 | 259 | ``` 260 | 0 1 2 3 4 5 6 7 261 | +---+---+---+---+---+---+---+---+ 262 | | 0 | 0 | 0 | 1 | Index (4+) | 263 | +---+---+-----------------------+ 264 | | H | Value Length (7+) | 265 | +---+---------------------------+ 266 | | Value String (Length octets) | 267 | +-------------------------------+ 268 | ``` 269 | 270 | ### New Name 271 | 272 | ``` 273 | 0 1 2 3 4 5 6 7 274 | +---+---+---+---+---+---+---+---+ 275 | | 0 | 0 | 0 | 1 | 0 | 276 | +---+---+-----------------------+ 277 | | H | Name Length (7+) | 278 | +---+---------------------------+ 279 | | Name String (Length octets) | 280 | +---+---------------------------+ 281 | | H | Value Length (7+) | 282 | +---+---------------------------+ 283 | | Value String (Length octets) | 284 | +-------------------------------+ 285 | ``` 286 | 287 | ## Dynamic Table Size Update 288 | 289 | ``` 290 | 0 1 2 3 4 5 6 7 291 | +---+---+---+---+---+---+---+---+ 292 | | 0 | 0 | 1 | Max size (5+) | 293 | +---+---------------------------+ 294 | ``` 295 | --------------------------------------------------------------------------------