diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 99eed18..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Documentation: https://github.com/codecov/support/wiki/codecov.yml - -coverage: - precision: 3 - round: down - range: 80...100 - - status: - # Learn more at https://codecov.io/docs#yaml_default_commit_status - project: off - patch: - default: - informational: true - changes: off - - -comment: false diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index decb0cd..0000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*.{c,h,d,di,dd,json}] -end_of_line = lf -insert_final_newline = true -indent_style = tab -indent_size = 4 -trim_trailing_whitespace = true -charset = utf-8 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94f2657..12a3096 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,31 +9,90 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest ] - dc: [ dmd-latest, dmd-2.094.2, dmd-2.089.1, dmd-2.084.1, ldc-1.24.0, ldc-1.21.0, ldc-1.17.0 ] + os: + - ubuntu-latest + dc: + - dmd-latest + - dmd-2.097.1 + - dmd-2.096.1 + - dmd-2.093.1 + - dmd-2.091.1 + - ldc-latest + - ldc-1.27.1 + - ldc-1.26.0 + - ldc-1.23.0 + - ldc-1.21.0 + parts: + - 'builds,unittests,examples,tests' + extra_dflags: + - '' include: - # Default - - { parts: 'builds,unittests,examples,tests', extra_dflags: '' } # Custom part for coverage - - { dc: dmd-2.094.2, parts: 'unittests,tests', extra_dflags: "-cov -version=VibedSetCoverageMerge" } + - { os: ubuntu-latest, dc: dmd-latest, parts: 'unittests,tests', extra_dflags: "-cov -version=VibedSetCoverageMerge" } + # Custom part for vibe-core 1.x.x testing + - { os: ubuntu-latest, dc: dmd-latest, parts: 'builds,unittests,tests', extra_dflags: '' } runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Prepare compiler uses: dlang-community/setup-dlang@v1 with: compiler: ${{ matrix.dc }} + dub: 1.29.0 - name: '[POSIX] Run tests' env: - PARTS: builds,unittests,examples,tests + VIBED_DRIVER: vibe-core + PARTS: ${{ matrix.parts }} run: | - ./travis-ci.sh + ./run-ci.sh - name: '[DMD] Upload coverage to Codecov' if: matrix.dc == 'dmd-latest' uses: codecov/codecov-action@v1 + + + main_win: + name: Run Windows + strategy: + fail-fast: false + matrix: + os: + - windows-latest + dc: + - dmd-latest + - dmd-2.097.1 + - dmd-2.096.1 + - dmd-2.093.1 + - dmd-2.091.1 + - ldc-latest + - ldc-1.27.1 + - ldc-1.26.0 + - ldc-1.23.0 + - ldc-1.21.0 + parts: + - 'builds,unittests,examples,tests' + extra_dflags: + - '' + + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v3 + + - name: Prepare compiler + uses: dlang-community/setup-dlang@v1 + with: + compiler: ${{ matrix.dc }} + dub: 1.29.0 + + - name: '[POSIX] Run tests' + env: + PARTS: ${{ matrix.parts }} + run: | + ./run-ci.sh diff --git a/.gitignore b/.gitignore index 36b83de..adca066 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,48 @@ -/libevent.dll -/dub.selections.json -/http-test-library.exe +*.[oa] +*.so +*.lib +*.dll +.*.sw* +*.lst +*.map +*.pdb +*.suo +*.sln +*.visualdproj +*.exe_cv + +docs.json +/docs +__dummy.html + .dub -libvibe-http.a -vibe-http-test-library +dub.selections.json + +# Auto-generated +tls/openssl_version.d + +# Auto-downloaded 3rd-party (Meson) +lib/subprojects/allocator +lib/subprojects/diet/ +lib/subprojects/openssl/ + +# Mono-D files +*.userprefs + +# Unittest binaries +vibe-d +tests/*/tests +__test__*__ +vibe-d-test* +vibe-d-*-test* + +# Examples +examples/*/*-example +examples/app_skeleton/app-skeleton +examples/app_skeleton/__test__library__ +examples/bench-http-request/bench-http-request +examples/bench-http-server/bench-http-server +examples/bench-mongodb/bench-mongodb +examples/bench-urlrouter/bench-urlrouter +*.exe + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 557a87e..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,14 +0,0 @@ -Guidelines for Contributing -=========================== - -Welcome to the vibe.d HTTP repository and thank you for your interest in contributing to its development! - -When contributing pull requests, the following points should ideally apply: - - - Each pull request should contain only one isolated functional change - - The code adheres to the [style guide](http://vibed.org/style-guide) - - For the occasional more complex pull request each change should be separated into its own commit - - Try not to mix whitespace or style changes with functional changes in the same commit - - The pull request must pass the test suite (run `dub test` to test locally) - -Exceptions to these rules are accepted all the time, but please try to follow them as closely as possible, because otherwise it often considerably increases the total amount of work and communication overhead. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 0cae710..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2012-2018 Sönke Ludwig - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/LICENSE_DE.txt b/LICENSE_DE.txt deleted file mode 100644 index 94193a5..0000000 --- a/LICENSE_DE.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2012-2018 Sönke Ludwig - -Hiermit wird unentgeltlich, jeder Person, die eine Kopie der Software und der zugehörigen Dokumentationen (die "Software") erhält, die Erlaubnis erteilt, uneingeschränkt zu benutzen, inklusive und ohne Ausnahme, dem Recht, sie zu verwenden, kopieren, ändern, fusionieren, verlegen, verbreiten, unterlizenzieren und/oder zu verkaufen, und Personen, die diese Software erhalten, diese Rechte zu geben, unter den folgenden Bedingungen: - -Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen Kopien oder Teilkopien der Software beizulegen. - -DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER IMPLIZIERTE GARANTIE BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE ZUR BENUTZUNG FÜR DEN VORGESEHENEN ODER EINEM BESTIMMTEN ZWECK SOWIE JEGLICHER RECHTSVERLETZUNG, JEDOCH NICHT DARAUF BESCHRÄNKT. IN KEINEM FALL SIND DIE AUTOREN ODER COPYRIGHTINHABER FÜR JEGLICHEN SCHADEN ODER SONSTIGE ANSPRÜCHE HAFTBAR ZU MACHEN, OB INFOLGE DER ERFÜLLUNG EINES VERTRAGES, EINES DELIKTES ODER ANDERS IM ZUSAMMENHANG MIT DER SOFTWARE ODER SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 515b349..0000000 --- a/README.md +++ /dev/null @@ -1,20 +0,0 @@ -[![vibe.d](http://vibed.org/images/logo-and-title.png)](http://vibed.org) - -vibe.d is a high-performance asynchronous I/O, concurrency and web application -toolkit written in D. This repository contains the upcoming HTTP module that -is going to replace the existing HTTP/1.1 implementation. - -[![Posix Build Status](https://travis-ci.org/vibe-d/vibe-http.svg?branch=master)](https://travis-ci.org/vibe-d/vibe-http) -[![Windows Build status](https://ci.appveyor.com/api/projects/status/9r1p1avpl75nb73e?svg=true)](https://ci.appveyor.com/project/s-ludwig/vibe-http/branch/master) - - -Experimental status -=================== - -This library is a partial rewrite of the original `vibe-d:http` package and should -be considered experimental. Do not use it in production, yet. - -- It may still receive breaking changes -- The code in general should not be considered ready for production -- It currently lacks some features, such as the HTTP client implementation -- The HTTP/2 server still needs extensive testing diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index f5bcb52..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,114 +0,0 @@ -platform: x64 -environment: - matrix: - - DC: dmd - DVersion: 2.094.2 - arch: x64 - - DC: dmd - DVersion: 2.094.2 - arch: x86 - - DC: dmd - DVersion: 2.089.1 - arch: x64 - - DC: dmd - DVersion: 2.084.1 - arch: x86_mscoff - - DC: ldc - DVersion: 1.24.0 - arch: x64 - - DC: ldc - DVersion: 1.17.0 - arch: x86 - -skip_tags: false - -install: - - ps: function SetUpDCompiler - { - if($env:DC -eq "dmd"){ - if($env:arch -eq "x86"){ - $env:DConf = "m32"; - } - elseif($env:arch -eq "x64"){ - $env:DConf = "m64"; - } - echo "downloading ..."; - $env:toolchain = "msvc"; - $version = $env:DVersion; - Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z"; - echo "finished."; - pushd c:\\; - 7z x dmd.7z > $null; - popd; - } - elseif($env:DC -eq "ldc"){ - echo "downloading ..."; - if($env:arch -eq "x86"){ - $env:DConf = "m32"; - } - elseif($env:arch -eq "x64"){ - $env:DConf = "m64"; - } - $env:toolchain = "msvc"; - $version = $env:DVersion; - if ([System.Version]$version -lt [System.Version]"1.7.0") { - Invoke-WebRequest "https://github.com/ldc-developers/ldc/releases/download/v$($version)/ldc2-$($version)-win64-msvc.zip" -OutFile "c:\ldc.zip"; - echo "finished."; - pushd c:\\; - 7z x ldc.zip > $null; - popd; - } - else { - Invoke-WebRequest "https://github.com/ldc-developers/ldc/releases/download/v$($version)/ldc2-$($version)-windows-multilib.7z" -OutFile "c:\ldc.7z"; - echo "finished."; - pushd c:\\; - 7z x ldc.7z > $null; - popd; - } - } - } - - ps: SetUpDCompiler - - powershell -Command [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest "https://github.com/dlang/dub/releases/download/v1.23.0/dub-v1.23.0-windows-i686.zip" -OutFile dub.zip - - 7z x dub.zip -odub > nul - - set PATH=%CD%\%binpath%;%CD%\dub;%PATH% - - dub --version - -before_build: - - ps: if($env:arch -eq "x86"){ - $env:compilersetupargs = "x86"; - $env:Darch = "x86"; - } - elseif($env:arch -eq "x86_mscoff"){ - $env:compilersetupargs = "x86"; - $env:Darch = "x86_mscoff"; - } - elseif($env:arch -eq "x64"){ - $env:compilersetupargs = "amd64"; - $env:Darch = "x86_64"; - } - - ps : if($env:DC -eq "dmd"){ - $env:PATH += ";C:\dmd2\windows\bin;"; - } - elseif($env:DC -eq "ldc"){ - $version = $env:DVersion; - if ([System.Version]$version -lt [System.Version]"1.7.0") { - $env:PATH += ";C:\ldc2-$($version)-win64-msvc\bin"; - } else { - $env:PATH += ";C:\ldc2-$($version)-windows-multilib\bin"; - } - $env:DC = "ldc2"; - } - - ps: $env:compilersetup = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall"; - - '"%compilersetup%" %compilersetupargs%' - -build_script: - - dub build -b release --arch=%Darch% --compiler=%DC% - - echo dummy build script - dont remove me - -test_script: - - echo %PLATFORM% - - echo %Darch% - - echo %DC% - - echo %PATH% - - '%DC% --version' - - dub test --arch=%Darch% --compiler=%DC% diff --git a/dub.sdl b/dub.sdl index 2fffcd0..7c510e8 100644 --- a/dub.sdl +++ b/dub.sdl @@ -1,17 +1,8 @@ name "vibe-http" description "HTTP server and client implementation and higher level HTTP functionality" -homepage "https://vibed.org/" - -license "MIT" -copyright "Copyright © 2012-2019 Sönke Ludwig" -authors "Sönke Ludwig" "Francesco Galla" "see GitHub for all" - -dependency "vibe-d:crypto" version=">0.8.4" -dependency "vibe-d:tls" version=">0.8.4" -dependency "vibe-d:inet" version="*" -dependency "vibe-d:stream" version="*" -dependency "vibe-d:textfilter" version="*" -dependency "diet-ng" version="~>1.1" -dependency "taggedalgebraic" version=">=0.10.9 <0.12.0-0" - +dependency "vibe-d:crypto" version="~>0.9.7" +dependency "vibe-d:inet" version="~>0.9.7" +dependency "vibe-d:tls" version="~>0.9.7" +dependency "vibe-d:textfilter" version="~>0.9.0" +dependency "diet-ng" version="~>1.2" targetType "library" diff --git a/examples/auth_basic/dub.sdl b/examples/auth_basic/dub.sdl new file mode 100644 index 0000000..d60b06c --- /dev/null +++ b/examples/auth_basic/dub.sdl @@ -0,0 +1,3 @@ +name "auth-basic-example" +description "Demonstrates basic authentication." +dependency "vibe-http" path="../../" diff --git a/examples/auth_basic/source/app.d b/examples/auth_basic/source/app.d new file mode 100644 index 0000000..e548e01 --- /dev/null +++ b/examples/auth_basic/source/app.d @@ -0,0 +1,33 @@ +module app; + +import vibe.core.core; +import vibe.http.auth.basic_auth; +import vibe.http.router; +import vibe.http.server; +import std.functional : toDelegate; + +bool checkPassword(string user, string password) +{ + return user == "admin" && password == "secret"; +} + +int main(string[] args) +{ + auto router = new URLRouter; + + // the following routes are accessible without authentication: + router.get("/", staticTemplate!"index.dt"); + + // now any request is matched and checked for authentication: + router.any("*", performBasicAuth("Site Realm", toDelegate(&checkPassword))); + + // the following routes can only be reached if authenticated: + router.get("/internal", staticTemplate!"internal.dt"); + + auto settings = new HTTPServerSettings; + settings.port = 8080; + settings.bindAddresses = ["::1", "127.0.0.1"]; + + auto listener = listenHTTP(settings, router); + return runApplication(&args); +} diff --git a/examples/auth_basic/views/index.dt b/examples/auth_basic/views/index.dt new file mode 100644 index 0000000..bc13fee --- /dev/null +++ b/examples/auth_basic/views/index.dt @@ -0,0 +1,10 @@ +doctype html +html + head + title Basic Authentication Example + body + p Click on the + a(href="/internal") link + | to access a protected page. + + diff --git a/examples/auth_basic/views/internal.dt b/examples/auth_basic/views/internal.dt new file mode 100644 index 0000000..76ec83a --- /dev/null +++ b/examples/auth_basic/views/internal.dt @@ -0,0 +1,8 @@ +doctype html +html + head + title Basic Authentication Example + body + p This page is protected. + + diff --git a/examples/auth_digest/dub.sdl b/examples/auth_digest/dub.sdl new file mode 100644 index 0000000..0737bdf --- /dev/null +++ b/examples/auth_digest/dub.sdl @@ -0,0 +1,3 @@ +name "auth-digest-example" +description "Demonstrates digest authentication." +dependency "vibe-http" path="../../" diff --git a/examples/auth_digest/source/app.d b/examples/auth_digest/source/app.d new file mode 100644 index 0000000..000453b --- /dev/null +++ b/examples/auth_digest/source/app.d @@ -0,0 +1,38 @@ +module app; + +import vibe.core.core; +import vibe.http.auth.digest_auth; +import vibe.http.router; +import vibe.http.server; +import std.functional : toDelegate; + +string digestPassword(string realm, string user) @safe +{ + if (realm == "Site Realm" && user == "admin") + return createDigestPassword(realm, user, "secret"); + return ""; +} + +int main(string[] args) +{ + auto authinfo = new DigestAuthInfo; + authinfo.realm = "Site Realm"; + + auto router = new URLRouter; + + // the following routes are accessible without authentication: + router.get("/", staticTemplate!"index.dt"); + + // now any request is matched and checked for authentication: + router.any("*", performDigestAuth(authinfo, toDelegate(&digestPassword))); + + // the following routes can only be reached if authenticated: + router.get("/internal", staticTemplate!"internal.dt"); + + auto settings = new HTTPServerSettings; + settings.port = 8080; + settings.bindAddresses = ["::1", "127.0.0.1"]; + + auto listener = listenHTTP(settings, router); + return runApplication(&args); +} diff --git a/examples/auth_digest/views/index.dt b/examples/auth_digest/views/index.dt new file mode 100644 index 0000000..acf269d --- /dev/null +++ b/examples/auth_digest/views/index.dt @@ -0,0 +1,10 @@ +doctype html +html + head + title Digest Authentication Example + body + p Click on the + a(href="/internal") link + | to access a protected page. + + diff --git a/examples/auth_digest/views/internal.dt b/examples/auth_digest/views/internal.dt new file mode 100644 index 0000000..052761e --- /dev/null +++ b/examples/auth_digest/views/internal.dt @@ -0,0 +1,8 @@ +doctype html +html + head + title Digest Authentication Example + body + p This page is protected. + + diff --git a/examples/bench-http-request/dub.sdl b/examples/bench-http-request/dub.sdl new file mode 100644 index 0000000..7233738 --- /dev/null +++ b/examples/bench-http-request/dub.sdl @@ -0,0 +1,3 @@ +name "bench-http-request" +dependency "vibe-http" path="../../" +versions "VibeManualMemoryManagement" diff --git a/examples/bench-http-request/source/app.d b/examples/bench-http-request/source/app.d new file mode 100644 index 0000000..32fdb82 --- /dev/null +++ b/examples/bench-http-request/source/app.d @@ -0,0 +1,105 @@ +import vibe.core.args; +import vibe.core.core; +import vibe.http.client; + +import core.atomic; + +import std.datetime.stopwatch; +import std.functional; +import std.stdio; + + +shared long nreq = 0; +shared long nerr = 0; +shared long nreqc = 1000; +shared long ndisconns = 0; +shared long nconn = 0; + +shared long g_concurrency = 100; +shared long g_requestDelay = 0; +shared long g_maxKeepAliveRequests = 1000; + +void request(bool disconnect) +nothrow { + atomicOp!"+="(nconn, 1); + try { + requestHTTP("http://127.0.0.1:8080/empty", + (scope req){ + req.headers.remove("Accept-Encoding"); + if (disconnect) { + atomicOp!"+="(ndisconns, 1); + req.headers["Connection"] = "close"; + } + }, + (scope res){ + if (g_requestDelay) + sleep(g_requestDelay.msecs()); + res.dropBody(); + } + ); + } catch (Exception) { atomicOp!"+="(nerr, 1); } + atomicOp!"-="(nconn, 1); + atomicOp!"+="(nreq, 1); +} + +void distTask() +nothrow { + static shared int s_threadCount = 0; + static shared int s_token = 0; + int id = atomicOp!"+="(s_threadCount, 1) - 1; + + while (true) { + while (atomicLoad(s_token) != id && g_concurrency > 0) {} + if (g_concurrency == 0) break; + runTask({ + long keep_alives = 0; + while (true) { + bool disconnect = ++keep_alives >= g_maxKeepAliveRequests; + request(disconnect); + if (disconnect) keep_alives = 0; + } + }); + atomicOp!"+="(g_concurrency, -1); + atomicStore(s_token, cast(int)((id + 1) % workerThreadCount)); + } +} + +void benchmark() +nothrow { + atomicOp!"+="(g_concurrency, -1); + if (g_concurrency > 0) { + runWorkerTaskDist(&distTask); + while (atomicLoad(nreq) == 0) { sleepUninterruptible(1.msecs); } + } + + StopWatch sw; + sw.start(); + ulong next_ts = 100; + + long keep_alives = 0; + while (true) { + auto tm = sw.peek().total!"msecs"; + + if (nreq >= nreqc && tm >= next_ts) { + try writefln("%s iterations: %s req/s, %s err/s (%s active conn, %s disconnects/s)", nreq, (nreq*1_000)/tm, (nerr*1_000)/tm, nconn, (ndisconns*1_000)/tm); + catch (Exception e) assert(false, e.msg); + nreqc.atomicOp!"+="(1000); + next_ts += 100; + } + bool disconnect = ++keep_alives >= g_maxKeepAliveRequests; + request(disconnect); + if (disconnect) keep_alives = 0; +// if (nreq >= 5000) exitEventLoop(true); + } +} + +void main() +{ + import vibe.core.args; + readOption("c", cast(long*) &g_concurrency, "The maximum number of concurrent requests"); + readOption("d", cast(long*) &g_requestDelay, "Artificial request delay in milliseconds"); + readOption("k", cast(long*) &g_maxKeepAliveRequests, "Maximum number of keep-alive requests for each connection"); + if (!finalizeCommandLineOptions()) return; + runTask(&benchmark); + runEventLoop(); +} diff --git a/examples/bench-http-server/dub.sdl b/examples/bench-http-server/dub.sdl new file mode 100644 index 0000000..3a78d55 --- /dev/null +++ b/examples/bench-http-server/dub.sdl @@ -0,0 +1,3 @@ +name "bench-http-server" +dependency "vibe-http" path="../../" +versions "VibeManualMemoryManagement" diff --git a/examples/bench-http-server/public/10 b/examples/bench-http-server/public/10 new file mode 100644 index 0000000..6a537b5 --- /dev/null +++ b/examples/bench-http-server/public/10 @@ -0,0 +1 @@ +1234567890 \ No newline at end of file diff --git a/examples/bench-http-server/public/10k b/examples/bench-http-server/public/10k new file mode 100644 index 0000000..22d650d --- /dev/null +++ b/examples/bench-http-server/public/10k @@ -0,0 +1 @@ +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 \ No newline at end of file diff --git a/examples/bench-http-server/public/1k b/examples/bench-http-server/public/1k new file mode 100644 index 0000000..33602dc --- /dev/null +++ b/examples/bench-http-server/public/1k @@ -0,0 +1 @@ +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 \ No newline at end of file diff --git a/examples/bench-http-server/source/app.d b/examples/bench-http-server/source/app.d new file mode 100644 index 0000000..bf3c218 --- /dev/null +++ b/examples/bench-http-server/source/app.d @@ -0,0 +1,101 @@ +module app; + +import vibe.core.core; +import vibe.http.fileserver; +import vibe.http.router; +import vibe.http.server; +import vibe.core.stream : pipe, nullSink; + +import std.functional : toDelegate; + + +shared string data; + +void empty(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody(""); +} + +void static_10(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody(cast(string)data[0 .. 10]); +} + +void static_1k(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody(cast(string)data[0 .. 1000]); +} + +void static_10k(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody(cast(string)data[0 .. 10_000]); +} + +void static_100k(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody(cast(string)data[0 .. 100_000]); +} + +void quit(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody("Exiting event loop..."); + exitEventLoop(); +} + +void staticAnswer(TCPConnection conn) +@safe nothrow { + try { + conn.write("HTTP/1.0 200 OK\r\nContent-Length: 0\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"); + conn.close(); + } catch (Exception e) { + // increment error counter + } +} + +pure char[] generateData() +{ + char[] data; + data.length = 100_000; + foreach (i; 0 .. data.length) { + data[i] = (i % 10) + '0'; + if (i % 100 == 99) data[i] = '\n'; + } + return data; +} + + +int main(string[] args) +{ + //setLogLevel(LogLevel.Trace); + data = generateData(); + + runWorkerTaskDist(() nothrow { + try { + auto settings = new HTTPServerSettings; + settings.port = 8080; + settings.bindAddresses = ["127.0.0.1"]; + settings.options = HTTPServerOption.reusePort; + //settings.accessLogToConsole = true; + + auto fsettings = new HTTPFileServerSettings; + fsettings.serverPathPrefix = "/file"; + + auto routes = new URLRouter; + routes.get("/", staticTemplate!"home.dt"); + routes.get("/empty", &empty); + routes.get("/static/10", &static_10); + routes.get("/static/1k", &static_1k); + routes.get("/static/10k", &static_10k); + routes.get("/static/100k", &static_100k); + routes.get("/quit", &quit); + routes.get("/file/*", serveStaticFiles("./public", fsettings)); + routes.rebuild(); + + auto httpListener = listenHTTP(settings, routes); + auto tcpListener = listenTCP(8081, toDelegate(&staticAnswer), + "127.0.0.1", TCPListenOptions.reusePort); + } catch (Exception e) assert(false, e.msg); + }); + + return runApplication(&args); +} diff --git a/examples/bench-http-server/views/home.dt b/examples/bench-http-server/views/home.dt new file mode 100644 index 0000000..894840e --- /dev/null +++ b/examples/bench-http-server/views/home.dt @@ -0,0 +1,25 @@ +doctype html +html + head + title Benchmark site + body + h1 Available tests: + ol + li + a(href="empty") Empty response + li + a(href="static/10") 10B static page + li + a(href="static/1k") 1kB static page + li + a(href="static/10k") 10kB static page + li + a(href="file/10") 10B file + li + a(href="file/1k") 1kB file + li + a(href="file/10k") 10kB file + li + a(href="http://127.0.0.1:8081/") static response + li + a(href="quit") Quit diff --git a/examples/bench-urlrouter/dub.sdl b/examples/bench-urlrouter/dub.sdl new file mode 100644 index 0000000..7e05a29 --- /dev/null +++ b/examples/bench-urlrouter/dub.sdl @@ -0,0 +1,4 @@ +name "bench-urlrouter" +description "Benchmark for the URLRouter class" +dependency "vibe-http" path="../../" +versions "VibeRouterTreeMatch" diff --git a/examples/bench-urlrouter/source/app.d b/examples/bench-urlrouter/source/app.d new file mode 100644 index 0000000..1de0db6 --- /dev/null +++ b/examples/bench-urlrouter/source/app.d @@ -0,0 +1,77 @@ +import vibe.core.log; +import vibe.http.router; +import vibe.http.server; +import vibe.inet.url; +import std.datetime.stopwatch; +import std.string : format; + + +void req(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + if (!res.headerWritten) + res.writeVoidBody(); +} + +Duration runTimed(scope void delegate() del) +{ + StopWatch sw; + sw.start(); + del(); + sw.stop(); + return sw.peek; +} + +void main() +{ + enum nroutes = 1_000; + enum nrequests = 1_000_000; + + auto router = new URLRouter; + + logInfo("Setting up routes..."); + auto duradd = runTimed({ + foreach (i; 0 .. nroutes) + router.get(format("/%s/:test1/%s/:test2", i/100, i), &req); + }); + + import std.random; + foreach (i; 0 .. nroutes) + router.get(format("/%s%s", uniform!uint(), uniform!uint()), &req); + + logInfo("Performing first request..."); + auto req = createTestHTTPServerRequest(URL("http://localhost/0/test/0/test"), HTTPMethod.GET); + auto res = createTestHTTPServerResponse(); + auto durfirst = runTimed({ + router.handleRequest(req, res); + }); + + logInfo("Performing first match requests..."); + auto durfirstmatch = runTimed({ + foreach (i; 0 .. nrequests) + router.handleRequest(req, res); + }); + + logInfo("Performing last match requests..."); + req = createTestHTTPServerRequest(URL(format("http://localhost/%s/test/%s/test", (nroutes-1)/100, nroutes-1)), HTTPMethod.GET); + auto durlastmatch = runTimed({ + foreach (i; 0 .. nrequests) + router.handleRequest(req, res); + }); + + logInfo("Performing non-match requests..."); + req = createTestHTTPServerRequest(URL("http://localhost/test/x/test"), HTTPMethod.GET); + auto durnonmatch = runTimed({ + foreach (i; 0 .. nrequests) + router.handleRequest(req, res); + }); + + version (VibeRouterTreeMatch) enum method = "tree match"; + else enum method = "linear probe"; + + logInfo("Results (%s):", method); + logInfo(" Add %s routes: %s", nroutes, duradd); + logInfo(" First request: %s", durfirst); + logInfo(" %s first match requests: %s", nrequests, durfirstmatch); + logInfo(" %s last match requests: %s", nrequests, durlastmatch); + logInfo(" %s non-match requests: %s", nrequests, durnonmatch); +} diff --git a/examples/dummy/dub.sdl b/examples/dummy/dub.sdl deleted file mode 100644 index eeafa87..0000000 --- a/examples/dummy/dub.sdl +++ /dev/null @@ -1 +0,0 @@ -name "dummy-example" diff --git a/examples/dummy/source/app.d b/examples/dummy/source/app.d deleted file mode 100644 index 255e5a0..0000000 --- a/examples/dummy/source/app.d +++ /dev/null @@ -1,6 +0,0 @@ -import std.stdio; - -void main() -{ - writeln("This is a dummy example"); -} diff --git a/examples/http2/dub.sdl b/examples/http2/dub.sdl deleted file mode 100644 index 73f5d59..0000000 --- a/examples/http2/dub.sdl +++ /dev/null @@ -1,7 +0,0 @@ -name "http2-example" - -dependency "vibe-http" path="../../../vibe-http" - -/*versions "VibeForceALPN"*/ -targetType "executable" -/*buildOptions "profileGC"*/ diff --git a/examples/http2/source/app.d b/examples/http2/source/app.d deleted file mode 100644 index ebb9f2d..0000000 --- a/examples/http2/source/app.d +++ /dev/null @@ -1,92 +0,0 @@ -/* ==== Vibe.d HTTP/2 Webserver Example ==== */ -/* Supports both HTTP and HTTPS transport */ -/* Transparent (WIP: exposing settings) */ -/* ========================================= */ - -import vibe.http.server; -import vibe.stream.tls; -import vibe.http.internal.http2.http2 : http2Callback; // ALPN negotiation -import vibe.core.core : runApplication; - -/* ==== declare two handlers (could use the same one) ==== */ -void handleReq(HTTPServerRequest req, HTTPServerResponse res) -@safe { - if (res.httpVersion == HTTPVersion.HTTP_2) - res.writeBody("Hello, you connected to "~req.path~"! This response is sent through HTTP/2\n"); - else - res.writeBody("Hello, World! You connected through HTTP/1, try using HTTP/2!\n"); -} - -void tlsHandleReq(HTTPServerRequest req, HTTPServerResponse res) -@safe { - if (req.httpVersion == HTTPVersion.HTTP_2) - res.writeBody("Hello, you connected to "~req.path~"! This response is sent through HTTP/2 with TLS\n"); - else - res.writeBody("Hello, World! You connected through HTTP/1 with TLS, try using HTTP/2!\n"); -} - -// sends a very big data frame -void bigHandleReq(size_t DIM)(HTTPServerRequest req, HTTPServerResponse res) -@trusted { - import vibe.utils.array : FixedAppender; - import std.range : iota; - - FixedAppender!(immutable(char)[], DIM) appender; - - if (req.path == "/") { - foreach(i; iota(1,DIM-4)) appender.put('1'); - appender.put(['O','k','!', '\n']); - res.writeBody(appender.data); - } -} - -void main() -{ - //import vibe.core.log; - //setLogLevel(LogLevel.trace); - -/* ==== cleartext HTTP/2 support (h2c) ==== */ - auto settings = new HTTPServerSettings; - settings.port = 8090; - settings.bindAddresses = ["127.0.0.1"]; - listenHTTP!handleReq(settings); - -/* ==== cleartext HTTP/2 support (h2c) with a heavy DATA frame ==== */ - auto bigSettings = new HTTPServerSettings; - settings.port = 8092; - settings.bindAddresses = ["127.0.0.1"]; - listenHTTP!(bigHandleReq!100000)(settings); - -/* ========== HTTPS (h2) support ========== */ - auto tlsSettings = new HTTPServerSettings; - tlsSettings.port = 8091; - tlsSettings.bindAddresses = ["127.0.0.1"]; - - /// setup TLS context by using cert and key in example rootdir - tlsSettings.tlsContext = createTLSContext(TLSContextKind.server); - tlsSettings.tlsContext.useCertificateChainFile("server.crt"); - tlsSettings.tlsContext.usePrivateKeyFile("server.key"); - - // set alpn callback to support HTTP/2 protocol negotiation - tlsSettings.tlsContext.alpnCallback(http2Callback); - listenHTTP!tlsHandleReq(tlsSettings); - -/* ========== HTTPS (h2) support with a heavy DATA frame ========== */ - auto bigTLSSettings = new HTTPServerSettings; - bigTLSSettings.port = 8093; - bigTLSSettings.bindAddresses = ["127.0.0.1"]; - - /// setup TLS context by using cert and key in example rootdir - bigTLSSettings.tlsContext = createTLSContext(TLSContextKind.server); - bigTLSSettings.tlsContext.useCertificateChainFile("server.crt"); - bigTLSSettings.tlsContext.usePrivateKeyFile("server.key"); - - // set alpn callback to support HTTP/2 protocol negotiation - bigTLSSettings.tlsContext.alpnCallback(http2Callback); - auto l = listenHTTP!(bigHandleReq!100000)(bigTLSSettings); - scope(exit) l.stopListening(); - -/* ========== Run both `listenHTTP` handlers ========== */ - // UNCOMMENT to run - runApplication(); -} diff --git a/examples/http_forward_proxy/dub.sdl b/examples/http_forward_proxy/dub.sdl index 6ce4a9e..85f8b44 100644 --- a/examples/http_forward_proxy/dub.sdl +++ b/examples/http_forward_proxy/dub.sdl @@ -1,3 +1,3 @@ -name "http-forward-proxy-example" +name "http-forward-proxy-example" description "Sets up a simple forward proxy." dependency "vibe-http" path="../../" diff --git a/examples/http_forward_proxy/source/app.d b/examples/http_forward_proxy/source/app.d index ea2005b..6fdba65 100644 --- a/examples/http_forward_proxy/source/app.d +++ b/examples/http_forward_proxy/source/app.d @@ -1,15 +1,15 @@ +module app; + import vibe.core.core; import vibe.http.proxy; import vibe.http.server; - -void main() +int main(string[] args) { auto settings = new HTTPServerSettings; settings.port = 8080; settings.bindAddresses = ["::1", "127.0.0.1"]; listenHTTPForwardProxy(settings); - - runApplication(); + return runApplication(&args); } diff --git a/examples/http_info/dub.sdl b/examples/http_info/dub.sdl index ceb17ba..7e1c23a 100644 --- a/examples/http_info/dub.sdl +++ b/examples/http_info/dub.sdl @@ -1,3 +1,3 @@ -name "http-info-example" +name "http-info-example" description "Displays request information using a Diet template." dependency "vibe-http" path="../../" diff --git a/examples/http_info/source/app.d b/examples/http_info/source/app.d index 2225b79..9981353 100644 --- a/examples/http_info/source/app.d +++ b/examples/http_info/source/app.d @@ -1,14 +1,15 @@ +module app; + import vibe.core.core; import vibe.http.server; -void main() +int main (string[] args) { auto settings = new HTTPServerSettings; settings.sessionStore = new MemorySessionStore(); settings.port = 8080; settings.bindAddresses = ["::1", "127.0.0.1"]; - listenHTTP(settings, staticTemplate!("info.dt")); - - runApplication(); + auto listener = listenHTTP(settings, staticTemplate!("info.dt")); + return runApplication(&args); } diff --git a/examples/http_request/dub.sdl b/examples/http_request/dub.sdl index a929013..54617a9 100644 --- a/examples/http_request/dub.sdl +++ b/examples/http_request/dub.sdl @@ -1,4 +1,4 @@ -name "http-request-example" +name "http-request-example" description "Performs a custom HTTP request." authors "Sönke Ludwig" dependency "vibe-http" path="../../" diff --git a/examples/http_request_digest/dub.sdl b/examples/http_request_digest/dub.sdl index f0254bd..8dd92cb 100644 --- a/examples/http_request_digest/dub.sdl +++ b/examples/http_request_digest/dub.sdl @@ -1,4 +1,4 @@ -name "http-request-digest-example" +name "http-request-digest-example" description "Performs a custom HTTP request with digest authentication." authors "Sönke Ludwig" dependency "vibe-http" path="../../" diff --git a/examples/http_reverse_proxy/dub.sdl b/examples/http_reverse_proxy/dub.sdl index 8ad5bd1..a323114 100644 --- a/examples/http_reverse_proxy/dub.sdl +++ b/examples/http_reverse_proxy/dub.sdl @@ -1,3 +1,3 @@ -name "http-reverse-proxy-example" +name "http-reverse-proxy-example" description "Sets up a simple reverse proxy to a foreign server." dependency "vibe-http" path="../../" diff --git a/examples/http_reverse_proxy/source/app.d b/examples/http_reverse_proxy/source/app.d index 9ad9ea9..ca952b4 100644 --- a/examples/http_reverse_proxy/source/app.d +++ b/examples/http_reverse_proxy/source/app.d @@ -1,15 +1,15 @@ +module app; + import vibe.core.core; import vibe.http.proxy; import vibe.http.server; - -void main() +int main(string[] args) { auto settings = new HTTPServerSettings; settings.port = 8080; settings.bindAddresses = ["::1", "127.0.0.1"]; listenHTTPReverseProxy(settings, "vibed.org", 80); - - runApplication(); + return runApplication(&args); } diff --git a/examples/https_server_sni/dub.sdl b/examples/https_server_sni/dub.sdl index 56bee74..624dbc9 100644 --- a/examples/https_server_sni/dub.sdl +++ b/examples/https_server_sni/dub.sdl @@ -1,3 +1,3 @@ -name "https-sni-server-example" +name "https-sni-server-example" description "Uses SNI to serve multiple virtual hosts in a single HTTPS port." dependency "vibe-http" path="../../" diff --git a/examples/https_server_sni/source/app.d b/examples/https_server_sni/source/app.d index d5ebc33..0afafb7 100644 --- a/examples/https_server_sni/source/app.d +++ b/examples/https_server_sni/source/app.d @@ -1,10 +1,11 @@ +module app; + import vibe.core.core; import vibe.core.log; import vibe.http.server; import vibe.stream.tls; - -void main() +int main(string[] args) { { auto settings = new HTTPServerSettings; @@ -41,7 +42,7 @@ and should be presented with a different certificate each time, matching the host name entered. `); - runApplication(); + return runApplication(&args); } void handleRequestA(scope HTTPServerRequest req, scope HTTPServerResponse res) diff --git a/examples/uploader/dub.sdl b/examples/uploader/dub.sdl new file mode 100644 index 0000000..ba78330 --- /dev/null +++ b/examples/uploader/dub.sdl @@ -0,0 +1,3 @@ +name "uploader-example" +description "Simple form based file upload example." +dependency "vibe-http" path="../../" diff --git a/examples/uploader/source/app.d b/examples/uploader/source/app.d new file mode 100644 index 0000000..c4483ae --- /dev/null +++ b/examples/uploader/source/app.d @@ -0,0 +1,38 @@ +module app; + +import vibe.core.core; +import vibe.core.file; +import vibe.core.log; +import vibe.core.path; +import vibe.http.router; +import vibe.http.server; + +import std.exception; + +void uploadFile(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + auto pf = "file" in req.files; + enforce(pf !is null, "No file uploaded!"); + try moveFile(pf.tempPath, NativePath(".") ~ pf.filename); + catch (Exception e) { + logWarn("Failed to move file to destination folder: %s", e.msg); + logInfo("Performing copy+delete instead."); + copyFile(pf.tempPath, NativePath(".") ~ pf.filename); + } + + res.writeBody("File uploaded!", "text/plain"); +} + +int main(string[] args) +{ + auto router = new URLRouter; + router.get("/", staticTemplate!"upload_form.dt"); + router.post("/upload", &uploadFile); + + auto settings = new HTTPServerSettings; + settings.port = 8080; + settings.bindAddresses = ["::1", "127.0.0.1"]; + + auto listener = listenHTTP(settings, router); + return runApplication(&args); +} diff --git a/examples/uploader/views/upload_form.dt b/examples/uploader/views/upload_form.dt new file mode 100644 index 0000000..6ee0677 --- /dev/null +++ b/examples/uploader/views/upload_form.dt @@ -0,0 +1,11 @@ +doctype 5 +html(lang="en") + head + title Uploader + body + h1 Uploader + form(method="POST", action="/upload", enctype="multipart/form-data") + p File: + input(type="file", name="file", size="50", maxlength="100000") + input(type="submit", value="Upload!") + \ No newline at end of file diff --git a/examples/websocket/dub.sdl b/examples/websocket/dub.sdl new file mode 100644 index 0000000..050a2c0 --- /dev/null +++ b/examples/websocket/dub.sdl @@ -0,0 +1,3 @@ +name "websocket-example" +description "Example for using the WebSocket feature" +dependency "vibe-http" path="../../" diff --git a/examples/websocket/public/index.html b/examples/websocket/public/index.html new file mode 100644 index 0000000..172792b --- /dev/null +++ b/examples/websocket/public/index.html @@ -0,0 +1,17 @@ + + + + WebSocket Test + + + + +

The following box should show a running counter, updated by the server:

+

+	
+
+
diff --git a/examples/websocket/public/scripts/websocket.js b/examples/websocket/public/scripts/websocket.js
new file mode 100644
index 0000000..3981d81
--- /dev/null
+++ b/examples/websocket/public/scripts/websocket.js
@@ -0,0 +1,35 @@
+var socket
+
+function connect()
+{
+	setText("connecting...");
+	socket = new WebSocket(getBaseURL() + "/ws");
+	socket.onopen = function() {
+		setText("connected. waiting for timer...");
+	}
+	socket.onmessage = function(message) {	
+		setText(message.data);
+	}
+	socket.onclose = function() {
+		setText("connection closed.");
+	}
+	socket.onerror = function() {
+		setText("Error!");
+	}
+}
+
+function closeConnection()
+{
+	socket.close();
+	setText("closed.");
+}
+
+function setText(text)
+{
+	document.getElementById("timer").innerHTML = text;
+}
+
+function getBaseURL()
+{
+	return "ws://" + window.location.host;
+}
diff --git a/examples/websocket/source/app.d b/examples/websocket/source/app.d
new file mode 100644
index 0000000..14465e5
--- /dev/null
+++ b/examples/websocket/source/app.d
@@ -0,0 +1,41 @@
+module app;
+
+import vibe.core.core;
+import vibe.core.log;
+import vibe.http.fileserver : serveStaticFiles;
+import vibe.http.router : URLRouter;
+import vibe.http.server;
+import vibe.http.websockets : WebSocket, handleWebSockets;
+
+import core.time;
+import std.conv : to;
+
+
+int main(string[] args)
+{
+	auto router = new URLRouter;
+	router.get("/", staticRedirect("/index.html"));
+	router.get("/ws", handleWebSockets(&handleWebSocketConnection));
+	router.get("*", serveStaticFiles("public/"));
+
+	auto settings = new HTTPServerSettings;
+	settings.port = 8080;
+	settings.bindAddresses = ["::1", "127.0.0.1"];
+
+	auto listener = listenHTTP(settings, router);
+	return runApplication(&args);
+}
+
+void handleWebSocketConnection(scope WebSocket socket)
+{
+	int counter = 0;
+	logInfo("Got new web socket connection.");
+	while (true) {
+		sleep(1.seconds);
+		if (!socket.connected) break;
+		counter++;
+		logInfo("Sending '%s'.", counter);
+		socket.send(counter.to!string);
+	}
+	logInfo("Client disconnected.");
+}
diff --git a/run-ci.sh b/run-ci.sh
new file mode 100755
index 0000000..d475551
--- /dev/null
+++ b/run-ci.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+set -e -x -o pipefail
+
+DUB_ARGS="--build-mode=${DUB_BUILD_MODE:-separate} ${DUB_ARGS:-}"
+# default to run all parts
+: ${PARTS:=lint,builds,unittests,examples,tests}
+
+# force selecting vibe-core 2.x.x
+if [[ $PARTS =~ (^|,)vibe-core-1(,|$) ]]; then
+    RECIPES=`find | grep dub.sdl`
+    sed -i "s/\"vibe-core\" version=\">=1\.0\.0 <3\.0\.0-0\"/\"vibe-core\" version=\">=1.0.0 <2.0.0-0\"/g" $RECIPES
+fi
+
+if [[ $PARTS =~ (^|,)lint(,|$) ]]; then
+    ./scripts/test_version.sh
+    # Check for trailing whitespace"
+    grep -nrI --include=*.d '\s$'  && (echo "Trailing whitespace found"; exit 1)
+fi
+
+if [[ $PARTS =~ (^|,)builds(,|$) ]]; then
+    # test for successful release build
+    dub build --combined -b release --compiler=$DC
+    dub clean --all-packages
+
+    # test for successful 32-bit build
+    if [ "$DC" == "dmd" ]; then
+        dub build --combined --arch=x86
+        dub clean --all-packages
+    fi
+fi
+
+if [[ $PARTS =~ (^|,)unittests(,|$) ]]; then
+    dub test --compiler=$DC $DUB_ARGS
+    dub clean --all-packages
+fi
+
+if [[ $PARTS =~ (^|,)examples(,|$) ]]; then
+    for ex in $(\ls -1 examples/); do
+        echo "[INFO] Building example $ex"
+        (cd examples/$ex && dub build --compiler=$DC $DUB_ARGS && dub clean)
+    done
+fi
+
+if [[ $PARTS =~ (^|,)tests(,|$) ]]; then
+    for ex in `\ls -1 tests/`; do
+        if ! [[ $PARTS =~ (^|,)redis(,|$) ]] && [ $ex == "redis" ]; then
+            continue
+        fi
+        if [ -r tests/$ex/run.sh ]; then
+            echo "[INFO] Running test $ex"
+            (cd tests/$ex && ./run.sh)
+        elif [ -r tests/$ex/dub.json ] || [ -r tests/$ex/dub.sdl ]; then
+            if [ $ex == "vibe.http.client.2080" ]; then
+                echo "[WARNING] Skipping test $ex due to TravisCI incompatibility".
+            else
+                echo "[INFO] Running test $ex"
+                (cd tests/$ex && dub --compiler=$DC $DUB_ARGS && dub clean)
+            fi
+        fi
+    done
+fi
diff --git a/source/vibe/http/auth/digest_auth.d b/source/vibe/http/auth/digest_auth.d
index 60f0189..15bdf67 100644
--- a/source/vibe/http/auth/digest_auth.d
+++ b/source/vibe/http/auth/digest_auth.d
@@ -29,16 +29,16 @@ class DigestAuthInfo
 	@safe:
 
 	string realm;
-	ubyte[32] secret;
-	ulong timeout;
+	ubyte[16] secret;
+	Duration timeout;
 
 	this()
 	{
 		secureRNG.read(secret[]);
-		timeout = 300;
+		timeout = 300.seconds;
 	}
 
-	string createNonce(in HTTPServerRequest req)
+	string createNonce(scope const HTTPServerRequest req)
 	{
 		auto now = Clock.currTime(UTC()).stdTime();
 		auto time = () @trusted { return *cast(ubyte[now.sizeof]*)&now; } ();
@@ -49,14 +49,14 @@ class DigestAuthInfo
 		return Base64.encode(time ~ data);
 	}
 
-	NonceState checkNonce(in string nonce, in HTTPServerRequest req)
+	NonceState checkNonce(string nonce, scope const HTTPServerRequest req)
 	{
 		auto now = Clock.currTime(UTC()).stdTime();
 		ubyte[] decoded = Base64.decode(nonce);
 		if (decoded.length != now.sizeof + secret.length) return NonceState.Invalid;
 		auto timebytes = decoded[0 .. now.sizeof];
 		auto time = () @trusted { return (cast(typeof(now)[])timebytes)[0]; } ();
-		if (timeout + time > now) return NonceState.Expired;
+		if (timeout.total!"hnsecs" + time < now) return NonceState.Expired;
 		MD5 md5;
 		md5.put(timebytes);
 		md5.put(secret);
@@ -66,6 +66,14 @@ class DigestAuthInfo
 	}
 }
 
+unittest
+{
+	auto authInfo = new DigestAuthInfo;
+	auto req = createTestHTTPServerRequest(URL("http://localhost/"));
+	auto nonce = authInfo.createNonce(req);
+	assert(authInfo.checkNonce(nonce, req) == NonceState.Valid);
+}
+
 private bool checkDigest(scope HTTPServerRequest req, DigestAuthInfo info, scope DigestHashCallback pwhash, out bool stale, out string username)
 {
 	stale = false;
diff --git a/source/vibe/http/client.d b/source/vibe/http/client.d
index 9485c3d..5a0062c 100644
--- a/source/vibe/http/client.d
+++ b/source/vibe/http/client.d
@@ -77,11 +77,7 @@ HTTPClientResponse requestHTTP(string url, scope void delegate(scope HTTPClientR
 /// ditto
 HTTPClientResponse requestHTTP(URL url, scope void delegate(scope HTTPClientRequest req) requester = null, const(HTTPClientSettings) settings = defaultSettings)
 {
-	import std.algorithm.searching : canFind;
-
-	bool use_tls = isTLSRequired(url, settings);
-
-	auto cli = connectHTTP(url.getFilteredHost, url.port, use_tls, settings);
+	auto cli = connectHTTP(url, settings);
 	auto res = cli.request(
 		(scope req){ httpRequesterDg(req, url, settings, requester); },
 	);
@@ -100,9 +96,7 @@ void requestHTTP(string url, scope void delegate(scope HTTPClientRequest req) re
 /// ditto
 void requestHTTP(URL url, scope void delegate(scope HTTPClientRequest req) requester, scope void delegate(scope HTTPClientResponse req) responder, const(HTTPClientSettings) settings = defaultSettings)
 {
-	bool use_tls = isTLSRequired(url, settings);
-
-	auto cli = connectHTTP(url.getFilteredHost, url.port, use_tls, settings);
+	auto cli = connectHTTP(url, settings);
 	cli.request(
 		(scope req){ httpRequesterDg(req, url, settings, requester); },
 		responder
@@ -197,7 +191,7 @@ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, const(HTTPC
 	auto sttngs = settings ? settings : defaultSettings;
 
 	if (port == 0) port = use_tls ? 443 : 80;
-	auto ckey = ConnInfo(host, port, use_tls, sttngs.proxyURL.host, sttngs.proxyURL.port, sttngs.networkInterface);
+	auto ckey = ConnInfo(host, sttngs.tlsPeerName, port, use_tls, sttngs.proxyURL.host, sttngs.proxyURL.port, sttngs.networkInterface);
 
 	ConnectionPool!HTTPClient pool;
 	s_connections.opApply((ref c) @safe {
@@ -207,7 +201,7 @@ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, const(HTTPC
 	});
 
 	if (!pool) {
-		logDebug("Create HTTP client pool %s:%s %s proxy %s:%d", host, port, use_tls, sttngs.proxyURL.host, sttngs.proxyURL.port);
+		logDebug("Create HTTP client pool %s(%s):%s %s proxy %s:%d", host, sttngs.tlsPeerName, port, use_tls, sttngs.proxyURL.host, sttngs.proxyURL.port);
 		pool = new ConnectionPool!HTTPClient({
 				auto ret = new HTTPClient;
 				ret.connect(host, port, use_tls, sttngs);
@@ -220,6 +214,13 @@ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, const(HTTPC
 	return pool.lockConnection();
 }
 
+/// Ditto
+auto connectHTTP(URL url, const(HTTPClientSettings) settings = null)
+{
+	const use_tls = isTLSRequired(url, settings);
+	return connectHTTP(url.getFilteredHost, url.port, use_tls, settings);
+}
+
 static ~this()
 {
 	foreach (ci; s_connections) {
@@ -229,7 +230,7 @@ static ~this()
 	}
 }
 
-private struct ConnInfo { string host; ushort port; bool useTLS; string proxyIP; ushort proxyPort; NetworkAddress bind_addr; }
+private struct ConnInfo { string host; string tlsPeerName; ushort port; bool useTLS; string proxyIP; ushort proxyPort; NetworkAddress bind_addr; }
 private static vibe.utils.array.FixedRingBuffer!(Tuple!(ConnInfo, ConnectionPool!HTTPClient), 16) s_connections;
 
 
@@ -244,13 +245,7 @@ class HTTPClientSettings {
 	URL proxyURL;
 	Duration defaultKeepAliveTimeout = 10.seconds;
 
-	/** Timeout for establishing a connection to the server
-
-		Note that this setting is only supported when using the vibe-core
-		module. If using one of the legacy drivers, any value other than
-		`Duration.max` will emit a runtime warning and connects without a
-		specific timeout.
-	*/
+	/// Timeout for establishing a connection to the server
 	Duration connectTimeout = Duration.max;
 
 	/// Timeout during read operations on the underyling transport
@@ -268,6 +263,13 @@ class HTTPClientSettings {
 	*/
 	void delegate(TLSContext ctx) @safe nothrow tlsContextSetup;
 
+	/**
+		TLS Peer name override.
+
+		Allows to customize the tls peer name sent to server during the TLS connection setup (SNI)
+	*/
+	string tlsPeerName;
+
 	@property HTTPClientSettings dup()
 	const @safe {
 		auto ret = new HTTPClientSettings;
@@ -277,6 +279,7 @@ class HTTPClientSettings {
 		ret.networkInterface = this.networkInterface;
 		ret.dnsAddressFamily = this.dnsAddressFamily;
 		ret.tlsContextSetup = this.tlsContextSetup;
+		ret.tlsPeerName = this.tlsPeerName;
 		return ret;
 	}
 }
@@ -294,8 +297,8 @@ unittest {
 		},
 		(scope res){
 			logInfo("Headers:");
-			foreach(h; res.headers.byKeyValue) {
-				logInfo("%s: %s", h.key, h.value);
+			foreach (key, ref value; res.headers.byKeyValue) {
+				logInfo("%s: %s", key, value);
 			}
 			logInfo("Response: %s", res.bodyReader.readAllUTF8());
 		}, settings);
@@ -377,6 +380,7 @@ final class HTTPClient {
 	private {
 		Rebindable!(const(HTTPClientSettings)) m_settings;
 		string m_server;
+		string m_tlsPeerName;
 		ushort m_port;
 		bool m_useTLS;
 		TCPConnection m_conn;
@@ -409,9 +413,11 @@ final class HTTPClient {
 	static void setTLSSetupCallback(void function(TLSContext) @safe func) @trusted { ms_tlsSetup = func; }
 
 	/**
-		Connects to a specific server.
+		Sets up this HTTPClient to connect to a specific server.
 
 		This method may only be called if any previous connection has been closed.
+
+		The actual connection is deferred until a request is initiated (using `HTTPClient.request`).
 	*/
 	void connect(string server, ushort port = 80, bool use_tls = false, const(HTTPClientSettings) settings = defaultSettings)
 	{
@@ -423,6 +429,7 @@ final class HTTPClient {
 		m_keepAliveTimeout = settings.defaultKeepAliveTimeout;
 		m_keepAliveLimit = Clock.currTime(UTC()) + m_keepAliveTimeout;
 		m_server = server;
+		m_tlsPeerName = settings.tlsPeerName.length ? settings.tlsPeerName : server;
 		m_port = port;
 		m_useTLS = use_tls;
 		if (use_tls) {
@@ -502,13 +509,15 @@ final class HTTPClient {
 		m_responding = true;
 
 		static if (is(T == HTTPClientResponse))
-			res = new HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
+			res = new HTTPClientResponse(this, close_conn);
 		else
-			res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
+			res = scoped!HTTPClientResponse(this, close_conn);
+
+		res.initialize(has_body, request_allocator, connected_time);
 
 		if (res.headers.get("Proxy-Authenticate", null) !is null){
 			res.dropBody();
-			throw new HTTPStatusException(HTTPStatus.ProxyAuthenticationRequired, "Proxy Authentication Failed.");
+			throw new HTTPStatusException(HTTPStatus.proxyAuthenticationRequired, "Proxy Authentication Failed.");
 		}
 
 	}
@@ -549,7 +558,8 @@ final class HTTPClient {
 		bool has_body = doRequestWithRetry(requester, false, close_conn, connected_time);
 
 		m_responding = true;
-		auto res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
+		auto res = scoped!HTTPClientResponse(this, close_conn);
+		res.initialize(has_body, request_allocator, connected_time);
 
 		// proxy implementation
 		if (res.headers.get("Proxy-Authenticate", null) !is null) {
@@ -568,7 +578,8 @@ final class HTTPClient {
 				// just an informational status -> read and handle next response
 				if (m_responding) res.dropBody();
 				if (m_conn) {
-					res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time);
+					res = scoped!HTTPClientResponse(this, close_conn);
+					res.initialize(has_body, request_allocator, connected_time);
 					continue;
 				}
 			}
@@ -596,7 +607,8 @@ final class HTTPClient {
 		}
 		bool has_body = doRequestWithRetry(requester, false, close_conn, connected_time);
 		m_responding = true;
-		auto res = new HTTPClientResponse(this, has_body, close_conn, () @trusted { return vibeThreadAllocator(); } (), connected_time);
+		auto res = new HTTPClientResponse(this, close_conn);
+		res.initialize(has_body, () @trusted { return vibeThreadAllocator(); } (), connected_time);
 
 		// proxy implementation
 		if (res.headers.get("Proxy-Authenticate", null) !is null) {
@@ -608,7 +620,7 @@ final class HTTPClient {
 
 	private bool doRequestWithRetry(scope void delegate(HTTPClientRequest req) requester, bool confirmed_proxy_auth /* basic only */, out bool close_conn, out SysTime connected_time)
 	{
-		if (m_conn && m_conn.connected && connected_time > m_keepAliveLimit){
+		if (m_conn && m_conn.connected && Clock.currTime(UTC()) > m_keepAliveLimit){
 			logDebug("Disconnected to avoid timeout");
 			disconnect();
 		}
@@ -719,7 +731,7 @@ final class HTTPClient {
 
 			m_stream = m_conn;
 			if (m_useTLS) {
-				try m_tlsStream = createTLSStream(m_conn, m_tls, TLSStreamState.connecting, m_server, m_conn.remoteAddress);
+				try m_tlsStream = createTLSStream(m_conn, m_tls, TLSStreamState.connecting, m_tlsPeerName, m_conn.remoteAddress);
 				catch (Exception e) {
 					m_conn.close();
 					m_conn = TCPConnection.init;
@@ -768,13 +780,7 @@ final class HTTPClient {
 
 private auto connectTCPWithTimeout(NetworkAddress addr, NetworkAddress bind_address, Duration timeout)
 {
-	version (Have_vibe_core) {
-		return connectTCP(addr, bind_address, timeout);
-	} else {
-		if (timeout != Duration.max)
-			logWarn("HTTP client connect timeout is set, but not supported by the legacy vibe-d:core module.");
-		return connectTCP(addr, bind_address);
-	}
+	return connectTCP(addr, bind_address, timeout);
 }
 
 /**
@@ -852,7 +858,7 @@ final class HTTPClientRequest : HTTPRequest {
 	{
 		import vibe.stream.wrapper : streamOutputRange;
 
-		headers["Content-Type"] = "application/json";
+		headers["Content-Type"] = "application/json; charset=UTF-8";
 
 		// set an explicit content-length field if chunked encoding is not allowed
 		if (!allow_chunked) {
@@ -1016,16 +1022,19 @@ final class HTTPClientResponse : HTTPResponse {
 	}
 
 	/// private
-	this(HTTPClient client, bool has_body, bool close_conn, IAllocator alloc, SysTime connected_time = Clock.currTime(UTC()))
-	{
+	this(HTTPClient client, bool close_conn)
+	nothrow {
 		m_client = client;
 		m_closeConn = close_conn;
+	}
 
+	private void initialize(bool has_body, IAllocator alloc, SysTime connected_time = Clock.currTime(UTC()))
+	{
 		scope(failure) finalize(true);
 
 		// read and parse status line ("HTTP/#.# #[ $]\r\n")
 		logTrace("HTTP client reading status line");
-		string stln = () @trusted { return cast(string)client.m_stream.readLine(HTTPClient.maxHeaderLineLength, "\r\n", alloc); } ();
+		string stln = () @trusted { return cast(string)m_client.m_stream.readLine(HTTPClient.maxHeaderLineLength, "\r\n", alloc); } ();
 		logTrace("stln: %s", stln);
 		this.httpVersion = parseHTTPVersion(stln);
 
@@ -1039,7 +1048,7 @@ final class HTTPClientResponse : HTTPResponse {
 		}
 
 		// read headers until an empty line is hit
-		parseRFC5322Header(client.m_stream, this.headers, HTTPClient.maxHeaderLineLength, alloc, false);
+		parseRFC5322Header(m_client.m_stream, this.headers, HTTPClient.maxHeaderLineLength, alloc, false);
 
 		logTrace("---------------------");
 		logTrace("HTTP client response:");
@@ -1258,6 +1267,7 @@ final class HTTPClientResponse : HTTPResponse {
 		auto cli = m_client;
 		m_client = null;
 		cli.m_responding = false;
+		destroy(m_endCallback);
 		destroy(m_zlibInputStream);
 		destroy(m_chunkedInputStream);
 		destroy(m_limitedInputStream);
@@ -1282,7 +1292,7 @@ package auto getFilteredHost(URL url)
 
 // This object is a placeholder and should to never be modified.
 package @property const(HTTPClientSettings) defaultSettings()
-@trusted {
+@trusted nothrow {
 	__gshared HTTPClientSettings ret = new HTTPClientSettings;
 	return ret;
 }
diff --git a/source/vibe/http/common.d b/source/vibe/http/common.d
index 540819a..e9d368e 100644
--- a/source/vibe/http/common.d
+++ b/source/vibe/http/common.d
@@ -1,7 +1,7 @@
 /**
 	Common classes for HTTP clients and servers.
 
-	Copyright: © 2012-2015 RejectedSoftware e.K.
+	Copyright: © 2012-2015 Sönke Ludwig
 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
 	Authors: Sönke Ludwig, Jan Krüger
 */
@@ -15,10 +15,10 @@ import vibe.inet.message;
 import vibe.stream.operations;
 import vibe.textfilter.urlencode : urlEncode, urlDecode;
 import vibe.utils.array;
+import vibe.utils.dictionarylist;
+import vibe.internal.allocator;
 import vibe.internal.freelistref;
 import vibe.internal.interfaceproxy : InterfaceProxy, interfaceProxy;
-import vibe.internal.allocator;
-import vibe.utils.dictionarylist;
 import vibe.utils.string;
 
 import std.algorithm;
@@ -30,12 +30,12 @@ import std.format;
 import std.range : isOutputRange;
 import std.string;
 import std.typecons;
+import std.uni: asLowerCase, sicmp;
 
 
 enum HTTPVersion {
 	HTTP_1_0,
-	HTTP_1_1,
-	HTTP_2
+	HTTP_1_1
 }
 
 
@@ -213,6 +213,8 @@ class HTTPRequest {
 	{
 	}
 
+	scope:
+
 	public override string toString()
 	{
 		return httpMethodString(method) ~ " " ~ requestURL ~ " " ~ getHTTPVersionString(httpVersion);
@@ -261,10 +263,10 @@ class HTTPRequest {
 		auto ph = "connection" in headers;
 		switch(httpVersion) {
 			case HTTPVersion.HTTP_1_0:
-				if (ph && toLower(*ph) == "keep-alive") return true;
+				if (ph && asLowerCase(*ph).equal("keep-alive")) return true;
 				return false;
 			case HTTPVersion.HTTP_1_1:
-				if (ph && toLower(*ph) != "keep-alive") return false;
+				if (ph && !(asLowerCase(*ph).equal("keep-alive"))) return false;
 				return true;
 			default:
 				return false;
@@ -286,7 +288,7 @@ class HTTPResponse {
 		HTTPVersion httpVersion = HTTPVersion.HTTP_1_1;
 
 		/// The status code of the response, 200 by default
-		int statusCode = HTTPStatus.OK;
+		int statusCode = HTTPStatus.ok;
 
 		/** The status phrase of the response
 
@@ -301,6 +303,8 @@ class HTTPResponse {
 		@property ref DictionaryList!Cookie cookies() { return m_cookies; }
 	}
 
+	scope:
+
 	public override string toString()
 	{
 		auto app = appender!string();
@@ -322,7 +326,7 @@ class HTTPResponse {
 	Throwing this exception from within a request handler will produce a matching error page.
 */
 class HTTPStatusException : Exception {
-	@safe:
+	pure nothrow @safe @nogc:
 
 	private {
 		int m_status;
@@ -330,7 +334,7 @@ class HTTPStatusException : Exception {
 
 	this(int status, string message = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
 	{
-		super(message != "" ? message : httpStatusText(status), file, line, next);
+		super(message.length ? message : httpStatusText(status), file, line, next);
 		m_status = status;
 	}
 
@@ -349,26 +353,27 @@ final class MultiPart {
 	string[string] form;
 }
 
+/**
+ * Returns:
+ *     The version string corresponding to the `ver`,
+ *     suitable for usage in the start line of the request.
+ */
 string getHTTPVersionString(HTTPVersion ver)
-@safe nothrow {
+nothrow pure @nogc @safe {
 	final switch(ver){
 		case HTTPVersion.HTTP_1_0: return "HTTP/1.0";
 		case HTTPVersion.HTTP_1_1: return "HTTP/1.1";
-		case HTTPVersion.HTTP_2:   return "HTTP/2";
 	}
 }
 
 
 HTTPVersion parseHTTPVersion(ref string str)
 @safe {
-	enforceBadRequest(str.startsWith("HTTP/"));
-	str = str[5 .. $];
-	int majorVersion = parse!int(str);
-	enforceBadRequest(str.startsWith("."));
-	str = str[1 .. $];
+	enforceBadRequest(str.startsWith("HTTP/1."));
+	str = str[7 .. $];
 	int minorVersion = parse!int(str);
 
-	enforceBadRequest( majorVersion == 1 && (minorVersion == 0 || minorVersion == 1) );
+	enforceBadRequest( minorVersion == 0 || minorVersion == 1 );
 	return minorVersion == 0 ? HTTPVersion.HTTP_1_0 : HTTPVersion.HTTP_1_1;
 }
 
@@ -385,12 +390,6 @@ final class ChunkedInputStream : InputStream
 		ulong m_bytesInCurrentChunk = 0;
 	}
 
-	deprecated("Use createChunkedInputStream() instead.")
-	this(InputStream stream)
-	{
-		this(interfaceProxy!InputStream(stream), true);
-	}
-
 	/// private
 	this(InterfaceProxy!InputStream stream, bool dummy)
 	{
@@ -489,17 +488,11 @@ final class ChunkedOutputStream : OutputStream {
 		ChunkExtensionCallback m_chunkExtensionCallback = null;
 	}
 
-	deprecated("Use createChunkedOutputStream() instead.")
-	this(OutputStream stream, IAllocator alloc = vibeThreadAllocator())
-	{
-		this(interfaceProxy!OutputStream(stream), alloc, true);
-	}
-
 	/// private
 	this(InterfaceProxy!OutputStream stream, IAllocator alloc, bool dummy)
 	{
 		m_out = stream;
-        m_buffer = AllocAppender!(ubyte[])(alloc);
+		m_buffer = AllocAppender!(ubyte[])(alloc);
 	}
 
 	/** Maximum buffer size used to buffer individual chunks.
@@ -548,7 +541,15 @@ final class ChunkedOutputStream : OutputStream {
 		}
 	}
 
-	size_t write(in ubyte[] bytes_, IOMode mode)
+	static if (is(typeof(.OutputStream.outputStreamVersion)) && .OutputStream.outputStreamVersion > 1) {
+		override size_t write(scope const(ubyte)[] bytes_, IOMode mode) { return doWrite(bytes_, mode); }
+	} else {
+		override size_t write(in ubyte[] bytes_, IOMode mode) { return doWrite(bytes_, mode); }
+	}
+
+	alias write = OutputStream.write;
+
+	private size_t doWrite(scope const(ubyte)[] bytes_, IOMode mode)
 	{
 		assert(!m_finalized);
 		const(ubyte)[] bytes = bytes_;
@@ -568,8 +569,6 @@ final class ChunkedOutputStream : OutputStream {
 		return nbytes;
 	}
 
-	alias write = OutputStream.write;
-
 	void flush()
 	{
 		assert(!m_finalized);
@@ -612,18 +611,17 @@ final class ChunkedOutputStream : OutputStream {
 }
 
 /// Creates a new `ChunkedInputStream` instance.
-ChunkedOutputStream createChunkedOutputStream(OS)(OS destination_stream, IAllocator allocator = vibeThreadAllocator()) if (isOutputStream!OS)
+ChunkedOutputStream createChunkedOutputStream(OS)(OS destination_stream, IAllocator allocator = theAllocator()) if (isOutputStream!OS)
 {
 	return new ChunkedOutputStream(interfaceProxy!OutputStream(destination_stream), allocator, true);
 }
 
 /// Creates a new `ChunkedOutputStream` instance.
-FreeListRef!ChunkedOutputStream createChunkedOutputStreamFL(OS)(OS destination_stream, IAllocator allocator = vibeThreadAllocator()) if (isOutputStream!OS)
+FreeListRef!ChunkedOutputStream createChunkedOutputStreamFL(OS)(OS destination_stream, IAllocator allocator = theAllocator()) if (isOutputStream!OS)
 {
 	return FreeListRef!ChunkedOutputStream(interfaceProxy!OutputStream(destination_stream), allocator, true);
 }
 
-
 /// Parses the cookie from a header field, returning the name of the cookie.
 /// Implements an algorithm equivalent to https://tools.ietf.org/html/rfc6265#section-5.2
 /// Returns: the cookie name as return value, populates the dst argument or allocates on the GC for the tuple overload.
@@ -632,8 +630,6 @@ string parseHTTPCookie(string header_string, scope Cookie dst)
 in {
 	assert(dst !is null);
 } do {
-	import std.uni : sicmp;
-
 	if (!header_string.length)
 		return typeof(return).init;
 
@@ -714,6 +710,7 @@ final class Cookie {
 		long m_maxAge;
 		bool m_secure;
 		bool m_httpOnly;
+		SameSite m_sameSite;
 	}
 
 	enum Encoding {
@@ -722,6 +719,12 @@ final class Cookie {
 		none = raw
 	}
 
+	enum SameSite {
+		default_,
+		lax,
+		strict,
+	}
+
 	/// Cookie payload
 	@property void value(string value) { m_value = urlEncode(value); }
 	/// ditto
@@ -772,6 +775,12 @@ final class Cookie {
 	/// ditto
 	@property bool httpOnly() const { return m_httpOnly; }
 
+	/** Prevent cross-site request forgery.
+	*/
+	@property void sameSite(Cookie.SameSite value) { m_sameSite = value; }
+	/// ditto
+	@property Cookie.SameSite sameSite() const { return m_sameSite; }
+
 	/** Sets the "expires" and "max-age" attributes to limit the life time of
 		the cookie.
 	*/
@@ -820,6 +829,12 @@ final class Cookie {
 		if (this.maxAge) dst.formattedWrite("; Max-Age=%s", this.maxAge);
 		if (this.secure) dst.put("; Secure");
 		if (this.httpOnly) dst.put("; HttpOnly");
+		with(Cookie.SameSite)
+		final switch(this.sameSite) {
+			case default_: break;
+			case lax: dst.put("; SameSite=Lax"); break;
+			case strict: dst.put("; SameSite=Strict"); break;
+		}
 	}
 
 	private static void validateValue(string value)
@@ -849,6 +864,43 @@ unittest {
 	assertThrown(c.value);
 
 	assertThrown(c.setValue("foo;bar", Cookie.Encoding.raw));
+
+	auto tup = parseHTTPCookie("foo=bar; HttpOnly; Secure; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=60000; Domain=foo.com; Path=/users");
+	assert(tup[0] == "foo");
+	assert(tup[1].value == "bar");
+	assert(tup[1].httpOnly == true);
+	assert(tup[1].secure == true);
+	assert(tup[1].expires == "Wed, 09 Jun 2021 10:18:14 GMT");
+	assert(tup[1].maxAge == 60000L);
+	assert(tup[1].domain == "foo.com");
+	assert(tup[1].path == "/users");
+
+	tup = parseHTTPCookie("SESSIONID=0123456789ABCDEF0123456789ABCDEF; Path=/site; HttpOnly");
+	assert(tup[0] == "SESSIONID");
+	assert(tup[1].value == "0123456789ABCDEF0123456789ABCDEF");
+	assert(tup[1].httpOnly == true);
+	assert(tup[1].secure == false);
+	assert(tup[1].expires == "");
+	assert(tup[1].maxAge == 0);
+	assert(tup[1].domain == "");
+	assert(tup[1].path == "/site");
+
+	tup = parseHTTPCookie("invalid");
+	assert(!tup[0].length);
+
+	tup = parseHTTPCookie("valid=");
+	assert(tup[0] == "valid");
+	assert(tup[1].value == "");
+
+	tup = parseHTTPCookie("valid=;Path=/bar;Path=foo;Expires=14   ; Something   ; Domain=..example.org");
+	assert(tup[0] == "valid");
+	assert(tup[1].value == "");
+	assert(tup[1].httpOnly == false);
+	assert(tup[1].secure == false);
+	assert(tup[1].expires == "");
+	assert(tup[1].maxAge == 0);
+	assert(tup[1].domain == ".example.org"); // spec says you must strip only the first leading dot
+	assert(tup[1].path == "");
 }
 
 
diff --git a/source/vibe/http/dist.d b/source/vibe/http/dist.d
new file mode 100644
index 0000000..c45f11a
--- /dev/null
+++ b/source/vibe/http/dist.d
@@ -0,0 +1,52 @@
+/**
+	Interface for the VibeDist load balancer
+
+	Copyright: © 2012-2013 Sönke Ludwig
+	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
+	Authors: Sönke Ludwig, Jan Krüger
+*/
+module vibe.http.dist;
+
+import vibe.core.log;
+import vibe.data.json;
+import vibe.inet.url;
+import vibe.http.client;
+import vibe.http.server;
+
+import std.conv;
+import std.exception;
+import std.process;
+
+
+/**
+	Listens for HTTP connections on the specified load balancer using the given HTTP server settings.
+
+	This function is usable as direct replacement of listenHTTP
+*/
+HTTPListener listenHTTPDist(HTTPServerSettings settings, HTTPServerRequestDelegate handler, string balancer_address, ushort balancer_port = 11000)
+@safe {
+	Json regmsg = Json.emptyObject;
+	regmsg["host_name"] = settings.hostName;
+	regmsg["port"] = settings.port;
+	regmsg["ssl_settings"] = "";
+	regmsg["pid"] = thisProcessID;
+	//regmsg.sslContext = settings.sslContext; // TODO: send key/cert contents
+
+	HTTPServerSettings local_settings = settings.dup;
+	local_settings.bindAddresses = ["127.0.0.1"];
+	local_settings.port = 0;
+	local_settings.disableDistHost = true;
+	auto ret = listenHTTP(local_settings, handler);
+
+	requestHTTP(URL("http://"~balancer_address~":"~to!string(balancer_port)~"/register"), (scope req){
+			logInfo("Listening for VibeDist connections on port %d", req.localAddress.port);
+			regmsg["local_address"] = "127.0.0.1";
+			regmsg["local_port"] = req.localAddress.port;
+			req.method = HTTPMethod.POST;
+			req.writeJsonBody(regmsg);
+		}, (scope res){
+			enforce(res.statusCode == HTTPStatus.ok, "Failed to register with load balancer.");
+		});
+
+	return ret;
+}
diff --git a/source/vibe/http/fileserver.d b/source/vibe/http/fileserver.d
index e76a28c..8f5b82b 100644
--- a/source/vibe/http/fileserver.d
+++ b/source/vibe/http/fileserver.d
@@ -55,7 +55,7 @@ HTTPServerRequestDelegateS serveStaticFiles(NativePath local_path, HTTPFileServe
 	@safe {
 		string srv_path;
 		if (auto pp = "pathMatch" in req.params) srv_path = *pp;
-		else if (req.path.length > 0) srv_path = req.path;
+		else if (req.requestPath != InetPath.init) srv_path = (cast(PosixPath)req.requestPath).toString();
 		else srv_path = req.requestURL;
 
 		if (!srv_path.startsWith(settings.serverPathPrefix)) {
@@ -282,14 +282,14 @@ private void sendFileImpl(scope HTTPServerRequest req, scope HTTPServerResponse
 	// return if the file does not exist
 	if (!existsFile(pathstr)){
 		if (settings.options & HTTPFileServerOption.failIfNotFound)
-			throw new HTTPStatusException(HTTPStatus.NotFound);
+			throw new HTTPStatusException(HTTPStatus.notFound);
 		return;
 	}
 
 	FileInfo dirent;
 	try dirent = getFileInfo(pathstr);
 	catch(Exception){
-		throw new HTTPStatusException(HTTPStatus.InternalServerError, "Failed to get information for the file due to a file system error.");
+		throw new HTTPStatusException(HTTPStatus.internalServerError, "Failed to get information for the file due to a file system error.");
 	}
 
 	if (dirent.isDirectory) {
@@ -297,7 +297,7 @@ private void sendFileImpl(scope HTTPServerRequest req, scope HTTPServerResponse
 			return sendFileImpl(req, res, path ~ "index.html", settings);
 		logDebugV("Hit directory when serving files, ignoring: %s", pathstr);
 		if (settings.options & HTTPFileServerOption.failIfNotFound)
-			throw new HTTPStatusException(HTTPStatus.NotFound);
+			throw new HTTPStatusException(HTTPStatus.notFound);
 		return;
 	}
 
@@ -320,7 +320,7 @@ private void sendFileImpl(scope HTTPServerRequest req, scope HTTPServerResponse
 		range = parseRangeHeader(*prange, dirent.size, res);
 
 		// potential integer overflow with rangeEnd - rangeStart == size_t.max is intended. This only happens with empty files, the + 1 will then put it back to 0
-		res.headers["Content-Length"] = to!string(range.min - range.max);
+		res.headers["Content-Length"] = to!string(range.max - range.min);
 		res.headers["Content-Range"] = "bytes %s-%s/%s".format(range.min, range.max - 1, dirent.size);
 		res.statusCode = HTTPStatus.partialContent;
 	} else res.headers["Content-Length"] = dirent.size.to!string;
@@ -341,7 +341,7 @@ private void sendFileImpl(scope HTTPServerRequest req, scope HTTPServerResponse
 
 		try dirent = getFileInfo(encodedFilepath);
 		catch(Exception){
-			throw new HTTPStatusException(HTTPStatus.InternalServerError, "Failed to get information for the file due to a file system error.");
+			throw new HTTPStatusException(HTTPStatus.internalServerError, "Failed to get information for the file due to a file system error.");
 		}
 
 		// encoded file must be younger than original else warn
diff --git a/source/vibe/http/form.d b/source/vibe/http/form.d
index 2a46b91..7a772af 100644
--- a/source/vibe/http/form.d
+++ b/source/vibe/http/form.d
@@ -5,7 +5,6 @@
 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
 	Authors: Sönke Ludwig, Jan Krüger
 */
-deprecated("Use vibe.inet.webform instead")
 module vibe.http.form;
 
 public import vibe.inet.webform;
diff --git a/source/vibe/http/headers.d b/source/vibe/http/headers.d
deleted file mode 100644
index 1501cfa..0000000
--- a/source/vibe/http/headers.d
+++ /dev/null
@@ -1,13 +0,0 @@
-module vibe.http.headers;
-
-import taggedalgebraic;
-import std.datetime.systime : SysTime;
-import std.typecons : Tuple;
-
-struct HTTPHeaderValueTypes {
-	Tuple!(const(char)[], const(char)[]) generic;
-	SysTime date;
-	long contentLength;
-}
-
-alias HTTPHeaderValue = TaggedAlgebraic!HTTPHeaderValueTypes;
diff --git a/source/vibe/http/internal/http1.d b/source/vibe/http/internal/http1.d
deleted file mode 100644
index ff10472..0000000
--- a/source/vibe/http/internal/http1.d
+++ /dev/null
@@ -1,514 +0,0 @@
-module vibe.http.internal.http1;
-
-import vibe.http.internal.http2.http2;
-import vibe.http.internal.http2.settings;
-import vibe.core.stream;
-import vibe.core.core : runTask;
-import vibe.core.net;
-import vibe.http.server;
-import vibe.core.stream;
-import vibe.core.core : runTask;
-import vibe.core.net;
-import vibe.http.server;
-import vibe.internal.allocator;
-import vibe.internal.freelistref;
-import vibe.internal.interfaceproxy : InterfaceProxy;
-import vibe.core.file;
-import vibe.core.log;
-import vibe.inet.url;
-import vibe.inet.webform;
-import vibe.data.json;
-import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
-import vibe.utils.array;
-import vibe.utils.string;
-import vibe.stream.counting;
-import vibe.stream.operations;
-import vibe.stream.zlib;
-import vibe.textfilter.urlencode : urlEncode, urlDecode;
-import vibe.stream.tls;
-
-import std.datetime;
-import std.typecons;
-import std.conv;
-import std.array;
-import std.algorithm;
-import std.format;
-import std.parallelism;
-import std.exception;
-import std.string;
-import std.encoding : sanitize;
-import std.traits : isInstanceOf, ReturnType;
-
-private alias TLSStreamType = ReturnType!(createTLSStreamFL!(InterfaceProxy!Stream));
-
-void handleHTTP1Connection(ConnectionStream)(ConnectionStream connection, HTTPServerContext context)
-@safe	if (isConnectionStream!ConnectionStream)
-{
-	connection.tcpNoDelay = true;
-
-	//logInfo("Connection");
-	version(HaveNoTLS) {} else {
-		TLSStreamType tls_stream;
-	}
-
-	// If this is a HTTPS server, initiate TLS
-	if (context.tlsContext) {
-		version (HaveNoTLS) assert(false, "No TLS support compiled in.");
-		else {
-
-			logDebug("Accept TLS connection: %s", context.tlsContext.kind);
-
-			//TODO: determine if there's a better alternative to InterfaceProxy
-			InterfaceProxy!Stream http_stream;
-			http_stream = connection;
-
-			// TODO: reverse DNS lookup for peer_name of the incoming connection for TLS client certificate verification purposes
-			tls_stream = createTLSStreamFL(http_stream, context.tlsContext, TLSStreamState.accepting, null, connection.remoteAddress);
-
-			Nullable!string proto = tls_stream.alpn;
-			if(!proto.isNull && proto == "h2") {
-				HTTP2Settings settings;
-				auto h2context = new HTTP2ServerContext(context, settings);
-				handleHTTP2Connection(tls_stream, connection, h2context);
-				return;
-			}
-			http_stream = tls_stream;
-		}
-	}
-	handleHTTP1RequestChain(connection, context);
-}
-
-private void handleHTTP1RequestChain(ConnectionStream)(ConnectionStream connection, HTTPContext context)
-@safe
-{
-	//logInfo("HTTP/1 Request Chain Handler");
-
-	// copies connection/context instead of creating a heap closure
-	static struct CB {
-		ConnectionStream connection;
-		HTTPContext context;
-
-		void opCall(bool st)
-		{
-			if (!st) connection.close;
-			else runTask(&handleHTTP1RequestChain, connection, context);
-		}
-	}
-
-	while(true) {
-		CB cb = {connection, context};
-		auto st = connection.waitForDataAsync(cb);
-
-		final switch(st) {
-			case WaitForDataAsyncStatus.waiting: return;
-			case WaitForDataAsyncStatus.noMoreData: connection.close; return;
-			case WaitForDataAsyncStatus.dataAvailable: handleHTTP1Request(connection, context); break;
-		}
-	}
-}
-
-private void handleHTTP1Request(ConnectionStream)(ConnectionStream connection, HTTPContext context)
-@safe
-{
-	//logInfo("HTTP/1 Request Handler");
-
-	HTTPServerSettings settings;
-	InterfaceProxy!Stream http_stream;
-	http_stream = connection;
-	bool keep_alive;
-	() @trusted {
-		import vibe.internal.utilallocator: RegionListAllocator;
-		version (VibeManualMemoryManagement)
-			scope request_allocator = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
-		else
-			scope request_allocator = new RegionListAllocator!(shared(GCAllocator), true)(1024, GCAllocator.instance);
-
-		originalHandleRequest(http_stream, connection, context, settings, keep_alive, request_allocator);
-
-		if (!keep_alive) { connection.close; return; }
-	} ();
-
-	if(!connection.waitForData()) {
-		connection.close;
-		logWarn("Reached end of stream while reading.");
-	}
-}
-
-
-/* Previous vibe.http handleRequest
- * Gets called by handleHTTP1Request
- */
-private bool originalHandleRequest(InterfaceProxy!Stream http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, HTTPServerSettings settings, ref bool keep_alive, scope IAllocator request_allocator)
-@safe {
-
-	//logInfo ("Old request handler");
-	import std.algorithm.searching : canFind;
-
-	SysTime reqtime = Clock.currTime(UTC());
-
-	// some instances that live only while the request is running
-	//FreeListRef!HTTPServerRequest req = FreeListRef!HTTPServerRequest(reqtime, listen_info.bindPort);
-	auto req = HTTPServerRequest(reqtime, listen_info.bindPort);
-
-	FreeListRef!TimeoutHTTPInputStream timeout_http_input_stream;
-	FreeListRef!LimitedHTTPInputStream limited_http_input_stream;
-	FreeListRef!ChunkedInputStream chunked_input_stream;
-
-	// store the IP address
-	req.clientAddress = tcp_connection.remoteAddress;
-
-	if (!listen_info.hasVirtualHosts) {
-		logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
-		keep_alive = false;
-		return false;
-	}
-
-	// Default to the first virtual host for this listener
-	HTTPServerContext.VirtualHost context = listen_info.virtualHosts[0];
-	HTTPServerRequestDelegate request_task = context.requestHandler;
-	settings = context.settings;
-
-	// temporarily set to the default settings, the virtual host specific settings will be set further down
-	req.m_settings = settings;
-
-	// Create the response object
-	InterfaceProxy!ConnectionStream cproxy = tcp_connection;
-	auto res = HTTPServerResponse(http_stream, cproxy, settings, request_allocator/*.Scoped_payload*/);
-	auto istls = listen_info.tlsContext !is null;
-	req.tls = istls;
-	res.tls = istls;
-
-	if (req.tls) {
-		version (HaveNoTLS) assert(false);
-		else {
-			static if (is(InterfaceProxy!ConnectionStream == ConnectionStream))
-				req.clientCertificate = (cast(TLSStream)http_stream).peerCertificate;
-			else if (is(typeof(http_stream) : TLSStream))
-				req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate;
-			else
-				// TODO fix: no client certificate
-				assert(false);
-		}
-	}
-
-	// Error page handler
-	void errorOut(int code, string msg, string debug_msg, Throwable ex)
-	@safe {
-		assert(!res.headerWritten);
-
-		// stack traces sometimes contain random bytes - make sure they are replaced
-		debug_msg = sanitizeUTF8(cast(const(ubyte)[])debug_msg);
-
-		res.setStatusCode(code);
-		if (settings.errorPageHandler) {
-			//[>scope<] auto err = new HTTPServerErrorInfo;
-			HTTPServerErrorInfo err;
-			err.code = code;
-			err.message = msg;
-			err.debugMessage = debug_msg;
-			err.exception = ex;
-			settings.handleErrorPage(req, res, err);
-		} else {
-			if (debug_msg.length)
-				res.writeBody(format("%s - %s\n\n%s\n\nInternal error information:\n%s", code, httpStatusText(code), msg, debug_msg));
-			else res.writeBody(format("%s - %s\n\n%s", code, httpStatusText(code), msg));
-		}
-		assert(res.headerWritten);
-	}
-
-	bool parsed = false;
-	/*bool*/ keep_alive = false;
-
-	// parse the request
-	try {
-		logTrace("reading request..");
-
-		// limit the total request time
-		InterfaceProxy!InputStream reqReader = http_stream;
-		if (settings.maxRequestTime > dur!"seconds"(0) && settings.maxRequestTime != Duration.max) {
-			timeout_http_input_stream = FreeListRef!TimeoutHTTPInputStream(reqReader, settings.maxRequestTime, reqtime);
-			reqReader = timeout_http_input_stream;
-		}
-
-		// basic request parsing
-		uint h2 = parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize);
-		if(h2) {
-			// start http/2 with prior knowledge
-			uint len = 22 - h2;
-			ubyte[] dummy; dummy.length = len;
-
-			http_stream.read(dummy); // finish reading connection preface
-			auto h2settings = HTTP2Settings();
-			auto h2context = new HTTP2ServerContext(listen_info, h2settings);
-			handleHTTP2Connection(tcp_connection, tcp_connection, h2context, true);
-			return true;
-		}
-
-		logTrace("Got request header.");
-
-		// find the matching virtual host
-		string reqhost;
-		ushort reqport = 0;
-		{
-			string s = req.host;
-			enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
-			if (s.startsWith('[')) { // IPv6 address
-				auto idx = s.indexOf(']');
-				enforce(idx > 0, "Missing closing ']' for IPv6 address.");
-				reqhost = s[1 .. idx];
-				s = s[idx+1 .. $];
-			} else if (s.length) { // host name or IPv4 address
-				auto idx = s.indexOf(':');
-				if (idx < 0) idx = s.length;
-				enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
-				reqhost = s[0 .. idx];
-				s = s[idx .. $];
-			}
-			if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
-		}
-
-		foreach (ctx; listen_info.virtualHosts)
-			if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
-				(!reqport || reqport == ctx.settings.port))
-			{
-				context = ctx;
-				settings = ctx.settings;
-				request_task = ctx.requestHandler;
-				break;
-			}
-		req.m_settings = settings;
-		res.m_settings = settings;
-
-		// setup compressed output
-		if (settings.useCompressionIfPossible) {
-			if (auto pae = "Accept-Encoding" in req.headers) {
-				if (canFind(*pae, "gzip")) {
-					res.headers["Content-Encoding"] = "gzip";
-				} else if (canFind(*pae, "deflate")) {
-					res.headers["Content-Encoding"] = "deflate";
-				}
-			}
-		}
-
-		// limit request size
-		if (auto pcl = "Content-Length" in req.headers) {
-			string v = *pcl;
-			auto contentLength = parse!ulong(v); // DMDBUG: to! thinks there is a H in the string
-			enforceBadRequest(v.length == 0, "Invalid content-length");
-			enforceBadRequest(settings.maxRequestSize <= 0 || contentLength <= settings.maxRequestSize, "Request size too big");
-			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, contentLength);
-		} else if (auto pt = "Transfer-Encoding" in req.headers) {
-			enforceBadRequest(icmp(*pt, "chunked") == 0);
-			chunked_input_stream = createChunkedInputStreamFL(reqReader);
-			InterfaceProxy!InputStream ciproxy = chunked_input_stream;
-			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(ciproxy, settings.maxRequestSize, true);
-		} else {
-			limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, 0);
-		}
-		req.bodyReader = limited_http_input_stream;
-
-		// handle Expect header
-		if (auto pv = "Expect" in req.headers) {
-			if (icmp2(*pv, "100-continue") == 0) {
-				logTrace("sending 100 continue");
-				http_stream.write("HTTP/1.1 100 Continue\r\n\r\n");
-			}
-		}
-
-		// eagerly parse the URL as its lightweight and defacto @nogc
-		auto url = URL.parse(req.requestURI);
-		req.queryString = url.queryString;
-		req.username = url.username;
-		req.password = url.password;
-		req.requestPath = url.path;
-
-		// lookup the session
-		if (settings.sessionStore) {
-			// use the first cookie that contains a valid session ID in case
-			// of multiple matching session cookies
-			foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
-				req.session = settings.sessionStore.open(val);
-				res.m_session = req.session;
-				if (req.session) break;
-			}
-		}
-
-		// write default headers
-		if (req.method == HTTPMethod.HEAD) res.m_isHeadResponse = true;
-		if (settings.serverString.length)
-			res.headers["Server"] = settings.serverString;
-		res.headers["Date"] = formatRFC822DateAlloc(request_allocator, reqtime);
-		if (req.persistent) res.headers["Keep-Alive"] = formatAlloc(request_allocator, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
-
-		// finished parsing the request
-		parsed = true;
-		logTrace("persist: %s", req.persistent);
-		keep_alive = req.persistent;
-
-		// handle the request
-		logTrace("handle request (body %d)", req.bodyReader.leastSize);
-
-		/**
-		 * UPGRADE TO HTTP/2 for cleartext HTTP/1
-		 * this requires that the "Upgrade" header is set
-		 * with value "h2c" (for cleartext)
-		 * "h2" is ignored since it is used for TLS protocol switching (ALPN)
-		 */
-		if(req.headers.get("Upgrade") == "h2c" ) {
-			import vibe.stream.memory;
-			import vibe.http.internal.http2.exchange;
-
-			// write the original response to a buffer
-			string createResBuffer(ref MemoryOutputStream buf, ref size_t hlen) @trusted
-			{
-				res.bodyWriterH2(buf, true);
-				auto statusLine = (cast(string)buf.data).split("\r\n")[0];
-				hlen = buf.data.length;
-
-				// write body to buffer
-				request_task(req, res);
-
-				// no matching path in handler
-				if(buf.data.length == hlen && req.method != HTTPMethod.HEAD) {
-					return "HTTP/2 404 Not Found\r\n";
-				}
-
-				//// matching path, data needs to be saved
-				//if(req.method != HTTPMethod.HEAD) {
-					//ctx.resBody = request_allocator.make!(ubyte)(buf.data[hlen..$]);
-				//}
-
-				return statusLine;
-			}
-
-			auto psettings = "HTTP2-Settings" in req.headers;
-			enforceHTTP(psettings !is null, HTTPStatus.badRequest, "Upgrade request must
-					include HTTP2-Settings");
-			auto h2settings = *psettings;
-
-			logInfo("Switching to HTTP/2");
-			logTrace("handle request (body %d)", req.bodyReader.leastSize);
-
-			// initialize the request handler
-			auto h2context = new HTTP2ServerContext(listen_info);
-
-			MemoryOutputStream buf = createMemoryOutputStream(request_allocator);
-			size_t hlen;
-			auto st = createResBuffer(buf, hlen);
-			auto switchRes = HTTPServerResponse(http_stream, cproxy, settings, request_allocator);
-
-			return startHTTP2Connection(tcp_connection, h2settings, h2context, switchRes,
-					res.headers, st, request_allocator, buf.data[hlen..$]);
-		}
-
-		res.httpVersion = req.httpVersion;
-		request_task(req, res);
-
-
-		// if no one has written anything, return 404
-		if (!res.headerWritten) {
-			string dbg_msg;
-			logDiagnostic("No response written for %s", req.requestURI);
-			if (settings.options & HTTPServerOption.errorStackTraces)
-				dbg_msg = format("No routes match path '%s'", req.requestURI);
-			errorOut(HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound), dbg_msg, null);
-		}
-	} catch (HTTPStatusException err) {
-		if (!res.headerWritten) errorOut(err.status, err.msg, err.debugMessage, err);
-		else logDiagnostic("HTTPSterrorOutatusException while writing the response: %s", err.msg);
-		debug logDebug("Exception while handling request %s %s: %s", req.method, req.requestURI, () @trusted { return err.toString().sanitize; } ());
-		if (!parsed || res.headerWritten || justifiesConnectionClose(err.status))
-			keep_alive = false;
-	} catch (UncaughtException e) {
-		auto status = parsed ? HTTPStatus.internalServerError : HTTPStatus.badRequest;
-		string dbg_msg;
-		if (settings.options & HTTPServerOption.errorStackTraces) dbg_msg = () @trusted { return e.toString().sanitize; } ();
-		if (!res.headerWritten && tcp_connection.connected) errorOut(status, httpStatusText(status), dbg_msg, e);
-		else logDiagnostic("Error while writing the response: %s", e.msg);
-		debug logDebug("Exception while handling request %s %s: %s", req.method, req.requestURI, () @trusted { return e.toString().sanitize(); } ());
-		if (!parsed || res.headerWritten || !cast(Exception)e) keep_alive = false;
-	}
-
-	if (tcp_connection.connected) {
-		if (req.bodyReader && !req.bodyReader.empty) {
-			req.bodyReader.pipe(nullSink);
-			logTrace("dropped body");
-		}
-	}
-
-	// finalize (e.g. for chunked encoding)
-	res.finalize();
-
-	foreach (k, v ; req._files.byKeyValue) {
-		if (existsFile(v.tempPath)) {
-			removeFile(v.tempPath);
-			logDebug("Deleted upload tempfile %s", v.tempPath.toString());
-		}
-	}
-
-	if (!req.noLog) {
-		// log the request to access log
-		foreach (log; context.loggers)
-			log.log(req, res);
-	}
-
-	//logTrace("return %s (used pool memory: %s/%s)", keep_alive, request_allocator.allocatedSize, request_allocator.totalSize);
-	logTrace("return %s", keep_alive);
-	return keep_alive != false;
-}
-
-final class LimitedHTTPInputStream : LimitedInputStream {
-@safe:
-
-	this(InterfaceProxy!InputStream stream, ulong byte_limit, bool silent_limit = false) {
-		super(stream, byte_limit, silent_limit, true);
-	}
-	override void onSizeLimitReached() {
-		throw new HTTPStatusException(HTTPStatus.requestEntityTooLarge);
-	}
-}
-
-final class TimeoutHTTPInputStream : InputStream {
-@safe:
-
-	private {
-		long m_timeref;
-		long m_timeleft;
-		InterfaceProxy!InputStream m_in;
-	}
-
-	this(InterfaceProxy!InputStream stream, Duration timeleft, SysTime reftime)
-	{
-		enforce(timeleft > 0.seconds, "Timeout required");
-		m_in = stream;
-		m_timeleft = timeleft.total!"hnsecs"();
-		m_timeref = reftime.stdTime();
-	}
-
-	@property bool empty() { enforce(m_in, "InputStream missing"); return m_in.empty(); }
-	@property ulong leastSize() { enforce(m_in, "InputStream missing"); return m_in.leastSize();  }
-	@property bool dataAvailableForRead() {	 enforce(m_in, "InputStream missing"); return m_in.dataAvailableForRead; }
-	const(ubyte)[] peek() { return m_in.peek(); }
-
-	size_t read(scope ubyte[] dst, IOMode mode)
-	{
-		enforce(m_in, "InputStream missing");
-		size_t nread = 0;
-		checkTimeout();
-		// FIXME: this should use ConnectionStream.waitForData to enforce the timeout during the
-		// read operation
-		return m_in.read(dst, mode);
-	}
-
-	alias read = InputStream.read;
-
-	private void checkTimeout()
-	@safe {
-		auto curr = Clock.currStdTime();
-		auto diff = curr - m_timeref;
-		if (diff > m_timeleft) throw new HTTPStatusException(HTTPStatus.RequestTimeout);
-		m_timeleft -= diff;
-		m_timeref = curr;
-	}
-}
-
diff --git a/source/vibe/http/internal/http2/error.d b/source/vibe/http/internal/http2/error.d
deleted file mode 100644
index 4e00321..0000000
--- a/source/vibe/http/internal/http2/error.d
+++ /dev/null
@@ -1,84 +0,0 @@
-module vibe.http.internal.http2.error;
-
-import vibe.http.internal.http2.hpack.exception;
-import vibe.http.internal.http2.frame;
-
-import vibe.core.log;
-import vibe.core.net;
-import vibe.core.core;
-import vibe.core.stream;
-import vibe.stream.tls;
-import vibe.internal.array;
-import vibe.internal.allocator;
-import vibe.internal.freelistref;
-import vibe.internal.interfaceproxy;
-
-import std.range;
-import std.base64;
-import std.traits;
-import std.bitmanip; // read from ubyte (decoding)
-import std.typecons;
-import std.conv : to;
-import std.exception;
-import std.algorithm : canFind; // alpn callback
-import std.algorithm.iteration;
-
-enum HTTP2Error {
-	NO_ERROR 			= 0x0,
-	PROTOCOL_ERROR 		= 0x1,
-	INTERNAL_ERROR 		= 0x2,
-	FLOW_CONTROL_ERROR 	= 0x3,
-	SETTINGS_TIMEOUT 	= 0x4,
-	STREAM_CLOSED 		= 0x5,
-	FRAME_SIZE_ERROR 	= 0x6,
-	REFUSED_STREAM 		= 0x7,
-	CANCEL 				= 0x8,
-	COMPRESSION_ERROR 	= 0x9,
-	CONNECT_ERROR 		= 0xa,
-	ENHANCE_YOUR_CALM 	= 0xb,
-	INADEQUATE_SECURITY = 0xc,
-	HTTP_1_1_REQUIRED 	= 0xd
-}
-
-enum GOAWAYFrameLength = 17;
-
-/// creates a GOAWAY frame as defined in RFC 7540, section 6.8
-void buildGOAWAYFrame(R)(ref R buf, const uint streamId, HTTP2Error error)
-@safe @nogc
-{
-	assert(buf.length == GOAWAYFrameLength, "Unable to create GOAWAY frame");
-
-	// last stream processed by the server (client-initiated)
-	uint sid = (streamId > 1) ? streamId - 2 : 0;
-
-	buf.createHTTP2FrameHeader(8, HTTP2FrameType.GOAWAY, 0x0, 0x0);
-	buf.putBytes!4(sid & 127); // last stream ID
-	buf.putBytes!4(error);
-}
-/// ditto
-auto buildGOAWAYFrame(uint sid, HTTP2Error code) @safe
-{
-	BatchBuffer!(ubyte, GOAWAYFrameLength) gbuf;
-
-	gbuf.putN(GOAWAYFrameLength);
-	gbuf.buildGOAWAYFrame(sid, code);
-
-	return gbuf.peekDst;
-}
-
-
-/// exceptions
-T enforceHTTP2(T)(T condition, string message = null, HTTP2Error h2e = HTTP2Error.NO_ERROR, string file = __FILE__, typeof(__LINE__) line = __LINE__) @trusted
-{
-	return enforce(condition, new HTTP2Exception(message, h2e, file, line));
-}
-
-class HTTP2Exception : Exception
-{
-	HTTP2Error code;
-
-	this(string msg, HTTP2Error h2e = HTTP2Error.NO_ERROR, string file = __FILE__, size_t line = __LINE__) {
-		code = h2e;
-		super(msg, file, line);
-	}
-}
diff --git a/source/vibe/http/internal/http2/exchange.d b/source/vibe/http/internal/http2/exchange.d
deleted file mode 100644
index 8364b13..0000000
--- a/source/vibe/http/internal/http2/exchange.d
+++ /dev/null
@@ -1,585 +0,0 @@
-module vibe.http.internal.http2.exchange;
-
-import vibe.http.internal.http2.multiplexing;
-import vibe.http.internal.http2.settings;
-import vibe.http.internal.http2.http2 : HTTP2ConnectionStream, HTTP2StreamState;
-import vibe.http.internal.http2.hpack.hpack;
-import vibe.http.internal.http2.hpack.tables;
-import vibe.http.internal.http2.frame;
-
-import vibe.http.common;
-import vibe.http.status;
-import vibe.http.server;
-import vibe.core.log;
-import vibe.core.stream;
-import vibe.core.core;
-import vibe.internal.interfaceproxy;
-import vibe.stream.tls;
-import vibe.internal.allocator;
-import vibe.internal.array;
-import vibe.internal.utilallocator: RegionListAllocator;
-import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
-import vibe.utils.string;
-import vibe.stream.memory;
-import vibe.inet.url;
-import vibe.inet.message;
-
-import std.range;
-import std.string;
-import std.conv;
-import std.traits;
-import std.typecons;
-import std.datetime;
-import std.exception;
-import std.format;
-import std.algorithm.iteration;
-import std.algorithm.mutation;
-import std.algorithm.searching;
-import std.algorithm.comparison;
-
-/**
-  * HTTP/2 message exchange module as documented in:
-  * RFC 7540 (HTTP/2) section 8
-*/
-
-enum StartLine { REQUEST, RESPONSE };
-
-private alias H2F = HTTP2HeaderTableField;
-
-alias DataOutputStream = MemoryOutputStream;
-
-/// accepts a HTTP/1.1 header list, converts it to an HTTP/2 header frame and encodes it
-ubyte[] buildHeaderFrame(alias type)(string statusLine, InetHeaderMap headers,
-		HTTP2ServerContext context, ref IndexingTable table, scope IAllocator alloc, bool
-		isTLS = true) @safe
-{
-	// frame header + frame payload
-	FixedAppender!(ubyte[], 9) hbuf;
-	auto pbuf = AllocAppender!(ubyte[])(alloc);
-	auto res = AllocAppender!(ubyte[])(alloc);
-
-	// split the start line of each req / res into pseudo-headers
-	convertStartMessage(statusLine, pbuf, table, type, isTLS);
-
-	// "Host" header does not exist in HTTP/2, use ":authority" pseudo-header
-	if("Host" in headers) {
-		headers[":authority"] = headers["Host"];
-		headers.remove("Host");
-	}
-
-	foreach(k,v; headers.byKeyValue) {
-		H2F(k.toLower,v).encodeHPACK(pbuf, table);
-	}
-
-	// TODO padding
-	if(context.next_sid == 0) context.next_sid = 1;
-
-	hbuf.createHTTP2FrameHeader(cast(uint)pbuf.data.length, HTTP2FrameType.HEADERS, 0x0, context.next_sid);
-
-	res.put(hbuf.data);
-	res.put(pbuf.data);
-	return res.data;
-}
-
-/// DITTO for first request in case of h2c
-ubyte[] buildHeaderFrame(alias type)(string statusLine, InetHeaderMap headers,
-		HTTP2ServerContext context, scope IAllocator alloc) @trusted
-{
-	return buildHeaderFrame!type(statusLine, headers, context, context.table, alloc);
-}
-
-/// generates an HTTP/2 pseudo-header representation to encode a HTTP/1.1 start message line
-private void convertStartMessage(T)(string src, ref T dst, ref IndexingTable table, StartLine type, bool isTLS = true) @safe
-{
-	void toPseudo(string buf) @safe
-	{
-		// exclude protocol version (not needed in HTTP/2)
-		if(buf != "HTTP/1.1" && buf != "HTTP/2")
-		{
-			if(type == StartLine.REQUEST) { // request
-				//	request-line = method SP request-target SP HTTP-version CRLF
-					try {
-						auto method = httpMethodFromString(buf); // might throw
-						H2F(":method", method).encodeHPACK(dst, table);
-					} catch(Exception e) {
-						H2F(":scheme", (isTLS ? "https" : "http")).encodeHPACK(dst, table);
-						H2F(":path", buf).encodeHPACK(dst, table);
-				}
-			} else if(type == StartLine.RESPONSE) { // response (status-line)
-				// status-line = HTTP-version SP status-code SP reason-phrase CRLF
-				static foreach(st; __traits(allMembers, HTTPStatus)) {
-					if(buf.isNumeric && __traits(getMember, HTTPStatus, st) == buf.to!int) {
-						mixin("H2F(\":status\",HTTPStatus."~st~").encodeHPACK(dst, table); return;");
-					}
-				}
-			}
-		}
-	}
-
-	// consider each chunk of the start message line
-	src.strip("\r\n").splitter(' ').each!(s => toPseudo(s));
-}
-
-unittest {
-	import std.experimental.allocator;
-	import std.experimental.allocator.mallocator;
-	HTTP2Settings settings;
-	HTTPServerContext ctx;
-	auto context = new HTTP2ServerContext(ctx, settings);
-	auto table = IndexingTable(settings.headerTableSize);
-	scope alloc = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
-
-	string statusline = "GET / HTTP/2\r\n\r\n";
-	InetHeaderMap hmap;
-	hmap["Host"] = "www.example.com";
-	ubyte[] expected = [0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1 , 0xe3, 0xc2 , 0xe5, 0xf2 , 0x3a, 0x6b , 0xa0, 0xab , 0x90, 0xf4 , 0xff];
-	// [9..$] excludes the HTTP/2 Frame header
-	auto res = buildHeaderFrame!(StartLine.REQUEST)(statusline, hmap, context, table, alloc,
-			false)[9..$];
-	assert(res == expected);
-
-	statusline = "HTTP/2 200 OK";
-	InetHeaderMap hmap1;
-	expected = [0x88];
-	res = buildHeaderFrame!(StartLine.RESPONSE)(statusline, hmap1, context, table, alloc,
-			false)[9..$];
-
-	assert(res == expected);
-}
-
-/* ======================================================= */
-/* 					HTTP/2 REQUEST HANDLING 			   */
-/* ======================================================= */
-
-/** Similar to originalHandleRequest, adapted to HTTP/2
-  * The request is converted to HTTPServerRequest through parseHTTP2RequestHeader
-  * once the HTTPServerResponse is built, HEADERS frame and (optionally) DATA Frames are sent
-*/
-bool handleHTTP2Request(UStream)(ref HTTP2ConnectionStream!UStream stream,
-		TCPConnection tcp_connection, HTTP2ServerContext h2context,
-		HTTP2HeaderTableField[] headers, ref IndexingTable table, scope IAllocator alloc) @safe
-{
-	SysTime reqtime = Clock.currTime(UTC());
-	HTTPServerContext listen_info = h2context.h1context;
-
-	// initialize request
-	auto req = HTTPServerRequest(reqtime, listen_info.bindPort);
-
-	// store the IP address
-	req.clientAddress = tcp_connection.remoteAddress;
-
-	if (!listen_info.hasVirtualHosts) {
-		logWarn("Didn't find a HTTP listening context for incoming connection. Dropping.");
-		return false;
-	}
-
-	// Default to the first virtual host for this listener
-	HTTPServerContext.VirtualHost context = listen_info.virtualHosts[0];
-	HTTPServerRequestDelegate request_task = context.requestHandler;
-	HTTPServerSettings settings = context.settings;
-
-	// temporarily set to the default settings
-	req.m_settings = settings;
-
-	// Create the response object
-	InterfaceProxy!ConnectionStream cproxy = tcp_connection;
-	InterfaceProxy!Stream cstream = stream.connection; // TCPConnection / TLSStream
-	auto res = HTTPServerResponse(cstream, cproxy, settings, alloc);
-
-	// check for TLS encryption
-	bool istls;
-	static if(is(UStream : TLSStream)) {
-		istls = true;
-	} else {
-		istls = false;
-	}
-	req.tls = istls;
-	res.tls = istls;
-
-	if (req.tls) {
-		version (HaveNoTLS) assert(false);
-		else {
-			static if (is(InterfaceProxy!Stream == Stream))
-				req.clientCertificate = (cast(TLSStream)stream.connection).peerCertificate;
-			else static if (is(typeof(stream.connection) : TLSStream))
-				req.clientCertificate = stream.connection.peerCertificate;
-			else
-				assert(false);
-		}
-	}
-
-	bool parsed = false;
-
-	// parse request:
-	// both status line + headers (already unpacked in `headers`)
-	// defined in vibe.http.server because of protected struct HTTPServerRequest
-	parseHTTP2RequestHeader(headers, req);
-	if(req.host.empty) {
-		req.host = tcp_connection.localAddress.toString;
-	}
-
-	if(req.tls) req.requestURI = "https://" ~ req.host ~ req.path;
-	else req.requestURI = "http://" ~ req.host ~ req.path;
-
-	string reqhost;
-	ushort reqport = 0;
-	{
-		string s = req.host;
-		enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header.");
-		if (s.startsWith('[')) { // IPv6 address
-			auto idx = s.indexOf(']');
-			enforce(idx > 0, "Missing closing ']' for IPv6 address.");
-			reqhost = s[1 .. idx];
-			s = s[idx+1 .. $];
-		} else if (s.length) { // host name or IPv4 address
-			auto idx = s.indexOf(':');
-			if (idx < 0) idx = s.length;
-			enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header.");
-			reqhost = s[0 .. idx];
-			s = s[idx .. $];
-		}
-		if (s.startsWith(':')) reqport = s[1 .. $].to!ushort;
-	}
-	foreach (ctx; listen_info.virtualHosts) {
-		if (icmp2(ctx.settings.hostName, reqhost) == 0 &&
-				(!reqport || reqport == ctx.settings.port))
-		{
-			context = ctx;
-			settings = ctx.settings;
-			request_task = ctx.requestHandler;
-			break;
-		}
-	}
-	req.m_settings = settings;
-	res.m_settings = settings;
-
-	// setup compressed output
-	if (settings.useCompressionIfPossible) {
-		if (auto pae = "Accept-Encoding" in req.headers) {
-			if (canFind(*pae, "gzip")) {
-				res.headers["Content-Encoding"] = "gzip";
-			} else if (canFind(*pae, "deflate")) {
-				res.headers["Content-Encoding"] = "deflate";
-			}
-		}
-	}
-
-	// handle Expect header
-	if (auto pv = "Expect" in req.headers) {
-		if (icmp2(*pv, "100-continue") == 0) {
-			logTrace("sending 100 continue");
-			InetHeaderMap hmap;
-			auto cres =	buildHeaderFrame!(StartLine.RESPONSE)(
-					"HTTP/1.1 100 Continue\r\n\r\n", hmap, h2context, table, alloc, istls);
-		}
-		assert(false); // TODO determine if actually used with HTTP/2 (PUSH_PROMISE?)
-	}
-
-	// eagerly parse the URL as its lightweight and defacto @nogc
-	auto url = URL.parse(req.requestURI);
-	req.queryString = url.queryString;
-	req.username = url.username;
-	req.password = url.password;
-	req.requestPath = url.path;
-
-	// lookup the session
-	if (settings.sessionStore) {
-		// use the first cookie that contains a valid session ID in case
-		// of multiple matching session cookies
-		foreach (val; req.cookies.getAll(settings.sessionIdCookie)) {
-			req.session = settings.sessionStore.open(val);
-			res.m_session = req.session;
-			if (req.session) break;
-		}
-	}
-
-	// write default headers
-	if (req.method == HTTPMethod.HEAD) res.m_isHeadResponse = true;
-	if (settings.serverString.length)
-		res.headers["Server"] = settings.serverString;
-	res.headers["Date"] = formatRFC822DateAlloc(alloc, reqtime);
-	if (req.persistent) res.headers["Keep-Alive"] = formatAlloc(alloc, "timeout=%d", settings.keepAliveTimeout.total!"seconds"());
-
-	// finished parsing the request
-	parsed = true;
-	logTrace("persist: %s", req.persistent);
-	//keep_alive = req.persistent;
-	logDebug("Received %s request on stream ID %d", req.method, stream.streamId);
-
-	// utility to format the status line
-	auto statusLine = AllocAppender!string(alloc);
-
-	void writeLine(T...)(string fmt, T args)
-		@safe {
-			formattedWrite(() @trusted { return &statusLine; } (), fmt, args);
-			statusLine.put("\r\n");
-			logTrace(fmt, args);
-		}
-
-	// header frame to be sent
-	ubyte[] headerFrame;
-
-	// handle payload (DATA frame)
-	auto dataWriter = createDataOutputStream(alloc);
-	res.bodyWriterH2 = dataWriter;
-	h2context.next_sid = stream.streamId;
-
-	// run task (writes body)
-	request_task(req, res);
-
-	if(req.method != HTTPMethod.HEAD && dataWriter.data.length > 0) { // HEADERS + DATA
-
-		// write the status line
-		writeLine("%s %d %s",
-				getHTTPVersionString(res.httpVersion),
-				res.statusCode,
-				res.statusPhrase.length ? res.statusPhrase : httpStatusText(res.statusCode));
-
-		// build the HEADERS frame
-		() @trusted {
-			headerFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine.data, res.headers,
-					h2context, table, alloc, istls);
-		} ();
-
-		// send HEADERS frame
-		if(headerFrame.length < h2context.settings.maxFrameSize) {
-			headerFrame[4] += 0x4; // set END_HEADERS flag (sending complete header)
-			cstream.write(headerFrame);
-
-		} else {
-			// TODO CONTINUATION frames
-			assert(false);
-		}
-
-		logDebug("Sent HEADERS frame on streamID " ~ stream.streamId.to!string);
-
-		auto tlen = dataWriter.data.length;
-
-		// multiple DATA Frames might be required
-		void sendDataTask()
-		@safe {
-			logDebug("[DATA] Starting dispatch task");
-
-			scope(exit) {
-				if(stream.state == HTTP2StreamState.HALF_CLOSED_REMOTE) {
-					stream.state = HTTP2StreamState.CLOSED;
-				} else {
-					stream.state = HTTP2StreamState.HALF_CLOSED_LOCAL;
-				}
-			}
-
-			try {
-
-				auto abort = false;
-				uint done = 0;
-
-				// window length
-				uint wlen = sendWindowLength(h2context.multiplexer,
-						stream.streamId, h2context.settings.maxFrameSize, tlen);
-
-				// until the whole payload is sent
-				while(done <= tlen) {
-					auto dataFrame = AllocAppender!(ubyte[])(alloc);
-
-					dataFrame.createHTTP2FrameHeader(
-								wlen,
-								HTTP2FrameType.DATA,
-								(done+wlen >= tlen) ? 0x1 : 0x0, // END_STREAM 0x1
-								stream.streamId
-							);
-
-					// send is over
-					if(done == tlen) {
-						logDebug("[DATA] Completed DATA frame dispatch");
-						// remove task from waiting state
-						doneCondition(h2context.multiplexer, stream.streamId);
-						closeStream(h2context.multiplexer, stream.streamId);
-						break;
-					}
-
-					// wait to resume and retry
-					if(wlen == 0) {
-						logDebug("[DATA] Dispatch task waiting for WINDOW_UPDATE");
-
-						// after 60 seconds waiting, terminate dispatch
-						() @trusted {
-							auto timer = setTimer(600.seconds, {
-									logDebug("[DATA] timer expired, aborting dispatch");
-									notifyCondition(h2context.multiplexer);
-									abort = true;
-									});
-
-							// wait until a new WINDOW_UPDATE is received (or timer expires)
-							waitCondition(h2context.multiplexer, stream.streamId);
-
-							// task resumed: cancel timer
-							if(!abort) timer.stop;
-							else return;
-						} ();
-
-						logDebug("[DATA] Dispatch task resumed");
-
-					} else {
-						// write
-
-						dataFrame.put(dataWriter.data[done..done+wlen]);
-						cstream.write(dataFrame.data);
-
-						done += wlen;
-
-						logDebug("[DATA] Sent frame chunk (%d/%d bytes) on streamID %d",
-								done, tlen, stream.streamId);
-
-						updateWindow(h2context.multiplexer, stream.streamId, wlen);
-
-						// return control to the event loop
-						yield();
-					}
-
-					// compute new window length
-					wlen = sendWindowLength(h2context.multiplexer,
-							stream.streamId, h2context.settings.maxFrameSize, tlen - done);
-				}
-
-			} catch (Exception e) {
-				logDebug("[DATA] "~e.msg);
-				return;
-			}
-		}
-
-		// spawn the asynchronous data sender
-		sendDataTask();
-
-	} else if(dataWriter.data.length > 0) { // HEAD response, HEADERS frame, no DATA
-
-		// write the status line
-		writeLine("%s %d %s",
-				getHTTPVersionString(res.httpVersion),
-				res.statusCode,
-				res.statusPhrase.length ? res.statusPhrase : httpStatusText(res.statusCode));
-
-		// build the HEADERS frame
-		() @trusted {
-			headerFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine.data, res.headers,
-					h2context, table, alloc, istls);
-		} ();
-
-		// send HEADERS frame
-		if(headerFrame.length < h2context.settings.maxFrameSize) {
-			headerFrame[4] += 0x5; // set END_HEADERS, END_STREAM flag
-			cstream.write(headerFrame);
-		} else {
-			// TODO CONTINUATION frames
-			assert(false);
-		}
-
-		logDebug("Sent HEADERS frame on streamID " ~ stream.streamId.to!string);
-
-		logDebug("[Data] No DATA frame to send");
-
-		if(stream.state == HTTP2StreamState.HALF_CLOSED_REMOTE) {
-			stream.state = HTTP2StreamState.CLOSED;
-		} else {
-			stream.state = HTTP2StreamState.HALF_CLOSED_LOCAL;
-		}
-		closeStream(h2context.multiplexer, stream.streamId);
-
-	} else { // 404: no DATA for the given path
-
-		writeLine("%s %d %s",
-				"HTTP/2",
-				404,
-				"Not Found");
-
-		// build the HEADERS frame
-		() @trusted {
-			headerFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine.data, res.headers,
-					h2context, table, alloc, istls);
-		} ();
-
-		if(headerFrame.length < h2context.settings.maxFrameSize) {
-			headerFrame[4] += 0x5; // set END_HEADERS, END_STREAM flag
-			cstream.write(headerFrame);
-		}
-
-		logDebug("No response: sent 404 HEADERS frame");
-
-	}
-
-	return true;
-}
-
-
-uint sendWindowLength(Mux)(ref Mux multiplexer, const uint sid, const uint maxfsize, const ulong len) @safe
-{
-	return min(connectionWindow(multiplexer), streamConnectionWindow(multiplexer, sid), maxfsize, len);
-}
-
-void updateWindow(Mux)(ref Mux multiplexer, const uint sid, const ulong sent) @safe
-{
-	auto cw = connectionWindow(multiplexer) - sent;
-	auto scw = streamConnectionWindow(multiplexer, sid) - sent;
-
-	updateConnectionWindow(multiplexer, cw);
-	updateStreamConnectionWindow(multiplexer, sid, cw);
-}
-
-private DataOutputStream createDataOutputStream(IAllocator alloc = vibeThreadAllocator())
-@safe nothrow {
-	return createMemoryOutputStream(alloc);
-}
-
-private HeaderOutputStream createHeaderOutputStream(IAllocator alloc = vibeThreadAllocator())
-@safe nothrow {
-    return new HeaderOutputStream(alloc);
-}
-
-private final class HeaderOutputStream : OutputStream {
-@safe:
-
-    private {
-        AllocAppender!(string) m_destination;
-    }
-
-    this(IAllocator alloc)
-    nothrow {
-        m_destination = AllocAppender!(string)(alloc);
-    }
-
-    /// An array with all data written to the stream so far.
-    @property string data() @trusted nothrow { return m_destination.data(); }
-
-    /// Resets the stream to its initial state containing no data.
-    void reset(AppenderResetMode mode = AppenderResetMode.keepData)
-    @system {
-        m_destination.reset(mode);
-    }
-
-    /// Reserves space for data - useful for optimization.
-    void reserve(size_t nbytes)
-    {
-        m_destination.reserve(nbytes);
-    }
-
-    size_t write(in string bytes, IOMode)
-    {
-        () @trusted { m_destination.put(bytes); } ();
-        return bytes.length;
-	}
-	/// DITTO
-    size_t write(const(ubyte[]) bytes, IOMode)
-    {
-        () @trusted { m_destination.put(cast(string)bytes); } ();
-        return bytes.length;
-	}
-
-    alias write = OutputStream.write;
-
-    void flush()
-    nothrow {
-    }
-
-    void finalize()
-    nothrow {
-    }
-}
diff --git a/source/vibe/http/internal/http2/frame.d b/source/vibe/http/internal/http2/frame.d
deleted file mode 100644
index b153f85..0000000
--- a/source/vibe/http/internal/http2/frame.d
+++ /dev/null
@@ -1,471 +0,0 @@
-module vibe.http.internal.http2.frame;
-
-import vibe.http.internal.http2.settings;
-import vibe.http.internal.http2.error;
-
-import vibe.internal.array;
-
-import std.typecons;
-import std.traits;
-import std.range;
-import std.array;
-import std.exception;
-import std.algorithm.iteration;
-import std.algorithm.mutation;
-
-
-/** This module implements HTTP/2 Frames, as defined in RFC 7540 under:
-  *
-  * Section 4: Frame overview, Frame header composition (octets) and their meaning
-  * https://tools.ietf.org/html/rfc7540#section-4
-  *
-  * Section 6: Frame definition according to Frame Type
-  * https://tools.ietf.org/html/rfc7540#section-6
-*/
-
-enum uint HTTP2HeaderLength = 9;
-
-enum HTTP2FrameType {
-	DATA 			= 0x0,
-	HEADERS 		= 0x1,
-	PRIORITY 		= 0x2,
-	RST_STREAM 		= 0x3,
-	SETTINGS 		= 0x4,
-	PUSH_PROMISE 	= 0x5,
-	PING 			= 0x6,
-	GOAWAY 			= 0x7,
-	WINDOW_UPDATE 	= 0x8,
-	CONTINUATION 	= 0x9
-}
-
-/*** FRAME PARSING ***/
-
-/// updated by `unpackHTTP2Frame`
-struct HTTP2FrameStreamDependency {
-	bool exclusive = false;
-	bool isPushPromise = false;
-	uint streamId = 0;
-	ubyte weight = 0;
-
-	@property bool isSet() @safe @nogc { return streamId != 0; }
-
-	void fill(R)(ref R src) @safe @nogc
-		if(is(ElementType!R : ubyte))
-	{
-		uint first = src.takeExactly(4).fromBytes(4);
-		exclusive = first & (cast(ulong)1 << 32);
-		streamId = first & ((cast(ulong)1 << 32) - 1);
-		src.popFrontN(4);
-
-		if(!isPushPromise) {
-			weight = src.front;
-			src.popFront();
-		}
-	}
-}
-
-/** unpacks a frame putting the payload in `payloadDst` and returning the header
-  * implements the checks required for each frame type (Section 6 of HTTP/2 RFC)
-  *
-  * Invoked by a possible HTTP/2 request handler, the payload is meant to be handled by
-  * the caller.
-  *
-  * Note: @nogc-compatible as long as payloadDst.put is @nogc (AllocAppender.put isn't)
-  */
-HTTP2FrameHeader unpackHTTP2Frame(R,T)(ref R payloadDst, T src, ref bool endStream, ref bool endHeaders, ref bool ack, ref HTTP2FrameStreamDependency sdep) @safe
-{
-	auto header = unpackHTTP2FrameHeader(src);
-	unpackHTTP2Frame(payloadDst, src, header, endStream, endHeaders, ack, sdep);
-	return header;
-}
-
-/// DITTO
-void unpackHTTP2Frame(R,T)(ref R payloadDst, T src, HTTP2FrameHeader header, ref bool endStream, ref bool endHeaders, ref bool ack, ref HTTP2FrameStreamDependency sdep) @safe
-{
-	size_t len = header.payloadLength;
-
-	switch(header.type) {
-		case HTTP2FrameType.DATA:
-			if(header.flags & 0x8) { // padding is set, first bit is pad length
-				len -= cast(size_t)src.front + 1;
-				src.popFront();
-				enforceHTTP2(src.length >= len, "Invalid pad length", HTTP2Error.PROTOCOL_ERROR);
-			}
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			src.popFrontN(header.payloadLength - len - 1); // remove padding
-			if(header.flags & 0x1) endStream = true;
-			break;
-
-		case HTTP2FrameType.HEADERS:
-			if(header.flags & 0x8) { // padding is set, first bit is pad length
-				len -= cast(size_t)src.front + 1;
-				src.popFront();
-				enforceHTTP2(src.length >= len, "Invalid pad length", HTTP2Error.PROTOCOL_ERROR);
-			}
-			if(header.flags & 0x20) { // priority is set, fill `sdep`
-				sdep.fill(src);
-				len -= 5;
-			}
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			src.popFrontN(header.payloadLength - len - 1); // remove padding
-			if(header.flags & 0x1) endStream = true;
-			if(header.flags & 0x4) endHeaders = true;
-			break;
-
-		case HTTP2FrameType.PRIORITY:
-			enforceHTTP2(len == 5, "Invalid PRIORITY Frame", HTTP2Error.PROTOCOL_ERROR);
-			sdep.fill(src);
-			break;
-
-		case HTTP2FrameType.RST_STREAM:
-			enforceHTTP2(len == 4, "Invalid RST_STREAM Frame", HTTP2Error.PROTOCOL_ERROR);
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			break;
-
-		case HTTP2FrameType.SETTINGS:
-			enforceHTTP2(len % 6 == 0, "Invalid SETTINGS Frame (FRAME_SIZE error)", HTTP2Error.PROTOCOL_ERROR);
-			enforceHTTP2(header.streamId == 0, "Invalid streamId for SETTINGS Frame", HTTP2Error.PROTOCOL_ERROR);
-			if(header.flags & 0x1) { // this is an ACK frame
-				enforceHTTP2(len == 0, "Invalid SETTINGS ACK Frame (FRAME_SIZE error)", HTTP2Error.PROTOCOL_ERROR);
-				ack = true;
-				break;
-			}
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			break;
-
-		case HTTP2FrameType.PUSH_PROMISE:
-			if(header.flags & 0x8) { // padding is set, first bit is pad length
-				len -= cast(size_t)src.front + 1;
-				src.popFront();
-				enforceHTTP2(src.length >= len, "Invalid pad length", HTTP2Error.PROTOCOL_ERROR);
-			}
-			sdep.isPushPromise = true;
-			sdep.fill(src);
-			len -= 4;
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			src.popFrontN(header.payloadLength - len - 1); // remove padding
-			if(header.flags & 0x4) endHeaders = true;
-			break;
-
-		case HTTP2FrameType.PING:
-			enforceHTTP2(len == 8, "Invalid PING Frame (FRAME_SIZE error)",
-					HTTP2Error.PROTOCOL_ERROR);
-			enforceHTTP2(header.streamId == 0, "Invalid streamId for PING Frame",
-					HTTP2Error.PROTOCOL_ERROR);
-			if(header.flags & 0x1) {
-				ack = true;
-			}
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			break;
-
-		case HTTP2FrameType.GOAWAY: // GOAWAY is used to close connection (in handler)
-			enforceHTTP2(len >= 8, "Invalid GOAWAY Frame (FRAME_SIZE error)",
-					HTTP2Error.PROTOCOL_ERROR);
-			enforceHTTP2(header.streamId == 0, "Invalid streamId for GOAWAY Frame",
-					HTTP2Error.PROTOCOL_ERROR);
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			break;
-
-		case HTTP2FrameType.WINDOW_UPDATE:
-			enforceHTTP2(len == 4, "Invalid WINDOW_UPDATE Frame (FRAME_SIZE error)",
-					HTTP2Error.PROTOCOL_ERROR);
-			foreach(i,b; src.takeExactly(len).enumerate) {
-				if(i == 0) b &= 0x7F; // reserved bit
-				payloadDst.put(b);
-				src.popFront();
-			}
-			break;
-
-		case HTTP2FrameType.CONTINUATION:
-			enforceHTTP2(header.streamId != 0, "Invalid streamId for CONTINUATION frame",
-					HTTP2Error.PROTOCOL_ERROR);
-			foreach(b; src.takeExactly(len)) {
-				payloadDst.put(b);
-				src.popFront();
-			}
-			if(header.flags & 0x4) endHeaders = true;
-			break;
-
-		default:
-			enforceHTTP2(false, "Invalid frame header unpacked.", HTTP2Error.PROTOCOL_ERROR);
-			break;
-	}
-}
-
-unittest {
-	import vibe.internal.array : FixedAppender;
-
-	FixedAppender!(ubyte[], 4) payloadDst;
-	bool endStream = false;
-	bool endHeaders = false;
-	bool ack = false;
-	HTTP2FrameStreamDependency sdep;
-
-
-	// DATA Frame
-	ubyte[] data = [0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == [1, 1, 1, 1]);
-
-	// HEADERS Frame
-	payloadDst.clear;
-	data = [0, 0, 4, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == [2, 2, 2, 2]);
-
-	// PRIORITY Frame
-	payloadDst.clear;
-	data = [0, 0, 5, 2, 0, 0, 0, 0, 3, 0, 0, 0, 2, 5];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == []);
-	assert(sdep.weight == 5 &&  sdep.streamId == 2);
-
-	// RST_STREAM Frame
-	payloadDst.clear;
-	data = [0, 0, 4, 3, 0, 0, 0, 0, 4, 4, 4, 4, 4];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == [4, 4, 4, 4]);
-
-	// SETTINGS Frame
-	FixedAppender!(ubyte[], 6) settingsDst;
-	data = [0, 0, 6, 4, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2];
-	settingsDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(settingsDst.data == [0, 1, 2, 2, 2, 2]);
-
-	// PUSH_PROMISE Frame
-	payloadDst.clear;
-	data = [0, 0, 8, 5, 0, 0, 0, 0, 5, 0, 0, 0, 2, 4, 4, 4, 4];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == [4, 4, 4, 4]);
-	assert(sdep.weight == 5 &&  sdep.streamId == 2);
-
-	// PING Frame
-	FixedAppender!(ubyte[], 8) pingDst;
-	data = [0, 0, 8, 6, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 4, 4];
-	pingDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(pingDst.data == [0, 0, 0, 2, 4, 4, 4, 4]);
-
-	// GOAWAY Frame
-	pingDst.clear;
-	data = [0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4];
-	pingDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(pingDst.data == [0, 0, 0, 2, 0, 0, 0, 4]);
-
-	// WINDOW_UPDATE
-	payloadDst.clear;
-	data = [0, 0, 4, 8, 0, 0, 0, 0, 6, 1, 1, 1, 1];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == [1, 1, 1, 1]);
-
-	// CONTINUATION
-	payloadDst.clear;
-	data = [0, 0, 4, 9, 0, 0, 0, 0, 6, 2, 2, 2, 2];
-	payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
-	assert(payloadDst.data == [2, 2, 2, 2]);
-}
-
-/*** FRAME BUILDING ***/
-
-/// concatenates a Frame header with a Frame payload
-void buildHTTP2Frame(R,H,T)(ref R dst, ref H header, ref T payload) @safe @nogc
-	if(is(ElementType!R : ubyte) && is(ElementType!T : ubyte))
-{
-	// put header
-	static if(is(H == HTTP2FrameHeader)) {
-		assert(header.payloadLength == payload.length, "Invalid payload length");
-		dst.serializeHTTP2FrameHeader(header);
-
-	} else static if(is(ElementType!H : ubyte)) {
-		auto len = header.takeExactly(3).fromBytes(3);
-		assert(len == payload.length, "Invalid payload length");
-		foreach(b; header) dst.put(b);
-	}
-
-	// put payload
-	foreach(b; payload) dst.put(b);
-}
-
-/// DITTO
-/// @nogc-compatible if dst.put is @nogc
-void buildHTTP2Frame(R,T)(ref R dst, T payload) @safe
-{
-	payload.copy(dst);
-}
-
-unittest {
-	auto header = HTTP2FrameHeader(4, cast(HTTP2FrameType)1, 0, 5);
-	ubyte[4] payload = [0, 1, 2, 3];
-	ubyte[] bheader = [0, 0, 4, 1, 0, 0, 0, 0, 5];
-	ubyte[13] expected = [0, 0, 4, 1, 0, 0, 0, 0, 5, 0, 1, 2, 3];
-
-	BatchBuffer!(ubyte, 13) dst, ddst;
-	dst.putN(13);
-	ddst.putN(13);
-	dst.buildHTTP2Frame(header, payload);
-	ddst.buildHTTP2Frame(bheader, payload);
-
-	assert(dst.peekDst == expected);
-	assert(ddst.peekDst == expected);
-}
-
-/*** FRAME HEADER ***/
-/// header packing
-/// @nogc-compatible if dst.put is @nogc
-void createHTTP2FrameHeader(R)(ref R dst, const uint len, const HTTP2FrameType type, const ubyte flags, const uint sid) @safe
-{
-	dst.serialize(HTTP2FrameHeader(len, type, flags, sid));
-}
-
-/// serializing
-void serializeHTTP2FrameHeader(R)(ref R dst, HTTP2FrameHeader header) @safe @nogc
-{
-	dst.serialize(header);
-}
-
-/// unpacking
-HTTP2FrameHeader unpackHTTP2FrameHeader(R)(scope ref R src) @safe @nogc
-{
-	scope header = HTTP2FrameHeader(src);
-	return header;
-}
-
-/** Implement an HTTP/2 Frame header
-  * The header is a 9-bit ubyte[9] string
-  */
-struct HTTP2FrameHeader
-{
-	private {
-		//ubyte[3] m_length; 			// 24-bit frame payload length
-		FixedAppender!(ubyte[], 3) m_length;
-		HTTP2FrameType m_type; 		// frame type (stored as ubyte for serialization)
-		ubyte m_flags; 				// frame flags
-		//ubyte[4] m_streamId;  		// stream id, uint (stored as ubyte for serialization)
-		FixedAppender!(ubyte[], 4) m_streamId;
-	}
-
-	this(const uint len, const HTTP2FrameType tp, const ubyte flg, const uint sid) @safe @nogc
-	{
-		assert(sid < (cast(ulong)1 << 32), "Invalid stream id");
-
-		m_length.putBytes!3(len);
-		m_type = tp;
-		m_flags = flg;
-		m_streamId.putBytes!4(sid & ((cast(ulong)1 << 32) - 1)); // reserved bit is 0
-	}
-
-	this(T)(ref T src) @safe @nogc
-		if(is(ElementType!T : ubyte))
-	{
-		m_length.put(src.take(3));
-		src.popFrontN(3);
-
-		m_type = cast(HTTP2FrameType)src.front; src.popFront;
-		m_flags = src.front; src.popFront;
-
-		m_streamId.put(src.take(1).front & 127); src.popFront; // ignore reserved bit
-		m_streamId.put(src.take(3));
-		src.popFrontN(3);
-	}
-
-	@property HTTP2FrameType type() @safe @nogc { return m_type; }
-
-	@property uint payloadLength() @safe @nogc { return m_length.data.fromBytes(3); }
-
-	@property ubyte flags() @safe @nogc { return m_flags; }
-
-	@property uint streamId() @safe @nogc { return m_streamId.data.fromBytes(4); }
-}
-
-/// convert 32-bit unsigned integer to N bytes (MSB first)
-void putBytes(uint N, R)(ref R dst, const(ulong) src) @safe @nogc
-{
-	assert(src >= 0 && src < (cast(ulong)1 << N*8), "Invalid frame payload length");
-	static if(hasLength!R) assert(dst.length >= N);
-
-	ubyte[N] buf;
-	foreach(i,ref b; buf) b = cast(ubyte)(src >> 8*(N-1-i)) & 0xff;
-
-	static if(isArray!R) {
-		dst.put(buf);
-	} else {
-		foreach(b; buf) dst.put(b);
-	}
-}
-
-/// convert a N-bytes representation MSB->LSB to uint
-uint fromBytes(R)(R src, uint n) @safe @nogc
-{
-	uint res = 0;
-	static if(isArray!R) {
-		foreach(i,b; src) res = res + (b << 8*(n-1-i));
-	} else {
-		foreach(i,b; src.enumerate.retro) res = res + (b << 8*i);
-	}
-	return res;
-}
-
-/// fill a buffer with fields from `header`
-/// @nogc-compatible if dst.put is @nogc
-private void serialize(R)(ref R dst, HTTP2FrameHeader header) @safe
-	if(isOutputRange!(R, ubyte))
-{
-	static foreach(f; __traits(allMembers, HTTP2FrameHeader)) {
-		static if(f != "__ctor" && f != "type"
-				&& f != "payloadLength" && f != "flags" && f != "streamId") {
-			static if(f == "m_length" || f == "m_streamId") {
-				mixin("dst.put(header."~f~".data);");
-			} else static if(f == "m_type") {
-				mixin("dst.put(cast(ubyte)header."~f~");");
-			} else {
-				mixin("dst.put(header."~f~");");
-			}
-		}
-	}
-}
-
-unittest {
-	import vibe.internal.array : FixedAppender;
-
-	auto header = HTTP2FrameHeader(2, cast(HTTP2FrameType)1, 0, 5);
-	ubyte[] expected = [0, 0, 2, 1, 0, 0, 0, 0, 5];
-	FixedAppender!(ubyte[], 9) dst;
-	// serialize to a ubyte[9] array
-	serialize(dst,header);
-	assert(dst.data == expected);
-
-	// test utility functions
-	FixedAppender!(ubyte[], 9) ddst;
-	ddst.createHTTP2FrameHeader(2, cast(HTTP2FrameType)1, 0, 5);
-	assert(dst.data == ddst.data);
-
-	FixedAppender!(ubyte[], 9) dddst;
-	dddst.serializeHTTP2FrameHeader(header);
-	assert(dst.data == dddst.data);
-
-	// test unpacking
-	assert(header == unpackHTTP2FrameHeader(expected));
-
-	assert(header.payloadLength == 2);
-}
-
diff --git a/source/vibe/http/internal/http2/hpack/decoder.d b/source/vibe/http/internal/http2/hpack/decoder.d
deleted file mode 100644
index c083b3e..0000000
--- a/source/vibe/http/internal/http2/hpack/decoder.d
+++ /dev/null
@@ -1,142 +0,0 @@
-module vibe.http.internal.http2.hpack.decoder;
-
-import vibe.http.internal.http2.hpack.huffman;
-import vibe.http.internal.http2.hpack.tables;
-import vibe.http.internal.http2.hpack.util;
-import vibe.http.internal.http2.hpack.exception;
-
-import vibe.internal.array : AllocAppender;
-import vibe.core.log;
-
-import std.range; // Decoder
-import std.string;
-import std.experimental.allocator;
-import std.experimental.allocator.mallocator;
-import std.exception;
-
-/** Module to implement an header decoder consistent with HPACK specifications (RFC 7541)
-  * The detailed description of the decoding process, examples and binary format details can
-  * be found at:
-  * Section 3: https://tools.ietf.org/html/rfc7541#section-3
-  * Section 6: https://tools.ietf.org/html/rfc7541#section-6
-  * Appendix C: https://tools.ietf.org/html/rfc7541#appendix-C
-*/
-alias HTTP2SettingValue = uint;
-
-void decode(I, R, T)(ref I src, ref R dst, ref IndexingTable table,  ref T alloc, ulong maxTableSize=4096) @trusted
-{
-	ubyte bbuf = src[0];
-	src = src[1..$];
-
-	if(bbuf & 128) {
-		auto res = decodeInteger(src, bbuf, 7);
-		dst.put(table[res]);
-	} else {
-		HTTP2HeaderTableField hres;
-		bool update = false;
-		auto adst = AllocAppender!string(alloc);
-
-		if (bbuf & 64) { // inserted in dynamic table
-			size_t idx = decodeInteger(src, bbuf, 6);
-			if(idx > 0) {  // name == table[index].name, value == literal
-				hres.name = table[idx].name;
-			} else {   // name == literal, value == literal
-				decodeLiteral(src, adst);
-				hres.name.setReset(adst);
-			}
-			decodeLiteral(src, adst);
-			hres.value.setReset(adst);
-			hres.index = true;
-			hres.neverIndex = false;
-
-		} else if(bbuf & 32) {
-			update = true;
-			auto nsize = decodeInteger(src, bbuf, 3);
-			enforce(nsize <= maxTableSize, "Invalid table size update");
-
-			table.updateSize(cast(HTTP2SettingValue)nsize);
-			logDebug("Updated dynamic table size to: %d octets", nsize);
-
-		} else if(bbuf & 16) { // NEVER inserted in dynamic table
-			size_t idx = decodeInteger(src, bbuf, 4);
-			if(idx > 0) {  // name == table[index].name, value == literal
-				hres.name = table[idx].name;
-			} else {   // name == literal, value == literal
-				decodeLiteral(src, adst);
-				hres.name.setReset(adst);
-			}
-			decodeLiteral(src, adst);
-			hres.value.setReset(adst);
-			hres.index = false;
-			hres.neverIndex = true;
-
-		} else { // this occourrence is not inserted in dynamic table
-			size_t idx = decodeInteger(src, bbuf, 4);
-			if(idx > 0) {  // name == table[index].name, value == literal
-				hres.name = table[idx].name;
-			} else {   // name == literal, value == literal
-				decodeLiteral(src, adst);
-				hres.name.setReset(adst);
-			}
-			decodeLiteral(src, adst);
-			hres.value.setReset(adst);
-			hres.index = hres.neverIndex = false;
-
-		}
-		assert(!(hres.index && hres.neverIndex), "Invalid header indexing information");
-
-		if(!update) dst.put(hres);
-	}
-}
-
-private void setReset(I,R)(ref I dst, ref R buf)
-	if(is(R == AllocAppender!string) || is(R == AllocAppender!(immutable(ubyte)[])))
-{
-	dst = buf.data;
-	buf.reset;
-}
-
-private size_t decodeInteger(I)(ref I src, ubyte bbuf, uint nbits) @safe @nogc
-{
-	auto res = bbuf.toInteger(8-nbits);
-
-	if (res < (1 << nbits) - 1) {
-		return res;
-	} else {
-		uint m = 0;
-		do {
-			// take another octet
-			bbuf = src[0];
-			src = src[1..$];
-			// concatenate it to the result
-			res = res + bbuf.toInteger(1)*(1 << m);
-			m += 7;
-		} while((bbuf & 128) == 128);
-		return res;
-	}
-}
-
-private void decodeLiteral(I,R)(ref I src, ref R dst) @safe
-{
- 	enforceHPACK(!src.empty, "Invalid literal header block");
-
-	ubyte bbuf = src[0];
-	src = src[1..$];
-
-	bool huffman = (bbuf & 128) ? true : false;
-
-	assert(!src.empty, "Cannot decode from empty range block");
-
-	// take a buffer of remaining octets
-	auto vlen = decodeInteger(src, bbuf, 7); // value length
-	enforceHPACK(vlen <= src.length, "Invalid literal decoded");
-
-	auto buf = src[0..vlen];
-	src = src[vlen..$];
-
-	if(huffman) { // huffman encoded
-		decodeHuffman(buf, dst);
-	} else { // raw encoded
-		dst.put(cast(string)buf);
-	}
-}
diff --git a/source/vibe/http/internal/http2/hpack/encoder.d b/source/vibe/http/internal/http2/hpack/encoder.d
deleted file mode 100644
index 3f15bd6..0000000
--- a/source/vibe/http/internal/http2/hpack/encoder.d
+++ /dev/null
@@ -1,126 +0,0 @@
-module vibe.http.internal.http2.hpack.encoder;
-
-import vibe.http.internal.http2.hpack.tables;
-import vibe.http.internal.http2.hpack.huffman;
-import vibe.http.internal.http2.hpack.util;
-
-import std.range;
-import std.typecons;
-import std.conv;
-import std.array;
-
-void encode(R)(HTTP2HeaderTableField header, ref R dst, ref IndexingTable table, bool huffman = true)
-@safe
-{
-	// try to encode as integer
-	bool indexed = encodeInteger(header, dst, table, huffman);
-	// if fail, encode as literal
-	if(!indexed) encodeLiteral(header, dst, huffman);
-}
-
-/// encode a pure integer (present in table) or integer name + literal value
-private bool encodeInteger(R)(const HTTP2HeaderTableField header, ref R dst, ref IndexingTable table, bool huffman = true)
-@trusted
-{
-	// check table for indexed headers
-	size_t idx = 1;
-	bool found = false;
-	size_t partialFound = false;
-
-	while(idx < table.size) {
-		// encode both name / value as index
-		auto h = table[idx];
-		if(h.name == header.name && h.value == header.value) {
-			found = true;
-			partialFound = false;
-			break;
-			// encode name as index, value as literal
-		} else if(h.name == header.name && h.value != header.value) {
-			found = false;
-			partialFound = idx;
-		}
-		idx++;
-	}
-
-	if(found) {
-		if(idx < 127) { // can be fit in one octet
-			dst.put(cast(ubyte)(idx ^ 128));
-		} else { 		// must be split in multiple octets
-			dst.put(cast(ubyte)255);
-			idx -= 127;
-			while (idx > 127) {
-				dst.put(cast(ubyte)((idx % 128) ^ 128));
-				idx = idx / 128;
-			}
-			dst.put(cast(ubyte)(idx & 127));
-		}
-		return true;
-
-	} else if(partialFound) {
-		// encode name as index ( always smaller than 64 )
-		if(header.index) dst.put(cast(ubyte)((partialFound + 64) & 127));
-		else if (header.neverIndex) dst.put(cast(ubyte)((partialFound + 16) & 31));
-		else dst.put(cast(ubyte)(partialFound & 15));
-		// encode value as literal
-		encodeLiteralField(to!string(header.value), dst, huffman);
-
-		return true;
-	}
-
-	return false;
-}
-
-/// encode a literal field depending on its indexing requirements
-private void encodeLiteral(R)(const HTTP2HeaderTableField header, ref R dst, bool huffman = true)
-@safe
-{
-	if(header.index) dst.put(cast(ubyte)(64));
-	else if(header.neverIndex) dst.put(cast(ubyte)(16));
-	else dst.put(cast(ubyte)(0));
-
-	encodeLiteralField(to!string(header.name), dst, huffman);
-	encodeLiteralField(to!string(header.value), dst, huffman);
-}
-
-/// encode a field (name / value) using huffman or raw encoding
-private void encodeLiteralField(R)(string src, ref R dst, bool huffman = true) @safe
-{
-	if(huffman) {
-		encodeHuffman(src, dst);
-	} else {
-		auto blen = (src.length) & 127;
-		dst.put(cast(ubyte)blen);
-		dst.put(cast(ubyte[])(to!string(src).dup));
-	}
-}
-
-unittest {
-	// encode integer
-	import vibe.internal.array : BatchBuffer;
-	import vibe.http.common;
-	auto table = IndexingTable(4096);
-
-	BatchBuffer!(ubyte, 1) bres;
-	bres.putN(1);
-	ubyte[1] expected = [0x82];
-	auto hint = HTTP2HeaderTableField(":method", HTTPMethod.GET);
-
-	assert(encodeInteger(hint, bres, table));
-	assert(bres.peekDst == expected);
-}
-
-unittest {
-	// encode literal
-	// custom-key: custom-header
-	import vibe.internal.array : BatchBuffer;
-	ubyte[26] lexpected = [0x40, 0x0a, 0x63, 0x75,  0x73, 0x74,  0x6f, 0x6d,  0x2d, 0x6b,
-		0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64,
-		0x65, 0x72];
-
-	BatchBuffer!(ubyte, 26) lres;
-	lres.putN(26);
-	auto hlit = HTTP2HeaderTableField("custom-key", "custom-header");
-
-	encodeLiteral(hlit, lres, false);
-	assert(lres.peekDst == lexpected);
-}
diff --git a/source/vibe/http/internal/http2/hpack/exception.d b/source/vibe/http/internal/http2/hpack/exception.d
deleted file mode 100644
index 5df59ba..0000000
--- a/source/vibe/http/internal/http2/hpack/exception.d
+++ /dev/null
@@ -1,30 +0,0 @@
-module vibe.http.internal.http2.hpack.exception;
-
-import std.exception;
-
-T enforceHPACK(T)(T condition, string message = null, string file = __FILE__,
-		typeof(__LINE__) line = __LINE__) @safe
-{
-	return enforce(condition, new HPACKException(message, file, line));
-}
-
-class HPACKException : Exception
-{
-	this(string msg, string file = __FILE__, size_t line = __LINE__) @safe {
-		super(msg, file, line);
-	}
-}
-
-class HPACKDecoderException : HPACKException
-{
-	this(string msg, string file = __FILE__, size_t line = __LINE__) {
-		super(msg, file, line);
-	}
-}
-
-class HPACKEncoderException : HPACKException
-{
-	this(string msg, string file = __FILE__, size_t line = __LINE__) {
-		super(msg, file, line);
-	}
-}
diff --git a/source/vibe/http/internal/http2/hpack/hpack.d b/source/vibe/http/internal/http2/hpack/hpack.d
deleted file mode 100644
index 31981ec..0000000
--- a/source/vibe/http/internal/http2/hpack/hpack.d
+++ /dev/null
@@ -1,267 +0,0 @@
-//module vibe.http.internal.hpack.hpack;
-module vibe.http.internal.http2.hpack.hpack;
-
-import vibe.http.internal.http2.hpack.encoder;
-import vibe.http.internal.http2.hpack.decoder;
-import vibe.http.internal.http2.hpack.tables;
-
-
-import std.range;
-import std.typecons;
-import std.array; // appender
-import std.algorithm.iteration;
-
-
-/// interface for the HPACK encoder
-void encodeHPACK(I,R)(I src, ref R dst, ref IndexingTable table, bool huffman = true) @safe
-	if(is(I == HTTP2HeaderTableField) || is(ElementType!I : HTTP2HeaderTableField))
-{
-	static if(is(I == HTTP2HeaderTableField)) {
-		src.encode(dst, table, huffman);
-	} else if(is(ElementType!I : HTTP2HeaderTableField)){
-		src.each!(h => h.encode(dst, table, huffman));
-	}
-}
-
-void decodeHPACK(I,R,T)(I src, ref R dst, ref IndexingTable table, ref T alloc, uint maxTableSize = 4096) @safe
-	if(isInputRange!I && (is(ElementType!I : immutable(ubyte)) || is(ElementType!I : immutable(char))))
-{
-	while(!src.empty) src.decode(dst, table, alloc, maxTableSize);
-}
-
-/// ENCODER
-unittest {
-	//// Following examples can be found in Appendix C of the HPACK RFC
-	import vibe.http.status;
-	import vibe.http.common;
-	import vibe.internal.utilallocator: RegionListAllocator;
-	import std.experimental.allocator;
-	import std.experimental.allocator.gc_allocator;
-
-	auto table = IndexingTable(4096);
-	scope alloc = new RegionListAllocator!(shared(GCAllocator), false)(1024, GCAllocator.instance);
-
-	/** 1. Literal header field w. indexing (raw)
-	  * custom-key: custom-header
-	  */
-	HTTP2HeaderTableField h1 = HTTP2HeaderTableField("custom-key", "custom-header");
-	auto e1 = appender!(ubyte[]);
-	auto dec1 = appender!(HTTP2HeaderTableField[]);
-
-	h1.encodeHPACK(e1, table, false);
-	decodeHPACK(cast(immutable(ubyte)[])e1.data, dec1, table, alloc);
-	assert(dec1.data.front == h1);
-
-	/** 1bis. Literal header field w. indexing (huffman encoded)
-	  * :authority: www.example.com
-	  */
-	table.insert(HTTP2HeaderTableField(":authority", "www.example.com"));
-	HTTP2HeaderTableField h1b = HTTP2HeaderTableField(":authority", "www.example.com");
-	h1b.neverIndex = false;
-	h1b.index = true;
-	auto e1b = appender!(ubyte[]);
-	auto dec1b = appender!(HTTP2HeaderTableField[]);
-
-	h1b.encodeHPACK(e1b, table, true);
-	decodeHPACK(cast(immutable(ubyte)[])e1b.data, dec1b, table, alloc);
-	assert(dec1b.data.front == h1b);
-
-	/** 2. Literal header field without indexing (raw)
-	  * :path: /sample/path
-	  */
-	auto h2 = HTTP2HeaderTableField(":path", "/sample/path");
-	h2.neverIndex = false;
-	h2.index = false;
-	// initialize with huffman=false (can be modified by e2.huffman)
-	auto e2 = appender!(ubyte[]);
-	auto dec2 = appender!(HTTP2HeaderTableField[]);
-
-	h2.encodeHPACK(e2, table, false);
-	decodeHPACK(cast(immutable(ubyte)[])e2.data, dec2, table, alloc);
-	assert(dec2.data.front == h2);
-
-	/** 3. Literal header field never indexed (raw)
-	  * password: secret
-	  */
-	HTTP2HeaderTableField h3 = HTTP2HeaderTableField("password", "secret");
-	h3.neverIndex = true;
-	h3.index = false;
-	auto e3 = appender!(ubyte[]);
-	auto dec3 = appender!(HTTP2HeaderTableField[]);
-
-	h3.encodeHPACK(e3, table, false);
-	decodeHPACK(cast(immutable(ubyte)[])e3.data, dec3, table, alloc);
-	assert(dec3.data.front == h3);
-
-	/** 4. Indexed header field (integer)
-	  * :method: GET
-	  */
-	HTTP2HeaderTableField h4 = HTTP2HeaderTableField(":method", HTTPMethod.GET);
-	auto e4 = appender!(ubyte[]);
-	auto dec4 = appender!(HTTP2HeaderTableField[]);
-
-	h4.encodeHPACK(e4, table);
-	decodeHPACK(cast(immutable(ubyte)[])e4.data, dec4, table, alloc);
-	assert(dec4.data.front == h4);
-
-	/** 5. Full request without huffman encoding
-	  * :method: GET
-      * :scheme: http
-      * :path: /
-      * :authority: www.example.com
-      * cache-control: no-cache
-	  */
-	HTTP2HeaderTableField[] block = [
-		HTTP2HeaderTableField(":method", HTTPMethod.GET),
-		HTTP2HeaderTableField(":scheme", "http"),
-		HTTP2HeaderTableField(":path", "/"),
-		HTTP2HeaderTableField(":authority", "www.example.com"),
-		HTTP2HeaderTableField("cache-control", "no-cache")
-	];
-
-	ubyte[14] expected = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65];
-	auto bres = appender!(ubyte[]);
-	block.encodeHPACK(bres, table, false);
-	assert(bres.data == expected);
-
-	/** 5. Full request with huffman encoding
-	  * :method: GET
-      * :scheme: http
-      * :path: /
-      * :authority: www.example.com
-      * cache-control: no-cache
-	  */
-	ubyte[12] eexpected = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf];
-	auto bbres = appender!(ubyte[]);
-	block.encodeHPACK(bbres, table, true);
-	assert(bbres.data == eexpected);
-}
-
-/// DECODER
-unittest {
-	//// Following examples can be found in Appendix C of the HPACK RFC
-
-	import vibe.internal.utilallocator: RegionListAllocator;
-	import std.experimental.allocator;
-	import std.experimental.allocator.gc_allocator;
-
-	auto table = IndexingTable(4096);
-	scope alloc = new RegionListAllocator!(shared(GCAllocator), false)(1024, GCAllocator.instance);
-
-	/** 1. Literal header field w. indexing (raw)
-	  * custom-key: custom-header
-	  */
-	immutable(ubyte)[] block = [0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79,
-		0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72];
-
-	//auto decoder = HeaderDecoder!(ubyte[])(block, table);
-	auto dec1 = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(dec1, table, alloc);
-	assert(dec1.data.front.name == "custom-key" && dec1.data.front.value == "custom-header");
-	// check entries to be inserted in the indexing table (dynamic)
-	assert(dec1.data.front.index);
-
-	/** 1bis. Literal header field w. indexing (huffman encoded)
-	  * :authority: www.example.com
-	  */
-	block = [0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
-	auto dec1b = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(dec1b, table, alloc);
-	assert(dec1b.data.front.name == ":authority" && dec1b.data.front.value == "www.example.com");
-	assert(dec1b.data.front.index);
-
-	/** 2. Literal header field without indexing (raw)
-	  * :path: /sample/path
-	  */
-	block = [0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68];
-	auto dec2 = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(dec2, table, alloc);
-	assert(dec2.data.front.name == ":path" && dec2.data.front.value == "/sample/path");
-
-
-	/** 3. Literal header field never indexed (raw)
-	  * password: secret
-	  */
-	block = [0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65,
-		  0x63, 0x72, 0x65, 0x74];
-	auto dec3 = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(dec3, table, alloc);
-	assert(dec3.data.front.name == "password" && dec3.data.front.value == "secret");
-	assert(dec3.data.front.neverIndex);
-
-
-	/** 4. Indexed header field (integer)
-	  * :method: GET
-	  */
-	import vibe.http.common;
-	block = [0x82];
-	auto dec4 = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(dec4, table, alloc);
-	assert(dec4.data.front.name == ":method" && dec4.data.front.value == HTTPMethod.GET);
-
-	/** 5. Full request without huffman encoding
-	  * :method: GET
-      * :scheme: http
-      * :path: /
-      * :authority: www.example.com
-      * cache-control: no-cache
-	  */
-	block = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65];
-	table.insert(HTTP2HeaderTableField(":authority", "www.example.com"));
-	auto decR1 = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(decR1, table, alloc);
-	HTTP2HeaderTableField[] expected = [
-		HTTP2HeaderTableField(":method", HTTPMethod.GET),
-		HTTP2HeaderTableField(":scheme", "http"),
-		HTTP2HeaderTableField(":path", "/"),
-		HTTP2HeaderTableField(":authority", "www.example.com"),
-		HTTP2HeaderTableField("cache-control", "no-cache")];
-
-	foreach(i,h; decR1.data.enumerate(0)) {
-		assert(h == expected[i]);
-	}
-
-	/** 5. Full request with huffman encoding
-	  * :method: GET
-	  * :scheme: http
-	  * :path: /
-	  * :authority: www.example.com
-	  * cache-control: no-cache
-	  */
-	block = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c,0xbf];
-	auto decR2 = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(decR2, table, alloc);
-
-	foreach(i,h; decR2.data.enumerate(0)) {
-		assert(h == expected[i]);
-	}
-
-	/** Cookie header
-	  * cookie: filter=downloading
-	*/
-	auto ckexp = HTTP2HeaderTableField("cookie", "filter=downloading");
-	block = [96, 141, 148, 212, 36, 182, 65, 33, 252, 85, 65, 199, 33, 170, 155];
-	auto ckdec = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(ckdec, table, alloc);
-	assert(ckdec.data.front == ckexp);
-}
-
-
-/// Mallocator
-unittest {
-	import vibe.internal.utilallocator: RegionListAllocator;
-	import std.experimental.allocator;
-	import std.experimental.allocator.mallocator;
-	import std.experimental.allocator.gc_allocator;
-	auto table = IndexingTable(4096);
-	/** 1bis. Literal header field w. indexing (huffman encoded)
-	  * :authority: www.example.com
-	  */
-	immutable(ubyte)[] block = [0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
-	scope alloc = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
-
-	auto dec1b = appender!(HTTP2HeaderTableField[]);
-	block.decodeHPACK(dec1b, table, alloc);
-	assert(dec1b.data.front.name == ":authority" && dec1b.data.front.value == "www.example.com");
-	assert(dec1b.data.front.index);
-}
diff --git a/source/vibe/http/internal/http2/hpack/huffman.d b/source/vibe/http/internal/http2/hpack/huffman.d
deleted file mode 100644
index 30b21aa..0000000
--- a/source/vibe/http/internal/http2/hpack/huffman.d
+++ /dev/null
@@ -1,2807 +0,0 @@
-module vibe.http.internal.http2.hpack.huffman;
-
-import vibe.http.internal.http2.hpack.exception;
-
-import std.range;
-
-/** Huffman encoding for HPACK header compression
-  * The huffman table specifications can be found at:
-  * Appendix B of RFC 7541: https://tools.ietf.org/html/rfc7541#appendix-B
-*/
-void encodeHuffman(I, O)(I source, ref O dst) @safe
-{
-	ulong bits;
-	int bitsLeft = 40;
-	size_t len = 0;
-
-	// compute length
-	foreach(c; source) {
-		auto e = HuffEncodeCodes[c];
-		len += e.length;
-	}
-
-	dst.put(cast(ubyte)((len/8 ^ 128) + 1));
-
-	foreach(c; source) {
-		auto e = HuffEncodeCodes[c];
-		bits |= (cast(ulong)e.code) << (bitsLeft - e.length);
-		bitsLeft -= e.length;
-		while(bitsLeft <= 32) {
-			dst.put(cast(ubyte)(bits >> 32));
-			bits <<= 8;
-			bitsLeft += 8;
-		}
-	}
-
-	if(bitsLeft != 40) {
-		bits |= ((cast(ulong)1) << bitsLeft) - 1;
-		dst.put(cast(ubyte)(bits >> 32));
-	}
-}
-
-@nogc unittest {
-	import vibe.internal.array : BatchBuffer;
-
-	string src = "www.example.com";
-	BatchBuffer!(ubyte, 13) bres;
-	bres.putN(13);
-	ubyte[13] expected = [0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
-	encodeHuffman(src, bres);
-	assert(bres.peekDst == expected);
-}
-
-/** Huffman decoding for HPACK header compression
-  * The huffman table specifications can be found at:
-  * Appendix B of RFC 7541: https://tools.ietf.org/html/rfc7541#appendix-B
-*/
-void decodeHuffman(I, O)(I source, ref O dst) @safe
-{
-	auto block = cast(immutable(ubyte)[])source;
-
-	char state = 0;
-	char eos = true; // termination flag
-
-	while(!block.empty) {
-		char ch = block[0];
-		block.popFront();
-		decodeSymbol(dst, state, ch >> 4, eos);
-		decodeSymbol(dst, state, ch & 0xf, eos);
-	}
-
-	enforceHPACK(eos, "Invalid encoded source");
-}
-
-private void decodeSymbol(O)(ref O decoded, ref char state, int pos, ref char eos)
-@safe
-{
-	enforceHPACK(state < 256 && pos < 16, "Invalid entry reference");
-	auto entry = HuffDecodeCodes[state][pos];
-	enforceHPACK(entry.next != state, "Invalid symbol");
-
-	if (entry.emit) { // if the symbol is terminal
-		auto sym = cast(immutable(char))entry.symbol;
-		decoded.put(sym);
-	}
-
-	state = entry.next;
-	eos = entry.ending;
-}
-
-unittest {
-	import std.array;
-	immutable ubyte[] test = [0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
-
-	auto dst = appender!string;
-	decodeHuffman(test, dst);
-	assert(dst.data == "www.example.com");
-
-	immutable char[] ctest = [0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
-	auto dst2 = appender!string;
-	decodeHuffman(ctest, dst2);
-	assert(dst2.data == "www.example.com");
-}
-
-unittest { // could be @nogc (exceptions aren't)
-	import vibe.internal.array : BatchBuffer;
-
-	immutable ubyte[12] src = [0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
-	BatchBuffer!(char, 15) bres;
-	bres.putN(15);
-	decodeHuffman(src, bres);
-	assert(bres.peekDst == "www.example.com");
-}
-
-private struct HuffCode {
-	char next;
-	char emit;
-	char symbol;
-	char ending;
-}
-
-private struct HuffEncCode {
-	uint code;
-	size_t length;
-}
-
-private static immutable HuffEncCode[256] HuffEncodeCodes = [
-    {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28},
-    {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28},
-    {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28},
-    {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28},
-    {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28},
-    {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28},
-    {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28},
-    {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28},
-    {0x00000014,  6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12},
-    {0x00001ff9, 13}, {0x00000015,  6}, {0x000000f8,  8}, {0x000007fa, 11},
-    {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9,  8}, {0x000007fb, 11},
-    {0x000000fa,  8}, {0x00000016,  6}, {0x00000017,  6}, {0x00000018,  6},
-    {0x00000000,  5}, {0x00000001,  5}, {0x00000002,  5}, {0x00000019,  6},
-    {0x0000001a,  6}, {0x0000001b,  6}, {0x0000001c,  6}, {0x0000001d,  6},
-    {0x0000001e,  6}, {0x0000001f,  6}, {0x0000005c,  7}, {0x000000fb,  8},
-    {0x00007ffc, 15}, {0x00000020,  6}, {0x00000ffb, 12}, {0x000003fc, 10},
-    {0x00001ffa, 13}, {0x00000021,  6}, {0x0000005d,  7}, {0x0000005e,  7},
-    {0x0000005f,  7}, {0x00000060,  7}, {0x00000061,  7}, {0x00000062,  7},
-    {0x00000063,  7}, {0x00000064,  7}, {0x00000065,  7}, {0x00000066,  7},
-    {0x00000067,  7}, {0x00000068,  7}, {0x00000069,  7}, {0x0000006a,  7},
-    {0x0000006b,  7}, {0x0000006c,  7}, {0x0000006d,  7}, {0x0000006e,  7},
-    {0x0000006f,  7}, {0x00000070,  7}, {0x00000071,  7}, {0x00000072,  7},
-    {0x000000fc,  8}, {0x00000073,  7}, {0x000000fd,  8}, {0x00001ffb, 13},
-    {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022,  6},
-    {0x00007ffd, 15}, {0x00000003,  5}, {0x00000023,  6}, {0x00000004,  5},
-    {0x00000024,  6}, {0x00000005,  5}, {0x00000025,  6}, {0x00000026,  6},
-    {0x00000027,  6}, {0x00000006,  5}, {0x00000074,  7}, {0x00000075,  7},
-    {0x00000028,  6}, {0x00000029,  6}, {0x0000002a,  6}, {0x00000007,  5},
-    {0x0000002b,  6}, {0x00000076,  7}, {0x0000002c,  6}, {0x00000008,  5},
-    {0x00000009,  5}, {0x0000002d,  6}, {0x00000077,  7}, {0x00000078,  7},
-    {0x00000079,  7}, {0x0000007a,  7}, {0x0000007b,  7}, {0x00007ffe, 15},
-    {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28},
-    {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20},
-    {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23},
-    {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23},
-    {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23},
-    {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23},
-    {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23},
-    {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23},
-    {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24},
-    {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22},
-    {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21},
-    {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24},
-    {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23},
-    {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21},
-    {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23},
-    {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22},
-    {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23},
-    {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19},
-    {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25},
-    {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27},
-    {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25},
-    {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27},
-    {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24},
-    {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26},
-    {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27},
-    {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21},
-    {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23},
-    {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25},
-    {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23},
-    {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26},
-    {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27},
-    {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27},
-    {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26}
-];
-
-private static immutable HuffCode[16][256] HuffDecodeCodes = [
-	/* 0 */
-	[
-// 		 next  emit  sym  ending   next  emit  sym  ending
-		{0x04, 0x00, 0x00, 0x00}, {0x05, 0x00, 0x00, 0x00},
-		{0x07, 0x00, 0x00, 0x00}, {0x08, 0x00, 0x00, 0x00},
-		{0x0b, 0x00, 0x00, 0x00}, {0x0c, 0x00, 0x00, 0x00},
-		{0x10, 0x00, 0x00, 0x00}, {0x13, 0x00, 0x00, 0x00},
-		{0x19, 0x00, 0x00, 0x00}, {0x1c, 0x00, 0x00, 0x00},
-		{0x20, 0x00, 0x00, 0x00}, {0x23, 0x00, 0x00, 0x00},
-		{0x2a, 0x00, 0x00, 0x00}, {0x31, 0x00, 0x00, 0x00},
-		{0x39, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0x30, 0x01}, {0x00, 0x01, 0x31, 0x01},
-		{0x00, 0x01, 0x32, 0x01}, {0x00, 0x01, 0x61, 0x01},
-		{0x00, 0x01, 0x63, 0x01}, {0x00, 0x01, 0x65, 0x01},
-		{0x00, 0x01, 0x69, 0x01}, {0x00, 0x01, 0x6f, 0x01},
-		{0x00, 0x01, 0x73, 0x01}, {0x00, 0x01, 0x74, 0x01},
-		{0x0d, 0x00, 0x00, 0x00}, {0x0e, 0x00, 0x00, 0x00},
-		{0x11, 0x00, 0x00, 0x00}, {0x12, 0x00, 0x00, 0x00},
-		{0x14, 0x00, 0x00, 0x00}, {0x15, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x01, 0x01, 0x30, 0x00}, {0x16, 0x01, 0x30, 0x01},
-		{0x01, 0x01, 0x31, 0x00}, {0x16, 0x01, 0x31, 0x01},
-		{0x01, 0x01, 0x32, 0x00}, {0x16, 0x01, 0x32, 0x01},
-		{0x01, 0x01, 0x61, 0x00}, {0x16, 0x01, 0x61, 0x01},
-		{0x01, 0x01, 0x63, 0x00}, {0x16, 0x01, 0x63, 0x01},
-		{0x01, 0x01, 0x65, 0x00}, {0x16, 0x01, 0x65, 0x01},
-		{0x01, 0x01, 0x69, 0x00}, {0x16, 0x01, 0x69, 0x01},
-		{0x01, 0x01, 0x6f, 0x00}, {0x16, 0x01, 0x6f, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x30, 0x00}, {0x09, 0x01, 0x30, 0x00},
-		{0x17, 0x01, 0x30, 0x00}, {0x28, 0x01, 0x30, 0x01},
-		{0x02, 0x01, 0x31, 0x00}, {0x09, 0x01, 0x31, 0x00},
-		{0x17, 0x01, 0x31, 0x00}, {0x28, 0x01, 0x31, 0x01},
-		{0x02, 0x01, 0x32, 0x00}, {0x09, 0x01, 0x32, 0x00},
-		{0x17, 0x01, 0x32, 0x00}, {0x28, 0x01, 0x32, 0x01},
-		{0x02, 0x01, 0x61, 0x00}, {0x09, 0x01, 0x61, 0x00},
-		{0x17, 0x01, 0x61, 0x00}, {0x28, 0x01, 0x61, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x30, 0x00}, {0x06, 0x01, 0x30, 0x00},
-		{0x0a, 0x01, 0x30, 0x00}, {0x0f, 0x01, 0x30, 0x00},
-		{0x18, 0x01, 0x30, 0x00}, {0x1f, 0x01, 0x30, 0x00},
-		{0x29, 0x01, 0x30, 0x00}, {0x38, 0x01, 0x30, 0x01},
-		{0x03, 0x01, 0x31, 0x00}, {0x06, 0x01, 0x31, 0x00},
-		{0x0a, 0x01, 0x31, 0x00}, {0x0f, 0x01, 0x31, 0x00},
-		{0x18, 0x01, 0x31, 0x00}, {0x1f, 0x01, 0x31, 0x00},
-		{0x29, 0x01, 0x31, 0x00}, {0x38, 0x01, 0x31, 0x01}
-	],
-	/* 5 */
-	[
-		{0x03, 0x01, 0x32, 0x00}, {0x06, 0x01, 0x32, 0x00},
-		{0x0a, 0x01, 0x32, 0x00}, {0x0f, 0x01, 0x32, 0x00},
-		{0x18, 0x01, 0x32, 0x00}, {0x1f, 0x01, 0x32, 0x00},
-		{0x29, 0x01, 0x32, 0x00}, {0x38, 0x01, 0x32, 0x01},
-		{0x03, 0x01, 0x61, 0x00}, {0x06, 0x01, 0x61, 0x00},
-		{0x0a, 0x01, 0x61, 0x00}, {0x0f, 0x01, 0x61, 0x00},
-		{0x18, 0x01, 0x61, 0x00}, {0x1f, 0x01, 0x61, 0x00},
-		{0x29, 0x01, 0x61, 0x00}, {0x38, 0x01, 0x61, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x63, 0x00}, {0x09, 0x01, 0x63, 0x00},
-		{0x17, 0x01, 0x63, 0x00}, {0x28, 0x01, 0x63, 0x01},
-		{0x02, 0x01, 0x65, 0x00}, {0x09, 0x01, 0x65, 0x00},
-		{0x17, 0x01, 0x65, 0x00}, {0x28, 0x01, 0x65, 0x01},
-		{0x02, 0x01, 0x69, 0x00}, {0x09, 0x01, 0x69, 0x00},
-		{0x17, 0x01, 0x69, 0x00}, {0x28, 0x01, 0x69, 0x01},
-		{0x02, 0x01, 0x6f, 0x00}, {0x09, 0x01, 0x6f, 0x00},
-		{0x17, 0x01, 0x6f, 0x00}, {0x28, 0x01, 0x6f, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x63, 0x00}, {0x06, 0x01, 0x63, 0x00},
-		{0x0a, 0x01, 0x63, 0x00}, {0x0f, 0x01, 0x63, 0x00},
-		{0x18, 0x01, 0x63, 0x00}, {0x1f, 0x01, 0x63, 0x00},
-		{0x29, 0x01, 0x63, 0x00}, {0x38, 0x01, 0x63, 0x01},
-		{0x03, 0x01, 0x65, 0x00}, {0x06, 0x01, 0x65, 0x00},
-		{0x0a, 0x01, 0x65, 0x00}, {0x0f, 0x01, 0x65, 0x00},
-		{0x18, 0x01, 0x65, 0x00}, {0x1f, 0x01, 0x65, 0x00},
-		{0x29, 0x01, 0x65, 0x00}, {0x38, 0x01, 0x65, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x69, 0x00}, {0x06, 0x01, 0x69, 0x00},
-		{0x0a, 0x01, 0x69, 0x00}, {0x0f, 0x01, 0x69, 0x00},
-		{0x18, 0x01, 0x69, 0x00}, {0x1f, 0x01, 0x69, 0x00},
-		{0x29, 0x01, 0x69, 0x00}, {0x38, 0x01, 0x69, 0x01},
-		{0x03, 0x01, 0x6f, 0x00}, {0x06, 0x01, 0x6f, 0x00},
-		{0x0a, 0x01, 0x6f, 0x00}, {0x0f, 0x01, 0x6f, 0x00},
-		{0x18, 0x01, 0x6f, 0x00}, {0x1f, 0x01, 0x6f, 0x00},
-		{0x29, 0x01, 0x6f, 0x00}, {0x38, 0x01, 0x6f, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x73, 0x00}, {0x16, 0x01, 0x73, 0x01},
-		{0x01, 0x01, 0x74, 0x00}, {0x16, 0x01, 0x74, 0x01},
-		{0x00, 0x01, 0x20, 0x01}, {0x00, 0x01, 0x25, 0x01},
-		{0x00, 0x01, 0x2d, 0x01}, {0x00, 0x01, 0x2e, 0x01},
-		{0x00, 0x01, 0x2f, 0x01}, {0x00, 0x01, 0x33, 0x01},
-		{0x00, 0x01, 0x34, 0x01}, {0x00, 0x01, 0x35, 0x01},
-		{0x00, 0x01, 0x36, 0x01}, {0x00, 0x01, 0x37, 0x01},
-		{0x00, 0x01, 0x38, 0x01}, {0x00, 0x01, 0x39, 0x01}
-	],
-	/* 10 */
-	[
-		{0x02, 0x01, 0x73, 0x00}, {0x09, 0x01, 0x73, 0x00},
-		{0x17, 0x01, 0x73, 0x00}, {0x28, 0x01, 0x73, 0x01},
-		{0x02, 0x01, 0x74, 0x00}, {0x09, 0x01, 0x74, 0x00},
-		{0x17, 0x01, 0x74, 0x00}, {0x28, 0x01, 0x74, 0x01},
-		{0x01, 0x01, 0x20, 0x00}, {0x16, 0x01, 0x20, 0x01},
-		{0x01, 0x01, 0x25, 0x00}, {0x16, 0x01, 0x25, 0x01},
-		{0x01, 0x01, 0x2d, 0x00}, {0x16, 0x01, 0x2d, 0x01},
-		{0x01, 0x01, 0x2e, 0x00}, {0x16, 0x01, 0x2e, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x73, 0x00}, {0x06, 0x01, 0x73, 0x00},
-		{0x0a, 0x01, 0x73, 0x00}, {0x0f, 0x01, 0x73, 0x00},
-		{0x18, 0x01, 0x73, 0x00}, {0x1f, 0x01, 0x73, 0x00},
-		{0x29, 0x01, 0x73, 0x00}, {0x38, 0x01, 0x73, 0x01},
-		{0x03, 0x01, 0x74, 0x00}, {0x06, 0x01, 0x74, 0x00},
-		{0x0a, 0x01, 0x74, 0x00}, {0x0f, 0x01, 0x74, 0x00},
-		{0x18, 0x01, 0x74, 0x00}, {0x1f, 0x01, 0x74, 0x00},
-		{0x29, 0x01, 0x74, 0x00}, {0x38, 0x01, 0x74, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x20, 0x00}, {0x09, 0x01, 0x20, 0x00},
-		{0x17, 0x01, 0x20, 0x00}, {0x28, 0x01, 0x20, 0x01},
-		{0x02, 0x01, 0x25, 0x00}, {0x09, 0x01, 0x25, 0x00},
-		{0x17, 0x01, 0x25, 0x00}, {0x28, 0x01, 0x25, 0x01},
-		{0x02, 0x01, 0x2d, 0x00}, {0x09, 0x01, 0x2d, 0x00},
-		{0x17, 0x01, 0x2d, 0x00}, {0x28, 0x01, 0x2d, 0x01},
-		{0x02, 0x01, 0x2e, 0x00}, {0x09, 0x01, 0x2e, 0x00},
-		{0x17, 0x01, 0x2e, 0x00}, {0x28, 0x01, 0x2e, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x20, 0x00}, {0x06, 0x01, 0x20, 0x00},
-		{0x0a, 0x01, 0x20, 0x00}, {0x0f, 0x01, 0x20, 0x00},
-		{0x18, 0x01, 0x20, 0x00}, {0x1f, 0x01, 0x20, 0x00},
-		{0x29, 0x01, 0x20, 0x00}, {0x38, 0x01, 0x20, 0x01},
-		{0x03, 0x01, 0x25, 0x00}, {0x06, 0x01, 0x25, 0x00},
-		{0x0a, 0x01, 0x25, 0x00}, {0x0f, 0x01, 0x25, 0x00},
-		{0x18, 0x01, 0x25, 0x00}, {0x1f, 0x01, 0x25, 0x00},
-		{0x29, 0x01, 0x25, 0x00}, {0x38, 0x01, 0x25, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x2d, 0x00}, {0x06, 0x01, 0x2d, 0x00},
-		{0x0a, 0x01, 0x2d, 0x00}, {0x0f, 0x01, 0x2d, 0x00},
-		{0x18, 0x01, 0x2d, 0x00}, {0x1f, 0x01, 0x2d, 0x00},
-		{0x29, 0x01, 0x2d, 0x00}, {0x38, 0x01, 0x2d, 0x01},
-		{0x03, 0x01, 0x2e, 0x00}, {0x06, 0x01, 0x2e, 0x00},
-		{0x0a, 0x01, 0x2e, 0x00}, {0x0f, 0x01, 0x2e, 0x00},
-		{0x18, 0x01, 0x2e, 0x00}, {0x1f, 0x01, 0x2e, 0x00},
-		{0x29, 0x01, 0x2e, 0x00}, {0x38, 0x01, 0x2e, 0x01}
-	],
-	/* 15 */
-	[
-		{0x01, 0x01, 0x2f, 0x00}, {0x16, 0x01, 0x2f, 0x01},
-		{0x01, 0x01, 0x33, 0x00}, {0x16, 0x01, 0x33, 0x01},
-		{0x01, 0x01, 0x34, 0x00}, {0x16, 0x01, 0x34, 0x01},
-		{0x01, 0x01, 0x35, 0x00}, {0x16, 0x01, 0x35, 0x01},
-		{0x01, 0x01, 0x36, 0x00}, {0x16, 0x01, 0x36, 0x01},
-		{0x01, 0x01, 0x37, 0x00}, {0x16, 0x01, 0x37, 0x01},
-		{0x01, 0x01, 0x38, 0x00}, {0x16, 0x01, 0x38, 0x01},
-		{0x01, 0x01, 0x39, 0x00}, {0x16, 0x01, 0x39, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x2f, 0x00}, {0x09, 0x01, 0x2f, 0x00},
-		{0x17, 0x01, 0x2f, 0x00}, {0x28, 0x01, 0x2f, 0x01},
-		{0x02, 0x01, 0x33, 0x00}, {0x09, 0x01, 0x33, 0x00},
-		{0x17, 0x01, 0x33, 0x00}, {0x28, 0x01, 0x33, 0x01},
-		{0x02, 0x01, 0x34, 0x00}, {0x09, 0x01, 0x34, 0x00},
-		{0x17, 0x01, 0x34, 0x00}, {0x28, 0x01, 0x34, 0x01},
-		{0x02, 0x01, 0x35, 0x00}, {0x09, 0x01, 0x35, 0x00},
-		{0x17, 0x01, 0x35, 0x00}, {0x28, 0x01, 0x35, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x2f, 0x00}, {0x06, 0x01, 0x2f, 0x00},
-		{0x0a, 0x01, 0x2f, 0x00}, {0x0f, 0x01, 0x2f, 0x00},
-		{0x18, 0x01, 0x2f, 0x00}, {0x1f, 0x01, 0x2f, 0x00},
-		{0x29, 0x01, 0x2f, 0x00}, {0x38, 0x01, 0x2f, 0x01},
-		{0x03, 0x01, 0x33, 0x00}, {0x06, 0x01, 0x33, 0x00},
-		{0x0a, 0x01, 0x33, 0x00}, {0x0f, 0x01, 0x33, 0x00},
-		{0x18, 0x01, 0x33, 0x00}, {0x1f, 0x01, 0x33, 0x00},
-		{0x29, 0x01, 0x33, 0x00}, {0x38, 0x01, 0x33, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x34, 0x00}, {0x06, 0x01, 0x34, 0x00},
-		{0x0a, 0x01, 0x34, 0x00}, {0x0f, 0x01, 0x34, 0x00},
-		{0x18, 0x01, 0x34, 0x00}, {0x1f, 0x01, 0x34, 0x00},
-		{0x29, 0x01, 0x34, 0x00}, {0x38, 0x01, 0x34, 0x01},
-		{0x03, 0x01, 0x35, 0x00}, {0x06, 0x01, 0x35, 0x00},
-		{0x0a, 0x01, 0x35, 0x00}, {0x0f, 0x01, 0x35, 0x00},
-		{0x18, 0x01, 0x35, 0x00}, {0x1f, 0x01, 0x35, 0x00},
-		{0x29, 0x01, 0x35, 0x00}, {0x38, 0x01, 0x35, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x36, 0x00}, {0x09, 0x01, 0x36, 0x00},
-		{0x17, 0x01, 0x36, 0x00}, {0x28, 0x01, 0x36, 0x01},
-		{0x02, 0x01, 0x37, 0x00}, {0x09, 0x01, 0x37, 0x00},
-		{0x17, 0x01, 0x37, 0x00}, {0x28, 0x01, 0x37, 0x01},
-		{0x02, 0x01, 0x38, 0x00}, {0x09, 0x01, 0x38, 0x00},
-		{0x17, 0x01, 0x38, 0x00}, {0x28, 0x01, 0x38, 0x01},
-		{0x02, 0x01, 0x39, 0x00}, {0x09, 0x01, 0x39, 0x00},
-		{0x17, 0x01, 0x39, 0x00}, {0x28, 0x01, 0x39, 0x01}
-	],
-	/* 20 */
-	[
-		{0x03, 0x01, 0x36, 0x00}, {0x06, 0x01, 0x36, 0x00},
-		{0x0a, 0x01, 0x36, 0x00}, {0x0f, 0x01, 0x36, 0x00},
-		{0x18, 0x01, 0x36, 0x00}, {0x1f, 0x01, 0x36, 0x00},
-		{0x29, 0x01, 0x36, 0x00}, {0x38, 0x01, 0x36, 0x01},
-		{0x03, 0x01, 0x37, 0x00}, {0x06, 0x01, 0x37, 0x00},
-		{0x0a, 0x01, 0x37, 0x00}, {0x0f, 0x01, 0x37, 0x00},
-		{0x18, 0x01, 0x37, 0x00}, {0x1f, 0x01, 0x37, 0x00},
-		{0x29, 0x01, 0x37, 0x00}, {0x38, 0x01, 0x37, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x38, 0x00}, {0x06, 0x01, 0x38, 0x00},
-		{0x0a, 0x01, 0x38, 0x00}, {0x0f, 0x01, 0x38, 0x00},
-		{0x18, 0x01, 0x38, 0x00}, {0x1f, 0x01, 0x38, 0x00},
-		{0x29, 0x01, 0x38, 0x00}, {0x38, 0x01, 0x38, 0x01},
-		{0x03, 0x01, 0x39, 0x00}, {0x06, 0x01, 0x39, 0x00},
-		{0x0a, 0x01, 0x39, 0x00}, {0x0f, 0x01, 0x39, 0x00},
-		{0x18, 0x01, 0x39, 0x00}, {0x1f, 0x01, 0x39, 0x00},
-		{0x29, 0x01, 0x39, 0x00}, {0x38, 0x01, 0x39, 0x01}
-	],
-	[
-		{0x1a, 0x00, 0x00, 0x00}, {0x1b, 0x00, 0x00, 0x00},
-		{0x1d, 0x00, 0x00, 0x00}, {0x1e, 0x00, 0x00, 0x00},
-		{0x21, 0x00, 0x00, 0x00}, {0x22, 0x00, 0x00, 0x00},
-		{0x24, 0x00, 0x00, 0x00}, {0x25, 0x00, 0x00, 0x00},
-		{0x2b, 0x00, 0x00, 0x00}, {0x2e, 0x00, 0x00, 0x00},
-		{0x32, 0x00, 0x00, 0x00}, {0x35, 0x00, 0x00, 0x00},
-		{0x3a, 0x00, 0x00, 0x00}, {0x3d, 0x00, 0x00, 0x00},
-		{0x41, 0x00, 0x00, 0x00}, {0x44, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0x3d, 0x01}, {0x00, 0x01, 0x41, 0x01},
-		{0x00, 0x01, 0x5f, 0x01}, {0x00, 0x01, 0x62, 0x01},
-		{0x00, 0x01, 0x64, 0x01}, {0x00, 0x01, 0x66, 0x01},
-		{0x00, 0x01, 0x67, 0x01}, {0x00, 0x01, 0x68, 0x01},
-		{0x00, 0x01, 0x6c, 0x01}, {0x00, 0x01, 0x6d, 0x01},
-		{0x00, 0x01, 0x6e, 0x01}, {0x00, 0x01, 0x70, 0x01},
-		{0x00, 0x01, 0x72, 0x01}, {0x00, 0x01, 0x75, 0x01},
-		{0x26, 0x00, 0x00, 0x00}, {0x27, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x01, 0x01, 0x3d, 0x00}, {0x16, 0x01, 0x3d, 0x01},
-		{0x01, 0x01, 0x41, 0x00}, {0x16, 0x01, 0x41, 0x01},
-		{0x01, 0x01, 0x5f, 0x00}, {0x16, 0x01, 0x5f, 0x01},
-		{0x01, 0x01, 0x62, 0x00}, {0x16, 0x01, 0x62, 0x01},
-		{0x01, 0x01, 0x64, 0x00}, {0x16, 0x01, 0x64, 0x01},
-		{0x01, 0x01, 0x66, 0x00}, {0x16, 0x01, 0x66, 0x01},
-		{0x01, 0x01, 0x67, 0x00}, {0x16, 0x01, 0x67, 0x01},
-		{0x01, 0x01, 0x68, 0x00}, {0x16, 0x01, 0x68, 0x01}
-	],
-	/* 25 */
-	[
-		{0x02, 0x01, 0x3d, 0x00}, {0x09, 0x01, 0x3d, 0x00},
-		{0x17, 0x01, 0x3d, 0x00}, {0x28, 0x01, 0x3d, 0x01},
-		{0x02, 0x01, 0x41, 0x00}, {0x09, 0x01, 0x41, 0x00},
-		{0x17, 0x01, 0x41, 0x00}, {0x28, 0x01, 0x41, 0x01},
-		{0x02, 0x01, 0x5f, 0x00}, {0x09, 0x01, 0x5f, 0x00},
-		{0x17, 0x01, 0x5f, 0x00}, {0x28, 0x01, 0x5f, 0x01},
-		{0x02, 0x01, 0x62, 0x00}, {0x09, 0x01, 0x62, 0x00},
-		{0x17, 0x01, 0x62, 0x00}, {0x28, 0x01, 0x62, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x3d, 0x00}, {0x06, 0x01, 0x3d, 0x00},
-		{0x0a, 0x01, 0x3d, 0x00}, {0x0f, 0x01, 0x3d, 0x00},
-		{0x18, 0x01, 0x3d, 0x00}, {0x1f, 0x01, 0x3d, 0x00},
-		{0x29, 0x01, 0x3d, 0x00}, {0x38, 0x01, 0x3d, 0x01},
-		{0x03, 0x01, 0x41, 0x00}, {0x06, 0x01, 0x41, 0x00},
-		{0x0a, 0x01, 0x41, 0x00}, {0x0f, 0x01, 0x41, 0x00},
-		{0x18, 0x01, 0x41, 0x00}, {0x1f, 0x01, 0x41, 0x00},
-		{0x29, 0x01, 0x41, 0x00}, {0x38, 0x01, 0x41, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x5f, 0x00}, {0x06, 0x01, 0x5f, 0x00},
-		{0x0a, 0x01, 0x5f, 0x00}, {0x0f, 0x01, 0x5f, 0x00},
-		{0x18, 0x01, 0x5f, 0x00}, {0x1f, 0x01, 0x5f, 0x00},
-		{0x29, 0x01, 0x5f, 0x00}, {0x38, 0x01, 0x5f, 0x01},
-		{0x03, 0x01, 0x62, 0x00}, {0x06, 0x01, 0x62, 0x00},
-		{0x0a, 0x01, 0x62, 0x00}, {0x0f, 0x01, 0x62, 0x00},
-		{0x18, 0x01, 0x62, 0x00}, {0x1f, 0x01, 0x62, 0x00},
-		{0x29, 0x01, 0x62, 0x00}, {0x38, 0x01, 0x62, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x64, 0x00}, {0x09, 0x01, 0x64, 0x00},
-		{0x17, 0x01, 0x64, 0x00}, {0x28, 0x01, 0x64, 0x01},
-		{0x02, 0x01, 0x66, 0x00}, {0x09, 0x01, 0x66, 0x00},
-		{0x17, 0x01, 0x66, 0x00}, {0x28, 0x01, 0x66, 0x01},
-		{0x02, 0x01, 0x67, 0x00}, {0x09, 0x01, 0x67, 0x00},
-		{0x17, 0x01, 0x67, 0x00}, {0x28, 0x01, 0x67, 0x01},
-		{0x02, 0x01, 0x68, 0x00}, {0x09, 0x01, 0x68, 0x00},
-		{0x17, 0x01, 0x68, 0x00}, {0x28, 0x01, 0x68, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x64, 0x00}, {0x06, 0x01, 0x64, 0x00},
-		{0x0a, 0x01, 0x64, 0x00}, {0x0f, 0x01, 0x64, 0x00},
-		{0x18, 0x01, 0x64, 0x00}, {0x1f, 0x01, 0x64, 0x00},
-		{0x29, 0x01, 0x64, 0x00}, {0x38, 0x01, 0x64, 0x01},
-		{0x03, 0x01, 0x66, 0x00}, {0x06, 0x01, 0x66, 0x00},
-		{0x0a, 0x01, 0x66, 0x00}, {0x0f, 0x01, 0x66, 0x00},
-		{0x18, 0x01, 0x66, 0x00}, {0x1f, 0x01, 0x66, 0x00},
-		{0x29, 0x01, 0x66, 0x00}, {0x38, 0x01, 0x66, 0x01}
-	],
-	/* 30 */
-	[
-		{0x03, 0x01, 0x67, 0x00}, {0x06, 0x01, 0x67, 0x00},
-		{0x0a, 0x01, 0x67, 0x00}, {0x0f, 0x01, 0x67, 0x00},
-		{0x18, 0x01, 0x67, 0x00}, {0x1f, 0x01, 0x67, 0x00},
-		{0x29, 0x01, 0x67, 0x00}, {0x38, 0x01, 0x67, 0x01},
-		{0x03, 0x01, 0x68, 0x00}, {0x06, 0x01, 0x68, 0x00},
-		{0x0a, 0x01, 0x68, 0x00}, {0x0f, 0x01, 0x68, 0x00},
-		{0x18, 0x01, 0x68, 0x00}, {0x1f, 0x01, 0x68, 0x00},
-		{0x29, 0x01, 0x68, 0x00}, {0x38, 0x01, 0x68, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x6c, 0x00}, {0x16, 0x01, 0x6c, 0x01},
-		{0x01, 0x01, 0x6d, 0x00}, {0x16, 0x01, 0x6d, 0x01},
-		{0x01, 0x01, 0x6e, 0x00}, {0x16, 0x01, 0x6e, 0x01},
-		{0x01, 0x01, 0x70, 0x00}, {0x16, 0x01, 0x70, 0x01},
-		{0x01, 0x01, 0x72, 0x00}, {0x16, 0x01, 0x72, 0x01},
-		{0x01, 0x01, 0x75, 0x00}, {0x16, 0x01, 0x75, 0x01},
-		{0x00, 0x01, 0x3a, 0x01}, {0x00, 0x01, 0x42, 0x01},
-		{0x00, 0x01, 0x43, 0x01}, {0x00, 0x01, 0x44, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x6c, 0x00}, {0x09, 0x01, 0x6c, 0x00},
-		{0x17, 0x01, 0x6c, 0x00}, {0x28, 0x01, 0x6c, 0x01},
-		{0x02, 0x01, 0x6d, 0x00}, {0x09, 0x01, 0x6d, 0x00},
-		{0x17, 0x01, 0x6d, 0x00}, {0x28, 0x01, 0x6d, 0x01},
-		{0x02, 0x01, 0x6e, 0x00}, {0x09, 0x01, 0x6e, 0x00},
-		{0x17, 0x01, 0x6e, 0x00}, {0x28, 0x01, 0x6e, 0x01},
-		{0x02, 0x01, 0x70, 0x00}, {0x09, 0x01, 0x70, 0x00},
-		{0x17, 0x01, 0x70, 0x00}, {0x28, 0x01, 0x70, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x6c, 0x00}, {0x06, 0x01, 0x6c, 0x00},
-		{0x0a, 0x01, 0x6c, 0x00}, {0x0f, 0x01, 0x6c, 0x00},
-		{0x18, 0x01, 0x6c, 0x00}, {0x1f, 0x01, 0x6c, 0x00},
-		{0x29, 0x01, 0x6c, 0x00}, {0x38, 0x01, 0x6c, 0x01},
-		{0x03, 0x01, 0x6d, 0x00}, {0x06, 0x01, 0x6d, 0x00},
-		{0x0a, 0x01, 0x6d, 0x00}, {0x0f, 0x01, 0x6d, 0x00},
-		{0x18, 0x01, 0x6d, 0x00}, {0x1f, 0x01, 0x6d, 0x00},
-		{0x29, 0x01, 0x6d, 0x00}, {0x38, 0x01, 0x6d, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x6e, 0x00}, {0x06, 0x01, 0x6e, 0x00},
-		{0x0a, 0x01, 0x6e, 0x00}, {0x0f, 0x01, 0x6e, 0x00},
-		{0x18, 0x01, 0x6e, 0x00}, {0x1f, 0x01, 0x6e, 0x00},
-		{0x29, 0x01, 0x6e, 0x00}, {0x38, 0x01, 0x6e, 0x01},
-		{0x03, 0x01, 0x70, 0x00}, {0x06, 0x01, 0x70, 0x00},
-		{0x0a, 0x01, 0x70, 0x00}, {0x0f, 0x01, 0x70, 0x00},
-		{0x18, 0x01, 0x70, 0x00}, {0x1f, 0x01, 0x70, 0x00},
-		{0x29, 0x01, 0x70, 0x00}, {0x38, 0x01, 0x70, 0x01}
-	],
-	/* 35 */
-	[
-		{0x02, 0x01, 0x72, 0x00}, {0x09, 0x01, 0x72, 0x00},
-		{0x17, 0x01, 0x72, 0x00}, {0x28, 0x01, 0x72, 0x01},
-		{0x02, 0x01, 0x75, 0x00}, {0x09, 0x01, 0x75, 0x00},
-		{0x17, 0x01, 0x75, 0x00}, {0x28, 0x01, 0x75, 0x01},
-		{0x01, 0x01, 0x3a, 0x00}, {0x16, 0x01, 0x3a, 0x01},
-		{0x01, 0x01, 0x42, 0x00}, {0x16, 0x01, 0x42, 0x01},
-		{0x01, 0x01, 0x43, 0x00}, {0x16, 0x01, 0x43, 0x01},
-		{0x01, 0x01, 0x44, 0x00}, {0x16, 0x01, 0x44, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x72, 0x00}, {0x06, 0x01, 0x72, 0x00},
-		{0x0a, 0x01, 0x72, 0x00}, {0x0f, 0x01, 0x72, 0x00},
-		{0x18, 0x01, 0x72, 0x00}, {0x1f, 0x01, 0x72, 0x00},
-		{0x29, 0x01, 0x72, 0x00}, {0x38, 0x01, 0x72, 0x01},
-		{0x03, 0x01, 0x75, 0x00}, {0x06, 0x01, 0x75, 0x00},
-		{0x0a, 0x01, 0x75, 0x00}, {0x0f, 0x01, 0x75, 0x00},
-		{0x18, 0x01, 0x75, 0x00}, {0x1f, 0x01, 0x75, 0x00},
-		{0x29, 0x01, 0x75, 0x00}, {0x38, 0x01, 0x75, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x3a, 0x00}, {0x09, 0x01, 0x3a, 0x00},
-		{0x17, 0x01, 0x3a, 0x00}, {0x28, 0x01, 0x3a, 0x01},
-		{0x02, 0x01, 0x42, 0x00}, {0x09, 0x01, 0x42, 0x00},
-		{0x17, 0x01, 0x42, 0x00}, {0x28, 0x01, 0x42, 0x01},
-		{0x02, 0x01, 0x43, 0x00}, {0x09, 0x01, 0x43, 0x00},
-		{0x17, 0x01, 0x43, 0x00}, {0x28, 0x01, 0x43, 0x01},
-		{0x02, 0x01, 0x44, 0x00}, {0x09, 0x01, 0x44, 0x00},
-		{0x17, 0x01, 0x44, 0x00}, {0x28, 0x01, 0x44, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x3a, 0x00}, {0x06, 0x01, 0x3a, 0x00},
-		{0x0a, 0x01, 0x3a, 0x00}, {0x0f, 0x01, 0x3a, 0x00},
-		{0x18, 0x01, 0x3a, 0x00}, {0x1f, 0x01, 0x3a, 0x00},
-		{0x29, 0x01, 0x3a, 0x00}, {0x38, 0x01, 0x3a, 0x01},
-		{0x03, 0x01, 0x42, 0x00}, {0x06, 0x01, 0x42, 0x00},
-		{0x0a, 0x01, 0x42, 0x00}, {0x0f, 0x01, 0x42, 0x00},
-		{0x18, 0x01, 0x42, 0x00}, {0x1f, 0x01, 0x42, 0x00},
-		{0x29, 0x01, 0x42, 0x00}, {0x38, 0x01, 0x42, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x43, 0x00}, {0x06, 0x01, 0x43, 0x00},
-		{0x0a, 0x01, 0x43, 0x00}, {0x0f, 0x01, 0x43, 0x00},
-		{0x18, 0x01, 0x43, 0x00}, {0x1f, 0x01, 0x43, 0x00},
-		{0x29, 0x01, 0x43, 0x00}, {0x38, 0x01, 0x43, 0x01},
-		{0x03, 0x01, 0x44, 0x00}, {0x06, 0x01, 0x44, 0x00},
-		{0x0a, 0x01, 0x44, 0x00}, {0x0f, 0x01, 0x44, 0x00},
-		{0x18, 0x01, 0x44, 0x00}, {0x1f, 0x01, 0x44, 0x00},
-		{0x29, 0x01, 0x44, 0x00}, {0x38, 0x01, 0x44, 0x01}
-	],
-	/* 40 */
-	[
-		{0x2c, 0x00, 0x00, 0x00}, {0x2d, 0x00, 0x00, 0x00},
-		{0x2f, 0x00, 0x00, 0x00}, {0x30, 0x00, 0x00, 0x00},
-		{0x33, 0x00, 0x00, 0x00}, {0x34, 0x00, 0x00, 0x00},
-		{0x36, 0x00, 0x00, 0x00}, {0x37, 0x00, 0x00, 0x00},
-		{0x3b, 0x00, 0x00, 0x00}, {0x3c, 0x00, 0x00, 0x00},
-		{0x3e, 0x00, 0x00, 0x00}, {0x3f, 0x00, 0x00, 0x00},
-		{0x42, 0x00, 0x00, 0x00}, {0x43, 0x00, 0x00, 0x00},
-		{0x45, 0x00, 0x00, 0x00}, {0x48, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0x45, 0x01}, {0x00, 0x01, 0x46, 0x01},
-		{0x00, 0x01, 0x47, 0x01}, {0x00, 0x01, 0x48, 0x01},
-		{0x00, 0x01, 0x49, 0x01}, {0x00, 0x01, 0x4a, 0x01},
-		{0x00, 0x01, 0x4b, 0x01}, {0x00, 0x01, 0x4c, 0x01},
-		{0x00, 0x01, 0x4d, 0x01}, {0x00, 0x01, 0x4e, 0x01},
-		{0x00, 0x01, 0x4f, 0x01}, {0x00, 0x01, 0x50, 0x01},
-		{0x00, 0x01, 0x51, 0x01}, {0x00, 0x01, 0x52, 0x01},
-		{0x00, 0x01, 0x53, 0x01}, {0x00, 0x01, 0x54, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x45, 0x00}, {0x16, 0x01, 0x45, 0x01},
-		{0x01, 0x01, 0x46, 0x00}, {0x16, 0x01, 0x46, 0x01},
-		{0x01, 0x01, 0x47, 0x00}, {0x16, 0x01, 0x47, 0x01},
-		{0x01, 0x01, 0x48, 0x00}, {0x16, 0x01, 0x48, 0x01},
-		{0x01, 0x01, 0x49, 0x00}, {0x16, 0x01, 0x49, 0x01},
-		{0x01, 0x01, 0x4a, 0x00}, {0x16, 0x01, 0x4a, 0x01},
-		{0x01, 0x01, 0x4b, 0x00}, {0x16, 0x01, 0x4b, 0x01},
-		{0x01, 0x01, 0x4c, 0x00}, {0x16, 0x01, 0x4c, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x45, 0x00}, {0x09, 0x01, 0x45, 0x00},
-		{0x17, 0x01, 0x45, 0x00}, {0x28, 0x01, 0x45, 0x01},
-		{0x02, 0x01, 0x46, 0x00}, {0x09, 0x01, 0x46, 0x00},
-		{0x17, 0x01, 0x46, 0x00}, {0x28, 0x01, 0x46, 0x01},
-		{0x02, 0x01, 0x47, 0x00}, {0x09, 0x01, 0x47, 0x00},
-		{0x17, 0x01, 0x47, 0x00}, {0x28, 0x01, 0x47, 0x01},
-		{0x02, 0x01, 0x48, 0x00}, {0x09, 0x01, 0x48, 0x00},
-		{0x17, 0x01, 0x48, 0x00}, {0x28, 0x01, 0x48, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x45, 0x00}, {0x06, 0x01, 0x45, 0x00},
-		{0x0a, 0x01, 0x45, 0x00}, {0x0f, 0x01, 0x45, 0x00},
-		{0x18, 0x01, 0x45, 0x00}, {0x1f, 0x01, 0x45, 0x00},
-		{0x29, 0x01, 0x45, 0x00}, {0x38, 0x01, 0x45, 0x01},
-		{0x03, 0x01, 0x46, 0x00}, {0x06, 0x01, 0x46, 0x00},
-		{0x0a, 0x01, 0x46, 0x00}, {0x0f, 0x01, 0x46, 0x00},
-		{0x18, 0x01, 0x46, 0x00}, {0x1f, 0x01, 0x46, 0x00},
-		{0x29, 0x01, 0x46, 0x00}, {0x38, 0x01, 0x46, 0x01}
-	],
-	/* 45 */
-	[
-		{0x03, 0x01, 0x47, 0x00}, {0x06, 0x01, 0x47, 0x00},
-		{0x0a, 0x01, 0x47, 0x00}, {0x0f, 0x01, 0x47, 0x00},
-		{0x18, 0x01, 0x47, 0x00}, {0x1f, 0x01, 0x47, 0x00},
-		{0x29, 0x01, 0x47, 0x00}, {0x38, 0x01, 0x47, 0x01},
-		{0x03, 0x01, 0x48, 0x00}, {0x06, 0x01, 0x48, 0x00},
-		{0x0a, 0x01, 0x48, 0x00}, {0x0f, 0x01, 0x48, 0x00},
-		{0x18, 0x01, 0x48, 0x00}, {0x1f, 0x01, 0x48, 0x00},
-		{0x29, 0x01, 0x48, 0x00}, {0x38, 0x01, 0x48, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x49, 0x00}, {0x09, 0x01, 0x49, 0x00},
-		{0x17, 0x01, 0x49, 0x00}, {0x28, 0x01, 0x49, 0x01},
-		{0x02, 0x01, 0x4a, 0x00}, {0x09, 0x01, 0x4a, 0x00},
-		{0x17, 0x01, 0x4a, 0x00}, {0x28, 0x01, 0x4a, 0x01},
-		{0x02, 0x01, 0x4b, 0x00}, {0x09, 0x01, 0x4b, 0x00},
-		{0x17, 0x01, 0x4b, 0x00}, {0x28, 0x01, 0x4b, 0x01},
-		{0x02, 0x01, 0x4c, 0x00}, {0x09, 0x01, 0x4c, 0x00},
-		{0x17, 0x01, 0x4c, 0x00}, {0x28, 0x01, 0x4c, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x49, 0x00}, {0x06, 0x01, 0x49, 0x00},
-		{0x0a, 0x01, 0x49, 0x00}, {0x0f, 0x01, 0x49, 0x00},
-		{0x18, 0x01, 0x49, 0x00}, {0x1f, 0x01, 0x49, 0x00},
-		{0x29, 0x01, 0x49, 0x00}, {0x38, 0x01, 0x49, 0x01},
-		{0x03, 0x01, 0x4a, 0x00}, {0x06, 0x01, 0x4a, 0x00},
-		{0x0a, 0x01, 0x4a, 0x00}, {0x0f, 0x01, 0x4a, 0x00},
-		{0x18, 0x01, 0x4a, 0x00}, {0x1f, 0x01, 0x4a, 0x00},
-		{0x29, 0x01, 0x4a, 0x00}, {0x38, 0x01, 0x4a, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x4b, 0x00}, {0x06, 0x01, 0x4b, 0x00},
-		{0x0a, 0x01, 0x4b, 0x00}, {0x0f, 0x01, 0x4b, 0x00},
-		{0x18, 0x01, 0x4b, 0x00}, {0x1f, 0x01, 0x4b, 0x00},
-		{0x29, 0x01, 0x4b, 0x00}, {0x38, 0x01, 0x4b, 0x01},
-		{0x03, 0x01, 0x4c, 0x00}, {0x06, 0x01, 0x4c, 0x00},
-		{0x0a, 0x01, 0x4c, 0x00}, {0x0f, 0x01, 0x4c, 0x00},
-		{0x18, 0x01, 0x4c, 0x00}, {0x1f, 0x01, 0x4c, 0x00},
-		{0x29, 0x01, 0x4c, 0x00}, {0x38, 0x01, 0x4c, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x4d, 0x00}, {0x16, 0x01, 0x4d, 0x01},
-		{0x01, 0x01, 0x4e, 0x00}, {0x16, 0x01, 0x4e, 0x01},
-		{0x01, 0x01, 0x4f, 0x00}, {0x16, 0x01, 0x4f, 0x01},
-		{0x01, 0x01, 0x50, 0x00}, {0x16, 0x01, 0x50, 0x01},
-		{0x01, 0x01, 0x51, 0x00}, {0x16, 0x01, 0x51, 0x01},
-		{0x01, 0x01, 0x52, 0x00}, {0x16, 0x01, 0x52, 0x01},
-		{0x01, 0x01, 0x53, 0x00}, {0x16, 0x01, 0x53, 0x01},
-		{0x01, 0x01, 0x54, 0x00}, {0x16, 0x01, 0x54, 0x01}
-	],
-	/* 50 */
-	[
-		{0x02, 0x01, 0x4d, 0x00}, {0x09, 0x01, 0x4d, 0x00},
-		{0x17, 0x01, 0x4d, 0x00}, {0x28, 0x01, 0x4d, 0x01},
-		{0x02, 0x01, 0x4e, 0x00}, {0x09, 0x01, 0x4e, 0x00},
-		{0x17, 0x01, 0x4e, 0x00}, {0x28, 0x01, 0x4e, 0x01},
-		{0x02, 0x01, 0x4f, 0x00}, {0x09, 0x01, 0x4f, 0x00},
-		{0x17, 0x01, 0x4f, 0x00}, {0x28, 0x01, 0x4f, 0x01},
-		{0x02, 0x01, 0x50, 0x00}, {0x09, 0x01, 0x50, 0x00},
-		{0x17, 0x01, 0x50, 0x00}, {0x28, 0x01, 0x50, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x4d, 0x00}, {0x06, 0x01, 0x4d, 0x00},
-		{0x0a, 0x01, 0x4d, 0x00}, {0x0f, 0x01, 0x4d, 0x00},
-		{0x18, 0x01, 0x4d, 0x00}, {0x1f, 0x01, 0x4d, 0x00},
-		{0x29, 0x01, 0x4d, 0x00}, {0x38, 0x01, 0x4d, 0x01},
-		{0x03, 0x01, 0x4e, 0x00}, {0x06, 0x01, 0x4e, 0x00},
-		{0x0a, 0x01, 0x4e, 0x00}, {0x0f, 0x01, 0x4e, 0x00},
-		{0x18, 0x01, 0x4e, 0x00}, {0x1f, 0x01, 0x4e, 0x00},
-		{0x29, 0x01, 0x4e, 0x00}, {0x38, 0x01, 0x4e, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x4f, 0x00}, {0x06, 0x01, 0x4f, 0x00},
-		{0x0a, 0x01, 0x4f, 0x00}, {0x0f, 0x01, 0x4f, 0x00},
-		{0x18, 0x01, 0x4f, 0x00}, {0x1f, 0x01, 0x4f, 0x00},
-		{0x29, 0x01, 0x4f, 0x00}, {0x38, 0x01, 0x4f, 0x01},
-		{0x03, 0x01, 0x50, 0x00}, {0x06, 0x01, 0x50, 0x00},
-		{0x0a, 0x01, 0x50, 0x00}, {0x0f, 0x01, 0x50, 0x00},
-		{0x18, 0x01, 0x50, 0x00}, {0x1f, 0x01, 0x50, 0x00},
-		{0x29, 0x01, 0x50, 0x00}, {0x38, 0x01, 0x50, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x51, 0x00}, {0x09, 0x01, 0x51, 0x00},
-		{0x17, 0x01, 0x51, 0x00}, {0x28, 0x01, 0x51, 0x01},
-		{0x02, 0x01, 0x52, 0x00}, {0x09, 0x01, 0x52, 0x00},
-		{0x17, 0x01, 0x52, 0x00}, {0x28, 0x01, 0x52, 0x01},
-		{0x02, 0x01, 0x53, 0x00}, {0x09, 0x01, 0x53, 0x00},
-		{0x17, 0x01, 0x53, 0x00}, {0x28, 0x01, 0x53, 0x01},
-		{0x02, 0x01, 0x54, 0x00}, {0x09, 0x01, 0x54, 0x00},
-		{0x17, 0x01, 0x54, 0x00}, {0x28, 0x01, 0x54, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x51, 0x00}, {0x06, 0x01, 0x51, 0x00},
-		{0x0a, 0x01, 0x51, 0x00}, {0x0f, 0x01, 0x51, 0x00},
-		{0x18, 0x01, 0x51, 0x00}, {0x1f, 0x01, 0x51, 0x00},
-		{0x29, 0x01, 0x51, 0x00}, {0x38, 0x01, 0x51, 0x01},
-		{0x03, 0x01, 0x52, 0x00}, {0x06, 0x01, 0x52, 0x00},
-		{0x0a, 0x01, 0x52, 0x00}, {0x0f, 0x01, 0x52, 0x00},
-		{0x18, 0x01, 0x52, 0x00}, {0x1f, 0x01, 0x52, 0x00},
-		{0x29, 0x01, 0x52, 0x00}, {0x38, 0x01, 0x52, 0x01}
-	],
-	/* 55 */
-	[
-		{0x03, 0x01, 0x53, 0x00}, {0x06, 0x01, 0x53, 0x00},
-		{0x0a, 0x01, 0x53, 0x00}, {0x0f, 0x01, 0x53, 0x00},
-		{0x18, 0x01, 0x53, 0x00}, {0x1f, 0x01, 0x53, 0x00},
-		{0x29, 0x01, 0x53, 0x00}, {0x38, 0x01, 0x53, 0x01},
-		{0x03, 0x01, 0x54, 0x00}, {0x06, 0x01, 0x54, 0x00},
-		{0x0a, 0x01, 0x54, 0x00}, {0x0f, 0x01, 0x54, 0x00},
-		{0x18, 0x01, 0x54, 0x00}, {0x1f, 0x01, 0x54, 0x00},
-		{0x29, 0x01, 0x54, 0x00}, {0x38, 0x01, 0x54, 0x01}
-	],
-	[
-		{0x00, 0x01, 0x55, 0x01}, {0x00, 0x01, 0x56, 0x01},
-		{0x00, 0x01, 0x57, 0x01}, {0x00, 0x01, 0x59, 0x01},
-		{0x00, 0x01, 0x6a, 0x01}, {0x00, 0x01, 0x6b, 0x01},
-		{0x00, 0x01, 0x71, 0x01}, {0x00, 0x01, 0x76, 0x01},
-		{0x00, 0x01, 0x77, 0x01}, {0x00, 0x01, 0x78, 0x01},
-		{0x00, 0x01, 0x79, 0x01}, {0x00, 0x01, 0x7a, 0x01},
-		{0x46, 0x00, 0x00, 0x00}, {0x47, 0x00, 0x00, 0x00},
-		{0x49, 0x00, 0x00, 0x00}, {0x4a, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x55, 0x00}, {0x16, 0x01, 0x55, 0x01},
-		{0x01, 0x01, 0x56, 0x00}, {0x16, 0x01, 0x56, 0x01},
-		{0x01, 0x01, 0x57, 0x00}, {0x16, 0x01, 0x57, 0x01},
-		{0x01, 0x01, 0x59, 0x00}, {0x16, 0x01, 0x59, 0x01},
-		{0x01, 0x01, 0x6a, 0x00}, {0x16, 0x01, 0x6a, 0x01},
-		{0x01, 0x01, 0x6b, 0x00}, {0x16, 0x01, 0x6b, 0x01},
-		{0x01, 0x01, 0x71, 0x00}, {0x16, 0x01, 0x71, 0x01},
-		{0x01, 0x01, 0x76, 0x00}, {0x16, 0x01, 0x76, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x55, 0x00}, {0x09, 0x01, 0x55, 0x00},
-		{0x17, 0x01, 0x55, 0x00}, {0x28, 0x01, 0x55, 0x01},
-		{0x02, 0x01, 0x56, 0x00}, {0x09, 0x01, 0x56, 0x00},
-		{0x17, 0x01, 0x56, 0x00}, {0x28, 0x01, 0x56, 0x01},
-		{0x02, 0x01, 0x57, 0x00}, {0x09, 0x01, 0x57, 0x00},
-		{0x17, 0x01, 0x57, 0x00}, {0x28, 0x01, 0x57, 0x01},
-		{0x02, 0x01, 0x59, 0x00}, {0x09, 0x01, 0x59, 0x00},
-		{0x17, 0x01, 0x59, 0x00}, {0x28, 0x01, 0x59, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x55, 0x00}, {0x06, 0x01, 0x55, 0x00},
-		{0x0a, 0x01, 0x55, 0x00}, {0x0f, 0x01, 0x55, 0x00},
-		{0x18, 0x01, 0x55, 0x00}, {0x1f, 0x01, 0x55, 0x00},
-		{0x29, 0x01, 0x55, 0x00}, {0x38, 0x01, 0x55, 0x01},
-		{0x03, 0x01, 0x56, 0x00}, {0x06, 0x01, 0x56, 0x00},
-		{0x0a, 0x01, 0x56, 0x00}, {0x0f, 0x01, 0x56, 0x00},
-		{0x18, 0x01, 0x56, 0x00}, {0x1f, 0x01, 0x56, 0x00},
-		{0x29, 0x01, 0x56, 0x00}, {0x38, 0x01, 0x56, 0x01}
-	],
-	/* 60 */
-	[
-		{0x03, 0x01, 0x57, 0x00}, {0x06, 0x01, 0x57, 0x00},
-		{0x0a, 0x01, 0x57, 0x00}, {0x0f, 0x01, 0x57, 0x00},
-		{0x18, 0x01, 0x57, 0x00}, {0x1f, 0x01, 0x57, 0x00},
-		{0x29, 0x01, 0x57, 0x00}, {0x38, 0x01, 0x57, 0x01},
-		{0x03, 0x01, 0x59, 0x00}, {0x06, 0x01, 0x59, 0x00},
-		{0x0a, 0x01, 0x59, 0x00}, {0x0f, 0x01, 0x59, 0x00},
-		{0x18, 0x01, 0x59, 0x00}, {0x1f, 0x01, 0x59, 0x00},
-		{0x29, 0x01, 0x59, 0x00}, {0x38, 0x01, 0x59, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x6a, 0x00}, {0x09, 0x01, 0x6a, 0x00},
-		{0x17, 0x01, 0x6a, 0x00}, {0x28, 0x01, 0x6a, 0x01},
-		{0x02, 0x01, 0x6b, 0x00}, {0x09, 0x01, 0x6b, 0x00},
-		{0x17, 0x01, 0x6b, 0x00}, {0x28, 0x01, 0x6b, 0x01},
-		{0x02, 0x01, 0x71, 0x00}, {0x09, 0x01, 0x71, 0x00},
-		{0x17, 0x01, 0x71, 0x00}, {0x28, 0x01, 0x71, 0x01},
-		{0x02, 0x01, 0x76, 0x00}, {0x09, 0x01, 0x76, 0x00},
-		{0x17, 0x01, 0x76, 0x00}, {0x28, 0x01, 0x76, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x6a, 0x00}, {0x06, 0x01, 0x6a, 0x00},
-		{0x0a, 0x01, 0x6a, 0x00}, {0x0f, 0x01, 0x6a, 0x00},
-		{0x18, 0x01, 0x6a, 0x00}, {0x1f, 0x01, 0x6a, 0x00},
-		{0x29, 0x01, 0x6a, 0x00}, {0x38, 0x01, 0x6a, 0x01},
-		{0x03, 0x01, 0x6b, 0x00}, {0x06, 0x01, 0x6b, 0x00},
-		{0x0a, 0x01, 0x6b, 0x00}, {0x0f, 0x01, 0x6b, 0x00},
-		{0x18, 0x01, 0x6b, 0x00}, {0x1f, 0x01, 0x6b, 0x00},
-		{0x29, 0x01, 0x6b, 0x00}, {0x38, 0x01, 0x6b, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x71, 0x00}, {0x06, 0x01, 0x71, 0x00},
-		{0x0a, 0x01, 0x71, 0x00}, {0x0f, 0x01, 0x71, 0x00},
-		{0x18, 0x01, 0x71, 0x00}, {0x1f, 0x01, 0x71, 0x00},
-		{0x29, 0x01, 0x71, 0x00}, {0x38, 0x01, 0x71, 0x01},
-		{0x03, 0x01, 0x76, 0x00}, {0x06, 0x01, 0x76, 0x00},
-		{0x0a, 0x01, 0x76, 0x00}, {0x0f, 0x01, 0x76, 0x00},
-		{0x18, 0x01, 0x76, 0x00}, {0x1f, 0x01, 0x76, 0x00},
-		{0x29, 0x01, 0x76, 0x00}, {0x38, 0x01, 0x76, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x77, 0x00}, {0x16, 0x01, 0x77, 0x01},
-		{0x01, 0x01, 0x78, 0x00}, {0x16, 0x01, 0x78, 0x01},
-		{0x01, 0x01, 0x79, 0x00}, {0x16, 0x01, 0x79, 0x01},
-		{0x01, 0x01, 0x7a, 0x00}, {0x16, 0x01, 0x7a, 0x01},
-		{0x00, 0x01, 0x26, 0x01}, {0x00, 0x01, 0x2a, 0x01},
-		{0x00, 0x01, 0x2c, 0x01}, {0x00, 0x01, 0x3b, 0x01},
-		{0x00, 0x01, 0x58, 0x01}, {0x00, 0x01, 0x5a, 0x01},
-		{0x4b, 0x00, 0x00, 0x00}, {0x4e, 0x00, 0x00, 0x01}
-	],
-	/* 65 */
-	[
-		{0x02, 0x01, 0x77, 0x00}, {0x09, 0x01, 0x77, 0x00},
-		{0x17, 0x01, 0x77, 0x00}, {0x28, 0x01, 0x77, 0x01},
-		{0x02, 0x01, 0x78, 0x00}, {0x09, 0x01, 0x78, 0x00},
-		{0x17, 0x01, 0x78, 0x00}, {0x28, 0x01, 0x78, 0x01},
-		{0x02, 0x01, 0x79, 0x00}, {0x09, 0x01, 0x79, 0x00},
-		{0x17, 0x01, 0x79, 0x00}, {0x28, 0x01, 0x79, 0x01},
-		{0x02, 0x01, 0x7a, 0x00}, {0x09, 0x01, 0x7a, 0x00},
-		{0x17, 0x01, 0x7a, 0x00}, {0x28, 0x01, 0x7a, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x77, 0x00}, {0x06, 0x01, 0x77, 0x00},
-		{0x0a, 0x01, 0x77, 0x00}, {0x0f, 0x01, 0x77, 0x00},
-		{0x18, 0x01, 0x77, 0x00}, {0x1f, 0x01, 0x77, 0x00},
-		{0x29, 0x01, 0x77, 0x00}, {0x38, 0x01, 0x77, 0x01},
-		{0x03, 0x01, 0x78, 0x00}, {0x06, 0x01, 0x78, 0x00},
-		{0x0a, 0x01, 0x78, 0x00}, {0x0f, 0x01, 0x78, 0x00},
-		{0x18, 0x01, 0x78, 0x00}, {0x1f, 0x01, 0x78, 0x00},
-		{0x29, 0x01, 0x78, 0x00}, {0x38, 0x01, 0x78, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x79, 0x00}, {0x06, 0x01, 0x79, 0x00},
-		{0x0a, 0x01, 0x79, 0x00}, {0x0f, 0x01, 0x79, 0x00},
-		{0x18, 0x01, 0x79, 0x00}, {0x1f, 0x01, 0x79, 0x00},
-		{0x29, 0x01, 0x79, 0x00}, {0x38, 0x01, 0x79, 0x01},
-		{0x03, 0x01, 0x7a, 0x00}, {0x06, 0x01, 0x7a, 0x00},
-		{0x0a, 0x01, 0x7a, 0x00}, {0x0f, 0x01, 0x7a, 0x00},
-		{0x18, 0x01, 0x7a, 0x00}, {0x1f, 0x01, 0x7a, 0x00},
-		{0x29, 0x01, 0x7a, 0x00}, {0x38, 0x01, 0x7a, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x26, 0x00}, {0x16, 0x01, 0x26, 0x01},
-		{0x01, 0x01, 0x2a, 0x00}, {0x16, 0x01, 0x2a, 0x01},
-		{0x01, 0x01, 0x2c, 0x00}, {0x16, 0x01, 0x2c, 0x01},
-		{0x01, 0x01, 0x3b, 0x00}, {0x16, 0x01, 0x3b, 0x01},
-		{0x01, 0x01, 0x58, 0x00}, {0x16, 0x01, 0x58, 0x01},
-		{0x01, 0x01, 0x5a, 0x00}, {0x16, 0x01, 0x5a, 0x01},
-		{0x4c, 0x00, 0x00, 0x00}, {0x4d, 0x00, 0x00, 0x00},
-		{0x4f, 0x00, 0x00, 0x00}, {0x51, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x26, 0x00}, {0x09, 0x01, 0x26, 0x00},
-		{0x17, 0x01, 0x26, 0x00}, {0x28, 0x01, 0x26, 0x01},
-		{0x02, 0x01, 0x2a, 0x00}, {0x09, 0x01, 0x2a, 0x00},
-		{0x17, 0x01, 0x2a, 0x00}, {0x28, 0x01, 0x2a, 0x01},
-		{0x02, 0x01, 0x2c, 0x00}, {0x09, 0x01, 0x2c, 0x00},
-		{0x17, 0x01, 0x2c, 0x00}, {0x28, 0x01, 0x2c, 0x01},
-		{0x02, 0x01, 0x3b, 0x00}, {0x09, 0x01, 0x3b, 0x00},
-		{0x17, 0x01, 0x3b, 0x00}, {0x28, 0x01, 0x3b, 0x01}
-	],
-	/* 70 */
-	[
-		{0x03, 0x01, 0x26, 0x00}, {0x06, 0x01, 0x26, 0x00},
-		{0x0a, 0x01, 0x26, 0x00}, {0x0f, 0x01, 0x26, 0x00},
-		{0x18, 0x01, 0x26, 0x00}, {0x1f, 0x01, 0x26, 0x00},
-		{0x29, 0x01, 0x26, 0x00}, {0x38, 0x01, 0x26, 0x01},
-		{0x03, 0x01, 0x2a, 0x00}, {0x06, 0x01, 0x2a, 0x00},
-		{0x0a, 0x01, 0x2a, 0x00}, {0x0f, 0x01, 0x2a, 0x00},
-		{0x18, 0x01, 0x2a, 0x00}, {0x1f, 0x01, 0x2a, 0x00},
-		{0x29, 0x01, 0x2a, 0x00}, {0x38, 0x01, 0x2a, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x2c, 0x00}, {0x06, 0x01, 0x2c, 0x00},
-		{0x0a, 0x01, 0x2c, 0x00}, {0x0f, 0x01, 0x2c, 0x00},
-		{0x18, 0x01, 0x2c, 0x00}, {0x1f, 0x01, 0x2c, 0x00},
-		{0x29, 0x01, 0x2c, 0x00}, {0x38, 0x01, 0x2c, 0x01},
-		{0x03, 0x01, 0x3b, 0x00}, {0x06, 0x01, 0x3b, 0x00},
-		{0x0a, 0x01, 0x3b, 0x00}, {0x0f, 0x01, 0x3b, 0x00},
-		{0x18, 0x01, 0x3b, 0x00}, {0x1f, 0x01, 0x3b, 0x00},
-		{0x29, 0x01, 0x3b, 0x00}, {0x38, 0x01, 0x3b, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x58, 0x00}, {0x09, 0x01, 0x58, 0x00},
-		{0x17, 0x01, 0x58, 0x00}, {0x28, 0x01, 0x58, 0x01},
-		{0x02, 0x01, 0x5a, 0x00}, {0x09, 0x01, 0x5a, 0x00},
-		{0x17, 0x01, 0x5a, 0x00}, {0x28, 0x01, 0x5a, 0x01},
-		{0x00, 0x01, 0x21, 0x01}, {0x00, 0x01, 0x22, 0x01},
-		{0x00, 0x01, 0x28, 0x01}, {0x00, 0x01, 0x29, 0x01},
-		{0x00, 0x01, 0x3f, 0x01}, {0x50, 0x00, 0x00, 0x00},
-		{0x52, 0x00, 0x00, 0x00}, {0x54, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x58, 0x00}, {0x06, 0x01, 0x58, 0x00},
-		{0x0a, 0x01, 0x58, 0x00}, {0x0f, 0x01, 0x58, 0x00},
-		{0x18, 0x01, 0x58, 0x00}, {0x1f, 0x01, 0x58, 0x00},
-		{0x29, 0x01, 0x58, 0x00}, {0x38, 0x01, 0x58, 0x01},
-		{0x03, 0x01, 0x5a, 0x00}, {0x06, 0x01, 0x5a, 0x00},
-		{0x0a, 0x01, 0x5a, 0x00}, {0x0f, 0x01, 0x5a, 0x00},
-		{0x18, 0x01, 0x5a, 0x00}, {0x1f, 0x01, 0x5a, 0x00},
-		{0x29, 0x01, 0x5a, 0x00}, {0x38, 0x01, 0x5a, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x21, 0x00}, {0x16, 0x01, 0x21, 0x01},
-		{0x01, 0x01, 0x22, 0x00}, {0x16, 0x01, 0x22, 0x01},
-		{0x01, 0x01, 0x28, 0x00}, {0x16, 0x01, 0x28, 0x01},
-		{0x01, 0x01, 0x29, 0x00}, {0x16, 0x01, 0x29, 0x01},
-		{0x01, 0x01, 0x3f, 0x00}, {0x16, 0x01, 0x3f, 0x01},
-		{0x00, 0x01, 0x27, 0x01}, {0x00, 0x01, 0x2b, 0x01},
-		{0x00, 0x01, 0x7c, 0x01}, {0x53, 0x00, 0x00, 0x00},
-		{0x55, 0x00, 0x00, 0x00}, {0x58, 0x00, 0x00, 0x01}
-	],
-	/* 75 */
-	[
-		{0x02, 0x01, 0x21, 0x00}, {0x09, 0x01, 0x21, 0x00},
-		{0x17, 0x01, 0x21, 0x00}, {0x28, 0x01, 0x21, 0x01},
-		{0x02, 0x01, 0x22, 0x00}, {0x09, 0x01, 0x22, 0x00},
-		{0x17, 0x01, 0x22, 0x00}, {0x28, 0x01, 0x22, 0x01},
-		{0x02, 0x01, 0x28, 0x00}, {0x09, 0x01, 0x28, 0x00},
-		{0x17, 0x01, 0x28, 0x00}, {0x28, 0x01, 0x28, 0x01},
-		{0x02, 0x01, 0x29, 0x00}, {0x09, 0x01, 0x29, 0x00},
-		{0x17, 0x01, 0x29, 0x00}, {0x28, 0x01, 0x29, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x21, 0x00}, {0x06, 0x01, 0x21, 0x00},
-		{0x0a, 0x01, 0x21, 0x00}, {0x0f, 0x01, 0x21, 0x00},
-		{0x18, 0x01, 0x21, 0x00}, {0x1f, 0x01, 0x21, 0x00},
-		{0x29, 0x01, 0x21, 0x00}, {0x38, 0x01, 0x21, 0x01},
-		{0x03, 0x01, 0x22, 0x00}, {0x06, 0x01, 0x22, 0x00},
-		{0x0a, 0x01, 0x22, 0x00}, {0x0f, 0x01, 0x22, 0x00},
-		{0x18, 0x01, 0x22, 0x00}, {0x1f, 0x01, 0x22, 0x00},
-		{0x29, 0x01, 0x22, 0x00}, {0x38, 0x01, 0x22, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x28, 0x00}, {0x06, 0x01, 0x28, 0x00},
-		{0x0a, 0x01, 0x28, 0x00}, {0x0f, 0x01, 0x28, 0x00},
-		{0x18, 0x01, 0x28, 0x00}, {0x1f, 0x01, 0x28, 0x00},
-		{0x29, 0x01, 0x28, 0x00}, {0x38, 0x01, 0x28, 0x01},
-		{0x03, 0x01, 0x29, 0x00}, {0x06, 0x01, 0x29, 0x00},
-		{0x0a, 0x01, 0x29, 0x00}, {0x0f, 0x01, 0x29, 0x00},
-		{0x18, 0x01, 0x29, 0x00}, {0x1f, 0x01, 0x29, 0x00},
-		{0x29, 0x01, 0x29, 0x00}, {0x38, 0x01, 0x29, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x3f, 0x00}, {0x09, 0x01, 0x3f, 0x00},
-		{0x17, 0x01, 0x3f, 0x00}, {0x28, 0x01, 0x3f, 0x01},
-		{0x01, 0x01, 0x27, 0x00}, {0x16, 0x01, 0x27, 0x01},
-		{0x01, 0x01, 0x2b, 0x00}, {0x16, 0x01, 0x2b, 0x01},
-		{0x01, 0x01, 0x7c, 0x00}, {0x16, 0x01, 0x7c, 0x01},
-		{0x00, 0x01, 0x23, 0x01}, {0x00, 0x01, 0x3e, 0x01},
-		{0x56, 0x00, 0x00, 0x00}, {0x57, 0x00, 0x00, 0x00},
-		{0x59, 0x00, 0x00, 0x00}, {0x5a, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x3f, 0x00}, {0x06, 0x01, 0x3f, 0x00},
-		{0x0a, 0x01, 0x3f, 0x00}, {0x0f, 0x01, 0x3f, 0x00},
-		{0x18, 0x01, 0x3f, 0x00}, {0x1f, 0x01, 0x3f, 0x00},
-		{0x29, 0x01, 0x3f, 0x00}, {0x38, 0x01, 0x3f, 0x01},
-		{0x02, 0x01, 0x27, 0x00}, {0x09, 0x01, 0x27, 0x00},
-		{0x17, 0x01, 0x27, 0x00}, {0x28, 0x01, 0x27, 0x01},
-		{0x02, 0x01, 0x2b, 0x00}, {0x09, 0x01, 0x2b, 0x00},
-		{0x17, 0x01, 0x2b, 0x00}, {0x28, 0x01, 0x2b, 0x01}
-	],
-	/* 80 */
-	[
-		{0x03, 0x01, 0x27, 0x00}, {0x06, 0x01, 0x27, 0x00},
-		{0x0a, 0x01, 0x27, 0x00}, {0x0f, 0x01, 0x27, 0x00},
-		{0x18, 0x01, 0x27, 0x00}, {0x1f, 0x01, 0x27, 0x00},
-		{0x29, 0x01, 0x27, 0x00}, {0x38, 0x01, 0x27, 0x01},
-		{0x03, 0x01, 0x2b, 0x00}, {0x06, 0x01, 0x2b, 0x00},
-		{0x0a, 0x01, 0x2b, 0x00}, {0x0f, 0x01, 0x2b, 0x00},
-		{0x18, 0x01, 0x2b, 0x00}, {0x1f, 0x01, 0x2b, 0x00},
-		{0x29, 0x01, 0x2b, 0x00}, {0x38, 0x01, 0x2b, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x7c, 0x00}, {0x09, 0x01, 0x7c, 0x00},
-		{0x17, 0x01, 0x7c, 0x00}, {0x28, 0x01, 0x7c, 0x01},
-		{0x01, 0x01, 0x23, 0x00}, {0x16, 0x01, 0x23, 0x01},
-		{0x01, 0x01, 0x3e, 0x00}, {0x16, 0x01, 0x3e, 0x01},
-		{0x00, 0x01, 0x00, 0x01}, {0x00, 0x01, 0x24, 0x01},
-		{0x00, 0x01, 0x40, 0x01}, {0x00, 0x01, 0x5b, 0x01},
-		{0x00, 0x01, 0x5d, 0x01}, {0x00, 0x01, 0x7e, 0x01},
-		{0x5b, 0x00, 0x00, 0x00}, {0x5c, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x7c, 0x00}, {0x06, 0x01, 0x7c, 0x00},
-		{0x0a, 0x01, 0x7c, 0x00}, {0x0f, 0x01, 0x7c, 0x00},
-		{0x18, 0x01, 0x7c, 0x00}, {0x1f, 0x01, 0x7c, 0x00},
-		{0x29, 0x01, 0x7c, 0x00}, {0x38, 0x01, 0x7c, 0x01},
-		{0x02, 0x01, 0x23, 0x00}, {0x09, 0x01, 0x23, 0x00},
-		{0x17, 0x01, 0x23, 0x00}, {0x28, 0x01, 0x23, 0x01},
-		{0x02, 0x01, 0x3e, 0x00}, {0x09, 0x01, 0x3e, 0x00},
-		{0x17, 0x01, 0x3e, 0x00}, {0x28, 0x01, 0x3e, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x23, 0x00}, {0x06, 0x01, 0x23, 0x00},
-		{0x0a, 0x01, 0x23, 0x00}, {0x0f, 0x01, 0x23, 0x00},
-		{0x18, 0x01, 0x23, 0x00}, {0x1f, 0x01, 0x23, 0x00},
-		{0x29, 0x01, 0x23, 0x00}, {0x38, 0x01, 0x23, 0x01},
-		{0x03, 0x01, 0x3e, 0x00}, {0x06, 0x01, 0x3e, 0x00},
-		{0x0a, 0x01, 0x3e, 0x00}, {0x0f, 0x01, 0x3e, 0x00},
-		{0x18, 0x01, 0x3e, 0x00}, {0x1f, 0x01, 0x3e, 0x00},
-		{0x29, 0x01, 0x3e, 0x00}, {0x38, 0x01, 0x3e, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x00, 0x00}, {0x16, 0x01, 0x00, 0x01},
-		{0x01, 0x01, 0x24, 0x00}, {0x16, 0x01, 0x24, 0x01},
-		{0x01, 0x01, 0x40, 0x00}, {0x16, 0x01, 0x40, 0x01},
-		{0x01, 0x01, 0x5b, 0x00}, {0x16, 0x01, 0x5b, 0x01},
-		{0x01, 0x01, 0x5d, 0x00}, {0x16, 0x01, 0x5d, 0x01},
-		{0x01, 0x01, 0x7e, 0x00}, {0x16, 0x01, 0x7e, 0x01},
-		{0x00, 0x01, 0x5e, 0x01}, {0x00, 0x01, 0x7d, 0x01},
-		{0x5d, 0x00, 0x00, 0x00}, {0x5e, 0x00, 0x00, 0x01}
-	],
-	/* 85 */
-	[
-		{0x02, 0x01, 0x00, 0x00}, {0x09, 0x01, 0x00, 0x00},
-		{0x17, 0x01, 0x00, 0x00}, {0x28, 0x01, 0x00, 0x01},
-		{0x02, 0x01, 0x24, 0x00}, {0x09, 0x01, 0x24, 0x00},
-		{0x17, 0x01, 0x24, 0x00}, {0x28, 0x01, 0x24, 0x01},
-		{0x02, 0x01, 0x40, 0x00}, {0x09, 0x01, 0x40, 0x00},
-		{0x17, 0x01, 0x40, 0x00}, {0x28, 0x01, 0x40, 0x01},
-		{0x02, 0x01, 0x5b, 0x00}, {0x09, 0x01, 0x5b, 0x00},
-		{0x17, 0x01, 0x5b, 0x00}, {0x28, 0x01, 0x5b, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x00, 0x00}, {0x06, 0x01, 0x00, 0x00},
-		{0x0a, 0x01, 0x00, 0x00}, {0x0f, 0x01, 0x00, 0x00},
-		{0x18, 0x01, 0x00, 0x00}, {0x1f, 0x01, 0x00, 0x00},
-		{0x29, 0x01, 0x00, 0x00}, {0x38, 0x01, 0x00, 0x01},
-		{0x03, 0x01, 0x24, 0x00}, {0x06, 0x01, 0x24, 0x00},
-		{0x0a, 0x01, 0x24, 0x00}, {0x0f, 0x01, 0x24, 0x00},
-		{0x18, 0x01, 0x24, 0x00}, {0x1f, 0x01, 0x24, 0x00},
-		{0x29, 0x01, 0x24, 0x00}, {0x38, 0x01, 0x24, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x40, 0x00}, {0x06, 0x01, 0x40, 0x00},
-		{0x0a, 0x01, 0x40, 0x00}, {0x0f, 0x01, 0x40, 0x00},
-		{0x18, 0x01, 0x40, 0x00}, {0x1f, 0x01, 0x40, 0x00},
-		{0x29, 0x01, 0x40, 0x00}, {0x38, 0x01, 0x40, 0x01},
-		{0x03, 0x01, 0x5b, 0x00}, {0x06, 0x01, 0x5b, 0x00},
-		{0x0a, 0x01, 0x5b, 0x00}, {0x0f, 0x01, 0x5b, 0x00},
-		{0x18, 0x01, 0x5b, 0x00}, {0x1f, 0x01, 0x5b, 0x00},
-		{0x29, 0x01, 0x5b, 0x00}, {0x38, 0x01, 0x5b, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x5d, 0x00}, {0x09, 0x01, 0x5d, 0x00},
-		{0x17, 0x01, 0x5d, 0x00}, {0x28, 0x01, 0x5d, 0x01},
-		{0x02, 0x01, 0x7e, 0x00}, {0x09, 0x01, 0x7e, 0x00},
-		{0x17, 0x01, 0x7e, 0x00}, {0x28, 0x01, 0x7e, 0x01},
-		{0x01, 0x01, 0x5e, 0x00}, {0x16, 0x01, 0x5e, 0x01},
-		{0x01, 0x01, 0x7d, 0x00}, {0x16, 0x01, 0x7d, 0x01},
-		{0x00, 0x01, 0x3c, 0x01}, {0x00, 0x01, 0x60, 0x01},
-		{0x00, 0x01, 0x7b, 0x01}, {0x5f, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x5d, 0x00}, {0x06, 0x01, 0x5d, 0x00},
-		{0x0a, 0x01, 0x5d, 0x00}, {0x0f, 0x01, 0x5d, 0x00},
-		{0x18, 0x01, 0x5d, 0x00}, {0x1f, 0x01, 0x5d, 0x00},
-		{0x29, 0x01, 0x5d, 0x00}, {0x38, 0x01, 0x5d, 0x01},
-		{0x03, 0x01, 0x7e, 0x00}, {0x06, 0x01, 0x7e, 0x00},
-		{0x0a, 0x01, 0x7e, 0x00}, {0x0f, 0x01, 0x7e, 0x00},
-		{0x18, 0x01, 0x7e, 0x00}, {0x1f, 0x01, 0x7e, 0x00},
-		{0x29, 0x01, 0x7e, 0x00}, {0x38, 0x01, 0x7e, 0x01}
-	],
-	/* 90 */
-	[
-		{0x02, 0x01, 0x5e, 0x00}, {0x09, 0x01, 0x5e, 0x00},
-		{0x17, 0x01, 0x5e, 0x00}, {0x28, 0x01, 0x5e, 0x01},
-		{0x02, 0x01, 0x7d, 0x00}, {0x09, 0x01, 0x7d, 0x00},
-		{0x17, 0x01, 0x7d, 0x00}, {0x28, 0x01, 0x7d, 0x01},
-		{0x01, 0x01, 0x3c, 0x00}, {0x16, 0x01, 0x3c, 0x01},
-		{0x01, 0x01, 0x60, 0x00}, {0x16, 0x01, 0x60, 0x01},
-		{0x01, 0x01, 0x7b, 0x00}, {0x16, 0x01, 0x7b, 0x01},
-		{0x60, 0x00, 0x00, 0x00}, {0x6e, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x5e, 0x00}, {0x06, 0x01, 0x5e, 0x00},
-		{0x0a, 0x01, 0x5e, 0x00}, {0x0f, 0x01, 0x5e, 0x00},
-		{0x18, 0x01, 0x5e, 0x00}, {0x1f, 0x01, 0x5e, 0x00},
-		{0x29, 0x01, 0x5e, 0x00}, {0x38, 0x01, 0x5e, 0x01},
-		{0x03, 0x01, 0x7d, 0x00}, {0x06, 0x01, 0x7d, 0x00},
-		{0x0a, 0x01, 0x7d, 0x00}, {0x0f, 0x01, 0x7d, 0x00},
-		{0x18, 0x01, 0x7d, 0x00}, {0x1f, 0x01, 0x7d, 0x00},
-		{0x29, 0x01, 0x7d, 0x00}, {0x38, 0x01, 0x7d, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x3c, 0x00}, {0x09, 0x01, 0x3c, 0x00},
-		{0x17, 0x01, 0x3c, 0x00}, {0x28, 0x01, 0x3c, 0x01},
-		{0x02, 0x01, 0x60, 0x00}, {0x09, 0x01, 0x60, 0x00},
-		{0x17, 0x01, 0x60, 0x00}, {0x28, 0x01, 0x60, 0x01},
-		{0x02, 0x01, 0x7b, 0x00}, {0x09, 0x01, 0x7b, 0x00},
-		{0x17, 0x01, 0x7b, 0x00}, {0x28, 0x01, 0x7b, 0x01},
-		{0x61, 0x00, 0x00, 0x00}, {0x65, 0x00, 0x00, 0x00},
-		{0x6f, 0x00, 0x00, 0x00}, {0x85, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x3c, 0x00}, {0x06, 0x01, 0x3c, 0x00},
-		{0x0a, 0x01, 0x3c, 0x00}, {0x0f, 0x01, 0x3c, 0x00},
-		{0x18, 0x01, 0x3c, 0x00}, {0x1f, 0x01, 0x3c, 0x00},
-		{0x29, 0x01, 0x3c, 0x00}, {0x38, 0x01, 0x3c, 0x01},
-		{0x03, 0x01, 0x60, 0x00}, {0x06, 0x01, 0x60, 0x00},
-		{0x0a, 0x01, 0x60, 0x00}, {0x0f, 0x01, 0x60, 0x00},
-		{0x18, 0x01, 0x60, 0x00}, {0x1f, 0x01, 0x60, 0x00},
-		{0x29, 0x01, 0x60, 0x00}, {0x38, 0x01, 0x60, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x7b, 0x00}, {0x06, 0x01, 0x7b, 0x00},
-		{0x0a, 0x01, 0x7b, 0x00}, {0x0f, 0x01, 0x7b, 0x00},
-		{0x18, 0x01, 0x7b, 0x00}, {0x1f, 0x01, 0x7b, 0x00},
-		{0x29, 0x01, 0x7b, 0x00}, {0x38, 0x01, 0x7b, 0x01},
-		{0x62, 0x00, 0x00, 0x00}, {0x63, 0x00, 0x00, 0x00},
-		{0x66, 0x00, 0x00, 0x00}, {0x69, 0x00, 0x00, 0x00},
-		{0x70, 0x00, 0x00, 0x00}, {0x77, 0x00, 0x00, 0x00},
-		{0x86, 0x00, 0x00, 0x00}, {0x99, 0x00, 0x00, 0x01}
-	],
-	/* 95 */
-	[
-		{0x00, 0x01, 0x5c, 0x01}, {0x00, 0x01, 0xc3, 0x01},
-		{0x00, 0x01, 0xd0, 0x01}, {0x64, 0x00, 0x00, 0x00},
-		{0x67, 0x00, 0x00, 0x00}, {0x68, 0x00, 0x00, 0x00},
-		{0x6a, 0x00, 0x00, 0x00}, {0x6b, 0x00, 0x00, 0x00},
-		{0x71, 0x00, 0x00, 0x00}, {0x74, 0x00, 0x00, 0x00},
-		{0x78, 0x00, 0x00, 0x00}, {0x7e, 0x00, 0x00, 0x00},
-		{0x87, 0x00, 0x00, 0x00}, {0x8e, 0x00, 0x00, 0x00},
-		{0x9a, 0x00, 0x00, 0x00}, {0xa9, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x5c, 0x00}, {0x16, 0x01, 0x5c, 0x01},
-		{0x01, 0x01, 0xc3, 0x00}, {0x16, 0x01, 0xc3, 0x01},
-		{0x01, 0x01, 0xd0, 0x00}, {0x16, 0x01, 0xd0, 0x01},
-		{0x00, 0x01, 0x80, 0x01}, {0x00, 0x01, 0x82, 0x01},
-		{0x00, 0x01, 0x83, 0x01}, {0x00, 0x01, 0xa2, 0x01},
-		{0x00, 0x01, 0xb8, 0x01}, {0x00, 0x01, 0xc2, 0x01},
-		{0x00, 0x01, 0xe0, 0x01}, {0x00, 0x01, 0xe2, 0x01},
-		{0x6c, 0x00, 0x00, 0x00}, {0x6d, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x02, 0x01, 0x5c, 0x00}, {0x09, 0x01, 0x5c, 0x00},
-		{0x17, 0x01, 0x5c, 0x00}, {0x28, 0x01, 0x5c, 0x01},
-		{0x02, 0x01, 0xc3, 0x00}, {0x09, 0x01, 0xc3, 0x00},
-		{0x17, 0x01, 0xc3, 0x00}, {0x28, 0x01, 0xc3, 0x01},
-		{0x02, 0x01, 0xd0, 0x00}, {0x09, 0x01, 0xd0, 0x00},
-		{0x17, 0x01, 0xd0, 0x00}, {0x28, 0x01, 0xd0, 0x01},
-		{0x01, 0x01, 0x80, 0x00}, {0x16, 0x01, 0x80, 0x01},
-		{0x01, 0x01, 0x82, 0x00}, {0x16, 0x01, 0x82, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x5c, 0x00}, {0x06, 0x01, 0x5c, 0x00},
-		{0x0a, 0x01, 0x5c, 0x00}, {0x0f, 0x01, 0x5c, 0x00},
-		{0x18, 0x01, 0x5c, 0x00}, {0x1f, 0x01, 0x5c, 0x00},
-		{0x29, 0x01, 0x5c, 0x00}, {0x38, 0x01, 0x5c, 0x01},
-		{0x03, 0x01, 0xc3, 0x00}, {0x06, 0x01, 0xc3, 0x00},
-		{0x0a, 0x01, 0xc3, 0x00}, {0x0f, 0x01, 0xc3, 0x00},
-		{0x18, 0x01, 0xc3, 0x00}, {0x1f, 0x01, 0xc3, 0x00},
-		{0x29, 0x01, 0xc3, 0x00}, {0x38, 0x01, 0xc3, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xd0, 0x00}, {0x06, 0x01, 0xd0, 0x00},
-		{0x0a, 0x01, 0xd0, 0x00}, {0x0f, 0x01, 0xd0, 0x00},
-		{0x18, 0x01, 0xd0, 0x00}, {0x1f, 0x01, 0xd0, 0x00},
-		{0x29, 0x01, 0xd0, 0x00}, {0x38, 0x01, 0xd0, 0x01},
-		{0x02, 0x01, 0x80, 0x00}, {0x09, 0x01, 0x80, 0x00},
-		{0x17, 0x01, 0x80, 0x00}, {0x28, 0x01, 0x80, 0x01},
-		{0x02, 0x01, 0x82, 0x00}, {0x09, 0x01, 0x82, 0x00},
-		{0x17, 0x01, 0x82, 0x00}, {0x28, 0x01, 0x82, 0x01}
-	],
-	/* 100 */
-	[
-		{0x03, 0x01, 0x80, 0x00}, {0x06, 0x01, 0x80, 0x00},
-		{0x0a, 0x01, 0x80, 0x00}, {0x0f, 0x01, 0x80, 0x00},
-		{0x18, 0x01, 0x80, 0x00}, {0x1f, 0x01, 0x80, 0x00},
-		{0x29, 0x01, 0x80, 0x00}, {0x38, 0x01, 0x80, 0x01},
-		{0x03, 0x01, 0x82, 0x00}, {0x06, 0x01, 0x82, 0x00},
-		{0x0a, 0x01, 0x82, 0x00}, {0x0f, 0x01, 0x82, 0x00},
-		{0x18, 0x01, 0x82, 0x00}, {0x1f, 0x01, 0x82, 0x00},
-		{0x29, 0x01, 0x82, 0x00}, {0x38, 0x01, 0x82, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x83, 0x00}, {0x16, 0x01, 0x83, 0x01},
-		{0x01, 0x01, 0xa2, 0x00}, {0x16, 0x01, 0xa2, 0x01},
-		{0x01, 0x01, 0xb8, 0x00}, {0x16, 0x01, 0xb8, 0x01},
-		{0x01, 0x01, 0xc2, 0x00}, {0x16, 0x01, 0xc2, 0x01},
-		{0x01, 0x01, 0xe0, 0x00}, {0x16, 0x01, 0xe0, 0x01},
-		{0x01, 0x01, 0xe2, 0x00}, {0x16, 0x01, 0xe2, 0x01},
-		{0x00, 0x01, 0x99, 0x01}, {0x00, 0x01, 0xa1, 0x01},
-		{0x00, 0x01, 0xa7, 0x01}, {0x00, 0x01, 0xac, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x83, 0x00}, {0x09, 0x01, 0x83, 0x00},
-		{0x17, 0x01, 0x83, 0x00}, {0x28, 0x01, 0x83, 0x01},
-		{0x02, 0x01, 0xa2, 0x00}, {0x09, 0x01, 0xa2, 0x00},
-		{0x17, 0x01, 0xa2, 0x00}, {0x28, 0x01, 0xa2, 0x01},
-		{0x02, 0x01, 0xb8, 0x00}, {0x09, 0x01, 0xb8, 0x00},
-		{0x17, 0x01, 0xb8, 0x00}, {0x28, 0x01, 0xb8, 0x01},
-		{0x02, 0x01, 0xc2, 0x00}, {0x09, 0x01, 0xc2, 0x00},
-		{0x17, 0x01, 0xc2, 0x00}, {0x28, 0x01, 0xc2, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x83, 0x00}, {0x06, 0x01, 0x83, 0x00},
-		{0x0a, 0x01, 0x83, 0x00}, {0x0f, 0x01, 0x83, 0x00},
-		{0x18, 0x01, 0x83, 0x00}, {0x1f, 0x01, 0x83, 0x00},
-		{0x29, 0x01, 0x83, 0x00}, {0x38, 0x01, 0x83, 0x01},
-		{0x03, 0x01, 0xa2, 0x00}, {0x06, 0x01, 0xa2, 0x00},
-		{0x0a, 0x01, 0xa2, 0x00}, {0x0f, 0x01, 0xa2, 0x00},
-		{0x18, 0x01, 0xa2, 0x00}, {0x1f, 0x01, 0xa2, 0x00},
-		{0x29, 0x01, 0xa2, 0x00}, {0x38, 0x01, 0xa2, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xb8, 0x00}, {0x06, 0x01, 0xb8, 0x00},
-		{0x0a, 0x01, 0xb8, 0x00}, {0x0f, 0x01, 0xb8, 0x00},
-		{0x18, 0x01, 0xb8, 0x00}, {0x1f, 0x01, 0xb8, 0x00},
-		{0x29, 0x01, 0xb8, 0x00}, {0x38, 0x01, 0xb8, 0x01},
-		{0x03, 0x01, 0xc2, 0x00}, {0x06, 0x01, 0xc2, 0x00},
-		{0x0a, 0x01, 0xc2, 0x00}, {0x0f, 0x01, 0xc2, 0x00},
-		{0x18, 0x01, 0xc2, 0x00}, {0x1f, 0x01, 0xc2, 0x00},
-		{0x29, 0x01, 0xc2, 0x00}, {0x38, 0x01, 0xc2, 0x01}
-	],
-	/* 105 */
-	[
-		{0x02, 0x01, 0xe0, 0x00}, {0x09, 0x01, 0xe0, 0x00},
-		{0x17, 0x01, 0xe0, 0x00}, {0x28, 0x01, 0xe0, 0x01},
-		{0x02, 0x01, 0xe2, 0x00}, {0x09, 0x01, 0xe2, 0x00},
-		{0x17, 0x01, 0xe2, 0x00}, {0x28, 0x01, 0xe2, 0x01},
-		{0x01, 0x01, 0x99, 0x00}, {0x16, 0x01, 0x99, 0x01},
-		{0x01, 0x01, 0xa1, 0x00}, {0x16, 0x01, 0xa1, 0x01},
-		{0x01, 0x01, 0xa7, 0x00}, {0x16, 0x01, 0xa7, 0x01},
-		{0x01, 0x01, 0xac, 0x00}, {0x16, 0x01, 0xac, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xe0, 0x00}, {0x06, 0x01, 0xe0, 0x00},
-		{0x0a, 0x01, 0xe0, 0x00}, {0x0f, 0x01, 0xe0, 0x00},
-		{0x18, 0x01, 0xe0, 0x00}, {0x1f, 0x01, 0xe0, 0x00},
-		{0x29, 0x01, 0xe0, 0x00}, {0x38, 0x01, 0xe0, 0x01},
-		{0x03, 0x01, 0xe2, 0x00}, {0x06, 0x01, 0xe2, 0x00},
-		{0x0a, 0x01, 0xe2, 0x00}, {0x0f, 0x01, 0xe2, 0x00},
-		{0x18, 0x01, 0xe2, 0x00}, {0x1f, 0x01, 0xe2, 0x00},
-		{0x29, 0x01, 0xe2, 0x00}, {0x38, 0x01, 0xe2, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x99, 0x00}, {0x09, 0x01, 0x99, 0x00},
-		{0x17, 0x01, 0x99, 0x00}, {0x28, 0x01, 0x99, 0x01},
-		{0x02, 0x01, 0xa1, 0x00}, {0x09, 0x01, 0xa1, 0x00},
-		{0x17, 0x01, 0xa1, 0x00}, {0x28, 0x01, 0xa1, 0x01},
-		{0x02, 0x01, 0xa7, 0x00}, {0x09, 0x01, 0xa7, 0x00},
-		{0x17, 0x01, 0xa7, 0x00}, {0x28, 0x01, 0xa7, 0x01},
-		{0x02, 0x01, 0xac, 0x00}, {0x09, 0x01, 0xac, 0x00},
-		{0x17, 0x01, 0xac, 0x00}, {0x28, 0x01, 0xac, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x99, 0x00}, {0x06, 0x01, 0x99, 0x00},
-		{0x0a, 0x01, 0x99, 0x00}, {0x0f, 0x01, 0x99, 0x00},
-		{0x18, 0x01, 0x99, 0x00}, {0x1f, 0x01, 0x99, 0x00},
-		{0x29, 0x01, 0x99, 0x00}, {0x38, 0x01, 0x99, 0x01},
-		{0x03, 0x01, 0xa1, 0x00}, {0x06, 0x01, 0xa1, 0x00},
-		{0x0a, 0x01, 0xa1, 0x00}, {0x0f, 0x01, 0xa1, 0x00},
-		{0x18, 0x01, 0xa1, 0x00}, {0x1f, 0x01, 0xa1, 0x00},
-		{0x29, 0x01, 0xa1, 0x00}, {0x38, 0x01, 0xa1, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xa7, 0x00}, {0x06, 0x01, 0xa7, 0x00},
-		{0x0a, 0x01, 0xa7, 0x00}, {0x0f, 0x01, 0xa7, 0x00},
-		{0x18, 0x01, 0xa7, 0x00}, {0x1f, 0x01, 0xa7, 0x00},
-		{0x29, 0x01, 0xa7, 0x00}, {0x38, 0x01, 0xa7, 0x01},
-		{0x03, 0x01, 0xac, 0x00}, {0x06, 0x01, 0xac, 0x00},
-		{0x0a, 0x01, 0xac, 0x00}, {0x0f, 0x01, 0xac, 0x00},
-		{0x18, 0x01, 0xac, 0x00}, {0x1f, 0x01, 0xac, 0x00},
-		{0x29, 0x01, 0xac, 0x00}, {0x38, 0x01, 0xac, 0x01}
-	],
-	/* 110 */
-	[
-		{0x72, 0x00, 0x00, 0x00}, {0x73, 0x00, 0x00, 0x00},
-		{0x75, 0x00, 0x00, 0x00}, {0x76, 0x00, 0x00, 0x00},
-		{0x79, 0x00, 0x00, 0x00}, {0x7b, 0x00, 0x00, 0x00},
-		{0x7f, 0x00, 0x00, 0x00}, {0x82, 0x00, 0x00, 0x00},
-		{0x88, 0x00, 0x00, 0x00}, {0x8b, 0x00, 0x00, 0x00},
-		{0x8f, 0x00, 0x00, 0x00}, {0x92, 0x00, 0x00, 0x00},
-		{0x9b, 0x00, 0x00, 0x00}, {0xa2, 0x00, 0x00, 0x00},
-		{0xaa, 0x00, 0x00, 0x00}, {0xb4, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0xb0, 0x01}, {0x00, 0x01, 0xb1, 0x01},
-		{0x00, 0x01, 0xb3, 0x01}, {0x00, 0x01, 0xd1, 0x01},
-		{0x00, 0x01, 0xd8, 0x01}, {0x00, 0x01, 0xd9, 0x01},
-		{0x00, 0x01, 0xe3, 0x01}, {0x00, 0x01, 0xe5, 0x01},
-		{0x00, 0x01, 0xe6, 0x01}, {0x7a, 0x00, 0x00, 0x00},
-		{0x7c, 0x00, 0x00, 0x00}, {0x7d, 0x00, 0x00, 0x00},
-		{0x80, 0x00, 0x00, 0x00}, {0x81, 0x00, 0x00, 0x00},
-		{0x83, 0x00, 0x00, 0x00}, {0x84, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x01, 0x01, 0xb0, 0x00}, {0x16, 0x01, 0xb0, 0x01},
-		{0x01, 0x01, 0xb1, 0x00}, {0x16, 0x01, 0xb1, 0x01},
-		{0x01, 0x01, 0xb3, 0x00}, {0x16, 0x01, 0xb3, 0x01},
-		{0x01, 0x01, 0xd1, 0x00}, {0x16, 0x01, 0xd1, 0x01},
-		{0x01, 0x01, 0xd8, 0x00}, {0x16, 0x01, 0xd8, 0x01},
-		{0x01, 0x01, 0xd9, 0x00}, {0x16, 0x01, 0xd9, 0x01},
-		{0x01, 0x01, 0xe3, 0x00}, {0x16, 0x01, 0xe3, 0x01},
-		{0x01, 0x01, 0xe5, 0x00}, {0x16, 0x01, 0xe5, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xb0, 0x00}, {0x09, 0x01, 0xb0, 0x00},
-		{0x17, 0x01, 0xb0, 0x00}, {0x28, 0x01, 0xb0, 0x01},
-		{0x02, 0x01, 0xb1, 0x00}, {0x09, 0x01, 0xb1, 0x00},
-		{0x17, 0x01, 0xb1, 0x00}, {0x28, 0x01, 0xb1, 0x01},
-		{0x02, 0x01, 0xb3, 0x00}, {0x09, 0x01, 0xb3, 0x00},
-		{0x17, 0x01, 0xb3, 0x00}, {0x28, 0x01, 0xb3, 0x01},
-		{0x02, 0x01, 0xd1, 0x00}, {0x09, 0x01, 0xd1, 0x00},
-		{0x17, 0x01, 0xd1, 0x00}, {0x28, 0x01, 0xd1, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xb0, 0x00}, {0x06, 0x01, 0xb0, 0x00},
-		{0x0a, 0x01, 0xb0, 0x00}, {0x0f, 0x01, 0xb0, 0x00},
-		{0x18, 0x01, 0xb0, 0x00}, {0x1f, 0x01, 0xb0, 0x00},
-		{0x29, 0x01, 0xb0, 0x00}, {0x38, 0x01, 0xb0, 0x01},
-		{0x03, 0x01, 0xb1, 0x00}, {0x06, 0x01, 0xb1, 0x00},
-		{0x0a, 0x01, 0xb1, 0x00}, {0x0f, 0x01, 0xb1, 0x00},
-		{0x18, 0x01, 0xb1, 0x00}, {0x1f, 0x01, 0xb1, 0x00},
-		{0x29, 0x01, 0xb1, 0x00}, {0x38, 0x01, 0xb1, 0x01}
-	],
-	/* 115 */
-	[
-		{0x03, 0x01, 0xb3, 0x00}, {0x06, 0x01, 0xb3, 0x00},
-		{0x0a, 0x01, 0xb3, 0x00}, {0x0f, 0x01, 0xb3, 0x00},
-		{0x18, 0x01, 0xb3, 0x00}, {0x1f, 0x01, 0xb3, 0x00},
-		{0x29, 0x01, 0xb3, 0x00}, {0x38, 0x01, 0xb3, 0x01},
-		{0x03, 0x01, 0xd1, 0x00}, {0x06, 0x01, 0xd1, 0x00},
-		{0x0a, 0x01, 0xd1, 0x00}, {0x0f, 0x01, 0xd1, 0x00},
-		{0x18, 0x01, 0xd1, 0x00}, {0x1f, 0x01, 0xd1, 0x00},
-		{0x29, 0x01, 0xd1, 0x00}, {0x38, 0x01, 0xd1, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xd8, 0x00}, {0x09, 0x01, 0xd8, 0x00},
-		{0x17, 0x01, 0xd8, 0x00}, {0x28, 0x01, 0xd8, 0x01},
-		{0x02, 0x01, 0xd9, 0x00}, {0x09, 0x01, 0xd9, 0x00},
-		{0x17, 0x01, 0xd9, 0x00}, {0x28, 0x01, 0xd9, 0x01},
-		{0x02, 0x01, 0xe3, 0x00}, {0x09, 0x01, 0xe3, 0x00},
-		{0x17, 0x01, 0xe3, 0x00}, {0x28, 0x01, 0xe3, 0x01},
-		{0x02, 0x01, 0xe5, 0x00}, {0x09, 0x01, 0xe5, 0x00},
-		{0x17, 0x01, 0xe5, 0x00}, {0x28, 0x01, 0xe5, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xd8, 0x00}, {0x06, 0x01, 0xd8, 0x00},
-		{0x0a, 0x01, 0xd8, 0x00}, {0x0f, 0x01, 0xd8, 0x00},
-		{0x18, 0x01, 0xd8, 0x00}, {0x1f, 0x01, 0xd8, 0x00},
-		{0x29, 0x01, 0xd8, 0x00}, {0x38, 0x01, 0xd8, 0x01},
-		{0x03, 0x01, 0xd9, 0x00}, {0x06, 0x01, 0xd9, 0x00},
-		{0x0a, 0x01, 0xd9, 0x00}, {0x0f, 0x01, 0xd9, 0x00},
-		{0x18, 0x01, 0xd9, 0x00}, {0x1f, 0x01, 0xd9, 0x00},
-		{0x29, 0x01, 0xd9, 0x00}, {0x38, 0x01, 0xd9, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xe3, 0x00}, {0x06, 0x01, 0xe3, 0x00},
-		{0x0a, 0x01, 0xe3, 0x00}, {0x0f, 0x01, 0xe3, 0x00},
-		{0x18, 0x01, 0xe3, 0x00}, {0x1f, 0x01, 0xe3, 0x00},
-		{0x29, 0x01, 0xe3, 0x00}, {0x38, 0x01, 0xe3, 0x01},
-		{0x03, 0x01, 0xe5, 0x00}, {0x06, 0x01, 0xe5, 0x00},
-		{0x0a, 0x01, 0xe5, 0x00}, {0x0f, 0x01, 0xe5, 0x00},
-		{0x18, 0x01, 0xe5, 0x00}, {0x1f, 0x01, 0xe5, 0x00},
-		{0x29, 0x01, 0xe5, 0x00}, {0x38, 0x01, 0xe5, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xe6, 0x00}, {0x16, 0x01, 0xe6, 0x01},
-		{0x00, 0x01, 0x81, 0x01}, {0x00, 0x01, 0x84, 0x01},
-		{0x00, 0x01, 0x85, 0x01}, {0x00, 0x01, 0x86, 0x01},
-		{0x00, 0x01, 0x88, 0x01}, {0x00, 0x01, 0x92, 0x01},
-		{0x00, 0x01, 0x9a, 0x01}, {0x00, 0x01, 0x9c, 0x01},
-		{0x00, 0x01, 0xa0, 0x01}, {0x00, 0x01, 0xa3, 0x01},
-		{0x00, 0x01, 0xa4, 0x01}, {0x00, 0x01, 0xa9, 0x01},
-		{0x00, 0x01, 0xaa, 0x01}, {0x00, 0x01, 0xad, 0x01}
-	],
-	/* 120 */
-	[
-		{0x02, 0x01, 0xe6, 0x00}, {0x09, 0x01, 0xe6, 0x00},
-		{0x17, 0x01, 0xe6, 0x00}, {0x28, 0x01, 0xe6, 0x01},
-		{0x01, 0x01, 0x81, 0x00}, {0x16, 0x01, 0x81, 0x01},
-		{0x01, 0x01, 0x84, 0x00}, {0x16, 0x01, 0x84, 0x01},
-		{0x01, 0x01, 0x85, 0x00}, {0x16, 0x01, 0x85, 0x01},
-		{0x01, 0x01, 0x86, 0x00}, {0x16, 0x01, 0x86, 0x01},
-		{0x01, 0x01, 0x88, 0x00}, {0x16, 0x01, 0x88, 0x01},
-		{0x01, 0x01, 0x92, 0x00}, {0x16, 0x01, 0x92, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xe6, 0x00}, {0x06, 0x01, 0xe6, 0x00},
-		{0x0a, 0x01, 0xe6, 0x00}, {0x0f, 0x01, 0xe6, 0x00},
-		{0x18, 0x01, 0xe6, 0x00}, {0x1f, 0x01, 0xe6, 0x00},
-		{0x29, 0x01, 0xe6, 0x00}, {0x38, 0x01, 0xe6, 0x01},
-		{0x02, 0x01, 0x81, 0x00}, {0x09, 0x01, 0x81, 0x00},
-		{0x17, 0x01, 0x81, 0x00}, {0x28, 0x01, 0x81, 0x01},
-		{0x02, 0x01, 0x84, 0x00}, {0x09, 0x01, 0x84, 0x00},
-		{0x17, 0x01, 0x84, 0x00}, {0x28, 0x01, 0x84, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x81, 0x00}, {0x06, 0x01, 0x81, 0x00},
-		{0x0a, 0x01, 0x81, 0x00}, {0x0f, 0x01, 0x81, 0x00},
-		{0x18, 0x01, 0x81, 0x00}, {0x1f, 0x01, 0x81, 0x00},
-		{0x29, 0x01, 0x81, 0x00}, {0x38, 0x01, 0x81, 0x01},
-		{0x03, 0x01, 0x84, 0x00}, {0x06, 0x01, 0x84, 0x00},
-		{0x0a, 0x01, 0x84, 0x00}, {0x0f, 0x01, 0x84, 0x00},
-		{0x18, 0x01, 0x84, 0x00}, {0x1f, 0x01, 0x84, 0x00},
-		{0x29, 0x01, 0x84, 0x00}, {0x38, 0x01, 0x84, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x85, 0x00}, {0x09, 0x01, 0x85, 0x00},
-		{0x17, 0x01, 0x85, 0x00}, {0x28, 0x01, 0x85, 0x01},
-		{0x02, 0x01, 0x86, 0x00}, {0x09, 0x01, 0x86, 0x00},
-		{0x17, 0x01, 0x86, 0x00}, {0x28, 0x01, 0x86, 0x01},
-		{0x02, 0x01, 0x88, 0x00}, {0x09, 0x01, 0x88, 0x00},
-		{0x17, 0x01, 0x88, 0x00}, {0x28, 0x01, 0x88, 0x01},
-		{0x02, 0x01, 0x92, 0x00}, {0x09, 0x01, 0x92, 0x00},
-		{0x17, 0x01, 0x92, 0x00}, {0x28, 0x01, 0x92, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x85, 0x00}, {0x06, 0x01, 0x85, 0x00},
-		{0x0a, 0x01, 0x85, 0x00}, {0x0f, 0x01, 0x85, 0x00},
-		{0x18, 0x01, 0x85, 0x00}, {0x1f, 0x01, 0x85, 0x00},
-		{0x29, 0x01, 0x85, 0x00}, {0x38, 0x01, 0x85, 0x01},
-		{0x03, 0x01, 0x86, 0x00}, {0x06, 0x01, 0x86, 0x00},
-		{0x0a, 0x01, 0x86, 0x00}, {0x0f, 0x01, 0x86, 0x00},
-		{0x18, 0x01, 0x86, 0x00}, {0x1f, 0x01, 0x86, 0x00},
-		{0x29, 0x01, 0x86, 0x00}, {0x38, 0x01, 0x86, 0x01}
-	],
-	/* 125 */
-	[
-		{0x03, 0x01, 0x88, 0x00}, {0x06, 0x01, 0x88, 0x00},
-		{0x0a, 0x01, 0x88, 0x00}, {0x0f, 0x01, 0x88, 0x00},
-		{0x18, 0x01, 0x88, 0x00}, {0x1f, 0x01, 0x88, 0x00},
-		{0x29, 0x01, 0x88, 0x00}, {0x38, 0x01, 0x88, 0x01},
-		{0x03, 0x01, 0x92, 0x00}, {0x06, 0x01, 0x92, 0x00},
-		{0x0a, 0x01, 0x92, 0x00}, {0x0f, 0x01, 0x92, 0x00},
-		{0x18, 0x01, 0x92, 0x00}, {0x1f, 0x01, 0x92, 0x00},
-		{0x29, 0x01, 0x92, 0x00}, {0x38, 0x01, 0x92, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x9a, 0x00}, {0x16, 0x01, 0x9a, 0x01},
-		{0x01, 0x01, 0x9c, 0x00}, {0x16, 0x01, 0x9c, 0x01},
-		{0x01, 0x01, 0xa0, 0x00}, {0x16, 0x01, 0xa0, 0x01},
-		{0x01, 0x01, 0xa3, 0x00}, {0x16, 0x01, 0xa3, 0x01},
-		{0x01, 0x01, 0xa4, 0x00}, {0x16, 0x01, 0xa4, 0x01},
-		{0x01, 0x01, 0xa9, 0x00}, {0x16, 0x01, 0xa9, 0x01},
-		{0x01, 0x01, 0xaa, 0x00}, {0x16, 0x01, 0xaa, 0x01},
-		{0x01, 0x01, 0xad, 0x00}, {0x16, 0x01, 0xad, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x9a, 0x00}, {0x09, 0x01, 0x9a, 0x00},
-		{0x17, 0x01, 0x9a, 0x00}, {0x28, 0x01, 0x9a, 0x01},
-		{0x02, 0x01, 0x9c, 0x00}, {0x09, 0x01, 0x9c, 0x00},
-		{0x17, 0x01, 0x9c, 0x00}, {0x28, 0x01, 0x9c, 0x01},
-		{0x02, 0x01, 0xa0, 0x00}, {0x09, 0x01, 0xa0, 0x00},
-		{0x17, 0x01, 0xa0, 0x00}, {0x28, 0x01, 0xa0, 0x01},
-		{0x02, 0x01, 0xa3, 0x00}, {0x09, 0x01, 0xa3, 0x00},
-		{0x17, 0x01, 0xa3, 0x00}, {0x28, 0x01, 0xa3, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x9a, 0x00}, {0x06, 0x01, 0x9a, 0x00},
-		{0x0a, 0x01, 0x9a, 0x00}, {0x0f, 0x01, 0x9a, 0x00},
-		{0x18, 0x01, 0x9a, 0x00}, {0x1f, 0x01, 0x9a, 0x00},
-		{0x29, 0x01, 0x9a, 0x00}, {0x38, 0x01, 0x9a, 0x01},
-		{0x03, 0x01, 0x9c, 0x00}, {0x06, 0x01, 0x9c, 0x00},
-		{0x0a, 0x01, 0x9c, 0x00}, {0x0f, 0x01, 0x9c, 0x00},
-		{0x18, 0x01, 0x9c, 0x00}, {0x1f, 0x01, 0x9c, 0x00},
-		{0x29, 0x01, 0x9c, 0x00}, {0x38, 0x01, 0x9c, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xa0, 0x00}, {0x06, 0x01, 0xa0, 0x00},
-		{0x0a, 0x01, 0xa0, 0x00}, {0x0f, 0x01, 0xa0, 0x00},
-		{0x18, 0x01, 0xa0, 0x00}, {0x1f, 0x01, 0xa0, 0x00},
-		{0x29, 0x01, 0xa0, 0x00}, {0x38, 0x01, 0xa0, 0x01},
-		{0x03, 0x01, 0xa3, 0x00}, {0x06, 0x01, 0xa3, 0x00},
-		{0x0a, 0x01, 0xa3, 0x00}, {0x0f, 0x01, 0xa3, 0x00},
-		{0x18, 0x01, 0xa3, 0x00}, {0x1f, 0x01, 0xa3, 0x00},
-		{0x29, 0x01, 0xa3, 0x00}, {0x38, 0x01, 0xa3, 0x01}
-	],
-	/* 130 */
-	[
-		{0x02, 0x01, 0xa4, 0x00}, {0x09, 0x01, 0xa4, 0x00},
-		{0x17, 0x01, 0xa4, 0x00}, {0x28, 0x01, 0xa4, 0x01},
-		{0x02, 0x01, 0xa9, 0x00}, {0x09, 0x01, 0xa9, 0x00},
-		{0x17, 0x01, 0xa9, 0x00}, {0x28, 0x01, 0xa9, 0x01},
-		{0x02, 0x01, 0xaa, 0x00}, {0x09, 0x01, 0xaa, 0x00},
-		{0x17, 0x01, 0xaa, 0x00}, {0x28, 0x01, 0xaa, 0x01},
-		{0x02, 0x01, 0xad, 0x00}, {0x09, 0x01, 0xad, 0x00},
-		{0x17, 0x01, 0xad, 0x00}, {0x28, 0x01, 0xad, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xa4, 0x00}, {0x06, 0x01, 0xa4, 0x00},
-		{0x0a, 0x01, 0xa4, 0x00}, {0x0f, 0x01, 0xa4, 0x00},
-		{0x18, 0x01, 0xa4, 0x00}, {0x1f, 0x01, 0xa4, 0x00},
-		{0x29, 0x01, 0xa4, 0x00}, {0x38, 0x01, 0xa4, 0x01},
-		{0x03, 0x01, 0xa9, 0x00}, {0x06, 0x01, 0xa9, 0x00},
-		{0x0a, 0x01, 0xa9, 0x00}, {0x0f, 0x01, 0xa9, 0x00},
-		{0x18, 0x01, 0xa9, 0x00}, {0x1f, 0x01, 0xa9, 0x00},
-		{0x29, 0x01, 0xa9, 0x00}, {0x38, 0x01, 0xa9, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xaa, 0x00}, {0x06, 0x01, 0xaa, 0x00},
-		{0x0a, 0x01, 0xaa, 0x00}, {0x0f, 0x01, 0xaa, 0x00},
-		{0x18, 0x01, 0xaa, 0x00}, {0x1f, 0x01, 0xaa, 0x00},
-		{0x29, 0x01, 0xaa, 0x00}, {0x38, 0x01, 0xaa, 0x01},
-		{0x03, 0x01, 0xad, 0x00}, {0x06, 0x01, 0xad, 0x00},
-		{0x0a, 0x01, 0xad, 0x00}, {0x0f, 0x01, 0xad, 0x00},
-		{0x18, 0x01, 0xad, 0x00}, {0x1f, 0x01, 0xad, 0x00},
-		{0x29, 0x01, 0xad, 0x00}, {0x38, 0x01, 0xad, 0x01}
-	],
-	[
-		{0x89, 0x00, 0x00, 0x00}, {0x8a, 0x00, 0x00, 0x00},
-		{0x8c, 0x00, 0x00, 0x00}, {0x8d, 0x00, 0x00, 0x00},
-		{0x90, 0x00, 0x00, 0x00}, {0x91, 0x00, 0x00, 0x00},
-		{0x93, 0x00, 0x00, 0x00}, {0x96, 0x00, 0x00, 0x00},
-		{0x9c, 0x00, 0x00, 0x00}, {0x9f, 0x00, 0x00, 0x00},
-		{0xa3, 0x00, 0x00, 0x00}, {0xa6, 0x00, 0x00, 0x00},
-		{0xab, 0x00, 0x00, 0x00}, {0xae, 0x00, 0x00, 0x00},
-		{0xb5, 0x00, 0x00, 0x00}, {0xbe, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0xb2, 0x01}, {0x00, 0x01, 0xb5, 0x01},
-		{0x00, 0x01, 0xb9, 0x01}, {0x00, 0x01, 0xba, 0x01},
-		{0x00, 0x01, 0xbb, 0x01}, {0x00, 0x01, 0xbd, 0x01},
-		{0x00, 0x01, 0xbe, 0x01}, {0x00, 0x01, 0xc4, 0x01},
-		{0x00, 0x01, 0xc6, 0x01}, {0x00, 0x01, 0xe4, 0x01},
-		{0x00, 0x01, 0xe8, 0x01}, {0x00, 0x01, 0xe9, 0x01},
-		{0x94, 0x00, 0x00, 0x00}, {0x95, 0x00, 0x00, 0x00},
-		{0x97, 0x00, 0x00, 0x00}, {0x98, 0x00, 0x00, 0x00}
-	],
-	/* 135 */
-	[
-		{0x01, 0x01, 0xb2, 0x00}, {0x16, 0x01, 0xb2, 0x01},
-		{0x01, 0x01, 0xb5, 0x00}, {0x16, 0x01, 0xb5, 0x01},
-		{0x01, 0x01, 0xb9, 0x00}, {0x16, 0x01, 0xb9, 0x01},
-		{0x01, 0x01, 0xba, 0x00}, {0x16, 0x01, 0xba, 0x01},
-		{0x01, 0x01, 0xbb, 0x00}, {0x16, 0x01, 0xbb, 0x01},
-		{0x01, 0x01, 0xbd, 0x00}, {0x16, 0x01, 0xbd, 0x01},
-		{0x01, 0x01, 0xbe, 0x00}, {0x16, 0x01, 0xbe, 0x01},
-		{0x01, 0x01, 0xc4, 0x00}, {0x16, 0x01, 0xc4, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xb2, 0x00}, {0x09, 0x01, 0xb2, 0x00},
-		{0x17, 0x01, 0xb2, 0x00}, {0x28, 0x01, 0xb2, 0x01},
-		{0x02, 0x01, 0xb5, 0x00}, {0x09, 0x01, 0xb5, 0x00},
-		{0x17, 0x01, 0xb5, 0x00}, {0x28, 0x01, 0xb5, 0x01},
-		{0x02, 0x01, 0xb9, 0x00}, {0x09, 0x01, 0xb9, 0x00},
-		{0x17, 0x01, 0xb9, 0x00}, {0x28, 0x01, 0xb9, 0x01},
-		{0x02, 0x01, 0xba, 0x00}, {0x09, 0x01, 0xba, 0x00},
-		{0x17, 0x01, 0xba, 0x00}, {0x28, 0x01, 0xba, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xb2, 0x00}, {0x06, 0x01, 0xb2, 0x00},
-		{0x0a, 0x01, 0xb2, 0x00}, {0x0f, 0x01, 0xb2, 0x00},
-		{0x18, 0x01, 0xb2, 0x00}, {0x1f, 0x01, 0xb2, 0x00},
-		{0x29, 0x01, 0xb2, 0x00}, {0x38, 0x01, 0xb2, 0x01},
-		{0x03, 0x01, 0xb5, 0x00}, {0x06, 0x01, 0xb5, 0x00},
-		{0x0a, 0x01, 0xb5, 0x00}, {0x0f, 0x01, 0xb5, 0x00},
-		{0x18, 0x01, 0xb5, 0x00}, {0x1f, 0x01, 0xb5, 0x00},
-		{0x29, 0x01, 0xb5, 0x00}, {0x38, 0x01, 0xb5, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xb9, 0x00}, {0x06, 0x01, 0xb9, 0x00},
-		{0x0a, 0x01, 0xb9, 0x00}, {0x0f, 0x01, 0xb9, 0x00},
-		{0x18, 0x01, 0xb9, 0x00}, {0x1f, 0x01, 0xb9, 0x00},
-		{0x29, 0x01, 0xb9, 0x00}, {0x38, 0x01, 0xb9, 0x01},
-		{0x03, 0x01, 0xba, 0x00}, {0x06, 0x01, 0xba, 0x00},
-		{0x0a, 0x01, 0xba, 0x00}, {0x0f, 0x01, 0xba, 0x00},
-		{0x18, 0x01, 0xba, 0x00}, {0x1f, 0x01, 0xba, 0x00},
-		{0x29, 0x01, 0xba, 0x00}, {0x38, 0x01, 0xba, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xbb, 0x00}, {0x09, 0x01, 0xbb, 0x00},
-		{0x17, 0x01, 0xbb, 0x00}, {0x28, 0x01, 0xbb, 0x01},
-		{0x02, 0x01, 0xbd, 0x00}, {0x09, 0x01, 0xbd, 0x00},
-		{0x17, 0x01, 0xbd, 0x00}, {0x28, 0x01, 0xbd, 0x01},
-		{0x02, 0x01, 0xbe, 0x00}, {0x09, 0x01, 0xbe, 0x00},
-		{0x17, 0x01, 0xbe, 0x00}, {0x28, 0x01, 0xbe, 0x01},
-		{0x02, 0x01, 0xc4, 0x00}, {0x09, 0x01, 0xc4, 0x00},
-		{0x17, 0x01, 0xc4, 0x00}, {0x28, 0x01, 0xc4, 0x01}
-	],
-	/* 140 */
-	[
-		{0x03, 0x01, 0xbb, 0x00}, {0x06, 0x01, 0xbb, 0x00},
-		{0x0a, 0x01, 0xbb, 0x00}, {0x0f, 0x01, 0xbb, 0x00},
-		{0x18, 0x01, 0xbb, 0x00}, {0x1f, 0x01, 0xbb, 0x00},
-		{0x29, 0x01, 0xbb, 0x00}, {0x38, 0x01, 0xbb, 0x01},
-		{0x03, 0x01, 0xbd, 0x00}, {0x06, 0x01, 0xbd, 0x00},
-		{0x0a, 0x01, 0xbd, 0x00}, {0x0f, 0x01, 0xbd, 0x00},
-		{0x18, 0x01, 0xbd, 0x00}, {0x1f, 0x01, 0xbd, 0x00},
-		{0x29, 0x01, 0xbd, 0x00}, {0x38, 0x01, 0xbd, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xbe, 0x00}, {0x06, 0x01, 0xbe, 0x00},
-		{0x0a, 0x01, 0xbe, 0x00}, {0x0f, 0x01, 0xbe, 0x00},
-		{0x18, 0x01, 0xbe, 0x00}, {0x1f, 0x01, 0xbe, 0x00},
-		{0x29, 0x01, 0xbe, 0x00}, {0x38, 0x01, 0xbe, 0x01},
-		{0x03, 0x01, 0xc4, 0x00}, {0x06, 0x01, 0xc4, 0x00},
-		{0x0a, 0x01, 0xc4, 0x00}, {0x0f, 0x01, 0xc4, 0x00},
-		{0x18, 0x01, 0xc4, 0x00}, {0x1f, 0x01, 0xc4, 0x00},
-		{0x29, 0x01, 0xc4, 0x00}, {0x38, 0x01, 0xc4, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xc6, 0x00}, {0x16, 0x01, 0xc6, 0x01},
-		{0x01, 0x01, 0xe4, 0x00}, {0x16, 0x01, 0xe4, 0x01},
-		{0x01, 0x01, 0xe8, 0x00}, {0x16, 0x01, 0xe8, 0x01},
-		{0x01, 0x01, 0xe9, 0x00}, {0x16, 0x01, 0xe9, 0x01},
-		{0x00, 0x01, 0x01, 0x01}, {0x00, 0x01, 0x87, 0x01},
-		{0x00, 0x01, 0x89, 0x01}, {0x00, 0x01, 0x8a, 0x01},
-		{0x00, 0x01, 0x8b, 0x01}, {0x00, 0x01, 0x8c, 0x01},
-		{0x00, 0x01, 0x8d, 0x01}, {0x00, 0x01, 0x8f, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xc6, 0x00}, {0x09, 0x01, 0xc6, 0x00},
-		{0x17, 0x01, 0xc6, 0x00}, {0x28, 0x01, 0xc6, 0x01},
-		{0x02, 0x01, 0xe4, 0x00}, {0x09, 0x01, 0xe4, 0x00},
-		{0x17, 0x01, 0xe4, 0x00}, {0x28, 0x01, 0xe4, 0x01},
-		{0x02, 0x01, 0xe8, 0x00}, {0x09, 0x01, 0xe8, 0x00},
-		{0x17, 0x01, 0xe8, 0x00}, {0x28, 0x01, 0xe8, 0x01},
-		{0x02, 0x01, 0xe9, 0x00}, {0x09, 0x01, 0xe9, 0x00},
-		{0x17, 0x01, 0xe9, 0x00}, {0x28, 0x01, 0xe9, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xc6, 0x00}, {0x06, 0x01, 0xc6, 0x00},
-		{0x0a, 0x01, 0xc6, 0x00}, {0x0f, 0x01, 0xc6, 0x00},
-		{0x18, 0x01, 0xc6, 0x00}, {0x1f, 0x01, 0xc6, 0x00},
-		{0x29, 0x01, 0xc6, 0x00}, {0x38, 0x01, 0xc6, 0x01},
-		{0x03, 0x01, 0xe4, 0x00}, {0x06, 0x01, 0xe4, 0x00},
-		{0x0a, 0x01, 0xe4, 0x00}, {0x0f, 0x01, 0xe4, 0x00},
-		{0x18, 0x01, 0xe4, 0x00}, {0x1f, 0x01, 0xe4, 0x00},
-		{0x29, 0x01, 0xe4, 0x00}, {0x38, 0x01, 0xe4, 0x01}
-	],
-	/* 145 */
-	[
-		{0x03, 0x01, 0xe8, 0x00}, {0x06, 0x01, 0xe8, 0x00},
-		{0x0a, 0x01, 0xe8, 0x00}, {0x0f, 0x01, 0xe8, 0x00},
-		{0x18, 0x01, 0xe8, 0x00}, {0x1f, 0x01, 0xe8, 0x00},
-		{0x29, 0x01, 0xe8, 0x00}, {0x38, 0x01, 0xe8, 0x01},
-		{0x03, 0x01, 0xe9, 0x00}, {0x06, 0x01, 0xe9, 0x00},
-		{0x0a, 0x01, 0xe9, 0x00}, {0x0f, 0x01, 0xe9, 0x00},
-		{0x18, 0x01, 0xe9, 0x00}, {0x1f, 0x01, 0xe9, 0x00},
-		{0x29, 0x01, 0xe9, 0x00}, {0x38, 0x01, 0xe9, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x01, 0x00}, {0x16, 0x01, 0x01, 0x01},
-		{0x01, 0x01, 0x87, 0x00}, {0x16, 0x01, 0x87, 0x01},
-		{0x01, 0x01, 0x89, 0x00}, {0x16, 0x01, 0x89, 0x01},
-		{0x01, 0x01, 0x8a, 0x00}, {0x16, 0x01, 0x8a, 0x01},
-		{0x01, 0x01, 0x8b, 0x00}, {0x16, 0x01, 0x8b, 0x01},
-		{0x01, 0x01, 0x8c, 0x00}, {0x16, 0x01, 0x8c, 0x01},
-		{0x01, 0x01, 0x8d, 0x00}, {0x16, 0x01, 0x8d, 0x01},
-		{0x01, 0x01, 0x8f, 0x00}, {0x16, 0x01, 0x8f, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x01, 0x00}, {0x09, 0x01, 0x01, 0x00},
-		{0x17, 0x01, 0x01, 0x00}, {0x28, 0x01, 0x01, 0x01},
-		{0x02, 0x01, 0x87, 0x00}, {0x09, 0x01, 0x87, 0x00},
-		{0x17, 0x01, 0x87, 0x00}, {0x28, 0x01, 0x87, 0x01},
-		{0x02, 0x01, 0x89, 0x00}, {0x09, 0x01, 0x89, 0x00},
-		{0x17, 0x01, 0x89, 0x00}, {0x28, 0x01, 0x89, 0x01},
-		{0x02, 0x01, 0x8a, 0x00}, {0x09, 0x01, 0x8a, 0x00},
-		{0x17, 0x01, 0x8a, 0x00}, {0x28, 0x01, 0x8a, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x01, 0x00}, {0x06, 0x01, 0x01, 0x00},
-		{0x0a, 0x01, 0x01, 0x00}, {0x0f, 0x01, 0x01, 0x00},
-		{0x18, 0x01, 0x01, 0x00}, {0x1f, 0x01, 0x01, 0x00},
-		{0x29, 0x01, 0x01, 0x00}, {0x38, 0x01, 0x01, 0x01},
-		{0x03, 0x01, 0x87, 0x00}, {0x06, 0x01, 0x87, 0x00},
-		{0x0a, 0x01, 0x87, 0x00}, {0x0f, 0x01, 0x87, 0x00},
-		{0x18, 0x01, 0x87, 0x00}, {0x1f, 0x01, 0x87, 0x00},
-		{0x29, 0x01, 0x87, 0x00}, {0x38, 0x01, 0x87, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x89, 0x00}, {0x06, 0x01, 0x89, 0x00},
-		{0x0a, 0x01, 0x89, 0x00}, {0x0f, 0x01, 0x89, 0x00},
-		{0x18, 0x01, 0x89, 0x00}, {0x1f, 0x01, 0x89, 0x00},
-		{0x29, 0x01, 0x89, 0x00}, {0x38, 0x01, 0x89, 0x01},
-		{0x03, 0x01, 0x8a, 0x00}, {0x06, 0x01, 0x8a, 0x00},
-		{0x0a, 0x01, 0x8a, 0x00}, {0x0f, 0x01, 0x8a, 0x00},
-		{0x18, 0x01, 0x8a, 0x00}, {0x1f, 0x01, 0x8a, 0x00},
-		{0x29, 0x01, 0x8a, 0x00}, {0x38, 0x01, 0x8a, 0x01}
-	],
-	/* 150 */
-	[
-		{0x02, 0x01, 0x8b, 0x00}, {0x09, 0x01, 0x8b, 0x00},
-		{0x17, 0x01, 0x8b, 0x00}, {0x28, 0x01, 0x8b, 0x01},
-		{0x02, 0x01, 0x8c, 0x00}, {0x09, 0x01, 0x8c, 0x00},
-		{0x17, 0x01, 0x8c, 0x00}, {0x28, 0x01, 0x8c, 0x01},
-		{0x02, 0x01, 0x8d, 0x00}, {0x09, 0x01, 0x8d, 0x00},
-		{0x17, 0x01, 0x8d, 0x00}, {0x28, 0x01, 0x8d, 0x01},
-		{0x02, 0x01, 0x8f, 0x00}, {0x09, 0x01, 0x8f, 0x00},
-		{0x17, 0x01, 0x8f, 0x00}, {0x28, 0x01, 0x8f, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x8b, 0x00}, {0x06, 0x01, 0x8b, 0x00},
-		{0x0a, 0x01, 0x8b, 0x00}, {0x0f, 0x01, 0x8b, 0x00},
-		{0x18, 0x01, 0x8b, 0x00}, {0x1f, 0x01, 0x8b, 0x00},
-		{0x29, 0x01, 0x8b, 0x00}, {0x38, 0x01, 0x8b, 0x01},
-		{0x03, 0x01, 0x8c, 0x00}, {0x06, 0x01, 0x8c, 0x00},
-		{0x0a, 0x01, 0x8c, 0x00}, {0x0f, 0x01, 0x8c, 0x00},
-		{0x18, 0x01, 0x8c, 0x00}, {0x1f, 0x01, 0x8c, 0x00},
-		{0x29, 0x01, 0x8c, 0x00}, {0x38, 0x01, 0x8c, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x8d, 0x00}, {0x06, 0x01, 0x8d, 0x00},
-		{0x0a, 0x01, 0x8d, 0x00}, {0x0f, 0x01, 0x8d, 0x00},
-		{0x18, 0x01, 0x8d, 0x00}, {0x1f, 0x01, 0x8d, 0x00},
-		{0x29, 0x01, 0x8d, 0x00}, {0x38, 0x01, 0x8d, 0x01},
-		{0x03, 0x01, 0x8f, 0x00}, {0x06, 0x01, 0x8f, 0x00},
-		{0x0a, 0x01, 0x8f, 0x00}, {0x0f, 0x01, 0x8f, 0x00},
-		{0x18, 0x01, 0x8f, 0x00}, {0x1f, 0x01, 0x8f, 0x00},
-		{0x29, 0x01, 0x8f, 0x00}, {0x38, 0x01, 0x8f, 0x01}
-	],
-	[
-		{0x9d, 0x00, 0x00, 0x00}, {0x9e, 0x00, 0x00, 0x00},
-		{0xa0, 0x00, 0x00, 0x00}, {0xa1, 0x00, 0x00, 0x00},
-		{0xa4, 0x00, 0x00, 0x00}, {0xa5, 0x00, 0x00, 0x00},
-		{0xa7, 0x00, 0x00, 0x00}, {0xa8, 0x00, 0x00, 0x00},
-		{0xac, 0x00, 0x00, 0x00}, {0xad, 0x00, 0x00, 0x00},
-		{0xaf, 0x00, 0x00, 0x00}, {0xb1, 0x00, 0x00, 0x00},
-		{0xb6, 0x00, 0x00, 0x00}, {0xb9, 0x00, 0x00, 0x00},
-		{0xbf, 0x00, 0x00, 0x00}, {0xcf, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0x93, 0x01}, {0x00, 0x01, 0x95, 0x01},
-		{0x00, 0x01, 0x96, 0x01}, {0x00, 0x01, 0x97, 0x01},
-		{0x00, 0x01, 0x98, 0x01}, {0x00, 0x01, 0x9b, 0x01},
-		{0x00, 0x01, 0x9d, 0x01}, {0x00, 0x01, 0x9e, 0x01},
-		{0x00, 0x01, 0xa5, 0x01}, {0x00, 0x01, 0xa6, 0x01},
-		{0x00, 0x01, 0xa8, 0x01}, {0x00, 0x01, 0xae, 0x01},
-		{0x00, 0x01, 0xaf, 0x01}, {0x00, 0x01, 0xb4, 0x01},
-		{0x00, 0x01, 0xb6, 0x01}, {0x00, 0x01, 0xb7, 0x01}
-	],
-	/* 155 */
-	[
-		{0x01, 0x01, 0x93, 0x00}, {0x16, 0x01, 0x93, 0x01},
-		{0x01, 0x01, 0x95, 0x00}, {0x16, 0x01, 0x95, 0x01},
-		{0x01, 0x01, 0x96, 0x00}, {0x16, 0x01, 0x96, 0x01},
-		{0x01, 0x01, 0x97, 0x00}, {0x16, 0x01, 0x97, 0x01},
-		{0x01, 0x01, 0x98, 0x00}, {0x16, 0x01, 0x98, 0x01},
-		{0x01, 0x01, 0x9b, 0x00}, {0x16, 0x01, 0x9b, 0x01},
-		{0x01, 0x01, 0x9d, 0x00}, {0x16, 0x01, 0x9d, 0x01},
-		{0x01, 0x01, 0x9e, 0x00}, {0x16, 0x01, 0x9e, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x93, 0x00}, {0x09, 0x01, 0x93, 0x00},
-		{0x17, 0x01, 0x93, 0x00}, {0x28, 0x01, 0x93, 0x01},
-		{0x02, 0x01, 0x95, 0x00}, {0x09, 0x01, 0x95, 0x00},
-		{0x17, 0x01, 0x95, 0x00}, {0x28, 0x01, 0x95, 0x01},
-		{0x02, 0x01, 0x96, 0x00}, {0x09, 0x01, 0x96, 0x00},
-		{0x17, 0x01, 0x96, 0x00}, {0x28, 0x01, 0x96, 0x01},
-		{0x02, 0x01, 0x97, 0x00}, {0x09, 0x01, 0x97, 0x00},
-		{0x17, 0x01, 0x97, 0x00}, {0x28, 0x01, 0x97, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x93, 0x00}, {0x06, 0x01, 0x93, 0x00},
-		{0x0a, 0x01, 0x93, 0x00}, {0x0f, 0x01, 0x93, 0x00},
-		{0x18, 0x01, 0x93, 0x00}, {0x1f, 0x01, 0x93, 0x00},
-		{0x29, 0x01, 0x93, 0x00}, {0x38, 0x01, 0x93, 0x01},
-		{0x03, 0x01, 0x95, 0x00}, {0x06, 0x01, 0x95, 0x00},
-		{0x0a, 0x01, 0x95, 0x00}, {0x0f, 0x01, 0x95, 0x00},
-		{0x18, 0x01, 0x95, 0x00}, {0x1f, 0x01, 0x95, 0x00},
-		{0x29, 0x01, 0x95, 0x00}, {0x38, 0x01, 0x95, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x96, 0x00}, {0x06, 0x01, 0x96, 0x00},
-		{0x0a, 0x01, 0x96, 0x00}, {0x0f, 0x01, 0x96, 0x00},
-		{0x18, 0x01, 0x96, 0x00}, {0x1f, 0x01, 0x96, 0x00},
-		{0x29, 0x01, 0x96, 0x00}, {0x38, 0x01, 0x96, 0x01},
-		{0x03, 0x01, 0x97, 0x00}, {0x06, 0x01, 0x97, 0x00},
-		{0x0a, 0x01, 0x97, 0x00}, {0x0f, 0x01, 0x97, 0x00},
-		{0x18, 0x01, 0x97, 0x00}, {0x1f, 0x01, 0x97, 0x00},
-		{0x29, 0x01, 0x97, 0x00}, {0x38, 0x01, 0x97, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x98, 0x00}, {0x09, 0x01, 0x98, 0x00},
-		{0x17, 0x01, 0x98, 0x00}, {0x28, 0x01, 0x98, 0x01},
-		{0x02, 0x01, 0x9b, 0x00}, {0x09, 0x01, 0x9b, 0x00},
-		{0x17, 0x01, 0x9b, 0x00}, {0x28, 0x01, 0x9b, 0x01},
-		{0x02, 0x01, 0x9d, 0x00}, {0x09, 0x01, 0x9d, 0x00},
-		{0x17, 0x01, 0x9d, 0x00}, {0x28, 0x01, 0x9d, 0x01},
-		{0x02, 0x01, 0x9e, 0x00}, {0x09, 0x01, 0x9e, 0x00},
-		{0x17, 0x01, 0x9e, 0x00}, {0x28, 0x01, 0x9e, 0x01}
-	],
-	/* 160 */
-	[
-		{0x03, 0x01, 0x98, 0x00}, {0x06, 0x01, 0x98, 0x00},
-		{0x0a, 0x01, 0x98, 0x00}, {0x0f, 0x01, 0x98, 0x00},
-		{0x18, 0x01, 0x98, 0x00}, {0x1f, 0x01, 0x98, 0x00},
-		{0x29, 0x01, 0x98, 0x00}, {0x38, 0x01, 0x98, 0x01},
-		{0x03, 0x01, 0x9b, 0x00}, {0x06, 0x01, 0x9b, 0x00},
-		{0x0a, 0x01, 0x9b, 0x00}, {0x0f, 0x01, 0x9b, 0x00},
-		{0x18, 0x01, 0x9b, 0x00}, {0x1f, 0x01, 0x9b, 0x00},
-		{0x29, 0x01, 0x9b, 0x00}, {0x38, 0x01, 0x9b, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x9d, 0x00}, {0x06, 0x01, 0x9d, 0x00},
-		{0x0a, 0x01, 0x9d, 0x00}, {0x0f, 0x01, 0x9d, 0x00},
-		{0x18, 0x01, 0x9d, 0x00}, {0x1f, 0x01, 0x9d, 0x00},
-		{0x29, 0x01, 0x9d, 0x00}, {0x38, 0x01, 0x9d, 0x01},
-		{0x03, 0x01, 0x9e, 0x00}, {0x06, 0x01, 0x9e, 0x00},
-		{0x0a, 0x01, 0x9e, 0x00}, {0x0f, 0x01, 0x9e, 0x00},
-		{0x18, 0x01, 0x9e, 0x00}, {0x1f, 0x01, 0x9e, 0x00},
-		{0x29, 0x01, 0x9e, 0x00}, {0x38, 0x01, 0x9e, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xa5, 0x00}, {0x16, 0x01, 0xa5, 0x01},
-		{0x01, 0x01, 0xa6, 0x00}, {0x16, 0x01, 0xa6, 0x01},
-		{0x01, 0x01, 0xa8, 0x00}, {0x16, 0x01, 0xa8, 0x01},
-		{0x01, 0x01, 0xae, 0x00}, {0x16, 0x01, 0xae, 0x01},
-		{0x01, 0x01, 0xaf, 0x00}, {0x16, 0x01, 0xaf, 0x01},
-		{0x01, 0x01, 0xb4, 0x00}, {0x16, 0x01, 0xb4, 0x01},
-		{0x01, 0x01, 0xb6, 0x00}, {0x16, 0x01, 0xb6, 0x01},
-		{0x01, 0x01, 0xb7, 0x00}, {0x16, 0x01, 0xb7, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xa5, 0x00}, {0x09, 0x01, 0xa5, 0x00},
-		{0x17, 0x01, 0xa5, 0x00}, {0x28, 0x01, 0xa5, 0x01},
-		{0x02, 0x01, 0xa6, 0x00}, {0x09, 0x01, 0xa6, 0x00},
-		{0x17, 0x01, 0xa6, 0x00}, {0x28, 0x01, 0xa6, 0x01},
-		{0x02, 0x01, 0xa8, 0x00}, {0x09, 0x01, 0xa8, 0x00},
-		{0x17, 0x01, 0xa8, 0x00}, {0x28, 0x01, 0xa8, 0x01},
-		{0x02, 0x01, 0xae, 0x00}, {0x09, 0x01, 0xae, 0x00},
-		{0x17, 0x01, 0xae, 0x00}, {0x28, 0x01, 0xae, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xa5, 0x00}, {0x06, 0x01, 0xa5, 0x00},
-		{0x0a, 0x01, 0xa5, 0x00}, {0x0f, 0x01, 0xa5, 0x00},
-		{0x18, 0x01, 0xa5, 0x00}, {0x1f, 0x01, 0xa5, 0x00},
-		{0x29, 0x01, 0xa5, 0x00}, {0x38, 0x01, 0xa5, 0x01},
-		{0x03, 0x01, 0xa6, 0x00}, {0x06, 0x01, 0xa6, 0x00},
-		{0x0a, 0x01, 0xa6, 0x00}, {0x0f, 0x01, 0xa6, 0x00},
-		{0x18, 0x01, 0xa6, 0x00}, {0x1f, 0x01, 0xa6, 0x00},
-		{0x29, 0x01, 0xa6, 0x00}, {0x38, 0x01, 0xa6, 0x01}
-	],
-	/* 165 */
-	[
-		{0x03, 0x01, 0xa8, 0x00}, {0x06, 0x01, 0xa8, 0x00},
-		{0x0a, 0x01, 0xa8, 0x00}, {0x0f, 0x01, 0xa8, 0x00},
-		{0x18, 0x01, 0xa8, 0x00}, {0x1f, 0x01, 0xa8, 0x00},
-		{0x29, 0x01, 0xa8, 0x00}, {0x38, 0x01, 0xa8, 0x01},
-		{0x03, 0x01, 0xae, 0x00}, {0x06, 0x01, 0xae, 0x00},
-		{0x0a, 0x01, 0xae, 0x00}, {0x0f, 0x01, 0xae, 0x00},
-		{0x18, 0x01, 0xae, 0x00}, {0x1f, 0x01, 0xae, 0x00},
-		{0x29, 0x01, 0xae, 0x00}, {0x38, 0x01, 0xae, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xaf, 0x00}, {0x09, 0x01, 0xaf, 0x00},
-		{0x17, 0x01, 0xaf, 0x00}, {0x28, 0x01, 0xaf, 0x01},
-		{0x02, 0x01, 0xb4, 0x00}, {0x09, 0x01, 0xb4, 0x00},
-		{0x17, 0x01, 0xb4, 0x00}, {0x28, 0x01, 0xb4, 0x01},
-		{0x02, 0x01, 0xb6, 0x00}, {0x09, 0x01, 0xb6, 0x00},
-		{0x17, 0x01, 0xb6, 0x00}, {0x28, 0x01, 0xb6, 0x01},
-		{0x02, 0x01, 0xb7, 0x00}, {0x09, 0x01, 0xb7, 0x00},
-		{0x17, 0x01, 0xb7, 0x00}, {0x28, 0x01, 0xb7, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xaf, 0x00}, {0x06, 0x01, 0xaf, 0x00},
-		{0x0a, 0x01, 0xaf, 0x00}, {0x0f, 0x01, 0xaf, 0x00},
-		{0x18, 0x01, 0xaf, 0x00}, {0x1f, 0x01, 0xaf, 0x00},
-		{0x29, 0x01, 0xaf, 0x00}, {0x38, 0x01, 0xaf, 0x01},
-		{0x03, 0x01, 0xb4, 0x00}, {0x06, 0x01, 0xb4, 0x00},
-		{0x0a, 0x01, 0xb4, 0x00}, {0x0f, 0x01, 0xb4, 0x00},
-		{0x18, 0x01, 0xb4, 0x00}, {0x1f, 0x01, 0xb4, 0x00},
-		{0x29, 0x01, 0xb4, 0x00}, {0x38, 0x01, 0xb4, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xb6, 0x00}, {0x06, 0x01, 0xb6, 0x00},
-		{0x0a, 0x01, 0xb6, 0x00}, {0x0f, 0x01, 0xb6, 0x00},
-		{0x18, 0x01, 0xb6, 0x00}, {0x1f, 0x01, 0xb6, 0x00},
-		{0x29, 0x01, 0xb6, 0x00}, {0x38, 0x01, 0xb6, 0x01},
-		{0x03, 0x01, 0xb7, 0x00}, {0x06, 0x01, 0xb7, 0x00},
-		{0x0a, 0x01, 0xb7, 0x00}, {0x0f, 0x01, 0xb7, 0x00},
-		{0x18, 0x01, 0xb7, 0x00}, {0x1f, 0x01, 0xb7, 0x00},
-		{0x29, 0x01, 0xb7, 0x00}, {0x38, 0x01, 0xb7, 0x01}
-	],
-	[
-		{0x00, 0x01, 0xbc, 0x01}, {0x00, 0x01, 0xbf, 0x01},
-		{0x00, 0x01, 0xc5, 0x01}, {0x00, 0x01, 0xe7, 0x01},
-		{0x00, 0x01, 0xef, 0x01}, {0xb0, 0x00, 0x00, 0x00},
-		{0xb2, 0x00, 0x00, 0x00}, {0xb3, 0x00, 0x00, 0x00},
-		{0xb7, 0x00, 0x00, 0x00}, {0xb8, 0x00, 0x00, 0x00},
-		{0xba, 0x00, 0x00, 0x00}, {0xbb, 0x00, 0x00, 0x00},
-		{0xc0, 0x00, 0x00, 0x00}, {0xc7, 0x00, 0x00, 0x00},
-		{0xd0, 0x00, 0x00, 0x00}, {0xdf, 0x00, 0x00, 0x01}
-	],
-	/* 170 */
-	[
-		{0x01, 0x01, 0xbc, 0x00}, {0x16, 0x01, 0xbc, 0x01},
-		{0x01, 0x01, 0xbf, 0x00}, {0x16, 0x01, 0xbf, 0x01},
-		{0x01, 0x01, 0xc5, 0x00}, {0x16, 0x01, 0xc5, 0x01},
-		{0x01, 0x01, 0xe7, 0x00}, {0x16, 0x01, 0xe7, 0x01},
-		{0x01, 0x01, 0xef, 0x00}, {0x16, 0x01, 0xef, 0x01},
-		{0x00, 0x01, 0x09, 0x01}, {0x00, 0x01, 0x8e, 0x01},
-		{0x00, 0x01, 0x90, 0x01}, {0x00, 0x01, 0x91, 0x01},
-		{0x00, 0x01, 0x94, 0x01}, {0x00, 0x01, 0x9f, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xbc, 0x00}, {0x09, 0x01, 0xbc, 0x00},
-		{0x17, 0x01, 0xbc, 0x00}, {0x28, 0x01, 0xbc, 0x01},
-		{0x02, 0x01, 0xbf, 0x00}, {0x09, 0x01, 0xbf, 0x00},
-		{0x17, 0x01, 0xbf, 0x00}, {0x28, 0x01, 0xbf, 0x01},
-		{0x02, 0x01, 0xc5, 0x00}, {0x09, 0x01, 0xc5, 0x00},
-		{0x17, 0x01, 0xc5, 0x00}, {0x28, 0x01, 0xc5, 0x01},
-		{0x02, 0x01, 0xe7, 0x00}, {0x09, 0x01, 0xe7, 0x00},
-		{0x17, 0x01, 0xe7, 0x00}, {0x28, 0x01, 0xe7, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xbc, 0x00}, {0x06, 0x01, 0xbc, 0x00},
-		{0x0a, 0x01, 0xbc, 0x00}, {0x0f, 0x01, 0xbc, 0x00},
-		{0x18, 0x01, 0xbc, 0x00}, {0x1f, 0x01, 0xbc, 0x00},
-		{0x29, 0x01, 0xbc, 0x00}, {0x38, 0x01, 0xbc, 0x01},
-		{0x03, 0x01, 0xbf, 0x00}, {0x06, 0x01, 0xbf, 0x00},
-		{0x0a, 0x01, 0xbf, 0x00}, {0x0f, 0x01, 0xbf, 0x00},
-		{0x18, 0x01, 0xbf, 0x00}, {0x1f, 0x01, 0xbf, 0x00},
-		{0x29, 0x01, 0xbf, 0x00}, {0x38, 0x01, 0xbf, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xc5, 0x00}, {0x06, 0x01, 0xc5, 0x00},
-		{0x0a, 0x01, 0xc5, 0x00}, {0x0f, 0x01, 0xc5, 0x00},
-		{0x18, 0x01, 0xc5, 0x00}, {0x1f, 0x01, 0xc5, 0x00},
-		{0x29, 0x01, 0xc5, 0x00}, {0x38, 0x01, 0xc5, 0x01},
-		{0x03, 0x01, 0xe7, 0x00}, {0x06, 0x01, 0xe7, 0x00},
-		{0x0a, 0x01, 0xe7, 0x00}, {0x0f, 0x01, 0xe7, 0x00},
-		{0x18, 0x01, 0xe7, 0x00}, {0x1f, 0x01, 0xe7, 0x00},
-		{0x29, 0x01, 0xe7, 0x00}, {0x38, 0x01, 0xe7, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xef, 0x00}, {0x09, 0x01, 0xef, 0x00},
-		{0x17, 0x01, 0xef, 0x00}, {0x28, 0x01, 0xef, 0x01},
-		{0x01, 0x01, 0x09, 0x00}, {0x16, 0x01, 0x09, 0x01},
-		{0x01, 0x01, 0x8e, 0x00}, {0x16, 0x01, 0x8e, 0x01},
-		{0x01, 0x01, 0x90, 0x00}, {0x16, 0x01, 0x90, 0x01},
-		{0x01, 0x01, 0x91, 0x00}, {0x16, 0x01, 0x91, 0x01},
-		{0x01, 0x01, 0x94, 0x00}, {0x16, 0x01, 0x94, 0x01},
-		{0x01, 0x01, 0x9f, 0x00}, {0x16, 0x01, 0x9f, 0x01}
-	],
-	/* 175 */
-	[
-		{0x03, 0x01, 0xef, 0x00}, {0x06, 0x01, 0xef, 0x00},
-		{0x0a, 0x01, 0xef, 0x00}, {0x0f, 0x01, 0xef, 0x00},
-		{0x18, 0x01, 0xef, 0x00}, {0x1f, 0x01, 0xef, 0x00},
-		{0x29, 0x01, 0xef, 0x00}, {0x38, 0x01, 0xef, 0x01},
-		{0x02, 0x01, 0x09, 0x00}, {0x09, 0x01, 0x09, 0x00},
-		{0x17, 0x01, 0x09, 0x00}, {0x28, 0x01, 0x09, 0x01},
-		{0x02, 0x01, 0x8e, 0x00}, {0x09, 0x01, 0x8e, 0x00},
-		{0x17, 0x01, 0x8e, 0x00}, {0x28, 0x01, 0x8e, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x09, 0x00}, {0x06, 0x01, 0x09, 0x00},
-		{0x0a, 0x01, 0x09, 0x00}, {0x0f, 0x01, 0x09, 0x00},
-		{0x18, 0x01, 0x09, 0x00}, {0x1f, 0x01, 0x09, 0x00},
-		{0x29, 0x01, 0x09, 0x00}, {0x38, 0x01, 0x09, 0x01},
-		{0x03, 0x01, 0x8e, 0x00}, {0x06, 0x01, 0x8e, 0x00},
-		{0x0a, 0x01, 0x8e, 0x00}, {0x0f, 0x01, 0x8e, 0x00},
-		{0x18, 0x01, 0x8e, 0x00}, {0x1f, 0x01, 0x8e, 0x00},
-		{0x29, 0x01, 0x8e, 0x00}, {0x38, 0x01, 0x8e, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x90, 0x00}, {0x09, 0x01, 0x90, 0x00},
-		{0x17, 0x01, 0x90, 0x00}, {0x28, 0x01, 0x90, 0x01},
-		{0x02, 0x01, 0x91, 0x00}, {0x09, 0x01, 0x91, 0x00},
-		{0x17, 0x01, 0x91, 0x00}, {0x28, 0x01, 0x91, 0x01},
-		{0x02, 0x01, 0x94, 0x00}, {0x09, 0x01, 0x94, 0x00},
-		{0x17, 0x01, 0x94, 0x00}, {0x28, 0x01, 0x94, 0x01},
-		{0x02, 0x01, 0x9f, 0x00}, {0x09, 0x01, 0x9f, 0x00},
-		{0x17, 0x01, 0x9f, 0x00}, {0x28, 0x01, 0x9f, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x90, 0x00}, {0x06, 0x01, 0x90, 0x00},
-		{0x0a, 0x01, 0x90, 0x00}, {0x0f, 0x01, 0x90, 0x00},
-		{0x18, 0x01, 0x90, 0x00}, {0x1f, 0x01, 0x90, 0x00},
-		{0x29, 0x01, 0x90, 0x00}, {0x38, 0x01, 0x90, 0x01},
-		{0x03, 0x01, 0x91, 0x00}, {0x06, 0x01, 0x91, 0x00},
-		{0x0a, 0x01, 0x91, 0x00}, {0x0f, 0x01, 0x91, 0x00},
-		{0x18, 0x01, 0x91, 0x00}, {0x1f, 0x01, 0x91, 0x00},
-		{0x29, 0x01, 0x91, 0x00}, {0x38, 0x01, 0x91, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x94, 0x00}, {0x06, 0x01, 0x94, 0x00},
-		{0x0a, 0x01, 0x94, 0x00}, {0x0f, 0x01, 0x94, 0x00},
-		{0x18, 0x01, 0x94, 0x00}, {0x1f, 0x01, 0x94, 0x00},
-		{0x29, 0x01, 0x94, 0x00}, {0x38, 0x01, 0x94, 0x01},
-		{0x03, 0x01, 0x9f, 0x00}, {0x06, 0x01, 0x9f, 0x00},
-		{0x0a, 0x01, 0x9f, 0x00}, {0x0f, 0x01, 0x9f, 0x00},
-		{0x18, 0x01, 0x9f, 0x00}, {0x1f, 0x01, 0x9f, 0x00},
-		{0x29, 0x01, 0x9f, 0x00}, {0x38, 0x01, 0x9f, 0x01}
-	],
-	/* 180 */
-	[
-		{0x00, 0x01, 0xab, 0x01}, {0x00, 0x01, 0xce, 0x01},
-		{0x00, 0x01, 0xd7, 0x01}, {0x00, 0x01, 0xe1, 0x01},
-		{0x00, 0x01, 0xec, 0x01}, {0x00, 0x01, 0xed, 0x01},
-		{0xbc, 0x00, 0x00, 0x00}, {0xbd, 0x00, 0x00, 0x00},
-		{0xc1, 0x00, 0x00, 0x00}, {0xc4, 0x00, 0x00, 0x00},
-		{0xc8, 0x00, 0x00, 0x00}, {0xcb, 0x00, 0x00, 0x00},
-		{0xd1, 0x00, 0x00, 0x00}, {0xd8, 0x00, 0x00, 0x00},
-		{0xe0, 0x00, 0x00, 0x00}, {0xee, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xab, 0x00}, {0x16, 0x01, 0xab, 0x01},
-		{0x01, 0x01, 0xce, 0x00}, {0x16, 0x01, 0xce, 0x01},
-		{0x01, 0x01, 0xd7, 0x00}, {0x16, 0x01, 0xd7, 0x01},
-		{0x01, 0x01, 0xe1, 0x00}, {0x16, 0x01, 0xe1, 0x01},
-		{0x01, 0x01, 0xec, 0x00}, {0x16, 0x01, 0xec, 0x01},
-		{0x01, 0x01, 0xed, 0x00}, {0x16, 0x01, 0xed, 0x01},
-		{0x00, 0x01, 0xc7, 0x01}, {0x00, 0x01, 0xcf, 0x01},
-		{0x00, 0x01, 0xea, 0x01}, {0x00, 0x01, 0xeb, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xab, 0x00}, {0x09, 0x01, 0xab, 0x00},
-		{0x17, 0x01, 0xab, 0x00}, {0x28, 0x01, 0xab, 0x01},
-		{0x02, 0x01, 0xce, 0x00}, {0x09, 0x01, 0xce, 0x00},
-		{0x17, 0x01, 0xce, 0x00}, {0x28, 0x01, 0xce, 0x01},
-		{0x02, 0x01, 0xd7, 0x00}, {0x09, 0x01, 0xd7, 0x00},
-		{0x17, 0x01, 0xd7, 0x00}, {0x28, 0x01, 0xd7, 0x01},
-		{0x02, 0x01, 0xe1, 0x00}, {0x09, 0x01, 0xe1, 0x00},
-		{0x17, 0x01, 0xe1, 0x00}, {0x28, 0x01, 0xe1, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xab, 0x00}, {0x06, 0x01, 0xab, 0x00},
-		{0x0a, 0x01, 0xab, 0x00}, {0x0f, 0x01, 0xab, 0x00},
-		{0x18, 0x01, 0xab, 0x00}, {0x1f, 0x01, 0xab, 0x00},
-		{0x29, 0x01, 0xab, 0x00}, {0x38, 0x01, 0xab, 0x01},
-		{0x03, 0x01, 0xce, 0x00}, {0x06, 0x01, 0xce, 0x00},
-		{0x0a, 0x01, 0xce, 0x00}, {0x0f, 0x01, 0xce, 0x00},
-		{0x18, 0x01, 0xce, 0x00}, {0x1f, 0x01, 0xce, 0x00},
-		{0x29, 0x01, 0xce, 0x00}, {0x38, 0x01, 0xce, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xd7, 0x00}, {0x06, 0x01, 0xd7, 0x00},
-		{0x0a, 0x01, 0xd7, 0x00}, {0x0f, 0x01, 0xd7, 0x00},
-		{0x18, 0x01, 0xd7, 0x00}, {0x1f, 0x01, 0xd7, 0x00},
-		{0x29, 0x01, 0xd7, 0x00}, {0x38, 0x01, 0xd7, 0x01},
-		{0x03, 0x01, 0xe1, 0x00}, {0x06, 0x01, 0xe1, 0x00},
-		{0x0a, 0x01, 0xe1, 0x00}, {0x0f, 0x01, 0xe1, 0x00},
-		{0x18, 0x01, 0xe1, 0x00}, {0x1f, 0x01, 0xe1, 0x00},
-		{0x29, 0x01, 0xe1, 0x00}, {0x38, 0x01, 0xe1, 0x01}
-	],
-	/* 185 */
-	[
-		{0x02, 0x01, 0xec, 0x00}, {0x09, 0x01, 0xec, 0x00},
-		{0x17, 0x01, 0xec, 0x00}, {0x28, 0x01, 0xec, 0x01},
-		{0x02, 0x01, 0xed, 0x00}, {0x09, 0x01, 0xed, 0x00},
-		{0x17, 0x01, 0xed, 0x00}, {0x28, 0x01, 0xed, 0x01},
-		{0x01, 0x01, 0xc7, 0x00}, {0x16, 0x01, 0xc7, 0x01},
-		{0x01, 0x01, 0xcf, 0x00}, {0x16, 0x01, 0xcf, 0x01},
-		{0x01, 0x01, 0xea, 0x00}, {0x16, 0x01, 0xea, 0x01},
-		{0x01, 0x01, 0xeb, 0x00}, {0x16, 0x01, 0xeb, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xec, 0x00}, {0x06, 0x01, 0xec, 0x00},
-		{0x0a, 0x01, 0xec, 0x00}, {0x0f, 0x01, 0xec, 0x00},
-		{0x18, 0x01, 0xec, 0x00}, {0x1f, 0x01, 0xec, 0x00},
-		{0x29, 0x01, 0xec, 0x00}, {0x38, 0x01, 0xec, 0x01},
-		{0x03, 0x01, 0xed, 0x00}, {0x06, 0x01, 0xed, 0x00},
-		{0x0a, 0x01, 0xed, 0x00}, {0x0f, 0x01, 0xed, 0x00},
-		{0x18, 0x01, 0xed, 0x00}, {0x1f, 0x01, 0xed, 0x00},
-		{0x29, 0x01, 0xed, 0x00}, {0x38, 0x01, 0xed, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xc7, 0x00}, {0x09, 0x01, 0xc7, 0x00},
-		{0x17, 0x01, 0xc7, 0x00}, {0x28, 0x01, 0xc7, 0x01},
-		{0x02, 0x01, 0xcf, 0x00}, {0x09, 0x01, 0xcf, 0x00},
-		{0x17, 0x01, 0xcf, 0x00}, {0x28, 0x01, 0xcf, 0x01},
-		{0x02, 0x01, 0xea, 0x00}, {0x09, 0x01, 0xea, 0x00},
-		{0x17, 0x01, 0xea, 0x00}, {0x28, 0x01, 0xea, 0x01},
-		{0x02, 0x01, 0xeb, 0x00}, {0x09, 0x01, 0xeb, 0x00},
-		{0x17, 0x01, 0xeb, 0x00}, {0x28, 0x01, 0xeb, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xc7, 0x00}, {0x06, 0x01, 0xc7, 0x00},
-		{0x0a, 0x01, 0xc7, 0x00}, {0x0f, 0x01, 0xc7, 0x00},
-		{0x18, 0x01, 0xc7, 0x00}, {0x1f, 0x01, 0xc7, 0x00},
-		{0x29, 0x01, 0xc7, 0x00}, {0x38, 0x01, 0xc7, 0x01},
-		{0x03, 0x01, 0xcf, 0x00}, {0x06, 0x01, 0xcf, 0x00},
-		{0x0a, 0x01, 0xcf, 0x00}, {0x0f, 0x01, 0xcf, 0x00},
-		{0x18, 0x01, 0xcf, 0x00}, {0x1f, 0x01, 0xcf, 0x00},
-		{0x29, 0x01, 0xcf, 0x00}, {0x38, 0x01, 0xcf, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xea, 0x00}, {0x06, 0x01, 0xea, 0x00},
-		{0x0a, 0x01, 0xea, 0x00}, {0x0f, 0x01, 0xea, 0x00},
-		{0x18, 0x01, 0xea, 0x00}, {0x1f, 0x01, 0xea, 0x00},
-		{0x29, 0x01, 0xea, 0x00}, {0x38, 0x01, 0xea, 0x01},
-		{0x03, 0x01, 0xeb, 0x00}, {0x06, 0x01, 0xeb, 0x00},
-		{0x0a, 0x01, 0xeb, 0x00}, {0x0f, 0x01, 0xeb, 0x00},
-		{0x18, 0x01, 0xeb, 0x00}, {0x1f, 0x01, 0xeb, 0x00},
-		{0x29, 0x01, 0xeb, 0x00}, {0x38, 0x01, 0xeb, 0x01}
-	],
-	/* 190 */
-	[
-		{0xc2, 0x00, 0x00, 0x00}, {0xc3, 0x00, 0x00, 0x00},
-		{0xc5, 0x00, 0x00, 0x00}, {0xc6, 0x00, 0x00, 0x00},
-		{0xc9, 0x00, 0x00, 0x00}, {0xca, 0x00, 0x00, 0x00},
-		{0xcc, 0x00, 0x00, 0x00}, {0xcd, 0x00, 0x00, 0x00},
-		{0xd2, 0x00, 0x00, 0x00}, {0xd5, 0x00, 0x00, 0x00},
-		{0xd9, 0x00, 0x00, 0x00}, {0xdc, 0x00, 0x00, 0x00},
-		{0xe1, 0x00, 0x00, 0x00}, {0xe7, 0x00, 0x00, 0x00},
-		{0xef, 0x00, 0x00, 0x00}, {0xf6, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0xc0, 0x01}, {0x00, 0x01, 0xc1, 0x01},
-		{0x00, 0x01, 0xc8, 0x01}, {0x00, 0x01, 0xc9, 0x01},
-		{0x00, 0x01, 0xca, 0x01}, {0x00, 0x01, 0xcd, 0x01},
-		{0x00, 0x01, 0xd2, 0x01}, {0x00, 0x01, 0xd5, 0x01},
-		{0x00, 0x01, 0xda, 0x01}, {0x00, 0x01, 0xdb, 0x01},
-		{0x00, 0x01, 0xee, 0x01}, {0x00, 0x01, 0xf0, 0x01},
-		{0x00, 0x01, 0xf2, 0x01}, {0x00, 0x01, 0xf3, 0x01},
-		{0x00, 0x01, 0xff, 0x01}, {0xce, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x01, 0x01, 0xc0, 0x00}, {0x16, 0x01, 0xc0, 0x01},
-		{0x01, 0x01, 0xc1, 0x00}, {0x16, 0x01, 0xc1, 0x01},
-		{0x01, 0x01, 0xc8, 0x00}, {0x16, 0x01, 0xc8, 0x01},
-		{0x01, 0x01, 0xc9, 0x00}, {0x16, 0x01, 0xc9, 0x01},
-		{0x01, 0x01, 0xca, 0x00}, {0x16, 0x01, 0xca, 0x01},
-		{0x01, 0x01, 0xcd, 0x00}, {0x16, 0x01, 0xcd, 0x01},
-		{0x01, 0x01, 0xd2, 0x00}, {0x16, 0x01, 0xd2, 0x01},
-		{0x01, 0x01, 0xd5, 0x00}, {0x16, 0x01, 0xd5, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xc0, 0x00}, {0x09, 0x01, 0xc0, 0x00},
-		{0x17, 0x01, 0xc0, 0x00}, {0x28, 0x01, 0xc0, 0x01},
-		{0x02, 0x01, 0xc1, 0x00}, {0x09, 0x01, 0xc1, 0x00},
-		{0x17, 0x01, 0xc1, 0x00}, {0x28, 0x01, 0xc1, 0x01},
-		{0x02, 0x01, 0xc8, 0x00}, {0x09, 0x01, 0xc8, 0x00},
-		{0x17, 0x01, 0xc8, 0x00}, {0x28, 0x01, 0xc8, 0x01},
-		{0x02, 0x01, 0xc9, 0x00}, {0x09, 0x01, 0xc9, 0x00},
-		{0x17, 0x01, 0xc9, 0x00}, {0x28, 0x01, 0xc9, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xc0, 0x00}, {0x06, 0x01, 0xc0, 0x00},
-		{0x0a, 0x01, 0xc0, 0x00}, {0x0f, 0x01, 0xc0, 0x00},
-		{0x18, 0x01, 0xc0, 0x00}, {0x1f, 0x01, 0xc0, 0x00},
-		{0x29, 0x01, 0xc0, 0x00}, {0x38, 0x01, 0xc0, 0x01},
-		{0x03, 0x01, 0xc1, 0x00}, {0x06, 0x01, 0xc1, 0x00},
-		{0x0a, 0x01, 0xc1, 0x00}, {0x0f, 0x01, 0xc1, 0x00},
-		{0x18, 0x01, 0xc1, 0x00}, {0x1f, 0x01, 0xc1, 0x00},
-		{0x29, 0x01, 0xc1, 0x00}, {0x38, 0x01, 0xc1, 0x01}
-	],
-	/* 195 */
-	[
-		{0x03, 0x01, 0xc8, 0x00}, {0x06, 0x01, 0xc8, 0x00},
-		{0x0a, 0x01, 0xc8, 0x00}, {0x0f, 0x01, 0xc8, 0x00},
-		{0x18, 0x01, 0xc8, 0x00}, {0x1f, 0x01, 0xc8, 0x00},
-		{0x29, 0x01, 0xc8, 0x00}, {0x38, 0x01, 0xc8, 0x01},
-		{0x03, 0x01, 0xc9, 0x00}, {0x06, 0x01, 0xc9, 0x00},
-		{0x0a, 0x01, 0xc9, 0x00}, {0x0f, 0x01, 0xc9, 0x00},
-		{0x18, 0x01, 0xc9, 0x00}, {0x1f, 0x01, 0xc9, 0x00},
-		{0x29, 0x01, 0xc9, 0x00}, {0x38, 0x01, 0xc9, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xca, 0x00}, {0x09, 0x01, 0xca, 0x00},
-		{0x17, 0x01, 0xca, 0x00}, {0x28, 0x01, 0xca, 0x01},
-		{0x02, 0x01, 0xcd, 0x00}, {0x09, 0x01, 0xcd, 0x00},
-		{0x17, 0x01, 0xcd, 0x00}, {0x28, 0x01, 0xcd, 0x01},
-		{0x02, 0x01, 0xd2, 0x00}, {0x09, 0x01, 0xd2, 0x00},
-		{0x17, 0x01, 0xd2, 0x00}, {0x28, 0x01, 0xd2, 0x01},
-		{0x02, 0x01, 0xd5, 0x00}, {0x09, 0x01, 0xd5, 0x00},
-		{0x17, 0x01, 0xd5, 0x00}, {0x28, 0x01, 0xd5, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xca, 0x00}, {0x06, 0x01, 0xca, 0x00},
-		{0x0a, 0x01, 0xca, 0x00}, {0x0f, 0x01, 0xca, 0x00},
-		{0x18, 0x01, 0xca, 0x00}, {0x1f, 0x01, 0xca, 0x00},
-		{0x29, 0x01, 0xca, 0x00}, {0x38, 0x01, 0xca, 0x01},
-		{0x03, 0x01, 0xcd, 0x00}, {0x06, 0x01, 0xcd, 0x00},
-		{0x0a, 0x01, 0xcd, 0x00}, {0x0f, 0x01, 0xcd, 0x00},
-		{0x18, 0x01, 0xcd, 0x00}, {0x1f, 0x01, 0xcd, 0x00},
-		{0x29, 0x01, 0xcd, 0x00}, {0x38, 0x01, 0xcd, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xd2, 0x00}, {0x06, 0x01, 0xd2, 0x00},
-		{0x0a, 0x01, 0xd2, 0x00}, {0x0f, 0x01, 0xd2, 0x00},
-		{0x18, 0x01, 0xd2, 0x00}, {0x1f, 0x01, 0xd2, 0x00},
-		{0x29, 0x01, 0xd2, 0x00}, {0x38, 0x01, 0xd2, 0x01},
-		{0x03, 0x01, 0xd5, 0x00}, {0x06, 0x01, 0xd5, 0x00},
-		{0x0a, 0x01, 0xd5, 0x00}, {0x0f, 0x01, 0xd5, 0x00},
-		{0x18, 0x01, 0xd5, 0x00}, {0x1f, 0x01, 0xd5, 0x00},
-		{0x29, 0x01, 0xd5, 0x00}, {0x38, 0x01, 0xd5, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xda, 0x00}, {0x16, 0x01, 0xda, 0x01},
-		{0x01, 0x01, 0xdb, 0x00}, {0x16, 0x01, 0xdb, 0x01},
-		{0x01, 0x01, 0xee, 0x00}, {0x16, 0x01, 0xee, 0x01},
-		{0x01, 0x01, 0xf0, 0x00}, {0x16, 0x01, 0xf0, 0x01},
-		{0x01, 0x01, 0xf2, 0x00}, {0x16, 0x01, 0xf2, 0x01},
-		{0x01, 0x01, 0xf3, 0x00}, {0x16, 0x01, 0xf3, 0x01},
-		{0x01, 0x01, 0xff, 0x00}, {0x16, 0x01, 0xff, 0x01},
-		{0x00, 0x01, 0xcb, 0x01}, {0x00, 0x01, 0xcc, 0x01}
-	],
-	/* 200 */
-	[
-		{0x02, 0x01, 0xda, 0x00}, {0x09, 0x01, 0xda, 0x00},
-		{0x17, 0x01, 0xda, 0x00}, {0x28, 0x01, 0xda, 0x01},
-		{0x02, 0x01, 0xdb, 0x00}, {0x09, 0x01, 0xdb, 0x00},
-		{0x17, 0x01, 0xdb, 0x00}, {0x28, 0x01, 0xdb, 0x01},
-		{0x02, 0x01, 0xee, 0x00}, {0x09, 0x01, 0xee, 0x00},
-		{0x17, 0x01, 0xee, 0x00}, {0x28, 0x01, 0xee, 0x01},
-		{0x02, 0x01, 0xf0, 0x00}, {0x09, 0x01, 0xf0, 0x00},
-		{0x17, 0x01, 0xf0, 0x00}, {0x28, 0x01, 0xf0, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xda, 0x00}, {0x06, 0x01, 0xda, 0x00},
-		{0x0a, 0x01, 0xda, 0x00}, {0x0f, 0x01, 0xda, 0x00},
-		{0x18, 0x01, 0xda, 0x00}, {0x1f, 0x01, 0xda, 0x00},
-		{0x29, 0x01, 0xda, 0x00}, {0x38, 0x01, 0xda, 0x01},
-		{0x03, 0x01, 0xdb, 0x00}, {0x06, 0x01, 0xdb, 0x00},
-		{0x0a, 0x01, 0xdb, 0x00}, {0x0f, 0x01, 0xdb, 0x00},
-		{0x18, 0x01, 0xdb, 0x00}, {0x1f, 0x01, 0xdb, 0x00},
-		{0x29, 0x01, 0xdb, 0x00}, {0x38, 0x01, 0xdb, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xee, 0x00}, {0x06, 0x01, 0xee, 0x00},
-		{0x0a, 0x01, 0xee, 0x00}, {0x0f, 0x01, 0xee, 0x00},
-		{0x18, 0x01, 0xee, 0x00}, {0x1f, 0x01, 0xee, 0x00},
-		{0x29, 0x01, 0xee, 0x00}, {0x38, 0x01, 0xee, 0x01},
-		{0x03, 0x01, 0xf0, 0x00}, {0x06, 0x01, 0xf0, 0x00},
-		{0x0a, 0x01, 0xf0, 0x00}, {0x0f, 0x01, 0xf0, 0x00},
-		{0x18, 0x01, 0xf0, 0x00}, {0x1f, 0x01, 0xf0, 0x00},
-		{0x29, 0x01, 0xf0, 0x00}, {0x38, 0x01, 0xf0, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xf2, 0x00}, {0x09, 0x01, 0xf2, 0x00},
-		{0x17, 0x01, 0xf2, 0x00}, {0x28, 0x01, 0xf2, 0x01},
-		{0x02, 0x01, 0xf3, 0x00}, {0x09, 0x01, 0xf3, 0x00},
-		{0x17, 0x01, 0xf3, 0x00}, {0x28, 0x01, 0xf3, 0x01},
-		{0x02, 0x01, 0xff, 0x00}, {0x09, 0x01, 0xff, 0x00},
-		{0x17, 0x01, 0xff, 0x00}, {0x28, 0x01, 0xff, 0x01},
-		{0x01, 0x01, 0xcb, 0x00}, {0x16, 0x01, 0xcb, 0x01},
-		{0x01, 0x01, 0xcc, 0x00}, {0x16, 0x01, 0xcc, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xf2, 0x00}, {0x06, 0x01, 0xf2, 0x00},
-		{0x0a, 0x01, 0xf2, 0x00}, {0x0f, 0x01, 0xf2, 0x00},
-		{0x18, 0x01, 0xf2, 0x00}, {0x1f, 0x01, 0xf2, 0x00},
-		{0x29, 0x01, 0xf2, 0x00}, {0x38, 0x01, 0xf2, 0x01},
-		{0x03, 0x01, 0xf3, 0x00}, {0x06, 0x01, 0xf3, 0x00},
-		{0x0a, 0x01, 0xf3, 0x00}, {0x0f, 0x01, 0xf3, 0x00},
-		{0x18, 0x01, 0xf3, 0x00}, {0x1f, 0x01, 0xf3, 0x00},
-		{0x29, 0x01, 0xf3, 0x00}, {0x38, 0x01, 0xf3, 0x01}
-	],
-	/* 205 */
-	[
-		{0x03, 0x01, 0xff, 0x00}, {0x06, 0x01, 0xff, 0x00},
-		{0x0a, 0x01, 0xff, 0x00}, {0x0f, 0x01, 0xff, 0x00},
-		{0x18, 0x01, 0xff, 0x00}, {0x1f, 0x01, 0xff, 0x00},
-		{0x29, 0x01, 0xff, 0x00}, {0x38, 0x01, 0xff, 0x01},
-		{0x02, 0x01, 0xcb, 0x00}, {0x09, 0x01, 0xcb, 0x00},
-		{0x17, 0x01, 0xcb, 0x00}, {0x28, 0x01, 0xcb, 0x01},
-		{0x02, 0x01, 0xcc, 0x00}, {0x09, 0x01, 0xcc, 0x00},
-		{0x17, 0x01, 0xcc, 0x00}, {0x28, 0x01, 0xcc, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xcb, 0x00}, {0x06, 0x01, 0xcb, 0x00},
-		{0x0a, 0x01, 0xcb, 0x00}, {0x0f, 0x01, 0xcb, 0x00},
-		{0x18, 0x01, 0xcb, 0x00}, {0x1f, 0x01, 0xcb, 0x00},
-		{0x29, 0x01, 0xcb, 0x00}, {0x38, 0x01, 0xcb, 0x01},
-		{0x03, 0x01, 0xcc, 0x00}, {0x06, 0x01, 0xcc, 0x00},
-		{0x0a, 0x01, 0xcc, 0x00}, {0x0f, 0x01, 0xcc, 0x00},
-		{0x18, 0x01, 0xcc, 0x00}, {0x1f, 0x01, 0xcc, 0x00},
-		{0x29, 0x01, 0xcc, 0x00}, {0x38, 0x01, 0xcc, 0x01}
-	],
-	[
-		{0xd3, 0x00, 0x00, 0x00}, {0xd4, 0x00, 0x00, 0x00},
-		{0xd6, 0x00, 0x00, 0x00}, {0xd7, 0x00, 0x00, 0x00},
-		{0xda, 0x00, 0x00, 0x00}, {0xdb, 0x00, 0x00, 0x00},
-		{0xdd, 0x00, 0x00, 0x00}, {0xde, 0x00, 0x00, 0x00},
-		{0xe2, 0x00, 0x00, 0x00}, {0xe4, 0x00, 0x00, 0x00},
-		{0xe8, 0x00, 0x00, 0x00}, {0xeb, 0x00, 0x00, 0x00},
-		{0xf0, 0x00, 0x00, 0x00}, {0xf3, 0x00, 0x00, 0x00},
-		{0xf7, 0x00, 0x00, 0x00}, {0xfa, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x00, 0x01, 0xd3, 0x01}, {0x00, 0x01, 0xd4, 0x01},
-		{0x00, 0x01, 0xd6, 0x01}, {0x00, 0x01, 0xdd, 0x01},
-		{0x00, 0x01, 0xde, 0x01}, {0x00, 0x01, 0xdf, 0x01},
-		{0x00, 0x01, 0xf1, 0x01}, {0x00, 0x01, 0xf4, 0x01},
-		{0x00, 0x01, 0xf5, 0x01}, {0x00, 0x01, 0xf6, 0x01},
-		{0x00, 0x01, 0xf7, 0x01}, {0x00, 0x01, 0xf8, 0x01},
-		{0x00, 0x01, 0xfa, 0x01}, {0x00, 0x01, 0xfb, 0x01},
-		{0x00, 0x01, 0xfc, 0x01}, {0x00, 0x01, 0xfd, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xd3, 0x00}, {0x16, 0x01, 0xd3, 0x01},
-		{0x01, 0x01, 0xd4, 0x00}, {0x16, 0x01, 0xd4, 0x01},
-		{0x01, 0x01, 0xd6, 0x00}, {0x16, 0x01, 0xd6, 0x01},
-		{0x01, 0x01, 0xdd, 0x00}, {0x16, 0x01, 0xdd, 0x01},
-		{0x01, 0x01, 0xde, 0x00}, {0x16, 0x01, 0xde, 0x01},
-		{0x01, 0x01, 0xdf, 0x00}, {0x16, 0x01, 0xdf, 0x01},
-		{0x01, 0x01, 0xf1, 0x00}, {0x16, 0x01, 0xf1, 0x01},
-		{0x01, 0x01, 0xf4, 0x00}, {0x16, 0x01, 0xf4, 0x01}
-	],
-	/* 210 */
-	[
-		{0x02, 0x01, 0xd3, 0x00}, {0x09, 0x01, 0xd3, 0x00},
-		{0x17, 0x01, 0xd3, 0x00}, {0x28, 0x01, 0xd3, 0x01},
-		{0x02, 0x01, 0xd4, 0x00}, {0x09, 0x01, 0xd4, 0x00},
-		{0x17, 0x01, 0xd4, 0x00}, {0x28, 0x01, 0xd4, 0x01},
-		{0x02, 0x01, 0xd6, 0x00}, {0x09, 0x01, 0xd6, 0x00},
-		{0x17, 0x01, 0xd6, 0x00}, {0x28, 0x01, 0xd6, 0x01},
-		{0x02, 0x01, 0xdd, 0x00}, {0x09, 0x01, 0xdd, 0x00},
-		{0x17, 0x01, 0xdd, 0x00}, {0x28, 0x01, 0xdd, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xd3, 0x00}, {0x06, 0x01, 0xd3, 0x00},
-		{0x0a, 0x01, 0xd3, 0x00}, {0x0f, 0x01, 0xd3, 0x00},
-		{0x18, 0x01, 0xd3, 0x00}, {0x1f, 0x01, 0xd3, 0x00},
-		{0x29, 0x01, 0xd3, 0x00}, {0x38, 0x01, 0xd3, 0x01},
-		{0x03, 0x01, 0xd4, 0x00}, {0x06, 0x01, 0xd4, 0x00},
-		{0x0a, 0x01, 0xd4, 0x00}, {0x0f, 0x01, 0xd4, 0x00},
-		{0x18, 0x01, 0xd4, 0x00}, {0x1f, 0x01, 0xd4, 0x00},
-		{0x29, 0x01, 0xd4, 0x00}, {0x38, 0x01, 0xd4, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xd6, 0x00}, {0x06, 0x01, 0xd6, 0x00},
-		{0x0a, 0x01, 0xd6, 0x00}, {0x0f, 0x01, 0xd6, 0x00},
-		{0x18, 0x01, 0xd6, 0x00}, {0x1f, 0x01, 0xd6, 0x00},
-		{0x29, 0x01, 0xd6, 0x00}, {0x38, 0x01, 0xd6, 0x01},
-		{0x03, 0x01, 0xdd, 0x00}, {0x06, 0x01, 0xdd, 0x00},
-		{0x0a, 0x01, 0xdd, 0x00}, {0x0f, 0x01, 0xdd, 0x00},
-		{0x18, 0x01, 0xdd, 0x00}, {0x1f, 0x01, 0xdd, 0x00},
-		{0x29, 0x01, 0xdd, 0x00}, {0x38, 0x01, 0xdd, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xde, 0x00}, {0x09, 0x01, 0xde, 0x00},
-		{0x17, 0x01, 0xde, 0x00}, {0x28, 0x01, 0xde, 0x01},
-		{0x02, 0x01, 0xdf, 0x00}, {0x09, 0x01, 0xdf, 0x00},
-		{0x17, 0x01, 0xdf, 0x00}, {0x28, 0x01, 0xdf, 0x01},
-		{0x02, 0x01, 0xf1, 0x00}, {0x09, 0x01, 0xf1, 0x00},
-		{0x17, 0x01, 0xf1, 0x00}, {0x28, 0x01, 0xf1, 0x01},
-		{0x02, 0x01, 0xf4, 0x00}, {0x09, 0x01, 0xf4, 0x00},
-		{0x17, 0x01, 0xf4, 0x00}, {0x28, 0x01, 0xf4, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xde, 0x00}, {0x06, 0x01, 0xde, 0x00},
-		{0x0a, 0x01, 0xde, 0x00}, {0x0f, 0x01, 0xde, 0x00},
-		{0x18, 0x01, 0xde, 0x00}, {0x1f, 0x01, 0xde, 0x00},
-		{0x29, 0x01, 0xde, 0x00}, {0x38, 0x01, 0xde, 0x01},
-		{0x03, 0x01, 0xdf, 0x00}, {0x06, 0x01, 0xdf, 0x00},
-		{0x0a, 0x01, 0xdf, 0x00}, {0x0f, 0x01, 0xdf, 0x00},
-		{0x18, 0x01, 0xdf, 0x00}, {0x1f, 0x01, 0xdf, 0x00},
-		{0x29, 0x01, 0xdf, 0x00}, {0x38, 0x01, 0xdf, 0x01}
-	],
-	/* 215 */
-	[
-		{0x03, 0x01, 0xf1, 0x00}, {0x06, 0x01, 0xf1, 0x00},
-		{0x0a, 0x01, 0xf1, 0x00}, {0x0f, 0x01, 0xf1, 0x00},
-		{0x18, 0x01, 0xf1, 0x00}, {0x1f, 0x01, 0xf1, 0x00},
-		{0x29, 0x01, 0xf1, 0x00}, {0x38, 0x01, 0xf1, 0x01},
-		{0x03, 0x01, 0xf4, 0x00}, {0x06, 0x01, 0xf4, 0x00},
-		{0x0a, 0x01, 0xf4, 0x00}, {0x0f, 0x01, 0xf4, 0x00},
-		{0x18, 0x01, 0xf4, 0x00}, {0x1f, 0x01, 0xf4, 0x00},
-		{0x29, 0x01, 0xf4, 0x00}, {0x38, 0x01, 0xf4, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xf5, 0x00}, {0x16, 0x01, 0xf5, 0x01},
-		{0x01, 0x01, 0xf6, 0x00}, {0x16, 0x01, 0xf6, 0x01},
-		{0x01, 0x01, 0xf7, 0x00}, {0x16, 0x01, 0xf7, 0x01},
-		{0x01, 0x01, 0xf8, 0x00}, {0x16, 0x01, 0xf8, 0x01},
-		{0x01, 0x01, 0xfa, 0x00}, {0x16, 0x01, 0xfa, 0x01},
-		{0x01, 0x01, 0xfb, 0x00}, {0x16, 0x01, 0xfb, 0x01},
-		{0x01, 0x01, 0xfc, 0x00}, {0x16, 0x01, 0xfc, 0x01},
-		{0x01, 0x01, 0xfd, 0x00}, {0x16, 0x01, 0xfd, 0x01}
-	],
-	[
-		{0x02, 0x01, 0xf5, 0x00}, {0x09, 0x01, 0xf5, 0x00},
-		{0x17, 0x01, 0xf5, 0x00}, {0x28, 0x01, 0xf5, 0x01},
-		{0x02, 0x01, 0xf6, 0x00}, {0x09, 0x01, 0xf6, 0x00},
-		{0x17, 0x01, 0xf6, 0x00}, {0x28, 0x01, 0xf6, 0x01},
-		{0x02, 0x01, 0xf7, 0x00}, {0x09, 0x01, 0xf7, 0x00},
-		{0x17, 0x01, 0xf7, 0x00}, {0x28, 0x01, 0xf7, 0x01},
-		{0x02, 0x01, 0xf8, 0x00}, {0x09, 0x01, 0xf8, 0x00},
-		{0x17, 0x01, 0xf8, 0x00}, {0x28, 0x01, 0xf8, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xf5, 0x00}, {0x06, 0x01, 0xf5, 0x00},
-		{0x0a, 0x01, 0xf5, 0x00}, {0x0f, 0x01, 0xf5, 0x00},
-		{0x18, 0x01, 0xf5, 0x00}, {0x1f, 0x01, 0xf5, 0x00},
-		{0x29, 0x01, 0xf5, 0x00}, {0x38, 0x01, 0xf5, 0x01},
-		{0x03, 0x01, 0xf6, 0x00}, {0x06, 0x01, 0xf6, 0x00},
-		{0x0a, 0x01, 0xf6, 0x00}, {0x0f, 0x01, 0xf6, 0x00},
-		{0x18, 0x01, 0xf6, 0x00}, {0x1f, 0x01, 0xf6, 0x00},
-		{0x29, 0x01, 0xf6, 0x00}, {0x38, 0x01, 0xf6, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xf7, 0x00}, {0x06, 0x01, 0xf7, 0x00},
-		{0x0a, 0x01, 0xf7, 0x00}, {0x0f, 0x01, 0xf7, 0x00},
-		{0x18, 0x01, 0xf7, 0x00}, {0x1f, 0x01, 0xf7, 0x00},
-		{0x29, 0x01, 0xf7, 0x00}, {0x38, 0x01, 0xf7, 0x01},
-		{0x03, 0x01, 0xf8, 0x00}, {0x06, 0x01, 0xf8, 0x00},
-		{0x0a, 0x01, 0xf8, 0x00}, {0x0f, 0x01, 0xf8, 0x00},
-		{0x18, 0x01, 0xf8, 0x00}, {0x1f, 0x01, 0xf8, 0x00},
-		{0x29, 0x01, 0xf8, 0x00}, {0x38, 0x01, 0xf8, 0x01}
-	],
-	/* 220 */
-	[
-		{0x02, 0x01, 0xfa, 0x00}, {0x09, 0x01, 0xfa, 0x00},
-		{0x17, 0x01, 0xfa, 0x00}, {0x28, 0x01, 0xfa, 0x01},
-		{0x02, 0x01, 0xfb, 0x00}, {0x09, 0x01, 0xfb, 0x00},
-		{0x17, 0x01, 0xfb, 0x00}, {0x28, 0x01, 0xfb, 0x01},
-		{0x02, 0x01, 0xfc, 0x00}, {0x09, 0x01, 0xfc, 0x00},
-		{0x17, 0x01, 0xfc, 0x00}, {0x28, 0x01, 0xfc, 0x01},
-		{0x02, 0x01, 0xfd, 0x00}, {0x09, 0x01, 0xfd, 0x00},
-		{0x17, 0x01, 0xfd, 0x00}, {0x28, 0x01, 0xfd, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xfa, 0x00}, {0x06, 0x01, 0xfa, 0x00},
-		{0x0a, 0x01, 0xfa, 0x00}, {0x0f, 0x01, 0xfa, 0x00},
-		{0x18, 0x01, 0xfa, 0x00}, {0x1f, 0x01, 0xfa, 0x00},
-		{0x29, 0x01, 0xfa, 0x00}, {0x38, 0x01, 0xfa, 0x01},
-		{0x03, 0x01, 0xfb, 0x00}, {0x06, 0x01, 0xfb, 0x00},
-		{0x0a, 0x01, 0xfb, 0x00}, {0x0f, 0x01, 0xfb, 0x00},
-		{0x18, 0x01, 0xfb, 0x00}, {0x1f, 0x01, 0xfb, 0x00},
-		{0x29, 0x01, 0xfb, 0x00}, {0x38, 0x01, 0xfb, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xfc, 0x00}, {0x06, 0x01, 0xfc, 0x00},
-		{0x0a, 0x01, 0xfc, 0x00}, {0x0f, 0x01, 0xfc, 0x00},
-		{0x18, 0x01, 0xfc, 0x00}, {0x1f, 0x01, 0xfc, 0x00},
-		{0x29, 0x01, 0xfc, 0x00}, {0x38, 0x01, 0xfc, 0x01},
-		{0x03, 0x01, 0xfd, 0x00}, {0x06, 0x01, 0xfd, 0x00},
-		{0x0a, 0x01, 0xfd, 0x00}, {0x0f, 0x01, 0xfd, 0x00},
-		{0x18, 0x01, 0xfd, 0x00}, {0x1f, 0x01, 0xfd, 0x00},
-		{0x29, 0x01, 0xfd, 0x00}, {0x38, 0x01, 0xfd, 0x01}
-	],
-	[
-		{0x00, 0x01, 0xfe, 0x01}, {0xe3, 0x00, 0x00, 0x00},
-		{0xe5, 0x00, 0x00, 0x00}, {0xe6, 0x00, 0x00, 0x00},
-		{0xe9, 0x00, 0x00, 0x00}, {0xea, 0x00, 0x00, 0x00},
-		{0xec, 0x00, 0x00, 0x00}, {0xed, 0x00, 0x00, 0x00},
-		{0xf1, 0x00, 0x00, 0x00}, {0xf2, 0x00, 0x00, 0x00},
-		{0xf4, 0x00, 0x00, 0x00}, {0xf5, 0x00, 0x00, 0x00},
-		{0xf8, 0x00, 0x00, 0x00}, {0xf9, 0x00, 0x00, 0x00},
-		{0xfb, 0x00, 0x00, 0x00}, {0xfc, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x01, 0x01, 0xfe, 0x00}, {0x16, 0x01, 0xfe, 0x01},
-		{0x00, 0x01, 0x02, 0x01}, {0x00, 0x01, 0x03, 0x01},
-		{0x00, 0x01, 0x04, 0x01}, {0x00, 0x01, 0x05, 0x01},
-		{0x00, 0x01, 0x06, 0x01}, {0x00, 0x01, 0x07, 0x01},
-		{0x00, 0x01, 0x08, 0x01}, {0x00, 0x01, 0x0b, 0x01},
-		{0x00, 0x01, 0x0c, 0x01}, {0x00, 0x01, 0x0e, 0x01},
-		{0x00, 0x01, 0x0f, 0x01}, {0x00, 0x01, 0x10, 0x01},
-		{0x00, 0x01, 0x11, 0x01}, {0x00, 0x01, 0x12, 0x01}
-	],
-	/* 225 */
-	[
-		{0x02, 0x01, 0xfe, 0x00}, {0x09, 0x01, 0xfe, 0x00},
-		{0x17, 0x01, 0xfe, 0x00}, {0x28, 0x01, 0xfe, 0x01},
-		{0x01, 0x01, 0x02, 0x00}, {0x16, 0x01, 0x02, 0x01},
-		{0x01, 0x01, 0x03, 0x00}, {0x16, 0x01, 0x03, 0x01},
-		{0x01, 0x01, 0x04, 0x00}, {0x16, 0x01, 0x04, 0x01},
-		{0x01, 0x01, 0x05, 0x00}, {0x16, 0x01, 0x05, 0x01},
-		{0x01, 0x01, 0x06, 0x00}, {0x16, 0x01, 0x06, 0x01},
-		{0x01, 0x01, 0x07, 0x00}, {0x16, 0x01, 0x07, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xfe, 0x00}, {0x06, 0x01, 0xfe, 0x00},
-		{0x0a, 0x01, 0xfe, 0x00}, {0x0f, 0x01, 0xfe, 0x00},
-		{0x18, 0x01, 0xfe, 0x00}, {0x1f, 0x01, 0xfe, 0x00},
-		{0x29, 0x01, 0xfe, 0x00}, {0x38, 0x01, 0xfe, 0x01},
-		{0x02, 0x01, 0x02, 0x00}, {0x09, 0x01, 0x02, 0x00},
-		{0x17, 0x01, 0x02, 0x00}, {0x28, 0x01, 0x02, 0x01},
-		{0x02, 0x01, 0x03, 0x00}, {0x09, 0x01, 0x03, 0x00},
-		{0x17, 0x01, 0x03, 0x00}, {0x28, 0x01, 0x03, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x02, 0x00}, {0x06, 0x01, 0x02, 0x00},
-		{0x0a, 0x01, 0x02, 0x00}, {0x0f, 0x01, 0x02, 0x00},
-		{0x18, 0x01, 0x02, 0x00}, {0x1f, 0x01, 0x02, 0x00},
-		{0x29, 0x01, 0x02, 0x00}, {0x38, 0x01, 0x02, 0x01},
-		{0x03, 0x01, 0x03, 0x00}, {0x06, 0x01, 0x03, 0x00},
-		{0x0a, 0x01, 0x03, 0x00}, {0x0f, 0x01, 0x03, 0x00},
-		{0x18, 0x01, 0x03, 0x00}, {0x1f, 0x01, 0x03, 0x00},
-		{0x29, 0x01, 0x03, 0x00}, {0x38, 0x01, 0x03, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x04, 0x00}, {0x09, 0x01, 0x04, 0x00},
-		{0x17, 0x01, 0x04, 0x00}, {0x28, 0x01, 0x04, 0x01},
-		{0x02, 0x01, 0x05, 0x00}, {0x09, 0x01, 0x05, 0x00},
-		{0x17, 0x01, 0x05, 0x00}, {0x28, 0x01, 0x05, 0x01},
-		{0x02, 0x01, 0x06, 0x00}, {0x09, 0x01, 0x06, 0x00},
-		{0x17, 0x01, 0x06, 0x00}, {0x28, 0x01, 0x06, 0x01},
-		{0x02, 0x01, 0x07, 0x00}, {0x09, 0x01, 0x07, 0x00},
-		{0x17, 0x01, 0x07, 0x00}, {0x28, 0x01, 0x07, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x04, 0x00}, {0x06, 0x01, 0x04, 0x00},
-		{0x0a, 0x01, 0x04, 0x00}, {0x0f, 0x01, 0x04, 0x00},
-		{0x18, 0x01, 0x04, 0x00}, {0x1f, 0x01, 0x04, 0x00},
-		{0x29, 0x01, 0x04, 0x00}, {0x38, 0x01, 0x04, 0x01},
-		{0x03, 0x01, 0x05, 0x00}, {0x06, 0x01, 0x05, 0x00},
-		{0x0a, 0x01, 0x05, 0x00}, {0x0f, 0x01, 0x05, 0x00},
-		{0x18, 0x01, 0x05, 0x00}, {0x1f, 0x01, 0x05, 0x00},
-		{0x29, 0x01, 0x05, 0x00}, {0x38, 0x01, 0x05, 0x01}
-	],
-	/* 230 */
-	[
-		{0x03, 0x01, 0x06, 0x00}, {0x06, 0x01, 0x06, 0x00},
-		{0x0a, 0x01, 0x06, 0x00}, {0x0f, 0x01, 0x06, 0x00},
-		{0x18, 0x01, 0x06, 0x00}, {0x1f, 0x01, 0x06, 0x00},
-		{0x29, 0x01, 0x06, 0x00}, {0x38, 0x01, 0x06, 0x01},
-		{0x03, 0x01, 0x07, 0x00}, {0x06, 0x01, 0x07, 0x00},
-		{0x0a, 0x01, 0x07, 0x00}, {0x0f, 0x01, 0x07, 0x00},
-		{0x18, 0x01, 0x07, 0x00}, {0x1f, 0x01, 0x07, 0x00},
-		{0x29, 0x01, 0x07, 0x00}, {0x38, 0x01, 0x07, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x08, 0x00}, {0x16, 0x01, 0x08, 0x01},
-		{0x01, 0x01, 0x0b, 0x00}, {0x16, 0x01, 0x0b, 0x01},
-		{0x01, 0x01, 0x0c, 0x00}, {0x16, 0x01, 0x0c, 0x01},
-		{0x01, 0x01, 0x0e, 0x00}, {0x16, 0x01, 0x0e, 0x01},
-		{0x01, 0x01, 0x0f, 0x00}, {0x16, 0x01, 0x0f, 0x01},
-		{0x01, 0x01, 0x10, 0x00}, {0x16, 0x01, 0x10, 0x01},
-		{0x01, 0x01, 0x11, 0x00}, {0x16, 0x01, 0x11, 0x01},
-		{0x01, 0x01, 0x12, 0x00}, {0x16, 0x01, 0x12, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x08, 0x00}, {0x09, 0x01, 0x08, 0x00},
-		{0x17, 0x01, 0x08, 0x00}, {0x28, 0x01, 0x08, 0x01},
-		{0x02, 0x01, 0x0b, 0x00}, {0x09, 0x01, 0x0b, 0x00},
-		{0x17, 0x01, 0x0b, 0x00}, {0x28, 0x01, 0x0b, 0x01},
-		{0x02, 0x01, 0x0c, 0x00}, {0x09, 0x01, 0x0c, 0x00},
-		{0x17, 0x01, 0x0c, 0x00}, {0x28, 0x01, 0x0c, 0x01},
-		{0x02, 0x01, 0x0e, 0x00}, {0x09, 0x01, 0x0e, 0x00},
-		{0x17, 0x01, 0x0e, 0x00}, {0x28, 0x01, 0x0e, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x08, 0x00}, {0x06, 0x01, 0x08, 0x00},
-		{0x0a, 0x01, 0x08, 0x00}, {0x0f, 0x01, 0x08, 0x00},
-		{0x18, 0x01, 0x08, 0x00}, {0x1f, 0x01, 0x08, 0x00},
-		{0x29, 0x01, 0x08, 0x00}, {0x38, 0x01, 0x08, 0x01},
-		{0x03, 0x01, 0x0b, 0x00}, {0x06, 0x01, 0x0b, 0x00},
-		{0x0a, 0x01, 0x0b, 0x00}, {0x0f, 0x01, 0x0b, 0x00},
-		{0x18, 0x01, 0x0b, 0x00}, {0x1f, 0x01, 0x0b, 0x00},
-		{0x29, 0x01, 0x0b, 0x00}, {0x38, 0x01, 0x0b, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x0c, 0x00}, {0x06, 0x01, 0x0c, 0x00},
-		{0x0a, 0x01, 0x0c, 0x00}, {0x0f, 0x01, 0x0c, 0x00},
-		{0x18, 0x01, 0x0c, 0x00}, {0x1f, 0x01, 0x0c, 0x00},
-		{0x29, 0x01, 0x0c, 0x00}, {0x38, 0x01, 0x0c, 0x01},
-		{0x03, 0x01, 0x0e, 0x00}, {0x06, 0x01, 0x0e, 0x00},
-		{0x0a, 0x01, 0x0e, 0x00}, {0x0f, 0x01, 0x0e, 0x00},
-		{0x18, 0x01, 0x0e, 0x00}, {0x1f, 0x01, 0x0e, 0x00},
-		{0x29, 0x01, 0x0e, 0x00}, {0x38, 0x01, 0x0e, 0x01}
-	],
-	/* 235 */
-	[
-		{0x02, 0x01, 0x0f, 0x00}, {0x09, 0x01, 0x0f, 0x00},
-		{0x17, 0x01, 0x0f, 0x00}, {0x28, 0x01, 0x0f, 0x01},
-		{0x02, 0x01, 0x10, 0x00}, {0x09, 0x01, 0x10, 0x00},
-		{0x17, 0x01, 0x10, 0x00}, {0x28, 0x01, 0x10, 0x01},
-		{0x02, 0x01, 0x11, 0x00}, {0x09, 0x01, 0x11, 0x00},
-		{0x17, 0x01, 0x11, 0x00}, {0x28, 0x01, 0x11, 0x01},
-		{0x02, 0x01, 0x12, 0x00}, {0x09, 0x01, 0x12, 0x00},
-		{0x17, 0x01, 0x12, 0x00}, {0x28, 0x01, 0x12, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x0f, 0x00}, {0x06, 0x01, 0x0f, 0x00},
-		{0x0a, 0x01, 0x0f, 0x00}, {0x0f, 0x01, 0x0f, 0x00},
-		{0x18, 0x01, 0x0f, 0x00}, {0x1f, 0x01, 0x0f, 0x00},
-		{0x29, 0x01, 0x0f, 0x00}, {0x38, 0x01, 0x0f, 0x01},
-		{0x03, 0x01, 0x10, 0x00}, {0x06, 0x01, 0x10, 0x00},
-		{0x0a, 0x01, 0x10, 0x00}, {0x0f, 0x01, 0x10, 0x00},
-		{0x18, 0x01, 0x10, 0x00}, {0x1f, 0x01, 0x10, 0x00},
-		{0x29, 0x01, 0x10, 0x00}, {0x38, 0x01, 0x10, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x11, 0x00}, {0x06, 0x01, 0x11, 0x00},
-		{0x0a, 0x01, 0x11, 0x00}, {0x0f, 0x01, 0x11, 0x00},
-		{0x18, 0x01, 0x11, 0x00}, {0x1f, 0x01, 0x11, 0x00},
-		{0x29, 0x01, 0x11, 0x00}, {0x38, 0x01, 0x11, 0x01},
-		{0x03, 0x01, 0x12, 0x00}, {0x06, 0x01, 0x12, 0x00},
-		{0x0a, 0x01, 0x12, 0x00}, {0x0f, 0x01, 0x12, 0x00},
-		{0x18, 0x01, 0x12, 0x00}, {0x1f, 0x01, 0x12, 0x00},
-		{0x29, 0x01, 0x12, 0x00}, {0x38, 0x01, 0x12, 0x01}
-	],
-	[
-		{0x00, 0x01, 0x13, 0x01}, {0x00, 0x01, 0x14, 0x01},
-		{0x00, 0x01, 0x15, 0x01}, {0x00, 0x01, 0x17, 0x01},
-		{0x00, 0x01, 0x18, 0x01}, {0x00, 0x01, 0x19, 0x01},
-		{0x00, 0x01, 0x1a, 0x01}, {0x00, 0x01, 0x1b, 0x01},
-		{0x00, 0x01, 0x1c, 0x01}, {0x00, 0x01, 0x1d, 0x01},
-		{0x00, 0x01, 0x1e, 0x01}, {0x00, 0x01, 0x1f, 0x01},
-		{0x00, 0x01, 0x7f, 0x01}, {0x00, 0x01, 0xdc, 0x01},
-		{0x00, 0x01, 0xf9, 0x01}, {0xfd, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x13, 0x00}, {0x16, 0x01, 0x13, 0x01},
-		{0x01, 0x01, 0x14, 0x00}, {0x16, 0x01, 0x14, 0x01},
-		{0x01, 0x01, 0x15, 0x00}, {0x16, 0x01, 0x15, 0x01},
-		{0x01, 0x01, 0x17, 0x00}, {0x16, 0x01, 0x17, 0x01},
-		{0x01, 0x01, 0x18, 0x00}, {0x16, 0x01, 0x18, 0x01},
-		{0x01, 0x01, 0x19, 0x00}, {0x16, 0x01, 0x19, 0x01},
-		{0x01, 0x01, 0x1a, 0x00}, {0x16, 0x01, 0x1a, 0x01},
-		{0x01, 0x01, 0x1b, 0x00}, {0x16, 0x01, 0x1b, 0x01}
-	],
-	/* 240 */
-	[
-		{0x02, 0x01, 0x13, 0x00}, {0x09, 0x01, 0x13, 0x00},
-		{0x17, 0x01, 0x13, 0x00}, {0x28, 0x01, 0x13, 0x01},
-		{0x02, 0x01, 0x14, 0x00}, {0x09, 0x01, 0x14, 0x00},
-		{0x17, 0x01, 0x14, 0x00}, {0x28, 0x01, 0x14, 0x01},
-		{0x02, 0x01, 0x15, 0x00}, {0x09, 0x01, 0x15, 0x00},
-		{0x17, 0x01, 0x15, 0x00}, {0x28, 0x01, 0x15, 0x01},
-		{0x02, 0x01, 0x17, 0x00}, {0x09, 0x01, 0x17, 0x00},
-		{0x17, 0x01, 0x17, 0x00}, {0x28, 0x01, 0x17, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x13, 0x00}, {0x06, 0x01, 0x13, 0x00},
-		{0x0a, 0x01, 0x13, 0x00}, {0x0f, 0x01, 0x13, 0x00},
-		{0x18, 0x01, 0x13, 0x00}, {0x1f, 0x01, 0x13, 0x00},
-		{0x29, 0x01, 0x13, 0x00}, {0x38, 0x01, 0x13, 0x01},
-		{0x03, 0x01, 0x14, 0x00}, {0x06, 0x01, 0x14, 0x00},
-		{0x0a, 0x01, 0x14, 0x00}, {0x0f, 0x01, 0x14, 0x00},
-		{0x18, 0x01, 0x14, 0x00}, {0x1f, 0x01, 0x14, 0x00},
-		{0x29, 0x01, 0x14, 0x00}, {0x38, 0x01, 0x14, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x15, 0x00}, {0x06, 0x01, 0x15, 0x00},
-		{0x0a, 0x01, 0x15, 0x00}, {0x0f, 0x01, 0x15, 0x00},
-		{0x18, 0x01, 0x15, 0x00}, {0x1f, 0x01, 0x15, 0x00},
-		{0x29, 0x01, 0x15, 0x00}, {0x38, 0x01, 0x15, 0x01},
-		{0x03, 0x01, 0x17, 0x00}, {0x06, 0x01, 0x17, 0x00},
-		{0x0a, 0x01, 0x17, 0x00}, {0x0f, 0x01, 0x17, 0x00},
-		{0x18, 0x01, 0x17, 0x00}, {0x1f, 0x01, 0x17, 0x00},
-		{0x29, 0x01, 0x17, 0x00}, {0x38, 0x01, 0x17, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x18, 0x00}, {0x09, 0x01, 0x18, 0x00},
-		{0x17, 0x01, 0x18, 0x00}, {0x28, 0x01, 0x18, 0x01},
-		{0x02, 0x01, 0x19, 0x00}, {0x09, 0x01, 0x19, 0x00},
-		{0x17, 0x01, 0x19, 0x00}, {0x28, 0x01, 0x19, 0x01},
-		{0x02, 0x01, 0x1a, 0x00}, {0x09, 0x01, 0x1a, 0x00},
-		{0x17, 0x01, 0x1a, 0x00}, {0x28, 0x01, 0x1a, 0x01},
-		{0x02, 0x01, 0x1b, 0x00}, {0x09, 0x01, 0x1b, 0x00},
-		{0x17, 0x01, 0x1b, 0x00}, {0x28, 0x01, 0x1b, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x18, 0x00}, {0x06, 0x01, 0x18, 0x00},
-		{0x0a, 0x01, 0x18, 0x00}, {0x0f, 0x01, 0x18, 0x00},
-		{0x18, 0x01, 0x18, 0x00}, {0x1f, 0x01, 0x18, 0x00},
-		{0x29, 0x01, 0x18, 0x00}, {0x38, 0x01, 0x18, 0x01},
-		{0x03, 0x01, 0x19, 0x00}, {0x06, 0x01, 0x19, 0x00},
-		{0x0a, 0x01, 0x19, 0x00}, {0x0f, 0x01, 0x19, 0x00},
-		{0x18, 0x01, 0x19, 0x00}, {0x1f, 0x01, 0x19, 0x00},
-		{0x29, 0x01, 0x19, 0x00}, {0x38, 0x01, 0x19, 0x01}
-	],
-	/* 245 */
-	[
-		{0x03, 0x01, 0x1a, 0x00}, {0x06, 0x01, 0x1a, 0x00},
-		{0x0a, 0x01, 0x1a, 0x00}, {0x0f, 0x01, 0x1a, 0x00},
-		{0x18, 0x01, 0x1a, 0x00}, {0x1f, 0x01, 0x1a, 0x00},
-		{0x29, 0x01, 0x1a, 0x00}, {0x38, 0x01, 0x1a, 0x01},
-		{0x03, 0x01, 0x1b, 0x00}, {0x06, 0x01, 0x1b, 0x00},
-		{0x0a, 0x01, 0x1b, 0x00}, {0x0f, 0x01, 0x1b, 0x00},
-		{0x18, 0x01, 0x1b, 0x00}, {0x1f, 0x01, 0x1b, 0x00},
-		{0x29, 0x01, 0x1b, 0x00}, {0x38, 0x01, 0x1b, 0x01}
-	],
-	[
-		{0x01, 0x01, 0x1c, 0x00}, {0x16, 0x01, 0x1c, 0x01},
-		{0x01, 0x01, 0x1d, 0x00}, {0x16, 0x01, 0x1d, 0x01},
-		{0x01, 0x01, 0x1e, 0x00}, {0x16, 0x01, 0x1e, 0x01},
-		{0x01, 0x01, 0x1f, 0x00}, {0x16, 0x01, 0x1f, 0x01},
-		{0x01, 0x01, 0x7f, 0x00}, {0x16, 0x01, 0x7f, 0x01},
-		{0x01, 0x01, 0xdc, 0x00}, {0x16, 0x01, 0xdc, 0x01},
-		{0x01, 0x01, 0xf9, 0x00}, {0x16, 0x01, 0xf9, 0x01},
-		{0xfe, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x01}
-	],
-	[
-		{0x02, 0x01, 0x1c, 0x00}, {0x09, 0x01, 0x1c, 0x00},
-		{0x17, 0x01, 0x1c, 0x00}, {0x28, 0x01, 0x1c, 0x01},
-		{0x02, 0x01, 0x1d, 0x00}, {0x09, 0x01, 0x1d, 0x00},
-		{0x17, 0x01, 0x1d, 0x00}, {0x28, 0x01, 0x1d, 0x01},
-		{0x02, 0x01, 0x1e, 0x00}, {0x09, 0x01, 0x1e, 0x00},
-		{0x17, 0x01, 0x1e, 0x00}, {0x28, 0x01, 0x1e, 0x01},
-		{0x02, 0x01, 0x1f, 0x00}, {0x09, 0x01, 0x1f, 0x00},
-		{0x17, 0x01, 0x1f, 0x00}, {0x28, 0x01, 0x1f, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x1c, 0x00}, {0x06, 0x01, 0x1c, 0x00},
-		{0x0a, 0x01, 0x1c, 0x00}, {0x0f, 0x01, 0x1c, 0x00},
-		{0x18, 0x01, 0x1c, 0x00}, {0x1f, 0x01, 0x1c, 0x00},
-		{0x29, 0x01, 0x1c, 0x00}, {0x38, 0x01, 0x1c, 0x01},
-		{0x03, 0x01, 0x1d, 0x00}, {0x06, 0x01, 0x1d, 0x00},
-		{0x0a, 0x01, 0x1d, 0x00}, {0x0f, 0x01, 0x1d, 0x00},
-		{0x18, 0x01, 0x1d, 0x00}, {0x1f, 0x01, 0x1d, 0x00},
-		{0x29, 0x01, 0x1d, 0x00}, {0x38, 0x01, 0x1d, 0x01}
-	],
-	[
-		{0x03, 0x01, 0x1e, 0x00}, {0x06, 0x01, 0x1e, 0x00},
-		{0x0a, 0x01, 0x1e, 0x00}, {0x0f, 0x01, 0x1e, 0x00},
-		{0x18, 0x01, 0x1e, 0x00}, {0x1f, 0x01, 0x1e, 0x00},
-		{0x29, 0x01, 0x1e, 0x00}, {0x38, 0x01, 0x1e, 0x01},
-		{0x03, 0x01, 0x1f, 0x00}, {0x06, 0x01, 0x1f, 0x00},
-		{0x0a, 0x01, 0x1f, 0x00}, {0x0f, 0x01, 0x1f, 0x00},
-		{0x18, 0x01, 0x1f, 0x00}, {0x1f, 0x01, 0x1f, 0x00},
-		{0x29, 0x01, 0x1f, 0x00}, {0x38, 0x01, 0x1f, 0x01}
-	],
-	/* 250 */
-	[
-		{0x02, 0x01, 0x7f, 0x00}, {0x09, 0x01, 0x7f, 0x00},
-		{0x17, 0x01, 0x7f, 0x00}, {0x28, 0x01, 0x7f, 0x01},
-		{0x02, 0x01, 0xdc, 0x00}, {0x09, 0x01, 0xdc, 0x00},
-		{0x17, 0x01, 0xdc, 0x00}, {0x28, 0x01, 0xdc, 0x01},
-		{0x02, 0x01, 0xf9, 0x00}, {0x09, 0x01, 0xf9, 0x00},
-		{0x17, 0x01, 0xf9, 0x00}, {0x28, 0x01, 0xf9, 0x01},
-		{0x00, 0x01, 0x0a, 0x01}, {0x00, 0x01, 0x0d, 0x01},
-		{0x00, 0x01, 0x16, 0x01}, {0xfa, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x03, 0x01, 0x7f, 0x00}, {0x06, 0x01, 0x7f, 0x00},
-		{0x0a, 0x01, 0x7f, 0x00}, {0x0f, 0x01, 0x7f, 0x00},
-		{0x18, 0x01, 0x7f, 0x00}, {0x1f, 0x01, 0x7f, 0x00},
-		{0x29, 0x01, 0x7f, 0x00}, {0x38, 0x01, 0x7f, 0x01},
-		{0x03, 0x01, 0xdc, 0x00}, {0x06, 0x01, 0xdc, 0x00},
-		{0x0a, 0x01, 0xdc, 0x00}, {0x0f, 0x01, 0xdc, 0x00},
-		{0x18, 0x01, 0xdc, 0x00}, {0x1f, 0x01, 0xdc, 0x00},
-		{0x29, 0x01, 0xdc, 0x00}, {0x38, 0x01, 0xdc, 0x01}
-	],
-	[
-		{0x03, 0x01, 0xf9, 0x00}, {0x06, 0x01, 0xf9, 0x00},
-		{0x0a, 0x01, 0xf9, 0x00}, {0x0f, 0x01, 0xf9, 0x00},
-		{0x18, 0x01, 0xf9, 0x00}, {0x1f, 0x01, 0xf9, 0x00},
-		{0x29, 0x01, 0xf9, 0x00}, {0x38, 0x01, 0xf9, 0x01},
-		{0x01, 0x01, 0x0a, 0x00}, {0x16, 0x01, 0x0a, 0x01},
-		{0x01, 0x01, 0x0d, 0x00}, {0x16, 0x01, 0x0d, 0x01},
-		{0x01, 0x01, 0x16, 0x00}, {0x16, 0x01, 0x16, 0x01},
-		{0xfc, 0x00, 0x00, 0x00}, {0xfc, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x02, 0x01, 0x0a, 0x00}, {0x09, 0x01, 0x0a, 0x00},
-		{0x17, 0x01, 0x0a, 0x00}, {0x28, 0x01, 0x0a, 0x01},
-		{0x02, 0x01, 0x0d, 0x00}, {0x09, 0x01, 0x0d, 0x00},
-		{0x17, 0x01, 0x0d, 0x00}, {0x28, 0x01, 0x0d, 0x01},
-		{0x02, 0x01, 0x16, 0x00}, {0x09, 0x01, 0x16, 0x00},
-		{0x17, 0x01, 0x16, 0x00}, {0x28, 0x01, 0x16, 0x01},
-		{0xfd, 0x00, 0x00, 0x00}, {0xfd, 0x00, 0x00, 0x00},
-		{0xfd, 0x00, 0x00, 0x00}, {0xfd, 0x00, 0x00, 0x00}
-	],
-	[
-		{0x03, 0x01, 0x0a, 0x00}, {0x06, 0x01, 0x0a, 0x00},
-		{0x0a, 0x01, 0x0a, 0x00}, {0x0f, 0x01, 0x0a, 0x00},
-		{0x18, 0x01, 0x0a, 0x00}, {0x1f, 0x01, 0x0a, 0x00},
-		{0x29, 0x01, 0x0a, 0x00}, {0x38, 0x01, 0x0a, 0x01},
-		{0x03, 0x01, 0x0d, 0x00}, {0x06, 0x01, 0x0d, 0x00},
-		{0x0a, 0x01, 0x0d, 0x00}, {0x0f, 0x01, 0x0d, 0x00},
-		{0x18, 0x01, 0x0d, 0x00}, {0x1f, 0x01, 0x0d, 0x00},
-		{0x29, 0x01, 0x0d, 0x00}, {0x38, 0x01, 0x0d, 0x01}
-	],
-	/* 255 */
-	[
-		{0x03, 0x01, 0x16, 0x00}, {0x06, 0x01, 0x16, 0x00},
-		{0x0a, 0x01, 0x16, 0x00}, {0x0f, 0x01, 0x16, 0x00},
-		{0x18, 0x01, 0x16, 0x00}, {0x1f, 0x01, 0x16, 0x00},
-		{0x29, 0x01, 0x16, 0x00}, {0x38, 0x01, 0x16, 0x01},
-		{0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00},
-		{0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00},
-		{0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00},
-		{0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00}
-	]
-];
diff --git a/source/vibe/http/internal/http2/hpack/tables.d b/source/vibe/http/internal/http2/hpack/tables.d
deleted file mode 100644
index 0948c9a..0000000
--- a/source/vibe/http/internal/http2/hpack/tables.d
+++ /dev/null
@@ -1,410 +0,0 @@
-//module vibe.http.internal.hpack.tables;
-module vibe.http.internal.http2.hpack.tables;
-
-import vibe.http.internal.http2.hpack.exception;
-
-import vibe.http.status;
-import vibe.http.common;
-import vibe.core.log;
-import vibe.core.sync;
-import vibe.internal.array : FixedRingBuffer;
-
-import std.variant;
-import std.traits;
-import std.meta;
-import std.range;
-import std.algorithm.iteration;
-import std.math : log10;
-import taggedalgebraic;
-
-
-alias HTTP2SettingValue = uint;
-
-// 4096 octets
-enum DEFAULT_DYNAMIC_TABLE_SIZE = 4096;
-
-/*
-	2.3.  Indexing Tables
-	HPACK uses two tables for associating header fields to indexes.  The
-	static table (see Section 2.3.1) is predefined and contains common
-	header fields (most of them with an empty value).  The dynamic table
-	(see Section 2.3.2) is dynamic and can be used by the encoder to
-	index header fields repeated in the encoded header lists.
-	These two tables are combined into a single address space for
-	defining index values (see Section 2.3.3).
- 2.3.1.  Static Table
-	The static table consists of a predefined static list of header
-	fields.  Its entries are defined in Appendix A.
- 2.3.2.  Dynamic Table
-	The dynamic table consists of a list of header fields maintained in
-	first-in, first-out order.  The first and newest entry in a dynamic
-	table is at the lowest index, and the oldest entry of a dynamic tabl
-	is at the highest index.
-	The dynamic table is initially empty.  Entries are added as each
-	header block is decompressed.
-	The dynamic table is initially empty.  Entries are added as each
-	header block is decompressed.
-	The dynamic table can contain duplicate entries (i.e., entries with
-	the same name and same value).  Therefore, duplicate entries MUST NOT
-	be treated as an error by a decoder.
-	The encoder decides how to update the dynamic table and as such can
-	control how much memory is used by the dynamic table.  To limit the
-	memory requirements of the decoder, the dynamic table size is
-	strictly bounded (see Section 4.2).
-	The decoder updates the dynamic table during the processing of a list
-	of header field representations (see Section 3.2).
-*/
-
-// wraps a header field = name:value
-struct HTTP2HeaderTableField {
-	private union HeaderValue {
-		string str;
-		string[] strarr;
-		HTTPStatus status;
-		HTTPMethod method;
-	}
-
-	string name;
-	TaggedAlgebraic!HeaderValue value;
-	bool index = true;
-	bool neverIndex = false;
-
-	// initializers
-	static foreach(t; __traits(allMembers, HeaderValue)) {
-		mixin("this(string n, " ~
-				typeof(__traits(getMember, HeaderValue, t)).stringof ~
-				" v) @safe { name = n; value = v; }");
-	}
-
-	this(R)(R t) @safe
-		if(is(ElementType!R : string))
-	{
-		assert(t.length == 2, "Invalid range for HTTP2HeaderTableField initializer");
-		this(t[0], t[1]);
-	}
-}
-
-// fixed as per HPACK RFC
-immutable size_t STATIC_TABLE_SIZE = 61;
-
-/** static table to index most common headers
-  * fixed size, fixed order of entries (read only)
-  * cannot be updated while decoding a header block
-  */
-static immutable HTTP2HeaderTableField[STATIC_TABLE_SIZE+1] StaticTable;
-
-shared static this() {
-	StaticTable = [
-		HTTP2HeaderTableField("",""), // 0 index is not allowed
-		HTTP2HeaderTableField(":authority", ""),
-		HTTP2HeaderTableField(":method", HTTPMethod.GET),
-		HTTP2HeaderTableField(":method", HTTPMethod.POST),
-		HTTP2HeaderTableField(":path", "/"),
-		HTTP2HeaderTableField(":path", "/index.html"),
-		HTTP2HeaderTableField(":scheme", "http"),
-		HTTP2HeaderTableField(":scheme", "https"),
-		HTTP2HeaderTableField(":status", HTTPStatus.ok), 					// 200
-		HTTP2HeaderTableField(":status", HTTPStatus.noContent), 				// 204
-		HTTP2HeaderTableField(":status", HTTPStatus.partialContent), 		// 206
-		HTTP2HeaderTableField(":status", HTTPStatus.notModified), 			// 304
-		HTTP2HeaderTableField(":status", HTTPStatus.badRequest), 			// 400
-		HTTP2HeaderTableField(":status", HTTPStatus.notFound), 				// 404
-		HTTP2HeaderTableField(":status", HTTPStatus.internalServerError), 	// 500
-		HTTP2HeaderTableField("accept-charset", ""),
-		HTTP2HeaderTableField("accept-encoding", ["gzip", "deflate"]),
-		HTTP2HeaderTableField("accept-language", ""),
-		HTTP2HeaderTableField("accept-ranges", ""),
-		HTTP2HeaderTableField("accept", ""),
-		HTTP2HeaderTableField("access-control-allow-origin", ""),
-		HTTP2HeaderTableField("age", ""),
-		HTTP2HeaderTableField("allow", ""),
-		HTTP2HeaderTableField("authorization", ""),
-		HTTP2HeaderTableField("cache-control", ""),
-		HTTP2HeaderTableField("content-disposition", ""),
-		HTTP2HeaderTableField("content-encoding", ""),
-		HTTP2HeaderTableField("content-language", ""),
-		HTTP2HeaderTableField("content-length", ""),
-		HTTP2HeaderTableField("content-location", ""),
-		HTTP2HeaderTableField("content-range", ""),
-		HTTP2HeaderTableField("content-type", ""),
-		HTTP2HeaderTableField("cookie", ""),
-		HTTP2HeaderTableField("date", ""),
-		HTTP2HeaderTableField("etag", ""),
-		HTTP2HeaderTableField("expect", ""),
-		HTTP2HeaderTableField("expires", ""),
-		HTTP2HeaderTableField("from", ""),
-		HTTP2HeaderTableField("host", ""),
-		HTTP2HeaderTableField("if-match", ""),
-		HTTP2HeaderTableField("if-modified-since", ""),
-		HTTP2HeaderTableField("if-none-match", ""),
-		HTTP2HeaderTableField("if-range", ""),
-		HTTP2HeaderTableField("if-unmodified-since", ""),
-		HTTP2HeaderTableField("last-modified", ""),
-		HTTP2HeaderTableField("link", ""),
-		HTTP2HeaderTableField("location", ""),
-		HTTP2HeaderTableField("max-forwards", ""),
-		HTTP2HeaderTableField("proxy-authenticate", ""),
-		HTTP2HeaderTableField("proxy-authorization", ""),
-		HTTP2HeaderTableField("range", ""),
-		HTTP2HeaderTableField("referer", ""),
-		HTTP2HeaderTableField("refresh", ""),
-		HTTP2HeaderTableField("retry-after", ""),
-		HTTP2HeaderTableField("server", ""),
-		HTTP2HeaderTableField("set-cookie", ""),
-		HTTP2HeaderTableField("strict-transport-security", ""),
-		HTTP2HeaderTableField("transfer-encoding", ""),
-		HTTP2HeaderTableField("user-agent", ""),
-		HTTP2HeaderTableField("vary", ""),
-		HTTP2HeaderTableField("via", ""),
-		HTTP2HeaderTableField("www-authenticate", "")
-	];
-}
-
-private ref immutable(HTTP2HeaderTableField) getStaticTableEntry(size_t key) @safe @nogc
-{
-    assert(key > 0 && key < StaticTable.length, "Invalid static table index");
-    return StaticTable[key];
-}
-
-// compute size of an entry as per RFC
-HTTP2SettingValue computeEntrySize(HTTP2HeaderTableField f) @safe
-{
-	alias k = HTTP2HeaderTableField.value.Kind;
-	HTTP2SettingValue ret = cast(HTTP2SettingValue)f.name.length + 32;
-
-	final switch (f.value.kind) {
-		case k.str: ret += f.value.get!string.length; break;
-		case k.strarr: ret += f.value.get!(string[]).map!(s => s.length).sum(); break;
-		case k.status: ret += cast(size_t)log10(cast(int)f.value.get!HTTPStatus) + 1; break;
-		case k.method: ret += httpMethodString(f.value.get!HTTPMethod).length; break;
-	}
-	return ret;
-}
-
-private struct DynamicTable {
-	private {
-		// default table is 4096 octs. / n. octets of an empty HTTP2HeaderTableField struct (32)
-		FixedRingBuffer!(HTTP2HeaderTableField, DEFAULT_DYNAMIC_TABLE_SIZE/HTTP2HeaderTableField.sizeof, false) m_table;
-
-		// extra table is a circular buffer, initially empty, used when
-		// maxsize > DEFAULT_DYNAMIC_TABLE_SIZE
-		FixedRingBuffer!HTTP2HeaderTableField m_extraTable;
-
-		// as defined in SETTINGS_HEADER_TABLE_SIZE
-		HTTP2SettingValue m_maxsize;
-
-		// current size
-		size_t m_size = 0;
-
-		// last index (table index starts from 1)
-		size_t m_index = 0;
-
-		// extra table index (starts from 0)
-		size_t m_extraIndex = 0;
-	}
-
-	this(HTTP2SettingValue ms) @trusted
-	{
-		m_maxsize = ms;
-
-		if(ms > DEFAULT_DYNAMIC_TABLE_SIZE) {
-			m_extraTable.capacity = (ms - DEFAULT_DYNAMIC_TABLE_SIZE)/HTTP2HeaderTableField.sizeof;
-		}
-	}
-
-	@property void dispose() { m_extraTable.dispose(); }
-
-	// number of elements inside dynamic table
-	@property size_t size() @safe @nogc { return m_size; }
-
-	@property size_t index() @safe @nogc { return m_index; }
-
-	HTTP2HeaderTableField opIndex(size_t idx) @safe @nogc
-	{
-		size_t totIndex = m_index + m_extraIndex;
-		assert(idx > 0 && idx <= totIndex, "Invalid table index");
-		if(idx > m_index && idx < totIndex) return m_extraTable[idx-m_index];
-		else return m_table[idx-1];
-	}
-
-	// insert at the head
-	void insert(HTTP2HeaderTableField header) @safe
-	{
-		auto nsize = computeEntrySize(header);
-		// ensure that the new entry does not exceed table capacity
-		while(m_size + nsize > m_maxsize) {
-			//logDebug("Maximum header table size exceeded"); // requires gc
-			remove();
-		}
-
-		// insert
-		if(m_size + nsize > DEFAULT_DYNAMIC_TABLE_SIZE) {
-			m_extraTable.put(header);
-			m_extraIndex++;
-		} else {
-			m_table.put(header);
-			m_index++;
-		}
-
-		m_size += nsize;
-	}
-
-	// evict an entry
-	void remove() @safe
-	{
-		enforceHPACK(!m_table.empty, "Cannot remove element from empty table");
-
-		if(m_extraIndex > 0) {
-			m_size -= computeEntrySize(m_extraTable.back);
-			m_extraTable.popFront();
-			m_extraIndex--;
-		} else {
-			m_size -= computeEntrySize(m_table.back);
-			m_table.popFront();
-			m_index--;
-		}
-	}
-
-	/** new size should be lower than the max set one
-	  * after size is successfully changed, an ACK has to be sent
-	  * multiple changes between two header fields are possible
-	  * if multiple changes occour, only the smallest maximum size
-	  * requested has to be acknowledged
-	*/
-	void updateSize(HTTP2SettingValue sz) @safe @nogc
-	{
-		m_maxsize = sz;
-	}
-}
-
-unittest {
-	// static table
-	auto a = getStaticTableEntry(1);
-	static assert(is(typeof(a) == immutable(HTTP2HeaderTableField)));
-	assert(a.name == ":authority");
-	assert(getStaticTableEntry(2).name == ":method" && getStaticTableEntry(2).value == HTTPMethod.GET);
-
-	DynamicTable dt = DynamicTable(DEFAULT_DYNAMIC_TABLE_SIZE+2048);
-	assert(dt.size == 0);
-	assert(dt.index == 0);
-
-	// dynamic table
-	import std.algorithm.comparison : equal;
-
-	auto h = HTTP2HeaderTableField("test", "testval");
-	dt.insert(h);
-	assert(dt.size > 0);
-	assert(dt.index == 1);
-	assert(dt[dt.index].name == "test");
-
-	dt.remove();
-	assert(dt.size == 0);
-	assert(dt.index == 0);
-}
-
-/** provides an unified address space through operator overloading
-  * this is the only interface that will be used for the two tables
-  */
-struct IndexingTable {
-	private {
-		DynamicTable m_dynamic;
-		RecursiveTaskMutex m_lock;
-	}
-
-	// requires the maximum size for the dynamic table
-	this(HTTP2SettingValue ms) @trusted
-	{
-		m_dynamic = DynamicTable(ms);
-		m_lock = new RecursiveTaskMutex;
-	}
-
-	~this()
-	{
-		m_dynamic.dispose();
-	}
-
-	@property size_t size() @safe @nogc { return STATIC_TABLE_SIZE + m_dynamic.index + 1; }
-
-	@property bool empty() @safe @nogc { return m_dynamic.size == 0; }
-
-	@property HTTP2HeaderTableField front() @safe { return this[0]; }
-
-	@property void popFront() @safe
-	{
-		assert(!empty, "Cannot call popFront on an empty dynamic table");
-		m_lock.performLocked!({
-			m_dynamic.remove();
-		});
-	}
-
-	// element retrieval
-	HTTP2HeaderTableField opIndex(size_t idx) @safe
-	{
-		enforceHPACK(idx > 0 && idx < size(), "Invalid HPACK table index");
-
-		if (idx < STATIC_TABLE_SIZE+1) return getStaticTableEntry(idx);
-		else return m_dynamic[m_dynamic.index - (idx - STATIC_TABLE_SIZE) + 1];
-	}
-
-	// dollar == size
-	// +1 to mantain consistency with the dollar operator
-	size_t opDollar() @safe @nogc
-	{
-		return size();
-	}
-
-	// assignment can only be done on the dynamic table
-	void insert(HTTP2HeaderTableField hf) @safe
-	{
-		m_lock.performLocked!({
-			m_dynamic.insert(hf);
-		});
-	}
-
-	// update max dynamic table size
-	void updateSize(HTTP2SettingValue sz) @safe
-	{
-		m_lock.performLocked!({
-			m_dynamic.updateSize(sz);
-		});
-	}
-}
-
-unittest {
-	// indexing table
-	IndexingTable table = IndexingTable(DEFAULT_DYNAMIC_TABLE_SIZE);
-	assert(table[2].name == ":method" && table[2].value == HTTPMethod.GET);
-
-	// assignment
-	auto h = HTTP2HeaderTableField("test", "testval");
-	table.insert(h);
-	assert(table.size == STATIC_TABLE_SIZE + 2);
-	assert(table[STATIC_TABLE_SIZE+1].name == "test");
-
-	auto h2 = HTTP2HeaderTableField("test2", "testval2");
-	table.insert(h2);
-	assert(table.size == STATIC_TABLE_SIZE + 3);
-	assert(table[STATIC_TABLE_SIZE+1].name == "test2");
-
-	// dollar
-	auto h3 = HTTP2HeaderTableField("test3", "testval3");
-	table.insert(h3);
-	assert(table.size == STATIC_TABLE_SIZE + 4);
-	assert(table[$-1].name == "test");
-	assert(table[$-2].name == "test2");
-	assert(table[STATIC_TABLE_SIZE+1].name == "test3");
-
-	// test removal on full table
-
-	HTTP2SettingValue hts = computeEntrySize(h); // only one header
-	IndexingTable t2 = IndexingTable(hts);
-	t2.insert(h);
-	t2.insert(h);
-	assert(t2.size == STATIC_TABLE_SIZE + 2);
-	assert(t2[STATIC_TABLE_SIZE + 1].name == "test");
-	assert(t2[$ - 1].name == "test");
-
-	auto h4 = HTTP2HeaderTableField("","");
-	hts = computeEntrySize(h4); // entry size of an empty field is 32 octets
-	assert(hts == 32);
-}
diff --git a/source/vibe/http/internal/http2/hpack/util.d b/source/vibe/http/internal/http2/hpack/util.d
deleted file mode 100644
index 883d338..0000000
--- a/source/vibe/http/internal/http2/hpack/util.d
+++ /dev/null
@@ -1,14 +0,0 @@
-module vibe.http.internal.http2.hpack.util;
-
-import std.range;
-
-// decode ubyte as integer representation according to prefix
-size_t toInteger(ubyte bbuf, uint prefix) @safe @nogc
-{
-	assert(prefix < 8, "Prefix must be at most an octet long");
-
-	bbuf = bbuf & ((1 << (8 - prefix)) - 1);
-	assert(bbuf >= 0, "Invalid decoded integer");
-
-	return bbuf;
-}
diff --git a/source/vibe/http/internal/http2/http2.d b/source/vibe/http/internal/http2/http2.d
deleted file mode 100644
index b21b00d..0000000
--- a/source/vibe/http/internal/http2/http2.d
+++ /dev/null
@@ -1,963 +0,0 @@
-module vibe.http.internal.http2.http2;
-
-import vibe.http.internal.http2.error;
-import vibe.http.internal.http2.multiplexing;
-import vibe.http.internal.http2.frame;
-import vibe.http.internal.http2.settings;
-import vibe.http.internal.http2.exchange;
-import vibe.http.internal.http2.hpack.tables;
-import vibe.http.internal.http2.hpack.hpack;
-import vibe.http.internal.http2.hpack.exception;
-import vibe.http.server;
-
-import vibe.core.log;
-import vibe.core.net;
-import vibe.core.core;
-import vibe.core.stream;
-import vibe.stream.tls;
-import vibe.internal.array;
-import vibe.internal.allocator;
-import vibe.internal.utilallocator : RegionListAllocator;
-import vibe.internal.freelistref;
-import vibe.internal.interfaceproxy;
-
-import std.range;
-import std.base64;
-import std.traits;
-import std.bitmanip; // read from ubyte (decoding)
-import std.typecons;
-import std.conv : to;
-import std.exception : enforce;
-import std.algorithm : canFind; // alpn callback
-import std.algorithm.iteration;
-import std.variant : Algebraic;
-
-/*
-   3.2.  Starting HTTP/2 for "http" URIs
-
-   A client that makes a request for an "http" URI without prior
-   knowledge about support for HTTP/2 on the next hop uses the HTTP
-   Upgrade mechanism (Section 6.7 of [RFC7230]).  The client does so by
-   making an HTTP/1.1 request that includes an Upgrade header field with
-   the "h2c" token.  Such an HTTP/1.1 request MUST include exactly one
-   HTTP2-Settings (Section 3.2.1) header field.
-
-   For example:
-
-	 GET / HTTP/1.1
-	 Host: server.example.com
-	 Connection: Upgrade, HTTP2-Settings
-	 Upgrade: h2c
-	 HTTP2-Settings: 
-
-   Requests that contain a payload body MUST be sent in their entirety
-   before the client can send HTTP/2 frames.  This means that a large
-   request can block the use of the connection until it is completely
-   sent.
-
-   If concurrency of an initial request with subsequent requests is
-   important, an OPTIONS request can be used to perform the upgrade to
-   HTTP/2, at the cost of an additional round trip.
-
-
-   A server that does not support HTTP/2 can respond to the request as
-   though the Upgrade header field were absent:
-
-	 HTTP/1.1 200 OK
-	 Content-Length: 243
-	 Content-Type: text/html
-
-	 ...
-
-   A server MUST ignore an "h2" token in an Upgrade header field.
-   Presence of a token with "h2" implies HTTP/2 over TLS, which is
-   instead negotiated as described in Section 3.3.
-
-   A server that supports HTTP/2 accepts the upgrade with a 101
-   (Switching Protocols) response.  After the empty line that terminates
-   the 101 response, the server can begin sending HTTP/2 frames.  These
-   frames MUST include a response to the request that initiated the
-   upgrade.
-
-   For example:
-
-	 HTTP/1.1 101 Switching Protocols
-	 Connection: Upgrade
-	 Upgrade: h2c
-
-	 [ HTTP/2 connection ...
-
-   The first HTTP/2 frame sent by the server MUST be a server connection
-   preface (Section 3.5) consisting of a SETTINGS frame (Section 6.5).
-   Upon receiving the 101 response, the client MUST send a connection
-   preface (Section 3.5), which includes a SETTINGS frame.
-
-   The HTTP/1.1 request that is sent prior to upgrade is assigned a
-   stream identifier of 1 (see Section 5.1.1) with default priority
-   values (Section 5.3.5).  Stream 1 is implicitly "half-closed" from
-   the client toward the server (see Section 5.1), since the request is
-   completed as an HTTP/1.1 request.  After commencing the HTTP/2
-   connection, stream 1 is used for the response.
-
-3.2.1.  HTTP2-Settings Header Field
-
-   A request that upgrades from HTTP/1.1 to HTTP/2 MUST include exactly
-   one "HTTP2-Settings" header field.  The HTTP2-Settings header field
-   is a connection-specific header field that includes parameters that
-   govern the HTTP/2 connection, provided in anticipation of the server
-   accepting the request to upgrade.
-
-	 HTTP2-Settings	= token68
-
-   A server MUST NOT upgrade the connection to HTTP/2 if this header
-   field is not present or if more than one is present.  A server MUST
-   NOT send this header field.
-
-   The content of the HTTP2-Settings header field is the payload of a
-   SETTINGS frame (Section 6.5), encoded as a base64url string (that is,
-   the URL- and filename-safe Base64 encoding described in Section 5 of
-   [RFC4648], with any trailing '=' characters omitted).  The ABNF
-   [RFC5234] production for "token68" is defined in Section 2.1 of
-   [RFC7235].
-
-   Since the upgrade is only intended to apply to the immediate
-   connection, a client sending the HTTP2-Settings header field MUST
-   also send "HTTP2-Settings" as a connection option in the Connection
-   header field to prevent it from being forwarded (see Section 6.1 of
-   [RFC7230]).
-
-   A server decodes and interprets these values as it would any other
-   SETTINGS frame.  Explicit acknowledgement of these settings
-   (Section 6.5.3) is not necessary, since a 101 response serves as
-   implicit acknowledgement.  Providing these values in the upgrade
-   request gives a client an opportunity to provide parameters prior to
-   receiving any frames from the server.
-
-*/
-
-/**
-  * an ALPN callback which can be used to detect the "h2" protocol
-  * must be set before initializing the server with 'listenHTTP'
-  * if the protocol is not set, it replies with HTTP/1.1
-  */
-TLSALPNCallback http2Callback = (string[] choices) {
-	if (choices.canFind("h2")) return "h2";
-	else return "http/1.1";
-};
-
-private alias TLSStreamType = ReturnType!(createTLSStreamFL!(InterfaceProxy!Stream));
-
-/* ==================================================== */
-/* 				CONNECTION INITIALIZATION				*/
-/* ==================================================== */
-
-/** h2c protocol switching ONLY: Check if SETTINGS payload is valid by trying to decode it
-  * if !valid, close connection and refuse to upgrade (RFC)
-  * if valid, send SWITCHING_PROTOCOL response and start an HTTP/2 connection handler
-  */
-bool startHTTP2Connection(ConnectionStream, H)(ConnectionStream connection, string h2settings,
-		HTTP2ServerContext context, HTTPServerResponse switchRes, H headers, string st,
-		IAllocator alloc, ubyte[] resBody) @safe
-	if (isConnectionStream!ConnectionStream)
-{
-	// init settings
-	HTTP2Settings settings;
-	logTrace("Starting HTTP/2 connection");
-
-	// try decoding settings
-	if (settings.decode!Base64URL(h2settings)) {
-
-		context.settings = settings;
-
-		// initialize IndexingTable (HPACK)
-		() @trusted {
-
-			if(!context.hasTable) context.table = FreeListRef!IndexingTable(context.settings.headerTableSize);
-
-			// save response converted to HTTP/2
-			context.resFrame = alloc.makeArray!ubyte(buildHeaderFrame!(StartLine.RESPONSE)
-						(st, headers, context, alloc));
-			context.resFrame ~= resBody;
-
-		} ();
-
-		// send response
-		switchRes.switchToHTTP2(&handleHTTP2Connection!ConnectionStream, context);
-		return true;
-
-	} else {
-		// reply with a 400 (bad request) header
-		switchRes.sendBadRequest();
-		connection.close;
-		return false;
-	}
-}
-
-/** client AND server should send a connection preface
-  * server should receive a connection preface from the client + SETTINGS Frame
-  * server connection preface consists of a SETTINGS Frame
-  */
-void handleHTTP2Connection(ConnectionStream)(ConnectionStream stream,
-		TCPConnection connection, HTTP2ServerContext context, bool priorKnowledge=false) @safe
-	if (isConnectionStream!ConnectionStream || is(ConnectionStream : TLSStreamType))
-{
-	logTrace("HTTP/2 Connection Handler");
-
-	// read the connection preface
-	if(!priorKnowledge) {
-		ubyte[24] h2connPreface;
-		stream.read(h2connPreface);
-
-		if(h2connPreface != "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") {
-			logDebug("Ignoring invalid HTTP/2 client connection preface");
-			return;
-		}
-		logTrace("Received client http2 connection preface");
-	}
-
-	// initialize Frame handler
-	handleHTTP2FrameChain(stream, connection, context);
-}
-
-/* ==================================================== */
-/* 					FRAME HANDLING						*/
-/* ==================================================== */
-
-/// async frame handler: in charge of closing the connection if no data flows
-private void handleHTTP2FrameChain(ConnectionStream)(ConnectionStream stream, TCPConnection
-		connection, HTTP2ServerContext context) @safe
-	if (isConnectionStream!ConnectionStream || is(ConnectionStream : TLSStream))
-{
-	logTrace("HTTP/2 Frame Chain Handler");
-
-	static struct CB {
-		ConnectionStream stream;
-		TCPConnection connection;
-		HTTP2ServerContext context;
-
-		void opCall(bool st)
-		{
-			if (!st) connection.close;
-			else runTask(&handleHTTP2FrameChain, stream, connection, context);
-		}
-	}
-
-	while(true) {
-		CB cb = {stream, connection, context};
-		auto st = connection.waitForDataAsync(cb);
-
-		final switch(st) {
-			case WaitForDataAsyncStatus.waiting:
-				return;
-
-			case WaitForDataAsyncStatus.noMoreData:
-				stream.finalize();
-				connection.close();
-				return;
-
-			case WaitForDataAsyncStatus.dataAvailable:
-				// start the frame handler
-				bool close = handleHTTP2Frame(stream, connection, context);
-
-				// determine if this connection needs to be closed
-				if(close) {
-					logTrace("Closing connection.");
-					stream.finalize();
-					connection.close();
-					return;
-				}
-		}
-	}
-}
-
-/// initializes an allocator and handles stream closing
-private bool handleHTTP2Frame(ConnectionStream)(ConnectionStream stream, TCPConnection
-		connection, HTTP2ServerContext context) @trusted
-	if (isConnectionStream!ConnectionStream || is(ConnectionStream : TLSStream))
-{
-	import vibe.internal.utilallocator: RegionListAllocator;
-	logTrace("HTTP/2 Frame Handler");
-
-	bool close = false;
-
-	() @trusted {
-		version (VibeManualMemoryManagement)
-			scope alloc = new RegionListAllocator!(shared(Mallocator), false)
-			(1024, Mallocator.instance);
-		else
-			scope alloc = new RegionListAllocator!(shared(GCAllocator), true)
-				(1024, GCAllocator.instance);
-
-		if(!context.hasMultiplexer) context.multiplexer = FreeListRef!HTTP2Multiplexer(
-				alloc,
-				context.settings.maxConcurrentStreams,
-				context.settings.initialWindowSize,
-				context.settings.headerTableSize);
-
-		if(!context.hasTable) context.table = FreeListRef!IndexingTable(context.settings.headerTableSize);
-
-		// create a HTTP/2 Stream
-		auto h2stream = HTTP2ConnectionStream!ConnectionStream(stream, 0, alloc);
-
-
-		close = handleFrameAlloc(h2stream, connection, context, alloc);
-
-		// if stream has to be closed
-		if(h2stream.state == HTTP2StreamState.CLOSED) {
-			try {
-				closeStream(context.multiplexer, h2stream.streamId);
-			} catch(Exception e) {
-				logWarn("Unable to close stream: " ~ e.msg);
-				close = true;
-			}
-		}
-	} ();
-
-	return close;
-}
-
-/// used (mixin) to check the validity of the received Frame w.r.to its stream ID
-private const string checkvalid = "enforceHTTP2(valid, \"Invalid stream ID\", HTTP2Error.STREAM_CLOSED);";
-
-/** Receives an HTTP2ConnectionStream, and handles the data received by decoding frames
-  * Currently supports simple requests / responses
-  * Stream Lifecycle is treated according to RFC 7540, Section 5.1
-*/
-private bool handleFrameAlloc(ConnectionStream)(ref ConnectionStream stream, TCPConnection connection,
-		HTTP2ServerContext context, IAllocator alloc) @trusted
-{
-	logTrace("HTTP/2 Frame Handler (Alloc)");
-
-	uint len = 0;
-
-	// payload buffer
-	auto rawBuf = AllocAppender!(ubyte[])(alloc);
-	auto payload = AllocAppender!(ubyte[])(alloc);
-
-	// Frame properties
-	bool endStream = false;
-	bool endHeaders = false;
-	bool isAck = false;
-	bool close = false;
-	scope HTTP2FrameStreamDependency sdep;
-
-	// frame struct
-	scope HTTP2FrameHeader header;
-
-/* ==================================================== */
-/* 				read received header 					*/
-/* ==================================================== */
-	if(stream.canRead) {
-		try {
-			len = stream.readHeader(rawBuf);
-		} catch (UncaughtException e) {
-			// failed reading from stream, do not close the connection
-			stream.state = HTTP2StreamState.CLOSED;
-			return false;
-		}
-	} else {
-		// failed reading from stream, do not close the connection
-		//stream.state = HTTP2StreamState.CLOSED;
-		return false;
-	}
-
-
-	// adjust buffer sizes
-	rawBuf.reserve(len);
-	payload.reserve(len);
-
-/* ==================================================== */
-/* 				read received payload 					*/
-/* ==================================================== */
-	if(len) stream.readPayload(rawBuf, len);
-
-
-/* ==================================================== */
-/* 				parse received Frame 					*/
-/* ==================================================== */
-	try {
-		header = payload.unpackHTTP2Frame(rawBuf.data, endStream, endHeaders, isAck, sdep);
-	} catch (HTTP2Exception e) {
-		if (stream.state != HTTP2StreamState.IDLE || stream.streamId == 0) {
-			auto f = buildGOAWAYFrame(stream.streamId, e.code);
-			stream.write(f);
-			stream.state = HTTP2StreamState.CLOSED;
-			logWarn("%s: %s", "Sent GOAWAY Frame", e.message);
-			return true;
-		} else {
-			logDebug("Ignoring unsupported extension header.");
-			return false;
-		}
-	} catch (Exception e) {
-		logWarn(e.msg);
-	}
-
-/* ==================================================== */
-/*    register stream on MUX and determine Frame type	*/
-/* ==================================================== */
-	try {
-		auto valid = registerStream(context.multiplexer, header.streamId);
-
-		logDebug("Received: "~to!string(header.type)~" on streamID "~to!string(header.streamId));
-
-		enforceHTTP2(header.streamId % 2 != 0 || header.streamId == 0, "Clients cannot register even streams", HTTP2Error.PROTOCOL_ERROR);
-
-		if(stream.needsContinuation) enforceHTTP2(header.type == HTTP2FrameType.CONTINUATION,
-				"Expected continuation frame", HTTP2Error.PROTOCOL_ERROR);
-
-		stream.streamId = header.streamId;
-
-		final switch(header.type) {
-/* ==================================================== */
-/* 					DATA Frame (TODO)					*/
-/* ==================================================== */
-			case HTTP2FrameType.DATA:
-				mixin(checkvalid);
-
-				if(endStream) {
-					if(stream.state == HTTP2StreamState.HALF_CLOSED_LOCAL) {
-						stream.state = HTTP2StreamState.CLOSED;
-						closeStream(context.multiplexer, stream.streamId);
-
-					} else if(stream.state == HTTP2StreamState.OPEN) {
-						stream.state = HTTP2StreamState.HALF_CLOSED_REMOTE;
-
-					} else if(stream.state == HTTP2StreamState.IDLE) {
-						enforceHTTP2(false, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
-
-					} else {
-						enforceHTTP2(false, "Stream closed", HTTP2Error.STREAM_CLOSED);
-					}
-				}
-				break;
-
-/* ==================================================== */
-/* 					HEADERS Frame 						*/
-/* ==================================================== */
-			case HTTP2FrameType.HEADERS:
-				mixin(checkvalid);
-				enforceHTTP2(stream.streamId > 0, "Invalid stream ID", HTTP2Error.PROTOCOL_ERROR);
-
-				stream.state = HTTP2StreamState.OPEN;
-				if(sdep.isSet) {
-					// update stream dependency with data in `sdep`
-				}
-
-				// save the header block for processing
-				stream.putHeaderBlock(payload.data);
-
-				if(endStream) {
-					stream.state = HTTP2StreamState.HALF_CLOSED_REMOTE;
-				}
-
-				// parse headers in payload
-				if(endHeaders) {
-					logDebug("Received full HEADERS block");
-					handleHTTP2HeadersFrame(stream, connection, context, alloc);
-
-				} else {
-					// wait for the next CONTINUATION frame until end_headers flag is set
-					// END_STREAM flag does not count in this case
-					logDebug("Incomplete HEADERS block, waiting for CONTINUATION frame.");
-					close = handleFrameAlloc(stream, connection, context, alloc);
-				}
-
-				break;
-
-/* ==================================================== */
-/* 					PRIORITY Frame (TODO) 				*/
-/* ==================================================== */
-			case HTTP2FrameType.PRIORITY:
-				// do not check validity since PRIORITY frames can be received on CLOSED
-				// streams
-
-				enforceHTTP2(stream.streamId > 0, "Invalid stream ID", HTTP2Error.PROTOCOL_ERROR);
-				// update stream dependency with data in `sdep`
-				break;
-
-/* ==================================================== */
-/* 					RST_STREAM Frame					*/
-/* ==================================================== */
-			case HTTP2FrameType.RST_STREAM:
-				enforceHTTP2(stream.state != HTTP2StreamState.IDLE || !valid,
-						"Invalid state", HTTP2Error.PROTOCOL_ERROR);
-
-				// reset stream in `closed` state
-				if(stream.state != HTTP2StreamState.CLOSED) {
-					closeStream(context.multiplexer, stream.streamId);
-				}
-
-				logDebug("RST_STREAM: Stream %d closed,  error: %s",
-						stream.streamId, cast(HTTP2Error)fromBytes(payload.data,4));
-				break;
-
-/* ==================================================== */
-/* 					SETTINGS Frame 						*/
-/* ==================================================== */
-			case HTTP2FrameType.SETTINGS:
-				if(!isAck) {
-					handleHTTP2SettingsFrame(stream, connection, payload.data, header, context);
-				} else {
-					enforceHTTP2(payload.data.length == 0,
-							"Invalid SETTINGS ACK (payload not empty)", HTTP2Error.FRAME_SIZE_ERROR);
-					logDebug("Received SETTINGS ACK");
-				}
-				break;
-
-/* ==================================================== */
-/* 			      PUSH_PROMISE Frame 					*/
-/* ==================================================== */
-			case HTTP2FrameType.PUSH_PROMISE:
-				enforceHTTP2(false,
-						"Client should not send PUSH_PROMISE Frames.", HTTP2Error.PROTOCOL_ERROR);
-				break;
-
-/* ==================================================== */
-/* 				      PING Frame 						*/
-/* ==================================================== */
-			case HTTP2FrameType.PING:
-				if(!isAck) {
-					// acknowledge ping with PING ACK Frame
-					FixedAppender!(ubyte[], 17) buf;
-					buf.createHTTP2FrameHeader(len, header.type, 0x1, header.streamId);
-
-					// write PING Frame header
-					stream.write(buf.data);
-					// write PING Frame payload (equal to the received one)
-					stream.write(payload.data);
-					logDebug("Sent PING ACK response");
-				}
-				break;
-
-/* ==================================================== */
-/* 				     GOAWAY Frame 						*/
-/* ==================================================== */
-			case HTTP2FrameType.GOAWAY:
-				logDebug("Received GOAWAY Frame. Closing connection");
-
-				stream.state = HTTP2StreamState.CLOSED;
-				closeStream(context.multiplexer, stream.streamId);
-				close = true;
-
-				break;
-
-/* ==================================================== */
-/* 				     WINDOW_UPDATE Frame 				*/
-/* ==================================================== */
-			case HTTP2FrameType.WINDOW_UPDATE:
-				// can be received on closed streams (in case of pending data)
-				enforceHTTP2(stream.state != HTTP2StreamState.IDLE || !valid ||
-						stream.streamId == 0, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
-
-				auto inc = fromBytes(payload.data, 4);
-				uint maxinc = 1 << 31;
-				enforceHTTP2(inc > 0, "Invalid WINDOW_UPDATE increment", HTTP2Error.PROTOCOL_ERROR);
-
-				// connection-based control window must be updated
-				auto cw = connectionWindow(context.multiplexer);
-				enforceHTTP2(cw + inc < maxinc, "Reached maximum WINDOW size",
-						HTTP2Error.FLOW_CONTROL_ERROR);
-				updateConnectionWindow(context.multiplexer, cw + inc);
-
-				// per-stream control window must be updated (together with cw)
-				if(stream.streamId > 0) {
-					auto scw = streamConnectionWindow(context.multiplexer, stream.streamId);
-					enforceHTTP2(scw + inc < maxinc, "Reached maximum WINDOW size",
-							HTTP2Error.FLOW_CONTROL_ERROR);
-
-					updateStreamConnectionWindow(context.multiplexer, stream.streamId, scw + inc);
-				}
-
-				// notify waiting DATA tasks if needed
-				if(checkCondition(context.multiplexer, stream.streamId)) {
-					logDebug("Notifying stopped tasks");
-					notifyCondition(context.multiplexer);
-					yield();
-				}
-				break;
-
-/* ==================================================== */
-/* 				     CONTINUATION Frame
-/* ==================================================== */
-			case HTTP2FrameType.CONTINUATION:
-				// must be received immediately after a HEADERS Frame or a
-				// CONTINUATION Frame
-				enforceHTTP2(stream.state != HTTP2StreamState.IDLE, "Invalid state",
-						HTTP2Error.PROTOCOL_ERROR);
-
-				// add the received block to buffer
-				stream.putHeaderBlock(payload.data);
-
-				// process header block fragment in payload
-				if(endHeaders) {
-					logDebug("Received full HEADERS block");
-					handleHTTP2HeadersFrame(stream, connection, context, alloc);
-				} else {
-					logDebug("Incomplete HEADERS block, waiting for CONTINUATION frame.");
-					handleFrameAlloc(stream, connection, context, alloc);
-				}
-				break;
-		}
-
-/* ==================================================== */
-/* 				  `h2c`: First Response
-/* ==================================================== */
-		static if(!is(ConnectionStream : TLSStream)) {
-
-			if (context.resFrame) {
-				auto l = context.resFrame.takeExactly(3).fromBytes(3) + 9;
-
-				if(l < context.settings.maxFrameSize)
-				{
-					auto isEndStream = (context.resFrame.length > l) ? 0x0 : 0x1;
-
-					context.resFrame[4] += 0x4 + isEndStream;
-
-					try {
-						stream.write(context.resFrame[0..l]);
-					} catch (Exception e) {
-						logWarn("Unable to write HEADERS Frame to stream");
-					}
-
-				} else {
-					// TODO CONTINUATION frames
-					assert(false);
-				}
-
-
-				auto resBody = context.resFrame[l..$];
-				alloc.dispose(context.resFrame);
-
-				// send DATA (body) if present
-				// since the first response is part of HTTP/2 initialization,
-				// this task is NOT executed asynchronously (for now) TODO
-				if(resBody.length > 0) {
-
-					auto dataFrame = AllocAppender!(ubyte[])(alloc);
-
-					// create DATA Frame with END_STREAM (0x1) flag
-					if(resBody.length > uint.max) assert(false, "TODO");
-
-					// create DATA frame header
-					dataFrame.createHTTP2FrameHeader(cast(uint)resBody.length, HTTP2FrameType.DATA, 0x1, 1);
-
-					// append the DATA body
-					dataFrame.put(resBody);
-
-					// try writing data
-					try {
-						stream.write(dataFrame.data);
-					} catch(Exception e) {
-						logWarn("Unable to write DATA Frame to stream.");
-					}
-
-					logTrace("Sent DATA frame on streamID %s", stream.streamId);
-
-				}
-
-			}
-		}
-
-		closeStream(context.multiplexer, stream.streamId);
-
-
-	} catch(HTTP2Exception e) {
-		auto f = buildGOAWAYFrame(stream.streamId, e.code);
-		stream.write(f);
-		logWarn("%s: %s", "Sent GOAWAY Frame", e.message);
-		stream.state = HTTP2StreamState.CLOSED;
-		return true;
-
-
-	} catch(HPACKException e) {
-		auto f = buildGOAWAYFrame(stream.streamId, HTTP2Error.COMPRESSION_ERROR);
-		stream.write(f);
-		stream.state = HTTP2StreamState.CLOSED;
-		logWarn("%s: %s", "Sent GOAWAY Frame", e.message);
-		return true;
-
-	} catch(Exception e) {
-		logWarn(e.msg);
-	}
-
-	return close;
-}
-/// process an HEADERS frame
-void handleHTTP2HeadersFrame(Stream)(ref Stream stream, TCPConnection connection,
-		HTTP2ServerContext context,  IAllocator alloc)
-{
-	// AllocAppender cannot be used here (TODO discuss)
-	auto hdec = appender!(HTTP2HeaderTableField[])();
-
-	// decode headers
-	decodeHPACK(cast(immutable(ubyte)[])stream.headerBlock, hdec, context.table, alloc, context.settings.headerTableSize);
-
-	// insert data in table
-	hdec.data.each!((h) { if(h.index) context.table.insert(h); });
-
-	// write a response (HEADERS + DATA according to request method)
-	handleHTTP2Request(stream, connection, context, hdec.data, context.table, alloc);
-
-	// clean the header block buffer
-	stream.resetHeaderBlock();
-}
-
-/// handle SETTINGS frame exchange
-void handleHTTP2SettingsFrame(Stream)(ref Stream stream, TCPConnection connection, ubyte[] data, HTTP2FrameHeader header, HTTP2ServerContext context) @safe
-{
-	// parse settings payload
-	context.settings.unpackSettings(data);
-
-	// update the connection window and notify waiting workers
-	if(stream.streamId == 0) updateConnectionWindow(context.multiplexer, context.settings.initialWindowSize);
-	updateStreamConnectionWindow(context.multiplexer, stream.streamId, context.settings.initialWindowSize);
-
-	// notify waiting threads if needed
-	if(checkCondition(context.multiplexer, stream.streamId)) {
-		logTrace("Notifying stopped tasks");
-		notifyCondition(context.multiplexer);
-		//yield();
-	}
-
-	// acknowledge settings with SETTINGS ACK Frame
-	FixedAppender!(ubyte[], 9) ackReply;
-	ackReply.createHTTP2FrameHeader(0, header.type, 0x1, header.streamId);
-
-	// new connection: must send a SETTINGS Frame as preface
-	if(isConnectionPreface(context.multiplexer)) sendHTTP2SettingsFrame(stream, context);
-
-	// write SETTINGS ACK
-	stream.write(ackReply.data);
-	logDebug("Sent SETTINGS ACK");
-}
-
-/// send a SETTINGS Frame
-void sendHTTP2SettingsFrame(Stream)(ref Stream stream, HTTP2ServerContext context) @safe
-{
-	FixedAppender!(ubyte[], HTTP2HeaderLength+36) settingDst;
-
-	settingDst.createHTTP2FrameHeader(36, HTTP2FrameType.SETTINGS, 0x0, 0);
-	settingDst.serializeSettings(context.settings);
-	stream.write(settingDst.data);
-
-	logDebug("Sent SETTINGS Frame");
-}
-
-enum HTTP2StreamState {
-	IDLE,
-	RESERVED_LOCAL,
-	RESERVED_REMOTE,
-	OPEN,
-	HALF_CLOSED_LOCAL,
-	HALF_CLOSED_REMOTE,
-	CLOSED
-}
-
-/** Represent a HTTP/2 Stream
-  * The underlying connection can be TCPConnection or TLSStream
-  * TODO: stream dependency, proper handling of stream IDs
-  * approach: mantain a union of IDs so that only correct streams are initialized
-*/
-struct HTTP2ConnectionStream(CS)
-{
-	static assert(isConnectionStream!CS || is(CS : TLSStream) || isOutputStream!Stream);
-
-	private {
-		enum Parse { HEADER, PAYLOAD };
-		CS m_conn;
-		uint m_streamId;
-		Parse toParse = Parse.HEADER;
-		HTTP2StreamState m_state;
-		AllocAppender!(ubyte[]) m_headerBlock;
-
-		// Stream dependency TODO
-		HTTP2FrameStreamDependency m_dependency;
-	}
-
-	// embed underlying connection
-	alias m_conn this;
-
-	this(CS)(ref CS conn, uint sid, IAllocator alloc) @safe
-	{
-		m_conn = conn;
-		m_streamId = sid;
-		m_state = HTTP2StreamState.IDLE;
-		m_headerBlock = AllocAppender!(ubyte[])(alloc);
-	}
-
-	this(CS)(ref CS conn, IAllocator alloc) @safe
-	{
-		this(conn, 0, alloc);
-	}
-
-	@property CS connection() @safe { return m_conn; }
-
-	@property HTTP2StreamState state() @safe @nogc { return m_state; }
-
-	@property bool canRead() @safe @nogc
-	{
-		return (m_state == HTTP2StreamState.OPEN || m_state == HTTP2StreamState.IDLE);
-	}
-
-	/// set state according to Stream lifecycle (RFC 7540 section 5.1)
-	@property void state(HTTP2StreamState st) @safe
-	{
-		switch(st) {
-			// allowed: IDLE -> OPEN
-			//          OPEN -> OPEN
-			case HTTP2StreamState.OPEN:
-				if(m_state == HTTP2StreamState.IDLE ||
-						m_state == HTTP2StreamState.OPEN)
-					m_state = st;
-				else enforceHTTP2(false, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
-				break;
-
-			// allowed: OPEN -> HCLOCAL
-			// 			RESERVED_REMOTE -> HCLOCAL
-			// 			HCLOCAL -> HCLOCAL
-			case HTTP2StreamState.HALF_CLOSED_LOCAL:
-				if(m_state == HTTP2StreamState.OPEN ||
-						m_state == HTTP2StreamState.RESERVED_REMOTE ||
-						m_state == HTTP2StreamState.HALF_CLOSED_LOCAL)
-					m_state = st;
-				else enforceHTTP2(false, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
-				break;
-
-			// allowed: OPEN -> HCREMOTE
-			// 			RESERVED_LOCAL -> HCREMOTE
-			// 			HCREMOTE -> HCREMOTE
-			case HTTP2StreamState.HALF_CLOSED_REMOTE:
-				if(m_state == HTTP2StreamState.OPEN ||
-						m_state == HTTP2StreamState.RESERVED_LOCAL ||
-						m_state == HTTP2StreamState.HALF_CLOSED_REMOTE)
-					m_state = st;
-				else enforceHTTP2(false, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
-				break;
-
-			// allowed: all transitions to CLOSED
-			//	(RST_STREAM, GOAWAY permit this)
-			case HTTP2StreamState.CLOSED:
-				m_state = st;
-				break;
-
-			// specific to PUSH_PROMISE Frames
-			case HTTP2StreamState.RESERVED_LOCAL:
-			case HTTP2StreamState.RESERVED_REMOTE:
-				if(m_state == HTTP2StreamState.IDLE) m_state = st;
-				else enforceHTTP2(false, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
-				break;
-
-			default:
-				enforceHTTP2(false, "Invalid state", HTTP2Error.PROTOCOL_ERROR);
- 		}
-		logTrace("Stream: %d state: %s", m_streamId, st);
-	}
-
-	@property uint streamId() @safe @nogc { return m_streamId; }
-
-	@property void streamId(uint sid) @safe @nogc { m_streamId = sid; }
-
-	@property HTTP2FrameStreamDependency dependency() @safe @nogc { return m_dependency; }
-
-	@property ubyte[] headerBlock() @safe
-	{
-		assert(!m_headerBlock.data.empty, "No data in header block buffer");
-		return m_headerBlock.data;
-	}
-
-	@property bool needsContinuation() @safe
-	{
-		return !m_headerBlock.data.empty;
-	}
-
-	/// reads from stream a frame header
-	uint readHeader(R)(ref R dst) @safe
-	{
-		assert(toParse == Parse.HEADER);
-
-		ubyte[HTTP2HeaderLength] buf; // should always be 9
-
-		m_conn.read(buf);
-		dst.put(buf);
-
-		// length of payload
-		auto len = dst.data[0..3].fromBytes(3);
-		if(len > 0) toParse = Parse.PAYLOAD;
-
-		return len;
-	}
-
-	/// reads from stream a frame payload
-	void readPayload(R)(ref R dst, int len) @safe
-	{
-		assert(toParse == Parse.PAYLOAD);
-		toParse = Parse.HEADER;
-
-		ubyte[8] buf = void;
-
-		/// perform multiple reads until payload is over (@nogc compatibility)
-		while(len > 0) {
-			auto end = (len < buf.length) ? len : buf.length;
-			len -= m_conn.read(buf[0..end], IOMode.all);
-			dst.put(buf[0..end]);
-		}
-	}
-
-	/// save a HEADERS / CONTINUATION block for processing
-	void putHeaderBlock(T)(T src) @safe
-		if(isInputRange!T && is(ElementType!T : ubyte))
-	{
-		m_headerBlock.put(src);
-	}
-
-	void resetHeaderBlock() @trusted
-	{
-		m_headerBlock.reset(AppenderResetMode.freeData);
-	}
-
-	void finalize() @safe { }
-}
-
-unittest {
-	import vibe.core.core : runApplication;
-	// empty handler, just to test if protocol switching works
-	void handleReq(HTTPServerRequest req, HTTPServerResponse res)
-	@safe {
-		if (req.path == "/")
-			res.writeBody("Hello, World! This response is sent through HTTP/2");
-	}
-
-	auto settings = new HTTPServerSettings();
-	settings.port = 8090;
-	settings.bindAddresses = ["localhost"];
-
-	listenHTTP!handleReq(settings);
-	//runApplication();
-}
-
-unittest {
-	import vibe.core.core : runApplication;
-
-	void handleRequest (HTTPServerRequest req, HTTPServerResponse res)
-	@safe {
-		if (req.path == "/")
-			res.writeBody("Hello, World! This response is sent through HTTP/2\n");
-	}
-
-
-	auto settings = new HTTPServerSettings;
-	settings.port = 8091;
-	settings.bindAddresses = ["127.0.0.1", "192.168.1.131"];
-	settings.tlsContext = createTLSContext(TLSContextKind.server);
-	settings.tlsContext.useCertificateChainFile("tests/server.crt");
-	settings.tlsContext.usePrivateKeyFile("tests/server.key");
-
-	// set alpn callback to support HTTP/2
-	// should accept the 'h2' protocol request
-	settings.tlsContext.alpnCallback(http2Callback);
-
-	// dummy, just for testing
-	listenHTTP!handleRequest(settings);
-	//runApplication();
-}
-
diff --git a/source/vibe/http/internal/http2/multiplexing.d b/source/vibe/http/internal/http2/multiplexing.d
deleted file mode 100644
index 20deafa..0000000
--- a/source/vibe/http/internal/http2/multiplexing.d
+++ /dev/null
@@ -1,275 +0,0 @@
-module vibe.http.internal.http2.multiplexing;
-
-import vibe.utils.hashmap;
-import vibe.core.sync;
-import vibe.core.log;
-import vibe.core.net;
-import vibe.core.core : yield;
-import vibe.internal.allocator;
-import vibe.internal.utilallocator: RegionListAllocator;
-
-import std.exception;
-import std.container : RedBlackTree;
-
-
-/** Stream multiplexing in HTTP/2
-  * References: https://tools.ietf.org/html/rfc7540#section-5
-  *
-  * The purposes of stream registration into a multiplexer are the following:
-  * 1. Check correctness of HTTP/2 frames received, following the rules defined
-  *    in the HTTP/2 RFC (https://tools.ietf.org/html/rfc7540)
-  * 2. Implement stream prioritization / dependency:
-  *    https://tools.ietf.org/html/rfc7540#section-5.3
-  * 3. Hold data structures which are supposed to mantain the state of a connection,
-  *	   since HTTP/2 opens only 1 tcp connection on which multiple frames can be sent.
-*/
-
-
-/* ======================================================= */
-/* ================ STREAM MANAGEMENT =================== */
-/* ======================================================= */
-
-/// register a stream on a MUX
-auto registerStream(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	return multiplexer.register(sid);
-}
-
-/// close a stream on a MUX
-auto closeStream(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	return multiplexer.close(sid);
-}
-
-/// check if stream is OPEN (meaning, currently registered and active)
-auto isOpenStream(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	return multiplexer.isOpen(sid);
-}
-
-/// connection preface (SETTINGS) can be received only ONCE
-auto isConnectionPreface(Mux)(ref Mux multiplexer) @trusted
-{
-	return multiplexer.isConnPreface();
-}
-
-/* ======================================================= */
-/* ================= FLOW CONTROL ======================== */
-/* ======================================================= */
-
-/** Per-connection window
-  * Valid for EVERY stream in MUX[idx]
-  */
-auto connectionWindow(Mux)(ref Mux multiplexer) @trusted
-{
-	return multiplexer.connWindow;
-}
-
-/// Update the connection window value
-auto updateConnectionWindow(Mux)(ref Mux multiplexer, const ulong newWin) @trusted
-{
-	return multiplexer.updateConnWindow(newWin);
-}
-
-/** Per-stream window
-  * Valid for stream `sid` in MUX[idx]
-  */
-auto streamConnectionWindow(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	return multiplexer.streamConnWindow(sid);
-}
-
-/// Update the stream connection window value
-auto updateStreamConnectionWindow(Mux)(ref Mux multiplexer, const uint sid, const ulong newWin) @trusted
-{
-	return multiplexer.updateStreamConnWindow(sid, newWin);
-}
-
-/** A TaskCondition is used to synchronize DATA frame sending
-  * this enforces flow control on every outgoing DATA frame
-  * So that the client-established connection/stream window
-  * is not exceeded.
-  * Each connection (MUX) has its own condition.
-  */
-void waitCondition(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	multiplexer.wait(sid);
-}
-
-/// signal the waiting task(s) that a change
-/// in the connection window has occourred
-void notifyCondition(Mux)(ref Mux multiplexer) @trusted
-{
-	multiplexer.notify();
-}
-
-/// check if waiting tasks are enqueued for this connection
-uint checkCondition(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	return multiplexer.checkCond(sid);
-}
-
-/// signal that the DATA dispatch is over
-/// task is no longer enqueued
-void doneCondition(Mux)(ref Mux multiplexer, const uint sid) @trusted
-{
-	multiplexer.endWait(sid);
-}
-
-/** Underlying multiplexer data structure
-  * Uses a TaskMutex to perform sensitive operations
-  * since multiple streams might be operating on the same
-  * connection (MUX)
-  */
-struct HTTP2Multiplexer {
-	/// used to register open streams, which must be unique
-	private alias H2Queue = RedBlackTree!uint;
-
-	private {
-		IAllocator m_alloc;
-		H2Queue m_open;		// set of open streams
-		uint m_closed;		// index of the last closed stream
-		uint m_last;		// index of last open stream
-		uint m_max;			// maximum number of streams open at the same time
-		uint m_countOpen;   // current number of open streams (in m_open)
-		TaskMutex m_lock;
-		TaskCondition m_cond;
-		uint[uint] m_waiting;
-		ulong m_wsize;
-		ulong[uint] m_streamWSize;
-		bool m_connPreface = true;
-	}
-
-	@disable this();
-
-	this(Alloc)(Alloc alloc, const uint max, const ulong wsize, const uint tsize=4096) @trusted
-	{
-		m_alloc = alloc;
-		m_lock = m_alloc.make!TaskMutex();
-		m_cond = m_alloc.make!TaskCondition(m_lock);
-		m_open = m_alloc.make!H2Queue();
-		m_last = 0;
-		m_max = max;
-		m_wsize = wsize;
-	}
-
-	/** The methods from here downwards
-	  * are not supposed to be used directly,
-	  * but through the documented wrappers above.
-	  */
-	@property void wait(const uint sid) @trusted
-	{
-		synchronized(m_lock) {
-			if(!(sid in m_waiting)) m_waiting[sid] = 0;
-			else m_waiting[sid]++;
-			m_cond.wait();
-		}
-	}
-
-	@property void endWait(const uint sid) @trusted
-	{
-		synchronized(m_lock) {
-			if(!(sid in m_waiting)) m_waiting[sid] = 0;
-			else m_waiting[sid]--;
-		}
-	}
-
-	@property void notify() @trusted
-	{
-		m_cond.notify();
-	}
-
-	@property uint checkCond(const uint sid) @safe
-	{
-		if(!(sid in m_waiting)) return 0;
-		return m_waiting[sid] > 0 && isOpen(sid);
-	}
-
-	@property ulong connWindow() @safe
-	{
-		return m_wsize;
-	}
-
-	@property ulong streamConnWindow(const uint sid) @safe
-	{
-		if(!(sid in m_streamWSize)) return 0;
-
-		return m_streamWSize[sid];
-	}
-
-	@property bool isConnPreface() @safe
-	{
-		// can only be true once per connection
-		auto b = m_connPreface;
-
-		m_lock.performLocked!({
-			m_connPreface = false;
-		});
-
-		return b;
-	}
-
-	// register a new open stream
-	bool register(const uint sid) @safe
-	{
-		if(sid == 0) return true; 					// success, but sid=0 is not registered
-		if(m_countOpen + 1 > m_max) return false; 	// PROTOCOL_ERROR: too many open streams
-		if(sid <= m_last && sid != 0) return false; // Stream ID must be greater than previously
-													// registered ones
-		m_lock.performLocked!({
-			m_countOpen++;
-			m_open.insert(sid);
-			m_last = sid;
-			m_streamWSize[sid] = m_wsize;
-		});
-		return true;
-	}
-
-	// close an open stream
-	bool close(const uint sid) @safe
-	{
-		if(!(sid in m_open)) return false; //Cannot close a stream which is not open
-		if(sid in m_waiting && m_waiting[sid]) return false; //Cannot close a stream which is blocked
-
-		m_lock.performLocked!({
-			m_countOpen--;
-			m_open.removeKey(sid);
-			m_streamWSize.remove(sid);
-		});
-		return true;
-	}
-
-	// open streams are present in m_open
-	bool isOpen(const uint sid) @safe
-	{
-		return sid in m_open;
-	}
-
-	bool updateConnWindow(const ulong newWin) @safe
-	{
-		if(newWin > ulong.max || newWin < 0) return false;
-		logDebug("MUX: CONTROL FLOW WINDOW: from %d to %d bytes", m_wsize, newWin);
-
-		m_lock.performLocked!({
-			m_wsize = newWin;
-		});
-
-		return true;
-	}
-
-	bool updateStreamConnWindow(const uint sid, const ulong newWin) @safe
-	{
-		if(newWin > ulong.max || newWin < 0) return false;
-		if(sid == 0) return true;
-
-		logDebug("MUX: CONTROL FLOW WINDOW: stream %d from %d to %d bytes",
-				sid, (sid in m_streamWSize) ? m_streamWSize[sid] : m_wsize, newWin);
-
-		m_lock.performLocked!({
-				m_streamWSize[sid] = newWin;
-		});
-
-		return true;
-	}
-
-}
diff --git a/source/vibe/http/internal/http2/settings.d b/source/vibe/http/internal/http2/settings.d
deleted file mode 100644
index 5190135..0000000
--- a/source/vibe/http/internal/http2/settings.d
+++ /dev/null
@@ -1,382 +0,0 @@
-module vibe.http.internal.http2.settings;
-
-import vibe.http.internal.http2.multiplexing;
-import vibe.http.internal.http2.frame;
-import vibe.http.internal.http2.hpack.tables;
-import vibe.http.internal.http2.error;
-
-import vibe.http.server;
-import vibe.core.log;
-import vibe.core.net;
-import vibe.core.task;
-import vibe.internal.freelistref;
-
-import std.range;
-import std.base64;
-import std.traits;
-import std.bitmanip; // read from ubyte (decoding)
-import std.typecons;
-import std.conv : to;
-import std.exception : enforce;
-import std.algorithm : canFind; // alpn callback
-import std.variant : Algebraic;
-
-/*
- *  6.5.1.  SETTINGS Format
- *
- *   The payload of a SETTINGS frame consists of zero or more parameters,
- *   each consisting of an unsigned 16-bit setting identifier and an
- *   unsigned 32-bit value.
- *
- *   +-------------------------------+
- *   |	   IDentifier (16)		 |
- *   +-------------------------------+-------------------------------+
- *   |						Value (32)							 |
- *   +---------------------------------------------------------------+
- *						Figure 10: Setting Format
- *
- *   6.5.2.  Defined SETTINGS Parameters
- *
- *   The following parameters are defined:
- *
- *   SETTINGS_HEADER_TABLE_SIZE (0x1):  Allows the sender to inform the
- *	 remote endpoint of the maximum size of the header compression
- *	 table used to decode header blocks, in octets.  The encoder can
- *	 select any size equal to or less than this value by using
- *	 signaling specific to the header compression format inside a
- *	 header block (see [COMPRESSION]).  The initial value is 4,096
- *	 octets.
- *
- *   SETTINGS_ENABLE_PUSH (0x2):  This setting can be used to disable
- *	  server push (Section 8.2).  An endpoint MUST NOT send a
- *	  PUSH_PROMISE frame if it receives this parameter set to a value of
- *	  0.  An endpoint that has both set this parameter to 0 and had it
- *	  acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a
- *	  connection error (Section 5.4.1) of type PROTOCOL_ERROR.
- *
- *	  The initial value is 1, which indicates that server push is
- *	  permitted.  Any value other than 0 or 1 MUST be treated as a
- *	  connection error (Section 5.4.1) of type PROTOCOL_ERROR.
- *
- *	SETTINGS_MAX_CONCURRENT_STREAMS (0x3):  Indicates the maximum number
- *	  of concurrent streams that the sender will allow.  This limit is
- *	  directional: it applies to the number of streams that the sender
- *	  permits the receiver to create.  Initially, there is no limit to
- *	  this value.  It is recommended that this value be no smaller than
- *	  100, so as to not unnecessarily limit parallelism.
- *
- *	  A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be
- *	  treated as special by endpoints.  A zero value does prevent the
- *	  creation of new streams; however, this can also happen for any
- *	  limit that is exhausted with active streams.  Servers SHOULD only
- *	  set a zero value for short durations; if a server does not wish to
- *	  accept requests, closing the connection is more appropriate.
- *
- *	SETTINGS_INITIAL_WINDOW_SIZE (0x4):  Indicates the sender's initial
- *	   window size (in octets) for stream-level flow control.  The
- *	   initial value is 2^16-1 (65,535) octets.
- *
- *	   This setting affects the window size of all streams (see
- *	   Section 6.9.2).
- *
- *	   Values above the maximum flow-control window size of 2^31-1 MUST
- *	   be treated as a connection error (Section 5.4.1) of type
- *	   FLOW_CONTROL_ERROR.
- *
- *	SETTINGS_MAX_FRAME_SIZE (0x5):  Indicates the size of the largest
- *	   frame payload that the sender is willing to receive, in octets.
- *
- *	   The initial value is 2^14 (16,384) octets.  The value advertised
- *	   by an endpoint MUST be between this initial value and the maximum
- *	   allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
- *	   Values outside this range MUST be treated as a connection error
- *	   (Section 5.4.1) of type PROTOCOL_ERROR.
- *
- *	SETTINGS_MAX_HEADER_LIST_SIZE (0x6):  This advisory setting informs a
- *	   peer of the maximum size of header list that the sender is
- *	   prepared to accept, in octets.  The value is based on the
- *	   uncompressed size of header fields, including the length of the
- *	   name and value in octets plus an overhead of 32 octets for each
- *	   header field.
- *
- *	   For any given request, a lower limit than what is advertised MAY
- *	   be enforced.  The initial value of this setting is unlimited.
- *
- *   An endpoint that receives a SETTINGS frame with any unknown or
- *   unsupported identifier MUST ignore that setting.
-*/
-//version = VibeForceALPN;
-
-alias HTTP2SettingID = ushort;
-alias HTTP2SettingValue = uint;
-
-// useful for bound checking
-const HTTP2SettingID minID = 0x1;
-const HTTP2SettingID maxID = 0x6;
-
-enum  HTTP2SettingsParameter {
-	headerTableSize				 = 0x1,
-	enablePush					  = 0x2,
-	maxConcurrentStreams			= 0x3,
-	initialWindowSize			   = 0x4,
-	maxFrameSize					= 0x5,
-	maxHeaderListSize			   = 0x6
-}
-
-// UDAs
-struct HTTP2Setting {
-	HTTP2SettingID id;
-	string name;
-}
-
-// UDAs
-HTTP2Setting http2Setting(HTTP2SettingID id, string name) {
-	if (!__ctfe) assert(false, "May only be used as a UDA");
-	return HTTP2Setting(id, name);
-}
-
-struct HTTP2Settings {
-
-	// no limit specified in the RFC
-	@http2Setting(0x1, "SETTINGS_HEADER_TABLE_SIZE")
-	HTTP2SettingValue headerTableSize = DEFAULT_DYNAMIC_TABLE_SIZE;
-
-	// TODO {0,1} otherwise CONNECTION_ERROR
-	@http2Setting(0x2, "SETTINGS_ENABLE_PUSH")
-	HTTP2SettingValue enablePush = 1;
-
-	/* set to the max value (UNLIMITED)
-	 * TODO manage connection with value == 0
-	 * might be closed as soon as possible
-	 */
-	@http2Setting(0x3, "SETTINGS_MAX_CONCURRENT_STREAMS")
-	//HTTP2SettingValue maxConcurrentStreams = HTTP2SettingValue.max; // lowered
-	//to 2^16
-	HTTP2SettingValue maxConcurrentStreams = 65536;
-
-	// TODO FLOW_CONTROL_ERRROR on values > 2^31-1
-	@http2Setting(0x4, "SETTINGS_INITIAL_WINDOW_SIZE")
-	HTTP2SettingValue initialWindowSize = 65535;
-
-	// TODO PROTOCOL_ERROR on values > 2^24-1
-	@http2Setting(0x5, "SETTINGS_MAX_FRAME_SIZE")
-	HTTP2SettingValue maxFrameSize = 16384;
-
-	// set to the max value (UNLIMITED)
-	@http2Setting(0x6, "SETTINGS_MAX_HEADER_LIST_SIZE")
-	HTTP2SettingValue maxHeaderListSize = HTTP2SettingValue.max;
-
-	/**
-	 * Use Decoder to decode a string and set the corresponding settings
-	 * The decoder must follow the base64url encoding
-	 * `bool` since the handler must ignore the Upgrade request
-	 * if the settings cannot be decoded
-	 */
-	bool decode(alias Decoder)(string encodedSettings) @safe
-		if (isInstanceOf!(Base64Impl, Decoder))
-	{
-		ubyte[] uset;
-		try {
-			// the Base64URL decoder throws a Base64exception if it fails
-			uset = Decoder.decode(encodedSettings);
-			enforce!Base64Exception(uset.length % 6 == 0, "Invalid SETTINGS payload length");
-		} catch (Base64Exception e) {
-			logDiagnostic("Failed to decode SETTINGS payload: " ~ e.msg);
-			return false;
-		}
-
-		// set values
-		while(!uset.empty) m_set(uset.read!HTTP2SettingID, uset.read!HTTP2SettingValue);
-		return true;
-	}
-
-	/*
-	 * Set parameter 'id' to 'value'
-	 * private overload for decoded parameters assignment
-	 */
-	void set(HTTP2SettingID id)(HTTP2SettingValue value) @safe
-		if(id <= maxID && id >= minID)
-	{
-		m_set(id,value);
-	}
-
-	private void m_set(HTTP2SettingID id, HTTP2SettingValue value) @safe
-	{
-		// must use labeled break w. static foreach
-		assign: switch(id) {
-			default: logWarn("Unsupported SETTINGS code:" ~ to!string(id)); return;
-			static foreach(c; __traits(allMembers, HTTP2SettingsParameter)) {
-				case __traits(getMember, HTTP2SettingsParameter, c):
-					__traits(getMember, this, c) = value;
-					break assign;
-			}
-		}
-
-	}
-
-}
-
-void serializeSettings(R)(ref R dst, HTTP2Settings settings) @safe @nogc
-{
-	static foreach(s; __traits(allMembers, HTTP2Settings)) {
-		static if(is(typeof(__traits(getMember, HTTP2Settings, s)) == HTTP2SettingValue)) {
-			mixin("dst.putBytes!2((getUDAs!(settings."~s~",HTTP2Setting)[0]).id);");
-			mixin("dst.putBytes!4(settings."~s~");");
-		}
-	}
-}
-
-void unpackSettings(R)(ref HTTP2Settings settings, R src) @safe
-{
-	while(!src.empty) {
-		auto id = src.takeExactly(2).fromBytes(2);
-		src.popFrontN(2);
-
-		// invalid IDs: ignore setting
-		if(!(id >= minID && id <= maxID)) {
-			src.popFrontN(4);
-			continue;
-		}
-
-		static foreach(s; __traits(allMembers, HTTP2Settings)) {
-			static if(is(typeof(__traits(getMember, HTTP2Settings, s)) == HTTP2SettingValue)) {
-				mixin("if(id == ((getUDAs!(settings."~s~",HTTP2Setting)[0]).id)) {
-							settings."~s~" = src.takeExactly(4).fromBytes(4);
-							src.popFrontN(4);
-						}");
-
-			}
-		}
-	}
-
-	enforceHTTP2(settings.enablePush == 0 || settings.enablePush == 1,
-			"Invalid value for ENABLE_PUSH setting.", HTTP2Error.PROTOCOL_ERROR);
-
-	enforceHTTP2(settings.initialWindowSize < (1 << 31),
-			"Invalid value for INITIAL_WINDOW_SIZE setting.", HTTP2Error.FLOW_CONTROL_ERROR);
-
-	enforceHTTP2(settings.maxFrameSize >= (1 << 14) && settings.maxFrameSize < (1 << 24),
-			"Invalid value for MAX_FRAME_SIZE setting.", HTTP2Error.FLOW_CONTROL_ERROR);
-}
-
-unittest {
-
-	HTTP2Settings settings;
-
-	// retrieve a value
-	assert(settings.headerTableSize == 4096);
-
-	//set a SETTINGS value using the enum table
-	settings.set!(HTTP2SettingsParameter.headerTableSize)(2048);
-	assert(settings.headerTableSize == 2048);
-
-	//set a SETTINGS value using the code directly
-	settings.set!0x4(1024);
-	assert(settings.initialWindowSize == 1024);
-
-	// SHOULD NOT COMPILE
-	//settings.set!0x7(1);
-
-	// get a HTTP2Setting struct containing the code and the parameter name
-	import std.traits : getUDAs;
-	assert(getUDAs!(settings.headerTableSize, HTTP2Setting)[0] == HTTP2Setting(0x1,
-				"SETTINGS_HEADER_TABLE_SIZE"));
-
-	// test decoding from base64url
-	// h2settings contains:
-	// 0x2 -> 0
-	// 0x3 -> 100
-	// 0x4 -> 1073741824
-	string h2settings = "AAMAAABkAARAAAAAAAIAAAAA";
-	assert(settings.decode!Base64URL(h2settings));
-
-	assert(settings.enablePush == 0);
-	assert(settings.maxConcurrentStreams == 100);
-	assert(settings.initialWindowSize == 1073741824);
-
-	// should throw a Base64Exception error (caught) and a logWarn
-	assert(!settings.decode!Base64URL("a|b+*-c"));
-}
-
-/** Context is initialized on each new connection
-  * it MUST remain consistent between streams of the same connection
-  * Contains HTTP2Settings negotiated during handshake
-  * TODO set of USED streams for proper HTTP2ConnectionStream initialization
-*/
-final class HTTP2ServerContext
-{
-	private {
-		HTTPServerContext m_context;
-		Nullable!HTTP2Settings m_settings;
-		uint m_sid = 0;
-		FreeListRef!IndexingTable m_table;
-		FreeListRef!HTTP2Multiplexer m_multiplexer;
-		bool m_initializedT = false;
-		bool m_initializedM = false;
-
-	}
-
-	alias m_context this;
-
-	// used to mantain the first request in case of `h2c` protocol switching
-	ubyte[] resFrame = void;
-
-	this(HTTPServerContext ctx, HTTP2Settings settings) @safe
-	{
-		m_context = ctx;
-		m_settings = settings;
-	}
-
-	this(HTTPServerContext ctx) @safe
-	{
-		m_context = ctx;
-	}
-
-	@property auto ref table() @safe { return m_table.get; }
-
-	@property bool hasTable() @safe { return m_initializedT; }
-
-	@property void table(T)(T table) @safe
-		if(is(T == typeof(m_table)))
-	{
-		assert(!m_initializedT);
-		m_table = table;
-		m_initializedT = true;
-	}
-
-	@property auto ref multiplexer() @safe { return m_multiplexer.get; }
-
-	@property bool hasMultiplexer() @safe { return m_initializedM; }
-
-	@property void multiplexer(T)(T multiplexer) @safe
-		if(is(T == typeof(m_multiplexer)))
-	{
-		assert(!m_initializedM);
-		m_multiplexer = multiplexer;
-		m_initializedM = true;
-	}
-
-	@property HTTPServerContext h1context() @safe @nogc { return m_context; }
-
-	@property uint next_sid() @safe @nogc { return m_sid; }
-
-	@property void next_sid(uint sid) @safe @nogc { m_sid = sid; }
-
-	@property ref HTTP2Settings settings() @safe @nogc
-	{
-		assert(!m_settings.isNull);
-		return m_settings.get;
-	}
-
-	@property void settings(ref HTTP2Settings settings) @safe
-	{
-		assert(m_settings.isNull);
-		m_settings = settings;
-		() @trusted {
-			if (settings.headerTableSize != 4096) {
-				table.updateSize(settings.headerTableSize);
-			}
-		} ();
-	}
-}
-
diff --git a/source/vibe/http/log.d b/source/vibe/http/log.d
index 927919d..5357b2d 100644
--- a/source/vibe/http/log.d
+++ b/source/vibe/http/log.d
@@ -1,7 +1,7 @@
 /**
 	A HTTP 1.1/1.0 server implementation.
 
-	Copyright: © 2012-2013 RejectedSoftware e.K.
+	Copyright: © 2012-2013 Sönke Ludwig
 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
 	Authors: Sönke Ludwig, Jan Krüger
 */
@@ -243,7 +243,7 @@ void formatApacheLog(R)(ref R ln, string format, scope HTTPServerRequest req, sc
 						ln.put(req.username.length ? req.username : "-");
 						break;
 					case 'U': //The URL path without query string
-						ln.put(req.path);
+						ln.put(req.requestPath.toString());
 						break;
 					case 'v': //Server name
 						ln.put(req.host.length ? req.host : "-");
diff --git a/source/vibe/http/proxy.d b/source/vibe/http/proxy.d
index 5d86f3c..b4b38e6 100644
--- a/source/vibe/http/proxy.d
+++ b/source/vibe/http/proxy.d
@@ -7,6 +7,7 @@
 */
 module vibe.http.proxy;
 
+import vibe.core.core : runTask;
 import vibe.core.log;
 import vibe.http.client;
 import vibe.http.server;
@@ -36,8 +37,6 @@ void listenHTTPProxy(HTTPServerSettings settings, HTTPProxySettings proxy_settin
 	settings.options = HTTPServerOption.none;
 	listenHTTP(settings, proxyRequest(proxy_settings));
 }
-// Compatibility alias - will be deprecated soon.
-alias listenHTTPReverseProxy = listenHTTPProxy;
 
 /**
 	Transparently forwards all requests to the proxy to a destination_host.
@@ -53,7 +52,7 @@ void listenHTTPReverseProxy(HTTPServerSettings settings, string destination_host
 	url.port = destination_port;
 	auto proxy_settings = new HTTPProxySettings(ProxyMode.reverse);
 	proxy_settings.destination = url;
-	listenHTTPReverseProxy(settings, proxy_settings);
+	listenHTTPProxy(settings, proxy_settings);
 }
 
 /**
@@ -109,8 +108,16 @@ HTTPServerRequestDelegateS proxyRequest(HTTPProxySettings settings)
 			auto scon = res.connectProxy();
 			assert (scon);
 
-			import vibe.core.core : runTask;
-			runTask({ scon.pipe(ccon); });
+			runTask(() nothrow {
+				try scon.pipe(ccon);
+				catch (Exception e) {
+					logException(e, "Failed to forward proxy data from server to client");
+					try scon.close();
+					catch (Exception e) logException(e, "Failed to close server connection after error");
+					try ccon.close();
+					catch (Exception e) logException(e, "Failed to close client connection after error");
+				}
+			});
 			ccon.pipe(scon);
 			return;
 		}
@@ -157,8 +164,16 @@ HTTPServerRequestDelegateS proxyRequest(HTTPProxySettings settings)
 				auto scon = res.switchProtocol("");
 				auto ccon = cres.switchProtocol("");
 
-				import vibe.core.core : runTask;
-				runTask({ ccon.pipe(scon); });
+				runTask(() nothrow {
+					try ccon.pipe(scon);
+					catch (Exception e) {
+						logException(e, "Failed to forward proxy data from client to server");
+						try scon.close();
+						catch (Exception e) logException(e, "Failed to close server connection after error");
+						try ccon.close();
+						catch (Exception e) logException(e, "Failed to close client connection after error");
+					}
+				});
 
 				scon.pipe(ccon);
 				return;
@@ -219,8 +234,6 @@ HTTPServerRequestDelegateS proxyRequest(HTTPProxySettings settings)
 
 	return &handleRequest;
 }
-/// Compatibility alias - will be deprecated soon
-alias reverseProxyRequest = proxyRequest;
 
 /**
 	Returns a HTTP request handler that forwards any request to the specified host/port.
@@ -279,9 +292,11 @@ final class HTTPProxySettings {
 	bool handleConnectRequests;
 
 	/// Empty default constructor for backwards compatibility - will be deprecated soon.
+	deprecated("Pass an explicit `ProxyMode` argument")
 	this() { proxyMode = ProxyMode.reverse; }
 	/// Explicitly sets the proxy mode.
 	this(ProxyMode mode) { proxyMode = mode; }
 }
-/// Compatibility alias - will be deprecated soon.
+/// Compatibility alias
+deprecated("Use `HTTPProxySettings(ProxyMode.reverse)` instead.")
 alias HTTPReverseProxySettings = HTTPProxySettings;
diff --git a/source/vibe/http/router.d b/source/vibe/http/router.d
index ae09e5c..c9a5f00 100644
--- a/source/vibe/http/router.d
+++ b/source/vibe/http/router.d
@@ -3,7 +3,7 @@
 
 	See `URLRouter` for more details.
 
-	Copyright: © 2012-2015 RejectedSoftware e.K.
+	Copyright: © 2012-2015 Sönke Ludwig
 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
 	Authors: Sönke Ludwig
 */
@@ -92,7 +92,7 @@ final class URLRouter : HTTPServerRequestHandler {
 	/// Returns a single route handle to conveniently register multiple methods.
 	URLRoute route(string path)
 	in { assert(path.length, "Cannot register null or empty path!"); }
-	body { return URLRoute(this, path); }
+	do { return URLRoute(this, path); }
 
 	///
 	unittest {
@@ -193,6 +193,8 @@ final class URLRouter : HTTPServerRequestHandler {
 	/// Handles a HTTP request by dispatching it to the registered route handlers.
 	void handleRequest(HTTPServerRequest req, HTTPServerResponse res)
 	{
+		import vibe.core.path : PosixPath;
+
 		auto method = req.method;
 
 		string calcBasePath()
@@ -200,10 +202,18 @@ final class URLRouter : HTTPServerRequestHandler {
 			import vibe.core.path : InetPath, relativeToWeb;
 			auto p = InetPath(prefix.length ? prefix : "/");
 			p.endsWithSlash = true;
-			return p.relativeToWeb(InetPath(req.path)).toString();
+			return p.relativeToWeb(req.requestPath).toString();
 		}
 
-		auto path = req.path;
+		string path;
+
+		// NOTE: Instead of failing, we just ignore requests with invalid path
+		//       segments (i.e. containing path separators) here. Any request
+		//       handlers later in the queue may still choose to process them
+		//       appropriately.
+		try path = (cast(PosixPath)req.requestPath).toString();
+		catch (Exception e) return;
+
 		if (path.length < m_prefix.length || path[0 .. m_prefix.length] != m_prefix) return;
 		path = path[m_prefix.length .. $];
 
@@ -212,7 +222,7 @@ final class URLRouter : HTTPServerRequestHandler {
 				auto r = () @trusted { return &m_routes.getTerminalData(ridx); } ();
 				if (r.method != method) return false;
 
-				logDebugV("route match: %s -> %s %s %s", req.path, r.method, r.pattern, values);
+				logDebugV("route match: %s -> %s %s %s", req.requestPath, r.method, r.pattern, values);
 				foreach (i, v; values) req.params[m_routes.getTerminalVarNames(ridx)[i]] = v;
 				if (m_computeBasePath) req.params["routerRootDir"] = calcBasePath();
 				r.cb(req, res);
@@ -247,10 +257,10 @@ final class URLRouter : HTTPServerRequestHandler {
 		static if (
 				is(Handler : HTTPServerRequestDelegate) ||
 				is(Handler : HTTPServerRequestFunction) ||
-				is(Handler : HTTPServerRequestHandler)
-				//is(Handler : HTTPServerRequestDelegateS) ||
-				//is(Handler : HTTPServerRequestFunctionS) ||
-				//is(Handler : HTTPServerRequestHandlerS)
+				is(Handler : HTTPServerRequestHandler) ||
+				is(Handler : HTTPServerRequestDelegateS) ||
+				is(Handler : HTTPServerRequestFunctionS) ||
+				is(Handler : HTTPServerRequestHandlerS)
 			)
 		{
 			enum isValidHandler = true;
@@ -278,9 +288,9 @@ final class URLRouter : HTTPServerRequestHandler {
 		static assert(isValidHandler!HTTPServerRequestFunction);
 		static assert(isValidHandler!HTTPServerRequestDelegate);
 		static assert(isValidHandler!HTTPServerRequestHandler);
-		//static assert(isValidHandler!HTTPServerRequestFunctionS);
-		//static assert(isValidHandler!HTTPServerRequestDelegateS);
-		//static assert(isValidHandler!HTTPServerRequestHandlerS);
+		static assert(isValidHandler!HTTPServerRequestFunctionS);
+		static assert(isValidHandler!HTTPServerRequestDelegateS);
+		static assert(isValidHandler!HTTPServerRequestHandlerS);
 		static assert(isValidHandler!(void delegate(HTTPServerRequest req, HTTPServerResponse res) @system));
 		static assert(isValidHandler!(void function(HTTPServerRequest req, HTTPServerResponse res) @system));
 		static assert(isValidHandler!(void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @system));
@@ -296,55 +306,55 @@ final class URLRouter : HTTPServerRequestHandler {
 }
 
 ///
-//@safe unittest {
-	//import vibe.http.fileserver;
-
-	//void addGroup(HTTPServerRequest req, HTTPServerResponse res)
-	//{
-		//// Route variables are accessible via the params map
-		//logInfo("Getting group %s for user %s.", req.params["groupname"], req.params["username"]);
-	//}
-
-	//void deleteUser(HTTPServerRequest req, HTTPServerResponse res)
-	//{
-		//// ...
-	//}
-
-	//void auth(HTTPServerRequest req, HTTPServerResponse res)
-	//{
-		//// TODO: check req.session to see if a user is logged in and
-		////	   write an error page or throw an exception instead.
-	//}
-
-	//void setup()
-	//{
-		//auto router = new URLRouter;
-		//// Matches all GET requests for /users/groups/* and places
-		//// the place holders in req.params as 'username' and 'groupname'.
-		//router.get("/users/:username/groups/:groupname", &addGroup);
-
-		//// Matches all requests. This can be useful for authorization and
-		//// similar tasks. The auth method will only write a response if the
-		//// user is _not_ authorized. Otherwise, the router will fall through
-		//// and continue with the following routes.
-		//router.any("*", &auth);
-
-		//// Matches a POST request
-		//router.post("/users/:username/delete", &deleteUser);
-
-		//// Matches all GET requests in /static/ such as /static/img.png or
-		//// /static/styles/sty.css
-		//router.get("/static/*", serveStaticFiles("public/"));
-
-		//// Setup a HTTP server...
-		//auto settings = new HTTPServerSettings;
-		//// ...
-
-		//// The router can be directly passed to the listenHTTP function as
-		//// the main request handler.
-		//listenHTTP(settings, router);
-	//}
-//}
+@safe unittest {
+	import vibe.http.fileserver;
+
+	void addGroup(HTTPServerRequest req, HTTPServerResponse res)
+	{
+		// Route variables are accessible via the params map
+		logInfo("Getting group %s for user %s.", req.params["groupname"], req.params["username"]);
+	}
+
+	void deleteUser(HTTPServerRequest req, HTTPServerResponse res)
+	{
+		// ...
+	}
+
+	void auth(HTTPServerRequest req, HTTPServerResponse res)
+	{
+		// TODO: check req.session to see if a user is logged in and
+		//       write an error page or throw an exception instead.
+	}
+
+	void setup()
+	{
+		auto router = new URLRouter;
+		// Matches all GET requests for /users/*/groups/* and places
+		// the place holders in req.params as 'username' and 'groupname'.
+		router.get("/users/:username/groups/:groupname", &addGroup);
+
+		// Matches all requests. This can be useful for authorization and
+		// similar tasks. The auth method will only write a response if the
+		// user is _not_ authorized. Otherwise, the router will fall through
+		// and continue with the following routes.
+		router.any("*", &auth);
+
+		// Matches a POST request
+		router.post("/users/:username/delete", &deleteUser);
+
+		// Matches all GET requests in /static/ such as /static/img.png or
+		// /static/styles/sty.css
+		router.get("/static/*", serveStaticFiles("public/"));
+
+		// Setup a HTTP server...
+		auto settings = new HTTPServerSettings;
+		// ...
+
+		// The router can be directly passed to the listenHTTP function as
+		// the main request handler.
+		listenHTTP(settings, router);
+	}
+}
 
 /** Using nested routers to map components to different sub paths. A component
 	could for example be an embedded blog engine.
@@ -391,12 +401,9 @@ final class URLRouter : HTTPServerRequestHandler {
 		// /component1/users/:user -> showComponentUser
 
 		// Start the HTTP server
-		//auto settings = HTTPServerSettings;
-		HTTPServerSettings settings;
+		auto settings = new HTTPServerSettings;
 		// ...
-		//listenHTTP(settings, mainrouter);
-
-		listenHTTP!mainrouter(settings);
+		listenHTTP(settings, mainrouter);
 	}
 }
 
@@ -503,7 +510,7 @@ final class URLRouter : HTTPServerRequestHandler {
 
 	assert(ensureMatch("/foo bar/", "/foo%20bar/") is null);   // normalized pattern: "/foo%20bar/"
 	//assert(ensureMatch("/foo%20bar/", "/foo%20bar/") is null); // normalized pattern: "/foo%20bar/"
-	assert(ensureMatch("/foo/bar/", "/foo/bar/") is null);	 // normalized pattern: "/foo/bar/"
+	assert(ensureMatch("/foo/bar/", "/foo/bar/") is null);     // normalized pattern: "/foo/bar/"
 	//assert(ensureMatch("/foo/bar/", "/foo%2fbar/") !is null);
 	//assert(ensureMatch("/foo%2fbar/", "/foo%2fbar/") is null); // normalized pattern: "/foo%2Fbar/"
 	//assert(ensureMatch("/foo%2Fbar/", "/foo%2fbar/") is null); // normalized pattern: "/foo%2Fbar/"
@@ -513,6 +520,66 @@ final class URLRouter : HTTPServerRequestHandler {
 	assert(ensureMatch("/:foo/", "/foo/bar/") !is null);
 }
 
+unittest { // issue #2561
+	import vibe.http.server : createTestHTTPServerRequest, createTestHTTPServerResponse;
+	import vibe.inet.url : URL;
+	import vibe.stream.memory : createMemoryOutputStream;
+
+	Route[] routes = [
+		Route(HTTPMethod.PUT, "/public/devices/commandList"),
+		Route(HTTPMethod.PUT, "/public/devices/logicStateList"),
+		Route(HTTPMethod.OPTIONS, "/public/devices/commandList"),
+		Route(HTTPMethod.OPTIONS, "/public/devices/logicStateList"),
+		Route(HTTPMethod.PUT, "/public/mnemoschema"),
+		Route(HTTPMethod.PUT, "/public/static"),
+		Route(HTTPMethod.PUT, "/public/dynamic"),
+		Route(HTTPMethod.PUT, "/public/info"),
+		Route(HTTPMethod.PUT, "/public/info-network"),
+		Route(HTTPMethod.PUT, "/public/events"),
+		Route(HTTPMethod.PUT, "/public/eventList"),
+		Route(HTTPMethod.PUT, "/public/availBatteryModels"),
+		Route(HTTPMethod.OPTIONS, "/public/availBatteryModels"),
+		Route(HTTPMethod.OPTIONS, "/public/dynamic"),
+		Route(HTTPMethod.OPTIONS, "/public/eventList"),
+		Route(HTTPMethod.OPTIONS, "/public/events"),
+		Route(HTTPMethod.OPTIONS, "/public/info"),
+		Route(HTTPMethod.OPTIONS, "/public/info-network"),
+		Route(HTTPMethod.OPTIONS, "/public/mnemoschema"),
+		Route(HTTPMethod.OPTIONS, "/public/static"),
+		Route(HTTPMethod.PUT, "/settings/admin/getinfo"),
+		Route(HTTPMethod.PUT, "/settings/admin/setconf"),
+		Route(HTTPMethod.PUT, "/settings/admin/checksetaccess"),
+		Route(HTTPMethod.OPTIONS, "/settings/admin/checksetaccess"),
+		Route(HTTPMethod.OPTIONS, "/settings/admin/getinfo"),
+		Route(HTTPMethod.OPTIONS, "/settings/admin/setconf"),
+	];
+
+	auto router = new URLRouter;
+
+	foreach (r; routes)
+		router.match(r.method, r.pattern, (req, res) {
+			res.writeBody("OK");
+		});
+
+	{ // make sure unmatched routes are not handled by the router
+		auto req = createTestHTTPServerRequest(URL("http://localhost/foobar"), HTTPMethod.PUT);
+		auto res = createTestHTTPServerResponse();
+		router.handleRequest(req, res);
+		assert(!res.headerWritten);
+	}
+
+	// ensure all routes are matched
+	foreach (r; routes) {
+		auto url = URL("http://localhost"~r.pattern);
+		auto output = createMemoryOutputStream();
+		auto req = createTestHTTPServerRequest(url, r.method);
+		auto res = createTestHTTPServerResponse(output, null, TestHTTPResponseMode.bodyOnly);
+		router.handleRequest(req, res);
+		//assert(res.headerWritten);
+		assert(output.data == "OK");
+	}
+}
+
 
 /**
 	Convenience abstraction for a single `URLRouter` route.
@@ -640,8 +707,8 @@ private struct MatchTree(T) {
 
 			void printRange(uint node, ubyte from, ubyte to)
 			{
-				if (to - from <= 10) logInfo("	%s -> %s", iota(from, cast(uint)to+1).map!(ch => mapChar(cast(ubyte)ch)).join("|"), node);
-				else logInfo("	%s-%s -> %s", mapChar(from), mapChar(to), node);
+				if (to - from <= 10) logInfo("    %s -> %s", iota(from, cast(uint)to+1).map!(ch => mapChar(cast(ubyte)ch)).join("|"), node);
+				else logInfo("    %s-%s -> %s", mapChar(from), mapChar(to), node);
 			}
 
 			auto last_to = NodeIndex.max;
@@ -958,7 +1025,7 @@ private struct MatchGraphBuilder {
 		if (!m_nodes.length) return;
 
 		import vibe.utils.hashmap;
-		HashMap!(size_t, NodeIndex) combined_nodes;
+		HashMap!(LinkedSetHash, NodeIndex) combined_nodes;
 		Array!bool visited;
 		visited.length = m_nodes.length * 2;
 		Stack!NodeIndex node_stack;
@@ -974,7 +1041,7 @@ private struct MatchGraphBuilder {
 
 			foreach (ch; ubyte.min .. ubyte.max+1) {
 				auto chnodes = m_nodes[n].edges[ch];
-				size_t chhash = m_edgeEntries.getHash(chnodes);
+				LinkedSetHash chhash = m_edgeEntries.getHash(chnodes);
 
 				// handle trivial cases
 				if (m_edgeEntries.hasMaxLength(chnodes, 1))
@@ -1047,7 +1114,7 @@ private struct MatchGraphBuilder {
 			}
 			logInfo("  %s: %s", i, n.terminals[].map!(t => t.var != VarIndex.max ? format("T%s(%s)", t.index, t.var) : format("T%s", t.index)).join(" "));
 			ubyte first_char;
-			size_t list_hash;
+			LinkedSetHash list_hash;
 			NodeSet list;
 
 			void printEdges(ubyte last_char) {
@@ -1056,7 +1123,7 @@ private struct MatchGraphBuilder {
 					foreach (tn; m_edgeEntries.getItems(list))
 						targets ~= format(" %s", tn);
 					if (targets.length > 0)
-						logInfo("	[%s ... %s] -> %s", mapChar(first_char), mapChar(last_char), targets);
+						logInfo("    [%s ... %s] -> %s", mapChar(first_char), mapChar(last_char), targets);
 				}
 			}
 			foreach (ch, tnodes; n.edges) {
@@ -1112,6 +1179,10 @@ private struct MatchGraphBuilder {
 	}
 }
 
+
+/** Used to store and manipulate multiple linked lists within a single array
+	based storage.
+*/
 struct LinkedSetBacking(T) {
 	import std.container.array : Array;
 	import std.range : isInputRange;
@@ -1170,13 +1241,16 @@ struct LinkedSetBacking(T) {
 			insert(h, itm);
 	}
 
-	size_t getHash(Handle sh)
+	LinkedSetHash getHash(Handle sh)
 	const {
+		import std.digest.md : md5Of;
+
 		// NOTE: the returned hash is order independent, to avoid bogus
-		//	   mismatches when comparing lists of different order
-		size_t ret = 0x72d2da6c;
+		//       mismatches when comparing lists of different order
+		LinkedSetHash ret = cast(LinkedSetHash)md5Of([]);
 		while (sh != Handle.init) {
-			ret ^= (hashOf(m_storage[sh.index].value) ^ 0xb1bdfb8d) * 0x5dbf04a4;
+			auto h = cast(LinkedSetHash)md5Of(cast(const(ubyte)[])(&m_storage[sh.index].value)[0 .. 1]);
+			foreach (i; 0 .. ret.length) ret[i] ^= h[i];
 			sh.index = m_storage[sh.index].next;
 		}
 		return ret;
@@ -1233,12 +1307,16 @@ unittest {
 	assert(h != b.getHash(b.emptySet));
 	s = b.create(5, 3, 7);
 	assert(b.getHash(s) == h);
+	assert(b.getItems(s).equal([7, 3, 5]));
 
 	b.insert(&s, 11);
 	assert(b.hasLength(s, 4));
 	assert(b.getHash(s) != h);
+	assert(b.getItems(s).equal([11, 7, 3, 5]));
 }
 
+alias LinkedSetHash = ulong[16/ulong.sizeof];
+
 private struct Stack(E)
 {
 	import std.range : isInputRange;
diff --git a/source/vibe/http/server.d b/source/vibe/http/server.d
index ddde4ab..3a1482a 100644
--- a/source/vibe/http/server.d
+++ b/source/vibe/http/server.d
@@ -1,43 +1,52 @@
+/**
+	A HTTP 1.1/1.0 server implementation.
+
+	Copyright: © 2012-2017 Sönke Ludwig
+	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
+	Authors: Sönke Ludwig, Jan Krüger, Ilya Shipunov
+*/
 module vibe.http.server;
 
 public import vibe.core.net;
-import vibe.core.stream;
-import vibe.http.internal.http1;
-import vibe.http.internal.http2.settings;
-import vibe.http.internal.http2.exchange;
-
-public import vibe.http.log;
 public import vibe.http.common;
 public import vibe.http.session;
-import vibe.inet.message;
+
 import vibe.core.file;
 import vibe.core.log;
+import vibe.data.json;
+import vibe.http.dist;
+import vibe.http.log;
+import vibe.inet.message;
 import vibe.inet.url;
 import vibe.inet.webform;
-import vibe.data.json;
-import vibe.internal.allocator;
-import vibe.internal.freelistref;
 import vibe.internal.interfaceproxy : InterfaceProxy;
-import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
-import vibe.stream.tls;
-import vibe.utils.array;
-import vibe.utils.string;
 import vibe.stream.counting;
 import vibe.stream.operations;
+import vibe.stream.tls;
+import vibe.stream.wrapper : ConnectionProxyStream, createConnectionProxyStream, createConnectionProxyStreamFL;
 import vibe.stream.zlib;
-import vibe.textfilter.urlencode : urlEncode, urlDecode;
+import vibe.textfilter.urlencode;
+import vibe.utils.array;
+import vibe.internal.allocator;
+import vibe.internal.freelistref;
+import vibe.utils.string;
 
-import std.datetime;
-import std.typecons;
-import std.conv;
+import core.atomic;
+import core.vararg;
+import diet.traits : SafeFilterCallback, dietTraits;
+import std.algorithm : canFind, splitter;
 import std.array;
-import std.algorithm;
-import std.format;
-import std.parallelism;
+import std.conv;
+import std.datetime;
+import std.encoding : sanitize;
 import std.exception;
+import std.format;
+import std.functional : toDelegate;
 import std.string;
-import std.traits;
-import std.encoding : sanitize;
+import std.traits : ReturnType;
+import std.typecons;
+import std.uri;
+
 
 version (VibeNoSSL) version = HaveNoTLS;
 else version (Have_botan) {}
@@ -45,7 +54,7 @@ else version (Have_openssl) {}
 else version = HaveNoTLS;
 
 /**************************************************************************************************/
-/* Public functions																				  */
+/* Public functions                                                                               */
 /**************************************************************************************************/
 
 /**
@@ -75,40 +84,81 @@ else version = HaveNoTLS;
 		requests with the supplied settings. Another call to `listenHTTP` can be
 		used afterwards to start listening again.
 */
-import vibe.http.router;
-HTTPListener listenHTTP(alias Handler)(HTTPServerSettings settings)
-	if (is(typeof(Handler) == URLRouter) || is(typeof(Handler) : HTTPServerRequestHandler))
-{
-	//if (!settings)
-		//settings = HTTPServerSettings;
-	return listenHTTPPlain(settings, (req, res) @trusted => Handler.handleRequest(req, res));
+HTTPListener listenHTTP(Settings)(Settings _settings, HTTPServerRequestDelegate request_handler)
+@safe
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	// auto-construct HTTPServerSettings
+	static if (is(Settings == string))
+		auto settings = new HTTPServerSettings(_settings);
+	else
+		alias settings = _settings;
+
+	enforce(settings.bindAddresses.length, "Must provide at least one bind address for a HTTP server.");
+
+	// if a VibeDist host was specified on the command line, register there instead of listening
+	// directly.
+	if (s_distHost.length && !settings.disableDistHost) {
+		return listenHTTPDist(settings, request_handler, s_distHost, s_distPort);
+	} else {
+		return listenHTTPPlain(settings, request_handler);
+	}
 }
-
-import std.traits;
-import std.typetuple;
-HTTPListener listenHTTP(alias Handler)(HTTPServerSettings settings)
-	if ((isCallable!Handler)
-		&& is(ReturnType!Handler == void)
-		&& is(ParameterTypeTuple!Handler == TypeTuple!(HTTPServerRequest, HTTPServerResponse)))
-{
-	//if (!settings)
-	//settings = HTTPServerSettings;
-	return listenHTTPPlain(settings, (req, res) @trusted => Handler(req, res));
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestFunction request_handler)
+@safe
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, () @trusted { return toDelegate(request_handler); } ());
 }
-
-HTTPListener listenHTTP(H)(HTTPServerSettings settings, H handler)
-{
-	return listenHTTP!handler(settings);
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestHandler request_handler)
+@safe
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, &request_handler.handleRequest);
+}
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestDelegateS request_handler)
+@safe
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, cast(HTTPServerRequestDelegate)request_handler);
+}
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestFunctionS request_handler)
+@safe
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, () @trusted { return toDelegate(request_handler); } ());
+}
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, HTTPServerRequestHandlerS request_handler)
+@safe
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, &request_handler.handleRequest);
 }
 
-HTTPListener listenHTTP(H)(string bind_string, H handler)
-{
-	auto settings = new HTTPServerSettings(bind_string);
-	return listenHTTP!handler(settings);
+/// Scheduled for deprecation - use a `@safe` callback instead.
+HTTPListener listenHTTP(Settings)(Settings settings, void delegate(HTTPServerRequest, HTTPServerResponse) @system request_handler)
+@system
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, (req, res) @trusted => request_handler(req, res));
+}
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, void function(HTTPServerRequest, HTTPServerResponse) @system request_handler)
+@system
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, (req, res) @trusted => request_handler(req, res));
+}
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, void delegate(scope HTTPServerRequest, scope HTTPServerResponse) @system request_handler)
+@system
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, (scope req, scope res) @trusted => request_handler(req, res));
+}
+/// ditto
+HTTPListener listenHTTP(Settings)(Settings settings, void function(scope HTTPServerRequest, scope HTTPServerResponse) @system request_handler)
+@system
+if (is(Settings == string) || is(Settings == HTTPServerSettings)) {
+	return listenHTTP(settings, (scope req, scope res) @trusted => request_handler(req, res));
 }
 
-/* Testing listenHTTP
- */
 unittest
 {
 	void test()
@@ -118,7 +168,7 @@ unittest
 		listenHTTP(":8080", new class HTTPServerRequestHandler {
 			void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe {}
 		});
-		//listenHTTP(":8080", (req, res) {}); // fails on parameter type tuple
+		listenHTTP(":8080", (req, res) {});
 
 		static void testSafeFunctionS(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
 		listenHTTP(":8080", &testSafeFunctionS);
@@ -127,35 +177,93 @@ unittest
 		listenHTTP(":8080", new class HTTPServerRequestHandler {
 			void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe {}
 		});
-		//listenHTTP(":8080", (scope req, scope res) {}); // fails on parameter type tuple
+		listenHTTP(":8080", (scope req, scope res) {});
 	}
 }
 
-unittest {
-	import vibe.http.router;
 
-	void test()
-	{
-		auto router = new URLRouter;
-		router.get("/old_url", staticRedirect("http://example.org/new_url", HTTPStatus.movedPermanently));
-		HTTPServerSettings settings;
-		listenHTTP!router(settings);
+/** Treats an existing connection as an HTTP connection and processes incoming
+	requests.
+
+	After all requests have been processed, the connection will be closed and
+	the function returns to the caller.
+
+	Params:
+		connection = The stream to treat as an incoming HTTP client connection.
+		context = Information about the incoming listener and available
+			virtual hosts
+*/
+void handleHTTPConnection(TCPConnection connection, HTTPServerContext context)
+@safe {
+	InterfaceProxy!Stream http_stream;
+	http_stream = connection;
+
+	scope (exit) connection.close();
+
+	// check wether the client's address is banned
+	foreach (ref virtual_host; context.m_virtualHosts)
+		if ((virtual_host.settings.rejectConnectionPredicate !is null) &&
+			virtual_host.settings.rejectConnectionPredicate(connection.remoteAddress()))
+			return;
+
+	// Set NODELAY to true, to avoid delays caused by sending the response
+	// header and body in separate chunks. Note that to avoid other performance
+	// issues (caused by tiny packets), this requires using an output buffer in
+	// the event driver, which is the case at least for the legacy libevent
+	// based driver.
+	connection.tcpNoDelay = true;
+
+	version(HaveNoTLS) {} else {
+		TLSStreamType tls_stream;
 	}
-}
 
-unittest {
-	// testing a callable as request handler
-	void handleRequest (HTTPServerRequest req, HTTPServerResponse res)
-	@safe {
-		if (req.path == "/")
-		res.writeBody("Hello, World! Delegate");
+	if (!connection.waitForData(10.seconds())) {
+		logDebug("Client didn't send the initial request in a timely manner. Closing connection.");
+		return;
+	}
+
+	// If this is a HTTPS server, initiate TLS
+	if (context.tlsContext) {
+		version (HaveNoTLS) assert(false, "No TLS support compiled in.");
+		else {
+			logDebug("Accept TLS connection: %s", context.tlsContext.kind);
+			// TODO: reverse DNS lookup for peer_name of the incoming connection for TLS client certificate verification purposes
+			tls_stream = createTLSStreamFL(http_stream, context.tlsContext, TLSStreamState.accepting, null, connection.remoteAddress);
+			http_stream = tls_stream;
+		}
 	}
 
-	auto settings = new HTTPServerSettings();
-	settings.port = 8060;
-	settings.bindAddresses = ["localhost"];
+	while (!connection.empty) {
+		HTTPServerSettings settings;
+		bool keep_alive;
+
+		version(HaveNoTLS) {} else {
+			// handle oderly TLS shutdowns
+			if (tls_stream && tls_stream.empty) break;
+		}
+
+		() @trusted {
+			import vibe.internal.utilallocator: RegionListAllocator;
+
+			version (VibeManualMemoryManagement)
+				scope request_allocator = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
+			else
+				scope request_allocator = new RegionListAllocator!(shared(GCAllocator), true)(1024, GCAllocator.instance);
 
-	listenHTTP!handleRequest(settings);
+			handleRequest(http_stream, connection, context, settings, keep_alive, request_allocator);
+		} ();
+		if (!keep_alive) { logTrace("No keep-alive - disconnecting client."); break; }
+
+		logTrace("Waiting for next request...");
+		// wait for another possible request on a keep-alive connection
+		if (!connection.waitForData(settings.keepAliveTimeout)) {
+			if (!connection.connected) logTrace("Client disconnected.");
+			else logDebug("Keep-alive connection timed out!");
+			break;
+		}
+	}
+
+	logTrace("Done handling connection.");
 }
 
 
@@ -169,7 +277,6 @@ unittest {
 	};
 }
 
-
 /**
 	Provides a HTTP request handler that responds with a static redirection to the specified URL.
 
@@ -195,6 +302,18 @@ HTTPServerRequestDelegate staticRedirect(URL url, HTTPStatus status = HTTPStatus
 }
 
 ///
+unittest {
+	import vibe.http.router;
+
+	void test()
+	{
+		auto router = new URLRouter;
+		router.get("/old_url", staticRedirect("http://example.org/new_url", HTTPStatus.movedPermanently));
+
+		listenHTTP(new HTTPServerSettings, router);
+	}
+}
+
 
 /**
 	Sets a VibeDist host to register with.
@@ -232,105 +351,102 @@ void setVibeDistHost(string host, ushort port)
 	compileHTMLDietFile!(template_file, ALIASES, DefaultDietFilters)(output);
 }
 
-version (Have_diet_ng)
-{
-	import diet.traits;
-
-	/**
-		Provides the default `css`, `javascript`, `markdown` and `htmlescape` filters
-	 */
-	@dietTraits
-	struct DefaultDietFilters {
-		import diet.html : HTMLOutputStyle;
-		import std.string : splitLines;
 
-		version (VibeOutputCompactHTML) enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.compact;
-		else enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.pretty;
+/**
+	Provides the default `css`, `javascript`, `markdown` and `htmlescape` filters
+ */
+@dietTraits
+struct DefaultDietFilters {
+	import diet.html : HTMLOutputStyle;
+	import diet.traits : SafeFilterCallback;
+	import std.string : splitLines;
 
-		static string filterCss(I)(I text, size_t indent = 0)
-		{
-			auto lines = splitLines(text);
+	version (VibeOutputCompactHTML) enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.compact;
+	else enum HTMLOutputStyle htmlOutputStyle = HTMLOutputStyle.pretty;
 
-			string indent_string = "\n";
-			while (indent-- > 0) indent_string ~= '\t';
+	static string filterCss(I)(I text, size_t indent = 0)
+	{
+		auto lines = splitLines(text);
 
-			string ret = indent_string~"";
+		string indent_string = "\n";
+		while (indent-- > 0) indent_string ~= '\t';
 
-			return ret;
-		}
+		string ret = indent_string~"";
 
+		return ret;
+	}
 
-		static string filterJavascript(I)(I text, size_t indent = 0)
-		{
-			auto lines = splitLines(text);
 
-			string indent_string = "\n";
-			while (indent-- > 0) indent_string ~= '\t';
+	static string filterJavascript(I)(I text, size_t indent = 0)
+	{
+		auto lines = splitLines(text);
 
-			string ret = indent_string~"";
+		string indent_string = "\n";
+		while (indent-- > 0) indent_string ~= '\t';
 
-			return ret;
-		}
+		string ret = indent_string~"";
 
-		static string filterMarkdown(I)(I text)
-		{
-			import vibe.textfilter.markdown : markdown = filterMarkdown;
-			// TODO: indent
-			return markdown(text);
-		}
+		return ret;
+	}
 
-		static string filterHtmlescape(I)(I text)
-		{
-			import vibe.textfilter.html : htmlEscape;
-			// TODO: indent
-			return htmlEscape(text);
-		}
+	static string filterMarkdown(I)(I text)
+	{
+		import vibe.textfilter.markdown : markdown = filterMarkdown;
+		// TODO: indent
+		return markdown(text);
+	}
 
-		static this()
-		{
-			filters["css"] = (input, scope output) { output(filterCss(input)); };
-			filters["javascript"] = (input, scope output) { output(filterJavascript(input)); };
-			filters["markdown"] = (input, scope output) { output(filterMarkdown(() @trusted { return cast(string)input; } ())); };
-			filters["htmlescape"] = (input, scope output) { output(filterHtmlescape(input)); };
-		}
+	static string filterHtmlescape(I)(I text)
+	{
+		import vibe.textfilter.html : htmlEscape;
+		// TODO: indent
+		return htmlEscape(text);
+	}
 
-		static SafeFilterCallback[string] filters;
+	static this()
+	{
+		filters["css"] = (in input, scope output) { output(filterCss(input)); };
+		filters["javascript"] = (in input, scope output) { output(filterJavascript(input)); };
+		filters["markdown"] = (in input, scope output) { output(filterMarkdown(() @trusted { return cast(string)input; } ())); };
+		filters["htmlescape"] = (in input, scope output) { output(filterHtmlescape(input)); };
 	}
 
+	static SafeFilterCallback[string] filters;
+}
 
-	unittest {
-		static string compile(string diet)() {
-			import std.array : appender;
-			import std.string : strip;
-			import diet.html : compileHTMLDietString;
-			auto dst = appender!string;
-			dst.compileHTMLDietString!(diet, DefaultDietFilters);
-			return strip(cast(string)(dst.data));
-		}
 
-		assert(compile!":css .test" == "");
-		assert(compile!":javascript test();" == "");
-		assert(compile!":markdown **test**" == "

test\n

"); - assert(compile!":htmlescape " == "<test>"); - assert(compile!":css !{\".test\"}" == ""); - assert(compile!":javascript !{\"test();\"}" == ""); - assert(compile!":markdown !{\"**test**\"}" == "

test\n

"); - assert(compile!":htmlescape !{\"\"}" == "<test>"); - assert(compile!":javascript\n\ttest();" == ""); - } +unittest { + static string compile(string diet)() { + import std.array : appender; + import std.string : strip; + import diet.html : compileHTMLDietString; + auto dst = appender!string; + dst.compileHTMLDietString!(diet, DefaultDietFilters); + return strip(cast(string)(dst.data)); + } + + assert(compile!":css .test" == ""); + assert(compile!":javascript test();" == ""); + assert(compile!":markdown **test**" == "

test\n

"); + assert(compile!":htmlescape " == "<test>"); + assert(compile!":css !{\".test\"}" == ""); + assert(compile!":javascript !{\"test();\"}" == ""); + assert(compile!":markdown !{\"**test**\"}" == "

test\n

"); + assert(compile!":htmlescape !{\"\"}" == "<test>"); + assert(compile!":javascript\n\ttest();" == ""); } /** - Creates a HTTPServerRequest suitable for writing unit tests. - */ + Creates a HTTPServerRequest suitable for writing unit tests. +*/ HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method = HTTPMethod.GET, InputStream data = null) @safe { InetHeaderMap headers; @@ -340,7 +456,8 @@ HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method = HTTPM HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method, InetHeaderMap headers, InputStream data = null) @safe { auto tls = url.schema == "https"; - auto ret = HTTPServerRequest(Clock.currTime(UTC()), url.port ? url.port : tls ? 443 : 80); + auto ret = new HTTPServerRequest(Clock.currTime(UTC()), url.port ? url.port : tls ? 443 : 80); + ret.m_settings = new HTTPServerSettings; ret.requestPath = url.path; ret.queryString = url.queryString; ret.username = url.username; @@ -348,7 +465,7 @@ HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method, InetHe ret.requestURI = url.localURI; ret.method = method; ret.tls = tls; - //ret.headers = headers; // TODO compiler error + ret.headers = headers; ret.bodyReader = data; return ret; } @@ -363,16 +480,16 @@ HTTPServerRequest createTestHTTPServerRequest(URL url, HTTPMethod method, InetHe data_mode = If set to `TestHTTPResponseMode.bodyOnly`, only the body contents get written to `data_sink`. Otherwise the raw response including the HTTP header is written. - */ +*/ HTTPServerResponse createTestHTTPServerResponse(OutputStream data_sink = null, SessionStore session_store = null, TestHTTPResponseMode data_mode = TestHTTPResponseMode.plain) @safe { import vibe.stream.wrapper; - auto settings = new HTTPServerSettings; + HTTPServerSettings settings; if (session_store) { - //settings = HTTPServerSettings; + settings = new HTTPServerSettings; settings.sessionStore = session_store; } @@ -381,35 +498,44 @@ HTTPServerResponse createTestHTTPServerResponse(OutputStream data_sink = null, outstr = createProxyStream(Stream.init, data_sink); else outstr = createProxyStream(Stream.init, nullSink); - auto ret = HTTPServerResponse(outstr, InterfaceProxy!ConnectionStream.init, + auto ret = new HTTPServerResponse(outstr, InterfaceProxy!ConnectionStream.init, settings, () @trusted { return vibeThreadAllocator(); } ()); - if (data_sink && data_mode == TestHTTPResponseMode.bodyOnly) ret.m_data.m_bodyWriter = data_sink; + if (data_sink && data_mode == TestHTTPResponseMode.bodyOnly) ret.m_bodyWriter = data_sink; return ret; } /**************************************************************************************************/ -/* Public types */ +/* Public types */ /**************************************************************************************************/ +/// Delegate based request handler +alias HTTPServerRequestDelegate = void delegate(HTTPServerRequest req, HTTPServerResponse res) @safe; +/// Static function based request handler +alias HTTPServerRequestFunction = void function(HTTPServerRequest req, HTTPServerResponse res) @safe; /// Interface for class based request handlers interface HTTPServerRequestHandler { /// Handles incoming HTTP requests void handleRequest(HTTPServerRequest req, HTTPServerResponse res) @safe ; } - -alias HTTPContext = HTTPServerContext; - -/// Delegate based request handler -alias HTTPServerRequestDelegate = void delegate(HTTPServerRequest req, HTTPServerResponse res) @safe; +/// Delegate based request handler with scoped parameters alias HTTPServerRequestDelegateS = void delegate(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe; -/// Static function based request handler -alias HTTPServerRequestFunction = void function(HTTPServerRequest req, HTTPServerResponse res) @safe; +/// Static function based request handler with scoped parameters +alias HTTPServerRequestFunctionS = void function(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe; +/// Interface for class based request handlers with scoped parameters +interface HTTPServerRequestHandlerS { + /// Handles incoming HTTP requests + void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe; +} +unittest { + static assert(is(HTTPServerRequestDelegateS : HTTPServerRequestDelegate)); + static assert(is(HTTPServerRequestFunctionS : HTTPServerRequestFunction)); +} /// Aggregates all information about an HTTP error status. -struct HTTPServerErrorInfo { +final class HTTPServerErrorInfo { /// The HTTP status code int code; /// The error message @@ -420,7 +546,6 @@ struct HTTPServerErrorInfo { Throwable exception; } - /// Delegate type used for user defined error page generator callbacks. alias HTTPServerErrorPageHandler = void delegate(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) @safe; @@ -431,54 +556,18 @@ enum TestHTTPResponseMode { } -private enum HTTPServerOptionImpl { - none = 0, - errorStackTraces = 1<<7, - reusePort = 1<<8, - distribute = 1<<9 // deprecated -} - -// TODO: Should be turned back into an enum once the deprecated symbols can be removed /** - Specifies optional features of the HTTP server. + Specifies optional features of the HTTP server. - Disabling unneeded features can speed up the server or reduce its memory usage. + Disabling unneeded features can speed up the server or reduce its memory usage. - Note that the options `parseFormBody`, `parseJsonBody` and `parseMultiPartBody` - will also drain the `HTTPServerRequest.bodyReader` stream whenever a request - body with form or JSON data is encountered. + Note that the options `parseFormBody`, `parseJsonBody` and `parseMultiPartBody` + will also drain the `HTTPServerRequest.bodyReader` stream whenever a request + body with form or JSON data is encountered. */ -struct HTTPServerOption { - static enum none = HTTPServerOptionImpl.none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum parseURL = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum parseQueryString = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum parseFormBody = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum parseJsonBody = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum parseMultiPartBody = none; - /* Deprecated: Distributes request processing among worker threads - - Note that this functionality assumes that the request handler - is implemented in a thread-safe way. However, the D type system - is bypassed, so that no static verification takes place. - - For this reason, it is recommended to instead use - `vibe.core.core.runWorkerTaskDist` and call `listenHTTP` - from each task/thread individually. If the `reusePort` option - is set, then all threads will be able to listen on the same port, - with the operating system distributing the incoming connections. - - If possible, instead of threads, the use of separate processes - is more robust and often faster. The `reusePort` option works - the same way in this scenario. - */ - deprecated("Use runWorkerTaskDist or start threads separately. It will be removed in 0.9.") - static enum distribute = HTTPServerOptionImpl.distribute; - /* Enables stack traces (`HTTPServerErrorInfo.debugMessage`). +enum HTTPServerOption { + none = 0, + /** Enables stack traces (`HTTPServerErrorInfo.debugMessage`). Note that generating the stack traces are generally a costly operation that should usually be avoided in production @@ -486,34 +575,17 @@ struct HTTPServerOption { the application, such as function addresses, which can help an attacker to abuse possible security holes. */ - static enum errorStackTraces = HTTPServerOptionImpl.errorStackTraces; + errorStackTraces = 1<<7, /// Enable port reuse in `listenTCP()` - static enum reusePort = HTTPServerOptionImpl.reusePort; - - /* The default set of options. + reusePort = 1<<8, + /// Enable address reuse in `listenTCP()` + reuseAddress = 1<<10, + /** The default set of options. Includes all parsing options, as well as the `errorStackTraces` option if the code is compiled in debug mode. */ - static enum defaults = () { debug return HTTPServerOptionImpl.errorStackTraces; else return HTTPServerOptionImpl.none; } ().HTTPServerOption; - - deprecated("None has been renamed to none.") - static enum None = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum ParseURL = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum ParseQueryString = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum ParseFormBody = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum ParseJsonBody = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum ParseMultiPartBody = none; - deprecated("This is done lazily. It will be removed in 0.9.") - static enum ParseCookies = none; - - HTTPServerOptionImpl x; - alias x this; + defaults = () { auto ret = reuseAddress; debug ret |= errorStackTraces; return ret; } (), } @@ -563,7 +635,7 @@ final class HTTPServerSettings { load in case of invalid or unwanted requests (DoS). By default, HTTPServerOption.defaults is used. */ - HTTPServerOptionImpl options = HTTPServerOption.defaults; + HTTPServerOption options = HTTPServerOption.defaults; /** Time of a request after which the connection is closed with an error; not supported yet @@ -586,6 +658,9 @@ final class HTTPServerSettings { /// the url and all headers. ulong maxRequestHeaderSize = 8192; + /// Maximum number of bytes in a single line in the request header. + size_t maxRequestHeaderLineSize = 4096; + /// Sets a custom handler for displaying error pages for HTTP errors @property HTTPServerErrorPageHandler errorPageHandler() @safe { return errorPageHandler_; } /// ditto @@ -596,11 +671,6 @@ final class HTTPServerSettings { this.errorPageHandler = (req, res, err) @trusted { del(req, res, err); }; } - void handleErrorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo err) - @safe { - errorPageHandler_(req, res, err); - } - private HTTPServerErrorPageHandler errorPageHandler_ = null; /// If set, a HTTPS server will be started instead of plain HTTP. @@ -610,6 +680,9 @@ final class HTTPServerSettings { SessionStore sessionStore; string sessionIdCookie = "vibe.session_id"; + /// Session options to use when initializing a new session. + SessionOption sessionOptions = SessionOption.httpOnly; + /// import vibe.core.core : vibeVersionString; string serverString = "vibe.d/" ~ vibeVersionString; @@ -637,7 +710,6 @@ final class HTTPServerSettings { /// Returns a duplicate of the settings object. @property HTTPServerSettings dup() @safe { - //auto ret = HTTPServerSettings; auto ret = new HTTPServerSettings; foreach (mem; __traits(allMembers, HTTPServerSettings)) { static if (mem == "sslContext") {} @@ -684,7 +756,7 @@ final class HTTPServerSettings { */ this(string bind_string) @safe { - //this(); + this(); if (bind_string.startsWith('[')) { auto idx = bind_string.indexOf(']'); @@ -726,7 +798,8 @@ final class HTTPServerSettings { alias RejectConnectionPredicate = bool delegate (in NetworkAddress) @safe nothrow; -/** Options altering how sessions are created. +/** + Options altering how sessions are created. Multiple values can be or'ed together. @@ -736,13 +809,13 @@ enum SessionOption { /// No options. none = 0, - /* Instructs the browser to disallow accessing the session ID from JavaScript. + /** Instructs the browser to disallow accessing the session ID from JavaScript. See_Also: Cookie.httpOnly */ httpOnly = 1<<0, - /* Instructs the browser to disallow sending the session ID over + /** Instructs the browser to disallow sending the session ID over unencrypted connections. By default, the type of the connection on which the session is started @@ -752,7 +825,7 @@ enum SessionOption { */ secure = 1<<1, - /* Instructs the browser to allow sending the session ID over unencrypted + /** Instructs the browser to allow sending the session ID over unencrypted connections. By default, the type of the connection on which the session is started @@ -760,78 +833,73 @@ enum SessionOption { See_Also: secure, Cookie.secure */ - noSecure = 1<<2 -} + noSecure = 1<<2, + /** + Instructs the browser to allow sending this cookie along with cross-site requests. + + By default, the protection is `strict`. This flag allows to set it to `lax`. + The strict value will prevent the cookie from being sent by the browser + to the target site in all cross-site browsing context, + even when following a regular link. + */ + noSameSiteStrict = 1<<3, +} /** Represents a HTTP request as received by the server side. - */ -struct HTTPServerRequest { +*/ +final class HTTPServerRequest : HTTPRequest { + import std.variant : Variant; import vibe.utils.dictionarylist : DictionaryList; - private HTTPServerRequestData* m_data; - - @safe: - - this (HTTPServerRequestData* data) - { - m_data = data; - } - - this (SysTime reqtime, ushort bindPort) - { - auto data = new HTTPServerRequestData(reqtime, bindPort); - () @trusted { m_data = data; } (); - } - - auto opCast(T)() const if (is(T == bool)) { return m_data !is null; } + private { + SysTime m_timeCreated; + HTTPServerSettings m_settings; + ushort m_port; + string m_peer; - package @property scope const(HTTPServerSettings) serverSettings() - const { - return m_data.serverSettings; + // lazily parsed request components + Nullable!string m_path; + Nullable!CookieValueMap m_cookies; + Nullable!FormFields m_query; + Nullable!Json m_json; + Nullable!FormFields m_form; + FilePartFormFields m_files; } - @property scope data() pure nothrow { return m_data; } - - @property scope string requestURI() const pure nothrow { return m_data.requestURI; } - // ditto - @property void requestURI(string uri) pure nothrow { m_data.requestURI = uri; } - - @property scope string peer() { return m_data.peer; } - - @property scope CookieValueMap cookies() { return m_data.cookies; } - - @property scope FormFields query() { return m_data.query; } + /// The IP address of the client + NetworkAddress clientAddress; - @property scope Json json() { return m_data.json; } + /// Determines if the request should be logged to the access log file. + bool noLog; - @property scope FormFields form() { return m_data.form; } + /// Determines if the request was issued over an TLS encrypted channel. + bool tls; - @property scope FilePartFormFields files() { return m_data.files; } + /** Information about the TLS certificate provided by the client. - @property scope SysTime timeCreated() const { return m_data.timeCreated; } - - @property scope URL fullURL() const { return m_data.fullURL; } - - @property scope string rootDir() const pure nothrow { return m_data.rootDir; } - - @property scope string username() const pure nothrow { return m_data.username; } - // ditto - @property void username(string name) nothrow { m_data.username = name; } + Remarks: This field is only set if `tls` is true, and the peer + presented a client certificate. + */ + TLSCertificateInformation clientCertificate; - @property scope string path() pure { return m_data.path; } - // ditto - @property void path(scope string p) { m_data.path = path; } + /** The path part of the requested URI. + */ + InetPath requestPath; - @property ref InetHeaderMap headers() pure nothrow { return m_data.headers; } + /** The user name part of the URL, if present. + */ + string username; - @property scope bool persistent() const pure nothrow { return m_data.persistent; } + /** The _password part of the URL, if present. + */ + string password; - @property scope string queryString() const pure nothrow { return m_data.queryString; } - // ditto - @property void queryString(string qstr) nothrow { m_data.queryString = qstr; } + /** The _query string part of the URL. + */ + string queryString; /** A map of general parameters for the request. @@ -839,249 +907,629 @@ struct HTTPServerRequest { information for later stages. For example vibe.http.router.URLRouter uses this map to store the value of any named placeholders. */ - @property scope ref DictionaryList!(string, true, 8) params() pure nothrow { return m_data.params; } + DictionaryList!(string, true, 8) params; - @property scope string requestURL() const pure nothrow { return m_data.requestURL; } + /** A map of context items for the request. - @property scope HTTPVersion httpVersion() const pure nothrow { return m_data.httpVersion; } - // ditto - @property void httpVersion(HTTPVersion hver) nothrow { m_data.httpVersion = hver; } + This is especially useful for passing application specific data down + the chain of processors along with the request itself. - @property scope string host() const pure nothrow { return m_data.host; } - // ditto - @property void host(string v) { m_data.headers["Host"] = v; } + For example, a generic route may be defined to check user login status, + if the user is logged in, add a reference to user specific data to the + context. - @property scope string contentType() const pure nothrow { return m_data.contentType; } - - // ditto - @property void contentType(string ct) { m_data.headers["Content-Type"] = ct; } - - @property scope string contentTypeParameters() const pure nothrow { return m_data.contentTypeParameters; } - - @property scope NetworkAddress clientAddress() const pure nothrow { return m_data.clientAddress; } - // ditto - @property void clientAddress(NetworkAddress naddr) nothrow { m_data.clientAddress = naddr; } + This is implemented with `std.variant.Variant` to allow any type of data. + */ + DictionaryList!(Variant, true, 2) context; - @property scope bool tls() const pure nothrow { return m_data.tls; } - // ditto - @property void tls(bool val) nothrow { m_data.tls = val; } + /** Supplies the request body as a stream. - @property scope HTTPMethod method() const pure nothrow { return m_data.method; } - // ditto - @property void method(HTTPMethod m) nothrow { m_data.method = m; } + Note that when certain server options are set (such as + HTTPServerOption.parseJsonBody) and a matching request was sent, + the returned stream will be empty. If needed, remove those + options and do your own processing of the body when launching + the server. HTTPServerOption has a list of all options that affect + the request body. + */ + InputStream bodyReader; - package @property scope HTTPServerSettings m_settings() pure nothrow { return m_data.m_settings; } - // ditto - package @property void m_settings(HTTPServerSettings settings) nothrow { m_data.m_settings = settings; } + /** The current Session object. - @property scope InputStream bodyReader() nothrow { return m_data.bodyReader; } - // ditto - @property void bodyReader(InputStream inStr) nothrow { m_data.bodyReader = inStr; } + This field is set if HTTPServerResponse.startSession() has been called + on a previous response and if the client has sent back the matching + cookie. - @property scope string password() const pure nothrow { return m_data.password; } - // ditto - @property void password(string pwd) nothrow { m_data.password = pwd; } + Remarks: Requires the HTTPServerOption.parseCookies option. + */ + Session session; - @property scope Session session() pure nothrow { return m_data.session; } - // ditto - @property void session(Session session) { m_data.session = session; } - @property scope InetPath requestPath() const pure nothrow { return m_data.requestPath; } - // ditto - @property void requestPath(InetPath reqpath) nothrow { m_data.requestPath = reqpath; } + this(SysTime time, ushort port) + @safe scope { + m_timeCreated = time.toUTC(); + m_port = port; + } - @property scope FilePartFormFields _files() pure nothrow { return m_data._files; } + /// The IP address of the client in string form + @property string peer() + @safe nothrow scope { + if (!m_peer) { + version (Have_vibe_core) {} else scope (failure) assert(false); + // store the IP address (IPv4 addresses forwarded over IPv6 are stored in IPv4 format) + auto peer_address_string = this.clientAddress.toString(); + if (peer_address_string.startsWith("::ffff:") && peer_address_string[7 .. $].indexOf(':') < 0) + m_peer = peer_address_string[7 .. $]; + else m_peer = peer_address_string; + } + return m_peer; + } - @property scope bool noLog() const pure nothrow { return m_data.noLog; } + /** The _path part of the URL. - @property scope TLSCertificateInformation clientCertificate() - pure nothrow { - return m_data.clientCertificate; + Note that this function contains the decoded version of the + requested path, which can yield incorrect results if the path + contains URL encoded path separators. Use `requestPath` instead to + get an encoding-aware representation. + */ + deprecated("Use .requestPath instead") + string path() + @safe scope { + if (m_path.isNull) + m_path = urlDecode(requestPath.toString); + return m_path.get; } - @property void clientCertificate(TLSCertificateInformation cert) - pure nothrow { - m_data.clientCertificate = cert; + /** Contains the list of cookies that are stored on the client. + + Note that the a single cookie name may occur multiple times if multiple + cookies have that name but different paths or domains that all match + the request URI. By default, the first cookie will be returned, which is + the or one of the cookies with the closest path match. + */ + @property ref CookieValueMap cookies() + @safe return { + if (m_cookies.isNull) { + m_cookies = CookieValueMap.init; + if (auto pv = "cookie" in headers) + parseCookies(*pv, m_cookies.get); + } + return m_cookies.get; } -} -/** - Represents a HTTP response as sent from the server side. -*/ -struct HTTPServerResponse { - @safe: + /** Contains all _form fields supplied using the _query string. - private HTTPServerResponseData* m_data; + The fields are stored in the same order as they are received. + */ + @property ref FormFields query() + @safe return { + if (m_query.isNull) { + m_query = FormFields.init; + parseURLEncodedForm(queryString, m_query.get); + } - this (HTTPServerResponseData* data) - { - m_data = data; + return m_query.get; } - static if (!is(Stream == InterfaceProxy!Stream)) { - this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc) - @safe { - this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc); + /** Contains the parsed Json for a JSON request. + + A JSON request must have the Content-Type "application/json" or "application/vnd.api+json". + */ + @property ref Json json() + @safe return { + if (m_json.isNull) { + auto splitter = contentType.splitter(';'); + auto ctype = splitter.empty ? "" : splitter.front; + + if (icmp2(ctype, "application/json") == 0 || icmp2(ctype, "application/vnd.api+json") == 0) { + auto bodyStr = bodyReader.readAllUTF8(); + if (!bodyStr.empty) m_json = parseJson(bodyStr); + else m_json = Json.undefined; + } else { + m_json = Json.undefined; + } } + return m_json.get; } - this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc) - { - HTTPServerResponseData* data = new HTTPServerResponseData(conn, raw_connection, settings, req_alloc); - this(data); + /// Get the json body when there is no content-type header + unittest { + assert(createTestHTTPServerRequest(URL("http://localhost/")).json.type == Json.Type.undefined); } - auto opCast(T)() const @safe nothrow if (is(T == bool)) { return m_data !is null; } - - @property scope data() @safe { return m_data; } + /** Contains the parsed parameters of a HTML POST _form request. - @property scope HTTPVersion httpVersion() { return m_data.httpVersion; } + The fields are stored in the same order as they are received. - @property void httpVersion(HTTPVersion h) { m_data.httpVersion = h; } - - @property scope int statusCode() { return m_data.statusCode; } - - @property void statusCode(scope HTTPStatus code) @safe { - m_data.statusCode = code; - } + Remarks: + A form request must either have the Content-Type + "application/x-www-form-urlencoded" or "multipart/form-data". + */ + @property ref FormFields form() + @safe return { + if (m_form.isNull) + parseFormAndFiles(); - @property void statusCode(scope int code) @safe { - m_data.statusCode = code; + return m_form.get; } - @property scope string statusPhrase() { return m_data.statusPhrase; } - - @property scope InetHeaderMap headers() { return m_data.headers; } + /** Contains information about any uploaded file for a HTML _form request. + */ + @property ref FilePartFormFields files() + @safe return { + // m_form and m_files are parsed in one step + if (m_form.isNull) { + parseFormAndFiles(); + assert(!m_form.isNull); + } - @property void headers(scope InetHeaderMap hmap) @safe { - m_data.headers = hmap; + return m_files; } - @property scope Cookie[string] cookies() { return m_data.cookies; } - - @property string toString() { return m_data.toString(); } + /** Time when this request started processing. + */ + @property SysTime timeCreated() const @safe scope { return m_timeCreated; } - @property scope string contentType() { return m_data.contentType(); } - @property void contentType(string ct) { return m_data.contentType(ct); } - @property scope SysTime timeFinalized() const { return m_data.timeFinalized; } + /** The full URL that corresponds to this request. - @property scope bool headerWritten() const { return m_data.headerWritten; } + The host URL includes the protocol, host and optionally the user + and password that was used for this request. This field is useful to + construct self referencing URLs. - @property scope bool isHeadResponse() const { return m_data.isHeadResponse(); } + Note that the port is currently not set, so that this only works if + the standard port is used. + */ + @property URL fullURL() + const @safe scope { + URL url; - @property scope bool tls() const { return m_data.tls(); } + auto xfh = this.headers.get("X-Forwarded-Host"); + auto xfp = this.headers.get("X-Forwarded-Port"); + auto xfpr = this.headers.get("X-Forwarded-Proto"); - @property void tls(bool v) { m_data.m_tls = v; } + // Set URL host segment. + if (xfh.length) { + url.host = xfh; + } else if (!this.host.empty) { + url.host = this.host; + } else if (!m_settings.hostName.empty) { + url.host = m_settings.hostName; + } else { + url.host = m_settings.bindAddresses[0]; + } + + // Set URL schema segment. + if (xfpr.length) { + url.schema = xfpr; + } else if (this.tls) { + url.schema = "https"; + } else { + url.schema = "http"; + } + + // Set URL port segment. + if (xfp.length) { + try { + url.port = xfp.to!ushort; + } catch (ConvException) { + // TODO : Consider responding with a 400/etc. error from here. + logWarn("X-Forwarded-Port header was not valid port (%s)", xfp); + } + } else if (!xfh) { + if (url.schema == "https") { + if (m_port != 443U) url.port = m_port; + } else { + if (m_port != 80U) url.port = m_port; + } + } - @property void m_settings(HTTPServerSettings s) { m_data.m_settings = s; } + if (url.host.startsWith('[')) { // handle IPv6 address + auto idx = url.host.indexOf(']'); + if (idx >= 0 && idx+1 < url.host.length && url.host[idx+1] == ':') + url.host = url.host[1 .. idx]; + } else { // handle normal host names or IPv4 address + auto idx = url.host.indexOf(':'); + if (idx >= 0) url.host = url.host[0 .. idx]; + } - @property void m_session(Session s) { m_data.m_session = s; } + url.username = this.username; + url.password = this.password; + url.localURI = this.requestURI; - @property void m_isHeadResponse(bool b) { m_data.m_isHeadResponse = b; } + return url; + } - void setStatusCode(int v) { m_data.statusCode = v; } + /** The relative path to the root folder. - void writeBody(in ubyte[] data, string contentType = null) - { - m_data.writeBody(data, contentType); + Using this function instead of absolute URLs for embedded links can be + useful to avoid dead link when the site is piped through a + reverse-proxy. + + The returned string always ends with a slash. + */ + @property string rootDir() + const @safe scope { + import std.algorithm.searching : count; + import std.range : empty; + auto depth = requestPath.bySegment.count!(s => !s.name.empty); + if (depth > 0 && !requestPath.endsWithSlash) depth--; + return depth == 0 ? "./" : replicate("../", depth); + } + + unittest { + assert(createTestHTTPServerRequest(URL("http://localhost/")).rootDir == "./"); + assert(createTestHTTPServerRequest(URL("http://localhost/foo")).rootDir == "./"); + assert(createTestHTTPServerRequest(URL("http://localhost/foo/")).rootDir == "../"); + assert(createTestHTTPServerRequest(URL("http://localhost/foo/bar")).rootDir == "../"); + assert(createTestHTTPServerRequest(URL("http://localhost")).rootDir == "./"); + } + + /** The settings of the server serving this request. + */ + package @property const(HTTPServerSettings) serverSettings() + const @safe scope { + return m_settings; + } + + private void parseFormAndFiles() + @safe scope { + m_form = FormFields.init; + parseFormData(m_form.get, m_files, headers.get("Content-Type", ""), bodyReader, m_settings.maxRequestHeaderLineSize); + } +} + + +/** + Represents a HTTP response as sent from the server side. +*/ +final class HTTPServerResponse : HTTPResponse { + private { + InterfaceProxy!Stream m_conn; + InterfaceProxy!ConnectionStream m_rawConnection; + InterfaceProxy!OutputStream m_bodyWriter; + IAllocator m_requestAlloc; + FreeListRef!ChunkedOutputStream m_chunkedBodyWriter; + FreeListRef!CountingOutputStream m_countingWriter; + FreeListRef!ZlibOutputStream m_zlibOutputStream; + HTTPServerSettings m_settings; + Session m_session; + bool m_headerWritten = false; + bool m_isHeadResponse = false; + bool m_tls; + bool m_requiresConnectionClose; + SysTime m_timeFinalized; + } + + static if (!is(Stream == InterfaceProxy!Stream)) { + this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc) + @safe scope { + this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc); + } + } + + this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc) + @safe scope { + m_conn = conn; + m_rawConnection = raw_connection; + m_countingWriter = createCountingOutputStreamFL(conn); + m_settings = settings; + m_requestAlloc = req_alloc; + } + + /** Sends a redirect request to the client. + + Params: + url = The URL to redirect to + status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found) + */ + void redirect(string url, int status = HTTPStatus.found) + @safe scope { + // Disallow any characters that may influence the header parsing + enforce(!url.representation.canFind!(ch => ch < 0x20), + "Control character in redirection URL."); + + statusCode = status; + headers["Location"] = url; + writeBody("redirecting..."); + } + /// ditto + void redirect(URL url, int status = HTTPStatus.found) + @safe scope { + redirect(url.toString(), status); + } + + /// + @safe unittest { + import vibe.http.router; + + void request_handler(HTTPServerRequest req, HTTPServerResponse res) + { + res.redirect("http://example.org/some_other_url"); + } + + void test() + { + auto router = new URLRouter; + router.get("/old_url", &request_handler); + + listenHTTP(new HTTPServerSettings, router); + } + } + +scope: + + /** Returns the time at which the request was finalized. + + Note that this field will only be set after `finalize` has been called. + */ + @property SysTime timeFinalized() const @safe { return m_timeFinalized; } + + /** Determines if the HTTP header has already been written. + */ + @property bool headerWritten() const @safe { return m_headerWritten; } + + /** Determines if the response does not need a body. + */ + bool isHeadResponse() const @safe { return m_isHeadResponse; } + + /** Determines if the response is sent over an encrypted connection. + */ + bool tls() const @safe { return m_tls; } + + /** Writes the entire response body at once. + + Params: + data = The data to write as the body contents + status = Optional response status code to set + content_type = Optional content type to apply to the response. + If no content type is given and no "Content-Type" header is + set in the response, this will default to + `"application/octet-stream"`. + + See_Also: `HTTPStatusCode` + */ + void writeBody(in ubyte[] data, string content_type = null) + @safe { + if (content_type.length) headers["Content-Type"] = content_type; + else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream"; + headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", data.length); + bodyWriter.write(data); + } + /// ditto + void writeBody(in ubyte[] data, int status, string content_type = null) + @safe { + statusCode = status; + writeBody(data, content_type); } + /// ditto void writeBody(scope InputStream data, string content_type = null) - { - m_data.writeBody(data, content_type); + @safe { + if (content_type.length) headers["Content-Type"] = content_type; + else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream"; + data.pipe(bodyWriter); } + + /** Writes the entire response body as a single string. + + Params: + data = The string to write as the body contents + status = Optional response status code to set + content_type = Optional content type to apply to the response. + If no content type is given and no "Content-Type" header is + set in the response, this will default to + `"text/plain; charset=UTF-8"`. + + See_Also: `HTTPStatusCode` + */ + /// ditto void writeBody(string data, string content_type = null) - { - m_data.writeBody(data, content_type); + @safe { + if (!content_type.length && "Content-Type" !in headers) + content_type = "text/plain; charset=UTF-8"; + writeBody(cast(const(ubyte)[])data, content_type); } + /// ditto void writeBody(string data, int status, string content_type = null) - { - m_data.writeBody(data, status, content_type); + @safe { + statusCode = status; + writeBody(data, content_type); } + /** Writes the whole response body at once, without doing any further encoding. + + The caller has to make sure that the appropriate headers are set correctly + (i.e. Content-Type and Content-Encoding). + + Note that the version taking a RandomAccessStream may perform additional + optimizations such as sending a file directly from the disk to the + network card using a DMA transfer. + + */ void writeRawBody(RandomAccessStream)(RandomAccessStream stream) @safe if (isRandomAccessStream!RandomAccessStream) { - m_data.writeRawBody(stream); + assert(!m_headerWritten, "A body was already written!"); + writeHeader(); + if (m_isHeadResponse) return; + + auto bytes = stream.size - stream.tell(); + stream.pipe(m_conn); + m_countingWriter.increment(bytes); } /// ditto void writeRawBody(InputStream)(InputStream stream, size_t num_bytes = 0) @safe if (isInputStream!InputStream && !isRandomAccessStream!InputStream) { - m_data.writeRawBody(stream, num_bytes); + assert(!m_headerWritten, "A body was already written!"); + writeHeader(); + if (m_isHeadResponse) return; + + if (num_bytes > 0) { + stream.pipe(m_conn, num_bytes); + m_countingWriter.increment(num_bytes); + } else stream.pipe(m_countingWriter, num_bytes); } /// ditto void writeRawBody(RandomAccessStream)(RandomAccessStream stream, int status) @safe if (isRandomAccessStream!RandomAccessStream) { - m_data.writeRawBody(stream, status); + statusCode = status; + writeRawBody(stream); } /// ditto void writeRawBody(InputStream)(InputStream stream, int status, size_t num_bytes = 0) @safe if (isInputStream!InputStream && !isRandomAccessStream!InputStream) { - m_data.writeRawBody(stream, status, num_bytes); + statusCode = status; + writeRawBody(stream, num_bytes); } + /// Writes a JSON message with the specified status void writeJsonBody(T)(T data, int status, bool allow_chunked = false) { - m_data.writeJsonBody(data, status, allow_chunked); + statusCode = status; + writeJsonBody(data, allow_chunked); } /// ditto void writeJsonBody(T)(T data, int status, string content_type, bool allow_chunked = false) { - m_data.writeJsonBody(data, status, content_type, allow_chunked); + statusCode = status; + writeJsonBody(data, content_type, allow_chunked); } /// ditto void writeJsonBody(T)(T data, string content_type, bool allow_chunked = false) { - m_data.writeJsonBody(data, content_type, allow_chunked); + headers["Content-Type"] = content_type; + writeJsonBody(data, allow_chunked); } /// ditto void writeJsonBody(T)(T data, bool allow_chunked = false) { - m_data.writeJsonBody(data, allow_chunked); + doWriteJsonBody!(T, false)(data, allow_chunked); } /// ditto void writePrettyJsonBody(T)(T data, bool allow_chunked = false) { - m_data.writePrettyJsonBody(data, allow_chunked); + doWriteJsonBody!(T, true)(data, allow_chunked); } - @property void writeVoidBody() - { - m_data.writeVoidBody(); - } - /// ditto - @property void writeVoidBody(Stream)(Stream stream) + private void doWriteJsonBody(T, bool PRETTY)(T data, bool allow_chunked = false) { - m_data.writeVoidBody(stream); - } + import std.traits; + import vibe.stream.wrapper; - @property InterfaceProxy!OutputStream bodyWriter() - { - return m_data.bodyWriter; + static if (!is(T == Json) && is(typeof(data.data())) && isArray!(typeof(data.data()))) { + static assert(!is(T == Appender!(typeof(data.data()))), "Passed an Appender!T to writeJsonBody - this is most probably not doing what's indended."); + } + + if ("Content-Type" !in headers) + headers["Content-Type"] = "application/json; charset=UTF-8"; + + + // set an explicit content-length field if chunked encoding is not allowed + if (!allow_chunked) { + import vibe.internal.rangeutil; + long length = 0; + auto counter = RangeCounter(() @trusted { return &length; } ()); + static if (PRETTY) serializeToPrettyJson(counter, data); + else serializeToJson(counter, data); + headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length); + } + + auto rng = streamOutputRange!1024(bodyWriter); + static if (PRETTY) serializeToPrettyJson(() @trusted { return &rng; } (), data); + else serializeToJson(() @trusted { return &rng; } (), data); } - package @property void bodyWriterH2(T)(ref T writer, const bool writeH = false) - { - m_data.bodyWriterH2(writer, writeH); + /** + * Writes the response with no body. + * + * This method should be used in situations where no body is + * requested, such as a HEAD request. For an empty body, just use writeBody, + * as this method causes problems with some keep-alive connections. + */ + void writeVoidBody() + @safe { + if (!m_isHeadResponse) { + assert("Content-Length" !in headers); + assert("Transfer-Encoding" !in headers); + } + assert(!headerWritten); + writeHeader(); + m_conn.flush(); } - /** Sends a redirect request to the client. + /** A stream for writing the body of the HTTP response. - Params: - url = The URL to redirect to - status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found) + Note that after 'bodyWriter' has been accessed for the first time, it + is not allowed to change any header or the status code of the response. */ - void redirect(T)(T url, int status = HTTPStatus.Found) - if(is(typeof(url) == string) || is(typeof(url) == URL)) - { - m_data.redirect(url, status); + @property InterfaceProxy!OutputStream bodyWriter() + @safe scope { + assert(!!m_conn); + if (m_bodyWriter) { + // for test responses, the body writer is pre-set, without headers + // being written, so we may need to do that here + if (!m_headerWritten) writeHeader(); + + return m_bodyWriter; + } + + assert(!m_headerWritten, "A void body was already written!"); + assert(this.statusCode >= 200, "1xx responses can't have body"); + + if (m_isHeadResponse) { + // for HEAD requests, we define a NullOutputWriter for convenience + // - no body will be written. However, the request handler should call writeVoidBody() + // and skip writing of the body in this case. + if ("Content-Length" !in headers) + headers["Transfer-Encoding"] = "chunked"; + writeHeader(); + m_bodyWriter = nullSink; + return m_bodyWriter; + } + + if ("Content-Encoding" in headers && "Content-Length" in headers) { + // we do not known how large the compressed body will be in advance + // so remove the content-length and use chunked transfer + headers.remove("Content-Length"); + } + + if (auto pcl = "Content-Length" in headers) { + writeHeader(); + m_countingWriter.writeLimit = (*pcl).to!ulong; + m_bodyWriter = m_countingWriter; + } else if (httpVersion <= HTTPVersion.HTTP_1_0) { + if ("Connection" in headers) + headers.remove("Connection"); // default to "close" + writeHeader(); + m_bodyWriter = m_conn; + } else { + headers["Transfer-Encoding"] = "chunked"; + writeHeader(); + m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter); + m_bodyWriter = m_chunkedBodyWriter; + } + + if (auto pce = "Content-Encoding" in headers) { + if (icmp2(*pce, "gzip") == 0) { + m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter); + m_bodyWriter = m_zlibOutputStream; + } else if (icmp2(*pce, "deflate") == 0) { + m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter); + m_bodyWriter = m_zlibOutputStream; + } else { + logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'"); + } + } + + return m_bodyWriter; } + + /** Special method sending a SWITCHING_PROTOCOLS response to the client. Notice: For the overload that returns a `ConnectionStream`, it must be @@ -1092,24 +1540,28 @@ struct HTTPServerResponse { protocol = The protocol set in the "Upgrade" header of the response. Use an empty string to skip setting this field. */ - scope ConnectionStream switchProtocol(string protocol) - { - return m_data.switchProtocol(protocol); + ConnectionStream switchProtocol(string protocol) + @safe { + statusCode = HTTPStatus.switchingProtocols; + if (protocol.length) headers["Upgrade"] = protocol; + writeVoidBody(); + m_requiresConnectionClose = true; + m_headerWritten = true; + return createConnectionProxyStream(m_conn, m_rawConnection); } /// ditto void switchProtocol(string protocol, scope void delegate(scope ConnectionStream) @safe del) - { - m_data.switchProtocol(protocol, del); - } - /// ditto - package void switchToHTTP2(HANDLER)(HANDLER handler, HTTP2ServerContext context) @safe { - m_data.switchToHTTP2(handler, context); - } - - // Send a BadRequest and close connection (failed switch to HTTP/2) - package void sendBadRequest() { - m_data.sendBadRequest(); + statusCode = HTTPStatus.switchingProtocols; + if (protocol.length) headers["Upgrade"] = protocol; + writeVoidBody(); + m_requiresConnectionClose = true; + m_headerWritten = true; + () @trusted { + auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection); + del(conn); + } (); + finalize(); } /** Special method for handling CONNECT proxy tunnel @@ -1118,14 +1570,18 @@ struct HTTPServerResponse { ensured that the returned instance doesn't outlive the request handler callback. */ - scope ConnectionStream connectProxy() - { - return m_data.connectProxy(); + ConnectionStream connectProxy() + @safe { + return createConnectionProxyStream(m_conn, m_rawConnection); } /// ditto void connectProxy(scope void delegate(scope ConnectionStream) @safe del) - { - m_data.connectProxy(del); + @safe { + () @trusted { + auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection); + del(conn); + } (); + finalize(); } /** Sets the specified cookie value. @@ -1134,10 +1590,19 @@ struct HTTPServerResponse { name = Name of the cookie value = New cookie value - pass null to clear the cookie path = Path (as seen by the client) of the directory tree in which the cookie is visible + encoding = Optional encoding (url, raw), default to URL encoding */ - scope Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url) - { - return m_data.setCookie(name, value, path, encoding); + Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url) + @safe { + auto cookie = new Cookie(); + cookie.path = path; + cookie.setValue(value, encoding); + if (value is null) { + cookie.maxAge = 0; + cookie.expires = "Thu, 01 Jan 1970 00:00:00 GMT"; + } + cookies[name] = cookie; + return cookie; } /** @@ -1147,20 +1612,46 @@ struct HTTPServerResponse { creating the server. Depending on this, the session can be persistent or temporary and specific to this server instance. */ - scope Session startSession(string path = "/", SessionOption options = SessionOption.httpOnly) - { - return m_data.startSession(path, options); + Session startSession(string path = "/") + @safe { + return startSession(path, m_settings.sessionOptions); + } + + /// ditto + Session startSession(string path, SessionOption options) + @safe { + assert(m_settings.sessionStore, "no session store set"); + assert(!m_session, "Try to start a session, but already started one."); + + bool secure; + if (options & SessionOption.secure) secure = true; + else if (options & SessionOption.noSecure) secure = false; + else secure = this.tls; + + m_session = m_settings.sessionStore.create(); + m_session.set("$sessionCookiePath", path); + m_session.set("$sessionCookieSecure", secure); + auto cookie = setCookie(m_settings.sessionIdCookie, m_session.id, path); + cookie.secure = secure; + cookie.httpOnly = (options & SessionOption.httpOnly) != 0; + cookie.sameSite = (options & SessionOption.noSameSiteStrict) ? + Cookie.SameSite.lax : Cookie.SameSite.strict; + return m_session; } /** Terminates the current session (if any). */ void terminateSession() - { - m_data.terminateSession(); + @safe { + if (!m_session) return; + auto cookie = setCookie(m_settings.sessionIdCookie, null, m_session.get!string("$sessionCookiePath")); + cookie.secure = m_session.get!bool("$sessionCookieSecure"); + m_session.destroy(); + m_session = Session.init; } - @property scope ulong bytesWritten() const { return m_data.bytesWritten; } + @property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; } /** Waits until either the connection closes, data arrives, or until the @@ -1173,8 +1664,10 @@ struct HTTPServerResponse { See_Also: `connected` */ bool waitForConnectionClose(Duration timeout = Duration.max) - { - return m_data.waitForConnectionClose(timeout); + @safe { + if (!m_rawConnection || !m_rawConnection.connected) return true; + m_rawConnection.waitForData(timeout); + return !m_rawConnection.connected; } /** @@ -1185,7 +1678,11 @@ struct HTTPServerResponse { See_Also: `waitForConnectionClose` */ - @property bool connected() const { return m_data.connected; } + @property bool connected() + @safe const { + if (!m_rawConnection) return false; + return m_rawConnection.connected; + } /** Finalizes the response. This is usually called automatically by the server. @@ -1194,9 +1691,87 @@ struct HTTPServerResponse { all network traffic associated with the current request to be finalized. After the call returns, the `timeFinalized` property will be set. */ - void finalize() { m_data.finalize(); } -} + void finalize() + @safe { + if (m_zlibOutputStream) { + m_zlibOutputStream.finalize(); + m_zlibOutputStream.destroy(); + } + if (m_chunkedBodyWriter) { + m_chunkedBodyWriter.finalize(); + m_chunkedBodyWriter.destroy(); + } + + // ignore exceptions caused by an already closed connection - the client + // may have closed the connection already and this doesn't usually indicate + // a problem. + if (m_rawConnection && m_rawConnection.connected) { + try if (m_conn) m_conn.flush(); + catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg); + if (!isHeadResponse && bytesWritten < headers.get("Content-Length", "0").to!ulong) { + logDebug("HTTP response only written partially before finalization. Terminating connection."); + m_requiresConnectionClose = true; + } + + m_rawConnection = InterfaceProxy!ConnectionStream.init; + } + + if (m_conn) { + m_conn = InterfaceProxy!Stream.init; + m_timeFinalized = Clock.currTime(UTC()); + } + } + + private void writeHeader() + @safe { + import vibe.stream.wrapper; + + assert(!m_headerWritten, "Try to write header after body has already begun."); + assert(this.httpVersion != HTTPVersion.HTTP_1_0 || this.statusCode >= 200, "Informational status codes aren't supported by HTTP/1.0."); + + // Don't set m_headerWritten for 1xx status codes + if (this.statusCode >= 200) m_headerWritten = true; + auto dst = streamOutputRange!1024(m_conn); + + void writeLine(T...)(string fmt, T args) + @safe { + formattedWrite(() @trusted { return &dst; } (), fmt, args); + dst.put("\r\n"); + logTrace(fmt, args); + } + + logTrace("---------------------"); + logTrace("HTTP server response:"); + logTrace("---------------------"); + + // write the status line + writeLine("%s %d %s", + getHTTPVersionString(this.httpVersion), + this.statusCode, + this.statusPhrase.length ? this.statusPhrase : httpStatusText(this.statusCode)); + + // write all normal headers + foreach (k, v; this.headers.byKeyValue) { + dst.put(k); + dst.put(": "); + dst.put(v); + dst.put("\r\n"); + logTrace("%s: %s", k, v); + } + + logTrace("---------------------"); + + // write cookies + foreach (n, cookie; this.cookies.byKeyValue) { + dst.put("Set-Cookie: "); + cookie.writeString(() @trusted { return &dst; } (), n); + dst.put("\r\n"); + } + // finalize response header + dst.put("\r\n"); + } +} /** Represents the request listener for a specific `listenHTTP` call. @@ -1211,9 +1786,9 @@ struct HTTPListener { private this(size_t[] ids) @safe { m_virtualHostIDs = ids; } @property NetworkAddress[] bindAddresses() - { + @safe { NetworkAddress[] ret; - foreach (l; s_contexts) + foreach (l; s_listeners) if (l.m_virtualHosts.canFind!(v => m_virtualHostIDs.canFind(v.id))) { NetworkAddress a; a = resolveHost(l.bindAddress); @@ -1231,13 +1806,12 @@ struct HTTPListener { import std.algorithm : countUntil; foreach (vhid; m_virtualHostIDs) { - foreach (lidx, l; s_contexts) { + foreach (lidx, l; s_listeners) { if (l.removeVirtualHost(vhid)) { if (!l.hasVirtualHosts) { - l.stopListening(); + l.m_listener.stopListening(); logInfo("Stopped to listen for HTTP%s requests on %s:%s", l.tlsContext ? "S": "", l.bindAddress, l.bindPort); - logInfo("Stopped to listen for HTTP%s requests on %s:%s", "", l.bindAddress, l.bindPort); - s_contexts = s_contexts[0 .. lidx] ~ s_contexts[lidx+1 .. $]; + s_listeners = s_listeners[0 .. lidx] ~ s_listeners[lidx+1 .. $]; } } break; @@ -1246,6 +1820,7 @@ struct HTTPListener { } } + /** Represents a single HTTP server port. This class defines the incoming interface, port, and TLS configuration of @@ -1255,9 +1830,10 @@ struct HTTPListener { Multiple virtual hosts can be configured to be served from the same port. Their TLS settings must be compatible and each virtual host must have a + unique name. */ final class HTTPServerContext { - struct VirtualHost { + private struct VirtualHost { HTTPServerRequestDelegate requestHandler; HTTPServerSettings settings; HTTPLogger[] loggers; @@ -1300,9 +1876,6 @@ final class HTTPServerContext { /// Determines if any virtual hosts have been addded @property bool hasVirtualHosts() const { return m_virtualHosts.length > 0; } - /// Make m_virtualhosts visible - @property scope VirtualHost[] virtualHosts() { return m_virtualHosts; } - /** Adds a single virtual host. Note that the port and bind address defined in `settings` must match the @@ -1358,11 +1931,6 @@ final class HTTPServerContext { return true; } - void stopListening() - { - m_listener.stopListening(); - } - private void addSNIHost(HTTPServerSettings settings) { if (settings.tlsContext !is m_tlsContext && m_tlsContext.kind != TLSContextKind.serverSNI) { @@ -1371,6 +1939,11 @@ final class HTTPServerContext { m_tlsContext.sniCallback = &onSNI; } + foreach (ctx; m_virtualHosts) { + /*enforce(ctx.settings.hostName != settings.hostName, + "A server with the host name '"~settings.hostName~"' is already " + "listening on "~addr~":"~to!string(settings.port)~".");*/ + } } private TLSContext onSNI(string servername) @@ -1385,15 +1958,67 @@ final class HTTPServerContext { } } - /**************************************************************************************************/ -/* Private types */ +/* Private types */ /**************************************************************************************************/ -private enum MaxHTTPHeaderLineLength = 4096; +private final class LimitedHTTPInputStream : LimitedInputStream { +@safe: + + this(InterfaceProxy!InputStream stream, ulong byte_limit, bool silent_limit = false) { + super(stream, byte_limit, silent_limit, true); + } + override void onSizeLimitReached() { + throw new HTTPStatusException(HTTPStatus.requestEntityTooLarge); + } +} + +private final class TimeoutHTTPInputStream : InputStream { +@safe: + + private { + long m_timeref; + long m_timeleft; + InterfaceProxy!InputStream m_in; + } + + this(InterfaceProxy!InputStream stream, Duration timeleft, SysTime reftime) + { + enforce(timeleft > 0.seconds, "Timeout required"); + m_in = stream; + m_timeleft = timeleft.total!"hnsecs"(); + m_timeref = reftime.stdTime(); + } + + @property bool empty() { enforce(m_in, "InputStream missing"); return m_in.empty(); } + @property ulong leastSize() { enforce(m_in, "InputStream missing"); return m_in.leastSize(); } + @property bool dataAvailableForRead() { enforce(m_in, "InputStream missing"); return m_in.dataAvailableForRead; } + const(ubyte)[] peek() { return m_in.peek(); } + + size_t read(scope ubyte[] dst, IOMode mode) + { + enforce(m_in, "InputStream missing"); + size_t nread = 0; + checkTimeout(); + // FIXME: this should use ConnectionStream.waitForData to enforce the timeout during the + // read operation + return m_in.read(dst, mode); + } + + alias read = InputStream.read; + + private void checkTimeout() + @safe { + auto curr = Clock.currStdTime(); + auto diff = curr - m_timeref; + if (diff > m_timeleft) throw new HTTPStatusException(HTTPStatus.requestTimeout); + m_timeleft -= diff; + m_timeref = curr; + } +} /**************************************************************************************************/ -/* Private functions */ +/* Private functions */ /**************************************************************************************************/ private { @@ -1405,45 +2030,26 @@ private { HTTPServerContext[] s_listeners; } - -private { - HTTPContext[] s_contexts; -} - -//private HTTPContext getDefaultHTTPContext(in ref NetworkAddress addr) - - //assert(false, "TODO"); -//} - - /** - [private] Starts a HTTP server listening on the specified port. + [private] Starts a HTTP server listening on the specified port. - This is the same as listenHTTP() except that it does not use a VibeDist host for - remote listening, even if specified on the command line. + This is the same as listenHTTP() except that it does not use a VibeDist host for + remote listening, even if specified on the command line. */ - private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequestDelegate request_handler) -@safe -{ +@safe { import vibe.core.core : runWorkerTaskDist; import std.algorithm : canFind, find; - static TCPListener doListen(HTTPServerContext listen_info, bool dist, bool reusePort) + static TCPListener doListen(HTTPServerContext listen_info, bool reusePort, bool reuseAddress, bool is_tls) @safe { try { TCPListenOptions options = TCPListenOptions.defaults; + if(reuseAddress) options |= TCPListenOptions.reuseAddress; else options &= ~TCPListenOptions.reuseAddress; if(reusePort) options |= TCPListenOptions.reusePort; else options &= ~TCPListenOptions.reusePort; auto ret = listenTCP(listen_info.bindPort, (TCPConnection conn) nothrow @safe { - // check wether the client's address is banned - foreach (ref virtual_host; listen_info.m_virtualHosts) - if ((virtual_host.settings.rejectConnectionPredicate !is null) && - virtual_host.settings.rejectConnectionPredicate(conn.remoteAddress)) - return; - - //logInfo("ListenHTTP"); - try { handleHTTP1Connection(conn, listen_info); - } catch (Exception e) { + try handleHTTPConnection(conn, listen_info); + catch (Exception e) { logError("HTTP connection handler has thrown: %s", e.msg); debug logDebug("Full error: %s", () @trusted { return e.toString().sanitize(); } ()); try conn.close(); @@ -1455,7 +2061,7 @@ private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequ if (listen_info.bindPort == 0) listen_info.m_bindPort = ret.bindAddress.port; - auto proto = listen_info.tlsContext ? "https" : "http"; + auto proto = is_tls ? "https" : "http"; auto urladdr = listen_info.bindAddress; if (urladdr.canFind(':')) urladdr = "["~urladdr~"]"; logInfo("Listening for requests on %s://%s:%s/", proto, urladdr, listen_info.bindPort); @@ -1473,14 +2079,17 @@ private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequ foreach (addr; settings.bindAddresses) { HTTPServerContext linfo; - auto l = s_contexts.find!(l => l.bindAddress == addr && l.bindPort == settings.port); + auto l = s_listeners.find!(l => l.bindAddress == addr && l.bindPort == settings.port); if (!l.empty) linfo = l.front; else { auto li = new HTTPServerContext(addr, settings.port); - if (auto tcp_lst = doListen(li, (settings.options & HTTPServerOptionImpl.distribute) != 0, (settings.options & HTTPServerOption.reusePort) != 0)) // DMD BUG 2043 + if (auto tcp_lst = doListen(li, + (settings.options & HTTPServerOption.reusePort) != 0, + (settings.options & HTTPServerOption.reuseAddress) != 0, + settings.tlsContext !is null)) // DMD BUG 2043 { li.m_listener = tcp_lst; - s_contexts ~= li; + s_listeners ~= li; linfo = li; } } @@ -1493,1212 +2102,286 @@ private HTTPListener listenHTTPPlain(HTTPServerSettings settings, HTTPServerRequ return HTTPListener(vid); } -unittest{ - // testing a class that implements HTTPServerRequestHandler - - class MyReqHandler : HTTPServerRequestHandler - { - override void handleRequest(HTTPServerRequest req, HTTPServerResponse res) - @safe { - if (req.path == "/") - res.writeBody("Hello, World! Interface"); - } - } +private alias TLSStreamType = ReturnType!(createTLSStreamFL!(InterfaceProxy!Stream)); - auto settings = new HTTPServerSettings(); - settings.port = 8050; - settings.bindAddresses = ["localhost"]; - - MyReqHandler mrh = new MyReqHandler; - - listenHTTP!mrh(settings); -} - -unittest { - // testing HTTPS connections - void handleRequest (HTTPServerRequest req, HTTPServerResponse res) - @safe { - if (req.path == "/") - res.writeBody("Hello, World! Delegate"); - } - - auto settings = new HTTPServerSettings(); - settings.port = 8070; - settings.bindAddresses = ["localhost"]; - settings.tlsContext = createTLSContext(TLSContextKind.server); - settings.tlsContext.useCertificateChainFile("tests/server.crt"); - settings.tlsContext.usePrivateKeyFile("tests/server.key"); - - listenHTTP!handleRequest(settings); -} - -//// NOTE: just a possible idea for the low level api -//struct HTTPRequestHandler { - //void read(alias HeaderCallback, alias BodyCallback)() - //{ - //connection.readHeaders!HeaderCallback(); - //connection.readBody!BodyCallback(); - //} - - //void write(alias HeaderCallback, alias BodyCallback)() - //{ - //connection.writeHeader!HeaderCallback(); - //connection.writeBody!BodyCallback(); - //} -//} +private bool handleRequest(InterfaceProxy!Stream http_stream, TCPConnection tcp_connection, HTTPServerContext listen_info, ref HTTPServerSettings settings, ref bool keep_alive, scope IAllocator request_allocator) +@safe { + import std.algorithm.searching : canFind; -struct HTTPServerRequestData { - import vibe.utils.dictionarylist; + SysTime reqtime = Clock.currTime(UTC()); - @disable this(this); + // some instances that live only while the request is running + FreeListRef!HTTPServerRequest req = FreeListRef!HTTPServerRequest(reqtime, listen_info.bindPort); + FreeListRef!TimeoutHTTPInputStream timeout_http_input_stream; + FreeListRef!LimitedHTTPInputStream limited_http_input_stream; + FreeListRef!ChunkedInputStream chunked_input_stream; - @safe: + // store the IP address + req.clientAddress = tcp_connection.remoteAddress; - private { - SysTime m_timeCreated; - HTTPServerSettings m_settings; - ushort m_port; - string m_peer; + if (!listen_info.hasVirtualHosts) { + logWarn("Didn't find a HTTP listening context for incoming connection. Dropping."); + keep_alive = false; + return false; } - protected { - - InterfaceProxy!Stream m_conn; - - /// The HTTP protocol version used for the request - - HTTPVersion httpVersion = HTTPVersion.HTTP_1_1; - - /// The HTTP _method of the request - HTTPMethod method = HTTPMethod.GET; - - /** The request URI - - Note that the request URI usually does not include the global - 'http://server' part, but only the local path and a query string. - A possible exception is a proxy server, which will get full URLs. - */ - string requestURI = "/"; - - /// Compatibility alias - scheduled for deprecation - alias requestURL = requestURI; - - /// All request _headers - InetHeaderMap headers; - - /// The IP address of the client - @property string peer() - @safe nothrow { - if (!m_peer) { - version (Have_vibe_core) {} else scope (failure) assert(false); - // store the IP address (IPv4 addresses forwarded over IPv6 are stored in IPv4 format) - auto peer_address_string = this.clientAddress.toAddressString(); - if (peer_address_string.startsWith("::ffff:") && peer_address_string[7 .. $].indexOf(':') < 0) - m_peer = peer_address_string[7 .. $]; - else m_peer = peer_address_string; - } - return m_peer; - } - - /// ditto - NetworkAddress clientAddress; - - /// Determines if the request should be logged to the access log file. - bool noLog; - - /// Determines if the request was issued over an TLS encrypted channel. - bool tls; - - /* Information about the TLS certificate provided by the client. - - Remarks: This field is only set if `tls` is true, and the peer - presented a client certificate. - */ - TLSCertificateInformation clientCertificate; - - /* Deprecated: The _path part of the URL. - - Note that this function contains the decoded version of the - requested path, which can yield incorrect results if the path - contains URL encoded path separators. Use `requestPath` instead to - get an encoding-aware representation. - */ - string path() @safe pure { - if (_path.isNull) { - _path = urlDecode(requestPath.toString); - } - return _path.get; - } - - void path(string st) @safe { - assert(_path.isNull, "Unable to set request path"); - _path = st; - } - - private Nullable!string _path; - - //* The path part of the requested URI. - InetPath requestPath; - - //* The user name part of the URL, if present. - string username; - - //* The _password part of the URL, if present. - string password; - - //* The _query string part of the URL. - string queryString; - - /** A map of general parameters for the request. - - This map is supposed to be used by middleware functionality to store - information for later stages. For example vibe.http.router.URLRouter uses this map - to store the value of any named placeholders. - */ - DictionaryList!(string, true, 8) params; - - /* Contains the list of _cookies that are stored on the client. - - Note that the a single cookie name may occur multiple times if multiple - cookies have that name but different paths or domains that all match - the request URI. By default, the first cookie will be returned, which is - the or one of the cookies with the closest path match. - */ - @property ref CookieValueMap cookies() @safe { - if (_cookies.isNull) { - _cookies = CookieValueMap.init; - if (auto pv = "cookie" in headers) - parseCookies(*pv, _cookies.get); - } - return _cookies.get; - } - private Nullable!CookieValueMap _cookies; - - /* Contains all _form fields supplied using the _query string. - - The fields are stored in the same order as they are received. - */ - @property ref FormFields query() @safe { - if (_query.isNull) { - _query = FormFields.init; - parseURLEncodedForm(queryString, _query.get); - } - - return _query.get; - } - Nullable!FormFields _query; - - import vibe.utils.dictionarylist; - /* A map of general parameters for the request. - - This map is supposed to be used by middleware functionality to store - information for later stages. For example vibe.http.router.URLRouter uses this map - to store the value of any named placeholders. - */ - - import std.variant : Variant; - /* A map of context items for the request. - - This is especially useful for passing application specific data down - the chain of processors along with the request itself. - - For example, a generic route may be defined to check user login status, - if the user is logged in, add a reference to user specific data to the - context. - - This is implemented with `std.variant.Variant` to allow any type of data. - */ - DictionaryList!(Variant, true, 2) context; - - /* Supplies the request body as a stream. - - Note that when certain server options are set (such as - HTTPServerOption.parseJsonBody) and a matching request was sent, - the returned stream will be empty. If needed, remove those - options and do your own processing of the body when launching - the server. HTTPServerOption has a list of all options that affect - the request body. - */ - InputStream bodyReader; - - /* Contains the parsed Json for a JSON request. - - A JSON request must have the Content-Type "application/json" or "application/vnd.api+json". - */ - @property ref Json json() @safe { - if (_json.isNull) { - if (icmp2(contentType, "application/json") == 0 || icmp2(contentType, "application/vnd.api+json") == 0 ) { - auto bodyStr = bodyReader.readAllUTF8(); - if (!bodyStr.empty) _json = parseJson(bodyStr); - } else { - _json = Json.undefined; - } - } - return _json.get; - } - - private Nullable!Json _json; - - /* Contains the parsed parameters of a HTML POST _form request. - - The fields are stored in the same order as they are received. - - Remarks: - A form request must either have the Content-Type - "application/x-www-form-urlencoded" or "multipart/form-data". - */ - @property ref FormFields form() @safe { - if (_form.isNull) - parseFormAndFiles(); - - return _form.get; - } - - private Nullable!FormFields _form; - - private void parseFormAndFiles() @safe { - _form = FormFields.init; - parseFormData(_form.get, _files, headers.get("Content-Type", ""), bodyReader, MaxHTTPHeaderLineLength); - } - - //* Contains information about any uploaded file for a HTML _form request. - @property ref FilePartFormFields files() @safe return { - // _form and _files are parsed in one step - if (_form.isNull) { - parseFormAndFiles(); - assert(!_form.isNull); - } - - return _files; - } - - private FilePartFormFields _files; - - /* The current Session object. + // Default to the first virtual host for this listener + HTTPServerContext.VirtualHost context = listen_info.m_virtualHosts[0]; + HTTPServerRequestDelegate request_task = context.requestHandler; + settings = context.settings; - This field is set if HTTPServerResponse.startSession() has been called - on a previous response and if the client has sent back the matching - cookie. - - Remarks: Requires the HTTPServerOption.parseCookies option. - */ - Session session; - - public string toString() - { - return httpMethodString(method) ~ " " ~ requestURL ~ " " ~ getHTTPVersionString(httpVersion); - } - - /** Shortcut to the 'Host' header (always present for HTTP 1.1) - */ - @property string host() const pure nothrow { auto ph = "Host" in headers; return ph ? *ph : null; } - /// ditto - @property void host(string v) { headers["Host"] = v; } - - /** Returns the mime type part of the 'Content-Type' header. - - This function gets the pure mime type (e.g. "text/plain") - without any supplimentary parameters such as "charset=...". - Use contentTypeParameters to get any parameter string or - headers["Content-Type"] to get the raw value. - */ - @property string contentType() - const pure nothrow { - auto pv = "Content-Type" in headers; - if( !pv ) return null; - auto idx = std.string.indexOf(*pv, ';'); - return idx >= 0 ? (*pv)[0 .. idx] : *pv; - } - /// ditto - @property void contentType(string ct) { headers["Content-Type"] = ct; } - - /** Returns any supplementary parameters of the 'Content-Type' header. - - This is a semicolon separated ist of key/value pairs. Usually, if set, - this contains the character set used for text based content types. - */ - @property string contentTypeParameters() - const pure nothrow { - auto pv = "Content-Type" in headers; - if( !pv ) return null; - auto idx = std.string.indexOf(*pv, ';'); - return idx >= 0 ? (*pv)[idx+1 .. $] : null; - } + // temporarily set to the default settings, the virtual host specific settings will be set further down + req.m_settings = settings; - /** Determines if the connection persists across requests. - */ - @property bool persistent() const pure nothrow - { - auto ph = "connection" in headers; - switch(httpVersion) { - case HTTPVersion.HTTP_1_0: - if (ph && icmp(*ph, "keep-alive") == 0) return true; - return false; - case HTTPVersion.HTTP_1_1: - if (ph && icmp(*ph, "keep-alive") != 0) return false; - return true; - default: - return false; - } - } - } - - package { - //* The settings of the server serving this request. - @property const(HTTPServerSettings) serverSettings() const nothrow @safe - { - return m_settings; + // Create the response object + InterfaceProxy!ConnectionStream cproxy = tcp_connection; + auto res = FreeListRef!HTTPServerResponse(http_stream, cproxy, settings, request_allocator/*.Scoped_payload*/); + req.tls = res.m_tls = listen_info.tlsContext !is null; + if (req.tls) { + version (HaveNoTLS) assert(false); + else { + static if (is(InterfaceProxy!ConnectionStream == ConnectionStream)) + req.clientCertificate = (cast(TLSStream)http_stream).peerCertificate; + else + req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate; } } - this(SysTime time, ushort port) - @safe nothrow { - m_timeCreated = time.toUTC(); - m_port = port; - } - - //* Time when this request started processing. - @property SysTime timeCreated() const @safe nothrow { return m_timeCreated; } - - - /* The full URL that corresponds to this request. - - The host URL includes the protocol, host and optionally the user - and password that was used for this request. This field is useful to - construct self referencing URLs. - - Note that the port is currently not set, so that this only works if - the standard port is used. - */ - @property URL fullURL() - const @safe { - URL url; - - auto xfh = this.headers.get("X-Forwarded-Host"); - auto xfp = this.headers.get("X-Forwarded-Port"); - auto xfpr = this.headers.get("X-Forwarded-Proto"); - - // Set URL host segment. - if (xfh.length) { - url.host = xfh; - } else if (!this.host.empty) { - url.host = this.host; - } else if (!m_settings.hostName.empty) { - url.host = m_settings.hostName; - } else { - url.host = m_settings.bindAddresses[0]; - } - - // Set URL schema segment. - if (xfpr.length) { - url.schema = xfpr; - } else if (this.tls) { - url.schema = "https"; + // Error page handler + void errorOut(int code, string msg, string debug_msg, Throwable ex) + @safe { + assert(!res.headerWritten); + + res.statusCode = code; + if (settings && settings.errorPageHandler) { + /*scope*/ auto err = new HTTPServerErrorInfo; + err.code = code; + err.message = msg; + err.debugMessage = debug_msg; + err.exception = ex; + settings.errorPageHandler_(req, res, err); } else { - url.schema = "http"; + if (debug_msg.length) + res.writeBody(format("%s - %s\n\n%s\n\nInternal error information:\n%s", code, httpStatusText(code), msg, debug_msg)); + else res.writeBody(format("%s - %s\n\n%s", code, httpStatusText(code), msg)); } - - // Set URL port segment. - if (xfp.length) { - try { - url.port = xfp.to!ushort; - } catch (ConvException) { - // TODO : Consider responding with a 400/etc. error from here. - logWarn("X-Forwarded-Port header was not valid port (%s)", xfp); - } - } else if (!xfh) { - if (url.schema == "https") { - if (m_port != 443U) url.port = m_port; - } else { - if (m_port != 80U) url.port = m_port; - } - } - - if (url.host.startsWith('[')) { // handle IPv6 address - auto idx = url.host.indexOf(']'); - if (idx >= 0 && idx+1 < url.host.length && url.host[idx+1] == ':') - url.host = url.host[1 .. idx]; - } else { // handle normal host names or IPv4 address - auto idx = url.host.indexOf(':'); - if (idx >= 0) url.host = url.host[0 .. idx]; - } - - url.username = this.username; - url.password = this.password; - url.localURI = this.requestURI; - - return url; - } - - /* The relative path to the root folder. - - Using this function instead of absolute URLs for embedded links can be - useful to avoid dead link when the site is piped through a - reverse-proxy. - - The returned string always ends with a slash. - */ - @property string rootDir() - const @safe pure nothrow { - import std.range.primitives : walkLength; - auto depth = requestPath.bySegment.walkLength; - return depth == 0 ? "./" : replicate("../", depth); - } -} - - -struct HTTPServerResponseData { - @disable this(this); - @safe: - - private { - InterfaceProxy!Stream m_conn; - InterfaceProxy!ConnectionStream m_rawConnection; - InterfaceProxy!OutputStream m_bodyWriter; - IAllocator m_requestAlloc; - FreeListRef!ChunkedOutputStream m_chunkedBodyWriter; - FreeListRef!CountingOutputStream m_countingWriter; - FreeListRef!ZlibOutputStream m_zlibOutputStream; - HTTPServerSettings m_settings; - Session m_session; - bool m_headerWritten = false; - bool m_isHeadResponse = false; - bool m_tls; - SysTime m_timeFinalized; + assert(res.headerWritten); } - protected { - - /// The protocol version of the response - should not be changed - HTTPVersion httpVersion = HTTPVersion.HTTP_1_1; - - /// The status code of the response, 200 by default - int statusCode = HTTPStatus.OK; - - /** The status phrase of the response + bool parsed = false; + /*bool*/ keep_alive = false; - If no phrase is set, a default one corresponding to the status code will be used. - */ - string statusPhrase; + // parse the request + try { + logTrace("reading request.."); - /// The response header fields - InetHeaderMap headers; - - /// All cookies that shall be set on the client for this request - Cookie[string] cookies; - /** Shortcut to the "Content-Type" header - */ - @property string contentType() const { auto pct = "Content-Type" in headers; return pct ? *pct : "application/octet-stream"; } - /// ditto - @property void contentType(string ct) { headers["Content-Type"] = ct; } - - static if (!is(Stream == InterfaceProxy!Stream)) { - this(Stream conn, ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc) - @safe { - this(InterfaceProxy!Stream(conn), InterfaceProxy!ConnectionStream(raw_connection), settings, req_alloc); - } + // limit the total request time + InterfaceProxy!InputStream reqReader = http_stream; + if (settings.maxRequestTime > dur!"seconds"(0) && settings.maxRequestTime != Duration.max) { + timeout_http_input_stream = FreeListRef!TimeoutHTTPInputStream(reqReader, settings.maxRequestTime, reqtime); + reqReader = timeout_http_input_stream; } - this(InterfaceProxy!Stream conn, InterfaceProxy!ConnectionStream raw_connection, HTTPServerSettings settings, IAllocator req_alloc) - @safe { - m_conn = conn; - m_rawConnection = raw_connection; - m_countingWriter = createCountingOutputStreamFL(conn); - m_settings = settings; - m_requestAlloc = req_alloc; - } - - /** Returns the time at which the request was finalized. - - Note that this field will only be set after `finalize` has been called. - */ - @property SysTime timeFinalized() const @safe { return m_timeFinalized; } - - /** Determines if the HTTP header has already been written. - */ - @property bool headerWritten() const @safe { return m_headerWritten; } - - /** Determines if the response does not need a body. - */ - bool isHeadResponse() const @safe { return m_isHeadResponse; } - - /** Determines if the response is sent over an encrypted connection. - */ - bool tls() const @safe { return m_tls; } - - /** Writes the entire response body at once. - - Params: - data = The data to write as the body contents - status = Optional response status code to set - content_tyoe = Optional content type to apply to the response. - If no content type is given and no "Content-Type" header is - set in the response, this will default to - `"application/octet-stream"`. - - See_Also: `HTTPStatusCode` - */ - void writeBody(in ubyte[] data, string content_type = null) - @safe { - if (content_type.length) headers["Content-Type"] = content_type; - else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream"; - ulong length = data.length; - headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length); - headers["Content-Length"] = format("%d", length); - bodyWriter.write(data); - } - /// ditto - void writeBody(in ubyte[] data, int status, string content_type = null) - @safe { - statusCode = status; - writeBody(data, content_type); - } - /// ditto - void writeBody(scope InputStream data, string content_type = null) - @safe { - if (content_type.length) headers["Content-Type"] = content_type; - else if ("Content-Type" !in headers) headers["Content-Type"] = "application/octet-stream"; - data.pipe(bodyWriter); - } + // basic request parsing + parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize, settings.maxRequestHeaderLineSize); + logTrace("Got request header."); - /** Writes the entire response body as a single string. - - Params: - data = The string to write as the body contents - status = Optional response status code to set - content_type = Optional content type to apply to the response. - If no content type is given and no "Content-Type" header is - set in the response, this will default to - `"text/plain; charset=UTF-8"`. - - See_Also: `HTTPStatusCode` - */ - /// ditto - void writeBody(string data, string content_type = null) - @safe { - if (!content_type.length && "Content-Type" !in headers) - content_type = "text/plain; charset=UTF-8"; - writeBody(cast(const(ubyte)[])data, content_type); - } - /// ditto - void writeBody(string data, int status, string content_type = null) - @safe { - statusCode = status; - writeBody(data, content_type); + // find the matching virtual host + string reqhost; + ushort reqport = 0; + { + string s = req.host; + enforceHTTP(s.length > 0 || req.httpVersion <= HTTPVersion.HTTP_1_0, HTTPStatus.badRequest, "Missing Host header."); + if (s.startsWith('[')) { // IPv6 address + auto idx = s.indexOf(']'); + enforce(idx > 0, "Missing closing ']' for IPv6 address."); + reqhost = s[1 .. idx]; + s = s[idx+1 .. $]; + } else if (s.length) { // host name or IPv4 address + auto idx = s.indexOf(':'); + if (idx < 0) idx = s.length; + enforceHTTP(idx > 0, HTTPStatus.badRequest, "Missing Host header."); + reqhost = s[0 .. idx]; + s = s[idx .. $]; } + if (s.startsWith(':')) reqport = s[1 .. $].to!ushort; + } - /** Writes the whole response body at once, without doing any further encoding. - - The caller has to make sure that the appropriate headers are set correctly - (i.e. Content-Type and Content-Encoding). - - Note that the version taking a RandomAccessStream may perform additional - optimizations such as sending a file directly from the disk to the - network card using a DMA transfer. - - */ - void writeRawBody(RandomAccessStream)(RandomAccessStream stream) @safe - if (isRandomAccessStream!RandomAccessStream) - { - assert(!m_headerWritten, "A body was already written!"); - writeHeader(); - if (m_isHeadResponse) return; - - auto bytes = stream.size - stream.tell(); - stream.pipe(m_conn); - m_countingWriter.increment(bytes); - } - /// ditto - void writeRawBody(InputStream)(InputStream stream, size_t num_bytes = 0) @safe - if (isInputStream!InputStream && !isRandomAccessStream!InputStream) - { - assert(!m_headerWritten, "A body was already written!"); - writeHeader(); - if (m_isHeadResponse) return; - - if (num_bytes > 0) { - stream.pipe(m_conn, num_bytes); - m_countingWriter.increment(num_bytes); - } else stream.pipe(m_countingWriter, num_bytes); - } - /// ditto - void writeRawBody(RandomAccessStream)(RandomAccessStream stream, int status) @safe - if (isRandomAccessStream!RandomAccessStream) + foreach (ctx; listen_info.m_virtualHosts) + if (icmp2(ctx.settings.hostName, reqhost) == 0 && + (!reqport || reqport == ctx.settings.port)) { - statusCode = status; - writeRawBody(stream); + context = ctx; + settings = ctx.settings; + request_task = ctx.requestHandler; + break; } - /// ditto - void writeRawBody(InputStream)(InputStream stream, int status, size_t num_bytes = 0) @safe - if (isInputStream!InputStream && !isRandomAccessStream!InputStream) - { - statusCode = status; - writeRawBody(stream, num_bytes); + req.m_settings = settings; + res.m_settings = settings; + + // setup compressed output + if (settings.useCompressionIfPossible) { + if (auto pae = "Accept-Encoding" in req.headers) { + if (canFind(*pae, "gzip")) { + res.headers["Content-Encoding"] = "gzip"; + } else if (canFind(*pae, "deflate")) { + res.headers["Content-Encoding"] = "deflate"; + } } - - - /// Writes a JSON message with the specified status - void writeJsonBody(T)(T data, int status, bool allow_chunked = false) - { - statusCode = status; - writeJsonBody(data, allow_chunked); - } - /// ditto - void writeJsonBody(T)(T data, int status, string content_type, bool allow_chunked = false) - { - statusCode = status; - writeJsonBody(data, content_type, allow_chunked); } - /// ditto - void writeJsonBody(T)(T data, string content_type, bool allow_chunked = false) - { - headers["Content-Type"] = content_type; - writeJsonBody(data, allow_chunked); - } - /// ditto - void writeJsonBody(T)(T data, bool allow_chunked = false) - { - doWriteJsonBody!(T, false)(data, allow_chunked); - } - /// ditto - void writePrettyJsonBody(T)(T data, bool allow_chunked = false) - { - doWriteJsonBody!(T, true)(data, allow_chunked); + // limit request size + if (auto pcl = "Content-Length" in req.headers) { + string v = *pcl; + auto contentLength = parse!ulong(v); // DMDBUG: to! thinks there is a H in the string + enforceBadRequest(v.length == 0, "Invalid content-length"); + enforceBadRequest(settings.maxRequestSize <= 0 || contentLength <= settings.maxRequestSize, "Request size too big"); + limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, contentLength); + } else if (auto pt = "Transfer-Encoding" in req.headers) { + enforceBadRequest(icmp(*pt, "chunked") == 0); + chunked_input_stream = createChunkedInputStreamFL(reqReader); + InterfaceProxy!InputStream ciproxy = chunked_input_stream; + limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(ciproxy, settings.maxRequestSize, true); + } else { + limited_http_input_stream = FreeListRef!LimitedHTTPInputStream(reqReader, 0); } + req.bodyReader = limited_http_input_stream; - private void doWriteJsonBody(T, bool PRETTY)(T data, bool allow_chunked = false) - { - import std.traits; - import vibe.stream.wrapper; - - static if (!is(T == Json) && is(typeof(data.data())) && isArray!(typeof(data.data()))) { - static assert(!is(T == Appender!(typeof(data.data()))), "Passed an Appender!T to writeJsonBody - this is most probably not doing what's indended."); + // handle Expect header + if (auto pv = "Expect" in req.headers) { + if (icmp2(*pv, "100-continue") == 0) { + logTrace("sending 100 continue"); + http_stream.write("HTTP/1.1 100 Continue\r\n\r\n"); } - - if ("Content-Type" !in headers) - headers["Content-Type"] = "application/json; charset=UTF-8"; - - - // set an explicit content-length field if chunked encoding is not allowed - if (!allow_chunked) { - import vibe.internal.rangeutil; - long length = 0; - auto counter = RangeCounter(() @trusted { return &length; } ()); - static if (PRETTY) serializeToPrettyJson(counter, data); - else serializeToJson(counter, data); - headers["Content-Length"] = formatAlloc(m_requestAlloc, "%d", length); - } - - auto rng = streamOutputRange!1024(bodyWriter); - static if (PRETTY) serializeToPrettyJson(() @trusted { return &rng; } (), data); - else serializeToJson(() @trusted { return &rng; } (), data); } - /** - * Writes the response with no body. - * - * This method should be used in situations where no body is - * requested, such as a HEAD request. For an empty body, just use writeBody, - * as this method causes problems with some keep-alive connections. - */ - void writeVoidBody() @safe - { - writeVoidBody(m_conn); - } - /// ditto - void writeVoidBody(Stream)(Stream stream) @safe - if(isOutputStream!Stream) - { - if (!m_isHeadResponse) { - assert("Content-Length" !in headers); - assert("Transfer-Encoding" !in headers); + // eagerly parse the URL as its lightweight and defacto @nogc + auto url = URL.parse(req.requestURI); + req.queryString = url.queryString; + req.username = url.username; + req.password = url.password; + req.requestPath = url.path; + + // lookup the session + if (settings.sessionStore) { + // use the first cookie that contains a valid session ID in case + // of multiple matching session cookies + foreach (val; req.cookies.getAll(settings.sessionIdCookie)) { + req.session = settings.sessionStore.open(val); + res.m_session = req.session; + if (req.session) break; } - assert(!headerWritten); - writeHeader(stream); - stream.flush(); } - /** A stream for writing the body of the HTTP response. - - Note that after 'bodyWriter' has been accessed for the first time, it - is not allowed to change any header or the status code of the response. - */ - @property InterfaceProxy!OutputStream bodyWriter() - @safe { - assert(!!m_conn); - if (m_bodyWriter) return m_bodyWriter; - - assert(!m_headerWritten, "A void body was already written!"); - - if (m_isHeadResponse) { - // for HEAD requests, we define a NullOutputWriter for convenience - // - no body will be written. However, the request handler should call writeVoidBody() - // and skip writing of the body in this case. - if ("Content-Length" !in headers) - headers["Transfer-Encoding"] = "chunked"; - writeHeader(); - m_bodyWriter = nullSink; - return m_bodyWriter; - } - - if ("Content-Encoding" in headers && "Content-Length" in headers) { - // we do not known how large the compressed body will be in advance - // so remove the content-length and use chunked transfer - headers.remove("Content-Length"); - } - - if (auto pcl = "Content-Length" in headers) { - writeHeader(); - m_countingWriter.writeLimit = (*pcl).to!ulong; - m_bodyWriter = m_countingWriter; - } else if (httpVersion <= HTTPVersion.HTTP_1_0) { - if ("Connection" in headers) - headers.remove("Connection"); // default to "close" - writeHeader(); - m_bodyWriter = m_conn; - } else { - headers["Transfer-Encoding"] = "chunked"; - writeHeader(); - m_chunkedBodyWriter = createChunkedOutputStreamFL(m_countingWriter); - m_bodyWriter = m_chunkedBodyWriter; - } - - if (auto pce = "Content-Encoding" in headers) { - if (icmp2(*pce, "gzip") == 0) { - m_zlibOutputStream = createGzipOutputStreamFL(m_bodyWriter); - m_bodyWriter = m_zlibOutputStream; - } else if (icmp2(*pce, "deflate") == 0) { - m_zlibOutputStream = createDeflateOutputStreamFL(m_bodyWriter); - m_bodyWriter = m_zlibOutputStream; - } else { - logWarn("Unsupported Content-Encoding set in response: '"~*pce~"'"); - } - } - - - - return m_bodyWriter; - } - - /** - * Used to change the bodyWriter during a HTTP/2 connection - */ - import vibe.stream.memory; - @property void bodyWriterH2(T)(ref T writer, const bool writeH = false) @safe - if(isOutputStream!T) + // write default headers + if (req.method == HTTPMethod.HEAD) res.m_isHeadResponse = true; + if (settings.serverString.length) + res.headers["Server"] = settings.serverString; + res.headers["Date"] = formatRFC822DateAlloc(reqtime); + if (req.persistent) + res.headers["Keep-Alive"] = formatAlloc( + request_allocator, "timeout=%d", settings.keepAliveTimeout.total!"seconds"()); + + // finished parsing the request + parsed = true; + logTrace("persist: %s", req.persistent); + keep_alive = req.persistent; + + if (context.settings.rejectConnectionPredicate !is null) { - assert(!m_bodyWriter, "Unable to set bodyWriter"); - - // write the current set headers before initiating the bodyWriter - if(writeH) writeHeader(writer); - - static if(!is(T == InterfaceProxy!OutputStream)) { - InterfaceProxy!OutputStream bwriter = writer; - m_bodyWriter = bwriter; - } else { - m_bodyWriter = writer; - } - } - - /** Sends a redirect request to the client. - - Params: - url = The URL to redirect to - status = The HTTP redirect status (3xx) to send - by default this is $(D HTTPStatus.found) - */ - void redirect(string url, int status = HTTPStatus.Found) - @safe { - // Disallow any characters that may influence the header parsing - enforce(!url.representation.canFind!(ch => ch < 0x20), - "Control character in redirection URL."); - - statusCode = status; - headers["Location"] = url; - writeBody("redirecting..."); - } - /// ditto - void redirect(URL url, int status = HTTPStatus.Found) - @safe { - redirect(url.toString(), status); - } - - /// - @safe unittest { - import vibe.http.router; - - void request_handler(HTTPServerRequest req, HTTPServerResponse res) + import std.socket : Address, parseAddress; + + auto forward = req.headers.get("X-Forwarded-For", null); + if (forward !is null) { - res.redirect("http://example.org/some_other_url"); - } - - void test() - { - auto router = new URLRouter; - router.get("/old_url", &request_handler); - HTTPServerSettings settings; - listenHTTP!router(settings); + try { + auto ix = forward.indexOf(','); + if (ix != -1) + forward = forward[0 .. ix]; + if (context.settings.rejectConnectionPredicate(NetworkAddress(parseAddress(forward)))) + errorOut(HTTPStatus.forbidden, + httpStatusText(HTTPStatus.forbidden), null, null); + } catch (Exception e) + logTrace("Malformed X-Forwarded-For header: %s", e.msg); } } - - /** Special method sending a SWITCHING_PROTOCOLS response to the client. - - Notice: For the overload that returns a `ConnectionStream`, it must be - ensured that the returned instance doesn't outlive the request - handler callback. - - Notice: The overload which accepts a connection_handler alias is used for - HTTP/1 to HTTP/2 switching in cleartext HTTP - - Params: - protocol = The protocol set in the "Upgrade" header of the response. - Use an empty string to skip setting this field. - */ - ConnectionStream switchProtocol(string protocol) - @safe { - statusCode = HTTPStatus.SwitchingProtocols; - if (protocol.length) headers["Upgrade"] = protocol; - writeVoidBody(); - return createConnectionProxyStream(m_conn, m_rawConnection); - } - - /// ditto - void switchProtocol(string protocol, scope void delegate(scope ConnectionStream) @safe del) - @safe { - statusCode = HTTPStatus.SwitchingProtocols; - if (protocol.length) headers["Upgrade"] = protocol; - writeVoidBody(); - () @trusted { - auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection); - del(conn); - } (); - finalize(); - if (m_rawConnection && m_rawConnection.connected) - m_rawConnection.close(); // connection not reusable after a protocol upgrade - } - - package void switchToHTTP2(HANDLER)(HANDLER handler, HTTP2ServerContext context) - @safe { - //logInfo("sending SWITCHING_PROTOCOL response"); - - statusCode = HTTPStatus.switchingProtocols; - headers["Upgrade"] = "h2c"; - - writeVoidBody(); - - // TODO improve handler (handleHTTP2Connection) connection management - auto tcp_conn = m_rawConnection.extract!TCPConnection; - handler(tcp_conn, tcp_conn, context, false); - - finalize(); - // close the existing connection - if (m_rawConnection && m_rawConnection.connected) - m_rawConnection.close(); // connection not reusable after a protocol upgrade - } - - // send a badRequest error response and close the connection - package void sendBadRequest() @safe - { - statusCode = HTTPStatus.badRequest; - - writeVoidBody(); - - finalize(); - if (m_rawConnection && m_rawConnection.connected) - m_rawConnection.close(); // connection not reusable after a protocol upgrade + // handle the request + logTrace("handle request (body %d)", req.bodyReader.leastSize); + res.httpVersion = req.httpVersion; + request_task(req, res); + + // if no one has written anything, return 404 + if (!res.headerWritten) { + string dbg_msg; + logDiagnostic("No response written for %s", req.requestURI); + if (settings.options & HTTPServerOption.errorStackTraces) + dbg_msg = format("No routes match path '%s'", req.requestURI); + errorOut(HTTPStatus.notFound, httpStatusText(HTTPStatus.notFound), dbg_msg, null); + } + } catch (HTTPStatusException err) { + if (!res.headerWritten) errorOut(err.status, err.msg, err.debugMessage, err); + else logDiagnostic("HTTPStatusException while writing the response: %s", err.msg); + debug logDebug("Exception while handling request %s %s: %s", req.method, + req.requestURI, () @trusted { return err.toString().sanitize; } ()); + if (!parsed || res.headerWritten || justifiesConnectionClose(err.status)) + keep_alive = false; + } catch (UncaughtException e) { + auto status = parsed ? HTTPStatus.internalServerError : HTTPStatus.badRequest; + string dbg_msg; + if (settings.options & HTTPServerOption.errorStackTraces) + dbg_msg = () @trusted { return e.toString().sanitize; } (); + if (!res.headerWritten && tcp_connection.connected) + errorOut(status, httpStatusText(status), dbg_msg, e); + else logDiagnostic("Error while writing the response: %s", e.msg); + debug logDebug("Exception while handling request %s %s: %s", req.method, + req.requestURI, () @trusted { return e.toString().sanitize(); } ()); + if (!parsed || res.headerWritten || !cast(Exception)e) keep_alive = false; + } + + if (tcp_connection.connected && keep_alive) { + if (req.bodyReader && !req.bodyReader.empty) { + req.bodyReader.pipe(nullSink); + logTrace("dropped body"); } - - - /** Special method for handling CONNECT proxy tunnel - - Notice: For the overload that returns a `ConnectionStream`, it must be - ensured that the returned instance doesn't outlive the request - handler callback. - */ - ConnectionStream connectProxy() - @safe { - return createConnectionProxyStream(m_conn, m_rawConnection); - } - /// ditto - void connectProxy(scope void delegate(scope ConnectionStream) @safe del) - @safe { - () @trusted { - auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection); - del(conn); - } (); - finalize(); - m_rawConnection.close(); // connection not reusable after a protocol upgrade - } - - /** Sets the specified cookie value. - - Params: - name = Name of the cookie - value = New cookie value - pass null to clear the cookie - path = Path (as seen by the client) of the directory tree in which the cookie is visible - */ - Cookie setCookie(string name, string value, string path = "/", Cookie.Encoding encoding = Cookie.Encoding.url) - @safe { - auto cookie = new Cookie(); - cookie.path = path; - cookie.setValue(value, encoding); - if (value is null) { - cookie.maxAge = 0; - cookie.expires = "Thu, 01 Jan 1970 00:00:00 GMT"; - } - cookies[name] = cookie; - return cookie; - } - - /** - Initiates a new session. - - The session is stored in the SessionStore that was specified when - creating the server. Depending on this, the session can be persistent - or temporary and specific to this server instance. - */ - Session startSession(string path = "/", SessionOption options = SessionOption.httpOnly) - @safe { - assert(m_settings.sessionStore, "no session store set"); - assert(!m_session, "Try to start a session, but already started one."); - - bool secure; - if (options & SessionOption.secure) secure = true; - else if (options & SessionOption.noSecure) secure = false; - else secure = this.tls; - - m_session = m_settings.sessionStore.create(); - m_session.set("$sessionCookiePath", path); - m_session.set("$sessionCookieSecure", secure); - auto cookie = setCookie(m_settings.sessionIdCookie, m_session.id, path); - cookie.secure = secure; - cookie.httpOnly = (options & SessionOption.httpOnly) != 0; - return m_session; - } - - /** - Terminates the current session (if any). - */ - void terminateSession() - @safe { - if (!m_session) return; - auto cookie = setCookie(m_settings.sessionIdCookie, null, m_session.get!string("$sessionCookiePath")); - cookie.secure = m_session.get!bool("$sessionCookieSecure"); - m_session.destroy(); - m_session = Session.init; - } - - @property ulong bytesWritten() @safe const { return m_countingWriter.bytesWritten; } - - /** - Waits until either the connection closes, data arrives, or until the - given timeout is reached. - - Returns: - $(D true) if the connection was closed and $(D false) if either the - timeout was reached, or if data has arrived for consumption. - - See_Also: `connected` - */ - bool waitForConnectionClose(Duration timeout = Duration.max) - @safe { - if (!m_rawConnection || !m_rawConnection.connected) return true; - m_rawConnection.waitForData(timeout); - return !m_rawConnection.connected; - } - - /** - Determines if the underlying connection is still alive. - - Returns $(D true) if the remote peer is still connected and $(D false) - if the remote peer closed the connection. - - See_Also: `waitForConnectionClose` - */ - @property bool connected() - @safe const { - if (!m_rawConnection) return false; - return m_rawConnection.connected; - } - - /** - Finalizes the response. This is usually called automatically by the server. - - This method can be called manually after writing the response to force - all network traffic associated with the current request to be finalized. - After the call returns, the `timeFinalized` property will be set. - */ - void finalize() - @safe { - if (m_zlibOutputStream) { - m_zlibOutputStream.finalize(); - m_zlibOutputStream.destroy(); - } - if (m_chunkedBodyWriter) { - m_chunkedBodyWriter.finalize(); - m_chunkedBodyWriter.destroy(); - } - - // ignore exceptions caused by an already closed connection - the client - // may have closed the connection already and this doesn't usually indicate - // a problem. - if (m_rawConnection && m_rawConnection.connected) { - try if (m_conn) m_conn.flush(); - catch (Exception e) logDebug("Failed to flush connection after finishing HTTP response: %s", e.msg); - if (!isHeadResponse && bytesWritten < headers.get("Content-Length", "0").to!long) { - logDebug("HTTP response only written partially before finalization. Terminating connection."); - m_rawConnection.close(); - } - m_rawConnection = InterfaceProxy!ConnectionStream.init; - } - - if (m_conn) { - m_conn = InterfaceProxy!Stream.init; - m_timeFinalized = Clock.currTime(UTC()); - } - } - - } - - private void writeHeader() - @safe { - writeHeader(m_conn); } - // accept a destination stream - private void writeHeader(Stream)(Stream conn) @safe - if(isOutputStream!Stream) - { - import vibe.stream.wrapper; - - assert(!m_bodyWriter && !m_headerWritten, "Try to write header after body has already begun."); - m_headerWritten = true; - auto dst = streamOutputRange!1024(conn); - - void writeLine(T...)(string fmt, T args) - @safe { - formattedWrite(() @trusted { return &dst; } (), fmt, args); - dst.put("\r\n"); - logTrace(fmt, args); - } + // finalize (e.g. for chunked encoding) + res.finalize(); - logTrace("---------------------"); - logTrace("HTTP server response:"); - logTrace("---------------------"); - - // write the status line - writeLine("%s %d %s", - getHTTPVersionString(this.httpVersion), - this.statusCode, - this.statusPhrase.length ? this.statusPhrase : httpStatusText(this.statusCode)); - - // write all normal headers - foreach (k, v; this.headers.byKeyValue) { - dst.put(k); - dst.put(": "); - dst.put(v); - dst.put("\r\n"); - logTrace("%s: %s", k, v); - } - - logTrace("---------------------"); - - // write cookies - foreach (n, cookie; this.cookies) { - dst.put("Set-Cookie: "); - cookie.writeString(() @trusted { return &dst; } (), n); - dst.put("\r\n"); - } + if (res.m_requiresConnectionClose) + keep_alive = false; - // finalize response header - dst.put("\r\n"); + // NOTE: req.m_files may or may not be parsed/filled with actual data, as + // it is lazily initialized when calling the .files or .form + // properties + foreach (k, v ; req.m_files.byKeyValue) { + if (existsFile(v.tempPath)) { + removeFile(v.tempPath); + logDebug("Deleted upload tempfile %s", v.tempPath.toString()); } - - public string toString() - { - auto app = appender!string(); - formattedWrite(app, "%s %d %s", getHTTPVersionString(this.httpVersion), this.statusCode, this.statusPhrase); - return app.data; } -} + if (!req.noLog) { + // log the request to access log + foreach (log; context.loggers) + log.log(req, res); + } -private void parseCookies(string str, ref CookieValueMap cookies) -@safe { - import std.encoding : sanitize; - import std.array : split; - import std.string : strip; - import std.algorithm.iteration : map, filter, each; - import vibe.http.common : Cookie; - () @trusted { return str.sanitize; } () - .split(";") - .map!(kv => kv.strip.split("=")) - .filter!(kv => kv.length == 2) //ignore illegal cookies - .each!(kv => cookies.add(kv[0], kv[1], Cookie.Encoding.raw) ); -} - -unittest -{ - auto cvm = CookieValueMap(); - parseCookies("foo=bar;; baz=zinga; öö=üü ; møøse=was=sacked; onlyval1; =onlyval2; onlykey=", cvm); - assert(cvm["foo"] == "bar"); - assert(cvm["baz"] == "zinga"); - assert(cvm["öö"] == "üü"); - assert( "møøse" ! in cvm); //illegal cookie gets ignored - assert( "onlyval1" ! in cvm); //illegal cookie gets ignored - assert(cvm["onlykey"] == ""); - assert(cvm[""] == "onlyval2"); - assert(cvm.length() == 5); - cvm = CookieValueMap(); - parseCookies("", cvm); - assert(cvm.length() == 0); - cvm = CookieValueMap(); - parseCookies(";;=", cvm); - assert(cvm.length() == 1); - assert(cvm[""] == ""); + //logTrace("return %s (used pool memory: %s/%s)", keep_alive, request_allocator.allocatedSize, request_allocator.totalSize); + logTrace("return %s", keep_alive); + return keep_alive != false; } -void parseHTTP2RequestHeader(R)(ref R headers, HTTPServerRequest reqStruct) @safe -{ - import std.algorithm.searching : find, startsWith; - import std.algorithm.iteration : filter; - auto req = reqStruct.data; - - //Method - req.method = cast(HTTPMethod)headers.find!((h,m) => h.name == m)(":method")[0].value; - - //Host - auto host = headers.find!((h,m) => h.name == m)(":authority"); - if(!host.empty) req.host = cast(string)host[0].value; - - //Path - req.path = cast(string)headers.find!((h,m) => h.name == m)(":path")[0].value; - //URI - req.requestURI = req.host; - - //HTTP version - req.httpVersion = HTTPVersion.HTTP_2; - - - //headers - foreach(h; headers.filter!(f => !f.name.startsWith(":"))) { - req.headers[h.name] = cast(string)h.value; - } -} - -uint parseRequestHeader(InputStream)(HTTPServerRequest reqStruct, InputStream http_stream, IAllocator alloc, ulong max_header_size) +private void parseRequestHeader(InputStream)(HTTPServerRequest req, InputStream http_stream, IAllocator alloc, ulong max_header_size, size_t max_header_line_size) if (isInputStream!InputStream) { - auto req = reqStruct.data; auto stream = FreeListRef!LimitedHTTPInputStream(http_stream, max_header_size); logTrace("HTTP server reading status line"); - auto reqln = () @trusted { return cast(string)stream.readLine(MaxHTTPHeaderLineLength, "\r\n", alloc); }(); - - if(reqln == "PRI * HTTP/2.0") return cast(uint)reqln.length; + auto reqln = () @trusted { return cast(string)stream.readLine(max_header_line_size, "\r\n", alloc); }(); logTrace("--------------------"); logTrace("HTTP server request:"); @@ -2721,20 +2404,91 @@ uint parseRequestHeader(InputStream)(HTTPServerRequest reqStruct, InputStream ht req.httpVersion = parseHTTPVersion(reqln); //headers - parseRFC5322Header(stream, req.headers, MaxHTTPHeaderLineLength, alloc, false); + parseRFC5322Header(stream, req.headers, max_header_line_size, alloc, false); foreach (k, v; req.headers.byKeyValue) logTrace("%s: %s", k, v); logTrace("--------------------"); - return 0; } -string formatRFC822DateAlloc(IAllocator alloc, SysTime time) +private void parseCookies(string str, ref CookieValueMap cookies) +@safe { + import std.encoding : sanitize; + import std.array : split; + import std.string : strip; + import std.algorithm.iteration : map, filter, each; + import vibe.http.common : Cookie; + () @trusted { return str.sanitize; } () + .split(";") + .map!(kv => kv.strip.split("=")) + .filter!(kv => kv.length == 2) //ignore illegal cookies + .each!(kv => cookies.add(kv[0], kv[1], Cookie.Encoding.raw) ); +} + +unittest +{ + auto cvm = CookieValueMap(); + parseCookies("foo=bar;; baz=zinga; öö=üü ; møøse=was=sacked; onlyval1; =onlyval2; onlykey=", cvm); + assert(cvm["foo"] == "bar"); + assert(cvm["baz"] == "zinga"); + assert(cvm["öö"] == "üü"); + assert( "møøse" ! in cvm); //illegal cookie gets ignored + assert( "onlyval1" ! in cvm); //illegal cookie gets ignored + assert(cvm["onlykey"] == ""); + assert(cvm[""] == "onlyval2"); + assert(cvm.length() == 5); + cvm = CookieValueMap(); + parseCookies("", cvm); + assert(cvm.length() == 0); + cvm = CookieValueMap(); + parseCookies(";;=", cvm); + assert(cvm.length() == 1); + assert(cvm[""] == ""); +} + +shared static this() +{ + version (VibeNoDefaultArgs) {} + else { + string disthost = s_distHost; + ushort distport = s_distPort; + import vibe.core.args : readOption; + readOption("disthost|d", () @trusted { return &disthost; } (), "Sets the name of a vibedist server to use for load balancing."); + readOption("distport", () @trusted { return &distport; } (), "Sets the port used for load balancing."); + setVibeDistHost(disthost, distport); + } +} + +private struct CacheTime +{ + string cachedDate; + SysTime nextUpdate; + + this(SysTime nextUpdate) @safe @nogc pure nothrow + { + this.nextUpdate = nextUpdate; + } + + void update(SysTime time) @safe + { + this.nextUpdate = time + 1.seconds; + this.nextUpdate.fracSecs = nsecs(0); + } +} + +private string formatRFC822DateAlloc(SysTime time) @safe { - auto app = AllocAppender!string(alloc); - writeRFC822DateTimeString(app, time); - return () @trusted { return app.data; } (); + static LAST = CacheTime(SysTime.min()); + + if (time > LAST.nextUpdate) { + auto app = new FixedAppender!(string, 32); + writeRFC822DateTimeString(app, time); + LAST.update(time); + LAST.cachedDate = () @trusted { return app.data; } (); + return () @trusted { return app.data; } (); + } else + return LAST.cachedDate; } -version (VibeDebugCatchAll) alias UncaughtException = Throwable; -else alias UncaughtException = Exception; +version (VibeDebugCatchAll) private alias UncaughtException = Throwable; +else private alias UncaughtException = Exception; diff --git a/source/vibe/http/session.d b/source/vibe/http/session.d index 79dff18..fa2b98d 100644 --- a/source/vibe/http/session.d +++ b/source/vibe/http/session.d @@ -1,7 +1,7 @@ /** Cookie based session support. - Copyright: © 2012-2013 RejectedSoftware e.K. + Copyright: © 2012-2013 Sönke Ludwig License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Jan Krüger, Sönke Ludwig, Ilya Shipunov */ @@ -153,7 +153,7 @@ struct Session { /** Interface for a basic session store. - A sesseion store is responsible for storing the id and the associated key/value pairs of a + A session store is responsible for storing the id and the associated key/value pairs of a session. */ interface SessionStore { diff --git a/source/vibe/http/status.d b/source/vibe/http/status.d index 98423bd..bbaa1cf 100644 --- a/source/vibe/http/status.d +++ b/source/vibe/http/status.d @@ -1,7 +1,7 @@ /** List of all standard HTTP status codes. - Copyright: © 2012 RejectedSoftware e.K. + Copyright: © 2012 Sönke Ludwig License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Jan Krüger */ @@ -54,53 +54,12 @@ enum HTTPStatus { gatewayTimeout = 504, httpVersionNotSupported = 505, // WebDAV status codes + processing = 102, /// See: https://tools.ietf.org/html/rfc2518#section-10.1 multiStatus = 207, unprocessableEntity = 422, locked = 423, failedDependency = 424, insufficientStorage = 507, - - requestedrangenotsatisfiable = rangeNotSatisfiable, /// deprecated - Continue = continue_, /// deprecated - SwitchingProtocols = switchingProtocols, /// deprecated - OK = ok, /// deprecated - Created = created, /// deprecated - Accepted = accepted, /// deprecated - NonAuthoritativeInformation = nonAuthoritativeInformation, /// deprecated - NoContent = noContent, /// deprecated - ResetContent = resetContent, /// deprecated - PartialContent = partialContent, /// deprecated - MultipleChoices = multipleChoices, /// deprecated - MovedPermanently = movedPermanently, /// deprecated - Found = found, /// deprecated - SeeOther = seeOther, /// deprecated - NotModified = notModified, /// deprecated - UseProxy = useProxy, /// deprecated - TemporaryRedirect = temporaryRedirect, /// deprecated - BadRequest = badRequest, /// deprecated - Unauthorized = unauthorized, /// deprecated - PaymentRequired = paymentRequired, /// deprecated - Forbidden = forbidden, /// deprecated - NotFound = notFound, /// deprecated - MethodNotAllowed = methodNotAllowed, /// deprecated - NotAcceptable = notAcceptable, /// deprecated - ProxyAuthenticationRequired = proxyAuthenticationRequired, /// deprecated - RequestTimeout = requestTimeout, /// deprecated - Conflict = conflict, /// deprecated - Gone = gone, /// deprecated - LengthRequired = lengthRequired, /// deprecated - PreconditionFailed = preconditionFailed, /// deprecated - RequestEntityTooLarge = requestEntityTooLarge, /// deprecated - RequestURITooLarge = requestURITooLarge, /// deprecated - UnsupportedMediaType = unsupportedMediaType, /// deprecated - Requestedrangenotsatisfiable = requestedrangenotsatisfiable, /// deprecated - ExpectationFailed = expectationFailed, /// deprecated - InternalServerError = internalServerError, /// deprecated - NotImplemented = notImplemented, /// deprecated - BadGateway = badGateway, /// deprecated - ServiceUnavailable = serviceUnavailable, /// deprecated - GatewayTimeout = gatewayTimeout, /// deprecated - HTTPVersionNotSupported = httpVersionNotSupported, /// deprecated } @@ -146,7 +105,7 @@ string httpStatusText(int code) case HTTPStatus.requestEntityTooLarge : return "Request Entity Too Large"; case HTTPStatus.requestURITooLarge : return "Request-URI Too Large"; case HTTPStatus.unsupportedMediaType : return "Unsupported Media Type"; - case HTTPStatus.requestedrangenotsatisfiable : return "Requested range not satisfiable"; + case HTTPStatus.rangeNotSatisfiable : return "Requested range not satisfiable"; case HTTPStatus.expectationFailed : return "Expectation Failed"; case HTTPStatus.unavailableForLegalReasons : return "Unavailable For Legal Reasons"; case HTTPStatus.internalServerError : return "Internal Server Error"; @@ -161,6 +120,7 @@ string httpStatusText(int code) case HTTPStatus.locked : return "Locked"; case HTTPStatus.failedDependency : return "Failed Dependency"; case HTTPStatus.insufficientStorage : return "Insufficient Storage"; + case HTTPStatus.processing : return "Processing"; } if( code >= 600 ) return "Unknown"; if( code >= 500 ) return "Unknown server error"; diff --git a/source/vibe/http/websockets.d b/source/vibe/http/websockets.d index c6505f9..c8663eb 100644 --- a/source/vibe/http/websockets.d +++ b/source/vibe/http/websockets.d @@ -38,7 +38,6 @@ import vibe.http.server; import vibe.http.client; import vibe.core.connectionpool; import vibe.utils.array; -static import vibe.internal.exception; import core.time; import std.algorithm: equal, splitter; @@ -221,7 +220,9 @@ void handleWebSocket(scope WebSocketHandshakeDelegate on_handshake, scope HTTPSe res.headers["Connection"] = "Upgrade"; ConnectionStream conn = res.switchProtocol("websocket"); - WebSocket socket = new WebSocket(conn, req, res); + // NOTE: silencing scope warning here - WebSocket references the scoped + // req/res objects throughout its lifetime, which has a narrower scope + scope socket = () @trusted { return new WebSocket(conn, req, res); } (); try { on_handshake(socket); } catch (Exception e) { @@ -487,9 +488,10 @@ unittest * Represents a single _WebSocket connection. * * --- - * shared static this () + * int main (string[] args) * { - * runTask(() => connectToWS()); + * auto taskHandle = runTask(() => connectToWS()); + * return runApplication(&args); * } * * void connectToWS () @@ -531,6 +533,8 @@ final class WebSocket { RandomNumberStream m_rng; } +scope: + /** * Private constructor, called from `connectWebSocket`. * @@ -553,22 +557,26 @@ final class WebSocket { m_readMutex = new InterruptibleTaskMutex; m_readCondition = new InterruptibleTaskCondition(m_readMutex); m_readMutex.performLocked!({ - m_reader = runTask(&startReader); - if (request && request.serverSettings.webSocketPingInterval != Duration.zero) { + // NOTE: Silencing scope warning here - m_reader MUST be stopped + // before the end of the lifetime of the WebSocket object, + // which is done in the mandatory call to close(). + // The same goes for m_pingTimer below. + m_reader = () @trusted { return runTask(&startReader); } (); + if (request !is null && request.serverSettings.webSocketPingInterval != Duration.zero) { m_pongReceived = true; - m_pingTimer = setTimer(request.serverSettings.webSocketPingInterval, &sendPing, true); + m_pingTimer = () @trusted { return setTimer(request.serverSettings.webSocketPingInterval, &sendPing, true); } (); } }); } private this(ConnectionStream conn, RandomNumberStream rng, HTTPClientResponse client_res) { - this(conn, HTTPServerRequest.init, HTTPServerResponse.init, rng, client_res); + this(conn, null, null, rng, client_res); } private this(ConnectionStream conn, in HTTPServerRequest request, HTTPServerResponse res) { - this(conn, request, res, RandomNumberStream.init, HTTPClientResponse.init); + this(conn, request, res, null, null); } /** @@ -683,20 +691,13 @@ final class WebSocket { void send(scope void delegate(scope OutgoingWebSocketMessage) @safe sender, FrameOpcode frameOpcode) { m_writeMutex.performLocked!({ - vibe.internal.exception.enforce!WebSocketException(!m_sentCloseFrame, "WebSocket connection already actively closed."); + enforce!WebSocketException(!m_sentCloseFrame, "WebSocket connection already actively closed."); /*scope*/auto message = new OutgoingWebSocketMessage(m_conn, frameOpcode, m_rng); scope(exit) message.finalize(); sender(message); }); } - /// Compatibility overload - will be removed soon. - deprecated("Call the overload which requires an explicit FrameOpcode.") - void send(scope void delegate(scope OutgoingWebSocketMessage) @safe sender) - { - send(sender, FrameOpcode.text); - } - /** Actively closes the connection. @@ -763,7 +764,7 @@ final class WebSocket { { ubyte[] ret; receive((scope message){ - vibe.internal.exception.enforce!WebSocketException(!strict || message.frameOpcode == FrameOpcode.binary, + enforce!WebSocketException(!strict || message.frameOpcode == FrameOpcode.binary, "Expected a binary message, got "~message.frameOpcode.to!string()); ret = message.readAll(); }); @@ -774,7 +775,7 @@ final class WebSocket { { string ret; receive((scope message){ - vibe.internal.exception.enforce!WebSocketException(!strict || message.frameOpcode == FrameOpcode.text, + enforce!WebSocketException(!strict || message.frameOpcode == FrameOpcode.text, "Expected a text message, got "~message.frameOpcode.to!string()); ret = message.readAllUTF8(); }); @@ -789,7 +790,7 @@ final class WebSocket { { m_readMutex.performLocked!({ while (!m_nextMessage) { - vibe.internal.exception.enforce!WebSocketException(connected, "Connection closed while reading message."); + enforce!WebSocketException(connected, "Connection closed while reading message."); m_readCondition.wait(); } receiver(m_nextMessage); @@ -799,13 +800,20 @@ final class WebSocket { } private void startReader() - { - m_readMutex.performLocked!({}); //Wait until initialization - scope (exit) { - m_conn.close(); - m_readCondition.notifyAll(); + nothrow { + try m_readMutex.performLocked!({}); //Wait until initialization + catch (Exception e) { + logException(e, "WebSocket reader task failed to wait for initialization"); + try m_conn.close(); + catch (Exception e) logException(e, "Failed to close WebSocket connection after initialization failure"); + m_closeCode = WebSocketCloseReason.abnormalClosure; + try m_readCondition.notifyAll(); + catch (Exception e) assert(false, e.msg); + return; } + try { + loop: while (!m_conn.empty) { assert(!m_nextMessage); /*scope*/auto msg = new IncomingWebSocketMessage(m_conn, m_rng); @@ -841,7 +849,7 @@ final class WebSocket { if(!m_sentCloseFrame) close(); logDebug("Terminating connection (%s)", m_sentCloseFrame); - return; + break loop; case FrameOpcode.text: case FrameOpcode.binary: case FrameOpcode.continuation: // FIXME: add proper support for continuation frames! @@ -860,7 +868,12 @@ final class WebSocket { // If no close code was passed, e.g. this was an unclean termination // of our websocket connection, set the close code to 1006. - if (this.m_closeCode == 0) this.m_closeCode = WebSocketCloseReason.abnormalClosure; + if (m_closeCode == 0) m_closeCode = WebSocketCloseReason.abnormalClosure; + + try m_conn.close(); + catch (Exception e) logException(e, "Failed to close WebSocket connection"); + try m_readCondition.notifyAll(); + catch (Exception e) assert(false, e.msg); } private void sendPing() @@ -902,7 +915,15 @@ final class OutgoingWebSocketMessage : OutputStream { m_rng = rng; } - size_t write(in ubyte[] bytes, IOMode mode) + static if (is(typeof(.OutputStream.outputStreamVersion)) && .OutputStream.outputStreamVersion > 1) { + override size_t write(scope const(ubyte)[] bytes_, IOMode mode) { return doWrite(bytes_, mode); } + } else { + override size_t write(in ubyte[] bytes_, IOMode mode) { return doWrite(bytes_, mode); } + } + + alias write = OutputStream.write; + + private size_t doWrite(scope const(ubyte)[] bytes, IOMode mode) { assert(!m_finalized); @@ -1008,8 +1029,8 @@ final class IncomingWebSocketMessage : InputStream { size_t nread = 0; while (dst.length > 0) { - vibe.internal.exception.enforce!WebSocketException(!empty , "cannot read from empty stream"); - vibe.internal.exception.enforce!WebSocketException(leastSize > 0, "no data available" ); + enforce!WebSocketException(!empty , "cannot read from empty stream"); + enforce!WebSocketException(leastSize > 0, "no data available" ); import std.algorithm : min; auto sz = cast(size_t)min(leastSize, dst.length); @@ -1042,7 +1063,7 @@ private static immutable s_webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11 * Currently only 6 values are defined, however the opcode is defined as * taking 4 bits. */ -private enum FrameOpcode : ubyte { +public enum FrameOpcode : ubyte { continuation = 0x0, text = 0x1, binary = 0x2, @@ -1121,7 +1142,7 @@ private struct Frame { } if (sys_rng) { - sys_rng.read(dst[0 .. 4]); + sys_rng.read(dst[0 .. 4]); for (size_t i = 0; i < payload.length; i++) payload[i] ^= dst[i % 4]; } @@ -1150,7 +1171,7 @@ private struct Frame { // RFC 6455, 5.2, 'Payload length': If 127, the following 8 bytes // interpreted as a 64-bit unsigned integer (the most significant // bit MUST be 0) - vibe.internal.exception.enforce!WebSocketException(!(length >> 63), + enforce!WebSocketException(!(length >> 63), "Received length has a non-zero most significant bit"); } @@ -1167,7 +1188,7 @@ private struct Frame { // Read payload // TODO: Provide a way to limit the size read, easy // DOS for server code here (rejectedsoftware/vibe.d#1496). - vibe.internal.exception.enforce!WebSocketException(length <= size_t.max); + enforce!WebSocketException(length <= size_t.max); frame.payload = new ubyte[](cast(size_t)length); stream.read(frame.payload); @@ -1250,7 +1271,7 @@ unittest { /** * Generate a challenge key for the protocol upgrade phase. */ -private string generateChallengeKey(scope RandomNumberStream rng) +private string generateChallengeKey(RandomNumberStream rng) { ubyte[16] buffer; rng.read(buffer); diff --git a/tests/dummy.d b/tests/dummy.d deleted file mode 100644 index df5cdd4..0000000 --- a/tests/dummy.d +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env dub -/+ dub.sdl: - name "test" -+/ - -module test; - -void main() -{ - import std.stdio : writeln; - writeln("Just a placeholder for actual tests."); -} diff --git a/tests/http2/cert.pem b/tests/http2/cert.pem deleted file mode 100644 index cb78d8d..0000000 --- a/tests/http2/cert.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC+jCCAeICCQC0s4K+3l6O0TANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJJ -VDENMAsGA1UECgwEU2FvYzENMAsGA1UECwwEU2FvYzESMBAGA1UEAwwJbG9jYWxo -b3N0MB4XDTE4MTEyNzIxMjAzOFoXDTI4MTEyNDIxMjAzOFowPzELMAkGA1UEBhMC -SVQxDTALBgNVBAoMBFNhb2MxDTALBgNVBAsMBFNhb2MxEjAQBgNVBAMMCWxvY2Fs -aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqg8tgGb8kMI6st -T79lraCQDnFuRYT9vgEJUQDMYAx84ECCmCKa3nT9vEAijRoXZ6h1aDAQOVCaZI6A -8YXIzhLqCjNI0bE1Y4TFRg+4J+APpwr5CloOHnzRceQUj9vUZLng5SGF+1Hue+hD -wqfZtYF0Lr4T9CA2/zX/l8uJeW/DgITVrbkpPHGKfKlbSVPsDNQzoVkrckRbqAsp -zj2MUVA2Sx9KeobcuGPjiAOQbTGdgoOdJr7arJviKDkCobLpQ79csS9It8vmpvsf -eGsD9l5McKsE3x/cYM0sm3CnCn1bhPl1EmCzUFyk8D89L+G+myEq/pQPMn43dIYu -ZcjqDr0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAD/0KCVWGuhx+CpsBnSlpUjkM -ThrJWCw1VR0vEKoGGPaaonM1Vdx1/RlmCd21CScgeYROcSsb6rE5lgcdLEjgoqyU -GflM5UK9pi9YPOUj0XZelXkIiWDuc7VS4xV9XsaD5Dock1iiGM6+q0YehmdzwNRv -CKafSL2TRZStTvGPv3n4v2e0hn/fr7N4ZaufddeS+XTQ4cNgcaoICvlQx3mxQoTB -R/mMVdL2EWOhiXzVjfns/mJhV/PUHzqsh/Xydl5O2W4WI/oxIl5lGKgZsjW3RObe -0LCz4kQ7cNoB+JZ4O9ZCHWeN0iZnqLNHOcaubLuwAYQjOEQwbr/hOi5UoMA82A== ------END CERTIFICATE----- diff --git a/tests/http2/key.pem b/tests/http2/key.pem deleted file mode 100644 index 5216c3e..0000000 --- a/tests/http2/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqoPLYBm/JDCOr -LU+/Za2gkA5xbkWE/b4BCVEAzGAMfOBAgpgimt50/bxAIo0aF2eodWgwEDlQmmSO -gPGFyM4S6gozSNGxNWOExUYPuCfgD6cK+QpaDh580XHkFI/b1GS54OUhhftR7nvo -Q8Kn2bWBdC6+E/QgNv81/5fLiXlvw4CE1a25KTxxinypW0lT7AzUM6FZK3JEW6gL -Kc49jFFQNksfSnqG3Lhj44gDkG0xnYKDnSa+2qyb4ig5AqGy6UO/XLEvSLfL5qb7 -H3hrA/ZeTHCrBN8f3GDNLJtwpwp9W4T5dRJgs1BcpPA/PS/hvpshKv6UDzJ+N3SG -LmXI6g69AgMBAAECggEABGMQ+1bnBt9GB3+pvj0KAsfRZz8zkGDJwtMPfxrCGXAY -f/Abo0AiGhXwAiIhSJtzcdRa8mloX9coYLWWAB+SlM/ppTnpEkpSHbbIv1y3wb4F -SHXwuzIDvKfR6tBQhpFmaZULrxn3xTqBew5K8e/aB9MJ5J80tqNTWnsQ1w3YznPI -l3gnxBS/8gAN0QAqmloSHcXTX87wvUYXjVHNQMhZl7lDuin7Riq6zsqtvE/auL8Q -fLoFMUepLwKMnsNa7R2AvAeve5nxLKqP5nCAWrpHaxKc/URIO/HvmLehjXj4C9iv -Nt7tQggmR0yK83nD4P7b71oEWaki3syCigdC6fn8gQKBgQDZ3b57jmEUkAUiAAQY -JIQ0PlAfJHm48XG/wbTT7r4D2xqZGh0DbIxaO8t3NotGclpFlYQK7c/EcUFqz0Ui -L11P7pbAb1sHNv57AQyZDk+G3eGYbXMYtxXGEE/MLSQXI34be6OAZWzGxAxF3CJv -6/pNciP+1+8nvriJ8X66vYaCYQKBgQDIfpBmPiZ/4ryIO1jFinXnf5BhmMR62T6O -4+ouBtoYkzY25I6iE3oACJIi4b6mu1hSFHV+X2wSJhxQbsHzhBbWkHGKww3ux7Dv -gkr3Lh5OyJQo7tQ+ZgPmwTvgQ5WPGyjCb/i/kQPyDhB/2CH9El6Fj7zARxDrnMxZ -QU6/EB0h3QKBgQCjT4sHxzSSW1rdn7Fx/31yQ/t/PLJZpfMkUKmh6+ZXsZgAiAiT -lnym7weXtzUze2ibnHY8UwNZN6RX0kL6WxLRZgFvAonzoI+l2KV+3bFTYWNIBVMv -hZ42t+d5H6VcCuju5QEsr9qYLEEe5OKTjwozn4nH8El/O4HNNU9P393CQQKBgQDE -8IV42P3KSvb0Po9hYzOTlRtGst/06CZqB3iFg5eB4kCzxOhSTh1doQ2jE+nJL2d/ -ILeUzbiDT0oqu9DqGcqxMqtFKXorHZ4wdeSbToLY0m+ukYKMVHrqlM7y6JAlRQFR -lNlbmt+sSaJtYelpBD0yptE6cdQuDBQybFMknHhxRQKBgBp/s/DVr9wxVUqNg+zo -UkAXypU+indLlKPQ9jJdl1jR3PWF/vtw12A290Jm1lN/OSoyUc2aALrNNtvsztN1 -gUyfjCUFW4ygpOCV4RumUHICSMfYVwDrTapxJ+KZBTPdFw4Cuta+VnW315Et95V8 -Ejyq9xag3+xGXQN8JPK/QWDg ------END PRIVATE KEY----- diff --git a/tests/server.crt b/tests/server.crt deleted file mode 100644 index 3053f6f..0000000 --- a/tests/server.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDUjCCAjoCCQDPHygOhe1ZVjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJE -RTEbMBkGA1UECBMSU2NobGVzd2lnLUhvbHN0ZWluMRAwDgYDVQQHFAdMw7xiZWNr -MRkwFwYDVQQKExBvdXRlcnByb2R1Y3Qub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3Qw -HhcNMTIwMjE0MjAxNDM0WhcNMTMwMjEzMjAxNDM0WjBrMQswCQYDVQQGEwJERTEb -MBkGA1UECBMSU2NobGVzd2lnLUhvbHN0ZWluMRAwDgYDVQQHFAdMw7xiZWNrMRkw -FwYDVQQKExBvdXRlcnByb2R1Y3Qub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4fLPWRShlq+o6vM5nhUCtR+IN -mlAlQCzM3asWpQ76T/MOX1Ci1brypRgOXCmYI8c5lFGOIMpH1ZAh987t4UdkwVCn -76pXv5yY4U1jJ5i9v7R7IUeKZ7utwlq1jPo3WWhiZwm1o6AxVRHGiUQ5XrcX0Rgt -0bh9/5BWHLks3OJ44myjpgGv2J7n1LcyecRAd+suN4qsBiTITQBYq27WYsDCqrVB -BoyCPhb51xTlaMlcAH2ekrm886FlC95VrwV7o4jb4sRGYdfKqT6HwXK1yTB3n742 -2WavDgRj2vNQaL+XoPiBOrQZ8fj9PeWCyefSEtqz8/dpnJO+pxidKAow8As5AgMB -AAEwDQYJKoZIhvcNAQEFBQADggEBAA8N27Wb9aq9JkAEGZ0Z/CLHbLeV3UQ5qb9V -6KG8vvSoew3oxMlcHa49kq89AKx/gewt4fqCAHk64qpx8aEdbYorTbo9VIbwoLek -9Lyp+AynmqDA6zk5+uOtPkwfN84f30khH04ouSmOvbV+uqD9bVZtR8ULTzbuE2h1 -jbT64JQd+GW/uLQ770EKDVFml52BMJWrRFFgaRQhkm9k8krKqsCvYfMoULk3EQqS -b5a/5q9pirXB7AHmiYAnqDu2xQL8N5e548RTZSWNl7mZD1NvVYc4l8tSKfCC3zG3 -4wKngPl6t68pdnli67lX3YDTdHgOZWL+CsJT9TIRAOojfp5kWGY= ------END CERTIFICATE----- diff --git a/tests/server.key b/tests/server.key deleted file mode 100644 index 8d3222e..0000000 --- a/tests/server.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAuHyz1kUoZavqOrzOZ4VArUfiDZpQJUAszN2rFqUO+k/zDl9Q -otW68qUYDlwpmCPHOZRRjiDKR9WQIffO7eFHZMFQp++qV7+cmOFNYyeYvb+0eyFH -ime7rcJatYz6N1loYmcJtaOgMVURxolEOV63F9EYLdG4ff+QVhy5LNzieOJso6YB -r9ie59S3MnnEQHfrLjeKrAYkyE0AWKtu1mLAwqq1QQaMgj4W+dcU5WjJXAB9npK5 -vPOhZQveVa8Fe6OI2+LERmHXyqk+h8Fytckwd5++Ntlmrw4EY9rzUGi/l6D4gTq0 -GfH4/T3lgsnn0hLas/P3aZyTvqcYnSgKMPALOQIDAQABAoIBABdkiJEk18h8kgi8 -pBdwSBEwyjMbXAo9JvEbMnR+nXWT6afq4hijrT7TPEel3AhUkRB2BBlXgw60v7/u -4ig7pofaE1YYB6t0unCQMPXfsXht9H6ga6fbG2se982JgLi/94JyukJz6v4WYVih -UytLHUBB3SUCMLiZTT3+CmTr5TOamtcUi9ZNKtbxT2l+8iDbi7WaCUznrODZ8YMn -/5RhE3XopBuCmeT8P7EhJSHe1THEcCcXtlChb7bMU7DMpvWYp9XBM4CQ2PpdHN5x -bvKP8P6IH0TpjdBrdE16yO9P14saJoy1GSOx8y6zUxTMdgyFUnXK4bC+nUi3VRrq -cikR3GECgYEA9MUsGAhVF8CRUsgvbn/q1QM1oFG1Dj1+LYEnXXNbdOugZVj2L8eT -olAFFjprciemADmqhnxNgM0Ev+RVZzSDZFrAhQ+NYKw6JQe/EfIvnGyt5GXnoIJQ -4hsibRH2UDHoGzxWTwcxbOdM3mqnY3WCMA3JWoLDEqApGRDh/hFFYtUCgYEAwPOB -Ryfx5wTg/HM2kaVETgPkML81PvGdJxgKDW6XabEVdqf3Ds1UfELHRCK0LH0RS+8L -5GDBuBeAuhpXtzkmwK30rTMfVqxi7IlddcYpBqCg9bHdyg6iAI2htwm+j9aLSCPw -uNeCSqIYKIvLrcvrFWOlLRYonSxlyj7XLFypkNUCgYAnjkGs9JPDzePuS9mWcueh -Wu5spSesUHW2ptuUt5K9F2MJXdITMJ6EKYhY6kH45b1m5erP5wCjYv50gFLo5cyi -CCR6nGPNjqeq2lCfdtMI5WtIsMs43jZyA86Rb8itdxM6a4rLJK9xGQQMIZJBeXj7 -iQ7UKLObq/RYT6kl5OagrQKBgGLsdRtGH3+RwMetSgzh7mMRG6ziWyoqNagVaxH3 -4SkO4TI0azXrj6Ull4QXRsiIVpXXuQEdmjQH2LeRSedmJbgjd45U53xIZW9f/cqk -DeSX9e4BgvRVDDm8Y2y0Uj7sf/w8cO5Tjzk0Ya5n/cTdB2mv7L9w3OG4IXfPQAI+ -f7EBAoGBAKgvIg/bvF705Lg2bebjR2MVeVySDZP/SjY5my+4cF8VSJOK+8WA1MeJ -dv4sukGjAMrMApegKmaiIngb2izINfanEd0xn4h1nCwVcRaNr9fSkZDBhi+JErol -0janntvmByXPD0BXguza7NVJGmtzCyZk+rOHtC1J+qG7kdXHpPYT ------END RSA PRIVATE KEY----- diff --git a/tests/vibe.http.client.1389/dub.sdl b/tests/vibe.http.client.1389/dub.sdl new file mode 100644 index 0000000..a1ded0b --- /dev/null +++ b/tests/vibe.http.client.1389/dub.sdl @@ -0,0 +1,3 @@ +name "1389" +dependency "vibe-http" path="../../" +versions "VibeDefaultMain" diff --git a/tests/vibe.http.client.1389/source/app.d b/tests/vibe.http.client.1389/source/app.d new file mode 100644 index 0000000..a506055 --- /dev/null +++ b/tests/vibe.http.client.1389/source/app.d @@ -0,0 +1,44 @@ +import vibe.core.core; +import vibe.core.log; +import vibe.http.client; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; +import std.exception : assertThrown; + +shared static this() +{ + // determine external network interface + auto ec = connectTCP("vibed.org", 80); + auto externalAddr = ec.localAddress; + ec.close(); + logInfo("External interface: %s", externalAddr.toString()); + + auto settings = new HTTPServerSettings; + settings.port = 0; + settings.bindAddresses = [externalAddr.toAddressString()]; + immutable serverAddr = listenHTTP(settings, (req, res) { + if (req.clientAddress.toAddressString() == "127.0.0.1") + res.writeBody("local"); + else res.writeBody("remote"); + }).bindAddresses[0]; + + runTask({ + scope(exit) exitEventLoop(true); + + try { + + auto url = "http://"~serverAddr.toString; + logInfo(url); + + auto cs = new HTTPClientSettings; + cs.networkInterface = resolveHost("127.0.0.1"); + auto res = requestHTTP(url, null, cs).bodyReader.readAllUTF8(); + assert(res == "local", "Unexpected reply: "~res); + + auto cs2 = new HTTPClientSettings; + cs2.networkInterface = resolveHost(externalAddr.toAddressString()); + res = requestHTTP(url, null, cs2).bodyReader.readAllUTF8(); + assert(res == "remote", "Unexpected reply: "~res); + } catch (Exception e) assert(false, e.msg); + }); +} diff --git a/tests/vibe.http.client.1426/dub.sdl b/tests/vibe.http.client.1426/dub.sdl new file mode 100644 index 0000000..b57f9c1 --- /dev/null +++ b/tests/vibe.http.client.1426/dub.sdl @@ -0,0 +1,3 @@ +name "tests" +description "Tests body transfer for non-keep-alive connections" +dependency "vibe-http" path="../.." diff --git a/tests/vibe.http.client.1426/source/app.d b/tests/vibe.http.client.1426/source/app.d new file mode 100644 index 0000000..bfb1072 --- /dev/null +++ b/tests/vibe.http.client.1426/source/app.d @@ -0,0 +1,27 @@ +import vibe.core.core; +import vibe.core.net; +import vibe.http.client; +import vibe.stream.operations; + +/// Workaround segv caused by parallel GC +extern(C) __gshared string[] rt_options = [ "gcopt=parallel:0" ]; + +int main () +{ + immutable serverAddr = listenTCP(0, (TCPConnection c) @safe nothrow { + try c.write("HTTP/1.1 200 OK\r\nConnection: Close\r\n\r\nqwerty"); + catch (Exception e) assert(0, e.msg); + }, "127.0.0.1").bindAddress; + + runTask({ + try requestHTTP("http://" ~ serverAddr.toString, + (scope req) {}, + (scope res) { + assert(res.bodyReader.readAllUTF8() == "qwerty"); + } + ); + catch (Exception e) assert(false, e.msg); + exitEventLoop(); + }); + return runEventLoop(); +} diff --git a/tests/vibe.http.client.2025/dub.sdl b/tests/vibe.http.client.2025/dub.sdl new file mode 100644 index 0000000..2691826 --- /dev/null +++ b/tests/vibe.http.client.2025/dub.sdl @@ -0,0 +1,2 @@ +name "2025" +dependency "vibe-http" path="../../" diff --git a/tests/vibe.http.client.2025/source/app.d b/tests/vibe.http.client.2025/source/app.d new file mode 100644 index 0000000..cd8eed9 --- /dev/null +++ b/tests/vibe.http.client.2025/source/app.d @@ -0,0 +1,43 @@ +/++ dub.sdl: + dependency "vibe-d" path=".." ++/ +import vibe.core.core; +import vibe.http.client; +import vibe.http.server; +import vibe.core.log; + +void main() +{ + auto settings = new HTTPServerSettings; + settings.port = 0; + settings.bindAddresses = ["127.0.0.1"]; + auto l = listenHTTP(settings, (req, res) { + res.headers.addField("Set-Cookie", "hello=world; Path=/path"); + // res.setCookie("hello", "world", "/path"); + res.writeBody("Hello, World!"); + }); + + auto url = URL("http", "127.0.0.1", l.bindAddresses[0].port, InetPath("/")); + + runTask({ + try { + auto res = requestHTTP(url); + assert(res.statusCode == 200, res.toString); + + assert(res.cookies.length == 1); + auto cookie = res.cookies.get("hello"); + assert(cookie !is null); + assert(cookie.value == "world"); + assert(cookie.httpOnly == false); + assert(cookie.secure == false); + assert(cookie.expires == ""); + assert(cookie.maxAge == 0); + assert(cookie.domain == ""); + assert(cookie.path == "/path"); + + res.dropBody(); + exitEventLoop(); + } catch (Exception e) assert(false, e.msg); + }); + runApplication(); +} diff --git a/tests/vibe.http.client.2080/dub.sdl b/tests/vibe.http.client.2080/dub.sdl new file mode 100644 index 0000000..653576a --- /dev/null +++ b/tests/vibe.http.client.2080/dub.sdl @@ -0,0 +1,2 @@ +name "2080" +dependency "vibe-http" path="../../" diff --git a/tests/vibe.http.client.2080/source/app.d b/tests/vibe.http.client.2080/source/app.d new file mode 100644 index 0000000..f6932be --- /dev/null +++ b/tests/vibe.http.client.2080/source/app.d @@ -0,0 +1,30 @@ +/++ dub.sdl: + dependency "vibe-d" path=".." ++/ +import vibe.core.core; +import vibe.http.client; +import vibe.http.server; +import vibe.core.log; + +void main() +{ + auto settings = new HTTPServerSettings; + settings.port = 0; + settings.bindAddresses = ["0.0.0.0"]; + auto l = listenHTTP(settings, (req, res) { + assert(req.fullURL.host == "::7f00:1"); + res.writeBody("Hello, World!"); + }); + + auto url = URL("http", "::7f00:1", l.bindAddresses[0].port, InetPath("/")); + + runTask({ + try { + auto res = requestHTTP(url); + assert(res.statusCode == 200, res.toString); + res.dropBody(); + exitEventLoop(); + } catch (Exception e) assert(false, e.msg); + }); + runApplication(); +} diff --git a/tests/vibe.http.server.1388/dub.sdl b/tests/vibe.http.server.1388/dub.sdl new file mode 100644 index 0000000..877e23d --- /dev/null +++ b/tests/vibe.http.server.1388/dub.sdl @@ -0,0 +1,3 @@ +name "tests" +description "IPv6 request" +dependency "vibe-http" path="../../" diff --git a/tests/vibe.http.server.1388/source/app.d b/tests/vibe.http.server.1388/source/app.d new file mode 100644 index 0000000..26c8f4f --- /dev/null +++ b/tests/vibe.http.server.1388/source/app.d @@ -0,0 +1,44 @@ +import vibe.core.core; +import vibe.core.net; +import vibe.http.server; +import vibe.stream.operations; +import core.time : msecs; +import std.datetime : Clock, UTC; + +void main() +{ + version (none) { + auto s1 = new HTTPServerSettings; + s1.port = 0; + s1.bindAddresses = ["::1"]; + immutable serverAddr = listenHTTP(s1, &handler).bindAddresses[0]; + + runTask({ + auto conn = connectTCP(serverAddr); + conn.write("GET / HTTP/1.1\r\nHost: [::1]\r\n\r\n"); + string res = cast(string)conn.readLine(); + assert(res == "HTTP/1.1 200 OK", res); + while (conn.readLine().length > 0) {} + assert(cast(string)conn.readLine() == "success"); + + conn.write("GET / HTTP/1.1\r\nHost: [::1]:11388\r\n\r\n"); + res = cast(string)conn.readLine(); + assert(res == "HTTP/1.1 200 OK", res); + while (conn.readLine().length > 0) {} + assert(cast(string)conn.readLine() == "success"); + + conn.close(); + + exitEventLoop(); + }); + runApplication(); + } + + import vibe.core.log : logWarn; + logWarn("Test disabled due to missing IPv6 support (loopback) on Travis-CI"); +} + +void handler(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody("success\r\n"); +} diff --git a/tests/vibe.http.server.1721/dub.sdl b/tests/vibe.http.server.1721/dub.sdl new file mode 100644 index 0000000..ec0c7a3 --- /dev/null +++ b/tests/vibe.http.server.1721/dub.sdl @@ -0,0 +1,4 @@ +name "tests" +description "Wrong host header tests" +dependency "vibe-http" path="../../" +versions "VibeDefaultMain" diff --git a/tests/vibe.http.server.1721/source/app.d b/tests/vibe.http.server.1721/source/app.d new file mode 100644 index 0000000..1035c50 --- /dev/null +++ b/tests/vibe.http.server.1721/source/app.d @@ -0,0 +1,44 @@ +import vibe.core.core; +import vibe.core.net; +import vibe.http.server; +import vibe.stream.operations; +import std.algorithm.searching : startsWith; +import std.string : toLower; +import core.time : seconds; + + +shared static this() +{ + auto s1 = new HTTPServerSettings; + s1.options &= ~HTTPServerOption.errorStackTraces; + s1.port = 0; + s1.bindAddresses = ["127.0.0.1"]; + immutable serverAddr = listenHTTP(s1, &handler).bindAddresses[0]; + + runTask({ + scope (exit) exitEventLoop(); + + try { + auto conn = connectTCP(serverAddr); + conn.write("GET / HTTP/1.0\r\n\r\n"); + string res = cast(string)conn.readLine(); + assert(res == "HTTP/1.0 200 OK", res); + while (true) { + auto ln = conn.readLine(); + if (!ln.length) break; + assert(!(cast(const(char)[])ln).toLower().startsWith("transfer-encoding:"), "Server sent transfer encoding on HTTP/1.0 connection."); + } + assert(cast(string)conn.readLine() == "Hello, World!"); + assert(!conn.waitForData(1.seconds), "Connection not closed by server after response was written."); + assert(conn.empty); + } catch (Exception e) { + assert(false, e.msg); + } + }); +} + +void handler(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.bodyWriter.write("Hello, "); + res.bodyWriter.write("World!\r\n"); +} diff --git a/tests/vibe.http.server.empty-json/dub.sdl b/tests/vibe.http.server.empty-json/dub.sdl new file mode 100644 index 0000000..3ae1002 --- /dev/null +++ b/tests/vibe.http.server.empty-json/dub.sdl @@ -0,0 +1,3 @@ +name "tests" +description "Receive an empty JSON body" +dependency "vibe-http" path="../../" diff --git a/tests/vibe.http.server.empty-json/source/app.d b/tests/vibe.http.server.empty-json/source/app.d new file mode 100644 index 0000000..c35a33c --- /dev/null +++ b/tests/vibe.http.server.empty-json/source/app.d @@ -0,0 +1,34 @@ +import vibe.core.core : runTask, runEventLoop, exitEventLoop; +import vibe.data.json : serializeToJsonString; +import vibe.http.client : requestHTTP; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; + +import std.conv : to; + +void main() +{ + auto s1 = new HTTPServerSettings; + s1.options &= ~HTTPServerOption.errorStackTraces; + s1.bindAddresses = ["127.0.0.1"]; + s1.port = 11721; + listenHTTP(s1, &handler); + + runTask({ + scope (exit) exitEventLoop(); + try { + auto req = requestHTTP("http://127.0.0.1:" ~ s1.port.to!string); + assert(req.bodyReader.readAllUTF8 == "JSON: null - World!\r\n"); + } catch (Exception e) { + assert(false, e.msg); + } + }); + runEventLoop(); +} + +void handler(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + req.contentType = "application/json; charset=UTF-8"; + res.bodyWriter.write("JSON: " ~ req.json.serializeToJsonString); + res.bodyWriter.write(" - World!\r\n"); +} diff --git a/tests/vibe.http.server.host-header/dub.sdl b/tests/vibe.http.server.host-header/dub.sdl new file mode 100644 index 0000000..ec0c7a3 --- /dev/null +++ b/tests/vibe.http.server.host-header/dub.sdl @@ -0,0 +1,4 @@ +name "tests" +description "Wrong host header tests" +dependency "vibe-http" path="../../" +versions "VibeDefaultMain" diff --git a/tests/vibe.http.server.host-header/source/app.d b/tests/vibe.http.server.host-header/source/app.d new file mode 100644 index 0000000..3a5b7a6 --- /dev/null +++ b/tests/vibe.http.server.host-header/source/app.d @@ -0,0 +1,64 @@ +import vibe.core.core; +import vibe.core.log : logInfo; +import vibe.core.net; +import vibe.http.server; +import vibe.stream.operations; +import core.time : msecs, seconds; +import std.datetime : Clock, UTC; + +shared static this() +{ + auto s1 = new HTTPServerSettings; + s1.options &= ~HTTPServerOption.errorStackTraces; + s1.port = 0; + s1.bindAddresses = ["127.0.0.1"]; + immutable serverAddr = listenHTTP(s1, &handler).bindAddresses[0]; + + runTask({ + try { + auto conn = connectTCP(serverAddr); + conn.write("GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"); + string res = cast(string)conn.readLine(); + assert(res == "HTTP/1.1 200 OK", res); + while (conn.readLine().length > 0) {} + assert(cast(string)conn.readLine() == "success"); + logInfo("1.1 with Host header OK."); + + conn.write("GET / HTTP/1.1\r\n\r\n"); + res = cast(string)conn.readLine(); + assert(res == "HTTP/1.1 400 Bad Request", res); + while (conn.readLine().length > 0) {} + ubyte[39] buf; + conn.read(buf); + assert(cast(string)buf == "400 - Bad Request\n\nMissing Host header."); + assert(!conn.waitForData(1.seconds)); + assert(conn.empty); + conn.close(); + assert(!conn.connected); + logInfo("1.1 without Host header OK."); + + conn = connectTCP(serverAddr); + conn.write("GET / HTTP/1.0\r\n\r\n"); + res = cast(string)conn.readLine(); + assert(res == "HTTP/1.0 200 OK", res); + while (conn.readLine().length > 0) {} + assert(cast(string)conn.readLine() == "success"); + assert(!conn.waitForData(1.seconds)); + assert(conn.empty); + conn.close(); + assert(!conn.connected); + assert(!conn.waitForData(1.seconds)); + assert(!conn.connected && conn.empty); + logInfo("1.0 without Host header OK."); + } catch (Exception e) { + assert(false, e.msg); + } + + scope (exit) exitEventLoop(); + }); +} + +void handler(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.writeBody("success\r\n"); +} diff --git a/tests/vibe.http.server.informational-status/dub.sdl b/tests/vibe.http.server.informational-status/dub.sdl new file mode 100644 index 0000000..958e938 --- /dev/null +++ b/tests/vibe.http.server.informational-status/dub.sdl @@ -0,0 +1,3 @@ +name "tests" +description "informational status response handling test" +dependency "vibe-http" path="../../" diff --git a/tests/vibe.http.server.informational-status/source/app.d b/tests/vibe.http.server.informational-status/source/app.d new file mode 100644 index 0000000..7e78d45 --- /dev/null +++ b/tests/vibe.http.server.informational-status/source/app.d @@ -0,0 +1,50 @@ +import core.time; +import vibe.core.log; +import vibe.core.core : exitEventLoop, runApplication, runTask, sleep; +import vibe.http.client; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; + +void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.statusCode = HTTPStatus.processing; + res.writeVoidBody(); + + sleep(100.msecs); + res.statusCode = HTTPStatus.ok; + res.writeBody("Hello, World!", "text/plain"); +} + +void main() +{ + auto settings = new HTTPServerSettings; + settings.port = 8099; + settings.bindAddresses = ["::1", "127.0.0.1"]; + + auto l = listenHTTP(settings, &handleRequest); + scope (exit) l.stopListening(); + + runTask({ + bool got102, got200; + scope (exit) exitEventLoop(); + + try requestHTTP("http://127.0.0.1:8099/", null, + (scope res) { + if (res.statusCode == HTTPStatus.processing) { + assert(!got200, "Status 200 received first"); + got102 = true; + } + else if (res.statusCode == HTTPStatus.ok) { + got200 = true; + assert(res.bodyReader.readAllUTF8() == "Hello, World!"); + } + } + ); + catch (Exception e) assert(false, e.msg); + assert(got102, "Status 102 wasn't received"); + assert(got200, "Status 200 wasn't received"); + logInfo("All web tests succeeded."); + }); + + runApplication(); +} diff --git a/tests/vibe.http.server.listenHTTP/dub.sdl b/tests/vibe.http.server.listenHTTP/dub.sdl new file mode 100644 index 0000000..ede0292 --- /dev/null +++ b/tests/vibe.http.server.listenHTTP/dub.sdl @@ -0,0 +1,4 @@ +name "tests" +description "listenHTTP tests" +dependency "vibe-http" path="../../" +versions "VibeDefaultMain" diff --git a/tests/vibe.http.server.listenHTTP/source/app.d b/tests/vibe.http.server.listenHTTP/source/app.d new file mode 100644 index 0000000..32bfc39 --- /dev/null +++ b/tests/vibe.http.server.listenHTTP/source/app.d @@ -0,0 +1,26 @@ +import vibe.core.core; +import vibe.core.log; +import vibe.http.client; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; +import std.algorithm : find; +import std.range.primitives : front; +import std.socket : AddressFamily; + +shared static this() +{ + immutable serverAddr = listenHTTP(":0", (scope req, scope res) { + res.writeBody("Hello world."); + }).bindAddresses.find!(addr => addr.family == AddressFamily.INET).front; + + runTask({ + scope (exit) exitEventLoop(); + + try { + auto res = requestHTTP("http://" ~ serverAddr.toString); + assert(res.statusCode == HTTPStatus.ok); + assert(res.bodyReader.readAllUTF8 == "Hello world."); + } catch (Exception e) assert(false, e.msg); + logInfo("All web tests succeeded."); + }); +} diff --git a/tests/vibe.http.server.reject-predicate/dub.sdl b/tests/vibe.http.server.reject-predicate/dub.sdl new file mode 100644 index 0000000..c414eac --- /dev/null +++ b/tests/vibe.http.server.reject-predicate/dub.sdl @@ -0,0 +1,3 @@ +name "tests" +description "reject connection predicate test" +dependency "vibe-http" path="../../" diff --git a/tests/vibe.http.server.reject-predicate/source/app.d b/tests/vibe.http.server.reject-predicate/source/app.d new file mode 100644 index 0000000..57e907b --- /dev/null +++ b/tests/vibe.http.server.reject-predicate/source/app.d @@ -0,0 +1,67 @@ +module app; + +import core.time; +import vibe.core.log; +import vibe.core.core : exitEventLoop, runApplication, runTask, sleep; +import vibe.http.client; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; + +void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.statusCode = HTTPStatus.ok; + res.writeBody("Hello, World!", "text/plain"); +} + +void main() +{ + immutable string xforward_addr = "127.0.0.2"; + immutable string xforward_addrs = xforward_addr ~ ", 127.0.0.3"; + + bool delegate (in NetworkAddress address) @safe nothrow rejectDg = (in address) @safe nothrow { + return (address.toAddressString == xforward_addr); + }; + + auto settings = new HTTPServerSettings; + settings.port = 8099; + settings.rejectConnectionPredicate = rejectDg; + settings.bindAddresses = ["::1", "127.0.0.1"]; + + auto l = listenHTTP(settings, &handleRequest); + scope (exit) l.stopListening(); + + runTask({ + bool got403, got403_multiple, got200; + scope (exit) exitEventLoop(); + + try { + requestHTTP("http://127.0.0.1:8099/", + (scope req) { + req.headers["X-Forwarded-For"] = xforward_addr; + }, + (scope res) { + got403 = (res.statusCode == HTTPStatus.forbidden); + } + ); + requestHTTP("http://127.0.0.1:8099/", + (scope req) { + req.headers["X-Forwarded-For"] = xforward_addrs; + }, + (scope res) { + got403_multiple = (res.statusCode == HTTPStatus.forbidden); + } + ); + requestHTTP("http://127.0.0.1:8099/", null, + (scope res) { + got200 = (res.statusCode == HTTPStatus.ok); + } + ); + } catch (Exception e) assert(false, e.msg); + assert(got403, "Status 403 wasn't received"); + assert(got403_multiple, "Status 403 wasn't received for multiple addresses"); + assert(got200, "Status 200 wasn't received"); + logInfo("All web tests succeeded."); + }); + + runApplication(); +} diff --git a/tests/vibe.http.websocket.2169/dub.sdl b/tests/vibe.http.websocket.2169/dub.sdl new file mode 100644 index 0000000..99c6023 --- /dev/null +++ b/tests/vibe.http.websocket.2169/dub.sdl @@ -0,0 +1,2 @@ +name "test" +dependency "vibe-http" path="../../" diff --git a/examples/http2/server.crt b/tests/vibe.http.websocket.2169/server.crt similarity index 100% rename from examples/http2/server.crt rename to tests/vibe.http.websocket.2169/server.crt diff --git a/examples/http2/server.key b/tests/vibe.http.websocket.2169/server.key similarity index 100% rename from examples/http2/server.key rename to tests/vibe.http.websocket.2169/server.key diff --git a/tests/vibe.http.websocket.2169/source/app.d b/tests/vibe.http.websocket.2169/source/app.d new file mode 100644 index 0000000..6a937be --- /dev/null +++ b/tests/vibe.http.websocket.2169/source/app.d @@ -0,0 +1,66 @@ +import vibe.core.core; +import vibe.core.log; +import vibe.inet.url; +import vibe.http.server; +import vibe.http.websockets; +import vibe.stream.tls; + + +void test(bool tls) +{ + auto settings = new HTTPServerSettings; + settings.port = 0; + settings.bindAddresses = ["127.0.0.1"]; + if (tls) { + settings.tlsContext = createTLSContext(TLSContextKind.server); + settings.tlsContext.useCertificateChainFile("server.crt"); + settings.tlsContext.usePrivateKeyFile("server.key"); + } + auto listener = listenHTTP(settings, handleWebSockets((scope ws) { + assert(ws.connected); // issue #2104 + assert(ws.receiveText() == "foo"); + ws.send("hello"); + assert(ws.receiveText() == "bar"); + ws.close(); + })); + + const serverAddr = listener.bindAddresses[0]; + const server_url = URL((tls ? "wss://" : "ws://") ~ serverAddr.toString); + + runTask({ + scope(exit) exitEventLoop(true); + + try { + // issue #2169 - calling connectWebSocket twice + auto ws1 = connectWebSocket(server_url); + yield(); + auto ws2 = connectWebSocket(server_url); + + testWS(ws1); + testWS(ws2); + yield(); + } catch (Exception e) { + assert(false, "Web sockets failed: "~e.msg); + } + }).join(); + + listener.stopListening(); +} + +void main() +{ + test(false); + test(true); +} + + +void testWS(scope WebSocket ws) +{ + assert(ws.connected); + ws.send("foo"); + assert(ws.receiveText() == "hello"); + ws.send("bar"); + assert(!ws.waitForData); + ws.close(); + logInfo("WebSocket test successful"); +} diff --git a/tests/vibe.http.websocket/dub.sdl b/tests/vibe.http.websocket/dub.sdl new file mode 100644 index 0000000..7e11a85 --- /dev/null +++ b/tests/vibe.http.websocket/dub.sdl @@ -0,0 +1,3 @@ +name "1332" +dependency "vibe-http" path="../../" +versions "VibeDefaultMain" diff --git a/tests/vibe.http.websocket/source/app.d b/tests/vibe.http.websocket/source/app.d new file mode 100644 index 0000000..3a86631 --- /dev/null +++ b/tests/vibe.http.websocket/source/app.d @@ -0,0 +1,34 @@ +import vibe.core.core; +import vibe.core.log; +import vibe.inet.url; +import vibe.http.server; +import vibe.http.websockets; + +shared static this() +{ + auto settings = new HTTPServerSettings; + settings.port = 0; + settings.bindAddresses = ["127.0.0.1"]; + immutable serverAddr = listenHTTP(settings, handleWebSockets((scope ws) { + assert(ws.connected); // issue #2104 + assert(ws.receiveText() == "foo"); + ws.send("hello"); + assert(ws.receiveText() == "bar"); + ws.close(); + })).bindAddresses[0]; + + runTask({ + scope(exit) exitEventLoop(true); + + try connectWebSocket(URL("http://" ~ serverAddr.toString), (scope ws) { + assert(ws.connected); + ws.send("foo"); + assert(ws.receiveText() == "hello"); + ws.send("bar"); + assert(!ws.waitForData); + ws.close(); + logInfo("WebSocket test successful"); + }); + catch (Exception e) assert(false, e.msg); + }); +} diff --git a/travis-ci.sh b/travis-ci.sh deleted file mode 100755 index 9556d8e..0000000 --- a/travis-ci.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -set -e -x -o pipefail - -DUB_FLAGS=${DUB_FLAGS:-} - -# Check for trailing whitespace" -grep -nrI --include='*.d' '\s$' . && (echo "Trailing whitespace found"; exit 1) - -# test for successful release build -dub build -b release --compiler=$DC $DUB_FLAGS - -# test for successful 32-bit build -if [ "$DC" == "dmd" ]; then - dub build --arch=x86 $DUB_FLAGS -fi - -dub test --compiler=$DC $DUB_FLAGS - -if [ ${BUILD_EXAMPLE=1} -eq 1 ]; then - for ex in $(\ls -1 examples/); do - echo "[INFO] Building example $ex" - # --override-config vibe-core/$CONFIG - (cd examples/$ex && dub build --compiler=$DC && dub clean) - done -fi -if [ ${RUN_TEST=1} -eq 1 ]; then - for ex in `\ls -1 tests/*.d`; do - script="${ex:0:-2}.sh" - if [ -e "$script" ]; then - echo "[INFO] Running test scipt $script" - (cd tests && "./${script:6}") - else - echo "[INFO] Running test $ex" - dub --temp-build --compiler=$DC --single $ex # --override-config vibe-core/$CONFIG - fi - done -fi