├── .devcontainer └── devcontainer.json ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build.ps1 │ └── build.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── README.md ├── api │ ├── configuration.md │ ├── connection.md │ ├── data-structures.md │ ├── global.md │ ├── listener.md │ └── request.md ├── building.md ├── client-usage.md ├── protocol-overview.md └── server-usage.md ├── lib ├── CMakeLists.txt ├── darwin │ └── exports.txt ├── libmsh3.pc.in ├── linux │ └── exports.txt ├── msh3-config.cmake.in ├── msh3.cpp ├── msh3.rc ├── msh3.ver ├── msh3_internal.hpp └── win32 │ └── msh3.def ├── msh3.h ├── msh3.hpp ├── test ├── CMakeLists.txt └── msh3test.cpp └── tool ├── CMakeLists.txt └── msh3_app.cpp /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "onCreateCommand": "git submodule update --init --recursive", 4 | "postCreateCommand": "mkdir -p build && cd build && cmake -DMSH3_TEST=ON .. && cmake --build . --config Debug" 5 | } 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Standardize line endings for all text files. 2 | * text eol=auto 3 | 4 | # Ensure text files stay text 5 | *.xml text 6 | 7 | # Ensure binary files stay binary 8 | *.pgd binary 9 | *.png binary 10 | *.jpg binary 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | day: "sunday" 10 | 11 | - package-ecosystem: "gitsubmodule" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" 15 | day: "sunday" 16 | -------------------------------------------------------------------------------- /.github/workflows/build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory = $false)] 3 | [ValidateSet("Debug", "Release")] 4 | [string]$Config = "Release", 5 | 6 | [Parameter(Mandatory = $false)] 7 | [ValidateSet("x86", "x64", "arm", "arm64", "arm64ec")] 8 | [string]$Arch = "x64", 9 | 10 | [Parameter(Mandatory = $false)] 11 | [ValidateSet("schannel", "quictls")] 12 | [string]$Tls = "quictls", 13 | 14 | [Parameter(Mandatory = $false)] 15 | [ValidateSet("static", "shared")] 16 | [string]$Link = "static", 17 | 18 | [Parameter(Mandatory = $false)] 19 | [string]$BuildId = "0", 20 | 21 | [Parameter(Mandatory = $false)] 22 | [string]$Suffix = "-private", 23 | 24 | [Parameter(Mandatory = $false)] 25 | [switch]$Clean = $false, 26 | 27 | [Parameter(Mandatory = $false)] 28 | [switch]$WithTools = $false, 29 | 30 | [Parameter(Mandatory = $false)] 31 | [switch]$WithTests = $false 32 | ) 33 | 34 | Set-StrictMode -Version 'Latest' 35 | $PSDefaultParameterValues['*:ErrorAction'] = 'Stop' 36 | 37 | if ($Clean) { 38 | if (Test-Path "./build") { Remove-Item "./build" -Recurse -Force | Out-Null } 39 | if (Test-Path "./artifacts") { Remove-Item "./artifacts" -Recurse -Force | Out-Null } 40 | } 41 | 42 | if (!(Test-Path "./build")) { 43 | New-Item -Path "./build" -ItemType Directory -Force | Out-Null 44 | } 45 | 46 | if (!(Test-Path "./artifacts")) { 47 | New-Item -Path "./artifacts" -ItemType Directory -Force | Out-Null 48 | } 49 | 50 | $Build = Resolve-Path ./build 51 | $Artifacts = Resolve-Path ./artifacts 52 | 53 | $Shared = "off" 54 | if ($Link -ne "static") { $Shared = "on" } 55 | 56 | $Tools = "off" 57 | if ($WithTools) { $Tools = "on" } 58 | 59 | $Tests = "off" 60 | if ($WithTests) { $Tests = "on" } 61 | 62 | function Execute([String]$Name, [String]$Arguments) { 63 | Write-Debug "$Name $Arguments" 64 | $process = Start-Process $Name $Arguments -PassThru -NoNewWindow -WorkingDirectory $Build 65 | $handle = $process.Handle # Magic work around. Don't remove this line. 66 | $process.WaitForExit(); 67 | if ($process.ExitCode -ne 0) { 68 | Write-Error "$Name exited with status code $($process.ExitCode)" 69 | } 70 | } 71 | 72 | if ($IsWindows) { 73 | 74 | $_Arch = $Arch 75 | if ($_Arch -eq "x86") { $_Arch = "Win32" } 76 | Execute "cmake" "-G ""Visual Studio 17 2022"" -A $_Arch -DMSH3_OUTPUT_DIR=$Artifacts -DQUIC_TLS_LIB=$Tls -DQUIC_BUILD_SHARED=$Shared -DMSH3_TEST=$Tests -DMSH3_TOOL=$Tools -DMSH3_VER_BUILD_ID=$BuildId -DMSH3_VER_SUFFIX=$Suffix .." 77 | Execute "cmake" "--build . --config $Config" 78 | 79 | } else { 80 | 81 | $BuildType = $Config 82 | if ($BuildType -eq "Release") { $BuildType = "RelWithDebInfo" } 83 | Execute "cmake" "-G ""Unix Makefiles"" -DCMAKE_BUILD_TYPE=$BuildType -DMSH3_OUTPUT_DIR=$Artifacts -DQUIC_TLS_LIB=$Tls -DQUIC_BUILD_SHARED=$Shared -DMSH3_TEST=$Tests -DMSH3_TOOL=$Tools .." 84 | Execute "cmake" "--build ." 85 | } 86 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: main 8 | 9 | concurrency: 10 | # Cancel any workflow currently in progress for the same PR. 11 | # Allow running concurrently with any other commits. 12 | group: build-${{ github.event.pull_request.number || github.sha }} 13 | cancel-in-progress: true 14 | 15 | permissions: read-all 16 | 17 | jobs: 18 | build: 19 | name: Build 20 | permissions: 21 | # required for all workflows 22 | security-events: write 23 | # required to fetch internal or private CodeQL packs 24 | packages: read 25 | # only required for workflows in private repositories 26 | actions: read 27 | contents: read 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | os: [ubuntu, windows] 32 | tls: [schannel, quictls] 33 | link: [static, shared] 34 | config: [Debug, Release] 35 | exclude: 36 | - os: ubuntu 37 | tls: schannel 38 | runs-on: ${{ matrix.os }}-latest 39 | timeout-minutes: 15 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 43 | with: 44 | submodules: 'recursive' 45 | - name: Install Perl 46 | if: runner.os == 'Windows' 47 | uses: shogo82148/actions-setup-perl@22423f01bde48fb88785c007e3166fbbbd8e892a 48 | with: 49 | perl-version: '5.34' 50 | - name: Install NASM 51 | if: runner.os == 'Windows' 52 | uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b 53 | - name: Install libnuma-dev 54 | if: runner.os == 'Ubuntu' 55 | run: sudo apt-get install -y libnuma-dev 56 | - name: Initialize CodeQL 57 | if: ${{ (matrix.os == 'ubuntu') && (matrix.tls == 'quictls') && (matrix.link == 'shared') && (matrix.config == 'Release') }} 58 | uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f 59 | with: 60 | languages: c-cpp 61 | build-mode: manual 62 | config: | 63 | queries: 64 | - uses: security-and-quality # (very verbose) 65 | query-filters: 66 | - exclude: 67 | id: cpp/loop-variable-changed 68 | - exclude: 69 | id: cpp/include-non-header 70 | - name: Build 71 | shell: pwsh 72 | run: ./.github/workflows/build.ps1 -Config ${{ matrix.config }} -Tls ${{ matrix.tls }} -Link ${{ matrix.link }} -BuildId ${{ github.run_number }} -Suffix "-official" -WithTests -WithTools -Debug 73 | - name: Perform CodeQL Analysis 74 | if: ${{ (matrix.os == 'ubuntu') && (matrix.tls == 'quictls') && (matrix.link == 'shared') && (matrix.config == 'Release') }} 75 | uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f 76 | with: 77 | category: "/language:c-cpp" 78 | output: sarif-results 79 | upload: failure-only 80 | - name: Filter SARIF 81 | if: ${{ (matrix.os == 'ubuntu') && (matrix.tls == 'quictls') && (matrix.link == 'shared') && (matrix.config == 'Release') }} 82 | uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d 83 | with: 84 | patterns: | 85 | -build/**/* 86 | -ls-qpack/**/* 87 | -msquic/**/* 88 | -test/**/* 89 | input: sarif-results/cpp.sarif 90 | output: sarif-results/cpp.sarif 91 | - name: Upload SARIF 92 | if: ${{ (matrix.os == 'ubuntu') && (matrix.tls == 'quictls') && (matrix.link == 'shared') && (matrix.config == 'Release') }} 93 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f 94 | with: 95 | sarif_file: sarif-results/cpp.sarif 96 | - name: Upload SARIF to Artifacts 97 | if: ${{ (matrix.os == 'ubuntu') && (matrix.tls == 'quictls') && (matrix.link == 'shared') && (matrix.config == 'Release') }} 98 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 99 | with: 100 | name: sarif-results 101 | path: sarif-results 102 | - name: Upload 103 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 104 | with: 105 | name: bin-${{ matrix.os }}-${{ matrix.tls }}-${{ matrix.link }}-${{ matrix.config }} 106 | path: | 107 | artifacts/*.dll 108 | artifacts/*.exe 109 | artifacts/*.pdb 110 | artifacts/*.so 111 | artifacts/msh3app 112 | artifacts/msh3test 113 | - name: msh3app 114 | run: | 115 | ./artifacts/msh3app outlook.office.com 116 | ./artifacts/msh3app www.cloudflare.com 117 | ./artifacts/msh3app www.google.com 118 | timeout-minutes: 1 119 | - name: msh3test 120 | run: ./artifacts/msh3test -v 121 | timeout-minutes: 1 122 | all-done: 123 | name: Build Complete 124 | if: always() 125 | needs: [build] 126 | runs-on: ubuntu-latest 127 | permissions: {} # No need for any permissions. 128 | steps: 129 | - name: Decide whether the needed jobs succeeded or failed 130 | uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe 131 | with: 132 | jobs: ${{ toJSON(needs) }} 133 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | build/ 353 | .vscode/ 354 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "msquic"] 2 | path = msquic 3 | url = https://github.com/microsoft/msquic 4 | branch = release/2.5 5 | [submodule "ls-qpack"] 6 | path = ls-qpack 7 | url = https://github.com/litespeedtech/ls-qpack 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | cmake_minimum_required(VERSION 3.16) 5 | 6 | file(READ "${CMAKE_CURRENT_SOURCE_DIR}/lib/msh3.ver" msh3_ver) 7 | if(NOT msh3_ver MATCHES ".*#define VER_MAJOR ([0-9]+).*#define VER_MINOR ([0-9]+).*#define VER_PATCH ([0-9]+).*") 8 | message(FATAL_ERROR "Cannot parse version from lib/msh3.ver") 9 | endif() 10 | set(msh3_ver "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") 11 | 12 | message(STATUS "msh3: Configuration start...") 13 | project(msh3 VERSION "${msh3_ver}") 14 | 15 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type on single-configuration generators") 16 | 17 | option(MSH3_TOOL "Build tool" OFF) 18 | option(MSH3_TEST "Build tests" OFF) 19 | 20 | set(MSH3_VER_BUILD_ID "0" CACHE STRING "The version build ID") 21 | message(STATUS "msh3: Version Build ID: ${MSH3_VER_BUILD_ID}") 22 | 23 | set(MSH3_VER_SUFFIX "-private" CACHE STRING "The version suffix") 24 | message(STATUS "msh3: Version Suffix: ${MSH3_VER_SUFFIX}") 25 | 26 | # use, i.e. don't skip the full RPATH for the build tree 27 | set(CMAKE_SKIP_BUILD_RPATH FALSE) 28 | 29 | # when building, don't use the install RPATH already 30 | # (but later on when installing) 31 | set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 32 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") 33 | 34 | # add the automatically determined parts of the RPATH 35 | # which point to directories outside the build tree to the install RPATH 36 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 37 | 38 | if (WIN32) 39 | # Statically link the OS included part of the runtime. 40 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 41 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib") 42 | 43 | add_compile_definitions(WIN32_LEAN_AND_MEAN) 44 | if(HAS_SPECTRE) 45 | add_compile_options(/Qspectre) 46 | endif() 47 | # Compile/link flags 48 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /GL /Zi") 49 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL /Zi") 50 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG /IGNORE:4075 /DEBUG /OPT:REF /OPT:ICF") 51 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /IGNORE:4075 /DEBUG /OPT:REF /OPT:ICF") 52 | endif() 53 | 54 | set(MSH3_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin CACHE STRING "Output directory for build artifacts") 55 | message(STATUS "msh3: Output set to ${MSH3_OUTPUT_DIR}") 56 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${MSH3_OUTPUT_DIR}) 57 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${MSH3_OUTPUT_DIR}) 58 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${MSH3_OUTPUT_DIR}) 59 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${MSH3_OUTPUT_DIR}) 60 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${MSH3_OUTPUT_DIR}) 61 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${MSH3_OUTPUT_DIR}) 62 | 63 | # ls-qpack dependency 64 | option(MSH3_USE_EXTERNAL_LSQPACK "Use an external ls-qpack installation") 65 | if(MSH3_USE_EXTERNAL_LSQPACK) 66 | find_package(ls-qpack CONFIG REQUIRED) 67 | else() 68 | # Configure and build 69 | set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) 70 | option(LSQPACK_TESTS "Build tests" OFF) 71 | option(LSQPACK_BIN "Build binaries" OFF) 72 | add_subdirectory(ls-qpack) 73 | endif() 74 | 75 | # msquic dependency 76 | option(MSH3_USE_EXTERNAL_MSQUIC "Use an external msquic installation") 77 | if(MSH3_USE_EXTERNAL_MSQUIC) 78 | find_package(msquic CONFIG REQUIRED) 79 | else() 80 | # Configure and build 81 | if (WIN32) 82 | set(QUIC_TLS_LIB "schannel" CACHE STRING "TLS Library to use") 83 | else() 84 | set(QUIC_TLS_LIB "quictls" CACHE STRING "TLS Library to use") 85 | endif() 86 | set(QUIC_BUILD_SHARED ON CACHE BOOL "Builds MsQuic as a dynamic library") 87 | set(QUIC_ENABLE_LOGGING ON CACHE BOOL "Enable MsQuic logging") 88 | set(QUIC_OUTPUT_DIR ${MSH3_OUTPUT_DIR} CACHE STRING "Output directory for build artifacts") 89 | add_subdirectory(msquic) 90 | endif() 91 | 92 | # Build msh3 library (and cmd line tool). 93 | add_subdirectory(lib) 94 | if (MSH3_TOOL) 95 | add_subdirectory(tool) 96 | endif() 97 | if (MSH3_TEST) 98 | add_subdirectory(test) 99 | endif() 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nick Banks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # msh3 2 | 3 | [![Build](https://github.com/nibanks/msh3/actions/workflows/build.yml/badge.svg)](https://github.com/nibanks/msh3/actions/workflows/build.yml) 4 | [![](https://img.shields.io/static/v1?label=RFC&message=9114&color=brightgreen)](https://tools.ietf.org/html/rfc9114) 5 | [![](https://img.shields.io/static/v1?label=RFC&message=9204&color=brightgreen)](https://tools.ietf.org/html/rfc9204) 6 | 7 | Minimal HTTP/3 library on top of [microsoft/msquic](https://github.com/microsoft/msquic) and [litespeedtech/ls-qpack](https://github.com/litespeedtech/ls-qpack). 8 | 9 | ## Features 10 | 11 | - Complete HTTP/3 ([RFC 9114](https://tools.ietf.org/html/rfc9114)) implementation 12 | - QPACK header compression ([RFC 9204](https://tools.ietf.org/html/rfc9204)) with dynamic table support 13 | - Client and server support 14 | - Sending and receiving request headers and payload 15 | - Various TLS certificate authentication options 16 | - Optional server validation ("unsecure" mode) 17 | 18 | See the [protocol overview](docs/protocol-overview.md) for more information. 19 | 20 | # Documentation 21 | 22 | Comprehensive documentation is available in the [docs](docs/README.md) folder: 23 | 24 | - **API Reference** - Detailed documentation of all APIs and data structures 25 | - **Getting Started** - Guides for building and using MSH3 26 | - **Protocol Overview** - Information about HTTP/3 and QUIC protocols 27 | 28 | ## Quick Example 29 | 30 | ```c 31 | // Initialize MSH3 API 32 | MSH3_API* api = MsH3ApiOpen(); 33 | if (api) { 34 | // Create a connection 35 | MSH3_CONNECTION* connection = MsH3ConnectionOpen(api, ConnectionCallback, context); 36 | if (connection) { 37 | // Start the connection to a server 38 | MsH3ConnectionStart(connection, config, "example.com", &serverAddress); 39 | 40 | // Create and send a request 41 | MSH3_REQUEST* request = MsH3RequestOpen(connection, RequestCallback, context, MSH3_REQUEST_FLAG_NONE); 42 | if (request) { 43 | // Send headers and optional data 44 | MsH3RequestSend(request, MSH3_REQUEST_SEND_FLAG_FIN, headers, headerCount, body, bodyLength, context); 45 | 46 | // Clean up when done 47 | MsH3RequestClose(request); 48 | } 49 | MsH3ConnectionClose(connection); 50 | } 51 | MsH3ApiClose(api); 52 | } 53 | ``` 54 | 55 | See the [client usage guide](docs/client-usage.md) and [server usage guide](docs/server-usage.md) for detailed examples. 56 | 57 | # Building 58 | 59 | For detailed build instructions, see the [building guide](docs/building.md). 60 | 61 | Quick start: 62 | 63 | ```bash 64 | git clone --recursive https://github.com/nibanks/msh3 65 | cd msh3 && mkdir build && cd build 66 | 67 | # Linux 68 | cmake -G 'Unix Makefiles' .. 69 | cmake --build . 70 | 71 | # Windows 72 | cmake -G 'Visual Studio 17 2022' -A x64 .. 73 | cmake --build . 74 | ``` 75 | 76 | # Running the Sample Application 77 | 78 | ```bash 79 | # Simple HTTP/3 GET requests to various servers 80 | msh3app outlook.office.com 81 | msh3app www.cloudflare.com 82 | msh3app www.google.com 83 | msh3app nghttp2.org:4433 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # MSH3 Documentation 2 | 3 | MSH3 is a minimal HTTP/3 library built on top of [MsQuic](https://github.com/microsoft/msquic) and [ls-qpack](https://github.com/litespeedtech/ls-qpack). 4 | 5 | ## API Documentation 6 | 7 | MSH3 provides a C-style API for HTTP/3 functionality. The API is divided into several components: 8 | 9 | - [Global API](api/global.md) - Library initialization and version information 10 | - [Configuration](api/configuration.md) - Managing configuration and credentials 11 | - [Connection](api/connection.md) - Managing HTTP/3 connections 12 | - [Request](api/request.md) - Managing HTTP/3 requests and responses 13 | - [Listener](api/listener.md) - Server-side listener for incoming connections 14 | - [Data Structures](api/data-structures.md) - Common data structures used throughout the API 15 | 16 | ## Getting Started 17 | 18 | - [Building MSH3](building.md) 19 | - [Using MSH3 as a Client](client-usage.md) 20 | - [Using MSH3 as a Server](server-usage.md) 21 | - [HTTP/3 and QUIC Protocol Overview](protocol-overview.md) 22 | 23 | ## Additional Resources 24 | 25 | - [HTTP/3 RFC 9114](https://tools.ietf.org/html/rfc9114) 26 | - [QPACK RFC 9204](https://tools.ietf.org/html/rfc9204) 27 | -------------------------------------------------------------------------------- /docs/api/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration API 2 | 3 | The Configuration API is used to create and manage configuration settings for MSH3 connections. 4 | 5 | ## MsH3ConfigurationOpen 6 | 7 | ```c 8 | MSH3_CONFIGURATION* 9 | MSH3_CALL 10 | MsH3ConfigurationOpen( 11 | MSH3_API* Api, 12 | const MSH3_SETTINGS* Settings, // optional 13 | uint32_t SettingsLength 14 | ); 15 | ``` 16 | 17 | Creates a new configuration object. 18 | 19 | ### Parameters 20 | 21 | `Api` - The API object handle. 22 | 23 | `Settings` - Optional pointer to a structure containing settings for the configuration. 24 | 25 | `SettingsLength` - The size of the settings structure in bytes. 26 | 27 | ### Returns 28 | 29 | Returns a pointer to the new configuration object, or NULL if the operation fails. 30 | 31 | ### Remarks 32 | 33 | The configuration object is used to configure connections. The settings parameter is optional and can be NULL. 34 | 35 | ### Example 36 | 37 | ```c 38 | MSH3_SETTINGS settings = {0}; 39 | settings.IsSetFlags = 0; 40 | settings.IsSet.IdleTimeoutMs = 1; 41 | settings.IdleTimeoutMs = 30000; // 30 seconds 42 | 43 | MSH3_CONFIGURATION* config = 44 | MsH3ConfigurationOpen(api, &settings, sizeof(settings)); 45 | if (config == NULL) { 46 | printf("Failed to create configuration\n"); 47 | return 1; 48 | } 49 | ``` 50 | 51 | ## MsH3ConfigurationLoadCredential 52 | 53 | ```c 54 | MSH3_STATUS 55 | MSH3_CALL 56 | MsH3ConfigurationLoadCredential( 57 | MSH3_CONFIGURATION* Configuration, 58 | const MSH3_CREDENTIAL_CONFIG* CredentialConfig 59 | ); 60 | ``` 61 | 62 | Loads credentials into a configuration object. 63 | 64 | ### Parameters 65 | 66 | `Configuration` - The configuration object. 67 | 68 | `CredentialConfig` - The credential configuration to load. 69 | 70 | ### Returns 71 | 72 | Returns MSH3_STATUS_SUCCESS if successful, or an error code otherwise. 73 | 74 | ### Remarks 75 | 76 | This function is used to load security credentials for TLS. Different credential types are supported through the MSH3_CREDENTIAL_CONFIG structure. 77 | 78 | ### Example 79 | 80 | ```c 81 | MSH3_CERTIFICATE_FILE certFile = { 82 | .PrivateKeyFile = "client.key", 83 | .CertificateFile = "client.crt" 84 | }; 85 | 86 | MSH3_CREDENTIAL_CONFIG credConfig = { 87 | .Type = MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE, 88 | .Flags = MSH3_CREDENTIAL_FLAG_CLIENT, 89 | .CertificateFile = &certFile 90 | }; 91 | 92 | MSH3_STATUS status = 93 | MsH3ConfigurationLoadCredential(config, &credConfig); 94 | if (MSH3_FAILED(status)) { 95 | printf("Failed to load credentials, status=0x%x\n", status); 96 | return 1; 97 | } 98 | ``` 99 | 100 | ## MsH3ConfigurationClose 101 | 102 | ```c 103 | void 104 | MSH3_CALL 105 | MsH3ConfigurationClose( 106 | MSH3_CONFIGURATION* Configuration 107 | ); 108 | ``` 109 | 110 | Closes a configuration object. 111 | 112 | ### Parameters 113 | 114 | `Configuration` - The configuration object to close. 115 | 116 | ### Remarks 117 | 118 | This function should be called when the configuration is no longer needed. After calling this function, the configuration handle is no longer valid. 119 | 120 | ### Example 121 | 122 | ```c 123 | MSH3_CONFIGURATION* config = MsH3ConfigurationOpen(api, NULL, 0); 124 | // Use configuration... 125 | MsH3ConfigurationClose(config); 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/api/connection.md: -------------------------------------------------------------------------------- 1 | # Connection API 2 | 3 | The Connection API provides functions for creating and managing HTTP/3 connections. 4 | 5 | ## MsH3ConnectionOpen 6 | 7 | ```c 8 | MSH3_CONNECTION* 9 | MSH3_CALL 10 | MsH3ConnectionOpen( 11 | MSH3_API* Api, 12 | const MSH3_CONNECTION_CALLBACK_HANDLER Handler, 13 | void* Context 14 | ); 15 | ``` 16 | 17 | Creates a new HTTP/3 connection object. 18 | 19 | ### Parameters 20 | 21 | `Api` - The API object. 22 | 23 | `Handler` - A callback function that will be invoked when connection events occur. 24 | 25 | `Context` - A user-provided context pointer that will be passed to the callback function. 26 | 27 | ### Returns 28 | 29 | Returns a pointer to the new connection object, or NULL if the operation fails. 30 | 31 | ### Remarks 32 | 33 | This function creates a connection object but does not start the connection. To start the connection, call MsH3ConnectionStart. 34 | 35 | ### Example 36 | 37 | ```c 38 | MSH3_STATUS 39 | ConnectionCallback( 40 | MSH3_CONNECTION* Connection, 41 | void* Context, 42 | MSH3_CONNECTION_EVENT* Event 43 | ) 44 | { 45 | switch (Event->Type) { 46 | case MSH3_CONNECTION_EVENT_CONNECTED: 47 | printf("Connection established!\n"); 48 | break; 49 | case MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE: 50 | printf("Connection shutdown complete\n"); 51 | break; 52 | // Handle other events... 53 | } 54 | return MSH3_STATUS_SUCCESS; 55 | } 56 | 57 | MSH3_CONNECTION* connection = 58 | MsH3ConnectionOpen(api, ConnectionCallback, myContext); 59 | if (connection == NULL) { 60 | printf("Failed to create connection\n"); 61 | return 1; 62 | } 63 | ``` 64 | 65 | ## MsH3ConnectionSetCallbackHandler 66 | 67 | ```c 68 | void 69 | MSH3_CALL 70 | MsH3ConnectionSetCallbackHandler( 71 | MSH3_CONNECTION* Connection, 72 | const MSH3_CONNECTION_CALLBACK_HANDLER Handler, 73 | void* Context 74 | ); 75 | ``` 76 | 77 | Sets or updates the callback handler for a connection. 78 | 79 | ### Parameters 80 | 81 | `Connection` - The connection object. 82 | 83 | `Handler` - The new callback handler. 84 | 85 | `Context` - The new context pointer to be passed to the callback. 86 | 87 | ### Remarks 88 | 89 | This function can be used to change the callback handler after a connection has been created. 90 | 91 | ### Example 92 | 93 | ```c 94 | // Update the callback handler 95 | MsH3ConnectionSetCallbackHandler(connection, NewCallback, newContext); 96 | ``` 97 | 98 | ## MsH3ConnectionSetConfiguration 99 | 100 | ```c 101 | MSH3_STATUS 102 | MSH3_CALL 103 | MsH3ConnectionSetConfiguration( 104 | MSH3_CONNECTION* Connection, 105 | MSH3_CONFIGURATION* Configuration 106 | ); 107 | ``` 108 | 109 | Sets the configuration for a connection. 110 | 111 | ### Parameters 112 | 113 | `Connection` - The connection object. 114 | 115 | `Configuration` - The configuration object to apply to the connection. 116 | 117 | ### Returns 118 | 119 | Returns MSH3_STATUS_SUCCESS if successful, or an error code otherwise. 120 | 121 | ### Remarks 122 | 123 | This function must be called before starting the connection. 124 | 125 | ### Example 126 | 127 | ```c 128 | MSH3_STATUS status = 129 | MsH3ConnectionSetConfiguration(connection, config); 130 | if (MSH3_FAILED(status)) { 131 | printf("Failed to set configuration, status=0x%x\n", status); 132 | return 1; 133 | } 134 | ``` 135 | 136 | ## MsH3ConnectionStart 137 | 138 | ```c 139 | MSH3_STATUS 140 | MSH3_CALL 141 | MsH3ConnectionStart( 142 | MSH3_CONNECTION* Connection, 143 | MSH3_CONFIGURATION* Configuration, 144 | const char* ServerName, 145 | const MSH3_ADDR* ServerAddress 146 | ); 147 | ``` 148 | 149 | Starts a connection to a server. 150 | 151 | ### Parameters 152 | 153 | `Connection` - The connection object. 154 | 155 | `Configuration` - The configuration object to use for the connection. 156 | 157 | `ServerName` - The server name (hostname) to connect to. 158 | 159 | `ServerAddress` - The server address to connect to. 160 | 161 | ### Returns 162 | 163 | Returns MSH3_STATUS_SUCCESS if the connection start was successfully initiated, or an error code otherwise. 164 | 165 | ### Remarks 166 | 167 | This function starts the connection process. The actual connection establishment happens asynchronously, and the connection callback will be invoked with a MSH3_CONNECTION_EVENT_CONNECTED event when the connection is established. 168 | 169 | ### Example 170 | 171 | ```c 172 | // Set up the server address 173 | MSH3_ADDR serverAddr = {0}; 174 | serverAddr.Ipv4.sin_family = AF_INET; 175 | inet_pton(AF_INET, "192.0.2.1", &serverAddr.Ipv4.sin_addr); 176 | MSH3_SET_PORT(&serverAddr, 443); 177 | 178 | MSH3_STATUS status = 179 | MsH3ConnectionStart(connection, config, "example.com", &serverAddr); 180 | if (MSH3_FAILED(status)) { 181 | printf("Failed to start connection, status=0x%x\n", status); 182 | return 1; 183 | } 184 | ``` 185 | 186 | ## MsH3ConnectionShutdown 187 | 188 | ```c 189 | void 190 | MSH3_CALL 191 | MsH3ConnectionShutdown( 192 | MSH3_CONNECTION* Connection, 193 | uint64_t ErrorCode 194 | ); 195 | ``` 196 | 197 | Initiates shutdown of a connection. 198 | 199 | ### Parameters 200 | 201 | `Connection` - The connection object. 202 | 203 | `ErrorCode` - An application-defined error code to send to the peer. 204 | 205 | ### Remarks 206 | 207 | This function initiates the shutdown process for a connection. The connection is not fully closed until the MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE event is received by the callback. 208 | 209 | ### Example 210 | 211 | ```c 212 | // Gracefully shutdown the connection 213 | MsH3ConnectionShutdown(connection, 0); 214 | ``` 215 | 216 | ## MsH3ConnectionClose 217 | 218 | ```c 219 | void 220 | MSH3_CALL 221 | MsH3ConnectionClose( 222 | MSH3_CONNECTION* Connection 223 | ); 224 | ``` 225 | 226 | Closes a connection and releases associated resources. 227 | 228 | ### Parameters 229 | 230 | `Connection` - The connection object to close. 231 | 232 | ### Remarks 233 | 234 | This function should be called when the connection is no longer needed. After calling this function, the connection handle is no longer valid. 235 | 236 | ### Example 237 | 238 | ```c 239 | // Close the connection 240 | MsH3ConnectionClose(connection); 241 | ``` 242 | -------------------------------------------------------------------------------- /docs/api/data-structures.md: -------------------------------------------------------------------------------- 1 | # MSH3 Data Structures 2 | 3 | This document provides an overview of the key data structures used in the MSH3 API. 4 | 5 | ## MSH3_SETTINGS 6 | 7 | ```c 8 | typedef struct MSH3_SETTINGS { 9 | union { 10 | uint64_t IsSetFlags; 11 | struct { 12 | uint64_t IdleTimeoutMs : 1; 13 | uint64_t DisconnectTimeoutMs : 1; 14 | uint64_t KeepAliveIntervalMs : 1; 15 | uint64_t InitialRttMs : 1; 16 | uint64_t PeerRequestCount : 1; 17 | uint64_t DatagramEnabled : 1; 18 | #ifdef MSH3_API_ENABLE_PREVIEW_FEATURES 19 | uint64_t XdpEnabled : 1; 20 | #endif 21 | } IsSet; 22 | }; 23 | uint64_t IdleTimeoutMs; 24 | uint32_t DisconnectTimeoutMs; 25 | uint32_t KeepAliveIntervalMs; 26 | uint32_t InitialRttMs; 27 | uint16_t PeerRequestCount; 28 | uint8_t DatagramEnabled : 1; // TODO - Add flags instead? 29 | #ifdef MSH3_API_ENABLE_PREVIEW_FEATURES 30 | uint8_t XdpEnabled : 1; 31 | uint8_t RESERVED : 6; 32 | #else 33 | uint8_t RESERVED : 7; 34 | #endif 35 | } MSH3_SETTINGS; 36 | ``` 37 | 38 | The `MSH3_SETTINGS` structure is used to configure various aspects of MSH3 behavior. 39 | 40 | - `IsSetFlags` / `IsSet`: A union that determines which settings are active. Each bit corresponds to a specific setting. 41 | - `IdleTimeoutMs`: The idle timeout in milliseconds. 42 | - `DisconnectTimeoutMs`: The disconnect timeout in milliseconds. 43 | - `KeepAliveIntervalMs`: The keep alive interval in milliseconds. 44 | - `InitialRttMs`: The initial round trip time estimate in milliseconds. 45 | - `PeerRequestCount`: The maximum number of requests allowed from a peer. 46 | - `DatagramEnabled`: Flag to enable QUIC datagrams. 47 | - `XdpEnabled`: Flag to enable XDP (available only when preview features are enabled). 48 | 49 | ## MSH3_ADDR 50 | 51 | ```c 52 | typedef union MSH3_ADDR { 53 | struct sockaddr Ip; 54 | struct sockaddr_in Ipv4; 55 | struct sockaddr_in6 Ipv6; 56 | } MSH3_ADDR; 57 | ``` 58 | 59 | The `MSH3_ADDR` union represents an IP address, supporting both IPv4 and IPv6. 60 | 61 | - `Ip`: Generic socket address. 62 | - `Ipv4`: IPv4 socket address. 63 | - `Ipv6`: IPv6 socket address. 64 | 65 | A helper macro `MSH3_SET_PORT` is provided to set the port in a portable way. 66 | 67 | ## MSH3_HEADER 68 | 69 | ```c 70 | typedef struct MSH3_HEADER { 71 | const char* Name; 72 | size_t NameLength; 73 | const char* Value; 74 | size_t ValueLength; 75 | } MSH3_HEADER; 76 | ``` 77 | 78 | The `MSH3_HEADER` structure represents an HTTP header. 79 | 80 | - `Name`: Pointer to the header name. 81 | - `NameLength`: Length of the header name. 82 | - `Value`: Pointer to the header value. 83 | - `ValueLength`: Length of the header value. 84 | 85 | ## MSH3_CREDENTIAL_CONFIG 86 | 87 | ```c 88 | typedef struct MSH3_CREDENTIAL_CONFIG { 89 | MSH3_CREDENTIAL_TYPE Type; 90 | MSH3_CREDENTIAL_FLAGS Flags; 91 | union { 92 | MSH3_CERTIFICATE_HASH* CertificateHash; 93 | MSH3_CERTIFICATE_HASH_STORE* CertificateHashStore; 94 | MSH3_CERTIFICATE_CONTEXT* CertificateContext; 95 | MSH3_CERTIFICATE_FILE* CertificateFile; 96 | MSH3_CERTIFICATE_FILE_PROTECTED* CertificateFileProtected; 97 | MSH3_CERTIFICATE_PKCS12* CertificatePkcs12; 98 | }; 99 | } MSH3_CREDENTIAL_CONFIG; 100 | ``` 101 | 102 | The `MSH3_CREDENTIAL_CONFIG` structure is used to configure TLS credentials. 103 | 104 | - `Type`: The type of credential to use. 105 | - `Flags`: Flags to control credential behavior. 106 | - Union containing a pointer to the specific credential type based on `Type`. 107 | 108 | ### Credential Types 109 | 110 | ```c 111 | typedef enum MSH3_CREDENTIAL_TYPE { 112 | MSH3_CREDENTIAL_TYPE_NONE, 113 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_HASH, 114 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_HASH_STORE, 115 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_CONTEXT, 116 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE, 117 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE_PROTECTED, 118 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_PKCS12, 119 | #ifdef MSH3_TEST_MODE 120 | MSH3_CREDENTIAL_TYPE_SELF_SIGNED_CERTIFICATE, 121 | #endif // MSH3_TEST_MODE 122 | } MSH3_CREDENTIAL_TYPE; 123 | ``` 124 | 125 | ### Credential Flags 126 | 127 | ```c 128 | typedef enum MSH3_CREDENTIAL_FLAGS { 129 | MSH3_CREDENTIAL_FLAG_NONE = 0x00000000, 130 | MSH3_CREDENTIAL_FLAG_CLIENT = 0x00000001, // Lack of client flag indicates server. 131 | MSH3_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION = 0x00000002, 132 | MSH3_CREDENTIAL_FLAG_REQUIRE_CLIENT_AUTHENTICATION = 0x00000004, 133 | } MSH3_CREDENTIAL_FLAGS; 134 | ``` 135 | 136 | ## Event Structures 137 | 138 | ### MSH3_CONNECTION_EVENT 139 | 140 | ```c 141 | typedef struct MSH3_CONNECTION_EVENT { 142 | MSH3_CONNECTION_EVENT_TYPE Type; 143 | union { 144 | struct { 145 | MSH3_STATUS Status; 146 | uint64_t ErrorCode; // Wire format error code. 147 | } SHUTDOWN_INITIATED_BY_TRANSPORT; 148 | struct { 149 | uint64_t ErrorCode; 150 | } SHUTDOWN_INITIATED_BY_PEER; 151 | struct { 152 | bool HandshakeCompleted : 1; 153 | bool PeerAcknowledgedShutdown : 1; 154 | bool AppCloseInProgress : 1; 155 | } SHUTDOWN_COMPLETE; 156 | struct { 157 | MSH3_REQUEST* Request; 158 | } NEW_REQUEST; 159 | }; 160 | } MSH3_CONNECTION_EVENT; 161 | ``` 162 | 163 | Connection event types: 164 | 165 | ```c 166 | typedef enum MSH3_CONNECTION_EVENT_TYPE { 167 | MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE = 0, // Ready for the handle to be closed. 168 | MSH3_CONNECTION_EVENT_CONNECTED = 1, 169 | MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT = 2, // The transport started the shutdown process. 170 | MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER = 3, // The peer application started the shutdown process. 171 | MSH3_CONNECTION_EVENT_NEW_REQUEST = 4, 172 | } MSH3_CONNECTION_EVENT_TYPE; 173 | ``` 174 | 175 | ### MSH3_REQUEST_EVENT 176 | 177 | ```c 178 | typedef struct MSH3_REQUEST_EVENT { 179 | MSH3_REQUEST_EVENT_TYPE Type; 180 | union { 181 | struct { 182 | bool ConnectionShutdown; 183 | bool AppCloseInProgress : 1; 184 | bool ConnectionShutdownByApp : 1; 185 | bool ConnectionClosedRemotely : 1; 186 | bool RESERVED : 1; 187 | bool RESERVED_2 : 1; 188 | bool RESERVED_3 : 1; 189 | bool RESERVED_4 : 1; 190 | bool RESERVED_5 : 1; 191 | uint64_t ConnectionErrorCode; 192 | MSH3_STATUS ConnectionCloseStatus; 193 | } SHUTDOWN_COMPLETE; 194 | struct { 195 | const MSH3_HEADER* Header; 196 | } HEADER_RECEIVED; 197 | struct { 198 | uint32_t Length; 199 | const uint8_t* Data; 200 | } DATA_RECEIVED; 201 | struct { 202 | uint64_t ErrorCode; 203 | } PEER_SEND_ABORTED; 204 | struct { 205 | uint64_t ByteCount; 206 | } IDEAL_SEND_SIZE; 207 | struct { 208 | bool Canceled; 209 | void* ClientContext; 210 | } SEND_COMPLETE; 211 | struct { 212 | bool Graceful; 213 | } SEND_SHUTDOWN_COMPLETE; 214 | struct { 215 | uint64_t ErrorCode; 216 | } PEER_RECEIVE_ABORTED; 217 | }; 218 | } MSH3_REQUEST_EVENT; 219 | ``` 220 | 221 | Request event types: 222 | 223 | ```c 224 | typedef enum MSH3_REQUEST_EVENT_TYPE { 225 | MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE = 0, // Ready for the handle to be closed. 226 | MSH3_REQUEST_EVENT_HEADER_RECEIVED = 1, 227 | MSH3_REQUEST_EVENT_DATA_RECEIVED = 2, 228 | MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN = 3, 229 | MSH3_REQUEST_EVENT_PEER_SEND_ABORTED = 4, 230 | MSH3_REQUEST_EVENT_IDEAL_SEND_SIZE = 5, 231 | MSH3_REQUEST_EVENT_SEND_COMPLETE = 6, 232 | MSH3_REQUEST_EVENT_SEND_SHUTDOWN_COMPLETE = 7, 233 | MSH3_REQUEST_EVENT_PEER_RECEIVE_ABORTED = 8, 234 | } MSH3_REQUEST_EVENT_TYPE; 235 | ``` 236 | 237 | ### MSH3_LISTENER_EVENT 238 | 239 | ```c 240 | typedef struct MSH3_LISTENER_EVENT { 241 | MSH3_LISTENER_EVENT_TYPE Type; 242 | union { 243 | struct { 244 | bool AppCloseInProgress : 1; 245 | bool RESERVED : 1; 246 | bool RESERVED_2 : 1; 247 | bool RESERVED_3 : 1; 248 | bool RESERVED_4 : 1; 249 | bool RESERVED_5 : 1; 250 | bool RESERVED_6 : 1; 251 | bool RESERVED_7 : 1; 252 | } SHUTDOWN_COMPLETE; 253 | struct { 254 | MSH3_CONNECTION* Connection; 255 | const char* ServerName; 256 | uint16_t ServerNameLength; 257 | } NEW_CONNECTION; 258 | }; 259 | } MSH3_LISTENER_EVENT; 260 | ``` 261 | 262 | Listener event types: 263 | 264 | ```c 265 | typedef enum MSH3_LISTENER_EVENT_TYPE { 266 | MSH3_LISTENER_EVENT_SHUTDOWN_COMPLETE = 0, // Ready for the handle to be closed. 267 | MSH3_LISTENER_EVENT_NEW_CONNECTION = 1, 268 | } MSH3_LISTENER_EVENT_TYPE; 269 | ``` 270 | 271 | ## Flag Enumerations 272 | 273 | ### MSH3_REQUEST_FLAGS 274 | 275 | ```c 276 | typedef enum MSH3_REQUEST_FLAGS { 277 | MSH3_REQUEST_FLAG_NONE = 0x0000, 278 | MSH3_REQUEST_FLAG_ALLOW_0_RTT = 0x0001, // Allows the use of encrypting with 0-RTT key. 279 | } MSH3_REQUEST_FLAGS; 280 | ``` 281 | 282 | ### MSH3_REQUEST_SEND_FLAGS 283 | 284 | ```c 285 | typedef enum MSH3_REQUEST_SEND_FLAGS { 286 | MSH3_REQUEST_SEND_FLAG_NONE = 0x0000, 287 | MSH3_REQUEST_SEND_FLAG_ALLOW_0_RTT = 0x0001, // Allows the use of encrypting with 0-RTT key. 288 | MSH3_REQUEST_SEND_FLAG_FIN = 0x0002, // Indicates the request should be gracefully shutdown too. 289 | MSH3_REQUEST_SEND_FLAG_DELAY_SEND = 0x0004, // Indicates the send should be delayed because more will be queued soon. 290 | } MSH3_REQUEST_SEND_FLAGS; 291 | ``` 292 | 293 | ### MSH3_REQUEST_SHUTDOWN_FLAGS 294 | 295 | ```c 296 | typedef enum MSH3_REQUEST_SHUTDOWN_FLAGS { 297 | MSH3_REQUEST_SHUTDOWN_FLAG_NONE = 0x0000, 298 | MSH3_REQUEST_SHUTDOWN_FLAG_GRACEFUL = 0x0001, // Cleanly closes the send path. 299 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT_SEND = 0x0002, // Abruptly closes the send path. 300 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT_RECEIVE = 0x0004, // Abruptly closes the receive path. 301 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT = 0x0006, // Abruptly closes both send and receive paths. 302 | } MSH3_REQUEST_SHUTDOWN_FLAGS; 303 | ``` 304 | -------------------------------------------------------------------------------- /docs/api/global.md: -------------------------------------------------------------------------------- 1 | # Global API 2 | 3 | The MSH3 global API provides functions for initializing and cleaning up the MSH3 library. 4 | 5 | ## MsH3Version 6 | 7 | ```c 8 | void 9 | MSH3_CALL 10 | MsH3Version( 11 | uint32_t Version[4] 12 | ); 13 | ``` 14 | 15 | Gets the MSH3 library version. 16 | 17 | ### Parameters 18 | 19 | `Version` - An array of 4 uint32_t values to receive the version information. 20 | 21 | ### Remarks 22 | 23 | The version array is filled with major, minor, patch, and build numbers. 24 | 25 | ### Example 26 | 27 | ```c 28 | uint32_t version[4]; 29 | MsH3Version(version); 30 | printf("MSH3 version: %u.%u.%u.%u\n", version[0], version[1], version[2], version[3]); 31 | ``` 32 | 33 | ## MsH3ApiOpen 34 | 35 | ```c 36 | MSH3_API* 37 | MSH3_CALL 38 | MsH3ApiOpen( 39 | void 40 | ); 41 | ``` 42 | 43 | Creates and initializes a new API object. 44 | 45 | ### Returns 46 | 47 | Returns a pointer to the newly created API object, or NULL if the operation fails. 48 | 49 | ### Remarks 50 | 51 | This function must be called before using any other MSH3 functions. The returned handle is used by other API calls. 52 | 53 | ### Example 54 | 55 | ```c 56 | MSH3_API* api = MsH3ApiOpen(); 57 | if (api == NULL) { 58 | printf("Failed to initialize MSH3 API\n"); 59 | return 1; 60 | } 61 | ``` 62 | 63 | ## MsH3ApiClose 64 | 65 | ```c 66 | void 67 | MSH3_CALL 68 | MsH3ApiClose( 69 | MSH3_API* Api 70 | ); 71 | ``` 72 | 73 | Cleans up and closes an API object. 74 | 75 | ### Parameters 76 | 77 | `Api` - The API object to close. 78 | 79 | ### Remarks 80 | 81 | This function should be called when you're done using MSH3 to clean up resources. After calling this function, the API handle is no longer valid. 82 | 83 | ### Example 84 | 85 | ```c 86 | MSH3_API* api = MsH3ApiOpen(); 87 | // Use MSH3 API... 88 | MsH3ApiClose(api); 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/api/listener.md: -------------------------------------------------------------------------------- 1 | # Listener API 2 | 3 | The Listener API provides functions for creating and managing HTTP/3 listeners for server applications. 4 | 5 | ## MsH3ListenerOpen 6 | 7 | ```c 8 | MSH3_LISTENER* 9 | MSH3_CALL 10 | MsH3ListenerOpen( 11 | MSH3_API* Api, 12 | const MSH3_ADDR* Address, 13 | const MSH3_LISTENER_CALLBACK_HANDLER Handler, 14 | void* Context 15 | ); 16 | ``` 17 | 18 | Creates a new HTTP/3 listener object. 19 | 20 | ### Parameters 21 | 22 | `Api` - The API object. 23 | 24 | `Address` - The local address and port to bind to. 25 | 26 | `Handler` - A callback function that will be invoked when listener events occur. 27 | 28 | `Context` - A user-provided context pointer that will be passed to the callback function. 29 | 30 | ### Returns 31 | 32 | Returns a pointer to the new listener object, or NULL if the operation fails. 33 | 34 | ### Remarks 35 | 36 | This function creates a listener for incoming HTTP/3 connections. The listener will immediately begin accepting connections after creation. 37 | 38 | When a new connection is accepted, the listener callback will be invoked with a MSH3_LISTENER_EVENT_NEW_CONNECTION event. 39 | 40 | ### Example 41 | 42 | ```c 43 | MSH3_STATUS 44 | ListenerCallback( 45 | MSH3_LISTENER* Listener, 46 | void* Context, 47 | MSH3_LISTENER_EVENT* Event 48 | ) 49 | { 50 | switch (Event->Type) { 51 | case MSH3_LISTENER_EVENT_NEW_CONNECTION: 52 | printf("New connection from %s\n", Event->NEW_CONNECTION.ServerName); 53 | 54 | // Set up the connection callback 55 | MsH3ConnectionSetCallbackHandler( 56 | Event->NEW_CONNECTION.Connection, 57 | ConnectionCallback, 58 | myContext); 59 | 60 | // The connection is now handed over to the application 61 | 62 | break; 63 | case MSH3_LISTENER_EVENT_SHUTDOWN_COMPLETE: 64 | printf("Listener shutdown complete\n"); 65 | break; 66 | } 67 | return MSH3_STATUS_SUCCESS; 68 | } 69 | 70 | // Set up the local address 71 | MSH3_ADDR localAddr = {0}; 72 | localAddr.Ipv4.sin_family = AF_INET; 73 | localAddr.Ipv4.sin_addr.s_addr = INADDR_ANY; 74 | MSH3_SET_PORT(&localAddr, 443); 75 | 76 | MSH3_LISTENER* listener = 77 | MsH3ListenerOpen(api, &localAddr, ListenerCallback, myContext); 78 | if (listener == NULL) { 79 | printf("Failed to create listener\n"); 80 | return 1; 81 | } 82 | ``` 83 | 84 | ## MsH3ListenerClose 85 | 86 | ```c 87 | void 88 | MSH3_CALL 89 | MsH3ListenerClose( 90 | MSH3_LISTENER* Listener 91 | ); 92 | ``` 93 | 94 | Closes a listener and releases associated resources. 95 | 96 | ### Parameters 97 | 98 | `Listener` - The listener object to close. 99 | 100 | ### Remarks 101 | 102 | This function should be called when the listener is no longer needed. After calling this function, the listener handle is no longer valid. 103 | 104 | Closing a listener does not affect any connections that were previously accepted by the listener. 105 | 106 | ### Example 107 | 108 | ```c 109 | // Close the listener 110 | MsH3ListenerClose(listener); 111 | ``` 112 | -------------------------------------------------------------------------------- /docs/api/request.md: -------------------------------------------------------------------------------- 1 | # Request API 2 | 3 | The Request API provides functions for creating and managing HTTP/3 requests. 4 | 5 | ## MsH3RequestOpen 6 | 7 | ```c 8 | MSH3_REQUEST* 9 | MSH3_CALL 10 | MsH3RequestOpen( 11 | MSH3_CONNECTION* Connection, 12 | const MSH3_REQUEST_CALLBACK_HANDLER Handler, 13 | void* Context, 14 | MSH3_REQUEST_FLAGS Flags 15 | ); 16 | ``` 17 | 18 | Creates a new HTTP/3 request object. 19 | 20 | ### Parameters 21 | 22 | `Connection` - The connection object. 23 | 24 | `Handler` - A callback function that will be invoked when request events occur. 25 | 26 | `Context` - A user-provided context pointer that will be passed to the callback function. 27 | 28 | `Flags` - Flags to control request behavior. 29 | 30 | ### Returns 31 | 32 | Returns a pointer to the new request object, or NULL if the operation fails. 33 | 34 | ### Remarks 35 | 36 | This function creates a request object. To send headers and data on the request, use MsH3RequestSend. 37 | 38 | ### Example 39 | 40 | ```c 41 | MSH3_STATUS 42 | RequestCallback( 43 | MSH3_REQUEST* Request, 44 | void* Context, 45 | MSH3_REQUEST_EVENT* Event 46 | ) 47 | { 48 | switch (Event->Type) { 49 | case MSH3_REQUEST_EVENT_HEADER_RECEIVED: 50 | printf("Header received: %.*s: %.*s\n", 51 | (int)Event->HEADER_RECEIVED.Header->NameLength, 52 | Event->HEADER_RECEIVED.Header->Name, 53 | (int)Event->HEADER_RECEIVED.Header->ValueLength, 54 | Event->HEADER_RECEIVED.Header->Value); 55 | break; 56 | case MSH3_REQUEST_EVENT_DATA_RECEIVED: 57 | printf("Data received: %u bytes\n", Event->DATA_RECEIVED.Length); 58 | // Process data... 59 | break; 60 | // Handle other events... 61 | } 62 | return MSH3_STATUS_SUCCESS; 63 | } 64 | 65 | MSH3_REQUEST* request = 66 | MsH3RequestOpen(connection, RequestCallback, myContext, 67 | MSH3_REQUEST_FLAG_NONE); 68 | if (request == NULL) { 69 | printf("Failed to create request\n"); 70 | return 1; 71 | } 72 | ``` 73 | 74 | ## MsH3RequestSetCallbackHandler 75 | 76 | ```c 77 | void 78 | MSH3_CALL 79 | MsH3RequestSetCallbackHandler( 80 | MSH3_REQUEST* Request, 81 | const MSH3_REQUEST_CALLBACK_HANDLER Handler, 82 | void* Context 83 | ); 84 | ``` 85 | 86 | Sets or updates the callback handler for a request. 87 | 88 | ### Parameters 89 | 90 | `Request` - The request object. 91 | 92 | `Handler` - The new callback handler. 93 | 94 | `Context` - The new context pointer to be passed to the callback. 95 | 96 | ### Remarks 97 | 98 | This function can be used to change the callback handler after a request has been created. 99 | 100 | ### Example 101 | 102 | ```c 103 | // Update the callback handler 104 | MsH3RequestSetCallbackHandler(request, NewCallback, newContext); 105 | ``` 106 | 107 | ## MsH3RequestSend 108 | 109 | ```c 110 | bool 111 | MSH3_CALL 112 | MsH3RequestSend( 113 | MSH3_REQUEST* Request, 114 | MSH3_REQUEST_SEND_FLAGS Flags, 115 | const MSH3_HEADER* Headers, 116 | size_t HeadersCount, 117 | const void* Data, 118 | uint32_t DataLength, 119 | void* AppContext 120 | ); 121 | ``` 122 | 123 | Sends headers and optional data on a request. 124 | 125 | ### Parameters 126 | 127 | `Request` - The request object. 128 | 129 | `Flags` - Flags to control the send operation. 130 | 131 | `Headers` - An array of HTTP headers to send. 132 | 133 | `HeadersCount` - The number of headers in the array. 134 | 135 | `Data` - Optional data to send after the headers. 136 | 137 | `DataLength` - The length of the data. 138 | 139 | `AppContext` - An application context pointer that will be returned in the SEND_COMPLETE event. 140 | 141 | ### Returns 142 | 143 | Returns true if the send operation was successfully queued, false otherwise. 144 | 145 | ### Remarks 146 | 147 | This function sends headers and optional data on a request. The headers must include the required HTTP/3 pseudo-headers (`:method`, `:path`, `:scheme`, `:authority`). 148 | 149 | If the MSH3_REQUEST_SEND_FLAG_FIN flag is specified, the request is marked as complete and no more data can be sent. 150 | 151 | ### Example 152 | 153 | ```c 154 | const MSH3_HEADER headers[] = { 155 | { ":method", 7, "GET", 3 }, 156 | { ":path", 5, "/index.html", 11 }, 157 | { ":scheme", 7, "https", 5 }, 158 | { ":authority", 10, "example.com", 11 }, 159 | { "user-agent", 10, "msh3-client", 11 } 160 | }; 161 | const size_t headersCount = sizeof(headers) / sizeof(MSH3_HEADER); 162 | 163 | bool success = MsH3RequestSend( 164 | request, 165 | MSH3_REQUEST_SEND_FLAG_FIN, // No more data to send 166 | headers, 167 | headersCount, 168 | NULL, // No body data 169 | 0, 170 | NULL); 171 | 172 | if (!success) { 173 | printf("Failed to send request\n"); 174 | return 1; 175 | } 176 | ``` 177 | 178 | ## MsH3RequestSetReceiveEnabled 179 | 180 | ```c 181 | void 182 | MSH3_CALL 183 | MsH3RequestSetReceiveEnabled( 184 | MSH3_REQUEST* Request, 185 | bool Enabled 186 | ); 187 | ``` 188 | 189 | Enables or disables receive operations on a request. 190 | 191 | ### Parameters 192 | 193 | `Request` - The request object. 194 | 195 | `Enabled` - Whether to enable or disable receiving. 196 | 197 | ### Remarks 198 | 199 | This function can be used to implement flow control. When receive is disabled, the peer will stop sending data once its flow control window is exhausted. 200 | 201 | ### Example 202 | 203 | ```c 204 | // Temporarily disable receiving to handle backpressure 205 | MsH3RequestSetReceiveEnabled(request, false); 206 | 207 | // Later, when ready to receive more data: 208 | MsH3RequestSetReceiveEnabled(request, true); 209 | ``` 210 | 211 | ## MsH3RequestCompleteReceive 212 | 213 | ```c 214 | void 215 | MSH3_CALL 216 | MsH3RequestCompleteReceive( 217 | MSH3_REQUEST* Request, 218 | uint32_t Length 219 | ); 220 | ``` 221 | 222 | Completes a receive operation. 223 | 224 | ### Parameters 225 | 226 | `Request` - The request object. 227 | 228 | `Length` - The number of bytes consumed. 229 | 230 | ### Remarks 231 | 232 | This function should be called after processing data received in a MSH3_REQUEST_EVENT_DATA_RECEIVED event. It updates the flow control window. 233 | 234 | ### Example 235 | 236 | ```c 237 | MSH3_STATUS 238 | RequestCallback( 239 | MSH3_REQUEST* Request, 240 | void* Context, 241 | MSH3_REQUEST_EVENT* Event 242 | ) 243 | { 244 | switch (Event->Type) { 245 | case MSH3_REQUEST_EVENT_DATA_RECEIVED: 246 | // Process data... 247 | 248 | // Indicate that we've consumed the data 249 | MsH3RequestCompleteReceive(Request, Event->DATA_RECEIVED.Length); 250 | break; 251 | // Handle other events... 252 | } 253 | return MSH3_STATUS_SUCCESS; 254 | } 255 | ``` 256 | 257 | ## MsH3RequestShutdown 258 | 259 | ```c 260 | void 261 | MSH3_CALL 262 | MsH3RequestShutdown( 263 | MSH3_REQUEST* Request, 264 | MSH3_REQUEST_SHUTDOWN_FLAGS Flags, 265 | uint64_t AbortError 266 | ); 267 | ``` 268 | 269 | Initiates shutdown of a request. 270 | 271 | ### Parameters 272 | 273 | `Request` - The request object. 274 | 275 | `Flags` - Flags indicating how to shutdown the request. 276 | 277 | `AbortError` - An error code to send to the peer, used only with abort flags. 278 | 279 | ### Remarks 280 | 281 | This function initiates the shutdown process for a request. The request is not fully closed until the MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE event is received by the callback. 282 | 283 | ### Example 284 | 285 | ```c 286 | // Gracefully shutdown the request 287 | MsH3RequestShutdown( 288 | request, 289 | MSH3_REQUEST_SHUTDOWN_FLAG_GRACEFUL, 290 | 0); // Ignored for graceful shutdown 291 | 292 | // Abortively shutdown the request 293 | MsH3RequestShutdown( 294 | request, 295 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT, 296 | 0x10); // Application-defined error code 297 | ``` 298 | 299 | ## MsH3RequestClose 300 | 301 | ```c 302 | void 303 | MSH3_CALL 304 | MsH3RequestClose( 305 | MSH3_REQUEST* Request 306 | ); 307 | ``` 308 | 309 | Closes a request and releases associated resources. 310 | 311 | ### Parameters 312 | 313 | `Request` - The request object to close. 314 | 315 | ### Remarks 316 | 317 | This function should be called when the request is no longer needed. After calling this function, the request handle is no longer valid. 318 | 319 | ### Example 320 | 321 | ```c 322 | // Close the request 323 | MsH3RequestClose(request); 324 | ``` 325 | -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | # Building MSH3 2 | 3 | This guide explains how to build MSH3 from source on different platforms. 4 | 5 | ## Prerequisites 6 | 7 | - CMake 3.16 or higher 8 | - A C++ compiler with C++14 support 9 | - Git (for cloning the repository and its dependencies) 10 | 11 | ## Getting the Source Code 12 | 13 | ```bash 14 | git clone --recursive https://github.com/nibanks/msh3 15 | cd msh3 16 | ``` 17 | 18 | If you've already cloned the repository without the `--recursive` flag, you can fetch the submodules separately: 19 | 20 | ```bash 21 | git submodule update --init --recursive 22 | ``` 23 | 24 | ## Building on Linux 25 | 26 | ```bash 27 | mkdir build && cd build 28 | cmake -G 'Unix Makefiles' .. 29 | cmake --build . 30 | ``` 31 | 32 | To build with specific compiler flags: 33 | 34 | ```bash 35 | cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O3" -DCMAKE_CXX_FLAGS="-O3" .. 36 | cmake --build . 37 | ``` 38 | 39 | ## Building on Windows 40 | 41 | Using Visual Studio: 42 | 43 | ```bash 44 | mkdir build && cd build 45 | cmake -G 'Visual Studio 17 2022' -A x64 .. 46 | cmake --build . 47 | ``` 48 | 49 | Using Ninja: 50 | 51 | ```bash 52 | mkdir build && cd build 53 | cmake -G 'Ninja' .. 54 | cmake --build . 55 | ``` 56 | 57 | ## Building on macOS 58 | 59 | ```bash 60 | mkdir build && cd build 61 | cmake -G 'Unix Makefiles' .. 62 | cmake --build . 63 | ``` 64 | 65 | ## Install 66 | 67 | To install MSH3 on your system: 68 | 69 | ```bash 70 | cmake --build . --target install 71 | ``` 72 | 73 | You might need elevated privileges (sudo on Linux/macOS, or Administrator on Windows) to install to system directories. 74 | 75 | ## Using MSH3 in Your Project 76 | 77 | ### With CMake 78 | 79 | ```cmake 80 | find_package(msh3 REQUIRED) 81 | target_link_libraries(your_target_name PRIVATE msh3::msh3) 82 | ``` 83 | 84 | ### With pkg-config 85 | 86 | ```bash 87 | pkg-config --cflags --libs libmsh3 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/client-usage.md: -------------------------------------------------------------------------------- 1 | # Using MSH3 as a Client 2 | 3 | This guide explains how to use MSH3 as an HTTP/3 client. 4 | 5 | ## Basic Client Usage 6 | 7 | Here's a step-by-step guide to using MSH3 as an HTTP/3 client: 8 | 9 | 1. Initialize the MSH3 API 10 | 2. Create a configuration with appropriate settings 11 | 3. Create a connection to the server 12 | 4. Create a request and send headers and data 13 | 5. Process response headers and data in the callbacks 14 | 6. Clean up resources 15 | 16 | ## Example 17 | 18 | ```c 19 | #include "msh3.h" 20 | #include 21 | #include 22 | #include 23 | 24 | // Callback for request events 25 | MSH3_STATUS 26 | RequestCallback( 27 | MSH3_REQUEST* Request, 28 | void* Context, 29 | MSH3_REQUEST_EVENT* Event 30 | ) 31 | { 32 | switch (Event->Type) { 33 | case MSH3_REQUEST_EVENT_HEADER_RECEIVED: 34 | printf("Header: %.*s: %.*s\n", 35 | (int)Event->HEADER_RECEIVED.Header->NameLength, 36 | Event->HEADER_RECEIVED.Header->Name, 37 | (int)Event->HEADER_RECEIVED.Header->ValueLength, 38 | Event->HEADER_RECEIVED.Header->Value); 39 | break; 40 | 41 | case MSH3_REQUEST_EVENT_DATA_RECEIVED: 42 | printf("Received %u bytes of data\n", Event->DATA_RECEIVED.Length); 43 | printf("%.*s", (int)Event->DATA_RECEIVED.Length, 44 | (const char*)Event->DATA_RECEIVED.Data); 45 | 46 | // Indicate that we've processed the data 47 | MsH3RequestCompleteReceive(Request, Event->DATA_RECEIVED.Length); 48 | break; 49 | 50 | case MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN: 51 | printf("Server completed sending data\n"); 52 | break; 53 | 54 | case MSH3_REQUEST_EVENT_SEND_COMPLETE: 55 | printf("Request sent completely\n"); 56 | break; 57 | 58 | case MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE: 59 | printf("Request shutdown complete\n"); 60 | break; 61 | } 62 | 63 | return MSH3_STATUS_SUCCESS; 64 | } 65 | 66 | // Callback for connection events 67 | MSH3_STATUS 68 | ConnectionCallback( 69 | MSH3_CONNECTION* Connection, 70 | void* Context, 71 | MSH3_CONNECTION_EVENT* Event 72 | ) 73 | { 74 | switch (Event->Type) { 75 | case MSH3_CONNECTION_EVENT_CONNECTED: 76 | printf("Connection established\n"); 77 | 78 | // Create a request when connected 79 | const char* host = (const char*)Context; 80 | MSH3_REQUEST* Request = MsH3RequestOpen( 81 | Connection, 82 | RequestCallback, 83 | NULL, 84 | MSH3_REQUEST_FLAG_NONE); 85 | 86 | if (Request) { 87 | // Setup HTTP headers 88 | const MSH3_HEADER Headers[] = { 89 | { ":method", 7, "GET", 3 }, 90 | { ":path", 5, "/", 1 }, 91 | { ":scheme", 7, "https", 5 }, 92 | { ":authority", 10, host, strlen(host) }, 93 | { "user-agent", 10, "msh3-client", 11 } 94 | }; 95 | const size_t HeadersCount = sizeof(Headers) / sizeof(MSH3_HEADER); 96 | 97 | // Send the request 98 | MsH3RequestSend( 99 | Request, 100 | MSH3_REQUEST_SEND_FLAG_FIN, // No more data to send 101 | Headers, 102 | HeadersCount, 103 | NULL, // No body data 104 | 0, 105 | NULL); 106 | } else { 107 | printf("Failed to create request\n"); 108 | } 109 | break; 110 | 111 | case MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: 112 | printf("Connection shutdown by transport, status=0x%x, error=0x%llx\n", 113 | Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status, 114 | (unsigned long long)Event->SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode); 115 | break; 116 | 117 | case MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: 118 | printf("Connection shutdown by peer, error=0x%llx\n", 119 | (unsigned long long)Event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode); 120 | break; 121 | 122 | case MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE: 123 | printf("Connection shutdown complete\n"); 124 | break; 125 | } 126 | 127 | return MSH3_STATUS_SUCCESS; 128 | } 129 | 130 | int main(int argc, char** argv) { 131 | if (argc < 2) { 132 | printf("Usage: %s [port]\n", argv[0]); 133 | return 1; 134 | } 135 | 136 | const char* hostname = argv[1]; 137 | uint16_t port = (argc > 2) ? (uint16_t)atoi(argv[2]) : 443; 138 | 139 | // Initialize MSH3 API 140 | MSH3_API* api = MsH3ApiOpen(); 141 | if (!api) { 142 | printf("Failed to initialize MSH3 API\n"); 143 | return 1; 144 | } 145 | 146 | // Create configuration 147 | MSH3_SETTINGS settings = {0}; 148 | settings.IsSetFlags = 0; 149 | settings.IsSet.IdleTimeoutMs = 1; 150 | settings.IdleTimeoutMs = 30000; // 30 seconds 151 | 152 | MSH3_CONFIGURATION* config = 153 | MsH3ConfigurationOpen(api, &settings, sizeof(settings)); 154 | if (!config) { 155 | printf("Failed to create configuration\n"); 156 | MsH3ApiClose(api); 157 | return 1; 158 | } 159 | 160 | // Create connection 161 | MSH3_CONNECTION* connection = 162 | MsH3ConnectionOpen(api, ConnectionCallback, (void*)hostname); 163 | if (!connection) { 164 | printf("Failed to create connection\n"); 165 | MsH3ConfigurationClose(config); 166 | MsH3ApiClose(api); 167 | return 1; 168 | } 169 | 170 | // Configure the connection 171 | MSH3_STATUS status = MsH3ConnectionSetConfiguration(connection, config); 172 | if (MSH3_FAILED(status)) { 173 | printf("Failed to set connection configuration, status=0x%x\n", status); 174 | MsH3ConnectionClose(connection); 175 | MsH3ConfigurationClose(config); 176 | MsH3ApiClose(api); 177 | return 1; 178 | } 179 | 180 | // Set up the server address 181 | MSH3_ADDR serverAddr = {0}; 182 | serverAddr.Ipv4.sin_family = AF_INET; 183 | // Resolve hostname and set IP address (simplified here) 184 | inet_pton(AF_INET, "198.51.100.1", &serverAddr.Ipv4.sin_addr); // Example IP 185 | MSH3_SET_PORT(&serverAddr, port); 186 | 187 | // Start the connection 188 | status = MsH3ConnectionStart(connection, config, hostname, &serverAddr); 189 | if (MSH3_FAILED(status)) { 190 | printf("Failed to start connection, status=0x%x\n", status); 191 | MsH3ConnectionClose(connection); 192 | MsH3ConfigurationClose(config); 193 | MsH3ApiClose(api); 194 | return 1; 195 | } 196 | 197 | // In a real application, you'd wait for events using an event loop 198 | // For this example, we'll just wait for user input before cleaning up 199 | printf("Press Enter to terminate...\n"); 200 | getchar(); 201 | 202 | // Clean up 203 | MsH3ConnectionClose(connection); 204 | MsH3ConfigurationClose(config); 205 | MsH3ApiClose(api); 206 | 207 | return 0; 208 | } 209 | ``` 210 | 211 | ## Advanced Features 212 | 213 | ### Handling Credentials 214 | 215 | ```c 216 | // Set up client authentication with a certificate 217 | MSH3_CERTIFICATE_FILE certFile = { 218 | .PrivateKeyFile = "client.key", 219 | .CertificateFile = "client.crt" 220 | }; 221 | 222 | MSH3_CREDENTIAL_CONFIG credConfig = { 223 | .Type = MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE, 224 | .Flags = MSH3_CREDENTIAL_FLAG_CLIENT, 225 | .CertificateFile = &certFile 226 | }; 227 | 228 | status = MsH3ConfigurationLoadCredential(config, &credConfig); 229 | if (MSH3_FAILED(status)) { 230 | printf("Failed to load credentials, status=0x%x\n", status); 231 | return 1; 232 | } 233 | ``` 234 | 235 | ### Sending POST Request with Data 236 | 237 | ```c 238 | const MSH3_HEADER Headers[] = { 239 | { ":method", 7, "POST", 4 }, 240 | { ":path", 5, "/api/data", 9 }, 241 | { ":scheme", 7, "https", 5 }, 242 | { ":authority", 10, host, strlen(host) }, 243 | { "content-type", 12, "application/json", 16 } 244 | }; 245 | const size_t HeadersCount = sizeof(Headers) / sizeof(MSH3_HEADER); 246 | 247 | const char* body = "{\"key\":\"value\"}"; 248 | uint32_t bodyLength = (uint32_t)strlen(body); 249 | 250 | // Send request with body 251 | MsH3RequestSend( 252 | Request, 253 | MSH3_REQUEST_SEND_FLAG_FIN, // No more data to send after this 254 | Headers, 255 | HeadersCount, 256 | body, // Request body 257 | bodyLength, 258 | NULL); 259 | ``` 260 | 261 | ### Sending Request in Multiple Parts 262 | 263 | ```c 264 | // Send headers first 265 | MsH3RequestSend( 266 | Request, 267 | MSH3_REQUEST_SEND_FLAG_NONE, // Not the end of the request 268 | Headers, 269 | HeadersCount, 270 | NULL, // No body data yet 271 | 0, 272 | NULL); 273 | 274 | // Then send the first part of the body 275 | const char* bodyPart1 = "First part of the data"; 276 | MsH3RequestSend( 277 | Request, 278 | MSH3_REQUEST_SEND_FLAG_DELAY_SEND, // More data coming 279 | NULL, // No headers this time 280 | 0, 281 | bodyPart1, 282 | (uint32_t)strlen(bodyPart1), 283 | NULL); 284 | 285 | // Send the last part of the body and finish the request 286 | const char* bodyPart2 = "Last part of the data"; 287 | MsH3RequestSend( 288 | Request, 289 | MSH3_REQUEST_SEND_FLAG_FIN, // End of request 290 | NULL, 291 | 0, 292 | bodyPart2, 293 | (uint32_t)strlen(bodyPart2), 294 | NULL); 295 | ``` 296 | -------------------------------------------------------------------------------- /docs/protocol-overview.md: -------------------------------------------------------------------------------- 1 | # HTTP/3 and QUIC Protocol Overview 2 | 3 | This document provides a high-level overview of the HTTP/3 and QUIC protocols that power the MSH3 library. 4 | 5 | ## QUIC Protocol (RFC 9000) 6 | 7 | QUIC is a modern, encrypted transport protocol designed for the modern internet. It was developed to address the limitations of TCP and TLS for web applications. 8 | 9 | ### Key Features of QUIC 10 | 11 | 1. **Multiplexing**: QUIC supports multiple simultaneous streams over a single connection, eliminating head-of-line blocking at the transport layer. 12 | 13 | 2. **Encryption by Default**: All QUIC packets, except the initial handshake packets, are encrypted. This provides privacy and security for all transported data. 14 | 15 | 3. **Connection Migration**: QUIC connections can survive network changes (like switching from Wi-Fi to cellular) without breaking. 16 | 17 | 4. **Reduced Connection Establishment Time**: QUIC combines the cryptographic and transport handshakes, reducing the round trips needed to establish a secure connection. 18 | 19 | 5. **Improved Congestion Control**: QUIC includes modern congestion control mechanisms and better loss recovery than TCP. 20 | 21 | 6. **Forward Error Correction**: QUIC can use forward error correction to recover from packet loss without waiting for retransmissions. 22 | 23 | 7. **Connection IDs**: QUIC uses connection IDs rather than IP address/port tuples to identify connections, enabling connection migration. 24 | 25 | ## HTTP/3 Protocol (RFC 9114) 26 | 27 | HTTP/3 is the latest version of the HTTP protocol, designed to use QUIC as its transport protocol. 28 | 29 | ### Key Features of HTTP/3 30 | 31 | 1. **Multiplexed Streams**: HTTP/3 requests and responses are sent over QUIC streams, eliminating head-of-line blocking issues present in HTTP/2. 32 | 33 | 2. **Header Compression**: HTTP/3 uses QPACK (RFC 9204) for header compression, which is designed to work efficiently with QUIC's out-of-order delivery. QPACK supports both static and dynamic table compression, allowing for efficient compression of repeated headers across requests. 34 | 35 | 3. **Server Push**: Like HTTP/2, HTTP/3 supports server push, allowing servers to proactively send resources to clients. 36 | 37 | 4. **Stream Priorities**: HTTP/3 includes a priority scheme for streams, allowing clients to specify which resources should be delivered first. 38 | 39 | 5. **Improved Performance**: By leveraging QUIC's features, HTTP/3 provides better performance especially on networks with high latency or packet loss. 40 | 41 | ### HTTP/3 Frame Types 42 | 43 | HTTP/3 defines several frame types for different purposes: 44 | 45 | - `DATA`: Contains the message body 46 | - `HEADERS`: Contains HTTP header fields 47 | - `CANCEL_PUSH`: Used to cancel server push 48 | - `SETTINGS`: Conveys configuration parameters 49 | - `PUSH_PROMISE`: Used for server push 50 | - `GOAWAY`: Indicates the end of a connection 51 | - `MAX_PUSH_ID`: Controls the number of server pushes 52 | - `RESERVED`: Reserved for future use 53 | 54 | ## QPACK Header Compression (RFC 9204) 55 | 56 | QPACK is the header compression format used in HTTP/3, designed to work well with QUIC's out-of-order delivery. 57 | 58 | ### Key Features of QPACK 59 | 60 | 1. **Static and Dynamic Tables**: QPACK uses both static and dynamic tables to achieve compression, similar to HPACK in HTTP/2. 61 | 62 | 2. **Encoder Stream**: The encoder stream is used to update the dynamic table at the decoder. 63 | 64 | 3. **Decoder Stream**: The decoder stream is used to acknowledge dynamic table updates and provide flow control. 65 | 66 | 4. **Blocking Avoidance**: QPACK is designed to minimize head-of-line blocking issues when dynamic table updates are in flight. 67 | 68 | ## How MSH3 Implements These Protocols 69 | 70 | MSH3 implements HTTP/3 on top of Microsoft's implementation of QUIC (MsQuic) and LiteSpeed's implementation of QPACK (ls-qpack). 71 | 72 | - **MsQuic**: Provides the QUIC transport functionality, including connection establishment, stream management, and encrypted transport. 73 | 74 | - **ls-qpack**: Provides the QPACK header compression and decompression functionality. 75 | 76 | - **MSH3**: Ties these components together to provide a complete HTTP/3 implementation, adding the HTTP/3 framing layer, request/response handling, and an API for applications to use. 77 | 78 | ## HTTP/3 Request and Response Flow 79 | 80 | 1. The client establishes a QUIC connection to the server. 81 | 82 | 2. The client creates a new bidirectional QUIC stream for each HTTP request. 83 | 84 | 3. The client sends HEADERS frames containing the request headers, and optionally DATA frames containing the request body. 85 | 86 | 4. The server processes the request and sends back HEADERS frames containing the response headers, and optionally DATA frames containing the response body. 87 | 88 | 5. The stream is closed when the response is complete. 89 | 90 | ## Performance Considerations 91 | 92 | - **0-RTT**: QUIC supports 0-RTT (zero round trip time) connection establishment, allowing clients to send data on the first packet of a connection if they've connected to the server before. 93 | 94 | - **Connection Coalescing**: Multiple HTTP/3 connections to the same server can be coalesced into a single QUIC connection, reducing overhead. 95 | 96 | - **Early Data**: QUIC supports sending application data during the handshake, reducing latency for certain types of requests. 97 | 98 | - **Flow Control**: QUIC and HTTP/3 include flow control mechanisms at both the connection and stream levels. 99 | 100 | ## Security Considerations 101 | 102 | - **TLS 1.3**: QUIC uses TLS 1.3 for encryption and authentication, providing modern cryptographic security. 103 | 104 | - **Always Encrypted**: All HTTP/3 traffic is encrypted by default. 105 | 106 | - **Authentication**: QUIC supports mutual authentication using certificates. 107 | 108 | - **Privacy**: QUIC's encrypted transport helps protect against traffic analysis and fingerprinting. 109 | 110 | ## References 111 | 112 | - [RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport](https://tools.ietf.org/html/rfc9000) 113 | - [RFC 9001: Using TLS to Secure QUIC](https://tools.ietf.org/html/rfc9001) 114 | - [RFC 9002: QUIC Loss Detection and Congestion Control](https://tools.ietf.org/html/rfc9002) 115 | - [RFC 9114: HTTP/3](https://tools.ietf.org/html/rfc9114) 116 | - [RFC 9204: QPACK: Field Compression for HTTP/3](https://tools.ietf.org/html/rfc9204) 117 | -------------------------------------------------------------------------------- /docs/server-usage.md: -------------------------------------------------------------------------------- 1 | # Using MSH3 as a Server 2 | 3 | This guide explains how to use MSH3 as an HTTP/3 server. 4 | 5 | ## Basic Server Usage 6 | 7 | Here's a step-by-step guide to using MSH3 as an HTTP/3 server: 8 | 9 | 1. Initialize the MSH3 API 10 | 2. Create a configuration with appropriate settings and credentials 11 | 3. Create a listener to accept incoming connections 12 | 4. Process incoming connections in the listener callback 13 | 5. Handle requests on each connection 14 | 6. Send responses to requests 15 | 7. Clean up resources 16 | 17 | ## Example 18 | 19 | ```c 20 | #include "msh3.h" 21 | #include 22 | #include 23 | #include 24 | 25 | // Callback for request events 26 | MSH3_STATUS 27 | RequestCallback( 28 | MSH3_REQUEST* Request, 29 | void* Context, 30 | MSH3_REQUEST_EVENT* Event 31 | ) 32 | { 33 | switch (Event->Type) { 34 | case MSH3_REQUEST_EVENT_HEADER_RECEIVED: 35 | printf("Header: %.*s: %.*s\n", 36 | (int)Event->HEADER_RECEIVED.Header->NameLength, 37 | Event->HEADER_RECEIVED.Header->Name, 38 | (int)Event->HEADER_RECEIVED.Header->ValueLength, 39 | Event->HEADER_RECEIVED.Header->Value); 40 | break; 41 | 42 | case MSH3_REQUEST_EVENT_DATA_RECEIVED: 43 | printf("Received %u bytes of data\n", Event->DATA_RECEIVED.Length); 44 | printf("%.*s", (int)Event->DATA_RECEIVED.Length, 45 | (const char*)Event->DATA_RECEIVED.Data); 46 | 47 | // Indicate that we've processed the data 48 | MsH3RequestCompleteReceive(Request, Event->DATA_RECEIVED.Length); 49 | break; 50 | 51 | case MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN: 52 | printf("Client completed sending data\n"); 53 | 54 | // Client has finished sending the request, send a response 55 | const MSH3_HEADER ResponseHeaders[] = { 56 | { ":status", 7, "200", 3 }, 57 | { "content-type", 12, "text/plain", 10 } 58 | }; 59 | const size_t ResponseHeadersCount = sizeof(ResponseHeaders) / sizeof(MSH3_HEADER); 60 | 61 | const char* responseBody = "Hello from MSH3 server!"; 62 | 63 | MsH3RequestSend( 64 | Request, 65 | MSH3_REQUEST_SEND_FLAG_FIN, 66 | ResponseHeaders, 67 | ResponseHeadersCount, 68 | responseBody, 69 | (uint32_t)strlen(responseBody), 70 | NULL); 71 | break; 72 | 73 | case MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE: 74 | printf("Request shutdown complete\n"); 75 | break; 76 | } 77 | 78 | return MSH3_STATUS_SUCCESS; 79 | } 80 | 81 | // Callback for connection events 82 | MSH3_STATUS 83 | ConnectionCallback( 84 | MSH3_CONNECTION* Connection, 85 | void* Context, 86 | MSH3_CONNECTION_EVENT* Event 87 | ) 88 | { 89 | switch (Event->Type) { 90 | case MSH3_CONNECTION_EVENT_NEW_REQUEST: 91 | printf("New request received\n"); 92 | 93 | // Set up the request callback 94 | MSH3_REQUEST* request = Event->NEW_REQUEST.Request; 95 | MsH3RequestSetCallbackHandler( 96 | request, 97 | RequestCallback, 98 | NULL); 99 | 100 | // Enable receiving data on the request 101 | MsH3RequestSetReceiveEnabled(request, true); 102 | break; 103 | 104 | case MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: 105 | printf("Connection shutdown by transport, status=0x%x, error=0x%llx\n", 106 | Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status, 107 | (unsigned long long)Event->SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode); 108 | break; 109 | 110 | case MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: 111 | printf("Connection shutdown by peer, error=0x%llx\n", 112 | (unsigned long long)Event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode); 113 | break; 114 | 115 | case MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE: 116 | printf("Connection shutdown complete\n"); 117 | break; 118 | } 119 | 120 | return MSH3_STATUS_SUCCESS; 121 | } 122 | 123 | // Callback for listener events 124 | MSH3_STATUS 125 | ListenerCallback( 126 | MSH3_LISTENER* Listener, 127 | void* Context, 128 | MSH3_LISTENER_EVENT* Event 129 | ) 130 | { 131 | MSH3_CONFIGURATION* config = (MSH3_CONFIGURATION*)Context; 132 | 133 | switch (Event->Type) { 134 | case MSH3_LISTENER_EVENT_NEW_CONNECTION: 135 | printf("New connection from %.*s\n", 136 | (int)Event->NEW_CONNECTION.ServerNameLength, 137 | Event->NEW_CONNECTION.ServerName); 138 | 139 | // Set up the connection callback 140 | MsH3ConnectionSetCallbackHandler( 141 | Event->NEW_CONNECTION.Connection, 142 | ConnectionCallback, 143 | NULL); 144 | 145 | // Apply configuration to the connection 146 | MSH3_STATUS status = 147 | MsH3ConnectionSetConfiguration( 148 | Event->NEW_CONNECTION.Connection, 149 | config); 150 | 151 | if (MSH3_FAILED(status)) { 152 | printf("Failed to configure connection, status=0x%x\n", status); 153 | MsH3ConnectionClose(Event->NEW_CONNECTION.Connection); 154 | } 155 | break; 156 | 157 | case MSH3_LISTENER_EVENT_SHUTDOWN_COMPLETE: 158 | printf("Listener shutdown complete\n"); 159 | break; 160 | } 161 | 162 | return MSH3_STATUS_SUCCESS; 163 | } 164 | 165 | int main(int argc, char** argv) { 166 | uint16_t port = (argc > 1) ? (uint16_t)atoi(argv[1]) : 443; 167 | 168 | // Initialize MSH3 API 169 | MSH3_API* api = MsH3ApiOpen(); 170 | if (!api) { 171 | printf("Failed to initialize MSH3 API\n"); 172 | return 1; 173 | } 174 | 175 | // Create configuration 176 | MSH3_SETTINGS settings = {0}; 177 | settings.IsSetFlags = 0; 178 | settings.IsSet.IdleTimeoutMs = 1; 179 | settings.IdleTimeoutMs = 30000; // 30 seconds 180 | 181 | MSH3_CONFIGURATION* config = 182 | MsH3ConfigurationOpen(api, &settings, sizeof(settings)); 183 | if (!config) { 184 | printf("Failed to create configuration\n"); 185 | MsH3ApiClose(api); 186 | return 1; 187 | } 188 | 189 | // Load server certificate and private key 190 | MSH3_CERTIFICATE_FILE certFile = { 191 | .PrivateKeyFile = "server.key", 192 | .CertificateFile = "server.crt" 193 | }; 194 | 195 | MSH3_CREDENTIAL_CONFIG credConfig = { 196 | .Type = MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE, 197 | .Flags = MSH3_CREDENTIAL_FLAG_NONE, // Server mode (no client flag) 198 | .CertificateFile = &certFile 199 | }; 200 | 201 | MSH3_STATUS status = MsH3ConfigurationLoadCredential(config, &credConfig); 202 | if (MSH3_FAILED(status)) { 203 | printf("Failed to load credentials, status=0x%x\n", status); 204 | MsH3ConfigurationClose(config); 205 | MsH3ApiClose(api); 206 | return 1; 207 | } 208 | 209 | // Set up the local address 210 | MSH3_ADDR localAddr = {0}; 211 | localAddr.Ipv4.sin_family = AF_INET; 212 | localAddr.Ipv4.sin_addr.s_addr = INADDR_ANY; 213 | MSH3_SET_PORT(&localAddr, port); 214 | 215 | // Create listener 216 | MSH3_LISTENER* listener = 217 | MsH3ListenerOpen(api, &localAddr, ListenerCallback, config); 218 | if (!listener) { 219 | printf("Failed to create listener\n"); 220 | MsH3ConfigurationClose(config); 221 | MsH3ApiClose(api); 222 | return 1; 223 | } 224 | 225 | printf("HTTP/3 server listening on port %u\n", port); 226 | printf("Press Enter to terminate...\n"); 227 | getchar(); 228 | 229 | // Clean up 230 | MsH3ListenerClose(listener); 231 | MsH3ConfigurationClose(config); 232 | MsH3ApiClose(api); 233 | 234 | return 0; 235 | } 236 | ``` 237 | 238 | ## Advanced Features 239 | 240 | ### Requiring Client Authentication 241 | 242 | To require clients to present a certificate: 243 | 244 | ```c 245 | // Set the server to require client authentication 246 | credConfig.Flags = MSH3_CREDENTIAL_FLAG_REQUIRE_CLIENT_AUTHENTICATION; 247 | ``` 248 | 249 | ### Customizing Server Settings 250 | 251 | ```c 252 | MSH3_SETTINGS settings = {0}; 253 | settings.IsSet.IdleTimeoutMs = 1; 254 | settings.IdleTimeoutMs = 60000; // 60 seconds idle timeout 255 | 256 | settings.IsSet.PeerRequestCount = 1; 257 | settings.PeerRequestCount = 100; // Allow up to 100 concurrent requests per connection 258 | 259 | settings.IsSet.DatagramEnabled = 1; 260 | settings.DatagramEnabled = 1; // Enable QUIC datagrams 261 | ``` 262 | 263 | ### Handling Different Types of Requests 264 | 265 | ```c 266 | // In the RequestCallback function 267 | if (strncmp(Event->HEADER_RECEIVED.Header->Name, ":path", 5) == 0) { 268 | const char* path = Event->HEADER_RECEIVED.Header->Value; 269 | size_t pathLength = Event->HEADER_RECEIVED.Header->ValueLength; 270 | 271 | // Store the path for later use 272 | char* requestPath = malloc(pathLength + 1); 273 | if (requestPath) { 274 | memcpy(requestPath, path, pathLength); 275 | requestPath[pathLength] = '\0'; 276 | 277 | // Store in the request context 278 | MsH3RequestSetCallbackHandler(Request, RequestCallback, requestPath); 279 | } 280 | } 281 | 282 | // When sending the response 283 | void* context = Context; // This is the path we stored 284 | const char* requestPath = (const char*)context; 285 | 286 | // Different response based on the path 287 | if (strcmp(requestPath, "/api/data") == 0) { 288 | // API response 289 | const char* jsonResponse = "{\"status\":\"success\",\"data\":{}}"; 290 | 291 | const MSH3_HEADER ResponseHeaders[] = { 292 | { ":status", 7, "200", 3 }, 293 | { "content-type", 12, "application/json", 16 } 294 | }; 295 | 296 | MsH3RequestSend( 297 | Request, 298 | MSH3_REQUEST_SEND_FLAG_FIN, 299 | ResponseHeaders, 300 | sizeof(ResponseHeaders) / sizeof(MSH3_HEADER), 301 | jsonResponse, 302 | (uint32_t)strlen(jsonResponse), 303 | NULL); 304 | } else { 305 | // Default response 306 | const char* textResponse = "404 Not Found"; 307 | 308 | const MSH3_HEADER ResponseHeaders[] = { 309 | { ":status", 7, "404", 3 }, 310 | { "content-type", 12, "text/plain", 10 } 311 | }; 312 | 313 | MsH3RequestSend( 314 | Request, 315 | MSH3_REQUEST_SEND_FLAG_FIN, 316 | ResponseHeaders, 317 | sizeof(ResponseHeaders) / sizeof(MSH3_HEADER), 318 | textResponse, 319 | (uint32_t)strlen(textResponse), 320 | NULL); 321 | } 322 | 323 | // Clean up the context 324 | free(requestPath); 325 | ``` 326 | 327 | ### Responding with a File 328 | 329 | ```c 330 | // Function to send a file as response 331 | void SendFileResponse(MSH3_REQUEST* Request, const char* filePath, const char* contentType) { 332 | FILE* file = fopen(filePath, "rb"); 333 | if (!file) { 334 | // Send 404 response 335 | const MSH3_HEADER ErrorHeaders[] = { 336 | { ":status", 7, "404", 3 }, 337 | { "content-type", 12, "text/plain", 10 } 338 | }; 339 | 340 | const char* errorMsg = "404 Not Found"; 341 | 342 | MsH3RequestSend( 343 | Request, 344 | MSH3_REQUEST_SEND_FLAG_FIN, 345 | ErrorHeaders, 346 | sizeof(ErrorHeaders) / sizeof(MSH3_HEADER), 347 | errorMsg, 348 | (uint32_t)strlen(errorMsg), 349 | NULL); 350 | return; 351 | } 352 | 353 | // Get file size 354 | fseek(file, 0, SEEK_END); 355 | long fileSize = ftell(file); 356 | fseek(file, 0, SEEK_SET); 357 | 358 | // Read file content 359 | char* buffer = (char*)malloc(fileSize); 360 | if (!buffer) { 361 | fclose(file); 362 | return; 363 | } 364 | 365 | size_t bytesRead = fread(buffer, 1, fileSize, file); 366 | fclose(file); 367 | 368 | // Send response 369 | const MSH3_HEADER Headers[] = { 370 | { ":status", 7, "200", 3 }, 371 | { "content-type", 12, contentType, strlen(contentType) } 372 | }; 373 | 374 | MsH3RequestSend( 375 | Request, 376 | MSH3_REQUEST_SEND_FLAG_FIN, 377 | Headers, 378 | sizeof(Headers) / sizeof(MSH3_HEADER), 379 | buffer, 380 | (uint32_t)bytesRead, 381 | buffer); // Use buffer as context to free it later 382 | 383 | // Buffer will be freed in the SEND_COMPLETE event 384 | } 385 | 386 | // In the send complete event handler 387 | case MSH3_REQUEST_EVENT_SEND_COMPLETE: 388 | if (Event->SEND_COMPLETE.ClientContext) { 389 | free(Event->SEND_COMPLETE.ClientContext); 390 | } 391 | break; 392 | ``` 393 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | if (WIN32) 5 | set(SOURCES msh3.cpp msh3.rc) 6 | else() 7 | set(SOURCES msh3.cpp) 8 | endif() 9 | add_library(msh3 SHARED ${SOURCES}) 10 | target_compile_features(msh3 PRIVATE cxx_std_20) 11 | target_compile_definitions(msh3 PRIVATE 12 | "VER_BUILD_ID=${MSH3_VER_BUILD_ID}" 13 | "VER_SUFFIX=${MSH3_VER_SUFFIX}" 14 | ) 15 | target_include_directories(msh3 PUBLIC 16 | $ 17 | $ 18 | ) 19 | target_link_libraries(msh3 PRIVATE msquic msquic_platform ls-qpack::ls-qpack) 20 | if (NOT BUILD_SHARED_LIBS) 21 | target_link_libraries(msh3 PRIVATE base_link) 22 | endif() 23 | 24 | if(WIN32) 25 | SET_TARGET_PROPERTIES(msh3 26 | PROPERTIES LINK_FLAGS "/DEF:\"${CMAKE_CURRENT_SOURCE_DIR}/win32/msh3.def\"") 27 | elseif (CX_PLATFORM STREQUAL "linux") 28 | SET_TARGET_PROPERTIES(msh3 29 | PROPERTIES LINK_FLAGS "-Wl,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/linux/exports.txt\"") 30 | elseif (CX_PLATFORM STREQUAL "darwin") 31 | SET_TARGET_PROPERTIES(msh3 32 | PROPERTIES LINK_FLAGS "-exported_symbols_list \"${CMAKE_CURRENT_SOURCE_DIR}/darwin/exports.txt\"") 33 | endif() 34 | 35 | install(TARGETS msh3 EXPORT msh3 36 | RUNTIME DESTINATION bin 37 | ARCHIVE DESTINATION lib 38 | LIBRARY DESTINATION lib 39 | ) 40 | install(FILES ../msh3.h DESTINATION include) 41 | configure_file(msh3-config.cmake.in ${CMAKE_BINARY_DIR}/msh3-config.cmake @ONLY) 42 | install(FILES ${CMAKE_BINARY_DIR}/msh3-config.cmake DESTINATION share/msh3) 43 | install(EXPORT msh3 DESTINATION share/msh3) 44 | set(prefix ${CMAKE_INSTALL_PREFIX}) 45 | configure_file(libmsh3.pc.in ${CMAKE_BINARY_DIR}/libmsh3.pc @ONLY) 46 | 47 | if(NOT WIN32 OR MINGW) 48 | set(msh3_install_pkgconfig_default ON) 49 | endif() 50 | option(MSH3_INSTALL_PKGCONFIG "Install libmsh3.pc" ${msh3_install_pkgconfig_default}) 51 | if(MSH3_INSTALL_PKGCONFIG) 52 | install(FILES ${CMAKE_BINARY_DIR}/libmsh3.pc DESTINATION lib/pkgconfig) 53 | endif() 54 | -------------------------------------------------------------------------------- /lib/darwin/exports.txt: -------------------------------------------------------------------------------- 1 | _MsH3Version 2 | _MsH3ApiOpen 3 | _MsH3ApiOpenWithExecution 4 | _MsH3ApiPoll 5 | _MsH3ApiClose 6 | _MsH3ConfigurationOpen 7 | _MsH3ConfigurationLoadCredential 8 | _MsH3ConfigurationClose 9 | _MsH3ConnectionOpen 10 | _MsH3ConnectionSetCallbackHandler 11 | _MsH3ConnectionSetConfiguration 12 | _MsH3ConnectionStart 13 | _MsH3ConnectionShutdown 14 | _MsH3ConnectionClose 15 | _MsH3RequestOpen 16 | _MsH3RequestSetCallbackHandler 17 | _MsH3RequestSetReceiveEnabled 18 | _MsH3RequestCompleteReceive 19 | _MsH3RequestSend 20 | _MsH3RequestShutdown 21 | _MsH3RequestClose 22 | _MsH3ListenerOpen 23 | _MsH3ListenerClose 24 | -------------------------------------------------------------------------------- /lib/libmsh3.pc.in: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | prefix=@prefix@ 5 | exec_prefix=${prefix} 6 | libdir=${exec_prefix}/lib 7 | includedir=${prefix}/include 8 | 9 | Name: libmsh3 10 | Description: Minimal HTTP/3 client on top of MsQuic 11 | Version: @PROJECT_VERSION@ 12 | Libs: -L${libdir} -lmsh3 13 | Cflags: -I${includedir} 14 | -------------------------------------------------------------------------------- /lib/linux/exports.txt: -------------------------------------------------------------------------------- 1 | msquic 2 | { 3 | global: MsH3Version; MsH3ApiOpen; MsH3ApiOpenWithExecution; MsH3ApiPoll; MsH3ApiClose; MsH3ConfigurationOpen; MsH3ConfigurationLoadCredential; MsH3ConfigurationClose; MsH3ConnectionOpen; MsH3ConnectionSetCallbackHandler; MsH3ConnectionSetConfiguration; MsH3ConnectionStart; MsH3ConnectionShutdown; MsH3ConnectionClose; MsH3RequestOpen; MsH3RequestSetCallbackHandler; MsH3RequestSetCallbackHandler; MsH3RequestSetReceiveEnabled; MsH3RequestCompleteReceive; MsH3RequestSend; MsH3RequestShutdown; MsH3RequestClose; MsH3ListenerOpen; MsH3ListenerClose; 4 | local: *; 5 | }; 6 | -------------------------------------------------------------------------------- /lib/msh3-config.cmake.in: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | if(NOT "@BUILD_SHARED_LIBS@") 3 | if("@MSH3_USE_EXTERNAL_LSQPACK@") 4 | find_dependency(ls-qpack CONFIG) 5 | endif() 6 | if("@MSH3_USE_EXTERNAL_MSQUIC@") 7 | find_dependency(msquic CONFIG) 8 | endif() 9 | endif() 10 | 11 | include("${CMAKE_CURRENT_LIST_DIR}/msh3.cmake") 12 | -------------------------------------------------------------------------------- /lib/msh3.rc: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | // 5 | 6 | #include 7 | 8 | #define VER_FILETYPE VFT_DLL 9 | #define VER_FILESUBTYPE VFT2_UNKNOWN 10 | #define VER_ORIGINALFILENAME_STR "msh3.dll" 11 | 12 | #include "msh3.ver" 13 | -------------------------------------------------------------------------------- /lib/msh3.ver: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. 3 | // Licensed under the MIT License. 4 | // 5 | 6 | #define VER_MAJOR 0 7 | #define VER_MINOR 9 8 | #define VER_PATCH 0 9 | 10 | #ifndef VER_BUILD_ID 11 | #define VER_BUILD_ID 0 12 | #endif 13 | 14 | #ifndef VER_SUFFIX 15 | #define VER_SUFFIX -private 16 | #endif 17 | 18 | #ifndef MSH3_VERSION_ONLY 19 | 20 | #define VER_COMPANYNAME_STR "Microsoft Corporation" 21 | #define VER_FILEDESCRIPTION_STR "Microsoft\256 H3 Library" 22 | #define VER_INTERNALNAME_STR "msh3" 23 | #define VER_LEGALCOPYRIGHT_STR "\251 Microsoft Corporation. All rights reserved." 24 | #define VER_PRODUCTNAME_STR "Microsoft\256 H3" 25 | 26 | #define STR_HELPER(x) #x 27 | #define STR(x) STR_HELPER(x) 28 | 29 | #define VER_FILEVERSION VER_MAJOR,VER_MINOR,VER_PATCH,0 30 | #define VER_FILEVERSION_STR STR(VER_MAJOR) "." STR(VER_MINOR) "." STR(VER_PATCH) "." STR(VER_BUILD_ID) "\0" 31 | #define VER_PRODUCTVERSION_STR STR(VER_MAJOR) "." STR(VER_MINOR) "." STR(VER_PATCH) "." STR(VER_BUILD_ID) STR(VER_SUFFIX) "\0" 32 | 33 | VS_VERSION_INFO VERSIONINFO 34 | FILEVERSION VER_FILEVERSION 35 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 36 | FILEFLAGS 0 37 | FILEOS VOS_NT_WINDOWS32 38 | FILETYPE VER_FILETYPE 39 | FILESUBTYPE VER_FILESUBTYPE 40 | 41 | BEGIN 42 | BLOCK "StringFileInfo" 43 | BEGIN 44 | BLOCK "040904B0" 45 | BEGIN 46 | VALUE "CompanyName", VER_COMPANYNAME_STR 47 | VALUE "FileDescription", VER_FILEDESCRIPTION_STR 48 | VALUE "FileVersion", VER_FILEVERSION_STR 49 | VALUE "InternalName", VER_INTERNALNAME_STR 50 | VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR 51 | VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR 52 | VALUE "ProductName", VER_PRODUCTNAME_STR 53 | VALUE "ProductVersion", VER_PRODUCTVERSION_STR 54 | END 55 | END 56 | 57 | BLOCK "VarFileInfo" 58 | BEGIN 59 | VALUE "Translation", 0x0409, 0x04B0 60 | END 61 | END 62 | 63 | #endif // MSH3_VERSION_ONLY 64 | -------------------------------------------------------------------------------- /lib/msh3_internal.hpp: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Microsoft Corporation. 4 | Licensed under the MIT License. 5 | 6 | --*/ 7 | 8 | #pragma once 9 | 10 | #ifdef _WIN32 11 | #pragma warning(push) 12 | #pragma warning(disable:4244) // LSQpack int conversion 13 | #pragma warning(disable:4267) // LSQpack int conversion 14 | #endif 15 | 16 | #define MSH3_TEST_MODE 1 // Always built in if server is supported 17 | #define QUIC_TEST_APIS 1 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef _WIN32 31 | #pragma warning(pop) 32 | #endif 33 | 34 | #include "msh3.h" 35 | #define MSH3_VERSION_ONLY 1 36 | #include "msh3.ver" 37 | 38 | #ifdef _WIN32 39 | #define CxPlatByteSwapUint16 _byteswap_ushort 40 | #define CxPlatByteSwapUint32 _byteswap_ulong 41 | #define CxPlatByteSwapUint64 _byteswap_uint64 42 | #else 43 | #define CxPlatByteSwapUint16(value) __builtin_bswap16((unsigned short)(value)) 44 | #define CxPlatByteSwapUint32(value) __builtin_bswap32((value)) 45 | #define CxPlatByteSwapUint64(value) __builtin_bswap64((value)) 46 | #endif 47 | 48 | #ifndef ARRAYSIZE 49 | #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) 50 | #endif 51 | 52 | #ifndef CXPLAT_ANALYSIS_ASSERT 53 | #define CXPLAT_ANALYSIS_ASSERT(X) 54 | #endif 55 | 56 | #ifndef min 57 | #define min(a,b) ((a) > (b) ? (b) : (a)) 58 | #endif 59 | 60 | #ifndef UNREFERENCED_PARAMETER 61 | #define UNREFERENCED_PARAMETER(P) (void)(P) 62 | #endif 63 | 64 | #include 65 | 66 | enum H3SettingsType { 67 | H3SettingQPackMaxTableCapacity = 1, 68 | H3SettingMaxHeaderListSize = 6, 69 | H3SettingQPackBlockedStreamsSize = 7, 70 | H3SettingNumPlaceholders = 9, 71 | // https://datatracker.ietf.org/doc/html/rfc9297#section-2.1.1 72 | H3SettingDatagrams = 0x33, 73 | }; 74 | 75 | // Contiguous buffer for (non-null-terminated) header name and value strings. 76 | struct H3HeadingPair : public lsxpack_header_t { 77 | char Buffer[512] = {0}; 78 | H3HeadingPair() { memset(this, 0, sizeof(lsxpack_header_t)); } 79 | bool Set(const MSH3_HEADER* Header) { 80 | if (Header->NameLength + Header->ValueLength > sizeof(Buffer)) return false; 81 | buf = Buffer; 82 | name_offset = 0; 83 | name_len = (lsxpack_strlen_t)Header->NameLength; 84 | val_offset = name_len; 85 | val_len = (lsxpack_strlen_t)Header->ValueLength; 86 | memcpy(Buffer, Header->Name, name_len); 87 | memcpy(Buffer+name_len, Header->Value, val_len); 88 | return true; 89 | } 90 | }; 91 | 92 | struct H3Headers { 93 | H3HeadingPair* Pairs; 94 | uint32_t PairCount; 95 | }; 96 | 97 | struct H3Settings { 98 | H3SettingsType Type; 99 | uint64_t Integer; 100 | }; 101 | 102 | enum H3StreamType { 103 | H3StreamTypeUnknown = 0xFF, 104 | H3StreamTypeControl = 0, 105 | H3StreamTypePush = 1, 106 | H3StreamTypeEncoder = 2, 107 | H3StreamTypeDecoder = 3, 108 | }; 109 | 110 | enum H3FrameType { 111 | H3FrameData = 0, 112 | H3FrameHeaders = 1, 113 | H3FramePriority = 2, 114 | H3FrameCancelPush = 3, 115 | H3FrameSettings = 4, 116 | H3FramePushPromise = 5, 117 | H3FrameGoaway = 7, 118 | H3FrameUnknown = 0xFF 119 | }; 120 | 121 | #define H3_RFC_DEFAULT_HEADER_TABLE_SIZE 0 122 | #define H3_RFC_DEFAULT_QPACK_BLOCKED_STREAM 0 123 | #define H3_DEFAULT_QPACK_MAX_TABLE_CAPACITY 4096 // Enable dynamic table with a default size of 4096 bytes 124 | #define H3_DEFAULT_QPACK_BLOCKED_STREAMS 100 // Allow up to 100 blocked streams 125 | 126 | const H3Settings SettingsH3[] = { 127 | { H3SettingQPackMaxTableCapacity, H3_DEFAULT_QPACK_MAX_TABLE_CAPACITY }, 128 | { H3SettingQPackBlockedStreamsSize, H3_DEFAULT_QPACK_BLOCKED_STREAMS }, 129 | { H3SettingDatagrams, 1 }, // N.B. - The MsH3pUniDirStream constructor assumes this is always last. 130 | }; 131 | 132 | // Copied from QuicVanIntDecode and changed to uint32_t offset/length 133 | inline 134 | _Success_(return != FALSE) 135 | BOOLEAN 136 | MsH3pVarIntDecode( 137 | _In_ uint32_t BufferLength, 138 | _In_reads_bytes_(BufferLength) 139 | const uint8_t * const Buffer, 140 | _Inout_ 141 | _Deref_in_range_(0, BufferLength) 142 | _Deref_out_range_(0, BufferLength) 143 | uint32_t* Offset, 144 | _Out_ QUIC_VAR_INT* Value 145 | ) 146 | { 147 | if (BufferLength < sizeof(uint8_t) + *Offset) { 148 | return FALSE; 149 | } 150 | if (Buffer[*Offset] < 0x40) { 151 | *Value = Buffer[*Offset]; 152 | CXPLAT_ANALYSIS_ASSERT(*Value < 0x100ULL); 153 | *Offset += sizeof(uint8_t); 154 | } else if (Buffer[*Offset] < 0x80) { 155 | if (BufferLength < sizeof(uint16_t) + *Offset) { 156 | return FALSE; 157 | } 158 | *Value = ((uint64_t)(Buffer[*Offset] & 0x3fUL)) << 8; 159 | *Value |= Buffer[*Offset + 1]; 160 | CXPLAT_ANALYSIS_ASSERT(*Value < 0x10000ULL); 161 | *Offset += sizeof(uint16_t); 162 | } else if (Buffer[*Offset] < 0xc0) { 163 | if (BufferLength < sizeof(uint32_t) + *Offset) { 164 | return FALSE; 165 | } 166 | uint32_t v; 167 | memcpy(&v, Buffer + *Offset, sizeof(uint32_t)); 168 | *Value = CxPlatByteSwapUint32(v) & 0x3fffffffUL; 169 | CXPLAT_ANALYSIS_ASSERT(*Value < 0x100000000ULL); 170 | *Offset += sizeof(uint32_t); 171 | } else { 172 | if (BufferLength < sizeof(uint64_t) + *Offset) { 173 | return FALSE; 174 | } 175 | uint64_t v; 176 | memcpy(&v, Buffer + *Offset, sizeof(uint64_t)); 177 | *Value = CxPlatByteSwapUint64(v) & 0x3fffffffffffffffULL; 178 | *Offset += sizeof(uint64_t); 179 | } 180 | return TRUE; 181 | } 182 | 183 | inline bool 184 | H3WriteFrameHeader( 185 | _In_ uint8_t Type, 186 | _In_ uint32_t Length, 187 | _Inout_ uint32_t* Offset, 188 | _In_ uint32_t BufferLength, 189 | _Out_writes_to_(BufferLength, *Offset) 190 | uint8_t* Buffer 191 | ) 192 | { 193 | const uint32_t RequiredLength = 194 | QuicVarIntSize(Type) + 195 | QuicVarIntSize(Length); 196 | if (BufferLength < *Offset + RequiredLength) { 197 | return false; 198 | } 199 | Buffer = Buffer + *Offset; 200 | Buffer = QuicVarIntEncode(Type, Buffer); 201 | Buffer = QuicVarIntEncode(Length, Buffer); 202 | *Offset += RequiredLength; 203 | return true; 204 | } 205 | 206 | inline bool 207 | H3WriteSettingsFrame( 208 | _In_reads_(SettingsCount) 209 | const H3Settings* Settings, 210 | _In_ uint32_t SettingsCount, 211 | _Inout_ uint32_t* Offset, 212 | _In_ uint32_t BufferLength, 213 | _Out_writes_to_(BufferLength, *Offset) 214 | uint8_t* Buffer 215 | ) 216 | { 217 | uint32_t PayloadSize = 0; 218 | for (uint32_t i = 0; i < SettingsCount; i++) { 219 | PayloadSize += QuicVarIntSize(Settings[i].Type); 220 | PayloadSize += QuicVarIntSize(Settings[i].Integer); 221 | } 222 | if (!H3WriteFrameHeader( 223 | H3FrameSettings, 224 | PayloadSize, 225 | Offset, 226 | BufferLength, 227 | Buffer)) { 228 | return false; 229 | } 230 | if (BufferLength < *Offset + PayloadSize) { 231 | return false; 232 | } 233 | Buffer = Buffer + *Offset; 234 | for (uint32_t i = 0; i < SettingsCount; i++) { 235 | Buffer = QuicVarIntEncode(Settings[i].Type, Buffer); 236 | Buffer = QuicVarIntEncode(Settings[i].Integer, Buffer); 237 | } 238 | *Offset += PayloadSize; 239 | return true; 240 | } 241 | 242 | inline QUIC_STREAM_OPEN_FLAGS ToQuicOpenFlags(MSH3_REQUEST_FLAGS Flags) { 243 | return Flags & MSH3_REQUEST_FLAG_ALLOW_0_RTT ? QUIC_STREAM_OPEN_FLAG_0_RTT : QUIC_STREAM_OPEN_FLAG_NONE; 244 | } 245 | 246 | inline QUIC_SEND_FLAGS ToQuicSendFlags(MSH3_REQUEST_SEND_FLAGS Flags) { 247 | QUIC_SEND_FLAGS QuicFlags = QUIC_SEND_FLAG_NONE; 248 | if (Flags & MSH3_REQUEST_SEND_FLAG_ALLOW_0_RTT) { 249 | QuicFlags |= QUIC_SEND_FLAG_ALLOW_0_RTT; 250 | } 251 | if (Flags & MSH3_REQUEST_SEND_FLAG_FIN) { 252 | QuicFlags |= QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN; 253 | } else if (Flags & MSH3_REQUEST_SEND_FLAG_DELAY_SEND) { 254 | QuicFlags |= QUIC_SEND_FLAG_DELAY_SEND; // TODO - Add support for a _START_DELAYED flag in MsQuic? 255 | } else { 256 | QuicFlags |= QUIC_SEND_FLAG_START; 257 | } 258 | return QuicFlags; 259 | } 260 | 261 | inline QUIC_STREAM_SHUTDOWN_FLAGS ToQuicShutdownFlags(MSH3_REQUEST_SHUTDOWN_FLAGS Flags) { 262 | QUIC_STREAM_SHUTDOWN_FLAGS QuicFlags = QUIC_STREAM_SHUTDOWN_FLAG_NONE; 263 | if (Flags & MSH3_REQUEST_SHUTDOWN_FLAG_GRACEFUL) { 264 | QuicFlags |= QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL; 265 | } else { 266 | if (Flags & MSH3_REQUEST_SHUTDOWN_FLAG_ABORT_SEND) { 267 | QuicFlags |= QUIC_STREAM_SHUTDOWN_FLAG_ABORT_SEND; 268 | } 269 | if (Flags & MSH3_REQUEST_SHUTDOWN_FLAG_ABORT_RECEIVE) { 270 | QuicFlags |= QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE; 271 | } 272 | } 273 | return QuicFlags; 274 | } 275 | 276 | struct MsH3pConfiguration : public MsQuicConfiguration { 277 | bool DatagramEnabled {false}; 278 | QUIC_CREDENTIAL_CONFIG* SelfSign {nullptr}; 279 | MsH3pConfiguration( 280 | const MsQuicRegistration& Registration, 281 | const MSH3_SETTINGS* Settings, 282 | uint32_t SettingsLength 283 | ); 284 | ~MsH3pConfiguration(); 285 | MSH3_STATUS 286 | LoadH3Credential( 287 | const MSH3_CREDENTIAL_CONFIG* CredentialConfig 288 | ); 289 | }; 290 | 291 | struct MsH3pUniDirStream; 292 | struct MsH3pBiDirStream; 293 | 294 | struct MsH3pConnection : public MsQuicConnection { 295 | 296 | MSH3_CONNECTION_CALLBACK_HANDLER Callbacks {nullptr}; 297 | void* Context {nullptr}; 298 | 299 | struct lsqpack_enc Encoder; 300 | struct lsqpack_dec Decoder; 301 | uint8_t tsu_buf[LSQPACK_LONGEST_SDTC]; 302 | size_t tsu_buf_sz; 303 | 304 | MsH3pUniDirStream* LocalControl {nullptr}; 305 | MsH3pUniDirStream* LocalEncoder {nullptr}; 306 | MsH3pUniDirStream* LocalDecoder {nullptr}; 307 | 308 | MsH3pUniDirStream* PeerControl {nullptr}; 309 | MsH3pUniDirStream* PeerEncoder {nullptr}; 310 | MsH3pUniDirStream* PeerDecoder {nullptr}; 311 | 312 | uint32_t PeerMaxTableSize {H3_RFC_DEFAULT_HEADER_TABLE_SIZE}; 313 | uint64_t PeerQPackBlockedStreams {H3_RFC_DEFAULT_QPACK_BLOCKED_STREAM}; 314 | 315 | std::mutex ShutdownCompleteMutex; 316 | std::condition_variable ShutdownCompleteEvent; 317 | bool ShutdownComplete {false}; 318 | bool HandshakeSuccess {false}; 319 | 320 | char HostName[256]; 321 | 322 | MsH3pConnection( 323 | const MsQuicRegistration& Registration, 324 | const MSH3_CONNECTION_CALLBACK_HANDLER Handler, 325 | void* Context 326 | ); 327 | 328 | MsH3pConnection( 329 | HQUIC ServerHandle 330 | ); 331 | 332 | ~MsH3pConnection(); 333 | 334 | void 335 | SetCallbackHandler( 336 | const MSH3_CONNECTION_CALLBACK_HANDLER Handler, 337 | void* _Context 338 | ) 339 | { 340 | Callbacks = Handler; 341 | Context = _Context; 342 | } 343 | 344 | MSH3_STATUS 345 | SetConfigurationH3( 346 | const MsH3pConfiguration& Configuration 347 | ); 348 | 349 | MSH3_STATUS 350 | StartH3( 351 | const MsH3pConfiguration& Configuration, 352 | const char* ServerName, 353 | const MSH3_ADDR* ServerAddress 354 | ); 355 | 356 | void WaitOnShutdownComplete() { 357 | std::unique_lock Lock{ShutdownCompleteMutex}; 358 | ShutdownCompleteEvent.wait(Lock, [&]{return ShutdownComplete;}); 359 | } 360 | 361 | private: 362 | 363 | MSH3_STATUS InitializeConfig(const MsH3pConfiguration& Configuration); 364 | 365 | void SetShutdownComplete() { 366 | std::lock_guard Lock{ShutdownCompleteMutex}; 367 | ShutdownComplete = true; 368 | ShutdownCompleteEvent.notify_all(); 369 | } 370 | 371 | friend struct MsH3pUniDirStream; 372 | friend struct MsH3pBiDirStream; 373 | 374 | static QUIC_STATUS 375 | s_MsQuicCallback( 376 | _In_ MsQuicConnection* /* Connection */, 377 | _In_opt_ void* Context, 378 | _Inout_ QUIC_CONNECTION_EVENT* Event 379 | ) 380 | { 381 | return ((MsH3pConnection*)Context)->MsQuicCallback(Event); 382 | } 383 | 384 | QUIC_STATUS 385 | MsQuicCallback( 386 | _Inout_ QUIC_CONNECTION_EVENT* Event 387 | ); 388 | 389 | bool 390 | ReceiveSettingsFrame( 391 | _In_ uint32_t BufferLength, 392 | _In_reads_bytes_(BufferLength) 393 | const uint8_t * const Buffer 394 | ); 395 | }; 396 | 397 | struct MsH3pUniDirStream : public MsQuicStream { 398 | 399 | MsH3pConnection& H3; 400 | H3StreamType Type; 401 | 402 | uint8_t RawBuffer[256]; 403 | QUIC_BUFFER Buffer {0, RawBuffer}; // Working space 404 | 405 | MsH3pUniDirStream(MsH3pConnection& Connection, H3StreamType Type); 406 | MsH3pUniDirStream(MsH3pConnection& Connection, const MsH3pConfiguration& Configuration); // Type == H3StreamTypeControl 407 | MsH3pUniDirStream(MsH3pConnection& Connection, const HQUIC StreamHandle); 408 | 409 | bool 410 | EncodeHeaders( 411 | _In_ struct MsH3pBiDirStream* Request, 412 | _In_reads_(HeadersCount) 413 | const MSH3_HEADER* Headers, 414 | _In_ size_t HeadersCount 415 | ); 416 | 417 | private: 418 | 419 | static QUIC_STATUS 420 | s_MsQuicCallback( 421 | _In_ MsQuicStream* /* Stream */, 422 | _In_opt_ void* Context, 423 | _Inout_ QUIC_STREAM_EVENT* Event 424 | ) 425 | { 426 | auto This = (MsH3pUniDirStream*)Context; 427 | switch (This->Type) { 428 | case H3StreamTypeControl: 429 | return This->ControlStreamCallback(Event); 430 | case H3StreamTypeEncoder: 431 | return This->EncoderStreamCallback(Event); 432 | case H3StreamTypeDecoder: 433 | return This->DecoderStreamCallback(Event); 434 | default: 435 | return This->UnknownStreamCallback(Event); 436 | } 437 | } 438 | 439 | QUIC_STATUS 440 | ControlStreamCallback( 441 | _Inout_ QUIC_STREAM_EVENT* Event 442 | ); 443 | 444 | void 445 | ControlReceive( 446 | _In_ const QUIC_BUFFER* Buffer 447 | ); 448 | 449 | QUIC_STATUS 450 | EncoderStreamCallback( 451 | _Inout_ QUIC_STREAM_EVENT* Event 452 | ); 453 | 454 | QUIC_STATUS 455 | DecoderStreamCallback( 456 | _Inout_ QUIC_STREAM_EVENT* Event 457 | ); 458 | 459 | QUIC_STATUS 460 | UnknownStreamCallback( 461 | _Inout_ QUIC_STREAM_EVENT* Event 462 | ); 463 | }; 464 | 465 | struct MsH3pAppSend { 466 | void* AppContext; 467 | uint8_t FrameHeaderBuffer[16]; 468 | QUIC_BUFFER Buffers[2] = { 469 | 0, FrameHeaderBuffer, 470 | 0, NULL 471 | }; 472 | MsH3pAppSend(_In_opt_ void* AppContext) : AppContext(AppContext) { } 473 | bool SetData( 474 | _In_reads_bytes_opt_(DataLength) const void* Data, 475 | _In_ uint32_t DataLength 476 | ) 477 | { 478 | Buffers[1].Length = DataLength; 479 | Buffers[1].Buffer = (uint8_t*)Data; 480 | return H3WriteFrameHeader(H3FrameData, DataLength, &Buffers[0].Length, sizeof(FrameHeaderBuffer), FrameHeaderBuffer); 481 | } 482 | }; 483 | 484 | struct MsH3pBiDirStream : public MsQuicStream { 485 | 486 | MsH3pConnection& H3; 487 | 488 | MSH3_REQUEST_CALLBACK_HANDLER Callbacks; 489 | void* Context; 490 | 491 | uint8_t FrameHeaderBuffer[16]; 492 | uint8_t PrefixBuffer[32]; 493 | uint8_t HeadersBuffer[256]; 494 | QUIC_BUFFER Buffers[3] = { // TODO - Put in AppSend struct? 495 | {0, FrameHeaderBuffer}, 496 | {0, PrefixBuffer}, 497 | {0, HeadersBuffer} 498 | }; 499 | 500 | static struct lsqpack_dec_hset_if hset_if; 501 | struct lsxpack_header CurDecodeHeader; 502 | char DecodeBuffer[4096]; 503 | 504 | QUIC_VAR_INT CurFrameType {0}; 505 | QUIC_VAR_INT CurFrameLength {0}; 506 | QUIC_VAR_INT CurFrameLengthLeft {0}; 507 | uint64_t CurRecvCompleteLength {0}; 508 | uint32_t CurRecvOffset {0}; 509 | 510 | uint8_t BufferedHeaders[2*sizeof(uint64_t)]; 511 | uint32_t BufferedHeadersLength {0}; 512 | 513 | bool Complete {false}; 514 | bool ShutdownComplete {false}; 515 | bool ReceivePending {false}; 516 | 517 | MsH3pBiDirStream( 518 | _In_ MsH3pConnection& Connection, 519 | const MSH3_REQUEST_CALLBACK_HANDLER Handler, 520 | _In_ void* Context, 521 | _In_ MSH3_REQUEST_FLAGS Flags 522 | ) : MsQuicStream(Connection, ToQuicOpenFlags(Flags), CleanUpManual, s_MsQuicCallback, this), 523 | H3(Connection), Callbacks(Handler), Context(Context) { } 524 | 525 | MsH3pBiDirStream( 526 | _In_ MsH3pConnection& Connection, 527 | _In_ HQUIC StreamHandle 528 | ) : MsQuicStream(StreamHandle, CleanUpManual, s_MsQuicCallback, this), 529 | H3(Connection) { } 530 | 531 | void 532 | CompleteReceive( 533 | _In_ uint32_t Length 534 | ); 535 | 536 | bool 537 | Send( 538 | _In_ MSH3_REQUEST_SEND_FLAGS Flags, 539 | _In_reads_(HeadersCount) 540 | const MSH3_HEADER* Headers, 541 | _In_ size_t HeadersCount, 542 | _In_reads_bytes_(DataLength) const void* Data, 543 | _In_ uint32_t DataLength, 544 | _In_opt_ void* AppContext 545 | ); 546 | 547 | void 548 | SetCallbackHandler( 549 | const MSH3_REQUEST_CALLBACK_HANDLER Handler, 550 | void* _Context 551 | ) 552 | { 553 | Callbacks = Handler; 554 | Context = _Context; 555 | } 556 | 557 | private: 558 | 559 | QUIC_STATUS 560 | Receive( 561 | _Inout_ QUIC_STREAM_EVENT* Event 562 | ); 563 | 564 | static QUIC_STATUS 565 | s_MsQuicCallback( 566 | _In_ MsQuicStream* /* Stream */, 567 | _In_opt_ void* Context, 568 | _Inout_ QUIC_STREAM_EVENT* Event 569 | ) 570 | { 571 | return ((MsH3pBiDirStream*)Context)->MsQuicCallback(Event); 572 | } 573 | 574 | QUIC_STATUS 575 | MsQuicCallback( 576 | _Inout_ QUIC_STREAM_EVENT* Event 577 | ); 578 | 579 | static void 580 | s_DecodeUnblocked( 581 | void* /* Context */ 582 | ) 583 | { 584 | /* no-op currently */ 585 | } 586 | 587 | static struct lsxpack_header* 588 | s_DecodePrepare( 589 | void *Context, 590 | struct lsxpack_header* Header, 591 | size_t Space 592 | ) 593 | { 594 | return ((MsH3pBiDirStream*)Context)->DecodePrepare(Header, Space); 595 | } 596 | 597 | struct lsxpack_header* 598 | DecodePrepare( 599 | struct lsxpack_header* Header, 600 | size_t Space 601 | ); 602 | 603 | static int 604 | s_DecodeProcess( 605 | void *Context, 606 | struct lsxpack_header* Header 607 | ) 608 | { 609 | ((MsH3pBiDirStream*)Context)->DecodeProcess(Header); 610 | return 0; 611 | } 612 | 613 | void 614 | DecodeProcess( 615 | struct lsxpack_header* Header 616 | ); 617 | }; 618 | 619 | struct MsH3pListener : public MsQuicListener { 620 | 621 | MSH3_LISTENER_CALLBACK_HANDLER Callbacks; 622 | void* Context; 623 | 624 | MsH3pListener( 625 | const MsQuicRegistration& Registration, 626 | const MSH3_ADDR* Address, 627 | const MSH3_LISTENER_CALLBACK_HANDLER Handler, 628 | void* Context 629 | ); 630 | 631 | private: 632 | 633 | static 634 | _IRQL_requires_max_(PASSIVE_LEVEL) 635 | _Function_class_(QUIC_LISTENER_CALLBACK) 636 | QUIC_STATUS 637 | QUIC_API 638 | s_MsQuicCallback( 639 | _In_ MsQuicListener* /* Listener */, 640 | _In_opt_ void* Context, 641 | _Inout_ QUIC_LISTENER_EVENT* Event 642 | ) 643 | { 644 | return ((MsH3pListener*)Context)->MsQuicCallback(Event); 645 | } 646 | 647 | QUIC_STATUS 648 | MsQuicCallback( 649 | _Inout_ QUIC_LISTENER_EVENT* Event 650 | ); 651 | }; 652 | -------------------------------------------------------------------------------- /lib/win32/msh3.def: -------------------------------------------------------------------------------- 1 | LIBRARY MSH3 2 | 3 | EXPORTS 4 | MsH3Version 5 | MsH3ApiOpen 6 | MsH3ApiOpenWithExecution 7 | MsH3ApiPoll 8 | MsH3ApiClose 9 | MsH3ConfigurationOpen 10 | MsH3ConfigurationLoadCredential 11 | MsH3ConfigurationClose 12 | MsH3ConnectionOpen 13 | MsH3ConnectionSetCallbackHandler 14 | MsH3ConnectionSetConfiguration 15 | MsH3ConnectionStart 16 | MsH3ConnectionShutdown 17 | MsH3ConnectionClose 18 | MsH3RequestOpen 19 | MsH3RequestSetCallbackHandler 20 | MsH3RequestSend 21 | MsH3RequestSetReceiveEnabled 22 | MsH3RequestCompleteReceive 23 | MsH3RequestShutdown 24 | MsH3RequestClose 25 | MsH3ListenerOpen 26 | MsH3ListenerClose 27 | -------------------------------------------------------------------------------- /msh3.h: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Microsoft Corporation. 4 | Licensed under the MIT License. 5 | 6 | --*/ 7 | 8 | #ifndef _MSH3_ 9 | #define _MSH3_ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #ifdef _WIN32 16 | #include 17 | #include 18 | #include 19 | #define SUCCESS_HRESULT_FROM_WIN32(x) \ 20 | ((HRESULT)(((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16))) 21 | #define MSH3_CALL __cdecl 22 | #define MSH3_STATUS HRESULT 23 | #define MSH3_STATUS_SUCCESS S_OK 24 | #define MSH3_STATUS_PENDING SUCCESS_HRESULT_FROM_WIN32(ERROR_IO_PENDING) 25 | #define MSH3_STATUS_INVALID_STATE E_NOT_VALID_STATE 26 | #define MSH3_FAILED(X) FAILED(X) 27 | typedef HANDLE MSH3_EVENTQ; 28 | typedef OVERLAPPED_ENTRY MSH3_CQE; 29 | typedef 30 | _IRQL_requires_max_(PASSIVE_LEVEL) 31 | void 32 | (MSH3_EVENT_COMPLETION)( 33 | MSH3_CQE* Cqe 34 | ); 35 | typedef MSH3_EVENT_COMPLETION *MSH3_EVENT_COMPLETION_HANDLER; 36 | typedef struct MSH3_SQE { 37 | OVERLAPPED Overlapped; 38 | MSH3_EVENT_COMPLETION_HANDLER Completion; 39 | #if DEBUG 40 | BOOLEAN IsQueued; // Debug flag to catch double queueing. 41 | #endif 42 | } MSH3_SQE; 43 | #else 44 | #include 45 | #include 46 | #include 47 | #define MSH3_CALL 48 | #define MSH3_STATUS unsigned int 49 | #define MSH3_STATUS_SUCCESS ((MSH3_STATUS)0) 50 | #define MSH3_STATUS_PENDING ((MSH3_STATUS)-2) 51 | #define MSH3_STATUS_INVALID_STATE ((MSH3_STATUS)EPERM) 52 | #define MSH3_FAILED(X) ((int)(X) > 0) 53 | #if __linux__ // epoll 54 | #include 55 | #include 56 | #include 57 | typedef int MSH3_EVENTQ; 58 | typedef struct epoll_event MSH3_CQE; 59 | typedef 60 | void 61 | (MSH3_EVENT_COMPLETION)( 62 | MSH3_CQE* Cqe 63 | ); 64 | typedef MSH3_EVENT_COMPLETION *MSH3_EVENT_COMPLETION_HANDLER; 65 | typedef struct MSH3_SQE { 66 | int fd; 67 | MSH3_EVENT_COMPLETION_HANDLER Completion; 68 | } MSH3_SQE; 69 | #elif __APPLE__ || __FreeBSD__ // kqueue 70 | #include 71 | #include 72 | typedef int MSH3_EVENTQ; 73 | typedef struct kevent MSH3_CQE; 74 | typedef 75 | void 76 | (MSH3_EVENT_COMPLETION)( 77 | MSH3_CQE* Cqe 78 | ); 79 | typedef MSH3_EVENT_COMPLETION *MSH3_EVENT_COMPLETION_HANDLER; 80 | typedef struct MSH3_SQE { 81 | uintptr_t Handle; 82 | MSH3_EVENT_COMPLETION_HANDLER Completion; 83 | } MSH3_SQE; 84 | #endif // __linux__ 85 | #ifndef DEFINE_ENUM_FLAG_OPERATORS 86 | #ifdef __cplusplus 87 | extern "C++" { 88 | template struct _ENUM_FLAG_INTEGER_FOR_SIZE; 89 | template <> struct _ENUM_FLAG_INTEGER_FOR_SIZE<1> { typedef uint8_t type; }; 90 | template <> struct _ENUM_FLAG_INTEGER_FOR_SIZE<2> { typedef uint16_t type; }; 91 | template <> struct _ENUM_FLAG_INTEGER_FOR_SIZE<4> { typedef uint32_t type; }; 92 | template <> struct _ENUM_FLAG_INTEGER_FOR_SIZE<8> { typedef uint64_t type; }; 93 | // used as an approximation of std::underlying_type 94 | template struct _ENUM_FLAG_SIZED_INTEGER { 95 | typedef typename _ENUM_FLAG_INTEGER_FOR_SIZE::type type; 96 | }; 97 | } 98 | #define DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \ 99 | extern "C++" { \ 100 | inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) throw() { return ENUMTYPE(((_ENUM_FLAG_SIZED_INTEGER::type)a) | ((_ENUM_FLAG_SIZED_INTEGER::type)b)); } \ 101 | inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) throw() { return (ENUMTYPE &)(((_ENUM_FLAG_SIZED_INTEGER::type &)a) |= ((_ENUM_FLAG_SIZED_INTEGER::type)b)); } \ 102 | inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) throw() { return ENUMTYPE(((_ENUM_FLAG_SIZED_INTEGER::type)a) & ((_ENUM_FLAG_SIZED_INTEGER::type)b)); } \ 103 | inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) throw() { return (ENUMTYPE &)(((_ENUM_FLAG_SIZED_INTEGER::type &)a) &= ((_ENUM_FLAG_SIZED_INTEGER::type)b)); } \ 104 | inline ENUMTYPE operator ~ (ENUMTYPE a) throw() { return ENUMTYPE(~((_ENUM_FLAG_SIZED_INTEGER::type)a)); } \ 105 | inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) throw() { return ENUMTYPE(((_ENUM_FLAG_SIZED_INTEGER::type)a) ^ ((_ENUM_FLAG_SIZED_INTEGER::type)b)); } \ 106 | inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) throw() { return (ENUMTYPE &)(((_ENUM_FLAG_SIZED_INTEGER::type &)a) ^= ((_ENUM_FLAG_SIZED_INTEGER::type)b)); } \ 107 | } 108 | #else 109 | #define DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) // NOP, C allows these operators. 110 | #endif 111 | #endif // DEFINE_ENUM_FLAG_OPERATORS 112 | #endif 113 | 114 | 115 | #if defined(__cplusplus) 116 | extern "C" { 117 | #endif 118 | 119 | typedef struct MSH3_API MSH3_API; 120 | typedef struct MSH3_CONFIGURATION MSH3_CONFIGURATION; 121 | typedef struct MSH3_CONNECTION MSH3_CONNECTION; 122 | typedef struct MSH3_REQUEST MSH3_REQUEST; 123 | typedef struct MSH3_LISTENER MSH3_LISTENER; 124 | 125 | typedef enum MSH3_CREDENTIAL_TYPE { 126 | MSH3_CREDENTIAL_TYPE_NONE, 127 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_HASH, 128 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_HASH_STORE, 129 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_CONTEXT, 130 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE, 131 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_FILE_PROTECTED, 132 | MSH3_CREDENTIAL_TYPE_CERTIFICATE_PKCS12, 133 | #ifdef MSH3_TEST_MODE 134 | MSH3_CREDENTIAL_TYPE_SELF_SIGNED_CERTIFICATE, 135 | #endif // MSH3_TEST_MODE 136 | } MSH3_CREDENTIAL_TYPE; 137 | 138 | typedef enum MSH3_CREDENTIAL_FLAGS { 139 | MSH3_CREDENTIAL_FLAG_NONE = 0x00000000, 140 | MSH3_CREDENTIAL_FLAG_CLIENT = 0x00000001, // Lack of client flag indicates server. 141 | MSH3_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION = 0x00000002, 142 | MSH3_CREDENTIAL_FLAG_REQUIRE_CLIENT_AUTHENTICATION = 0x00000004, 143 | } MSH3_CREDENTIAL_FLAGS; 144 | 145 | DEFINE_ENUM_FLAG_OPERATORS(MSH3_CREDENTIAL_FLAGS) 146 | 147 | typedef enum MSH3_CERTIFICATE_HASH_STORE_FLAGS { 148 | MSH3_CERTIFICATE_HASH_STORE_FLAG_NONE = 0x0000, 149 | MSH3_CERTIFICATE_HASH_STORE_FLAG_MACHINE_STORE = 0x0001, 150 | } MSH3_CERTIFICATE_HASH_STORE_FLAGS; 151 | 152 | DEFINE_ENUM_FLAG_OPERATORS(MSH3_CERTIFICATE_HASH_STORE_FLAGS) 153 | 154 | typedef enum MSH3_REQUEST_FLAGS { 155 | MSH3_REQUEST_FLAG_NONE = 0x0000, 156 | MSH3_REQUEST_FLAG_ALLOW_0_RTT = 0x0001, // Allows the use of encrypting with 0-RTT key. 157 | } MSH3_REQUEST_FLAGS; 158 | 159 | DEFINE_ENUM_FLAG_OPERATORS(MSH3_REQUEST_FLAGS) 160 | 161 | typedef enum MSH3_REQUEST_SEND_FLAGS { 162 | MSH3_REQUEST_SEND_FLAG_NONE = 0x0000, 163 | MSH3_REQUEST_SEND_FLAG_ALLOW_0_RTT = 0x0001, // Allows the use of encrypting with 0-RTT key. 164 | MSH3_REQUEST_SEND_FLAG_FIN = 0x0002, // Indicates the request should be gracefully shutdown too. 165 | MSH3_REQUEST_SEND_FLAG_DELAY_SEND = 0x0004, // Indicates the send should be delayed because more will be queued soon. 166 | } MSH3_REQUEST_SEND_FLAGS; 167 | 168 | DEFINE_ENUM_FLAG_OPERATORS(MSH3_REQUEST_SEND_FLAGS) 169 | 170 | typedef enum MSH3_REQUEST_SHUTDOWN_FLAGS { 171 | MSH3_REQUEST_SHUTDOWN_FLAG_NONE = 0x0000, 172 | MSH3_REQUEST_SHUTDOWN_FLAG_GRACEFUL = 0x0001, // Cleanly closes the send path. 173 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT_SEND = 0x0002, // Abruptly closes the send path. 174 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT_RECEIVE = 0x0004, // Abruptly closes the receive path. 175 | MSH3_REQUEST_SHUTDOWN_FLAG_ABORT = 0x0006, // Abruptly closes both send and receive paths. 176 | } MSH3_REQUEST_SHUTDOWN_FLAGS; 177 | 178 | DEFINE_ENUM_FLAG_OPERATORS(MSH3_REQUEST_SHUTDOWN_FLAGS) 179 | 180 | typedef struct MSH3_EXECUTION_CONFIG { 181 | uint32_t IdealProcessor; 182 | MSH3_EVENTQ* EventQ; 183 | } MSH3_EXECUTION_CONFIG; 184 | 185 | typedef struct MSH3_EXECUTION MSH3_EXECUTION; 186 | 187 | typedef struct MSH3_SETTINGS { 188 | union { 189 | uint64_t IsSetFlags; 190 | struct { 191 | uint64_t IdleTimeoutMs : 1; 192 | uint64_t DisconnectTimeoutMs : 1; 193 | uint64_t KeepAliveIntervalMs : 1; 194 | uint64_t InitialRttMs : 1; 195 | uint64_t PeerRequestCount : 1; 196 | uint64_t DatagramEnabled : 1; 197 | #ifdef MSH3_API_ENABLE_PREVIEW_FEATURES 198 | uint64_t XdpEnabled : 1; 199 | #endif 200 | } IsSet; 201 | }; 202 | uint64_t IdleTimeoutMs; 203 | uint32_t DisconnectTimeoutMs; 204 | uint32_t KeepAliveIntervalMs; 205 | uint32_t InitialRttMs; 206 | uint16_t PeerRequestCount; 207 | uint8_t DatagramEnabled : 1; // TODO - Add flags instead? 208 | #ifdef MSH3_API_ENABLE_PREVIEW_FEATURES 209 | uint8_t XdpEnabled : 1; 210 | uint8_t RESERVED : 6; 211 | #else 212 | uint8_t RESERVED : 7; 213 | #endif 214 | } MSH3_SETTINGS; 215 | 216 | typedef struct MSH3_CERTIFICATE_HASH { 217 | uint8_t ShaHash[20]; 218 | } MSH3_CERTIFICATE_HASH; 219 | 220 | typedef struct MSH3_CERTIFICATE_HASH_STORE { 221 | MSH3_CERTIFICATE_HASH_STORE_FLAGS Flags; 222 | uint8_t ShaHash[20]; 223 | char StoreName[128]; 224 | } MSH3_CERTIFICATE_HASH_STORE; 225 | 226 | typedef void MSH3_CERTIFICATE_CONTEXT; 227 | 228 | typedef struct MSH3_CERTIFICATE_FILE { 229 | const char *PrivateKeyFile; 230 | const char *CertificateFile; 231 | } MSH3_CERTIFICATE_FILE; 232 | 233 | typedef struct MSH3_CERTIFICATE_FILE_PROTECTED { 234 | const char *PrivateKeyFile; 235 | const char *CertificateFile; 236 | const char *PrivateKeyPassword; 237 | } MSH3_CERTIFICATE_FILE_PROTECTED; 238 | 239 | typedef struct MSH3_CERTIFICATE_PKCS12 { 240 | const uint8_t *Asn1Blob; 241 | uint32_t Asn1BlobLength; 242 | const char *PrivateKeyPassword; // Optional 243 | } MSH3_CERTIFICATE_PKCS12; 244 | 245 | typedef struct MSH3_CREDENTIAL_CONFIG { 246 | MSH3_CREDENTIAL_TYPE Type; 247 | MSH3_CREDENTIAL_FLAGS Flags; 248 | union { 249 | MSH3_CERTIFICATE_HASH* CertificateHash; 250 | MSH3_CERTIFICATE_HASH_STORE* CertificateHashStore; 251 | MSH3_CERTIFICATE_CONTEXT* CertificateContext; 252 | MSH3_CERTIFICATE_FILE* CertificateFile; 253 | MSH3_CERTIFICATE_FILE_PROTECTED* CertificateFileProtected; 254 | MSH3_CERTIFICATE_PKCS12* CertificatePkcs12; 255 | }; 256 | } MSH3_CREDENTIAL_CONFIG; 257 | 258 | typedef union MSH3_ADDR { 259 | struct sockaddr Ip; 260 | struct sockaddr_in Ipv4; 261 | struct sockaddr_in6 Ipv6; 262 | } MSH3_ADDR; 263 | 264 | #ifdef _WIN32 265 | #define MSH3_SET_PORT(addr, port) (addr)->Ipv4.sin_port = _byteswap_ushort(port) 266 | #else 267 | #define MSH3_SET_PORT(addr, port) (addr)->Ipv4.sin_port = __builtin_bswap16(port) 268 | #endif 269 | 270 | typedef struct MSH3_HEADER { 271 | const char* Name; 272 | size_t NameLength; 273 | const char* Value; 274 | size_t ValueLength; 275 | } MSH3_HEADER; 276 | 277 | // 278 | // API global interface 279 | // 280 | 281 | void 282 | MSH3_CALL 283 | MsH3Version( 284 | uint32_t Version[4] 285 | ); 286 | 287 | MSH3_API* 288 | MSH3_CALL 289 | MsH3ApiOpen( 290 | void 291 | ); 292 | 293 | #ifdef MSH3_API_ENABLE_PREVIEW_FEATURES 294 | MSH3_API* 295 | MSH3_CALL 296 | MsH3ApiOpenWithExecution( 297 | uint32_t ExecutionConfigCount, 298 | MSH3_EXECUTION_CONFIG* ExecutionConfigs, 299 | MSH3_EXECUTION** Executions 300 | ); 301 | 302 | uint32_t 303 | MSH3_CALL 304 | MsH3ApiPoll( 305 | MSH3_EXECUTION* Execution 306 | ); 307 | #endif 308 | 309 | void 310 | MSH3_CALL 311 | MsH3ApiClose( 312 | MSH3_API* Api 313 | ); 314 | 315 | // 316 | // Configuration interface 317 | // 318 | 319 | MSH3_CONFIGURATION* 320 | MSH3_CALL 321 | MsH3ConfigurationOpen( 322 | MSH3_API* Api, 323 | const MSH3_SETTINGS* Settings, // optional 324 | uint32_t SettingsLength 325 | ); 326 | 327 | MSH3_STATUS 328 | MSH3_CALL 329 | MsH3ConfigurationLoadCredential( 330 | MSH3_CONFIGURATION* Configuration, 331 | const MSH3_CREDENTIAL_CONFIG* CredentialConfig 332 | ); 333 | 334 | void 335 | MSH3_CALL 336 | MsH3ConfigurationClose( 337 | MSH3_CONFIGURATION* Configuration 338 | ); 339 | 340 | // 341 | // Connection interface 342 | // 343 | 344 | typedef enum MSH3_CONNECTION_EVENT_TYPE { 345 | MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE = 0, // Ready for the handle to be closed. 346 | MSH3_CONNECTION_EVENT_CONNECTED = 1, 347 | MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT = 2, // The transport started the shutdown process. 348 | MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER = 3, // The peer application started the shutdown process. 349 | MSH3_CONNECTION_EVENT_NEW_REQUEST = 4, 350 | // Future events may be added. Existing code should 351 | // return NOT_SUPPORTED for any unknown event. 352 | } MSH3_CONNECTION_EVENT_TYPE; 353 | 354 | typedef struct MSH3_CONNECTION_EVENT { 355 | MSH3_CONNECTION_EVENT_TYPE Type; 356 | union { 357 | struct { 358 | MSH3_STATUS Status; 359 | uint64_t ErrorCode; // Wire format error code. 360 | } SHUTDOWN_INITIATED_BY_TRANSPORT; 361 | struct { 362 | uint64_t ErrorCode; 363 | } SHUTDOWN_INITIATED_BY_PEER; 364 | struct { 365 | bool HandshakeCompleted : 1; 366 | bool PeerAcknowledgedShutdown : 1; 367 | bool AppCloseInProgress : 1; 368 | } SHUTDOWN_COMPLETE; 369 | struct { 370 | MSH3_REQUEST* Request; 371 | } NEW_REQUEST; 372 | }; 373 | } MSH3_CONNECTION_EVENT; 374 | 375 | typedef 376 | MSH3_STATUS 377 | (MSH3_CALL MSH3_CONNECTION_CALLBACK)( 378 | MSH3_CONNECTION* Connection, 379 | void* Context, 380 | MSH3_CONNECTION_EVENT* Event 381 | ); 382 | 383 | typedef MSH3_CONNECTION_CALLBACK *MSH3_CONNECTION_CALLBACK_HANDLER; 384 | 385 | MSH3_CONNECTION* 386 | MSH3_CALL 387 | MsH3ConnectionOpen( 388 | MSH3_API* Api, 389 | const MSH3_CONNECTION_CALLBACK_HANDLER Handler, 390 | void* Context 391 | ); 392 | 393 | void 394 | MSH3_CALL 395 | MsH3ConnectionSetCallbackHandler( 396 | MSH3_CONNECTION* Connection, 397 | const MSH3_CONNECTION_CALLBACK_HANDLER Handler, 398 | void* Context 399 | ); 400 | 401 | MSH3_STATUS 402 | MSH3_CALL 403 | MsH3ConnectionSetConfiguration( 404 | MSH3_CONNECTION* Connection, 405 | MSH3_CONFIGURATION* Configuration 406 | ); 407 | 408 | MSH3_STATUS 409 | MSH3_CALL 410 | MsH3ConnectionStart( 411 | MSH3_CONNECTION* Connection, 412 | MSH3_CONFIGURATION* Configuration, 413 | const char* ServerName, 414 | const MSH3_ADDR* ServerAddress 415 | ); 416 | 417 | void 418 | MSH3_CALL 419 | MsH3ConnectionShutdown( 420 | MSH3_CONNECTION* Connection, 421 | uint64_t ErrorCode 422 | ); 423 | 424 | void 425 | MSH3_CALL 426 | MsH3ConnectionClose( 427 | MSH3_CONNECTION* Connection 428 | ); 429 | 430 | // 431 | // Request Interface 432 | // 433 | 434 | typedef enum MSH3_REQUEST_EVENT_TYPE { 435 | MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE = 0, // Ready for the handle to be closed. 436 | MSH3_REQUEST_EVENT_HEADER_RECEIVED = 1, 437 | MSH3_REQUEST_EVENT_DATA_RECEIVED = 2, 438 | MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN = 3, 439 | MSH3_REQUEST_EVENT_PEER_SEND_ABORTED = 4, 440 | MSH3_REQUEST_EVENT_IDEAL_SEND_SIZE = 5, 441 | MSH3_REQUEST_EVENT_SEND_COMPLETE = 6, 442 | MSH3_REQUEST_EVENT_SEND_SHUTDOWN_COMPLETE = 7, 443 | MSH3_REQUEST_EVENT_PEER_RECEIVE_ABORTED = 8, 444 | // Future events may be added. Existing code should 445 | // return NOT_SUPPORTED for any unknown event. 446 | } MSH3_REQUEST_EVENT_TYPE; 447 | 448 | typedef struct MSH3_REQUEST_EVENT { 449 | MSH3_REQUEST_EVENT_TYPE Type; 450 | union { 451 | struct { 452 | bool ConnectionShutdown; 453 | bool AppCloseInProgress : 1; 454 | bool ConnectionShutdownByApp : 1; 455 | bool ConnectionClosedRemotely : 1; 456 | bool RESERVED : 1; 457 | bool RESERVED_2 : 1; 458 | bool RESERVED_3 : 1; 459 | bool RESERVED_4 : 1; 460 | bool RESERVED_5 : 1; 461 | uint64_t ConnectionErrorCode; 462 | MSH3_STATUS ConnectionCloseStatus; 463 | } SHUTDOWN_COMPLETE; 464 | struct { 465 | const MSH3_HEADER* Header; 466 | } HEADER_RECEIVED; 467 | struct { 468 | uint32_t Length; 469 | const uint8_t* Data; 470 | } DATA_RECEIVED; 471 | struct { 472 | uint64_t ErrorCode; 473 | } PEER_SEND_ABORTED; 474 | struct { 475 | uint64_t ByteCount; 476 | } IDEAL_SEND_SIZE; 477 | struct { 478 | bool Canceled; 479 | void* ClientContext; 480 | } SEND_COMPLETE; 481 | struct { 482 | bool Graceful; 483 | } SEND_SHUTDOWN_COMPLETE; 484 | struct { 485 | uint64_t ErrorCode; 486 | } PEER_RECEIVE_ABORTED; 487 | }; 488 | } MSH3_REQUEST_EVENT; 489 | 490 | typedef 491 | MSH3_STATUS 492 | (MSH3_CALL MSH3_REQUEST_CALLBACK)( 493 | MSH3_REQUEST* Request, 494 | void* Context, 495 | MSH3_REQUEST_EVENT* Event 496 | ); 497 | 498 | typedef MSH3_REQUEST_CALLBACK *MSH3_REQUEST_CALLBACK_HANDLER; 499 | 500 | MSH3_REQUEST* 501 | MSH3_CALL 502 | MsH3RequestOpen( 503 | MSH3_CONNECTION* Connection, 504 | const MSH3_REQUEST_CALLBACK_HANDLER Handler, 505 | void* Context, 506 | MSH3_REQUEST_FLAGS Flags 507 | ); 508 | 509 | void 510 | MSH3_CALL 511 | MsH3RequestSetCallbackHandler( 512 | MSH3_REQUEST* Request, 513 | const MSH3_REQUEST_CALLBACK_HANDLER Handler, 514 | void* Context 515 | ); 516 | 517 | bool 518 | MSH3_CALL 519 | MsH3RequestSend( 520 | MSH3_REQUEST* Request, 521 | MSH3_REQUEST_SEND_FLAGS Flags, 522 | const MSH3_HEADER* Headers, 523 | size_t HeadersCount, 524 | const void* Data, 525 | uint32_t DataLength, 526 | void* AppContext 527 | ); 528 | 529 | void 530 | MSH3_CALL 531 | MsH3RequestSetReceiveEnabled( 532 | MSH3_REQUEST* Request, 533 | bool Enabled 534 | ); 535 | 536 | void 537 | MSH3_CALL 538 | MsH3RequestCompleteReceive( 539 | MSH3_REQUEST* Request, 540 | uint32_t Length 541 | ); 542 | 543 | void 544 | MSH3_CALL 545 | MsH3RequestShutdown( 546 | MSH3_REQUEST* Request, 547 | MSH3_REQUEST_SHUTDOWN_FLAGS Flags, 548 | uint64_t AbortError // Only for MSH3_REQUEST_SHUTDOWN_FLAG_ABORT* 549 | ); 550 | 551 | void 552 | MSH3_CALL 553 | MsH3RequestClose( 554 | MSH3_REQUEST* Request 555 | ); 556 | 557 | // 558 | // Listener Interface 559 | // 560 | 561 | typedef enum MSH3_LISTENER_EVENT_TYPE { 562 | MSH3_LISTENER_EVENT_SHUTDOWN_COMPLETE = 0, // Ready for the handle to be closed. 563 | MSH3_LISTENER_EVENT_NEW_CONNECTION = 1, 564 | // Future events may be added. Existing code should 565 | // return NOT_SUPPORTED for any unknown event. 566 | } MSH3_LISTENER_EVENT_TYPE; 567 | 568 | typedef struct MSH3_LISTENER_EVENT { 569 | MSH3_LISTENER_EVENT_TYPE Type; 570 | union { 571 | struct { 572 | bool AppCloseInProgress : 1; 573 | bool RESERVED : 1; 574 | bool RESERVED_2 : 1; 575 | bool RESERVED_3 : 1; 576 | bool RESERVED_4 : 1; 577 | bool RESERVED_5 : 1; 578 | bool RESERVED_6 : 1; 579 | bool RESERVED_7 : 1; 580 | } SHUTDOWN_COMPLETE; 581 | struct { 582 | MSH3_CONNECTION* Connection; 583 | const char* ServerName; 584 | uint16_t ServerNameLength; 585 | } NEW_CONNECTION; 586 | }; 587 | } MSH3_LISTENER_EVENT; 588 | 589 | typedef 590 | MSH3_STATUS 591 | (MSH3_CALL MSH3_LISTENER_CALLBACK)( 592 | MSH3_LISTENER* Connection, 593 | void* Context, 594 | MSH3_LISTENER_EVENT* Event 595 | ); 596 | 597 | typedef MSH3_LISTENER_CALLBACK *MSH3_LISTENER_CALLBACK_HANDLER; 598 | 599 | MSH3_LISTENER* 600 | MSH3_CALL 601 | MsH3ListenerOpen( 602 | MSH3_API* Api, 603 | const MSH3_ADDR* Address, 604 | const MSH3_LISTENER_CALLBACK_HANDLER Handler, 605 | void* Context 606 | ); 607 | 608 | void 609 | MSH3_CALL 610 | MsH3ListenerClose( 611 | MSH3_LISTENER* Listener 612 | ); 613 | 614 | #if defined(__cplusplus) 615 | } 616 | #endif 617 | 618 | #endif // _MSH3_ 619 | -------------------------------------------------------------------------------- /msh3.hpp: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Microsoft Corporation. 4 | Licensed under the MIT License. 5 | 6 | --*/ 7 | 8 | #pragma once 9 | 10 | #include "msh3.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | using namespace std::chrono_literals; 19 | 20 | #if MSH3_TEST_MODE 21 | #define TEST_DEF(x) = x 22 | #else 23 | #define TEST_DEF(x) 24 | #endif 25 | 26 | #ifndef ARRAYSIZE 27 | #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) 28 | #endif 29 | 30 | struct MsH3Request; 31 | 32 | enum MsH3CleanUpMode { 33 | CleanUpManual, 34 | CleanUpAutoDelete, 35 | }; 36 | 37 | template 38 | struct MsH3Waitable { 39 | T Get() const { return State; } 40 | T GetAndReset() { 41 | std::lock_guard Lock{Mutex}; 42 | auto StateCopy = State; 43 | State = (T)0; 44 | return StateCopy; 45 | } 46 | void Set(T state) { 47 | std::lock_guard Lock{Mutex}; 48 | State = state; 49 | Event.notify_all(); 50 | } 51 | void Reset() { 52 | std::lock_guard Lock{Mutex}; 53 | State = (T)0; 54 | } 55 | // Enhanced safety: get current state with lock 56 | T GetSafe() { 57 | std::lock_guard Lock{Mutex}; 58 | return State; 59 | } 60 | T Wait() { 61 | if (!State) { 62 | std::unique_lock Lock{Mutex}; 63 | Event.wait(Lock, [&]{return State;}); 64 | } 65 | return State; 66 | } 67 | bool WaitFor(uint32_t milliseconds TEST_DEF(250)) { 68 | if (!State) { 69 | std::unique_lock Lock{Mutex}; 70 | return Event.wait_for(Lock, milliseconds*1ms, [&]{return State;}); 71 | } 72 | return true; 73 | } 74 | private: 75 | std::mutex Mutex; 76 | std::condition_variable Event; 77 | T State { (T)0 }; 78 | }; 79 | 80 | struct MsH3EventQueue { 81 | #if _WIN32 82 | HANDLE IOCP; 83 | operator MSH3_EVENTQ* () noexcept { return &IOCP; } 84 | MsH3EventQueue() : IOCP(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)) { } 85 | ~MsH3EventQueue() { CloseHandle(IOCP); } 86 | bool IsValid() const noexcept { return IOCP != nullptr; } 87 | bool Enqueue( 88 | _In_ LPOVERLAPPED lpOverlapped, 89 | _In_ uint32_t dwNumberOfBytesTransferred = 0, 90 | _In_ ULONG_PTR dwCompletionKey = 0 91 | ) noexcept { 92 | return PostQueuedCompletionStatus(IOCP, dwNumberOfBytesTransferred, dwCompletionKey, lpOverlapped); 93 | } 94 | uint32_t Dequeue( 95 | _Out_writes_to_(count, return) MSH3_CQE* events, 96 | _In_ uint32_t count, 97 | _In_ uint32_t waitTime 98 | ) noexcept { 99 | uint32_t result; 100 | if (!GetQueuedCompletionStatusEx(IOCP, events, count, (ULONG*)&result, waitTime, FALSE)) { 101 | return 0; 102 | } 103 | return result; 104 | } 105 | static MSH3_SQE* GetSqe(MSH3_CQE* Cqe) noexcept { 106 | return CONTAINING_RECORD(Cqe->lpOverlapped, MSH3_SQE, Overlapped); 107 | } 108 | #elif __linux__ 109 | // Linux-specific implementation 110 | int EpollFd; 111 | operator MSH3_EVENTQ* () noexcept { return &EpollFd; } 112 | MsH3EventQueue() : EpollFd(epoll_create1(0)) { } 113 | ~MsH3EventQueue() { close(EpollFd); } 114 | bool IsValid() const noexcept { return EpollFd >= 0; } 115 | bool Enqueue( 116 | MSH3_SQE* Sqe 117 | ) noexcept { 118 | return eventfd_write(Sqe->fd, 1) == 0; 119 | } 120 | uint32_t Dequeue( 121 | MSH3_CQE* events, 122 | uint32_t count, 123 | uint32_t waitTime 124 | ) noexcept { 125 | const int timeout = waitTime == UINT32_MAX ? -1 : (int)waitTime; 126 | int result; 127 | do { 128 | result = epoll_wait(EpollFd, events, count, timeout); 129 | } while ((result == -1L) && (errno == EINTR)); 130 | return (uint32_t)result; 131 | } 132 | static MSH3_SQE* GetSqe(MSH3_CQE* Cqe) noexcept { 133 | return (MSH3_SQE*)Cqe->data.ptr; 134 | } 135 | #elif __APPLE__ || __FreeBSD__ 136 | // macOS or FreeBSD-specific implementation 137 | int KqueueFd; 138 | operator MSH3_EVENTQ* () noexcept { return &KqueueFd; } 139 | MsH3EventQueue() : KqueueFd(kqueue()) { } 140 | ~MsH3EventQueue() { close(KqueueFd); } 141 | bool IsValid() const noexcept { return KqueueFd >= 0; } 142 | bool Enqueue( 143 | MSH3_SQE* Sqe 144 | ) noexcept { 145 | struct kevent event = {.ident = Sqe->Handle, .filter = EVFILT_USER, .flags = EV_ADD | EV_ONESHOT, .fflags = NOTE_TRIGGER, .data = 0, .udata = Sqe}; 146 | return kevent(KqueueFd, &event, 1, NULL, 0, NULL) == 0; 147 | } 148 | uint32_t Dequeue( 149 | MSH3_CQE* events, 150 | uint32_t count, 151 | uint32_t waitTime 152 | ) noexcept { 153 | struct timespec timeout = {0, 0}; 154 | if (waitTime != UINT32_MAX) { 155 | timeout.tv_sec = (waitTime / 1000); 156 | timeout.tv_nsec = ((waitTime % 1000) * 1000000); 157 | } 158 | int result; 159 | do { 160 | result = kevent(KqueueFd, NULL, 0, events, count, waitTime == UINT32_MAX ? NULL : &timeout); 161 | } while ((result == -1L) && (errno == EINTR)); 162 | return (uint32_t)result; 163 | } 164 | static MSH3_SQE* GetSqe(MSH3_CQE* Cqe) noexcept { 165 | return (MSH3_SQE*)Cqe->udata; 166 | } 167 | #endif // _WIN32 168 | void CompleteEvents(uint32_t WaitTime) noexcept { 169 | MSH3_CQE Events[8]; 170 | uint32_t EventCount = Dequeue(Events, ARRAYSIZE(Events), WaitTime); 171 | for (uint32_t i = 0; i < EventCount; ++i) { 172 | GetSqe(&Events[i])->Completion(&Events[i]); 173 | } 174 | } 175 | }; 176 | 177 | struct MsH3Api { 178 | MSH3_API* Handle { nullptr }; 179 | MsH3Api() noexcept : Handle(MsH3ApiOpen()) { } 180 | #ifdef MSH3_API_ENABLE_PREVIEW_FEATURES 181 | MsH3Api( 182 | uint32_t ExecutionConfigCount, 183 | MSH3_EXECUTION_CONFIG* ExecutionConfigs, 184 | MSH3_EXECUTION** Executions 185 | ) noexcept : Handle(MsH3ApiOpenWithExecution(ExecutionConfigCount, ExecutionConfigs, Executions)) { 186 | } 187 | uint32_t Poll(MSH3_EXECUTION* Execution) noexcept { 188 | return MsH3ApiPoll(Execution); 189 | } 190 | #endif 191 | ~MsH3Api() noexcept { if (Handle) { MsH3ApiClose(Handle); } } 192 | bool IsValid() const noexcept { return Handle != nullptr; } 193 | operator MSH3_API* () const noexcept { return Handle; } 194 | }; 195 | 196 | struct MsH3Configuration { 197 | MSH3_CONFIGURATION* Handle { nullptr }; 198 | MsH3Configuration(MsH3Api& Api) { 199 | Handle = MsH3ConfigurationOpen(Api, nullptr, 0); 200 | } 201 | MsH3Configuration(MsH3Api& Api, const MSH3_SETTINGS* Settings) { 202 | Handle = MsH3ConfigurationOpen(Api, Settings, sizeof(*Settings)); 203 | } 204 | ~MsH3Configuration() noexcept { if (Handle) { MsH3ConfigurationClose(Handle); } } 205 | bool IsValid() const noexcept { return Handle != nullptr; } 206 | operator MSH3_CONFIGURATION* () const noexcept { return Handle; } 207 | #if MSH3_TEST_MODE 208 | MSH3_STATUS LoadConfiguration() noexcept { 209 | const MSH3_CREDENTIAL_CONFIG Config = { MSH3_CREDENTIAL_TYPE_SELF_SIGNED_CERTIFICATE }; 210 | return MsH3ConfigurationLoadCredential(Handle, &Config); 211 | } 212 | #endif 213 | MSH3_STATUS LoadConfiguration(const MSH3_CREDENTIAL_CONFIG& Config) noexcept { 214 | return MsH3ConfigurationLoadCredential(Handle, &Config); 215 | } 216 | }; 217 | 218 | struct MsH3Addr { 219 | MSH3_ADDR Addr {0}; 220 | MsH3Addr(uint16_t Port TEST_DEF(4433)) { 221 | SetPort(Port); 222 | } 223 | operator const MSH3_ADDR* () const noexcept { return &Addr; } 224 | void SetPort(uint16_t Port) noexcept { MSH3_SET_PORT(&Addr, Port); } 225 | }; 226 | 227 | typedef MSH3_STATUS MsH3ListenerCallback( 228 | struct MsH3Listener* Listener, 229 | void* Context, 230 | MSH3_LISTENER_EVENT* Event 231 | ); 232 | 233 | struct MsH3Listener { 234 | MSH3_LISTENER* Handle { nullptr }; 235 | MsH3CleanUpMode CleanUpMode; 236 | MsH3ListenerCallback* Callback{ nullptr }; 237 | void* Context{ nullptr }; 238 | MsH3Listener( 239 | MsH3Api& Api, 240 | const MsH3Addr& Address, 241 | MsH3CleanUpMode CleanUpMode, 242 | MsH3ListenerCallback* Callback, 243 | void* Context = nullptr 244 | ) noexcept : CleanUpMode(CleanUpMode), Callback(Callback), Context(Context) { 245 | Handle = MsH3ListenerOpen(Api, Address, (MSH3_LISTENER_CALLBACK_HANDLER)MsH3Callback, this); 246 | } 247 | ~MsH3Listener() noexcept { if (Handle) { MsH3ListenerClose(Handle); } } 248 | MsH3Listener(MsH3Listener& other) = delete; 249 | MsH3Listener operator=(MsH3Listener& Other) = delete; 250 | bool IsValid() const noexcept { return Handle != nullptr; } 251 | operator MSH3_LISTENER* () const noexcept { return Handle; } 252 | private: 253 | static 254 | MSH3_STATUS 255 | MsH3Callback( 256 | MSH3_LISTENER* /* Listener */, 257 | MsH3Listener* pThis, 258 | MSH3_LISTENER_EVENT* Event 259 | ) noexcept { 260 | auto DeleteOnExit = 261 | Event->Type == MSH3_LISTENER_EVENT_SHUTDOWN_COMPLETE && 262 | pThis->CleanUpMode == CleanUpAutoDelete; 263 | auto Status = pThis->Callback(pThis, pThis->Context, Event); 264 | if (DeleteOnExit) { 265 | delete pThis; 266 | } 267 | return Status; 268 | } 269 | }; 270 | 271 | typedef MSH3_STATUS MsH3ConnectionCallback( 272 | struct MsH3Connection* Connection, 273 | void* Context, 274 | MSH3_CONNECTION_EVENT* Event 275 | ); 276 | 277 | struct MsH3Connection { 278 | MSH3_CONNECTION* Handle { nullptr }; 279 | MsH3Waitable Connected; 280 | MsH3Waitable ShutdownComplete; 281 | MsH3Connection( 282 | MsH3Api& Api, 283 | MsH3CleanUpMode CleanUpMode = CleanUpManual, 284 | MsH3ConnectionCallback* Callback = NoOpCallback, 285 | void* Context = nullptr 286 | ) noexcept : CleanUp(CleanUpMode), Callback(Callback), Context(Context) { 287 | Handle = MsH3ConnectionOpen(Api, (MSH3_CONNECTION_CALLBACK_HANDLER)MsH3Callback, this); 288 | } 289 | MsH3Connection( 290 | MSH3_CONNECTION* ServerHandle, 291 | MsH3CleanUpMode CleanUpMode, 292 | MsH3ConnectionCallback* Callback, 293 | void* Context = nullptr 294 | ) noexcept : Handle(ServerHandle), CleanUp(CleanUpMode), Callback(Callback), Context(Context) { 295 | MsH3ConnectionSetCallbackHandler(Handle, (MSH3_CONNECTION_CALLBACK_HANDLER)MsH3Callback, this); 296 | } 297 | virtual ~MsH3Connection() noexcept { Close(); } 298 | MsH3Connection(MsH3Connection& other) = delete; 299 | MsH3Connection operator=(MsH3Connection& Other) = delete; 300 | bool IsValid() const noexcept { return Handle != nullptr; } 301 | operator MSH3_CONNECTION* () const noexcept { return Handle; } 302 | void Close() noexcept { 303 | #ifdef _WIN32 304 | auto HandleToClose = (MSH3_CONNECTION*)InterlockedExchangePointer((PVOID*)&Handle, NULL); 305 | #else 306 | auto HandleToClose = (MSH3_CONNECTION*)__sync_fetch_and_and(&Handle, 0); 307 | #endif 308 | if (HandleToClose) { 309 | MsH3ConnectionClose(HandleToClose); 310 | } 311 | } 312 | MSH3_STATUS SetConfiguration(const MsH3Configuration& Configuration) noexcept { 313 | return MsH3ConnectionSetConfiguration(Handle, Configuration); 314 | } 315 | MSH3_STATUS Start( 316 | const MsH3Configuration& Configuration, 317 | const char* ServerName TEST_DEF("localhost"), 318 | const MsH3Addr& ServerAddress TEST_DEF(MsH3Addr()) 319 | ) noexcept { 320 | return MsH3ConnectionStart(Handle, Configuration, ServerName, ServerAddress); 321 | } 322 | void Shutdown(uint64_t ErrorCode = 0) noexcept { 323 | MsH3ConnectionShutdown(Handle, ErrorCode); 324 | } 325 | static 326 | MSH3_STATUS 327 | NoOpCallback( 328 | MsH3Connection* /* Connection */, 329 | void* /* Context */, 330 | MSH3_CONNECTION_EVENT* Event 331 | ) noexcept { 332 | if (Event->Type == MSH3_CONNECTION_EVENT_NEW_REQUEST) { 333 | // 334 | // Not great beacuse it doesn't provide an application specific 335 | // error code. If you expect to get streams, you should not no-op 336 | // the callbacks. 337 | // 338 | MsH3RequestClose(Event->NEW_REQUEST.Request); 339 | } 340 | return MSH3_STATUS_SUCCESS; 341 | } 342 | private: 343 | const MsH3CleanUpMode CleanUp; 344 | MsH3ConnectionCallback* Callback; 345 | void* Context; 346 | static 347 | MSH3_STATUS 348 | MsH3Callback( 349 | MSH3_CONNECTION* /* Connection */, 350 | MsH3Connection* pThis, 351 | MSH3_CONNECTION_EVENT* Event 352 | ) noexcept { 353 | if (Event->Type == MSH3_CONNECTION_EVENT_CONNECTED) { 354 | pThis->Connected.Set(true); 355 | } else if (Event->Type == MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE) { 356 | pThis->ShutdownComplete.Set(true); 357 | } 358 | auto DeleteOnExit = 359 | Event->Type == MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE && 360 | pThis->CleanUp == CleanUpAutoDelete; 361 | auto Status = pThis->Callback(pThis, pThis->Context, Event); 362 | if (DeleteOnExit) { 363 | delete pThis; 364 | } 365 | return Status; 366 | } 367 | }; 368 | 369 | struct MsH3AutoAcceptListener : public MsH3Listener { 370 | const MsH3Configuration* Configuration; 371 | MsH3ConnectionCallback* ConnectionHandler; 372 | void* ConnectionContext; 373 | MsH3Waitable NewConnection; 374 | 375 | MsH3AutoAcceptListener( 376 | MsH3Api& Api, 377 | const MsH3Addr& Address, 378 | MsH3ConnectionCallback* _ConnectionHandler, 379 | void* _ConnectionContext = nullptr 380 | ) noexcept : 381 | MsH3Listener(Api, Address, CleanUpManual, ListenerCallback, this), 382 | Configuration(nullptr), 383 | ConnectionHandler(_ConnectionHandler), 384 | ConnectionContext(_ConnectionContext) 385 | { } 386 | 387 | MsH3AutoAcceptListener( 388 | MsH3Api& Api, 389 | const MsH3Addr& Address, 390 | const MsH3Configuration& Config, 391 | MsH3ConnectionCallback* _ConnectionHandler, 392 | void* _ConnectionContext = nullptr 393 | ) noexcept : 394 | MsH3Listener(Api, Address, CleanUpManual, ListenerCallback, this), 395 | Configuration(&Config), 396 | ConnectionHandler(_ConnectionHandler), 397 | ConnectionContext(_ConnectionContext) 398 | { } 399 | 400 | private: 401 | 402 | static 403 | MSH3_STATUS 404 | ListenerCallback( 405 | MsH3Listener* /* Listener */, 406 | void* Context, 407 | MSH3_LISTENER_EVENT* Event 408 | ) noexcept { 409 | auto pThis = (MsH3AutoAcceptListener*)Context; 410 | #ifdef _WIN32 411 | MSH3_STATUS Status = E_NOT_VALID_STATE; 412 | #else 413 | MSH3_STATUS Status = EINVAL; 414 | #endif 415 | if (Event->Type == MSH3_LISTENER_EVENT_NEW_CONNECTION) { 416 | auto Connection = new(std::nothrow) MsH3Connection(Event->NEW_CONNECTION.Connection, CleanUpAutoDelete, pThis->ConnectionHandler, pThis->ConnectionContext); 417 | if (Connection) { 418 | if (pThis->Configuration && 419 | MSH3_FAILED(Status = Connection->SetConfiguration(*pThis->Configuration))) { 420 | // 421 | // The connection is being rejected. Let the library free the handle. 422 | // 423 | Connection->Handle = nullptr; 424 | delete Connection; 425 | } else { 426 | Status = MSH3_STATUS_SUCCESS; 427 | pThis->NewConnection.Set(Connection); 428 | } 429 | } 430 | } 431 | return Status; 432 | } 433 | }; 434 | 435 | typedef MSH3_STATUS MsH3RequestCallback( 436 | struct MsH3Request* Request, 437 | void* Context, 438 | MSH3_REQUEST_EVENT* Event 439 | ); 440 | 441 | struct MsH3Request { 442 | MSH3_REQUEST* Handle { nullptr }; 443 | MsH3CleanUpMode CleanUpMode; 444 | MsH3RequestCallback* Callback; 445 | void* Context; 446 | MsH3Waitable ShutdownComplete; 447 | bool Aborted {false}; 448 | uint64_t AbortError {0}; 449 | MsH3Request( 450 | MsH3Connection& Connection, 451 | MSH3_REQUEST_FLAGS Flags = MSH3_REQUEST_FLAG_NONE, 452 | MsH3CleanUpMode CleanUpMode = CleanUpManual, 453 | MsH3RequestCallback* Callback = NoOpCallback, 454 | void* Context = nullptr 455 | ) noexcept : CleanUpMode(CleanUpMode), Callback(Callback), Context(Context) { 456 | if (Connection.IsValid()) { 457 | Handle = MsH3RequestOpen(Connection, (MSH3_REQUEST_CALLBACK_HANDLER)MsH3Callback, this, Flags); 458 | } 459 | } 460 | MsH3Request( 461 | MSH3_REQUEST* ServerHandle, 462 | MsH3CleanUpMode CleanUpMode, 463 | MsH3RequestCallback* Callback = NoOpCallback, 464 | void* Context = nullptr 465 | ) noexcept : Handle(ServerHandle), CleanUpMode(CleanUpMode), Callback(Callback), Context(Context) { 466 | MsH3RequestSetCallbackHandler(Handle, (MSH3_REQUEST_CALLBACK_HANDLER)MsH3Callback, this); 467 | } 468 | ~MsH3Request() noexcept { Close(); } 469 | MsH3Request(MsH3Request& other) = delete; 470 | MsH3Request operator=(MsH3Request& Other) = delete; 471 | bool IsValid() const noexcept { return Handle != nullptr; } 472 | operator MSH3_REQUEST* () const noexcept { return Handle; } 473 | void 474 | Close() noexcept { 475 | #ifdef _WIN32 476 | auto HandleToClose = (MSH3_REQUEST*)InterlockedExchangePointer((PVOID*)&Handle, NULL); 477 | #else 478 | auto HandleToClose = (MSH3_REQUEST*)__sync_fetch_and_and(&Handle, 0); 479 | #endif 480 | if (HandleToClose) { 481 | MsH3RequestClose(HandleToClose); 482 | } 483 | } 484 | void CompleteReceive(uint32_t Length) noexcept { 485 | MsH3RequestCompleteReceive(Handle, Length); 486 | }; 487 | void SetReceiveEnabled(bool Enabled) noexcept { 488 | MsH3RequestSetReceiveEnabled(Handle, Enabled); 489 | }; 490 | bool Send( 491 | const MSH3_HEADER* Headers, 492 | size_t HeadersCount, 493 | const void* Data = nullptr, 494 | uint32_t DataLength = 0, 495 | MSH3_REQUEST_SEND_FLAGS Flags = MSH3_REQUEST_SEND_FLAG_NONE, 496 | void* SendContext = nullptr 497 | ) noexcept { 498 | return MsH3RequestSend(Handle, Flags, Headers, HeadersCount, Data, DataLength, SendContext); 499 | } 500 | void Shutdown( 501 | MSH3_REQUEST_SHUTDOWN_FLAGS Flags, 502 | uint64_t _AbortError = 0 503 | ) noexcept { 504 | return MsH3RequestShutdown(Handle, Flags, _AbortError); 505 | } 506 | static 507 | MSH3_STATUS 508 | NoOpCallback( 509 | MsH3Request* /* Request */, 510 | void* /* Context */, 511 | MSH3_REQUEST_EVENT* /* Event */ 512 | ) noexcept { 513 | return MSH3_STATUS_SUCCESS; 514 | } 515 | private: 516 | static 517 | MSH3_STATUS 518 | MsH3Callback( 519 | MSH3_REQUEST* /* Request */, 520 | MsH3Request* pThis, 521 | MSH3_REQUEST_EVENT* Event 522 | ) noexcept { 523 | if (Event->Type == MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE) { 524 | pThis->ShutdownComplete.Set(true); 525 | } 526 | auto DeleteOnExit = 527 | Event->Type == MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE && 528 | pThis->CleanUpMode == CleanUpAutoDelete; 529 | auto Status = pThis->Callback(pThis, pThis->Context, Event); 530 | if (DeleteOnExit) { 531 | delete pThis; 532 | } 533 | return Status; 534 | } 535 | }; 536 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | set(SOURCES msh3test.cpp) 5 | add_executable(msh3test ${SOURCES}) 6 | find_package(Threads REQUIRED) 7 | target_link_libraries(msh3test msh3 ${CMAKE_THREAD_LIBS_INIT}) 8 | install(TARGETS msh3test EXPORT msh3 RUNTIME DESTINATION bin) 9 | -------------------------------------------------------------------------------- /test/msh3test.cpp: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Microsoft Corporation. 4 | Licensed under the MIT License. 5 | 6 | --*/ 7 | 8 | #define MSH3_TEST_MODE 1 9 | #define MSH3_API_ENABLE_PREVIEW_FEATURES 1 10 | 11 | #include "msh3.hpp" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include // For atoi 19 | #include // For std::min 20 | #include // For watchdog timer thread 21 | #include // For timing 22 | #include // For thread communication 23 | 24 | // Global flags for command line options 25 | bool g_Verbose = false; 26 | const char* g_TestFilter = nullptr; 27 | uint32_t g_WatchdogTimeoutMs = 5000; // Default to 5 seconds 28 | MsH3Waitable g_TestAllDone; 29 | std::atomic_int g_ConnectionCount = 0; 30 | MsH3Waitable g_ConnectionsComplete; 31 | 32 | // Helper function to print logs when in verbose mode 33 | #define LOG(...) if (g_Verbose) { printf(__VA_ARGS__); fflush(stdout); } 34 | 35 | // Helper function to check if a string matches a pattern with wildcard (*) 36 | bool WildcardMatch(const char* pattern, const char* str) { 37 | // Case insensitive matching with * wildcard support 38 | if (!pattern || !str) return false; 39 | 40 | // Empty pattern matches only empty string 41 | if (*pattern == '\0') return *str == '\0'; 42 | 43 | // Special case: '*' matches everything 44 | if (*pattern == '*' && *(pattern+1) == '\0') return true; 45 | 46 | while (*pattern && *str) { 47 | if (*pattern == '*') { 48 | // Skip consecutive wildcards 49 | while (*(pattern+1) == '*') pattern++; 50 | 51 | // If this is the last character in pattern, we match 52 | if (*(pattern+1) == '\0') return true; 53 | 54 | // Try to match the rest of the pattern with different positions of str 55 | pattern++; 56 | while (*str) { 57 | if (WildcardMatch(pattern, str)) return true; 58 | str++; 59 | } 60 | return false; 61 | } else if (tolower(*pattern) == tolower(*str)) { 62 | // Characters match (case insensitive) 63 | pattern++; 64 | str++; 65 | } else { 66 | // No match 67 | return false; 68 | } 69 | } 70 | 71 | // Check if remaining pattern consists only of wildcards 72 | while (*pattern == '*') pattern++; 73 | 74 | return (*pattern == '\0' && *str == '\0'); 75 | } 76 | 77 | struct TestFunc { 78 | bool (*Func)(void); 79 | const char* Name; 80 | }; 81 | #define DEF_TEST(X) bool Test##X() 82 | #define ADD_TEST(X) { Test##X, #X } 83 | #define VERIFY(X) \ 84 | do { \ 85 | bool _result = (X); \ 86 | if (g_Verbose) { \ 87 | printf("%s: %s on %s:%d\n", _result ? "PASS" : "FAIL", #X, __FILE__, __LINE__); \ 88 | fflush(stdout); \ 89 | } \ 90 | if (!_result) { \ 91 | fprintf(stderr, #X " Failed on %s:%d!\n", __FILE__, __LINE__); \ 92 | return false; \ 93 | } \ 94 | } while (0) 95 | #define VERIFY_SUCCESS(X) \ 96 | do { \ 97 | auto _status = X; \ 98 | if (g_Verbose) { \ 99 | printf("%s: %s on %s:%d\n", MSH3_FAILED(_status) ? "FAIL" : "PASS", #X, __FILE__, __LINE__); \ 100 | fflush(stdout); \ 101 | } \ 102 | if (MSH3_FAILED(_status)) { \ 103 | fprintf(stderr, #X " Failed with %u on %s:%d!\n", (uint32_t)_status, __FILE__, __LINE__); \ 104 | return false; \ 105 | } \ 106 | } while (0) 107 | 108 | const MSH3_HEADER RequestHeaders[] = { 109 | { ":method", 7, "GET", 3 }, 110 | { ":path", 5, "/", 1 }, 111 | { ":scheme", 7, "https", 5 }, 112 | { ":authority", 10, "localhost", 9 }, 113 | { "user-agent", 10, "msh3test", 8 }, 114 | { "accept", 6, "*/*", 3 }, 115 | }; 116 | const size_t RequestHeadersCount = sizeof(RequestHeaders)/sizeof(MSH3_HEADER); 117 | 118 | const MSH3_HEADER ResponseHeaders[] = { 119 | { ":status", 7, "200", 3 }, 120 | { "content-type", 12, "application/json", 16 }, 121 | }; 122 | const size_t ResponseHeadersCount = sizeof(ResponseHeaders)/sizeof(MSH3_HEADER); 123 | 124 | const char ResponseData[] = "HELLO WORLD!\n"; 125 | 126 | 127 | // Add more types of request headers for testing 128 | const MSH3_HEADER PostRequestHeaders[] = { 129 | { ":method", 7, "POST", 4 }, 130 | { ":path", 5, "/upload", 7 }, 131 | { ":scheme", 7, "https", 5 }, 132 | { ":authority", 10, "localhost", 9 }, 133 | { "user-agent", 10, "msh3test", 8 }, 134 | { "content-type", 12, "application/json", 16 }, 135 | { "content-length", 14, "13", 2 }, 136 | }; 137 | const size_t PostRequestHeadersCount = sizeof(PostRequestHeaders)/sizeof(MSH3_HEADER); 138 | 139 | const MSH3_HEADER PutRequestHeaders[] = { 140 | { ":method", 7, "PUT", 3 }, 141 | { ":path", 5, "/resource", 9 }, 142 | { ":scheme", 7, "https", 5 }, 143 | { ":authority", 10, "localhost", 9 }, 144 | { "user-agent", 10, "msh3test", 8 }, 145 | { "content-type", 12, "text/plain", 10 }, 146 | { "content-length", 14, "11", 2 }, 147 | }; 148 | const size_t PutRequestHeadersCount = sizeof(PutRequestHeaders)/sizeof(MSH3_HEADER); 149 | 150 | // Add more response header types for testing 151 | const MSH3_HEADER Response201Headers[] = { 152 | { ":status", 7, "201", 3 }, 153 | { "location", 8, "/resource/123", 13 }, 154 | }; 155 | const size_t Response201HeadersCount = sizeof(Response201Headers)/sizeof(MSH3_HEADER); 156 | 157 | const MSH3_HEADER Response404Headers[] = { 158 | { ":status", 7, "404", 3 }, 159 | { "content-type", 12, "text/plain", 10 }, 160 | }; 161 | const size_t Response404HeadersCount = sizeof(Response404Headers)/sizeof(MSH3_HEADER); 162 | 163 | const MSH3_HEADER Response500Headers[] = { 164 | { ":status", 7, "500", 3 }, 165 | { "content-type", 12, "text/plain", 10 }, 166 | }; 167 | const size_t Response500HeadersCount = sizeof(Response500Headers)/sizeof(MSH3_HEADER); 168 | 169 | const char JsonRequestData[] = "{\"test\":\"data\"}"; 170 | const char TextRequestData[] = "Hello World"; 171 | 172 | const char* ToString(MSH3_CONNECTION_EVENT_TYPE Type) { 173 | switch (Type) { 174 | case MSH3_CONNECTION_EVENT_CONNECTED: return "CONNECTED"; 175 | case MSH3_CONNECTION_EVENT_NEW_REQUEST: return "NEW_REQUEST"; 176 | case MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: return "SHUTDOWN_INITIATED_BY_TRANSPORT"; 177 | case MSH3_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: return "SHUTDOWN_INITIATED_BY_PEER"; 178 | case MSH3_CONNECTION_EVENT_SHUTDOWN_COMPLETE: return "SHUTDOWN_COMPLETE"; 179 | default: return "UNKNOWN"; 180 | } 181 | } 182 | 183 | const char* ToString(MSH3_REQUEST_EVENT_TYPE Type) { 184 | switch (Type) { 185 | case MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE: return "SHUTDOWN_COMPLETE"; 186 | case MSH3_REQUEST_EVENT_HEADER_RECEIVED: return "HEADER_RECEIVED"; 187 | case MSH3_REQUEST_EVENT_DATA_RECEIVED: return "DATA_RECEIVED"; 188 | case MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN: return "PEER_SEND_SHUTDOWN"; 189 | case MSH3_REQUEST_EVENT_PEER_SEND_ABORTED: return "PEER_SEND_ABORTED"; 190 | case MSH3_REQUEST_EVENT_IDEAL_SEND_SIZE: return "IDEAL_SEND_SIZE"; 191 | case MSH3_REQUEST_EVENT_SEND_COMPLETE: return "SEND_COMPLETE"; 192 | case MSH3_REQUEST_EVENT_SEND_SHUTDOWN_COMPLETE: return "SEND_SHUTDOWN_COMPLETE"; 193 | case MSH3_REQUEST_EVENT_PEER_RECEIVE_ABORTED: return "PEER_RECEIVE_ABORTED"; 194 | default: return "UNKNOWN"; 195 | } 196 | } 197 | 198 | struct TestRequest : public MsH3Request { 199 | const char* Role; 200 | TestRequest(MsH3Connection& Connection, MsH3CleanUpMode CleanUpMode = CleanUpManual) 201 | : MsH3Request(Connection, MSH3_REQUEST_FLAG_NONE, CleanUpMode, RequestCallback, this), Role("CLIENT") { 202 | LOG("%s TestRequest constructed\n", Role); 203 | } 204 | TestRequest( 205 | MSH3_REQUEST* ServerHandle, 206 | MsH3CleanUpMode CleanUpMode 207 | ) noexcept : MsH3Request(ServerHandle, CleanUpMode, RequestCallback, this), Role("SERVER") { 208 | LOG("%s TestRequest constructed\n", Role); 209 | } 210 | ~TestRequest() noexcept { LOG("~TestRequest\n"); } 211 | 212 | struct StoredHeader { 213 | std::string Name; 214 | std::string Value; 215 | StoredHeader(const char* name, size_t nameLen, const char* value, size_t valueLen) 216 | : Name(name, nameLen), Value(value, valueLen) {} 217 | }; 218 | 219 | // Set of all the headers received 220 | std::vector Headers; 221 | MsH3Waitable AllDataSent; // Signal when all data has been sent 222 | MsH3Waitable AllHeadersReceived; // Signal when headers are complete (data received) 223 | MsH3Waitable AllDataReceived; // Signal when all data has been received 224 | MsH3Waitable LatestDataReceived; // Signal when latest data has been received 225 | uint64_t TotalDataReceived = 0; // Total data received in bytes 226 | bool PeerSendComplete = false; // Flag to track if peer send was gracefully completed 227 | bool PeerSendAborted = false; // Flag to track if peer send was aborted 228 | bool HandleReceivesAsync = false; 229 | bool CompleteAsyncReceivesInline = false; 230 | 231 | // Helper to get the first header by name 232 | StoredHeader* GetHeaderByName(const char* name, size_t nameLength) { 233 | std::string nameStr(name, nameLength); 234 | for (auto& header : Headers) { 235 | if (header.Name == nameStr) { 236 | return &header; 237 | } 238 | } 239 | return nullptr; 240 | } 241 | 242 | // Check if we have a specific number of headers 243 | bool HasExpectedHeaderCount(uint32_t expected) { 244 | return Headers.size() == expected; 245 | } 246 | 247 | // Helper to get the status code from headers 248 | uint32_t GetStatusCode() { 249 | auto statusHeader = GetHeaderByName(":status", 7); 250 | if (statusHeader && !statusHeader->Value.empty()) { 251 | // Convert status code string to integer 252 | return atoi(statusHeader->Value.c_str()); 253 | } 254 | return 0; // Return 0 if status header not found or invalid 255 | } 256 | 257 | static MSH3_STATUS RequestCallback( 258 | struct MsH3Request* Request, 259 | void* Context, 260 | MSH3_REQUEST_EVENT* Event 261 | ) { 262 | // First check if input is valid 263 | if (!Request || !Context || !Event) { 264 | LOG("Warning: Invalid RequestCallback input\n"); 265 | return MSH3_STATUS_SUCCESS; 266 | } 267 | 268 | auto ctx = (TestRequest*)Context; 269 | LOG("%s RequestEvent: %s\n", ctx->Role, ToString(Event->Type)); 270 | 271 | if (Event->Type == MSH3_REQUEST_EVENT_HEADER_RECEIVED) { 272 | const MSH3_HEADER* header = Event->HEADER_RECEIVED.Header; 273 | 274 | // Validate the header 275 | if (!header || !header->Name || header->NameLength == 0 || !header->Value) { 276 | LOG("%s Warning: Received invalid header\n", ctx->Role); 277 | return MSH3_STATUS_SUCCESS; 278 | } 279 | 280 | // Save the header data 281 | ctx->Headers.emplace_back( 282 | header->Name, header->NameLength, header->Value, header->ValueLength); 283 | 284 | LOG("%s Processed header: '%s'\n", ctx->Role, ctx->Headers.back().Name.c_str()); 285 | 286 | } else if (Event->Type == MSH3_REQUEST_EVENT_DATA_RECEIVED) { 287 | LOG("%s Data received: %u bytes\n", ctx->Role, Event->DATA_RECEIVED.Length); 288 | if (!ctx->AllHeadersReceived.Get()) { 289 | // Signal that all headers have been received (since data always comes after headers) 290 | LOG("%s Request headers complete\n", ctx->Role); 291 | ctx->AllHeadersReceived.Set(true); 292 | } 293 | 294 | ctx->TotalDataReceived += Event->DATA_RECEIVED.Length; 295 | ctx->LatestDataReceived.Set(Event->DATA_RECEIVED.Length); 296 | 297 | if (ctx->HandleReceivesAsync) { 298 | if (ctx->CompleteAsyncReceivesInline) { 299 | LOG("%s Completing async receive inline\n", ctx->Role); 300 | Request->CompleteReceive(Event->DATA_RECEIVED.Length); 301 | } 302 | return MSH3_STATUS_PENDING; 303 | } 304 | 305 | } else if (Event->Type == MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN) { 306 | if (!ctx->AllHeadersReceived.Get()) { 307 | // Signal that all headers have been received (since data always comes after headers) 308 | LOG("%s Request headers complete\n", ctx->Role); 309 | ctx->AllHeadersReceived.Set(true); 310 | } 311 | ctx->PeerSendComplete = true; 312 | ctx->AllDataReceived.Set(true); 313 | 314 | } else if (Event->Type == MSH3_REQUEST_EVENT_PEER_SEND_ABORTED) { 315 | // Handle case where request completes without data 316 | ctx->PeerSendAborted = true; 317 | if (!ctx->AllHeadersReceived.Get()) { 318 | // Signal that all headers have been received (since data always comes after headers) 319 | LOG("%s Request headers complete\n", ctx->Role); 320 | ctx->AllHeadersReceived.Set(true); 321 | } 322 | ctx->AllDataReceived.Set(true); 323 | 324 | } else if (Event->Type == MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE) { 325 | if (!ctx->AllHeadersReceived.Get()) { 326 | // Signal that all headers have been received (since data always comes after headers) 327 | LOG("%s Request headers complete\n", ctx->Role); 328 | ctx->AllHeadersReceived.Set(true); 329 | } 330 | if (!ctx->AllDataReceived.Get()) { 331 | // Signal that all headers have been received (since data always comes after headers) 332 | LOG("%s Data complete\n", ctx->Role); 333 | ctx->AllDataReceived.Set(true); 334 | } 335 | } else if (Event->Type == MSH3_REQUEST_EVENT_SEND_SHUTDOWN_COMPLETE) { 336 | if (!ctx->AllDataSent.Get()) { 337 | ctx->AllDataSent.Set(true); 338 | } 339 | } 340 | 341 | return MSH3_STATUS_SUCCESS; 342 | } 343 | }; 344 | 345 | struct TestConnection : public MsH3Connection { 346 | TestConnection( 347 | MsH3Api& Api, 348 | MsH3CleanUpMode CleanUpMode = CleanUpManual, 349 | MsH3ConnectionCallback* Callback = NoOpCallback, 350 | void* Context = nullptr 351 | ) noexcept : MsH3Connection(Api, CleanUpMode, Callback, Context) { 352 | g_ConnectionCount.fetch_add(1); 353 | LOG("TestConnection created (client)\n"); 354 | } 355 | TestConnection( 356 | MSH3_CONNECTION* ServerHandle, 357 | MsH3CleanUpMode CleanUpMode, 358 | MsH3ConnectionCallback* Callback, 359 | void* Context = nullptr 360 | ) noexcept : MsH3Connection(ServerHandle, CleanUpMode, Callback, Context) { 361 | g_ConnectionCount.fetch_add(1); 362 | LOG("TestConnection created (server)\n"); 363 | } 364 | virtual ~TestConnection() noexcept { 365 | if (g_ConnectionCount.fetch_sub(1) == 1) { 366 | LOG("All connections closed, signaling completion\n"); 367 | g_ConnectionsComplete.Set(true); 368 | } 369 | LOG("~TestConnection\n"); 370 | } 371 | }; 372 | 373 | struct TestServer : public MsH3Listener { 374 | bool AutoConfigure; // Automatically configure the server 375 | MsH3Configuration Configuration; 376 | MsH3Waitable NewConnection; 377 | MsH3Waitable NewRequest; 378 | TestServer(MsH3Api& Api, bool AutoConfigure = true) 379 | : MsH3Listener(Api, MsH3Addr(), CleanUpAutoDelete, ListenerCallback, this), Configuration(Api), AutoConfigure(AutoConfigure) { 380 | if (Handle && MSH3_FAILED(Configuration.LoadConfiguration())) { 381 | MsH3ListenerClose(Handle); Handle = nullptr; 382 | } 383 | } 384 | ~TestServer() noexcept { LOG("~TestServer\n"); } 385 | bool WaitForConnection() noexcept { 386 | VERIFY(NewConnection.WaitFor()); 387 | auto ServerConnection = NewConnection.Get(); 388 | VERIFY(ServerConnection->Connected.WaitFor()); 389 | return true; 390 | } 391 | static 392 | MSH3_STATUS 393 | ListenerCallback( 394 | MsH3Listener* /* Listener */, 395 | void* Context, 396 | MSH3_LISTENER_EVENT* Event 397 | ) noexcept { 398 | auto pThis = (TestServer*)Context; 399 | MSH3_STATUS Status = MSH3_STATUS_INVALID_STATE; 400 | if (Event->Type == MSH3_LISTENER_EVENT_NEW_CONNECTION) { 401 | auto Connection = new(std::nothrow) TestConnection(Event->NEW_CONNECTION.Connection, CleanUpAutoDelete, ConnectionCallback, pThis); 402 | if (Connection) { 403 | if (pThis->AutoConfigure && 404 | MSH3_FAILED(Status = Connection->SetConfiguration(pThis->Configuration))) { 405 | // 406 | // The connection is being rejected. Let the library free the handle. 407 | // 408 | Connection->Handle = nullptr; 409 | delete Connection; 410 | } else { 411 | Status = MSH3_STATUS_SUCCESS; 412 | pThis->NewConnection.Set(Connection); 413 | } 414 | } 415 | } 416 | return Status; 417 | } 418 | static 419 | MSH3_STATUS 420 | ConnectionCallback( 421 | MsH3Connection* /* Connection */, 422 | void* Context, 423 | MSH3_CONNECTION_EVENT* Event 424 | ) noexcept { 425 | auto pThis = (TestServer*)Context; 426 | LOG("SERVER ConnectionEvent: %s\n", ToString(Event->Type)); 427 | if (Event->Type == MSH3_CONNECTION_EVENT_NEW_REQUEST) { 428 | auto Request = new (std::nothrow) TestRequest(Event->NEW_REQUEST.Request, CleanUpAutoDelete); 429 | pThis->NewRequest.Set(Request); 430 | } 431 | return MSH3_STATUS_SUCCESS; 432 | } 433 | }; 434 | 435 | const MSH3_CREDENTIAL_CONFIG ClientCredConfig = { 436 | MSH3_CREDENTIAL_TYPE_NONE, 437 | MSH3_CREDENTIAL_FLAG_CLIENT | MSH3_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION, 438 | nullptr 439 | }; 440 | 441 | struct TestClient : public TestConnection { 442 | bool SingleThreaded; 443 | MsH3Configuration Config; 444 | TestClient(MsH3Api& Api, bool SingleThread = false, MsH3CleanUpMode CleanUpMode = CleanUpManual) 445 | : TestConnection(Api, CleanUpMode, Callbacks), Config(Api), SingleThreaded(SingleThread) { 446 | if (Handle && MSH3_FAILED(Config.LoadConfiguration(ClientCredConfig))) { 447 | MsH3ConnectionClose(Handle); Handle = nullptr; 448 | } 449 | } 450 | virtual ~TestClient() noexcept { 451 | Shutdown(); 452 | Close(); 453 | } 454 | MSH3_STATUS Start() noexcept { return MsH3Connection::Start(Config); } 455 | static 456 | MSH3_STATUS 457 | Callbacks( 458 | MsH3Connection* Connection, 459 | void* /* Context */, 460 | MSH3_CONNECTION_EVENT* Event 461 | ) noexcept { 462 | LOG("CLIENT ConnectionEvent: %s\n", ToString(Event->Type)); 463 | if (Event->Type == MSH3_CONNECTION_EVENT_NEW_REQUEST) { 464 | // 465 | // Not great beacuse it doesn't provide an application specific 466 | // error code. If you expect to get streams, you should not no-op 467 | // the callbacks. 468 | // 469 | MsH3RequestClose(Event->NEW_REQUEST.Request); 470 | } else if (Event->Type == MSH3_CONNECTION_EVENT_CONNECTED) { 471 | if (((TestClient*)Connection)->SingleThreaded) { 472 | Connection->Shutdown(); 473 | } 474 | } 475 | return MSH3_STATUS_SUCCESS; 476 | } 477 | }; 478 | 479 | DEF_TEST(Handshake) { 480 | MsH3Api Api; VERIFY(Api.IsValid()); 481 | TestServer Server(Api); VERIFY(Server.IsValid()); 482 | TestClient Client(Api); VERIFY(Client.IsValid()); 483 | VERIFY_SUCCESS(Client.Start()); 484 | VERIFY(Server.WaitForConnection()); 485 | VERIFY(Client.Connected.WaitFor()); 486 | Client.Shutdown(); 487 | VERIFY(Client.ShutdownComplete.WaitFor()); 488 | return true; 489 | } 490 | 491 | DEF_TEST(HandshakeSingleThread) { 492 | MsH3EventQueue EventQueue; VERIFY(EventQueue.IsValid()); 493 | MSH3_EXECUTION_CONFIG ExecutionConfig = { 0, EventQueue }; 494 | MSH3_EXECUTION* Execution; 495 | MsH3Api Api(1, &ExecutionConfig, &Execution); VERIFY(Api.IsValid()); 496 | TestServer Server(Api); VERIFY(Server.IsValid()); 497 | auto Client = new TestClient(Api, true, CleanUpAutoDelete); 498 | VERIFY(Client->IsValid()); 499 | VERIFY_SUCCESS(Client->Start()); 500 | uint32_t DrainCount = 10; 501 | while (!g_ConnectionsComplete.Get() && DrainCount-- > 0) { 502 | uint32_t WaitTime = Api.Poll(Execution); 503 | EventQueue.CompleteEvents(g_ConnectionsComplete.Get() ? 100 : WaitTime); 504 | } 505 | return true; 506 | } 507 | 508 | DEF_TEST(HandshakeFail) { 509 | MsH3Api Api; VERIFY(Api.IsValid()); 510 | TestClient Client(Api); VERIFY(Client.IsValid()); 511 | VERIFY_SUCCESS(Client.Start()); 512 | VERIFY(!Client.Connected.WaitFor(1000)); 513 | return true; 514 | } 515 | 516 | DEF_TEST(HandshakeSetCertTimeout) { 517 | MsH3Api Api; VERIFY(Api.IsValid()); 518 | TestServer Server(Api, false); VERIFY(Server.IsValid()); 519 | TestClient Client(Api); VERIFY(Client.IsValid()); 520 | VERIFY_SUCCESS(Client.Start()); 521 | VERIFY(Server.NewConnection.WaitFor()); 522 | VERIFY(!Server.NewConnection.Get()->Connected.WaitFor(1000)); 523 | VERIFY(!Client.Connected.WaitFor()); 524 | return true; 525 | } 526 | 527 | DEF_TEST(SimpleRequest) { 528 | MsH3Api Api; VERIFY(Api.IsValid()); 529 | TestServer Server(Api); VERIFY(Server.IsValid()); 530 | TestClient Client(Api); VERIFY(Client.IsValid()); 531 | TestRequest Request(Client); VERIFY(Request.IsValid()); 532 | VERIFY(Request.Send(RequestHeaders, RequestHeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 533 | VERIFY_SUCCESS(Client.Start()); 534 | VERIFY(Server.WaitForConnection()); 535 | VERIFY(Client.Connected.WaitFor()); 536 | VERIFY(Server.NewRequest.WaitFor()); 537 | auto ServerRequest = Server.NewRequest.Get(); 538 | ServerRequest->Shutdown(MSH3_REQUEST_SHUTDOWN_FLAG_GRACEFUL); 539 | VERIFY(Request.ShutdownComplete.WaitFor()); 540 | return true; 541 | } 542 | 543 | bool ReceiveData(bool Async, bool Inline = true) { 544 | MsH3Api Api; VERIFY(Api.IsValid()); 545 | TestServer Server(Api); VERIFY(Server.IsValid()); 546 | TestClient Client(Api); VERIFY(Client.IsValid()); 547 | TestRequest Request(Client); 548 | Request.HandleReceivesAsync = Async; 549 | Request.CompleteAsyncReceivesInline = Inline; 550 | VERIFY(Request.Send(RequestHeaders, RequestHeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 551 | VERIFY(Request.IsValid()); 552 | VERIFY_SUCCESS(Client.Start()); 553 | VERIFY(Server.WaitForConnection()); 554 | VERIFY(Client.Connected.WaitFor()); 555 | VERIFY(Server.NewRequest.WaitFor()); 556 | auto ServerRequest = Server.NewRequest.Get(); 557 | VERIFY(ServerRequest->Send(RequestHeaders, RequestHeadersCount, ResponseData, sizeof(ResponseData), MSH3_REQUEST_SEND_FLAG_FIN)); 558 | VERIFY(Request.LatestDataReceived.WaitFor()); 559 | VERIFY(Request.LatestDataReceived.Get() == sizeof(ResponseData)); 560 | if (Async && !Inline) { 561 | Request.CompleteReceive(Request.LatestDataReceived.Get()); 562 | } 563 | VERIFY(Request.ShutdownComplete.WaitFor()); 564 | return true; 565 | } 566 | 567 | DEF_TEST(ReceiveDataInline) { 568 | return ReceiveData(false); 569 | } 570 | 571 | DEF_TEST(ReceiveDataAsync) { 572 | return ReceiveData(true, false); 573 | } 574 | 575 | DEF_TEST(ReceiveDataAsyncInline) { 576 | return ReceiveData(true, true); 577 | } 578 | 579 | DEF_TEST(HeaderValidation) { 580 | MsH3Api Api; VERIFY(Api.IsValid()); 581 | TestServer Server(Api); VERIFY(Server.IsValid()); 582 | TestClient Client(Api); VERIFY(Client.IsValid()); 583 | VERIFY_SUCCESS(Client.Start()); 584 | VERIFY(Server.WaitForConnection()); 585 | VERIFY(Client.Connected.WaitFor()); 586 | LOG("Connection established\n"); 587 | 588 | TestRequest Request(Client); 589 | VERIFY(Request.IsValid()); 590 | LOG("Request created\n"); 591 | 592 | // Send a request with valid headers 593 | LOG("Sending request with headers\n"); 594 | VERIFY(Request.Send(RequestHeaders, RequestHeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 595 | LOG("Request sent, waiting for server to receive it\n"); 596 | VERIFY(Server.NewRequest.WaitFor()); 597 | auto ServerRequest = Server.NewRequest.Get(); 598 | LOG("Server received request\n"); 599 | 600 | // Server sends response with headers 601 | LOG("Server sending response\n"); 602 | VERIFY(ServerRequest->Send(ResponseHeaders, ResponseHeadersCount, ResponseData, sizeof(ResponseData), MSH3_REQUEST_SEND_FLAG_FIN)); 603 | LOG("Response sent\n"); 604 | 605 | // Wait for all headers to be received (signaled by data event or completion) 606 | LOG("Waiting for all headers to be received\n"); 607 | VERIFY(Request.AllHeadersReceived.WaitFor()); 608 | LOG("All headers received\n"); 609 | 610 | LOG("Header count received: %zu\n", Request.Headers.size()); 611 | VERIFY(Request.Headers.size() == ResponseHeadersCount); 612 | LOG("Successfully received the expected number of headers\n"); 613 | 614 | // Verify the header data we copied 615 | LOG("Verifying header data\n"); 616 | 617 | // Log how many headers we received 618 | LOG("Received %zu headers\n", Request.Headers.size()); 619 | for (size_t i = 0; i < Request.Headers.size(); i++) { 620 | LOG(" Header[%zu]: %s = %s\n", i, 621 | Request.Headers[i].Name.c_str(), 622 | Request.Headers[i].Value.c_str()); 623 | } 624 | 625 | // Check for the status header and verify it 626 | auto statusHeader = Request.GetHeaderByName(":status", 7); 627 | VERIFY(statusHeader != nullptr); 628 | 629 | // Verify header name 630 | VERIFY(statusHeader->Name == ":status"); 631 | LOG("Header name verified\n"); 632 | 633 | // Verify header value 634 | VERIFY(statusHeader->Value == "200"); 635 | LOG("Header value verified\n"); 636 | 637 | return true; 638 | } 639 | 640 | DEF_TEST(DifferentResponseCodes) { 641 | MsH3Api Api; VERIFY(Api.IsValid()); 642 | TestServer Server(Api); VERIFY(Server.IsValid()); 643 | TestClient Client(Api); VERIFY(Client.IsValid()); 644 | VERIFY_SUCCESS(Client.Start()); 645 | VERIFY(Server.WaitForConnection()); 646 | VERIFY(Client.Connected.WaitFor()); 647 | LOG("Connection established for DifferentResponseCodes test\n"); 648 | 649 | // Test 201 Created response 650 | { 651 | LOG("Testing 201 Created response\n"); 652 | TestRequest Request(Client); 653 | 654 | LOG("Sending PUT request\n"); 655 | VERIFY(Request.Send(PutRequestHeaders, PutRequestHeadersCount, TextRequestData, sizeof(TextRequestData) - 1, MSH3_REQUEST_SEND_FLAG_FIN)); 656 | LOG("Waiting for server to receive request\n"); 657 | VERIFY(Server.NewRequest.WaitFor()); 658 | auto ServerRequest = Server.NewRequest.Get(); 659 | LOG("Server request received\n"); 660 | 661 | // Server sends 201 Created response 662 | LOG("Sending 201 response\n"); 663 | VERIFY(ServerRequest->Send(Response201Headers, Response201HeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 664 | LOG("Response sent\n"); 665 | 666 | // Validate the received headers 667 | LOG("Waiting for all headers\n"); 668 | VERIFY(Request.AllHeadersReceived.WaitFor()); 669 | 670 | // Verify status code 671 | LOG("Verifying status code\n"); 672 | uint32_t statusCode = Request.GetStatusCode(); 673 | LOG("Status code received: %u\n", statusCode); 674 | VERIFY(statusCode == 201); 675 | 676 | // Verify location header 677 | auto locationHeader = Request.GetHeaderByName("location", 8); 678 | VERIFY(locationHeader != nullptr); 679 | VERIFY(locationHeader->Value == "/resource/123"); 680 | 681 | LOG("201 Created test passed\n"); 682 | } 683 | 684 | // Test 404 Not Found response 685 | { 686 | LOG("Testing 404 Not Found response\n"); 687 | // Reset the server's NewRequest waitable 688 | Server.NewRequest.Reset(); 689 | 690 | TestRequest Request(Client); 691 | 692 | LOG("Sending GET request for 404\n"); 693 | VERIFY(Request.Send(RequestHeaders, RequestHeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 694 | LOG("404 Request sent, waiting for server\n"); 695 | 696 | VERIFY(Server.NewRequest.WaitFor()); 697 | auto ServerRequest = Server.NewRequest.Get(); 698 | LOG("404 Server request received\n"); 699 | 700 | // Server sends 404 Not Found response 701 | const char notFoundBody[] = "Not Found"; 702 | LOG("Sending 404 response\n"); 703 | VERIFY(ServerRequest->Send(Response404Headers, Response404HeadersCount, notFoundBody, sizeof(notFoundBody) - 1, MSH3_REQUEST_SEND_FLAG_FIN)); 704 | LOG("404 Response sent\n"); 705 | 706 | // Validate the received status code 707 | LOG("Waiting for all headers\n"); 708 | VERIFY(Request.AllHeadersReceived.WaitFor()); 709 | 710 | // Verify status code 711 | uint32_t statusCode = Request.GetStatusCode(); 712 | LOG("Status code received: %u\n", statusCode); 713 | VERIFY(statusCode == 404); 714 | 715 | // Verify content-type header 716 | auto contentTypeHeader = Request.GetHeaderByName("content-type", 12); 717 | VERIFY(contentTypeHeader != nullptr); 718 | VERIFY(contentTypeHeader->Value == "text/plain"); 719 | 720 | LOG("404 Not Found test passed\n"); 721 | } 722 | 723 | // Test 500 Internal Server Error response 724 | { 725 | LOG("Testing 500 Internal Server Error response\n"); 726 | // Reset the server's NewRequest waitable 727 | Server.NewRequest.Reset(); 728 | 729 | TestRequest Request(Client); 730 | 731 | LOG("Sending request for 500\n"); 732 | VERIFY(Request.Send(RequestHeaders, RequestHeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 733 | LOG("500 Request sent\n"); 734 | 735 | VERIFY(Server.NewRequest.WaitFor()); 736 | auto ServerRequest = Server.NewRequest.Get(); 737 | LOG("500 Server request received\n"); 738 | 739 | // Server sends 500 Internal Server Error response 740 | const char serverErrorBody[] = "Server Error"; 741 | LOG("Sending 500 response\n"); 742 | VERIFY(ServerRequest->Send(Response500Headers, Response500HeadersCount, serverErrorBody, sizeof(serverErrorBody) - 1, MSH3_REQUEST_SEND_FLAG_FIN)); 743 | LOG("500 Response sent\n"); 744 | 745 | // Validate the received status code 746 | LOG("Waiting for all headers\n"); 747 | VERIFY(Request.AllHeadersReceived.WaitFor()); 748 | 749 | // Verify status code 750 | uint32_t statusCode = Request.GetStatusCode(); 751 | LOG("Status code received: %u\n", statusCode); 752 | VERIFY(statusCode == 500); 753 | 754 | // Verify content-type header 755 | auto contentTypeHeader = Request.GetHeaderByName("content-type", 12); 756 | VERIFY(contentTypeHeader != nullptr); 757 | VERIFY(contentTypeHeader->Value == "text/plain"); 758 | 759 | LOG("500 Internal Server Error test passed\n"); 760 | } 761 | return true; 762 | } 763 | 764 | DEF_TEST(MultipleRequests) { 765 | MsH3Api Api; VERIFY(Api.IsValid()); 766 | TestServer Server(Api); VERIFY(Server.IsValid()); 767 | TestClient Client(Api); VERIFY(Client.IsValid()); 768 | 769 | VERIFY_SUCCESS(Client.Start()); 770 | VERIFY(Server.WaitForConnection()); 771 | VERIFY(Client.Connected.WaitFor()); 772 | 773 | LOG("Connection established, starting requests\n"); 774 | 775 | // Send first request (GET) 776 | LOG("Sending first request (GET)\n"); 777 | TestRequest Request1(Client); 778 | VERIFY(Request1.Send(RequestHeaders, RequestHeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 779 | VERIFY(Server.NewRequest.WaitFor()); 780 | auto ServerRequest1 = Server.NewRequest.Get(); 781 | LOG("First server request received\n"); 782 | 783 | VERIFY(ServerRequest1->Send(ResponseHeaders, ResponseHeadersCount, ResponseData, sizeof(ResponseData), MSH3_REQUEST_SEND_FLAG_FIN)); 784 | LOG("First response sent\n"); 785 | 786 | // Wait for headers and validate status code 787 | VERIFY(Request1.AllHeadersReceived.WaitFor()); 788 | uint32_t statusCode1 = Request1.GetStatusCode(); 789 | LOG("First status code received: %u\n", statusCode1); 790 | VERIFY(statusCode1 == 200); 791 | 792 | // Verify content-type header 793 | auto contentType1 = Request1.GetHeaderByName("content-type", 12); 794 | VERIFY(contentType1 != nullptr); 795 | VERIFY(contentType1->Value == "application/json"); 796 | LOG("First request headers validated\n"); 797 | 798 | // Send second request (POST) 799 | LOG("Sending second request (POST)\n"); 800 | Server.NewRequest.Reset(); 801 | TestRequest Request2(Client); 802 | VERIFY(Request2.Send(PostRequestHeaders, PostRequestHeadersCount, JsonRequestData, sizeof(JsonRequestData) - 1, MSH3_REQUEST_SEND_FLAG_FIN)); 803 | VERIFY(Server.NewRequest.WaitFor()); 804 | auto ServerRequest2 = Server.NewRequest.Get(); 805 | LOG("Second server request received\n"); 806 | 807 | VERIFY(ServerRequest2->Send(Response201Headers, Response201HeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN)); 808 | LOG("Second response sent\n"); 809 | 810 | // Wait for headers and validate status code 811 | VERIFY(Request2.AllHeadersReceived.WaitFor()); 812 | uint32_t statusCode2 = Request2.GetStatusCode(); 813 | LOG("Second status code received: %u\n", statusCode2); 814 | VERIFY(statusCode2 == 201); 815 | 816 | // Verify location header 817 | auto locationHeader = Request2.GetHeaderByName("location", 8); 818 | VERIFY(locationHeader != nullptr); 819 | VERIFY(locationHeader->Value == "/resource/123"); 820 | LOG("Second request headers validated\n"); 821 | 822 | // Send third request (PUT) 823 | LOG("Sending third request (PUT)\n"); 824 | Server.NewRequest.Reset(); 825 | TestRequest Request3(Client); 826 | VERIFY(Request3.Send(PutRequestHeaders, PutRequestHeadersCount, TextRequestData, sizeof(TextRequestData) - 1, MSH3_REQUEST_SEND_FLAG_FIN)); 827 | VERIFY(Server.NewRequest.WaitFor()); 828 | auto ServerRequest3 = Server.NewRequest.Get(); 829 | LOG("Third server request received\n"); 830 | 831 | VERIFY(ServerRequest3->Send(ResponseHeaders, ResponseHeadersCount, ResponseData, sizeof(ResponseData), MSH3_REQUEST_SEND_FLAG_FIN)); 832 | LOG("Third response sent\n"); 833 | 834 | // Wait for headers and validate status code 835 | VERIFY(Request3.AllHeadersReceived.WaitFor()); 836 | uint32_t statusCode3 = Request3.GetStatusCode(); 837 | LOG("Third status code received: %u\n", statusCode3); 838 | VERIFY(statusCode3 == 200); 839 | 840 | // Verify content-type header for third request 841 | auto contentType3 = Request3.GetHeaderByName("content-type", 12); 842 | VERIFY(contentType3 != nullptr); 843 | VERIFY(contentType3->Value == "application/json"); 844 | LOG("Third request headers validated\n"); 845 | return true; 846 | } 847 | 848 | bool RequestTransferTest(uint32_t Upload, uint32_t Download) { 849 | MsH3Api Api; VERIFY(Api.IsValid()); 850 | TestServer Server(Api); VERIFY(Server.IsValid()); 851 | TestClient Client(Api); VERIFY(Client.IsValid()); 852 | TestRequest Request(Client); 853 | // Send out the requested data on upload 854 | std::vector RequestData(Upload, 0xEF); 855 | VERIFY(Request.Send(RequestHeaders, RequestHeadersCount, RequestData.data(), RequestData.size(), MSH3_REQUEST_SEND_FLAG_FIN)); 856 | VERIFY(Request.IsValid()); 857 | VERIFY_SUCCESS(Client.Start()); 858 | VERIFY(Server.WaitForConnection()); 859 | VERIFY(Client.Connected.WaitFor()); 860 | // Wait for the server to receive the request w/ payload 861 | VERIFY(Server.NewRequest.WaitFor()); 862 | auto ServerRequest = Server.NewRequest.Get(); 863 | VERIFY(ServerRequest->AllDataReceived.WaitFor(2000)); // A bit longer wait for data 864 | VERIFY(ServerRequest->PeerSendComplete); 865 | VERIFY(ServerRequest->TotalDataReceived == Upload); 866 | // Send the response data on download 867 | std::vector ResponseData(Download, 0xAB); 868 | VERIFY(ServerRequest->Send(ResponseHeaders, ResponseHeadersCount, ResponseData.data(), ResponseData.size(), MSH3_REQUEST_SEND_FLAG_FIN)); 869 | VERIFY(Request.AllDataReceived.WaitFor(2000)); // A bit longer wait for data 870 | VERIFY(Request.PeerSendComplete); 871 | VERIFY(Request.TotalDataReceived == Download); 872 | return true; 873 | } 874 | 875 | // Throughput test sizes 876 | #define LARGE_TEST_SIZE_1MB (1024 * 1024) 877 | #define LARGE_TEST_SIZE_10MB (10 * 1024 * 1024) 878 | #define LARGE_TEST_SIZE_50MB (50 * 1024 * 1024) 879 | #define LARGE_TEST_SIZE_100MB (100 * 1024 * 1024) 880 | 881 | DEF_TEST(RequestDownload1MB) { 882 | return RequestTransferTest(0, LARGE_TEST_SIZE_1MB); 883 | } 884 | 885 | DEF_TEST(RequestDownload10MB) { 886 | return RequestTransferTest(0, LARGE_TEST_SIZE_10MB); 887 | } 888 | 889 | DEF_TEST(RequestDownload50MB) { 890 | return RequestTransferTest(0, LARGE_TEST_SIZE_50MB); 891 | } 892 | 893 | DEF_TEST(RequestUpload1MB) { 894 | return RequestTransferTest(LARGE_TEST_SIZE_1MB, 0); 895 | } 896 | 897 | DEF_TEST(RequestUpload10MB) { 898 | return RequestTransferTest(LARGE_TEST_SIZE_10MB, 0); 899 | } 900 | 901 | DEF_TEST(RequestUpload50MB) { 902 | return RequestTransferTest(LARGE_TEST_SIZE_50MB, 0); 903 | } 904 | 905 | DEF_TEST(RequestBidirectional10MB) { 906 | return RequestTransferTest(LARGE_TEST_SIZE_10MB, LARGE_TEST_SIZE_10MB); 907 | } 908 | 909 | const TestFunc TestFunctions[] = { 910 | ADD_TEST(Handshake), 911 | //ADD_TEST(HandshakeSingleThread), 912 | ADD_TEST(HandshakeFail), 913 | ADD_TEST(HandshakeSetCertTimeout), 914 | ADD_TEST(SimpleRequest), 915 | ADD_TEST(ReceiveDataInline), 916 | ADD_TEST(ReceiveDataAsync), 917 | ADD_TEST(ReceiveDataAsyncInline), 918 | ADD_TEST(HeaderValidation), 919 | ADD_TEST(DifferentResponseCodes), 920 | ADD_TEST(MultipleRequests), 921 | ADD_TEST(RequestDownload1MB), 922 | ADD_TEST(RequestDownload10MB), 923 | ADD_TEST(RequestDownload50MB), 924 | ADD_TEST(RequestUpload1MB), 925 | ADD_TEST(RequestUpload10MB), 926 | ADD_TEST(RequestUpload50MB), 927 | ADD_TEST(RequestBidirectional10MB), 928 | }; 929 | const uint32_t TestCount = sizeof(TestFunctions)/sizeof(TestFunc); 930 | 931 | // Simple watchdog function: wait for test completion or exit on timeout 932 | void WatchdogFunction() { 933 | LOG("Watchdog started with timeout %u ms\n", g_WatchdogTimeoutMs); 934 | if (!g_TestAllDone.WaitFor(g_WatchdogTimeoutMs)) { 935 | printf("WATCHDOG TIMEOUT! Killing process...\n"); fflush(stdout); 936 | exit(1); // Exit if test takes too long 937 | } 938 | LOG("Watchdog completed successfully\n"); 939 | } 940 | 941 | // Helper function to check if a character is a quote (single, double, or backtick) 942 | static inline bool IsQuoteChar(char c) { 943 | return c == '"' || c == '\''; 944 | } 945 | 946 | void PrintUsage(const char* program) { 947 | printf("Usage: %s [options]\n", program); 948 | printf("Options:\n"); 949 | printf(" -f, --filter=PATTERN Only run tests matching pattern (supports * wildcard)\n"); 950 | printf(" -h, --help Print this help message\n"); 951 | printf(" -v, --verbose Print detailed test information\n"); 952 | printf(" -t, --timeout=MSEC Set watchdog timeout in milliseconds (default: 5000)\n"); 953 | } 954 | 955 | int MSH3_CALL main(int argc, char** argv) { 956 | // Parse command line options 957 | for (int i = 1; i < argc; ++i) { 958 | if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { 959 | g_Verbose = true; 960 | } else if (strncmp(argv[i], "-f=", 3) == 0 || strncmp(argv[i], "--filter=", 9) == 0) { 961 | char* filterValue = (argv[i][1] == 'f') ? argv[i] + 3 : argv[i] + 9; 962 | // If the filter value starts with a quote, remove the quotes 963 | if (filterValue[0] != '\0' && IsQuoteChar(filterValue[0])) { 964 | char quoteChar = filterValue[0]; 965 | size_t len = strlen(filterValue); 966 | // Check if it ends with the same quote 967 | if (len > 1 && filterValue[len-1] == quoteChar) { 968 | // Skip the first quote and null-terminate before the last quote 969 | // We're modifying argv directly which is allowed 970 | filterValue[len-1] = '\0'; 971 | filterValue++; 972 | } 973 | } 974 | g_TestFilter = filterValue; 975 | } else if (strncmp(argv[i], "-t=", 3) == 0 || strncmp(argv[i], "--timeout=", 10) == 0) { 976 | const char* timeoutValue = (argv[i][1] == 't') ? argv[i] + 3 : argv[i] + 10; 977 | // Convert timeout value to integer 978 | int timeout = atoi(timeoutValue); 979 | if (timeout > 0) { 980 | g_WatchdogTimeoutMs = (uint32_t)timeout; 981 | } else { 982 | printf("Invalid timeout value: %s\n", timeoutValue); 983 | PrintUsage(argv[0]); 984 | return 1; 985 | } 986 | } else if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 987 | PrintUsage(argv[0]); 988 | return 0; 989 | } else { 990 | printf("Unknown option: %s\n", argv[i]); 991 | PrintUsage(argv[0]); 992 | return 1; 993 | } 994 | } 995 | 996 | // Calculate how many tests will run with filter 997 | uint32_t runCount = 0; 998 | if (g_TestFilter) { 999 | for (uint32_t i = 0; i < TestCount; ++i) { 1000 | if (WildcardMatch(g_TestFilter, TestFunctions[i].Name)) { 1001 | runCount++; 1002 | } 1003 | } 1004 | printf("Running %u/%u tests matching filter: %s\n", runCount, TestCount, g_TestFilter); 1005 | } else { 1006 | runCount = TestCount; 1007 | printf("Running %u tests\n", TestCount); 1008 | } 1009 | 1010 | // If no tests match the filter, return early 1011 | if (runCount == 0) { 1012 | printf("No tests match the specified filter\n"); 1013 | return 1; 1014 | } 1015 | 1016 | // Run the tests 1017 | uint32_t FailCount = 0; 1018 | for (uint32_t i = 0; i < TestCount; ++i) { 1019 | // Skip tests that don't match the filter 1020 | if (g_TestFilter && !WildcardMatch(g_TestFilter, TestFunctions[i].Name)) { 1021 | continue; 1022 | } 1023 | 1024 | printf(" %s\n", TestFunctions[i].Name); 1025 | fflush(stdout); 1026 | 1027 | // Start watchdog thread for this test 1028 | g_TestAllDone.Reset(); 1029 | std::thread watchdogThread(WatchdogFunction); 1030 | 1031 | auto result = TestFunctions[i].Func(); 1032 | LOG("Completed test: %s - %s\n", TestFunctions[i].Name, result ? "PASSED" : "FAILED"); 1033 | 1034 | g_TestAllDone.Set(true); 1035 | 1036 | // Wait for watchdog thread to finish 1037 | if (watchdogThread.joinable()) { 1038 | watchdogThread.join(); 1039 | } 1040 | 1041 | if (!result) FailCount++; 1042 | } 1043 | 1044 | printf("Complete! %u test%s failed\n", FailCount, FailCount == 1 ? "" : "s"); 1045 | return FailCount ? 1 : 0; 1046 | } 1047 | -------------------------------------------------------------------------------- /tool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | set(SOURCES msh3_app.cpp) 5 | add_executable(msh3app ${SOURCES}) 6 | target_link_libraries(msh3app msh3) 7 | install(TARGETS msh3app EXPORT msh3 RUNTIME DESTINATION bin) 8 | -------------------------------------------------------------------------------- /tool/msh3_app.cpp: -------------------------------------------------------------------------------- 1 | /*++ 2 | 3 | Copyright (c) Microsoft Corporation. 4 | Licensed under the MIT License. 5 | 6 | --*/ 7 | 8 | #include "msh3.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | 18 | struct Arguments { 19 | const char* Host { nullptr }; 20 | MsH3Addr Address { 443 }; 21 | vector Paths; 22 | MSH3_CREDENTIAL_FLAGS Flags { MSH3_CREDENTIAL_FLAG_CLIENT }; 23 | bool Print { false }; 24 | uint32_t Count { 1 }; 25 | std::atomic_int CompletionCount { 0 }; 26 | MsH3Connection* Connection { nullptr }; 27 | } Args; 28 | 29 | MSH3_STATUS 30 | MsH3RequestHandler( 31 | MsH3Request* /* Request */, 32 | void* Context, 33 | MSH3_REQUEST_EVENT* Event 34 | ) 35 | { 36 | const uint32_t Index = (uint32_t)(size_t)Context; 37 | switch (Event->Type) { 38 | case MSH3_REQUEST_EVENT_SHUTDOWN_COMPLETE: 39 | if (++Args.CompletionCount == (int)Args.Count) { 40 | Args.Connection->Shutdown(); 41 | } 42 | break; 43 | case MSH3_REQUEST_EVENT_HEADER_RECEIVED: 44 | if (Args.Print) { 45 | auto Header = Event->HEADER_RECEIVED.Header; 46 | fwrite(Header->Name, 1, Header->NameLength, stdout); 47 | printf(":"); 48 | fwrite(Header->Value, 1, Header->ValueLength, stdout); 49 | printf("\n"); 50 | } 51 | break; 52 | case MSH3_REQUEST_EVENT_DATA_RECEIVED: 53 | if (Args.Print) { 54 | fwrite(Event->DATA_RECEIVED.Data, 1, Event->DATA_RECEIVED.Length, stdout); 55 | } 56 | break; 57 | case MSH3_REQUEST_EVENT_PEER_SEND_SHUTDOWN: 58 | if (Args.Print) printf("\n"); 59 | printf("Request %u complete\n", Index); 60 | break; 61 | case MSH3_REQUEST_EVENT_PEER_SEND_ABORTED: 62 | if (Args.Print) printf("\n"); 63 | printf("Request %u aborted: 0x%llx\n", Index, (long long unsigned)Event->PEER_SEND_ABORTED.ErrorCode); 64 | break; 65 | default: 66 | break; 67 | } 68 | return MSH3_STATUS_SUCCESS; 69 | } 70 | 71 | void ParseArgs(int argc, char **argv) { 72 | if (argc < 2 || !strcmp(argv[1], "-?") || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { 73 | printf("usage: %s [options...]\n" 74 | " -c, --count The number of times to query each path (def=1)\n" 75 | " -h, --help Prints this help text\n" 76 | " -p, --path The paths to query\n" 77 | " -u, --unsecure Allows unsecure connections\n" 78 | " -v, --verbose Enables verbose output\n" 79 | " -V, --version Prints out the version\n", 80 | argv[0]); 81 | exit(-1); 82 | } 83 | 84 | // Parse the server[:port] argument. 85 | Args.Host = argv[1]; 86 | char *port = strrchr(argv[1], ':'); 87 | if (port) { 88 | *port = 0; port++; 89 | Args.Address.SetPort((uint16_t)atoi(port)); 90 | } 91 | 92 | // Parse options. 93 | for (int i = 2; i < argc; ++i) { 94 | if (!strcmp(argv[i], "--count") || !strcmp(argv[i], "-c")) { 95 | if (++i >= argc) { printf("Missing count value\n"); exit(-1); } 96 | Args.Count = (uint32_t)atoi(argv[i]); 97 | 98 | } else if (!strcmp(argv[i], "--path") || !strcmp(argv[i], "-p")) { 99 | if (++i >= argc) { printf("Missing path value(s)\n"); exit(-1); } 100 | 101 | char* Path = (char*)argv[i]; 102 | do { 103 | char* End = strchr(Path, ','); 104 | if (End) *End = 0; 105 | Args.Paths.push_back(Path); 106 | if (!End) break; 107 | Path = End + 1; 108 | } while (true); 109 | 110 | } else if (!strcmp(argv[i], "--unsecure") || !strcmp(argv[i], "-u")) { 111 | Args.Flags |= MSH3_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION; 112 | 113 | } else if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { 114 | Args.Print = true; 115 | 116 | } else if (!strcmp(argv[i], "--version") || !strcmp(argv[i], "-V")) { 117 | uint32_t Version[4]; MsH3Version(Version); 118 | printf("Using msh3 v%u.%u.%u.%u\n", Version[0], Version[1], Version[2], Version[3]); 119 | } 120 | } 121 | 122 | if (Args.Paths.empty()) { 123 | Args.Paths.push_back("/"); 124 | } 125 | } 126 | 127 | int MSH3_CALL main(int argc, char **argv) { 128 | ParseArgs(argc, argv); 129 | 130 | MSH3_HEADER Headers[] = { 131 | { ":method", 7, "GET", 3 }, 132 | { ":path", 5, Args.Paths[0], strlen(Args.Paths[0]) }, 133 | { ":scheme", 7, "https", 5 }, 134 | { ":authority", 10, Args.Host, strlen(Args.Host) }, 135 | { "user-agent", 10, "curl/7.82.0-DEV", 15 }, 136 | { "accept", 6, "*/*", 3 }, 137 | }; 138 | const size_t HeadersCount = sizeof(Headers)/sizeof(MSH3_HEADER); 139 | 140 | MsH3Api Api; 141 | if (Api.IsValid()) { 142 | MsH3Configuration Configuration(Api); if (!Configuration.IsValid()) exit(-1); 143 | if (MSH3_FAILED(Configuration.LoadConfiguration({MSH3_CREDENTIAL_TYPE_NONE, Args.Flags, 0}))) exit(-1); 144 | MsH3Connection Connection(Api); if (!Connection.IsValid()) exit(-1); 145 | Args.Connection = &Connection; 146 | if (MSH3_FAILED(Connection.Start(Configuration, Args.Host, Args.Address))) exit(-1); 147 | for (auto Path : Args.Paths) { 148 | printf("HTTP/3 GET https://%s%s\n", Args.Host, Path); 149 | Headers[1].Value = Path; 150 | Headers[1].ValueLength = strlen(Path); 151 | for (uint32_t i = 0; i < Args.Count; ++i) { 152 | auto Request = new (std::nothrow) MsH3Request(Connection, MSH3_REQUEST_FLAG_NONE, CleanUpAutoDelete, MsH3RequestHandler, (void*)(size_t)(i+1)); 153 | if (!Request || !Request->IsValid()) { 154 | printf("Request %u failed to start\n", i+1); 155 | break; 156 | } 157 | Request->Send(Headers, HeadersCount, nullptr, 0, MSH3_REQUEST_SEND_FLAG_FIN); 158 | } 159 | } 160 | Connection.ShutdownComplete.Wait(); 161 | } 162 | 163 | return 0; 164 | } 165 | --------------------------------------------------------------------------------