├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── data └── static │ ├── index.html │ ├── lily_images │ ├── lily-0-0.jpg │ ├── lily-0-0.png │ ├── lily-0-1.jpg │ ├── lily-0-1.png │ ├── lily-0-2.jpg │ ├── lily-0-2.png │ ├── lily-0-3.jpg │ ├── lily-0-3.png │ ├── lily-0-4.jpg │ ├── lily-0-4.png │ ├── lily-0-5.jpg │ ├── lily-0-5.png │ ├── lily-0-6.jpg │ ├── lily-0-6.png │ ├── lily-0-7.jpg │ ├── lily-0-7.png │ ├── lily-0-8.jpg │ ├── lily-0-8.png │ ├── lily-0-9.jpg │ ├── lily-0-9.png │ ├── lily-1-0.jpg │ ├── lily-1-0.png │ ├── lily-1-1.jpg │ ├── lily-1-1.png │ ├── lily-1-2.jpg │ ├── lily-1-2.png │ ├── lily-1-3.jpg │ ├── lily-1-3.png │ ├── lily-1-4.jpg │ ├── lily-1-4.png │ ├── lily-1-5.jpg │ ├── lily-1-5.png │ ├── lily-1-6.jpg │ ├── lily-1-6.png │ ├── lily-1-7.jpg │ ├── lily-1-7.png │ ├── lily-1-8.jpg │ ├── lily-1-8.png │ ├── lily-1-9.jpg │ ├── lily-1-9.png │ ├── lily-10-0.jpg │ ├── lily-10-0.png │ ├── lily-10-1.jpg │ ├── lily-10-1.png │ ├── lily-10-2.jpg │ ├── lily-10-2.png │ ├── lily-10-3.jpg │ ├── lily-10-3.png │ ├── lily-10-4.jpg │ ├── lily-10-4.png │ ├── lily-10-5.jpg │ ├── lily-10-5.png │ ├── lily-10-6.jpg │ ├── lily-10-6.png │ ├── lily-10-7.jpg │ ├── lily-10-7.png │ ├── lily-10-8.jpg │ ├── lily-10-8.png │ ├── lily-10-9.jpg │ ├── lily-10-9.png │ ├── lily-11-0.jpg │ ├── lily-11-0.png │ ├── lily-11-1.jpg │ ├── lily-11-1.png │ ├── lily-11-2.jpg │ ├── lily-11-2.png │ ├── lily-11-3.jpg │ ├── lily-11-3.png │ ├── lily-11-4.jpg │ ├── lily-11-4.png │ ├── lily-11-5.jpg │ ├── lily-11-5.png │ ├── lily-11-6.jpg │ ├── lily-11-6.png │ ├── lily-11-7.jpg │ ├── lily-11-7.png │ ├── lily-11-8.jpg │ ├── lily-11-8.png │ ├── lily-11-9.jpg │ ├── lily-11-9.png │ ├── lily-12-0.jpg │ ├── lily-12-0.png │ ├── lily-12-1.jpg │ ├── lily-12-1.png │ ├── lily-12-2.jpg │ ├── lily-12-2.png │ ├── lily-12-3.jpg │ ├── lily-12-3.png │ ├── lily-12-4.jpg │ ├── lily-12-4.png │ ├── lily-12-5.jpg │ ├── lily-12-5.png │ ├── lily-12-6.jpg │ ├── lily-12-6.png │ ├── lily-12-7.jpg │ ├── lily-12-7.png │ ├── lily-12-8.jpg │ ├── lily-12-8.png │ ├── lily-12-9.jpg │ ├── lily-12-9.png │ ├── lily-13-0.jpg │ ├── lily-13-0.png │ ├── lily-13-1.jpg │ ├── lily-13-1.png │ ├── lily-13-2.jpg │ ├── lily-13-2.png │ ├── lily-13-3.jpg │ ├── lily-13-3.png │ ├── lily-13-4.jpg │ ├── lily-13-4.png │ ├── lily-13-5.jpg │ ├── lily-13-5.png │ ├── lily-13-6.jpg │ ├── lily-13-6.png │ ├── lily-13-7.jpg │ ├── lily-13-7.png │ ├── lily-13-8.jpg │ ├── lily-13-8.png │ ├── lily-13-9.jpg │ ├── lily-13-9.png │ ├── lily-14-0.jpg │ ├── lily-14-0.png │ ├── lily-14-1.jpg │ ├── lily-14-1.png │ ├── lily-14-2.jpg │ ├── lily-14-2.png │ ├── lily-14-3.jpg │ ├── lily-14-3.png │ ├── lily-14-4.jpg │ ├── lily-14-4.png │ ├── lily-14-5.jpg │ ├── lily-14-5.png │ ├── lily-14-6.jpg │ ├── lily-14-6.png │ ├── lily-14-7.jpg │ ├── lily-14-7.png │ ├── lily-14-8.jpg │ ├── lily-14-8.png │ ├── lily-14-9.jpg │ ├── lily-14-9.png │ ├── lily-15-0.jpg │ ├── lily-15-0.png │ ├── lily-15-1.jpg │ ├── lily-15-1.png │ ├── lily-15-2.jpg │ ├── lily-15-2.png │ ├── lily-15-3.jpg │ ├── lily-15-3.png │ ├── lily-15-4.jpg │ ├── lily-15-4.png │ ├── lily-15-5.jpg │ ├── lily-15-5.png │ ├── lily-15-6.jpg │ ├── lily-15-6.png │ ├── lily-15-7.jpg │ ├── lily-15-7.png │ ├── lily-15-8.jpg │ ├── lily-15-8.png │ ├── lily-15-9.jpg │ ├── lily-15-9.png │ ├── lily-16-0.jpg │ ├── lily-16-0.png │ ├── lily-16-1.jpg │ ├── lily-16-1.png │ ├── lily-16-2.jpg │ ├── lily-16-2.png │ ├── lily-16-3.jpg │ ├── lily-16-3.png │ ├── lily-16-4.jpg │ ├── lily-16-4.png │ ├── lily-16-5.jpg │ ├── lily-16-5.png │ ├── lily-16-6.jpg │ ├── lily-16-6.png │ ├── lily-16-7.jpg │ ├── lily-16-7.png │ ├── lily-16-8.jpg │ ├── lily-16-8.png │ ├── lily-16-9.jpg │ ├── lily-16-9.png │ ├── lily-17-0.jpg │ ├── lily-17-0.png │ ├── lily-17-1.jpg │ ├── lily-17-1.png │ ├── lily-17-2.jpg │ ├── lily-17-2.png │ ├── lily-17-3.jpg │ ├── lily-17-3.png │ ├── lily-17-4.jpg │ ├── lily-17-4.png │ ├── lily-17-5.jpg │ ├── lily-17-5.png │ ├── lily-17-6.jpg │ ├── lily-17-6.png │ ├── lily-17-7.jpg │ ├── lily-17-7.png │ ├── lily-17-8.jpg │ ├── lily-17-8.png │ ├── lily-17-9.jpg │ ├── lily-17-9.png │ ├── lily-2-0.jpg │ ├── lily-2-0.png │ ├── lily-2-1.jpg │ ├── lily-2-1.png │ ├── lily-2-2.jpg │ ├── lily-2-2.png │ ├── lily-2-3.jpg │ ├── lily-2-3.png │ ├── lily-2-4.jpg │ ├── lily-2-4.png │ ├── lily-2-5.jpg │ ├── lily-2-5.png │ ├── lily-2-6.jpg │ ├── lily-2-6.png │ ├── lily-2-7.jpg │ ├── lily-2-7.png │ ├── lily-2-8.jpg │ ├── lily-2-8.png │ ├── lily-2-9.jpg │ ├── lily-2-9.png │ ├── lily-3-0.jpg │ ├── lily-3-0.png │ ├── lily-3-1.jpg │ ├── lily-3-1.png │ ├── lily-3-2.jpg │ ├── lily-3-2.png │ ├── lily-3-3.jpg │ ├── lily-3-3.png │ ├── lily-3-4.jpg │ ├── lily-3-4.png │ ├── lily-3-5.jpg │ ├── lily-3-5.png │ ├── lily-3-6.jpg │ ├── lily-3-6.png │ ├── lily-3-7.jpg │ ├── lily-3-7.png │ ├── lily-3-8.jpg │ ├── lily-3-8.png │ ├── lily-3-9.jpg │ ├── lily-3-9.png │ ├── lily-4-0.jpg │ ├── lily-4-0.png │ ├── lily-4-1.jpg │ ├── lily-4-1.png │ ├── lily-4-2.jpg │ ├── lily-4-2.png │ ├── lily-4-3.jpg │ ├── lily-4-3.png │ ├── lily-4-4.jpg │ ├── lily-4-4.png │ ├── lily-4-5.jpg │ ├── lily-4-5.png │ ├── lily-4-6.jpg │ ├── lily-4-6.png │ ├── lily-4-7.jpg │ ├── lily-4-7.png │ ├── lily-4-8.jpg │ ├── lily-4-8.png │ ├── lily-4-9.jpg │ ├── lily-4-9.png │ ├── lily-5-0.jpg │ ├── lily-5-0.png │ ├── lily-5-1.jpg │ ├── lily-5-1.png │ ├── lily-5-2.jpg │ ├── lily-5-2.png │ ├── lily-5-3.jpg │ ├── lily-5-3.png │ ├── lily-5-4.jpg │ ├── lily-5-4.png │ ├── lily-5-5.jpg │ ├── lily-5-5.png │ ├── lily-5-6.jpg │ ├── lily-5-6.png │ ├── lily-5-7.jpg │ ├── lily-5-7.png │ ├── lily-5-8.jpg │ ├── lily-5-8.png │ ├── lily-5-9.jpg │ ├── lily-5-9.png │ ├── lily-6-0.jpg │ ├── lily-6-0.png │ ├── lily-6-1.jpg │ ├── lily-6-1.png │ ├── lily-6-2.jpg │ ├── lily-6-2.png │ ├── lily-6-3.jpg │ ├── lily-6-3.png │ ├── lily-6-4.jpg │ ├── lily-6-4.png │ ├── lily-6-5.jpg │ ├── lily-6-5.png │ ├── lily-6-6.jpg │ ├── lily-6-6.png │ ├── lily-6-7.jpg │ ├── lily-6-7.png │ ├── lily-6-8.jpg │ ├── lily-6-8.png │ ├── lily-6-9.jpg │ ├── lily-6-9.png │ ├── lily-7-0.jpg │ ├── lily-7-0.png │ ├── lily-7-1.jpg │ ├── lily-7-1.png │ ├── lily-7-2.jpg │ ├── lily-7-2.png │ ├── lily-7-3.jpg │ ├── lily-7-3.png │ ├── lily-7-4.jpg │ ├── lily-7-4.png │ ├── lily-7-5.jpg │ ├── lily-7-5.png │ ├── lily-7-6.jpg │ ├── lily-7-6.png │ ├── lily-7-7.jpg │ ├── lily-7-7.png │ ├── lily-7-8.jpg │ ├── lily-7-8.png │ ├── lily-7-9.jpg │ ├── lily-7-9.png │ ├── lily-8-0.jpg │ ├── lily-8-0.png │ ├── lily-8-1.jpg │ ├── lily-8-1.png │ ├── lily-8-2.jpg │ ├── lily-8-2.png │ ├── lily-8-3.jpg │ ├── lily-8-3.png │ ├── lily-8-4.jpg │ ├── lily-8-4.png │ ├── lily-8-5.jpg │ ├── lily-8-5.png │ ├── lily-8-6.jpg │ ├── lily-8-6.png │ ├── lily-8-7.jpg │ ├── lily-8-7.png │ ├── lily-8-8.jpg │ ├── lily-8-8.png │ ├── lily-8-9.jpg │ ├── lily-8-9.png │ ├── lily-9-0.jpg │ ├── lily-9-0.png │ ├── lily-9-1.jpg │ ├── lily-9-1.png │ ├── lily-9-2.jpg │ ├── lily-9-2.png │ ├── lily-9-3.jpg │ ├── lily-9-3.png │ ├── lily-9-4.jpg │ ├── lily-9-4.png │ ├── lily-9-5.jpg │ ├── lily-9-5.png │ ├── lily-9-6.jpg │ ├── lily-9-6.png │ ├── lily-9-7.jpg │ ├── lily-9-7.png │ ├── lily-9-8.jpg │ ├── lily-9-8.png │ ├── lily-9-9.jpg │ └── lily-9-9.png │ ├── test_image.png │ ├── test_image_small.jpg │ └── tiles.html ├── include ├── cashpack.h ├── client.h ├── frame.h ├── log.h ├── pqueue.h ├── request.h ├── stream.h └── util.h ├── src ├── client.c ├── frame.c ├── hh.c ├── pqueue.c ├── request.c └── stream.c └── test └── test_connect.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled objects 2 | build/ 3 | test/__pycache__ 4 | vgcore.* 5 | data/*.pem 6 | replay.json 7 | massif.* 8 | callgrind.* 9 | 10 | # Prerequisites 11 | *.d 12 | 13 | # Object files 14 | *.o 15 | *.ko 16 | *.obj 17 | *.elf 18 | 19 | # Linker output 20 | *.ilk 21 | *.map 22 | *.exp 23 | 24 | # Precompiled Headers 25 | *.gch 26 | *.pch 27 | 28 | # Libraries 29 | *.lib 30 | *.a 31 | *.la 32 | *.lo 33 | 34 | # Shared objects (inc. Windows DLLs) 35 | *.dll 36 | *.so 37 | *.so.* 38 | *.dylib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | *.i*86 45 | *.x86_64 46 | *.hex 47 | 48 | # Debug files 49 | *.dSYM/ 50 | *.su 51 | *.idb 52 | *.pdb 53 | 54 | # Kernel Module Compile Results 55 | *.mod* 56 | *.cmd 57 | .tmp_versions/ 58 | modules.order 59 | Module.symvers 60 | Mkfile.old 61 | dkms.conf 62 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | libhh -- Copyright (c) 2017 Matt Taylor -- MIT license 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD := build 2 | INCLUDE := include 3 | SRC := src 4 | TEST := test 5 | EXE := hh 6 | 7 | CFLAGS += -Wall -Wextra -Werror=implicit-function-declaration -std=gnu11 -Iinclude -Wno-implicit-fallthrough 8 | LDLIBS += -ls2n -lcrypto -pthread -l:libhpack.a 9 | 10 | SRCS := $(shell find $(SRC) -name "*.c") 11 | OBJS := $(addprefix $(BUILD)/,$(notdir $(patsubst %.c,%.o,$(SRCS)))) 12 | TESTS := $(shell find $(TEST) -name "test_*.py") 13 | DEPFILES := $(patsubst %.o,%.d,$(OBJS)) 14 | 15 | .PHONY: all clean run test valgrind rebuild cloc hexdump 16 | 17 | ifeq ($(HH_DEBUG),1) 18 | CFLAGS += -Og -g -DLOG_LEVEL=4 -fsanitize=address,undefined 19 | else 20 | CFLAGS += -O3 -DNDEBUG -DLOG_LEVEL=2 21 | endif 22 | 23 | all: $(BUILD)/$(EXE) $(BUILD)/$(HPACKER) 24 | 25 | clean: 26 | rm -rvf $(BUILD) 27 | 28 | rebuild: clean all 29 | 30 | cloc: 31 | @cloc . --not-match-d="build" 32 | 33 | hexdump: $(BUILD)/$(EXE) 34 | @./$(BUILD)/$(EXE) 2>&1 >/dev/null | hexdump -e '1/1 " %02X"' -C 35 | 36 | run: $(BUILD)/$(EXE) 37 | @echo "------------" 38 | @./$(BUILD)/$(EXE) 8000 39 | 40 | valgrind: $(BUILD)/$(EXE) 41 | @valgrind --leak-check=full ./$(BUILD)/$(EXE) 42 | 43 | 44 | test: $(BUILD)/$(EXE) 45 | @./$(BUILD)/$(EXE) &> $(BUILD)/output.log & 46 | @sleep 0.3 47 | @for TEST in $(TESTS); do python $$TEST; done 48 | @killall --signal SIGINT -w "$(EXE)" 49 | @echo "----------- Server output -----------" 50 | @cat $(BUILD)/output.log 51 | 52 | $(BUILD)/$(EXE): $(BUILD) $(OBJS) 53 | $(CC) $(CFLAGS) $(OBJS) -o $@ -L$(BUILD) $(LDLIBS) 54 | 55 | $(BUILD)/$(HPACKER): $(BUILD) $(HPACK_OBJS) 56 | $(CC) $(HPACKER_CFLAGS) $(HPACK_OBJS) -o $@ $(HPACKER_LDLIBS) 57 | 58 | $(BUILD): 59 | mkdir $@ 60 | 61 | $(BUILD)/%.o: $(SRC)/%.c 62 | $(CC) $(CFLAGS) -c $< -o $@ -MD 63 | 64 | $(BUILD)/%.o: $(HPACK)/%.c 65 | $(CC) $(HPACKER_CFLAGS) -c $< -o $@ -MD 66 | 67 | -include $(DEPFILES) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## HH 2 | 3 | Minimal HTTP/2 server written in C. 4 | 5 | The server is quite conformant to the specification, but some parts (e.g POST requests) are unimplemented. 6 | 7 | This is mostly intended to be educational, and you should certainly not use it for any other purposes because it has not been security audited. 8 | 9 | If you find a bug, feel free to open an issue on GitHub. The source code is MIT licensed (see `LICENSE.md` for more details). 10 | 11 | Compiling the library requires GCC7 or greater. It also requires the [cashpack](https://github.com/Dridi/cashpack/) library and the [s2n](https://github.com/awslabs/s2n/) 12 | library to be installed (with a slightly modified version - I will post more details on this in the future). 13 | 14 | Currently only works on Linux due to unrepentant use of epoll. 15 | -------------------------------------------------------------------------------- /data/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTP/2 Test Website 4 | 5 | 6 |

Hello World!

7 |

8 | This page was faithfully served to you via HTTP/2. 9 |
10 | See an example comparing the performance of HTTP/1.1 vs HTTP/2 here. 11 |

12 |

13 | GitHub link: libhh
14 | Wallpaper source: Positron Dream
15 | Below is an image for you to feast your eyes on. 16 |

17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-0-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-0-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-1-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-1-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-10-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-10-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-11-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-11-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-12-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-12-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-13-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-13-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-14-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-14-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-15-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-15-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-16-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-16-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-17-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-17-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-2-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-2-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-3-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-3-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-4-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-4-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-5-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-5-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-6-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-6-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-7-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-7-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-8-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-8-9.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-0.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-0.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-1.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-1.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-2.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-2.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-3.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-3.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-4.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-4.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-5.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-5.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-6.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-6.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-7.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-7.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-8.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-8.png -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-9.jpg -------------------------------------------------------------------------------- /data/static/lily_images/lily-9-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/lily_images/lily-9-9.png -------------------------------------------------------------------------------- /data/static/test_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/test_image.png -------------------------------------------------------------------------------- /data/static/test_image_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/64/hh/e12e21845d8891ed2f0aba513438994cfabadb77/data/static/test_image_small.jpg -------------------------------------------------------------------------------- /data/static/tiles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTP/2 Tiled Images 4 | 15 | 21 | 22 | 23 |

HTTP/1.1 vs HTTP/2 Comparison

24 |

25 | Back to home 26 |
27 | Use HTTP/1.1 28 |
29 | Use HTTP/2 30 |

31 |
32 | 46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /include/cashpack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // For some reason the cashpack header doesn't have include guards, and 4 | // it doesn't include the standard library headers that it requires. So 5 | // I wrote this quick wrapper to get around those two issues. 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | -------------------------------------------------------------------------------- /include/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "frame.h" 6 | #include "pqueue.h" 7 | #include "stream.h" 8 | #include "request.h" 9 | #include "cashpack.h" 10 | #include "util.h" 11 | 12 | #define CLIENT_EPOLL_EVENTS (EPOLLIN | EPOLLET | EPOLLRDHUP) 13 | 14 | struct h2_settings { 15 | uint32_t header_table_size; 16 | uint32_t enable_push; 17 | uint32_t max_concurrent_streams; 18 | uint32_t initial_window_size; 19 | uint32_t max_frame_size; 20 | uint32_t max_header_list_size; 21 | }; 22 | 23 | struct client { 24 | int fd; // This must be first, because of a little epoll hack we use 25 | int timer_fd; 26 | struct s2n_connection *tls; 27 | enum client_state { 28 | HH_IDLE, // Connection is ready for general HTTP/2 use 29 | HH_NEGOTIATING_TLS, // Connection initiated, doing TLS negotiation 30 | HH_WAITING_MAGIC, // Waiting for client to send connection preface 31 | HH_WAITING_SETTINGS, // Waiting for client to send initial SETTINGS frame 32 | HH_GOAWAY, // GOAWAY frame sent, about to shut down client 33 | HH_BLINDED, // Blinded by s2n, waiting for timer to expire 34 | HH_TLS_SHUTDOWN, // Sent TLS alert, waiting for acknowledgement 35 | HH_ALREADY_CLOSED, // Socket already closed 36 | } state; 37 | s2n_blocked_status blocked; 38 | bool is_write_blocked; 39 | bool is_closing; 40 | bool end_stream; 41 | uint32_t continuation_on_stream; 42 | uint32_t highest_stream_seen; 43 | size_t window_size; 44 | uint8_t *hdblock; // Big buffer for header decoding 45 | struct ib_frame ib_frame; 46 | struct pqueue pqueue; 47 | struct h2_settings settings; 48 | struct hpack *encoder; 49 | struct hpack *decoder; 50 | struct streamtab streams; 51 | }; 52 | 53 | void set_thread_state(struct thread_state *); 54 | struct client *client_new(int, int); 55 | void client_free(struct client *); 56 | int client_write_flush(struct client *); 57 | bool client_pending_write(struct client *); 58 | void client_close_immediate(struct client *); 59 | int client_close_graceful(struct client *); 60 | 61 | int client_on_timer_expired(struct client *); 62 | int client_on_write_ready(struct client *); 63 | int client_on_data_received(struct client *); 64 | -------------------------------------------------------------------------------- /include/frame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "cashpack.h" 7 | 8 | #define HH_HEADER_SIZE 9 9 | 10 | // Frame types 11 | #define HH_FT_DATA 0 12 | #define HH_FT_HEADERS 1 13 | #define HH_FT_PRIORITY 2 14 | #define HH_FT_RST_STREAM 3 15 | #define HH_FT_SETTINGS 4 16 | #define HH_FT_PUSH_PROMISE 5 17 | #define HH_FT_PING 6 18 | #define HH_FT_GOAWAY 7 19 | #define HH_FT_WINDOW_UPDATE 8 20 | #define HH_FT_CONTINUATION 9 21 | 22 | // Frame flags 23 | #define HH_SETTINGS_ACK 1 24 | #define HH_PING_ACK 1 25 | #define HH_HEADERS_END_HEADERS 4 26 | #define HH_END_STREAM 1 27 | #define HH_PADDED 8 28 | #define HH_PRIORITY 32 29 | 30 | // Error types 31 | #define HH_ERR_NONE 0 32 | #define HH_ERR_PROTOCOL 1 33 | #define HH_ERR_INTERNAL 2 34 | #define HH_ERR_FLOW_CONTROL 3 35 | #define HH_ERR_SETTINGS_TIMEOUT 4 36 | #define HH_ERR_STREAM_CLOSED 5 37 | #define HH_ERR_FRAME_SIZE 6 38 | #define HH_ERR_REFUSED_STREAM 7 39 | #define HH_ERR_CANCEL 8 40 | #define HH_ERR_COMPRESSION 9 41 | #define HH_ERR_CONNECT 10 42 | #define HH_ERR_EHNANCE_YOUR_CALM 11 // c|:^) 43 | #define HH_ERR_INADEQUATE_SECURITY 12 44 | 45 | struct h2_frame_hd { 46 | uint32_t length; // 24 bits 47 | uint8_t type; 48 | uint8_t flags; 49 | uint32_t stream_id; // 31 bits 50 | }; 51 | 52 | struct ib_frame { 53 | size_t remaining; 54 | char temp_buf[HH_HEADER_SIZE]; 55 | struct h2_frame_hd header; 56 | char *payload; 57 | enum { 58 | HH_FRAME_HD, 59 | HH_FRAME_PAYLOAD, 60 | } state; 61 | }; 62 | 63 | struct frame_header { 64 | uint8_t data[HH_HEADER_SIZE]; 65 | } __attribute__((packed)); 66 | 67 | struct client; 68 | struct h2_settings; 69 | 70 | void construct_frame_header(struct frame_header *hd, uint32_t length, uint8_t flags, uint8_t type, uint32_t stream_id); 71 | int send_rst_stream(struct client *client, uint32_t stream_id, uint32_t err_code); 72 | int send_goaway(struct client *client, uint32_t err_code); 73 | int send_ping(struct client *client, uint8_t *data, bool ack); 74 | int send_settings(struct client *client, struct h2_settings *server_settings, bool ack); 75 | int send_headers(struct client *client, uint32_t stream_id, struct hpack_field *fields, size_t len, bool end_stream); 76 | -------------------------------------------------------------------------------- /include/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define RED "\e[31m" 6 | #define BLUE "\e[34m" 7 | #define YELLOW "\e[33m" 8 | #define WHITE "\e[1m" 9 | #define GREY "\e[1;30m" 10 | #define COLOR_RST "\e[m" 11 | 12 | #define LOG_LEVEL_FATAL 0 13 | #define LOG_LEVEL_WARN 1 14 | #define LOG_LEVEL_INFO 2 15 | #define LOG_LEVEL_DEBUG 3 16 | #define LOG_LEVEL_TRACE 4 17 | 18 | #ifndef LOG_LEVEL 19 | #define LOG_LEVEL 4 20 | #endif 21 | 22 | #if LOG_LEVEL >= LOG_LEVEL_FATAL 23 | #define log_fatal(fmt, ...) fprintf(stderr, "[" RED "ERROR" COLOR_RST "] " fmt "\n",##__VA_ARGS__) 24 | #else 25 | #define log_fatal(fmt, ...) do {} while(0) 26 | #endif 27 | 28 | #if LOG_LEVEL >= LOG_LEVEL_WARN 29 | #define log_warn(fmt, ...) fprintf(stderr, "[" YELLOW "WARN" COLOR_RST "] " fmt "\n",##__VA_ARGS__) 30 | #else 31 | #define log_warn(fmt, ...) do {} while(0) 32 | #endif 33 | 34 | #if LOG_LEVEL >= LOG_LEVEL_INFO 35 | #define log_info(fmt, ...) fprintf(stdout, "[" BLUE "INFO" COLOR_RST "] " fmt "\n",##__VA_ARGS__) 36 | #else 37 | #define log_info(fmt, ...) do {} while(0) 38 | #endif 39 | 40 | #if LOG_LEVEL >= LOG_LEVEL_DEBUG 41 | #define log_debug(fmt, ...) fprintf(stderr, "[" GREY "DEBUG" COLOR_RST " - %s:%d] " fmt "\n", __FILE__, __LINE__,##__VA_ARGS__) 42 | #else 43 | #define log_debug(fmt, ...) do {} while(0) 44 | #endif 45 | 46 | #if LOG_LEVEL >= LOG_LEVEL_TRACE 47 | #define log_trace() fprintf(stderr, "[" WHITE "TRACE" COLOR_RST "] %s:%d\n", __FILE__, __LINE__) 48 | #else 49 | #define log_trace() do {} while(0) 50 | #endif 51 | -------------------------------------------------------------------------------- /include/pqueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct pqueue_node { 7 | struct pqueue_node *next; 8 | size_t nwritten; 9 | char data[]; 10 | }; 11 | 12 | enum pqueue_pri { 13 | HH_PRI_NONE, 14 | HH_PRI_HIGH, 15 | HH_PRI_MED, 16 | HH_PRI_LOW, 17 | }; 18 | 19 | struct pqueue { 20 | struct pqueue_node *high_pri; 21 | struct pqueue_node *med_pri; 22 | struct pqueue_node *low_pri; 23 | enum pqueue_pri write_head; 24 | }; 25 | 26 | void pqueue_init(struct pqueue *pqueue); 27 | void pqueue_free(struct pqueue *pqueue); 28 | struct pqueue_node *pqueue_node_alloc(size_t len); 29 | void pqueue_node_free(struct pqueue_node *frame); 30 | int pqueue_submit_frame(struct pqueue *pqueue, struct pqueue_node *frame, enum pqueue_pri priority); 31 | int pqueue_pop_next(struct pqueue *pqueue, struct pqueue_node **out_frame, char **out_data, size_t *out_len); 32 | int pqueue_report_write(struct pqueue *pqueue, struct pqueue_node *frame, size_t len_written); 33 | bool pqueue_is_data_pending(struct pqueue *pqueue); 34 | -------------------------------------------------------------------------------- /include/request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct client; 6 | struct stream; 7 | 8 | #define REQ_MAX_PATH 50 9 | 10 | struct request { 11 | struct client *client; // Client who initiated the request 12 | struct request *next; 13 | int fd; // File descriptor of whatever resource is being accessed 14 | size_t bytes_remaining; // How many more bytes we need to read from the file 15 | union { 16 | char pathbuf[REQ_MAX_PATH]; // Path to the file being accessed 17 | char *pathptr; 18 | }; 19 | struct { 20 | int has_path : 1; 21 | int has_method : 1; 22 | int has_scheme : 1; 23 | int done : 1; 24 | } pseudos; 25 | enum { 26 | HH_REQ_NOT_STARTED, 27 | HH_REQ_IN_PROGRESS, 28 | HH_REQ_DONE 29 | } state; 30 | int status_code; // HTTP status code 31 | }; 32 | 33 | void request_send_headers(struct client *, struct stream *); 34 | int request_fulfill(struct stream *s, uint8_t *buf, size_t *max_size); 35 | void request_cleanup(struct request *req); 36 | -------------------------------------------------------------------------------- /include/stream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "request.h" 7 | 8 | enum stream_state { 9 | HH_STREAM_IDLE, 10 | HH_STREAM_RESERVED_LOCAL, 11 | HH_STREAM_RESERVED_REMOTE, 12 | HH_STREAM_OPEN, 13 | HH_STREAM_HCLOSED_LOCAL, 14 | HH_STREAM_HCLOSED_REMOTE, 15 | HH_STREAM_CLOSED 16 | }; 17 | 18 | struct stream { 19 | uint32_t id; 20 | uint16_t weight; // TODO: Maybe make this a uint8_t 21 | enum stream_state state; 22 | struct { 23 | int remote : 1; 24 | int rst_stream : 1; 25 | } how_closed; 26 | size_t window_size; 27 | struct request req; 28 | struct stream *children, *parent, *siblings; 29 | struct stream *next; 30 | }; 31 | 32 | struct streamtab { 33 | size_t entries; 34 | size_t len; 35 | struct stream *root; 36 | struct stream **streams; 37 | }; 38 | 39 | void streamtab_alloc(struct streamtab *); 40 | void streamtab_insert(struct streamtab *, struct stream *); 41 | struct stream *streamtab_schedule(struct streamtab *, size_t *); 42 | struct stream *streamtab_find_id(struct streamtab *, uint32_t); 43 | void streamtab_free(struct streamtab *); 44 | 45 | struct stream *stream_alloc(void); 46 | void stream_add_child(struct stream *stream, struct stream *child); 47 | void stream_add_exclusive_child(struct stream *stream, struct stream *child); 48 | int stream_change_state(struct stream *stream, enum stream_state new_state); 49 | void stream_free(struct stream *); 50 | 51 | static inline struct stream *streamtab_root(struct streamtab *tab) { 52 | struct stream *s = streamtab_find_id(tab, 0); 53 | assert(s != NULL); 54 | return s; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // TODO: Make this safer 7 | #define MIN(x, y) ((x) > (y) ? (y) : (x)) 8 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 9 | 10 | struct thread_state { 11 | int event_fd; 12 | int epoll_fd; 13 | }; 14 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "client.h" 18 | #include "log.h" 19 | #include "util.h" 20 | 21 | #define CLIENT_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" 22 | #define CLIENT_MAGIC_LEN 24 23 | #define CLIENT_INITIAL_HDBUF_SIZE (1 << 13) 24 | #define DATA_BUF_SIZE ((1 << 14) + HH_HEADER_SIZE) 25 | 26 | extern struct s2n_config *server_config; 27 | 28 | _Thread_local struct thread_state thread_state; 29 | 30 | static int change_state(struct client *client, enum client_state to); 31 | 32 | struct h2_settings default_settings = { 33 | .header_table_size = 4096, 34 | .enable_push = 1, 35 | .max_concurrent_streams = 1, 36 | .initial_window_size = 65535, 37 | .max_frame_size = 16384, 38 | .max_header_list_size = 0xFFFFFFFF 39 | }; 40 | 41 | void set_thread_state(struct thread_state *ts) { 42 | memcpy(&thread_state, ts, sizeof *ts); 43 | } 44 | 45 | struct client *client_new(int fd, int timer_fd) { 46 | struct client *rv = malloc(sizeof(struct client)); 47 | rv->tls = s2n_connection_new(S2N_SERVER); 48 | if (rv->tls == NULL) { 49 | log_warn("Call to s2n_connection_new failed (%s)", s2n_strerror(s2n_errno, "EN")); 50 | goto cleanup; // Probably caused by mlock limits 51 | } 52 | s2n_connection_set_fd(rv->tls, fd); 53 | s2n_connection_set_config(rv->tls, server_config); 54 | s2n_connection_set_blinding(rv->tls, S2N_SELF_SERVICE_BLINDING); 55 | s2n_connection_prefer_low_latency(rv->tls); 56 | s2n_set_server_name(rv->tls, "mattst.me"); 57 | streamtab_alloc(&rv->streams); 58 | rv->highest_stream_seen = 0; 59 | rv->continuation_on_stream = 0; 60 | rv->window_size = default_settings.initial_window_size; 61 | rv->fd = fd; 62 | rv->timer_fd = timer_fd; 63 | rv->state = HH_NEGOTIATING_TLS; 64 | rv->end_stream = false; 65 | rv->is_closing = false; 66 | rv->is_write_blocked = false; 67 | rv->blocked = S2N_NOT_BLOCKED; 68 | rv->ib_frame.remaining = 0; 69 | rv->ib_frame.payload = NULL; 70 | rv->encoder = hpack_encoder(default_settings.header_table_size, -1, hpack_default_alloc); 71 | rv->decoder = hpack_decoder(default_settings.header_table_size, (1 << 12), hpack_default_alloc); 72 | pqueue_init(&rv->pqueue); 73 | rv->hdblock = malloc(CLIENT_INITIAL_HDBUF_SIZE); 74 | memcpy(&rv->settings, &default_settings, sizeof default_settings); 75 | memset(&rv->ib_frame.header, 0, sizeof rv->ib_frame.header); 76 | return rv; 77 | cleanup: 78 | free(rv); 79 | return NULL; 80 | } 81 | 82 | void client_free(struct client *client) { 83 | // TODO: Wipe the connection, don't free it 84 | s2n_connection_free(client->tls); 85 | pqueue_free(&client->pqueue); 86 | streamtab_free(&client->streams); 87 | hpack_free(&client->encoder); 88 | hpack_free(&client->decoder); 89 | free(client->ib_frame.payload); 90 | free(client->hdblock); 91 | free(client); 92 | } 93 | 94 | void client_close_immediate(struct client *client) { 95 | int rv = close(client->fd); 96 | if (rv < 0) 97 | log_warn("Call to close(client) failed (%s)", strerror(errno)); 98 | rv = close(client->timer_fd); 99 | if (rv < 0) 100 | log_warn("Call to close(client->timer_fd) failed (%s)", strerror(errno)); 101 | client_free(client); 102 | } 103 | 104 | static bool is_protocol_correct(struct client *client) { 105 | const char *proto = s2n_get_application_protocol(client->tls); 106 | if (proto == NULL) 107 | return false; 108 | return strcmp("h2", proto) == 0; 109 | } 110 | 111 | static void initiate_graceful_close(struct client *client) { 112 | client->is_closing = true; 113 | if (client->state == HH_NEGOTIATING_TLS || client->state == HH_ALREADY_CLOSED) 114 | return; 115 | else if (client->state == HH_IDLE) 116 | change_state(client, HH_GOAWAY); 117 | else if (client->state != HH_TLS_SHUTDOWN && client->state != HH_GOAWAY) 118 | change_state(client, HH_TLS_SHUTDOWN); 119 | } 120 | 121 | // Returns -1 when callers should call client_close_immediate 122 | int client_close_graceful(struct client *client) { 123 | if (client->state == HH_NEGOTIATING_TLS || client->state == HH_ALREADY_CLOSED) 124 | return -1; 125 | assert(client->state == HH_GOAWAY || client->state == HH_TLS_SHUTDOWN); 126 | assert(client->is_closing); 127 | if (client->state == HH_GOAWAY) { 128 | // Wait until the GOAWAY frame has been sent 129 | if (client->pqueue.high_pri == NULL) { 130 | change_state(client, HH_TLS_SHUTDOWN); 131 | } else 132 | return 0; 133 | } 134 | if (client->state == HH_TLS_SHUTDOWN) { 135 | if (s2n_shutdown(client->tls, &client->blocked) < 0) { 136 | switch(s2n_error_get_type(s2n_errno)) { 137 | case S2N_ERR_T_BLOCKED: 138 | return 0; 139 | case S2N_ERR_T_CLOSED: 140 | return -1; 141 | case S2N_ERR_T_IO: 142 | log_debug("Call to s2n_shutdown failed IO (%s)", strerror(errno)); 143 | return -1; 144 | default: 145 | //log_debug("Call to s2n_shutdown failed (%s)", s2n_strerror(s2n_errno, "EN")); 146 | return -1; 147 | } 148 | } 149 | } 150 | // Graceful shutdown was successful, we can close now 151 | return -1; 152 | } 153 | 154 | // Big state machine. 155 | static int change_state(struct client *client, enum client_state to) { 156 | if (to == HH_BLINDED || to == HH_ALREADY_CLOSED) { 157 | client->state = to; 158 | return 0; 159 | } 160 | 161 | switch (client->state) { 162 | case HH_IDLE: 163 | switch (to) { 164 | case HH_TLS_SHUTDOWN: 165 | case HH_GOAWAY: 166 | client->state = to; 167 | break; 168 | default: 169 | goto verybad; 170 | } 171 | break; 172 | case HH_WAITING_MAGIC: 173 | switch (to) { 174 | case HH_TLS_SHUTDOWN: 175 | case HH_WAITING_SETTINGS: 176 | client->state = to; 177 | break; 178 | default: 179 | goto verybad; 180 | } 181 | break; 182 | case HH_WAITING_SETTINGS: 183 | switch (to) { 184 | case HH_TLS_SHUTDOWN: 185 | case HH_GOAWAY: 186 | case HH_IDLE: 187 | client->state = to; 188 | break; 189 | default: 190 | goto verybad; 191 | } 192 | break; 193 | case HH_NEGOTIATING_TLS: 194 | switch (to) { 195 | case HH_WAITING_MAGIC: 196 | client->state = to; 197 | client->ib_frame.remaining = CLIENT_MAGIC_LEN; 198 | send_settings(client, NULL, false); // Server connection preface 199 | break; 200 | default: 201 | goto verybad; 202 | } 203 | break; 204 | case HH_GOAWAY: 205 | switch (to) { 206 | case HH_TLS_SHUTDOWN: 207 | client->state = to; 208 | break; 209 | default: 210 | goto verybad; 211 | } 212 | break; 213 | case HH_TLS_SHUTDOWN: 214 | switch (to) { 215 | case HH_TLS_SHUTDOWN: // Treat as no-op for simplicity 216 | break; 217 | default: 218 | goto verybad; 219 | } 220 | break; 221 | verybad: 222 | default: 223 | #ifdef NDEBUG 224 | __builtin_unreachable(); // Helps the optimizer 225 | #else 226 | log_fatal("You reached the unreachable (client state %d from %d), this is very bad.", to, client->state); 227 | log_trace(); 228 | exit(-1); 229 | #endif 230 | } 231 | return 0; 232 | } 233 | 234 | static int blind_client(struct client *client, uint64_t ns) { 235 | time_t seconds = ns / 1000000000; 236 | long nanos = ns % 1000000000; 237 | struct itimerspec expiry_time = { 238 | { 0, 0 }, // Interval time 239 | { seconds, nanos }, // Expiry time 240 | }; 241 | if (ns == 0) 242 | return -1; 243 | if (timerfd_settime(client->timer_fd, 0, &expiry_time, NULL) < 0) { 244 | log_warn("Call to timerfd_settime failed (%s)", strerror(errno)); 245 | return -1; 246 | } 247 | change_state(client, HH_BLINDED); 248 | return 0; 249 | } 250 | 251 | int client_on_timer_expired(struct client *client) { 252 | uint64_t read_buf; 253 | int rv = read(client->timer_fd, &read_buf, sizeof read_buf); 254 | if (rv < 0) { 255 | log_warn("Call to timerfd read failed (%s)", strerror(errno)); 256 | return -1; 257 | } 258 | if (client->state == HH_BLINDED) 259 | return -1; 260 | else 261 | log_warn("Timer expired, but client is not blinded"); 262 | return 0; 263 | } 264 | 265 | static int do_negotiate(struct client *client) { 266 | assert(client->state == HH_NEGOTIATING_TLS); 267 | s2n_errno = S2N_ERR_T_OK; 268 | errno = 0; 269 | if (s2n_negotiate(client->tls, &client->blocked) < 0) { 270 | switch (s2n_error_get_type(s2n_errno)) { 271 | case S2N_ERR_T_CLOSED: 272 | return -1; 273 | case S2N_ERR_T_BLOCKED: 274 | break; 275 | case S2N_ERR_T_ALERT: 276 | log_warn("Call to s2n_negotiate gave alert %d", s2n_connection_get_alert(client->tls)); 277 | break; 278 | case S2N_ERR_T_PROTO: 279 | log_debug("Call to s2n_negotiate returned protocol error"); 280 | return blind_client(client, s2n_connection_get_delay(client->tls)); 281 | case S2N_ERR_T_IO: 282 | // I suppose if ALPN fails, then negotiate will return an error even when successful 283 | // Skip printing a warning if this happens, but still close the connection. 284 | if (errno != 0) 285 | log_warn("Call to s2n_negotiate returned IO error"); 286 | return blind_client(client, s2n_connection_get_delay(client->tls)); 287 | default: 288 | log_warn("Call to s2n_negotiate failed (%s)", s2n_strerror(s2n_errno, "EN")); 289 | return blind_client(client, s2n_connection_get_delay(client->tls)); 290 | } 291 | } 292 | if (client->blocked == S2N_NOT_BLOCKED) { 293 | if (!is_protocol_correct(client)) { 294 | // HTTP/2 was not negotiated, close the connection (TODO: with an alert or message?) 295 | return -1; 296 | } 297 | // Might we need to check that there is no more data available? 298 | change_state(client, HH_WAITING_MAGIC); 299 | } 300 | return 0; 301 | } 302 | 303 | static int process_header(struct stream *stream, char *name, char *value, bool *stream_err) { 304 | //printf("%s: %s\n", name, value); 305 | // TODO: Use gperf or something to create a hash function for the strings in here 306 | // This is probably very slow and needs benchmarking 307 | if (*name == ':') { 308 | if (stream->req.pseudos.done) { 309 | *stream_err = true; 310 | return HH_ERR_PROTOCOL; 311 | } 312 | } else 313 | stream->req.pseudos.done = true; 314 | 315 | if (strcmp(name, ":path") == 0) { 316 | if (stream->req.pseudos.has_path) { 317 | *stream_err = true; 318 | return HH_ERR_PROTOCOL; 319 | } else if (*value != '/') { 320 | *stream_err = true; 321 | return HH_ERR_PROTOCOL; 322 | } else { 323 | stream->req.pseudos.has_path = true; 324 | // Dangerously copy everything up until a questionmark 325 | char *src = value, *dst = stream->req.pathbuf; 326 | while (*src && *src != '?' && src - value < REQ_MAX_PATH) // TODO: Should it be REQ_MAX_PATH - 1? 327 | *dst++ = *src++; 328 | *dst = '\0'; 329 | stream->req.pathbuf[REQ_MAX_PATH - 1] = '\0'; 330 | } 331 | } else if (strcmp(name, ":method") == 0) { 332 | if (stream->req.pseudos.has_method) { 333 | *stream_err = true; 334 | return HH_ERR_PROTOCOL; 335 | } else // TODO: Don't respond to requests other than GET 336 | stream->req.pseudos.has_method = true; 337 | } else if (strcmp(name, ":scheme") == 0) { 338 | if (stream->req.pseudos.has_scheme) { 339 | *stream_err = true; 340 | return HH_ERR_PROTOCOL; 341 | } else 342 | stream->req.pseudos.has_scheme = true; 343 | } else if (strcmp(name, ":status") == 0 || strcmp(name, "connection") == 0) { 344 | *stream_err = true; 345 | return HH_ERR_PROTOCOL; 346 | } else if (strcmp(name, "te") == 0) { 347 | if (strcmp(value, "trailers") != 0) { 348 | *stream_err = true; 349 | return HH_ERR_PROTOCOL; 350 | } 351 | } 352 | return 0; 353 | } 354 | 355 | static int finalise_request(struct client *client, struct stream *stream, bool *stream_err) { 356 | if (!(stream->req.pseudos.has_path 357 | && stream->req.pseudos.has_method 358 | && stream->req.pseudos.has_scheme)) { 359 | *stream_err = true; 360 | return HH_ERR_PROTOCOL; 361 | } 362 | 363 | if (strcmp(stream->req.pathbuf, "/") == 0) 364 | strcat(stream->req.pathbuf, "index.html"); 365 | 366 | char pathbuf[2 * REQ_MAX_PATH] = { 0 }; 367 | strcpy(pathbuf, "data/static"); 368 | strcat(pathbuf, stream->req.pathbuf); // Won't overflow since we terminated it at REQ_MAX_PATH - 1 369 | 370 | log_debug("GET %s", pathbuf + strlen("data/static/") - 1); 371 | stream->req.pathptr = &pathbuf[0]; 372 | // Prevent directory traversal attacks 373 | if (strstr(stream->req.pathbuf, "../") != NULL) { 374 | stream->req.status_code = 400; 375 | } else if ((stream->req.fd = open(pathbuf, O_RDONLY)) == -1) { 376 | if (errno == ENOENT) 377 | stream->req.status_code = 404; 378 | else if (errno == EPERM) 379 | stream->req.status_code = 400; 380 | else 381 | stream->req.status_code = 500; 382 | } else 383 | stream->req.status_code = 200; 384 | assert(stream->req.fd != -1 || stream->req.status_code != 200); 385 | 386 | // Send headers for request 387 | request_send_headers(client, stream); 388 | return 0; 389 | } 390 | 391 | static struct stream *update_stream(struct client *client, struct stream *stream, uint32_t stream_id, uint8_t *pri_data, 392 | enum stream_state state_if_created) { 393 | bool exclusive = false; 394 | uint32_t dependency = 0; 395 | uint16_t weight = 16; 396 | if (pri_data != NULL) { 397 | memcpy(&dependency, pri_data, sizeof dependency); 398 | dependency = ntohl(dependency); 399 | if ((dependency & (1U << 31)) != 0) // E(xclusive) bit 400 | exclusive = true; 401 | dependency &= ~(1U << 31); // Ignore E bit 402 | weight = *(uint8_t *)(pri_data + 4); 403 | weight++; // { 0 ... 255 } -> { 1 ... 256 } 404 | } 405 | // Create new stream if needed 406 | if (stream == NULL) { 407 | stream = stream_alloc(); 408 | stream->weight = weight; 409 | stream->id = stream_id; 410 | stream->window_size = client->settings.initial_window_size; 411 | stream->state = state_if_created; 412 | } 413 | if (dependency == stream_id) { 414 | return NULL; 415 | } 416 | if (stream->id > client->highest_stream_seen) { 417 | if (state_if_created == HH_STREAM_OPEN) { 418 | client->highest_stream_seen = stream->id; 419 | // TODO: Close all idle streams <= stream->id 420 | } 421 | } 422 | // If parent is null, we just created stream 423 | if (stream->parent == NULL || stream->parent->id != dependency) { 424 | struct stream *new_parent = streamtab_find_id(&client->streams, dependency); 425 | if (new_parent == NULL) { 426 | // Create the parent 427 | new_parent = stream_alloc(); 428 | new_parent->weight = 16; 429 | new_parent->id = dependency; 430 | new_parent->state = HH_STREAM_IDLE; 431 | new_parent->window_size = client->settings.initial_window_size; 432 | stream_add_child(streamtab_root(&client->streams), new_parent); 433 | streamtab_insert(&client->streams, new_parent); 434 | } 435 | if (exclusive) 436 | stream_add_exclusive_child(new_parent, stream); 437 | else 438 | stream_add_child(new_parent, stream); 439 | streamtab_insert(&client->streams, stream); 440 | } 441 | //log_debug("Openened stream %u, weight %u, depends %u", stream_id, stream->weight, dependency); 442 | return stream; 443 | } 444 | 445 | static int parse_frame(struct client *client, char *buf, size_t len) { 446 | #define advance(x) ({ bufp += (x); }) 447 | #define remaining_len (len - (bufp - buf)) 448 | struct ib_frame *ib = &client->ib_frame; 449 | char *bufp = buf; 450 | 451 | // Parse client connection preface 452 | if (client->state == HH_WAITING_MAGIC) { 453 | size_t read_length = MIN(remaining_len, ib->remaining); 454 | if (memcmp(CLIENT_MAGIC + CLIENT_MAGIC_LEN - ib->remaining, bufp, read_length) != 0) { 455 | //log_debug("Invalid client connection preface"); 456 | return -1; 457 | } 458 | ib->remaining -= read_length; 459 | advance(read_length); 460 | if (ib->remaining == 0) { 461 | change_state(client, HH_WAITING_SETTINGS); 462 | } else 463 | return 0; 464 | ib->remaining = HH_HEADER_SIZE; 465 | ib->state = HH_FRAME_HD; 466 | if (remaining_len == 0) 467 | return 0; 468 | // Otherwise we have remaining data to process 469 | } 470 | 471 | while (1) { 472 | bool has_full_frame = false; // True when payload is in memory 473 | switch (ib->state) { 474 | case HH_FRAME_HD: { 475 | // Store header in temporary buffer 476 | size_t read_length = MIN(remaining_len, ib->remaining); 477 | if (read_length == 0) 478 | return 0; 479 | memcpy(ib->temp_buf + HH_HEADER_SIZE - ib->remaining, bufp, read_length); 480 | advance(read_length); 481 | ib->remaining -= read_length; 482 | if (ib->remaining > 0) // Can't do any more processing, wait for rest of header 483 | return 0; 484 | 485 | // Parse frame header 486 | uint32_t tmp; 487 | ib->header.type = ib->temp_buf[3]; 488 | ib->header.flags = ib->temp_buf[4]; 489 | memcpy(&tmp, ib->temp_buf + 5, sizeof(uint32_t)); 490 | ib->header.stream_id = ntohl(tmp) & 0x7FFFFFFF; 491 | memcpy(&tmp, ib->temp_buf, sizeof(uint32_t)); 492 | ib->header.length = ntohl(tmp & 0xFFFFFF00) >> 8; 493 | 494 | // If we were waiting for the initial SETTINGS frame 495 | // TODO: Don't wait for the client before sending our own settings frame 496 | if (client->state == HH_WAITING_SETTINGS) { 497 | // It wasn't a settings frame 498 | if (ib->header.type != HH_FT_SETTINGS || (ib->header.flags & HH_SETTINGS_ACK) != 0) { 499 | send_goaway(client, HH_ERR_PROTOCOL); 500 | goto goaway; 501 | } else { 502 | change_state(client, HH_IDLE); 503 | } 504 | } 505 | 506 | // Expected a continuation, but our expectation was violated 507 | if (client->continuation_on_stream != 0 && 508 | (ib->header.type != HH_FT_CONTINUATION 509 | || ib->header.stream_id != client->continuation_on_stream)) { 510 | send_goaway(client, HH_ERR_PROTOCOL); 511 | goto goaway; 512 | } 513 | 514 | if (ib->header.length > default_settings.max_frame_size) { 515 | send_goaway(client, HH_ERR_FRAME_SIZE); 516 | } 517 | 518 | free(ib->payload); 519 | ib->payload = NULL; 520 | if (ib->header.length > 0) { 521 | ib->payload = malloc(ib->header.length); 522 | ib->state = HH_FRAME_PAYLOAD; 523 | ib->remaining = ib->header.length; 524 | } else { 525 | ib->remaining = HH_HEADER_SIZE; 526 | continue; 527 | } 528 | // fallthrough 529 | } case HH_FRAME_PAYLOAD: { 530 | // Read as much as we can into payload buffer 531 | size_t read_length = MIN(remaining_len, ib->remaining); 532 | if (read_length == 0) 533 | return 0; 534 | memcpy(ib->payload + (ib->header.length - ib->remaining), bufp, read_length); 535 | advance(read_length); 536 | ib->remaining -= read_length; 537 | // We read the entire payload, now wait for a frame header again 538 | if (ib->remaining == 0) { 539 | has_full_frame = true; 540 | ib->state = HH_FRAME_HD; 541 | ib->remaining = HH_HEADER_SIZE; 542 | } 543 | break; 544 | } 545 | } 546 | 547 | if (!has_full_frame) 548 | continue; 549 | 550 | struct stream *stream = streamtab_find_id(&client->streams, ib->header.stream_id); 551 | // Check whether the frame is allowed in this state 552 | if (ib->header.stream_id != 0) { 553 | int state; 554 | if (stream != NULL) 555 | state = stream->state; 556 | else // Not in the dependency tree, so must be not used already 557 | state = HH_STREAM_IDLE; 558 | switch (state) { 559 | case HH_STREAM_IDLE: 560 | if (ib->header.type != HH_FT_HEADERS 561 | && ib->header.type != HH_FT_PRIORITY) { 562 | send_goaway(client, HH_ERR_PROTOCOL); 563 | goto goaway; 564 | } 565 | break; 566 | case HH_STREAM_RESERVED_LOCAL: 567 | if (ib->header.type != HH_FT_RST_STREAM 568 | && ib->header.type != HH_FT_PRIORITY 569 | && ib->header.type != HH_FT_WINDOW_UPDATE) { 570 | send_goaway(client, HH_ERR_PROTOCOL); 571 | goto goaway; 572 | } 573 | break; 574 | case HH_STREAM_RESERVED_REMOTE: 575 | if (ib->header.type != HH_FT_HEADERS 576 | && ib->header.type != HH_FT_RST_STREAM 577 | && ib->header.type != HH_FT_PRIORITY) { 578 | send_goaway(client, HH_ERR_PROTOCOL); 579 | goto goaway; 580 | } 581 | break; 582 | case HH_STREAM_HCLOSED_REMOTE: 583 | if (ib->header.type != HH_FT_WINDOW_UPDATE 584 | && ib->header.type != HH_FT_PRIORITY 585 | && ib->header.type != HH_FT_RST_STREAM) { 586 | send_rst_stream(client, ib->header.stream_id, HH_ERR_STREAM_CLOSED); 587 | // TODO: Change stream state to closed 588 | goto rst_stream; 589 | } 590 | break; 591 | case HH_STREAM_CLOSED: 592 | // TODO: Technically not standards compliant here. See 5.1@"closed" 593 | if (ib->header.type != HH_FT_WINDOW_UPDATE 594 | && ib->header.type != HH_FT_PRIORITY 595 | && ib->header.type != HH_FT_RST_STREAM) { 596 | send_goaway(client, HH_ERR_PROTOCOL); 597 | goto goaway; 598 | } 599 | break; 600 | // Otherwise we don't care 601 | case HH_STREAM_OPEN: 602 | case HH_STREAM_HCLOSED_LOCAL: 603 | default: 604 | break; 605 | } 606 | } 607 | 608 | switch (ib->header.type) { 609 | case HH_FT_SETTINGS: 610 | // Parse settings frame 611 | if (ib->header.stream_id != 0 || ib->header.length % 6 != 0) { 612 | send_goaway(client, ib->header.stream_id != 0 ? HH_ERR_PROTOCOL : HH_ERR_FRAME_SIZE); 613 | goto goaway; 614 | } else if ((ib->header.flags & HH_SETTINGS_ACK) != 0) { 615 | if (ib->header.length != 0) { 616 | send_goaway(client, HH_ERR_FRAME_SIZE); 617 | goto goaway; 618 | } 619 | } else { 620 | size_t read = 0; 621 | while (read < ib->header.length) { 622 | uint16_t id; 623 | uint32_t value; 624 | memcpy(&id, &ib->payload[read], sizeof(uint16_t)); 625 | memcpy(&value, &ib->payload[read + 2], sizeof(uint32_t)); 626 | id = ntohs(id); 627 | value = ntohl(value); 628 | read += 6; 629 | switch (id) { 630 | case 1: 631 | client->settings.header_table_size = value; 632 | hpack_resize(&client->encoder, MIN(value, 65535)); 633 | break; 634 | case 2: 635 | if (value != 0 && value != 1) { 636 | send_goaway(client, HH_ERR_PROTOCOL); 637 | goto goaway; 638 | } 639 | client->settings.enable_push = value; 640 | break; 641 | case 3: 642 | client->settings.max_concurrent_streams = value; 643 | break; 644 | case 4: 645 | if (value > ((1U << 31) - 1)) { 646 | send_goaway(client, HH_ERR_FLOW_CONTROL); 647 | goto goaway; 648 | } 649 | client->settings.initial_window_size = value; 650 | break; 651 | case 5: 652 | if (value < (1 << 14) || value > ((1 << 24) - 1)) { 653 | send_goaway(client, HH_ERR_PROTOCOL); 654 | goto goaway; 655 | } 656 | client->settings.max_frame_size = value; 657 | break; 658 | case 6: 659 | client->settings.max_header_list_size = value; 660 | break; 661 | default: 662 | // Don't error, just ignore 663 | break; 664 | } 665 | //log_debug("Received setting %d, value %#x", id, value); 666 | } 667 | // ACKnowledge 668 | send_settings(client, NULL, true); 669 | } 670 | break; 671 | case HH_FT_WINDOW_UPDATE: 672 | if (ib->header.length != 4) { 673 | send_goaway(client, HH_ERR_FRAME_SIZE); 674 | goto goaway; 675 | } 676 | uint32_t increment_size = ntohl(*(uint32_t *)ib->payload) & 0x7FFFFFFF; 677 | //log_debug("Received WINDOW_UPDATE frame of size %u", increment_size); 678 | if (ib->header.stream_id == 0) { 679 | if (increment_size == 0) { 680 | send_goaway(client, HH_ERR_PROTOCOL); 681 | goto goaway; 682 | } else if ((client->window_size + increment_size) > ((1U << 31) - 1)) { 683 | send_goaway(client, HH_ERR_FLOW_CONTROL); 684 | goto goaway; 685 | } else 686 | client->window_size += increment_size; 687 | } else { 688 | if (stream->state == HH_STREAM_CLOSED) { 689 | assert(stream != NULL); 690 | } else if (increment_size == 0) { 691 | send_rst_stream(client, ib->header.stream_id, HH_ERR_PROTOCOL); 692 | goto rst_stream; 693 | // TODO: Change stream state to closed 694 | } else if ((stream->window_size + increment_size) > ((1U << 31) - 1)) { 695 | send_rst_stream(client, ib->header.stream_id, HH_ERR_FLOW_CONTROL); 696 | goto rst_stream; 697 | } else 698 | stream->window_size += increment_size; 699 | } 700 | break; 701 | case HH_FT_PRIORITY: 702 | if (ib->header.stream_id == 0) { 703 | send_goaway(client, HH_ERR_PROTOCOL); 704 | goto goaway; 705 | } else if (ib->header.length != 5) { 706 | send_goaway(client, HH_ERR_FRAME_SIZE); 707 | goto goaway; 708 | } 709 | stream = update_stream(client, stream, ib->header.stream_id, (uint8_t *)ib->payload, HH_STREAM_IDLE); 710 | if (stream == NULL) { 711 | // Circular dependency 712 | send_rst_stream(client, ib->header.stream_id, HH_ERR_PROTOCOL); 713 | goto rst_stream; 714 | } 715 | break; 716 | case HH_FT_CONTINUATION: 717 | if (stream == NULL || (ib->header.flags & (HH_END_STREAM | HH_PADDED | HH_PRIORITY)) != 0) { 718 | send_goaway(client, HH_ERR_PROTOCOL); 719 | goto goaway; 720 | } 721 | // fallthrough 722 | case HH_FT_HEADERS: { 723 | uint8_t *new_stream_pri_info = NULL; 724 | uint8_t *decode_start = (uint8_t *)ib->payload; 725 | size_t decode_len = ib->header.length; 726 | if (ib->header.stream_id == 0) { 727 | send_goaway(client, HH_ERR_PROTOCOL); 728 | goto goaway; 729 | } else if (stream == NULL) { 730 | // TODO: Check number of open streams 731 | // Odd numbered streams from client 732 | if (ib->header.stream_id % 2 != 1) { 733 | send_goaway(client, HH_ERR_PROTOCOL); 734 | goto goaway; 735 | } 736 | } 737 | client->continuation_on_stream = 738 | (ib->header.flags & HH_HEADERS_END_HEADERS) == 0 ? ib->header.stream_id : 0; 739 | if (ib->header.type == HH_FT_HEADERS) 740 | client->end_stream = (ib->header.flags & HH_END_STREAM) == HH_END_STREAM; 741 | //assert(client->continuation_on_stream != 0 || client->end_stream); 742 | if (ib->header.flags & HH_PADDED) { 743 | uint8_t pad_len = *(uint8_t *)ib->payload; 744 | if (pad_len >= ib->header.length) { 745 | send_goaway(client, HH_ERR_PROTOCOL); 746 | goto goaway; 747 | } 748 | decode_len -= pad_len + 1; // 1 for the pad_length itself 749 | decode_start += 1; 750 | } 751 | if (ib->header.flags & HH_PRIORITY) { 752 | if (decode_len < 5) { 753 | send_goaway(client, HH_ERR_PROTOCOL); 754 | goto goaway; 755 | } 756 | new_stream_pri_info = decode_start; 757 | decode_len -= 5; 758 | decode_start += 5; 759 | } 760 | stream = update_stream(client, stream, ib->header.stream_id, new_stream_pri_info, HH_STREAM_OPEN); 761 | if (stream == NULL || stream->req.state != HH_REQ_NOT_STARTED) { 762 | // Circular dependency or tried to send on an active stream 763 | send_rst_stream(client, ib->header.stream_id, HH_ERR_PROTOCOL); 764 | goto rst_stream; 765 | } 766 | assert(stream->req.state == HH_REQ_NOT_STARTED); 767 | struct hpack_decoding dec = { 768 | .blk = decode_start, 769 | .blk_len = decode_len, 770 | .buf = client->hdblock, 771 | .buf_len = CLIENT_INITIAL_HDBUF_SIZE, 772 | .cb = NULL, 773 | .priv = client, 774 | .cut = !(ib->header.flags & HH_HEADERS_END_HEADERS) 775 | }; 776 | int rv; 777 | bool stream_err; 778 | char *name = NULL, *value = NULL; 779 | while ((rv = hpack_decode_fields(client->decoder, 780 | &dec, (const char **)&name, (const char **)&value)) == HPACK_RES_FLD) { 781 | // Pack the header into the request struct 782 | rv = process_header(stream, name, value, &stream_err); 783 | if (rv != 0) { 784 | if (stream_err) { 785 | send_rst_stream(client, ib->header.stream_id, rv); 786 | goto rst_stream; 787 | } else { 788 | send_goaway(client, rv); 789 | } 790 | } 791 | } 792 | switch (rv) { 793 | case HPACK_RES_BLK: // Decoding not finished 794 | break; 795 | case HPACK_RES_OK: {// Decoding finished 796 | // Finalise request 797 | if (client->end_stream) { 798 | stream_change_state(stream, HH_STREAM_HCLOSED_REMOTE); 799 | client->end_stream = false; 800 | } 801 | rv = finalise_request(client, stream, &stream_err); 802 | if (rv != 0) { 803 | if (stream_err) { 804 | send_rst_stream(client, ib->header.stream_id, rv); 805 | goto rst_stream; 806 | } else { 807 | send_goaway(client, rv); 808 | } 809 | } 810 | break; 811 | } case HPACK_RES_SKP: // Buffer not large enough, recoverable 812 | assert(hpack_skip(client->decoder) == HPACK_RES_OK); 813 | send_rst_stream(client, ib->header.stream_id, HH_ERR_REFUSED_STREAM); 814 | goto rst_stream; 815 | case HPACK_RES_BIG: // Buffer not large enough, fatal 816 | send_goaway(client, HH_ERR_PROTOCOL); 817 | goto goaway; 818 | default: 819 | //log_debug("Call to hpack_decode failed (%s)", hpack_strerror(rv)); 820 | send_goaway(client, HH_ERR_COMPRESSION); 821 | goto goaway; 822 | } 823 | break; 824 | } case HH_FT_PING: 825 | if (ib->header.length != 8) { 826 | send_goaway(client, HH_ERR_FRAME_SIZE); 827 | goto goaway; 828 | } else if (ib->header.stream_id != 0) { 829 | send_goaway(client, HH_ERR_PROTOCOL); 830 | goto goaway; 831 | } else if ((ib->header.flags & HH_PING_ACK) == 0) { 832 | // We need to ACK the ping 833 | send_ping(client, (uint8_t *)ib->payload, true); 834 | } 835 | break; 836 | case HH_FT_GOAWAY: 837 | #ifndef NDEBUG 838 | if (ib->header.length >= 8) 839 | log_debug("Received GOAWAY (%d): %.*s", 840 | ntohl(*(uint32_t *)&ib->payload[4]), 841 | ib->header.length - 8, 842 | &ib->payload[8]); 843 | #endif 844 | return -1; 845 | case HH_FT_RST_STREAM: 846 | if (ib->header.length != 4) { 847 | send_goaway(client, HH_ERR_FRAME_SIZE); 848 | goto goaway; 849 | } else if (ib->header.stream_id == 0) { 850 | send_goaway(client, HH_ERR_PROTOCOL); 851 | goto goaway; 852 | } else if (stream == NULL || stream->state == HH_STREAM_CLOSED) { 853 | if (ib->header.stream_id <= client->highest_stream_seen) { 854 | send_goaway(client, HH_ERR_PROTOCOL); 855 | goto goaway; 856 | } 857 | } else if (stream->state == HH_STREAM_IDLE) { 858 | send_goaway(client, HH_ERR_PROTOCOL); 859 | goto goaway; 860 | } else { 861 | uint32_t err = ntohl(*(uint32_t *)ib->payload); 862 | log_debug("RST_STREAM: id %u, err %u", stream->id, err); 863 | (void)err; 864 | stream_change_state(stream, HH_STREAM_CLOSED); 865 | } 866 | break; 867 | case HH_FT_DATA: 868 | break; 869 | default: 870 | break; 871 | } 872 | rst_stream: 873 | continue; 874 | } 875 | log_trace(); 876 | return 0; 877 | goaway: 878 | return -1; 879 | } 880 | 881 | static int do_read(struct client *client) { 882 | #define READ_LEN 4096 883 | char recv_buffer[READ_LEN]; 884 | ssize_t nread; 885 | int rv = 0; 886 | do { 887 | s2n_errno = S2N_ERR_T_OK; 888 | if ((nread = s2n_recv(client->tls, recv_buffer, READ_LEN, &client->blocked)) < 0) { 889 | switch (s2n_error_get_type(s2n_errno)) { 890 | case S2N_ERR_T_CLOSED: 891 | change_state(client, HH_ALREADY_CLOSED); 892 | rv = -1; 893 | goto loop_end; 894 | case S2N_ERR_T_BLOCKED: 895 | break; 896 | case S2N_ERR_T_IO: 897 | log_trace(); 898 | rv = blind_client(client, s2n_connection_get_delay(client->tls)); 899 | goto loop_end; 900 | default: 901 | log_warn("s2n_recv: %s", s2n_strerror(s2n_errno, "EN")); 902 | rv = blind_client(client, s2n_connection_get_delay(client->tls)); 903 | goto loop_end; 904 | } 905 | } else if (nread == 0) { 906 | // Client disconnected, signal shutdown 907 | change_state(client, HH_ALREADY_CLOSED); 908 | rv = -1; 909 | goto loop_end; 910 | } else { 911 | if (client->state != HH_GOAWAY && parse_frame(client, recv_buffer, nread) < 0) { 912 | rv = -1; 913 | goto loop_end; 914 | } 915 | } 916 | } while (client->blocked == S2N_NOT_BLOCKED); 917 | loop_end: 918 | return rv; 919 | } 920 | 921 | static int signal_epollout(struct client *client, bool on) { 922 | struct epoll_event ev; 923 | ev.data.ptr = client; 924 | ev.events = CLIENT_EPOLL_EVENTS; 925 | if (on) ev.events |= EPOLLOUT; 926 | if (epoll_ctl(thread_state.epoll_fd, EPOLL_CTL_MOD, client->fd, &ev) < 0) 927 | return -1; 928 | return 0; 929 | } 930 | 931 | bool client_pending_write(struct client *client) { 932 | size_t dummy = ULONG_MAX; 933 | return (pqueue_is_data_pending(&client->pqueue)) || (streamtab_schedule(&client->streams, &dummy) != NULL); 934 | } 935 | 936 | int client_write_flush(struct client *client) { 937 | assert(!client->is_closing || client->state == HH_GOAWAY); 938 | struct pqueue_node *out; 939 | char buf[DATA_BUF_SIZE]; 940 | do { 941 | // Write the highest priority data first 942 | size_t out_len; 943 | char *out_data; 944 | pqueue_pop_next(&client->pqueue, &out, &out_data, &out_len); 945 | 946 | // If we're in the GOAWAY state, stop writing if not high priority 947 | if (client->state == HH_GOAWAY && (out != client->pqueue.high_pri || out == NULL)) { 948 | return 0; 949 | } 950 | 951 | 952 | // Check if we have any DATA frames to write 953 | struct stream *s = NULL; 954 | if (out == NULL) { 955 | // If we have exhausted the client's window, stop sending 956 | if (client->window_size == 0) 957 | return 0; 958 | // Nothing to write - fulfil a request by sending DATA 959 | size_t size_requested = MIN(DATA_BUF_SIZE - HH_HEADER_SIZE, client->window_size); 960 | s = streamtab_schedule(&client->streams, &size_requested); 961 | if (s == NULL) // Otherwise, we have nothing to write 962 | break; 963 | assert(size_requested > 0); 964 | assert(client->window_size >= size_requested); 965 | client->window_size -= size_requested; 966 | size_requested += HH_HEADER_SIZE; 967 | if (request_fulfill(s, (uint8_t *)buf, &size_requested) < 0) { 968 | // TODO: Send RST_STREAM 969 | return -1; 970 | } 971 | out_data = buf; 972 | out_len = size_requested; 973 | } 974 | 975 | ssize_t nwritten; 976 | s2n_errno = S2N_ERR_T_OK; 977 | nwritten = s2n_send(client->tls, out_data, out_len, &client->blocked); 978 | if (nwritten < 0) { 979 | switch (s2n_error_get_type(s2n_errno)) { 980 | case S2N_ERR_T_CLOSED: 981 | change_state(client, HH_ALREADY_CLOSED); 982 | return -1; 983 | case S2N_ERR_T_BLOCKED: 984 | nwritten = 0; 985 | break; 986 | case S2N_ERR_T_IO: 987 | return blind_client(client, s2n_connection_get_delay(client->tls)); 988 | default: 989 | log_warn("s2n_send: %s", s2n_strerror(s2n_errno, "EN")); 990 | return blind_client(client, s2n_connection_get_delay(client->tls)); 991 | } 992 | } 993 | if (out_data == buf) { 994 | // A DATA frame was sent 995 | // Malloc and push onto write queue for later 996 | size_t remaining = out_len - nwritten; 997 | if (remaining == 0) { 998 | out = NULL; 999 | } else { 1000 | // TODO: Don't copy the whole 16k here if possible 1001 | out = pqueue_node_alloc(out_len); 1002 | memcpy(out->data, buf, out_len); 1003 | pqueue_submit_frame(&client->pqueue, out, HH_PRI_LOW); 1004 | } 1005 | } 1006 | if (out != NULL) 1007 | pqueue_report_write(&client->pqueue, out, nwritten); 1008 | } while (client->blocked == S2N_NOT_BLOCKED); 1009 | 1010 | // If we still have data remaining, signal EPOLLOUT 1011 | bool pending = client_pending_write(client); 1012 | if (pending && !client->is_write_blocked) { 1013 | client->is_write_blocked = true; 1014 | if (signal_epollout(client, true) < 0) 1015 | return -1; 1016 | } else if (!pending && client->is_write_blocked) { 1017 | // All data is now written, clear EPOLLOUT 1018 | client->is_write_blocked = false; 1019 | if (signal_epollout(client, false) < 0) 1020 | return -1; 1021 | } 1022 | return 0; 1023 | } 1024 | 1025 | int client_on_write_ready(struct client *client) { 1026 | switch (client->state) { 1027 | case HH_NEGOTIATING_TLS: 1028 | if (client->blocked == S2N_BLOCKED_ON_READ) 1029 | break; 1030 | if (do_negotiate(client) < 0) 1031 | goto error; 1032 | break; 1033 | case HH_WAITING_SETTINGS: // Keep sending SETTINGS frame 1034 | case HH_IDLE: 1035 | if (client_write_flush(client) < 0) 1036 | goto graceful_exit; 1037 | break; 1038 | case HH_WAITING_MAGIC: 1039 | case HH_GOAWAY: 1040 | if (client_write_flush(client) < 0) 1041 | goto error; 1042 | break; 1043 | case HH_TLS_SHUTDOWN: 1044 | case HH_ALREADY_CLOSED: 1045 | case HH_BLINDED: 1046 | break; 1047 | default: 1048 | #ifdef NDEBUG 1049 | __builtin_unreachable(); 1050 | #else 1051 | log_fatal("Unknown client state %d", client->state); 1052 | exit(-1); 1053 | #endif 1054 | } 1055 | return 0; 1056 | 1057 | graceful_exit: 1058 | initiate_graceful_close(client); 1059 | return 0; 1060 | error: 1061 | return -1; 1062 | } 1063 | 1064 | int client_on_data_received(struct client *client) { 1065 | switch (client->state) { 1066 | case HH_NEGOTIATING_TLS: 1067 | if (client->blocked == S2N_BLOCKED_ON_WRITE) { 1068 | log_warn("Unexpected data on client socket\n"); 1069 | goto error; 1070 | } 1071 | if (do_negotiate(client) < 0) 1072 | goto error; 1073 | break; 1074 | case HH_WAITING_MAGIC: 1075 | case HH_WAITING_SETTINGS: 1076 | case HH_IDLE: 1077 | if (do_read(client) < 0) 1078 | goto graceful_exit; 1079 | break; 1080 | case HH_GOAWAY: { 1081 | // TODO: Check s2n_recv for EOF 1082 | if (do_read(client) < 0) 1083 | goto graceful_exit; 1084 | break; 1085 | } 1086 | case HH_TLS_SHUTDOWN: 1087 | case HH_BLINDED: 1088 | // Ignore it 1089 | break; 1090 | default: 1091 | #ifdef NDEBUG 1092 | __builtin_unreachable(); 1093 | #else 1094 | log_fatal("Unknown client state %d", client->state); 1095 | exit(-1); 1096 | #endif 1097 | } 1098 | return 0; 1099 | 1100 | graceful_exit: 1101 | initiate_graceful_close(client); 1102 | return 0; 1103 | error: 1104 | return -1; 1105 | } 1106 | -------------------------------------------------------------------------------- /src/frame.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "frame.h" 7 | #include "client.h" 8 | #include "log.h" 9 | #include "pqueue.h" 10 | 11 | struct frame_goaway { 12 | struct frame_header header; 13 | uint32_t last_stream; 14 | uint32_t err_code; 15 | } __attribute__((packed)); 16 | 17 | struct frame_rst_stream { 18 | struct frame_header header; 19 | uint32_t err_code; 20 | } __attribute__((packed)); 21 | 22 | struct frame_settings { 23 | struct frame_header header; 24 | struct { 25 | uint16_t identifier; 26 | uint32_t value; 27 | } __attribute__((packed)) fields[]; 28 | } __attribute__((packed)); 29 | 30 | struct frame_ping { 31 | struct frame_header header; 32 | uint8_t data[8]; 33 | } __attribute__((packed)); 34 | 35 | void construct_frame_header(struct frame_header *hd, uint32_t length, uint8_t flags, uint8_t type, uint32_t stream_id) { 36 | hd->data[2] = length & 0xFF; 37 | hd->data[1] = (length >> 8) & 0xFF; 38 | hd->data[0] = (length >> 16) & 0xFF; 39 | hd->data[3] = type; 40 | hd->data[4] = flags; 41 | hd->data[8] = stream_id & 0xFF; 42 | hd->data[7] = (stream_id >> 8) & 0xFF; 43 | hd->data[6] = (stream_id >> 16) & 0xFF; 44 | hd->data[5] = (stream_id >> 24) & 0x7F; 45 | } 46 | 47 | int send_rst_stream(struct client *client, uint32_t stream_id, uint32_t err_code) { 48 | assert(stream_id != 0); 49 | struct pqueue_node *node = pqueue_node_alloc(sizeof(struct frame_rst_stream)); 50 | struct frame_rst_stream *rst_stream = (struct frame_rst_stream *)&node->data; 51 | memset(rst_stream, 0, sizeof *rst_stream); 52 | rst_stream->err_code = htonl(err_code); 53 | construct_frame_header(&rst_stream->header, 4, 0, HH_FT_RST_STREAM, stream_id); 54 | pqueue_submit_frame(&client->pqueue, node, HH_PRI_MED); 55 | return 0; 56 | } 57 | 58 | int send_goaway(struct client *client, uint32_t err_code) { 59 | struct pqueue_node *node = pqueue_node_alloc(sizeof(struct frame_goaway)); 60 | struct frame_goaway *goaway = (struct frame_goaway *)&node->data; 61 | memset(goaway, 0, sizeof *goaway); 62 | if (err_code == 0) 63 | goaway->last_stream = htonl(0x7FFFFFFF); // High bit reserved 64 | else 65 | goaway->last_stream = htonl(client->highest_stream_seen); 66 | goaway->err_code = htonl(err_code); 67 | construct_frame_header(&goaway->header, 8, 0, HH_FT_GOAWAY, 0); 68 | pqueue_submit_frame(&client->pqueue, node, HH_PRI_HIGH); 69 | return 0; 70 | } 71 | 72 | // Data must be 8 bytes long 73 | int send_ping(struct client *client, uint8_t *data, bool ack) { 74 | struct pqueue_node *node = pqueue_node_alloc(sizeof(struct frame_ping)); 75 | struct frame_ping *ping = (struct frame_ping *)&node->data; 76 | memset(ping, 0, sizeof *ping); 77 | memcpy(ping->data, data, 8); 78 | construct_frame_header(&ping->header, 8, ack, HH_FT_PING, 0); 79 | pqueue_submit_frame(&client->pqueue, node, HH_PRI_HIGH); 80 | return 0; 81 | } 82 | 83 | // TODO: Implement sending non-empty settings frame 84 | int send_settings(struct client *client, struct h2_settings *server_settings, bool ack) { 85 | (void)server_settings; 86 | struct pqueue_node *node = pqueue_node_alloc(sizeof(struct frame_settings)); 87 | struct frame_settings *settings = (struct frame_settings *)&node->data; 88 | memset(settings, 0, sizeof *settings); 89 | assert(server_settings == NULL); // Not implemented yet 90 | construct_frame_header(&settings->header, 0, ack, HH_FT_SETTINGS, 0); 91 | pqueue_submit_frame(&client->pqueue, node, HH_PRI_MED); 92 | return 0; 93 | } 94 | 95 | struct tmp_buf { 96 | char *data; 97 | size_t offset; 98 | }; 99 | 100 | static void header_encode_cb(enum hpack_event_e evt, const char *buf, size_t len, void *priv) { 101 | struct tmp_buf *out = priv; 102 | switch (evt) { 103 | case HPACK_EVT_DATA: 104 | memcpy(out->data + out->offset, buf, len); 105 | out->offset += len; 106 | break; 107 | default: 108 | break; 109 | } 110 | } 111 | 112 | #define MAX_HEADERS_BUF 512 113 | // TODO: Padding etc 114 | int send_headers(struct client *client, uint32_t stream_id, struct hpack_field *fields, size_t len, bool end_stream) { 115 | struct pqueue_node *node = pqueue_node_alloc(sizeof(struct frame_header) + MAX_HEADERS_BUF); 116 | struct tmp_buf buf = { .data = node->data + sizeof(struct frame_header), .offset = 0 }; 117 | char hpack_buf[MAX_HEADERS_BUF]; 118 | struct hpack_encoding enc = { 119 | .fld = fields, 120 | .fld_cnt = len, 121 | .buf = hpack_buf, 122 | .buf_len = MAX_HEADERS_BUF, 123 | .cb = header_encode_cb, 124 | .priv = &buf, 125 | .cut = 0 126 | }; 127 | int rv = hpack_encode(client->encoder, &enc); 128 | if (rv != HPACK_RES_OK) { 129 | log_debug("HPACK encoding failed (%s)", hpack_strerror(rv)); 130 | return -1; 131 | } 132 | construct_frame_header((struct frame_header *)node->data, buf.offset, 133 | HH_HEADERS_END_HEADERS | (end_stream ? HH_END_STREAM : 0), 134 | HH_FT_HEADERS, stream_id); 135 | pqueue_submit_frame(&client->pqueue, node, HH_PRI_MED); 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /src/hh.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "client.h" 25 | #include "log.h" 26 | #include "util.h" 27 | 28 | #define DEFAULT_PORT 8000 29 | #define MAX_EVENTS 64 30 | #define MAX_FD_QUEUE 256 31 | #ifndef WORKER_THREADS 32 | #define WORKER_THREADS 4 33 | #endif 34 | 35 | static volatile int fd_queue_head, fd_queue_tail; 36 | static int signal_fd, fd_queue[MAX_FD_QUEUE]; 37 | 38 | static pthread_mutex_t fd_queue_lock = PTHREAD_MUTEX_INITIALIZER; 39 | 40 | static const char *certificate_path = "data/cert.pem"; 41 | static const char *pkey_path = "data/pkey.pem"; 42 | 43 | struct s2n_config *server_config; 44 | 45 | static void sig_prepare(void) { 46 | sigset_t sigset; 47 | sigemptyset(&sigset); 48 | sigaddset(&sigset, SIGTERM); 49 | sigaddset(&sigset, SIGINT); 50 | sigaddset(&sigset, SIGQUIT); 51 | signal_fd = signalfd(-1, &sigset, SFD_NONBLOCK); 52 | if (signal_fd < 0) 53 | exit(-1); 54 | 55 | sigaddset(&sigset, SIGPIPE); 56 | pthread_sigmask(SIG_BLOCK, &sigset, NULL); 57 | } 58 | 59 | static int load_server_cert(void) { 60 | server_config = s2n_config_new(); 61 | if (server_config == NULL) { 62 | log_fatal("Failed to allocate s2n server config (check mlock permissions)"); 63 | return -1; 64 | } 65 | 66 | // Set config and cipher suite preferences 67 | if (s2n_config_set_cipher_preferences(server_config, "h2") < 0) { 68 | log_fatal("Cannot find 'h2' cipher suite: you may need to apply a custom patch to s2n."); 69 | return -1; 70 | } 71 | 72 | // Set ALPN to negotiate h2 (HTTP/2) protocol 73 | const char *proto_name = "h2"; 74 | if (s2n_config_set_protocol_preferences(server_config, &proto_name, 1) < 0) { 75 | log_fatal("Cannot set ALPN identifier to 'h2' (%s)", s2n_strerror(s2n_errno, "EN")); 76 | return -1; 77 | } 78 | 79 | FILE *cert_file = fopen(certificate_path, "r"); 80 | FILE *pkey_file = fopen(pkey_path, "r"); 81 | if (cert_file == NULL) { 82 | log_fatal("Call to fopen on certificate file failed (%s)", strerror(errno)); 83 | return -1; 84 | } else if (pkey_file == NULL) { 85 | log_fatal("Call to fopen on private key file failed (%s)", strerror(errno)); 86 | return -1; 87 | } 88 | 89 | // TODO: Don't leak on early return 90 | // Read certificate file into buffer 91 | fseek(cert_file, 0, SEEK_END); 92 | long cert_size = ftell(cert_file); 93 | fseek(cert_file, 0, SEEK_SET); 94 | char *cert_data = malloc(cert_size + 1); 95 | if (fread(cert_data, cert_size, 1, cert_file) != 1) { 96 | log_fatal("Call to fread on certificate file failed"); 97 | return -1; 98 | } 99 | cert_data[cert_size] = '\0'; 100 | 101 | // Read private key file into buffer 102 | fseek(pkey_file, 0, SEEK_END); 103 | long pkey_size = ftell(pkey_file); 104 | fseek(pkey_file, 0, SEEK_SET); 105 | char *pkey_data = malloc(pkey_size + 1); 106 | if (fread(pkey_data, pkey_size, 1, pkey_file) != 1) { 107 | log_fatal("Call to fread on private key file failed"); 108 | return -1; 109 | } 110 | pkey_data[pkey_size] = '\0'; 111 | 112 | if (s2n_config_add_cert_chain_and_key(server_config, cert_data, pkey_data) != 0) { 113 | log_fatal("Failed to add certificate/key to s2n (%s)", s2n_strerror(s2n_errno, "EN")); 114 | return -1; 115 | } else 116 | log_info("Successfully loaded certificate and private key file"); 117 | 118 | free(cert_data); 119 | free(pkey_data); 120 | fclose(cert_file); 121 | fclose(pkey_file); 122 | return 0; 123 | } 124 | 125 | // Returns -1 if need to drop connection 126 | static int queue_fd(int event_fd, int client_fd) { 127 | pthread_mutex_lock(&fd_queue_lock); 128 | int new_head = (fd_queue_head + 1) % MAX_FD_QUEUE; 129 | if (new_head == fd_queue_tail) { 130 | // No space in queue 131 | pthread_mutex_unlock(&fd_queue_lock); 132 | return -1; 133 | } else { 134 | log_debug("Queued FD at %d", fd_queue_head); 135 | fd_queue[fd_queue_head] = client_fd; 136 | fd_queue_head = new_head; 137 | } 138 | pthread_mutex_unlock(&fd_queue_lock); 139 | 140 | // Signal that we added one connection to the queue (wakes up epoll watchers) 141 | uint64_t val = 1; 142 | if (write(event_fd, &val, sizeof val) == -1 && errno == EINVAL) { 143 | log_fatal("Call to write(event_fd) failed (%s)", strerror(errno)); 144 | exit(-1); 145 | } 146 | return 0; 147 | } 148 | 149 | // TODO: Better load balancing - may need to rework this with one queue per thread 150 | static void consume_available_fd(int epoll_fd) { 151 | pthread_mutex_lock(&fd_queue_lock); 152 | // Otherwise we now have the lock 153 | if (fd_queue_tail != fd_queue_head) { 154 | // Grab the FD from the queue 155 | int new_fd = fd_queue[fd_queue_tail]; 156 | log_debug("Consumed FD at position %d", fd_queue_tail); 157 | fd_queue_tail = (fd_queue_tail + 1) % MAX_FD_QUEUE; 158 | pthread_mutex_unlock(&fd_queue_lock); 159 | 160 | // Initialise per-client timer 161 | int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); 162 | if (timer_fd < 0) { 163 | log_warn("Call to timerfd_create failed (%s)", strerror(errno)); 164 | close(new_fd); 165 | return; 166 | } 167 | 168 | // Add the FDs to our epoll collection 169 | struct epoll_event event; 170 | event.data.ptr = client_new(new_fd, timer_fd); // Might fail due to mlock limits 171 | if (event.data.ptr == NULL) { 172 | log_warn("Call to client_new failed"); 173 | close(new_fd); 174 | close(timer_fd); 175 | return; 176 | } 177 | 178 | event.events = CLIENT_EPOLL_EVENTS; 179 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &event) == -1) { 180 | log_warn("FD: %d", new_fd); 181 | log_warn("Call to epoll_ctl on socket fd failed (%s)", strerror(errno)); 182 | client_free(event.data.ptr); 183 | return; 184 | } 185 | 186 | // Little hack: if bit 0 of ptr is set, timer event was fired. 187 | // Otherwise an event was fired for the actual socket FD. 188 | assert(((uintptr_t)event.data.ptr & 1) == 0); 189 | event.data.ptr = (void *)((uintptr_t)event.data.ptr + 1); 190 | event.events = EPOLLIN | EPOLLET; 191 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_fd, &event) == -1) { 192 | log_warn("FD: %d", timer_fd); 193 | log_warn("Call to epoll_ctl on timer fd failed (%s)", strerror(errno)); 194 | client_free(event.data.ptr); 195 | return; 196 | } 197 | } else 198 | pthread_mutex_unlock(&fd_queue_lock); 199 | } 200 | 201 | static int process_all_incoming_connections(int *event_fds, int server_fd) { 202 | while (1) { 203 | struct sockaddr in_addr; 204 | socklen_t in_len; 205 | int client_fd; 206 | 207 | in_len = sizeof in_addr; 208 | client_fd = accept4(server_fd, &in_addr, &in_len, SOCK_NONBLOCK); 209 | if (client_fd == -1) { 210 | if (errno == EAGAIN || errno == EWOULDBLOCK) 211 | break; // Processed all connections 212 | else { 213 | log_warn("Call to accept failed (%s)", strerror(errno)); 214 | break; 215 | } 216 | } 217 | 218 | int flag = 1; 219 | if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof (int)) < 0) { 220 | log_warn("Call to setsockopt(TCP_NODELAY) failed (%s)", strerror(errno)); 221 | close(client_fd); 222 | break; 223 | } 224 | 225 | // Send FD to other threads 226 | static sig_atomic_t next_thread = 0; 227 | if (queue_fd(event_fds[next_thread], client_fd) < 0) { 228 | log_warn("No room in FD queue: dropping connection"); 229 | close(client_fd); 230 | } 231 | // Wraparound to first thread 232 | if (++next_thread == WORKER_THREADS) 233 | next_thread = 0; 234 | } 235 | 236 | return 0; 237 | } 238 | 239 | static void *worker_event_loop(void *state) { 240 | struct thread_state *ts = (struct thread_state *)state; 241 | int epoll_fd = epoll_create1(0); 242 | ts->epoll_fd = epoll_fd; 243 | struct epoll_event *events = calloc(MAX_EVENTS, sizeof(struct epoll_event)); 244 | 245 | int event_fd = ts->event_fd; 246 | events[0].data.ptr = &event_fd; 247 | events[0].events = EPOLLIN | EPOLLET; 248 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &events[0]) == -1) { 249 | log_fatal("Call to epoll_ctl failed (%s)", strerror(errno)); 250 | return NULL; 251 | } 252 | 253 | set_thread_state(state); 254 | free(state); 255 | 256 | // Add the signal_fd to our set to epoll 257 | events[0].data.ptr = &signal_fd; 258 | events[0].events = EPOLLIN | EPOLLET; 259 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, signal_fd, &events[0]) == -1) { 260 | log_fatal("Call to epoll_ctl failed (%s)", strerror(errno)); 261 | return NULL; 262 | } 263 | 264 | // Event loop 265 | log_debug("Spawned worker thread"); 266 | int should_exit = 0; 267 | while (!should_exit) { 268 | int n, i; 269 | n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); 270 | if (n < 0 && errno == EINTR) 271 | continue; // Need to recheck exit condition 272 | for (i = 0; i < n; i++) { 273 | // If the last bit of the pointer is set, the timer and not the socket is ready to use 274 | bool timer_expired = (uintptr_t)(events[i].data.ptr) & 1; 275 | struct client *client = (struct client *)((uintptr_t)events[i].data.ptr & ~1); 276 | if (events[i].events & EPOLLRDHUP) { 277 | client_close_immediate(client); 278 | continue; 279 | } else if (events[i].events & EPOLLHUP) { 280 | client_close_immediate(client); 281 | continue; 282 | } else if (events[i].events & EPOLLERR) { 283 | if (!timer_expired && client->fd != event_fd && client->fd != signal_fd) { 284 | int error = 0; 285 | socklen_t errlen = sizeof(error); 286 | if (getsockopt(client->fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen) == 0) { 287 | log_warn("Epoll event error (%s: flags %d)", strerror(error), events[i].events); 288 | } else 289 | log_warn("Epoll event error on socket (flags %d)", events[i].events); 290 | } else 291 | log_warn("Epoll event error (flags %d)", events[i].events); 292 | client_close_immediate(client); 293 | continue; 294 | } 295 | if (events[i].events & EPOLLIN) { 296 | if (client->fd == event_fd) { 297 | uint64_t efd_read; 298 | if (read(event_fd, &efd_read, sizeof efd_read) > 0) 299 | consume_available_fd(epoll_fd); 300 | continue; 301 | } else if (timer_expired) { 302 | if (client_on_timer_expired(client) < 0) { 303 | client_close_immediate(client); 304 | } 305 | continue; 306 | } else if (client->fd == signal_fd) { 307 | should_exit = 1; // Maybe we should check which signal? 308 | break; 309 | } else if (client_on_data_received(client) < 0) { 310 | continue; // Client has been immediately closed 311 | } 312 | } 313 | if (events[i].events & EPOLLOUT || client_pending_write(client)) { 314 | if (client_on_write_ready(client) < 0) { 315 | continue; // Client has been immediately closed 316 | } 317 | } 318 | if (client->is_closing) { 319 | if (client_close_graceful(client) < 0) { 320 | client_close_immediate(client); 321 | } 322 | } 323 | } 324 | } 325 | 326 | // TODO: Call s2n_cleanup here 327 | 328 | free(events); 329 | return NULL; 330 | } 331 | 332 | static int server_init(unsigned short port) { 333 | int server_fd, yes = 1, rv = 0; 334 | struct addrinfo hints, *servinfo, *p; 335 | 336 | memset(&hints, 0, sizeof hints); 337 | hints.ai_family = AF_UNSPEC; 338 | hints.ai_socktype = SOCK_STREAM; 339 | hints.ai_flags = AI_PASSIVE; 340 | 341 | char port_buf[6]; 342 | snprintf(port_buf, 6, "%hu", port); 343 | 344 | if ((rv = getaddrinfo(NULL, port_buf, &hints, &servinfo)) != 0) { 345 | log_fatal("Call to getaddrinfo failed (%s)", gai_strerror(rv)); 346 | return -1; 347 | } 348 | 349 | // Bind to the first available 350 | for(p = servinfo; p != NULL; p = p->ai_next) { 351 | if ((server_fd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) { 352 | log_warn("Call to socket failed (%s)", strerror(errno)); 353 | continue; 354 | } 355 | 356 | if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { 357 | log_warn("Call to setsockopt failed (%s)", strerror(errno)); 358 | return -1; 359 | } 360 | 361 | if (bind(server_fd, p->ai_addr, p->ai_addrlen) == -1) { 362 | close(server_fd); 363 | log_warn("Call to bind failed (%s)", strerror(errno)); 364 | continue; 365 | } 366 | 367 | break; 368 | } 369 | 370 | freeaddrinfo(servinfo); 371 | 372 | if (p == NULL) { 373 | log_fatal("Could not bind to any available socket"); 374 | return -1; 375 | } 376 | 377 | // Initialise s2n 378 | if ((rv = s2n_init()) != 0) { 379 | log_fatal("Call to s2n_init failed (%s)", s2n_strerror(s2n_errno, "EN")); 380 | return -1; 381 | } 382 | 383 | if (load_server_cert() != 0) { 384 | log_fatal("Failed to load server certificate and private key file"); 385 | return -1; 386 | } 387 | 388 | return server_fd; 389 | } 390 | 391 | static int server_listen(int server_fd, unsigned short port) { 392 | assert(server_fd >= 0); 393 | 394 | if (listen(server_fd, SOMAXCONN) == -1) { 395 | log_fatal("Call to listen failed (%s)", strerror(errno)); 396 | return -1; 397 | } 398 | 399 | int epoll_fd = epoll_create1(0); 400 | struct epoll_event event; 401 | memset(&event, 0, sizeof event); 402 | event.data.fd = server_fd; 403 | event.events = EPOLLIN | EPOLLET; 404 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) { 405 | log_fatal("Call to epoll_ctl failed (%s)", strerror(errno)); 406 | return -1; 407 | } 408 | 409 | // Prepare signals 410 | sig_prepare(); 411 | event.data.fd = signal_fd; 412 | event.events = EPOLLIN | EPOLLET; 413 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, signal_fd, &event) == -1) { 414 | log_fatal("Call to epoll_ctl failed (%s)", strerror(errno)); 415 | return -1; 416 | } 417 | 418 | // Initialise eventfds (one per worker thread) 419 | int *event_fds = malloc(sizeof(int) * WORKER_THREADS); 420 | for (size_t i = 0; i < WORKER_THREADS; i++) { 421 | event_fds[i] = eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE); 422 | } 423 | 424 | log_info("Server waiting for connections on localhost:%hu...", port); 425 | 426 | // Start worker threads 427 | pthread_t worker_threads[WORKER_THREADS]; 428 | for (size_t i = 0; i < WORKER_THREADS; i++) { 429 | struct thread_state *state = malloc(sizeof *state); 430 | state->event_fd = event_fds[i]; 431 | state->epoll_fd = -1; 432 | if ((errno = pthread_create(&worker_threads[i], NULL, worker_event_loop, state)) != 0) { 433 | log_fatal("Call to pthread_create failed (%s)", strerror(errno)); 434 | free(event_fds); 435 | return -1; 436 | } 437 | } 438 | 439 | 440 | // Event loop 441 | while (1) { 442 | int n = epoll_wait(epoll_fd, &event, 1, -1); 443 | if (n == 0) 444 | continue; 445 | else if (n < 0) { 446 | if (errno == EINTR) 447 | continue; // Need to recheck exit condition 448 | log_fatal("Call to epoll_wait failed (%s)", strerror(errno)); 449 | break; 450 | } 451 | 452 | // Now we have one event, in &event 453 | if (event.data.fd == signal_fd) { 454 | break; 455 | } else if (event.events & EPOLLRDHUP || event.events & EPOLLHUP || event.events & EPOLLERR || !(event.events & EPOLLIN)) { 456 | log_fatal("Epoll event error (flags %d)", event.events); 457 | break; 458 | } else if (server_fd == event.data.fd) { 459 | // We have incoming connections 460 | if (process_all_incoming_connections(event_fds, server_fd) < 0) 461 | return -1; 462 | } else { 463 | log_warn("Epoll event unknown (%d)", event.events); 464 | return -1; 465 | } 466 | } 467 | 468 | log_info("Server shutting down..."); 469 | if (close(epoll_fd) == -1) { 470 | log_fatal("Call to close(epoll_fd) failed (%s)", strerror(errno)); 471 | return -1; 472 | } 473 | 474 | // Wait for worker threads to shut down 475 | for (size_t i = 0; i < WORKER_THREADS; i++) { 476 | if ((errno = pthread_join(worker_threads[i], NULL)) != 0) { 477 | log_fatal("Call to pthread_join failed (%s)", strerror(errno)); 478 | return -1; 479 | } 480 | if (close(event_fds[i]) == -1) { 481 | log_fatal("Failed to close eventfd of thread %zu (%s)", i, strerror(errno)); 482 | return -1; 483 | } 484 | } 485 | 486 | free(event_fds); 487 | 488 | if (close(signal_fd) == -1) { 489 | log_fatal("Call to close(signal_fd) failed (%s)", strerror(errno)); 490 | return -1; 491 | } 492 | 493 | return 0; 494 | } 495 | 496 | static int server_cleanup(int server_fd) { 497 | int rv = 0; 498 | if (close(server_fd) == -1) { 499 | log_fatal("Call to close(server_fd) failed (%s)", strerror(errno)); 500 | return -1; 501 | } 502 | s2n_config_free(server_config); 503 | if ((rv = s2n_cleanup()) != 0) { 504 | log_fatal("Call to s2n_cleanup on main thread failed (%s)", s2n_strerror(s2n_errno, "EN")); 505 | return -1; 506 | } 507 | return 0; 508 | } 509 | 510 | int main(int argc, char *argv[]) { 511 | int fd; 512 | // Parse port number from command line option 513 | unsigned short port_num = DEFAULT_PORT; 514 | if (argc > 2) { 515 | log_fatal("Usage: hh [port]"); 516 | return -1; 517 | } else if (argc == 2) { 518 | char *endptr; 519 | long temp = strtoul(argv[1], &endptr, 10); 520 | if (*endptr != '\0' || temp > USHRT_MAX) { 521 | log_fatal("Invalid port number '%s'.", argv[1]); 522 | return -1; 523 | } else 524 | port_num = (unsigned short)temp; 525 | } 526 | if ((fd = server_init(port_num)) < 0) 527 | return -1; 528 | else if (server_listen(fd, port_num) < 0) 529 | return -1; 530 | else if (server_cleanup(fd) < 0) 531 | return -1; 532 | return 0; 533 | } 534 | 535 | -------------------------------------------------------------------------------- /src/pqueue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "pqueue.h" 8 | #include "frame.h" 9 | #include "log.h" 10 | 11 | void pqueue_init(struct pqueue *pqueue) { 12 | pqueue->high_pri = NULL; 13 | pqueue->med_pri = NULL; 14 | pqueue->low_pri = NULL; 15 | pqueue->write_head = HH_PRI_NONE; 16 | } 17 | 18 | void pqueue_free(struct pqueue *pqueue) { 19 | free(pqueue->high_pri); 20 | free(pqueue->med_pri); 21 | free(pqueue->low_pri); 22 | } 23 | 24 | struct pqueue_node *pqueue_node_alloc(size_t len) { 25 | size_t alloc_len = len + sizeof(struct pqueue_node); 26 | struct pqueue_node *rv = malloc(alloc_len); 27 | rv->nwritten = 0; 28 | rv->next = NULL; 29 | return rv; 30 | } 31 | 32 | void pqueue_node_free(struct pqueue_node *frame) { 33 | free(frame); 34 | } 35 | 36 | static size_t frame_size(struct pqueue_node *frame) { 37 | uint32_t tmp = 0; 38 | memcpy(&tmp, frame->data, 3); // Length is first 3 bytes of 'data' 39 | tmp = ntohl(tmp << 8) & 0x00FFFFFF; 40 | return (size_t)tmp + HH_HEADER_SIZE; 41 | } 42 | 43 | #if 0 44 | static void debug_log_frame(struct pqueue_node *frame) { 45 | uint8_t type = frame->data[3]; 46 | uint32_t s_id = 0; 47 | memcpy(&s_id, frame->data + 5, 4); 48 | s_id = ntohl(s_id); 49 | log_debug("Type %u, ID %u", type, s_id); 50 | } 51 | #endif 52 | 53 | int pqueue_submit_frame(struct pqueue *pqueue, struct pqueue_node *frame, enum pqueue_pri priority) { 54 | struct pqueue_node **head = NULL, *tmp; 55 | assert(priority != HH_PRI_NONE); 56 | assert(frame != NULL && frame->next == NULL); 57 | switch (priority) { 58 | case HH_PRI_HIGH: 59 | head = &pqueue->high_pri; 60 | break; 61 | case HH_PRI_MED: 62 | head = &pqueue->med_pri; 63 | break; 64 | case HH_PRI_LOW: 65 | head = &pqueue->low_pri; 66 | break; 67 | default: 68 | // TODO: Cleanup 69 | log_trace(); 70 | __builtin_unreachable(); 71 | break; 72 | } 73 | 74 | if (*head == NULL) 75 | *head = frame; 76 | else { 77 | tmp = *head; 78 | while (tmp->next != NULL) 79 | tmp = tmp->next; 80 | tmp->next = frame; // Append 81 | } 82 | return 0; 83 | } 84 | 85 | int pqueue_pop_next(struct pqueue *pqueue, struct pqueue_node **out_frame, char **out_data, size_t *out_len) { 86 | struct pqueue_node **head = NULL; 87 | switch (pqueue->write_head) { 88 | case HH_PRI_HIGH: 89 | head = &pqueue->high_pri; 90 | break; 91 | case HH_PRI_MED: 92 | head = &pqueue->med_pri; 93 | break; 94 | case HH_PRI_LOW: 95 | head = &pqueue->low_pri; 96 | break; 97 | case HH_PRI_NONE: 98 | // Take whichever queue has the highest priority 99 | if (pqueue->high_pri != NULL) 100 | head = &pqueue->high_pri; 101 | else if (pqueue->med_pri != NULL) 102 | head = &pqueue->med_pri; 103 | else if (pqueue->low_pri != NULL) 104 | head = &pqueue->low_pri; 105 | else { 106 | // Nothing to write 107 | *out_frame = NULL; 108 | return -1; 109 | } 110 | break; 111 | } 112 | 113 | // Debugging 114 | if (pqueue->write_head == HH_PRI_NONE) 115 | assert((*head)->nwritten == 0); 116 | 117 | size_t total_frame_len = frame_size(*head); 118 | assert(total_frame_len > (*head)->nwritten); 119 | *out_data = (*head)->data + (*head)->nwritten; 120 | *out_len = total_frame_len - (*head)->nwritten; 121 | *out_frame = (*head); 122 | return 0; 123 | } 124 | 125 | int pqueue_report_write(struct pqueue *pqueue, struct pqueue_node *frame, size_t len_written) { 126 | struct pqueue_node **origin; 127 | enum pqueue_pri pri; 128 | if (frame == pqueue->high_pri) { 129 | origin = &pqueue->high_pri; 130 | pri = HH_PRI_HIGH; 131 | } else if (frame == pqueue->med_pri) { 132 | origin = &pqueue->med_pri; 133 | pri = HH_PRI_MED; 134 | } else if (frame == pqueue->low_pri) { 135 | origin = &pqueue->low_pri; 136 | pri = HH_PRI_LOW; 137 | } else { 138 | log_trace(); 139 | abort(); 140 | } 141 | 142 | //debug_log_frame(frame); 143 | 144 | frame->nwritten += len_written; 145 | if (frame->nwritten >= frame_size(frame)) { 146 | // Frame fully written, clean up 147 | struct pqueue_node *tmp = frame->next; 148 | pqueue_node_free(frame); 149 | *origin = tmp; 150 | pqueue->write_head = HH_PRI_NONE; // Free up the other queues for writing 151 | } else 152 | pqueue->write_head = pri; 153 | return 0; 154 | 155 | } 156 | 157 | bool pqueue_is_data_pending(struct pqueue *pqueue) { 158 | return pqueue->high_pri || pqueue->med_pri || pqueue->low_pri; 159 | } 160 | -------------------------------------------------------------------------------- /src/request.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "request.h" 11 | #include "stream.h" 12 | #include "cashpack.h" 13 | #include "frame.h" 14 | #include "log.h" 15 | 16 | #define MAX_HEADER_FIELDS 64 17 | #define HEADER(name, value) fields[pos++] = (struct hpack_field){ \ 18 | .nam = (name), \ 19 | .val = (value), \ 20 | .flg = HPACK_FLG_TYP_LIT | HPACK_FLG_NAM_HUF | HPACK_FLG_VAL_HUF \ 21 | } 22 | 23 | // TODO: Better allocation 24 | void request_send_headers(struct client *client, struct stream *stream) { 25 | struct hpack_field fields[MAX_HEADER_FIELDS]; 26 | char content_length[10] = { 0 }; 27 | size_t pos = 0; 28 | bool end_stream = false; 29 | 30 | switch (stream->req.status_code) { 31 | case 500: 32 | HEADER(":status", "500"); 33 | end_stream = true; 34 | break; 35 | case 404: 36 | HEADER(":status", "404"); 37 | end_stream = true; 38 | break; 39 | case 400: 40 | HEADER(":status", "400"); 41 | end_stream = true; 42 | break; 43 | case 200: 44 | HEADER(":status", "200"); 45 | // Set content-type depending on file extension 46 | char *extension = strrchr(stream->req.pathptr, '.'); 47 | if (extension == NULL) { 48 | log_warn("Retrieved file '%s' with no extension.", stream->req.pathptr); 49 | } else { 50 | extension++; 51 | if (strcmp(extension, "html") == 0) 52 | HEADER("content-type", "text/html"); 53 | else if (strcmp(extension, "js") == 0) 54 | HEADER("content-type", "application/javascript"); 55 | else if (strcmp(extension, "css") == 0) 56 | HEADER("content-type", "text/css"); 57 | else if (strcmp(extension, "png") == 0) 58 | HEADER("content-type", "image/png"); 59 | else if (strcmp(extension, "jpg") == 0) 60 | HEADER("content-type", "image/jpg"); 61 | } 62 | 63 | struct stat statbuf; 64 | if (fstat(stream->req.fd, &statbuf) < 0) { 65 | log_warn("Call to fstat failed (%s)", strerror(errno)); 66 | } else { 67 | snprintf(content_length, 10, "%zu", statbuf.st_size); 68 | HEADER("content-length", content_length); 69 | stream->req.bytes_remaining = statbuf.st_size; 70 | } 71 | 72 | break; 73 | default: 74 | log_trace(); 75 | //__builtin_unreachable(); 76 | } 77 | 78 | assert(pos < MAX_HEADER_FIELDS); 79 | 80 | if (end_stream) { 81 | stream_change_state(stream, HH_STREAM_HCLOSED_LOCAL); 82 | //stream->req.state = HH_REQ_DONE; 83 | } else 84 | stream->req.state = HH_REQ_IN_PROGRESS; 85 | 86 | if (send_headers(client, stream->id, fields, pos, end_stream) != 0) 87 | abort(); 88 | } 89 | 90 | // TODO: Padding etc 91 | int request_fulfill(struct stream *s, uint8_t *buf, size_t *max_size) { 92 | assert(s->req.status_code == 200); 93 | assert(s->req.fd != -1); 94 | assert(*max_size != 0 && *max_size + HH_HEADER_SIZE < (1 << 25)); 95 | assert(*max_size > HH_HEADER_SIZE); // TODO: Remove this assert 96 | uint8_t *ptr = buf + HH_HEADER_SIZE; // Move forwards 9 bytes for frame header 97 | size_t remaining = *max_size - HH_HEADER_SIZE; 98 | ssize_t nwritten; 99 | uint32_t total_nwritten = 0; 100 | uint8_t flags = 0; 101 | while ((nwritten = read(s->req.fd, ptr, remaining)) > 0) { 102 | size_t abs_nwritten = (nwritten > 0) ? (size_t)nwritten : 0; 103 | remaining -= abs_nwritten; 104 | s->req.bytes_remaining -= abs_nwritten; 105 | ptr += abs_nwritten; 106 | total_nwritten += abs_nwritten; 107 | if (remaining == 0) 108 | break; 109 | } 110 | if (nwritten == 0 || s->req.bytes_remaining == 0) { 111 | // EOF, set END_STREAM and close 112 | // Will automagically go to HH_STREAM_CLOSED if already in HH_STREAM_HCLOSED_REMOTE 113 | stream_change_state(s, HH_STREAM_HCLOSED_LOCAL); 114 | flags |= HH_END_STREAM; 115 | s->req.state = HH_REQ_DONE; 116 | if (close(s->req.fd) != 0) 117 | log_warn("Call to close failed (%s)", strerror(errno)); 118 | s->req.fd = -1; 119 | } else if (nwritten < 0) { 120 | log_warn("Call to read failed (%s)", strerror(errno)); 121 | return -1; 122 | } 123 | construct_frame_header((struct frame_header *)buf, total_nwritten, flags, HH_FT_DATA, s->id); 124 | *max_size = total_nwritten + HH_HEADER_SIZE; 125 | return 0; 126 | } 127 | 128 | void request_cleanup(struct request *req) { 129 | int rv = close(req->fd); 130 | req->state = HH_REQ_DONE; 131 | if (rv != 0) { 132 | log_warn("Call to close failed (%s)", strerror(errno)); 133 | } 134 | req->fd = -1; 135 | } 136 | -------------------------------------------------------------------------------- /src/stream.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "stream.h" 9 | #include "util.h" 10 | #include "log.h" 11 | 12 | #define STREAMTAB_INITIAL_LEN 64 13 | 14 | // Shamelessly stolen from NGHTTP2 15 | // https://github.com/nghttp2/nghttp2/blob/master/lib/nghttp2_map.c#L86 16 | static uint32_t hash(int32_t key, uint32_t mod) { 17 | uint32_t h = (uint32_t)key; 18 | h ^= (h >> 20) ^ (h >> 12); 19 | h ^= (h >> 7) ^ (h >> 4); 20 | return h & (mod - 1); 21 | } 22 | 23 | 24 | void streamtab_alloc(struct streamtab *tab) { 25 | tab->entries = 0; 26 | tab->len = STREAMTAB_INITIAL_LEN; 27 | tab->streams = calloc(tab->len, sizeof(struct stream *)); 28 | tab->root = stream_alloc(); 29 | tab->streams[hash(0, tab->len)] = tab->root; 30 | memset(tab->root, 0, sizeof(struct stream)); 31 | tab->root->weight = 256; 32 | tab->root->state = HH_STREAM_CLOSED; 33 | tab->root->req.fd = -1; 34 | } 35 | 36 | #if 0 37 | static void __print_streams(struct stream *s, int depth) { 38 | if (s == NULL) 39 | return; 40 | if (s->req.state != HH_REQ_DONE) 41 | log_debug("%*sStream ID %u, Req %u", depth, "", s->id, s->req.state); 42 | __print_streams(s->children, depth/* + 1*/); 43 | __print_streams(s->siblings, depth); 44 | } 45 | 46 | static void print_streams(struct streamtab *tab) { 47 | struct stream *stream = streamtab_root(tab); 48 | __print_streams(stream, 0); 49 | } 50 | 51 | static void resize(struct streamtab *tab) { 52 | (void)tab; 53 | } 54 | #endif 55 | 56 | void streamtab_insert(struct streamtab *tab, struct stream *stream) { 57 | // TODO: Resize if load is too high 58 | assert(stream->next == NULL); 59 | uint32_t idx = hash(stream->id, tab->len); 60 | stream->next = tab->streams[idx]; 61 | tab->streams[idx] = stream; 62 | tab->entries++; 63 | } 64 | 65 | struct stream *streamtab_find_id(struct streamtab *tab, uint32_t stream_id) { 66 | uint32_t idx = hash(stream_id, tab->len); 67 | struct stream *tmp; 68 | for (tmp = tab->streams[idx]; tmp != NULL; tmp = tmp->next) { 69 | if (tmp->id == stream_id) 70 | return tmp; 71 | } 72 | return NULL; 73 | } 74 | 75 | // TODO: Implement scheduling based on weight and priority; don't use recursion 76 | static struct stream *find_active(struct stream *s) { 77 | if (s == NULL) 78 | return NULL; 79 | else if (s->req.state == HH_REQ_IN_PROGRESS && s->window_size > 0) 80 | return s; 81 | struct stream *rv = find_active(s->siblings); 82 | if (rv != NULL) 83 | return rv; 84 | return find_active(s->children); 85 | } 86 | 87 | struct stream *streamtab_schedule(struct streamtab *tab, size_t *out_bytes) { 88 | struct stream *stream = find_active(streamtab_root(tab)); 89 | if (stream == NULL) 90 | return NULL; 91 | if (*out_bytes == ULONG_MAX) // Dummy call 92 | return stream; 93 | // TODO: Padding for DATA frames needs to be taken into account for flow control if added in the future 94 | size_t max_read = MIN(MIN(stream->window_size, *out_bytes), stream->req.bytes_remaining); 95 | stream->window_size -= max_read; 96 | *out_bytes = max_read; 97 | return stream; 98 | } 99 | 100 | void streamtab_free(struct streamtab *tab) { 101 | for (uint32_t i = 0; i < tab->len; i++) { 102 | struct stream *tmp, *next; 103 | for (tmp = tab->streams[i]; tmp != NULL; tmp = next) { 104 | next = tmp->next; 105 | stream_free(tmp); 106 | } 107 | } 108 | free(tab->streams); 109 | } 110 | 111 | struct stream *stream_alloc(void) { 112 | struct stream *rv = malloc(sizeof *rv); 113 | memset(rv, 0, sizeof *rv); 114 | rv->state = HH_STREAM_IDLE; 115 | rv->req.fd = -1; 116 | rv->req.state = HH_REQ_NOT_STARTED; 117 | return rv; 118 | } 119 | 120 | void stream_add_child(struct stream *stream, struct stream *child) { 121 | assert(child->parent == NULL); 122 | assert(child->siblings == NULL); 123 | assert(child->children == NULL); 124 | child->parent = stream; 125 | child->siblings = stream->children; 126 | stream->children = child; 127 | } 128 | 129 | void stream_add_exclusive_child(struct stream *stream, struct stream *child) { 130 | assert(child->parent == NULL); 131 | assert(child->siblings == NULL); 132 | assert(child->children == NULL); 133 | child->parent = stream; 134 | if (stream->children == NULL) 135 | stream->children = child; 136 | else { 137 | child->children = stream->children; 138 | stream->children = child; 139 | } 140 | } 141 | 142 | int stream_change_state(struct stream *stream, enum stream_state new_state) { 143 | assert(stream->id != 0); 144 | 145 | if (new_state == HH_STREAM_CLOSED && stream->req.state == HH_REQ_IN_PROGRESS) { 146 | stream->req.state = HH_REQ_DONE; 147 | request_cleanup(&stream->req); 148 | } 149 | 150 | switch (stream->state) { 151 | case HH_STREAM_IDLE: 152 | switch (new_state) { 153 | case HH_STREAM_HCLOSED_REMOTE: 154 | case HH_STREAM_OPEN: 155 | stream->state = new_state; 156 | break; 157 | default: 158 | goto verybad; 159 | } 160 | break; 161 | case HH_STREAM_OPEN: 162 | switch (new_state) { 163 | case HH_STREAM_OPEN: 164 | break; 165 | case HH_STREAM_CLOSED: 166 | stream->state = new_state; 167 | // TODO: Reprioritise children 168 | break; 169 | case HH_STREAM_HCLOSED_REMOTE: 170 | case HH_STREAM_HCLOSED_LOCAL: 171 | stream->state = new_state; 172 | break; 173 | default: 174 | goto verybad; 175 | } 176 | break; 177 | case HH_STREAM_HCLOSED_REMOTE: 178 | switch (new_state) { 179 | case HH_STREAM_HCLOSED_LOCAL: 180 | // Go to CLOSED instead 181 | stream->state = HH_STREAM_CLOSED; 182 | stream->how_closed.remote = 0; 183 | stream->how_closed.rst_stream = 0; 184 | break; 185 | case HH_STREAM_CLOSED: 186 | stream->state = new_state; 187 | stream->how_closed.remote = 0; // TODO: Get this information somehow 188 | stream->how_closed.rst_stream = 1; 189 | break; 190 | default: 191 | goto verybad; 192 | } 193 | break; 194 | case HH_STREAM_HCLOSED_LOCAL: 195 | switch (new_state) { 196 | case HH_STREAM_HCLOSED_REMOTE: 197 | // Go to CLOSED instead 198 | stream->state = HH_STREAM_CLOSED; 199 | stream->how_closed.remote = 1; 200 | stream->how_closed.rst_stream = 0; 201 | break; 202 | case HH_STREAM_CLOSED: 203 | stream->state = new_state; 204 | stream->how_closed.remote = 0; // TODO: Get this information somehow 205 | stream->how_closed.rst_stream = 1; 206 | break; 207 | default: 208 | goto verybad; 209 | } 210 | break; 211 | default: 212 | verybad: 213 | // TODO: Error handle 214 | log_debug("Unknown stream state transition from %d to %d", stream->state, new_state); 215 | abort(); 216 | break; 217 | } 218 | return 0; 219 | } 220 | 221 | void stream_free(struct stream *stream) { 222 | if (stream->req.fd != -1) { 223 | int rv = close(stream->req.fd); 224 | if (rv != 0) { 225 | log_warn("Call to close failed (%s)", strerror(errno)); 226 | } 227 | } 228 | free(stream); 229 | } 230 | -------------------------------------------------------------------------------- /test/test_connect.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import unittest 3 | import time 4 | import ssl 5 | 6 | def connect_tls(): 7 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | s = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_TLS) 9 | try: 10 | s.connect(("localhost", 8000)) 11 | except: 12 | s.shutdown(socket.SHUT_RDWR) 13 | s.close() 14 | return s 15 | 16 | def connect_h2(): 17 | sock = connect_tls() 18 | sock.sendall(bytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", "ascii")) 19 | return sock 20 | 21 | 22 | class TestConnect(unittest.TestCase): 23 | def test_connection_preface(self): 24 | sock = connect_tls() 25 | sock.sendall(bytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", "ascii")) 26 | sock.close() 27 | 28 | def test_initial_settings(self): 29 | sock = connect_h2() 30 | sock.sendall(bytes([0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00])) 31 | sock.close() 32 | 33 | if __name__ == "__main__": 34 | unittest.main() 35 | --------------------------------------------------------------------------------