diff options
Diffstat (limited to '')
-rw-r--r-- | doc/.gitignore | 1 | ||||
-rw-r--r-- | doc/Makefile.am | 65 | ||||
-rw-r--r-- | doc/make.bat | 35 | ||||
-rwxr-xr-x | doc/mkapiref.py | 357 | ||||
-rw-r--r-- | doc/source/.gitignore | 9 | ||||
-rw-r--r-- | doc/source/conf.py.in | 95 | ||||
-rw-r--r-- | doc/source/index.rst | 22 | ||||
-rw-r--r-- | doc/source/programmers-guide.rst | 446 | ||||
-rw-r--r-- | docker/Dockerfile | 39 |
9 files changed, 1069 insertions, 0 deletions
diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..e2339bd --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,65 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help html apiref cryptoapiref + +apiref: mkapiref.py \ + $(top_builddir)/lib/includes/ngtcp2/version.h \ + $(top_srcdir)/lib/includes/ngtcp2/ngtcp2.h + $(top_srcdir)/doc/mkapiref.py \ + --title='ngtcp2 API reference' \ + source/apiref.rst source/macros.rst source/enums.rst source/types.rst \ + source $(wordlist 2, $(words $^), $^) + +cryptoapiref: mkapiref.py \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_openssl.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h + $(top_srcdir)/doc/mkapiref.py \ + --title='ngtcp2 crypto API reference' \ + source/crypto_apiref.rst source/crypto_macros.rst \ + source/crypto_enums.rst source/crypto_types.rst \ + source $(wordlist 2, $(words $^), $^) + +html-local: apiref cryptoapiref + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean-local: + -rm -rf "$(BUILDDIR)" diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/doc/mkapiref.py b/doc/mkapiref.py new file mode 100755 index 0000000..a0d0483 --- /dev/null +++ b/doc/mkapiref.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2020 ngtcp2 contributors +# Copyright (c) 2012 Tatsuhiro Tsujikawa +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Generates API reference from C source code. + +import re, sys, argparse, os.path + +class FunctionDoc: + def __init__(self, name, content, domain, filename): + self.name = name + self.content = content + self.domain = domain + if self.domain == 'function': + self.funcname = re.search(r'(ngtcp2_[^ )]+)\(', self.name).group(1) + self.filename = filename + + def write(self, out): + out.write('.. {}:: {}\n'.format(self.domain, self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class StructDoc: + def __init__(self, name, content, members, member_domain): + self.name = name + self.content = content + self.members = members + self.member_domain = member_domain + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. {}:: {}\n'.format(self.member_domain, name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class EnumDoc: + def __init__(self, name, content, members): + self.name = name + self.content = content + self.members = members + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. enum:: {}\n'.format(name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class MacroDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. macro:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class MacroSectionDoc: + def __init__(self, content): + self.content = content + + def write(self, out): + out.write('\n') + c = ' '.join(self.content).strip() + out.write(c) + out.write('\n') + out.write('-' * len(c)) + out.write('\n\n') + +class TypedefDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. type:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +def make_api_ref(infile): + macros = [] + enums = [] + types = [] + functions = [] + while True: + line = infile.readline() + if not line: + break + elif line == '/**\n': + line = infile.readline() + doctype = line.split()[1] + if doctype == '@function': + functions.append(process_function('function', infile)) + elif doctype == '@functypedef': + types.append(process_function('type', infile)) + elif doctype == '@struct' or doctype == '@union': + types.append(process_struct(infile)) + elif doctype == '@enum': + enums.append(process_enum(infile)) + elif doctype == '@macro': + macros.append(process_macro(infile)) + elif doctype == '@macrosection': + macros.append(process_macrosection(infile)) + elif doctype == '@typedef': + types.append(process_typedef(infile)) + return macros, enums, types, functions + +def output( + title, indexfile, macrosfile, enumsfile, typesfile, funcsdir, + macros, enums, types, functions): + indexfile.write(''' +{title} +{titledecoration} + +.. toctree:: + :maxdepth: 1 + + {macros} + {enums} + {types} +'''.format( + title=title, titledecoration='='*len(title), + macros=os.path.splitext(os.path.basename(macrosfile.name))[0], + enums=os.path.splitext(os.path.basename(enumsfile.name))[0], + types=os.path.splitext(os.path.basename(typesfile.name))[0], +)) + + for doc in functions: + indexfile.write(' {}\n'.format(doc.funcname)) + + macrosfile.write(''' +Macros +====== +''') + for doc in macros: + doc.write(macrosfile) + + enumsfile.write(''' +Enums +===== +''') + for doc in enums: + doc.write(enumsfile) + + typesfile.write(''' +Types (structs, unions and typedefs) +==================================== +''') + for doc in types: + doc.write(typesfile) + + for doc in functions: + with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f: + f.write(''' +{funcname} +{secul} + +Synopsis +-------- + +*#include <ngtcp2/{filename}>* + +'''.format(funcname=doc.funcname, secul='='*len(doc.funcname), + filename=doc.filename)) + doc.write(f) + +def process_macro(infile): + content = read_content(infile) + lines = [] + while True: + line = infile.readline() + if not line: + break + line = line.rstrip() + lines.append(line.rstrip('\\')) + if not line.endswith('\\'): + break + + macro_name = re.sub(r'#define ', '', ''.join(lines)) + m = re.match(r'^[^( ]+(:?\(.*?\))?', macro_name) + macro_name = m.group(0) + return MacroDoc(macro_name, content) + +def process_macrosection(infile): + content = read_content(infile) + return MacroSectionDoc(content) + +def process_typedef(infile): + content = read_content(infile) + typedef = infile.readline() + typedef = re.sub(r';\n$', '', typedef) + typedef = re.sub(r'typedef ', '', typedef) + return TypedefDoc(typedef, content) + +def process_enum(infile): + members = [] + enum_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + items = line.split() + member_name = items[0].rstrip(',') + if len(items) >= 3: + member_content.insert(0, '(``{}``) '\ + .format(' '.join(items[2:]).rstrip(','))) + members.append((member_name, member_content)) + elif line.startswith('}'): + enum_name = line.rstrip().split()[1] + enum_name = re.sub(r';$', '', enum_name) + break + return EnumDoc(enum_name, content, members) + +def process_struct(infile): + members = [] + struct_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + member_name = line.rstrip().rstrip(';') + members.append((member_name, member_content)) + elif line.startswith('}') or\ + (line.startswith('typedef ') and line.endswith(';\n')): + if line.startswith('}'): + index = 1 + else: + index = 3 + struct_name = line.rstrip().split()[index] + struct_name = re.sub(r';$', '', struct_name) + break + return StructDoc(struct_name, content, members, 'member') + +def process_function(domain, infile): + content = read_content(infile) + func_proto = [] + while True: + line = infile.readline() + if not line: + break + elif line == '\n': + break + else: + func_proto.append(line) + func_proto = ''.join(func_proto) + func_proto = re.sub(r'int (pkt_info|transport_params|conn_stat|settings|callbacks)_version,', + '', func_proto) + func_proto = re.sub(r'_versioned\(', '(', func_proto) + func_proto = re.sub(r';\n$', '', func_proto) + func_proto = re.sub(r'\s+', ' ', func_proto) + func_proto = re.sub(r'NGTCP2_EXTERN ', '', func_proto) + func_proto = re.sub(r'typedef ', '', func_proto) + filename = os.path.basename(infile.name) + return FunctionDoc(func_proto, content, domain, filename) + +def read_content(infile): + content = [] + while True: + line = infile.readline() + if not line: + break + if re.match(r'\s*\*/\n', line): + break + else: + content.append(transform_content(line.rstrip())) + return content + +def arg_repl(matchobj): + return '*{}*'.format(matchobj.group(1).replace('*', '\\*')) + +def transform_content(content): + content = re.sub(r'^\s+\* ?', '', content) + content = re.sub(r'\|([^\s|]+)\|', arg_repl, content) + content = re.sub(r':enum:', ':macro:', content) + return content + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Generate API reference") + parser.add_argument('--title', default='API Reference', + help='title of index page') + parser.add_argument('index', type=argparse.FileType('w'), + help='index output file') + parser.add_argument('macros', type=argparse.FileType('w'), + help='macros section output file. The filename should be macros.rst') + parser.add_argument('enums', type=argparse.FileType('w'), + help='enums section output file. The filename should be enums.rst') + parser.add_argument('types', type=argparse.FileType('w'), + help='types section output file. The filename should be types.rst') + parser.add_argument('funcsdir', + help='functions doc output dir') + parser.add_argument('files', nargs='+', type=argparse.FileType('r'), + help='source file') + args = parser.parse_args() + macros = [] + enums = [] + types = [] + funcs = [] + for infile in args.files: + m, e, t, f = make_api_ref(infile) + macros.extend(m) + enums.extend(e) + types.extend(t) + funcs.extend(f) + funcs.sort(key=lambda x: x.funcname) + output( + args.title, + args.index, args.macros, args.enums, args.types, args.funcsdir, + macros, enums, types, funcs) diff --git a/doc/source/.gitignore b/doc/source/.gitignore new file mode 100644 index 0000000..a03e54b --- /dev/null +++ b/doc/source/.gitignore @@ -0,0 +1,9 @@ +/conf.py +/apiref.rst +/enums.rst +/macros.rst +/types.rst +/crypto_apiref.rst +/crypto_enums.rst +/crypto_macros.rst +/crypto_types.rst diff --git a/doc/source/conf.py.in b/doc/source/conf.py.in new file mode 100644 index 0000000..6ac325c --- /dev/null +++ b/doc/source/conf.py.in @@ -0,0 +1,95 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'ngtcp2' +copyright = '2020, ngtcp2 contributors' +author = 'ngtcp2 contributors' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' +html_theme_path = ['_themes'] + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = 'c:func' +primary_domain = 'c' + +# manpage URL pattern +manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.html' + +# The default language to highlight source code in. +highlight_language = 'c' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '@PACKAGE_VERSION@' +# The full version, including alpha/beta/rc tags. +release = '@PACKAGE_VERSION@' diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..6890563 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,22 @@ +.. ngtcp2 documentation master file, created by + sphinx-quickstart on Mon Nov 30 22:15:12 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to ngtcp2's documentation! +================================== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + programmers-guide + apiref + crypto_apiref + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/programmers-guide.rst b/doc/source/programmers-guide.rst new file mode 100644 index 0000000..5138361 --- /dev/null +++ b/doc/source/programmers-guide.rst @@ -0,0 +1,446 @@ +The ngtcp2 programmers' guide for early adopters +================================================ + +This document is written for early adopters of ngtcp2 library. It +describes a brief introduction of programming ngtcp2. + +Prerequisites +------------- + +Reading :rfc:`9000` and :rfc:`9001` helps you a lot to write QUIC +application. They describes how TLS is integrated into QUIC and why +the existing TLS stack cannot be used with QUIC. + +QUIC requires the special interface from TLS stack, which is probably +not available from most of the existing TLS stacks. As far as I know, +the TLS stacks maintained by the active participants of QUIC working +group only get this interface at the time of this writing. In order +to build QUIC application you have to choose one of them. Here is the +list of TLS stacks which are supposed to provide such interface and +for which we provide crypto helper libraries: + +* `OpenSSL with QUIC support + <https://github.com/quictls/openssl/tree/OpenSSL_1_1_1q+quic>`_ +* GnuTLS >= 3.7.2 +* BoringSSL +* Picotls +* wolfSSL + +Creating ngtcp2_conn object +--------------------------- + +:type:`ngtcp2_conn` is the primary object to present a single QUIC +connection. Use `ngtcp2_conn_client_new()` for client application, +and `ngtcp2_conn_server_new()` for server. + +They require :type:`ngtcp2_callbacks`, :type:`ngtcp2_settings`, and +:type:`ngtcp2_transport_params` objects. + +The :type:`ngtcp2_callbacks` contains the callback functions which +:type:`ngtcp2_conn` calls when a specific event happens, say, +receiving stream data or stream is closed, etc. Some of the callback +functions are optional. For client application, the following +callback functions must be set: + +* :member:`client_initial <ngtcp2_callbacks.client_initial>`: + `ngtcp2_crypto_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data <ngtcp2_callbacks.recv_crypto_data>`: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt <ngtcp2_callbacks.encrypt>`: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt <ngtcp2_callbacks.decrypt>`: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask <ngtcp2_callbacks.hp_mask>`: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`recv_retry <ngtcp2_callbacks.recv_retry>`: + `ngtcp2_crypto_recv_retry_cb()` can be passed directly. +* :member:`rand <ngtcp2_callbacks.rand>` +* :member:`get_new_connection_id + <ngtcp2_callbacks.get_new_connection_id>` +* :member:`update_key <ngtcp2_callbacks.update_key>`: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + <ngtcp2_callbacks.delete_crypto_aead_ctx>`: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + <ngtcp2_callbacks.delete_crypto_cipher_ctx>`: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + <ngtcp2_callbacks.get_path_challenge_data>`: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + <ngtcp2_callbacks.version_negotiation>`: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +For server application, the following callback functions must be set: + +* :member:`recv_client_initial + <ngtcp2_callbacks.recv_client_initial>`: + `ngtcp2_crypto_recv_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data <ngtcp2_callbacks.recv_crypto_data>`: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt <ngtcp2_callbacks.encrypt>`: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt <ngtcp2_callbacks.decrypt>`: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask <ngtcp2_callbacks.hp_mask>`: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`rand <ngtcp2_callbacks.rand>` +* :member:`get_new_connection_id + <ngtcp2_callbacks.get_new_connection_id>` +* :member:`update_key <ngtcp2_callbacks.update_key>`: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + <ngtcp2_callbacks.delete_crypto_aead_ctx>`: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + <ngtcp2_callbacks.delete_crypto_cipher_ctx>`: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + <ngtcp2_callbacks.get_path_challenge_data>`: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + <ngtcp2_callbacks.version_negotiation>`: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +``ngtcp2_crypto_*`` functions are a part of :doc:`ngtcp2 crypto API +<crypto_apiref>` which provides easy integration with the supported +TLS backend. It vastly simplifies TLS integration and is strongly +recommended. + +:type:`ngtcp2_settings` contains the settings for QUIC connection. +All fields must be set. Application should call +`ngtcp2_settings_default()` to set the default values. It would be +very useful to enable debug logging by setting logging function to +:member:`ngtcp2_settings.log_printf` field. ngtcp2 library relies on +the timestamp fed from application. The initial timestamp must be +passed to :member:`ngtcp2_settings.initial_ts` field in nanosecond +resolution. ngtcp2 cares about the difference from that initial +value. It could be any timestamp which increases monotonically, and +actual value does not matter. + +:type:`ngtcp2_transport_params` contains QUIC transport parameters +which is sent to a remote endpoint during handshake. All fields must +be set. Application should call `ngtcp2_transport_params_default()` +to set the default values. + +Client application has to supply Connection IDs to +`ngtcp2_conn_client_new()`. The *dcid* parameter is the destination +connection ID (DCID), and which should be random byte string and at +least 8 bytes long. The *scid* is the source connection ID (SCID) +which identifies the client itself. The *version* parameter is the +QUIC version to use. It should be :macro:`NGTCP2_PROTO_VER_V1`. + +Similarly, server application has to supply these parameters to +`ngtcp2_conn_server_new()`. But the *dcid* must be the same value +which is received from client (which is client SCID). The *scid* is +chosen by server. Don't use DCID in client packet as server SCID. +The *version* parameter is the QUIC version to use. It should be +:macro:`NGTCP2_PROTO_VER_V1`. + +A path is very important to QUIC connection. It is the pair of +endpoints, local and remote. The path passed to +`ngtcp2_conn_client_new()` and `ngtcp2_conn_server_new()` is a network +path that handshake is performed. The path must not change during +handshake. After handshake is confirmed, client can migrate to new +path. An application must provide actual path to the API function to +tell the library where a packet comes from. The "write" API function +takes path parameter and fills it to which the packet should be sent. + +TLS integration +--------------- + +Use of :doc:`ngtcp2 crypto API <crypto_apiref>` is strongly +recommended because it vastly simplifies the TLS integration. + +The most of the TLS work is done by the callback functions passed to +:type:`ngtcp2_callbacks` object. There are some operations left to +application in order to make TLS integration work. We have a set of +helper functions to make it easier for applications to configure TLS +stack object to work with QUIC and ngtcp2. They are specific to each +supported TLS stack: + +- OpenSSL + + * `ngtcp2_crypto_openssl_configure_client_context` + * `ngtcp2_crypto_openssl_configure_server_context` + +- BoringSSL + + * `ngtcp2_crypto_boringssl_configure_client_context` + * `ngtcp2_crypto_boringssl_configure_server_context` + +- GnuTLS + + * `ngtcp2_crypto_gnutls_configure_client_session` + * `ngtcp2_crypto_gnutls_configure_server_session` + +- Picotls + + * `ngtcp2_crypto_picotls_configure_client_context` + * `ngtcp2_crypto_picotls_configure_server_context` + * `ngtcp2_crypto_picotls_configure_client_session` + * `ngtcp2_crypto_picotls_configure_server_session` + +- wolfSSL + + * `ngtcp2_crypto_wolfssl_configure_client_context` + * `ngtcp2_crypto_wolfssl_configure_server_context` + +They make the minimal QUIC specific changes to TLS stack object. See +the ngtcp2 crypto API header files for each supported TLS stack. In +order to make these functions work, we require that a pointer to +:type:`ngtcp2_crypto_conn_ref` must be set as a user data in TLS stack +object, and its :member:`ngtcp2_crypto_conn_ref.get_conn` must point +to a function which returns :type:`ngtcp2_conn` of the underlying QUIC +connection. + +If you do not use the above helper functions, you need to generate and +install keys to :type:`ngtcp2_conn`, and pass handshake messages to +:type:`ngtcp2_conn` as well. When TLS stack generates new secrets, +they have to be installed to :type:`ngtcp2_conn` by calling +`ngtcp2_crypto_derive_and_install_rx_key()` and +`ngtcp2_crypto_derive_and_install_tx_key()`. When TLS stack generates +new crypto data to send, they must be passed to :type:`ngtcp2_conn` by +calling `ngtcp2_conn_submit_crypto_data()`. + +When QUIC handshake is completed, +:member:`ngtcp2_callbacks.handshake_completed` callback function is +called. The local and remote endpoint independently declare handshake +completion. The endpoint has to confirm that the other endpoint also +finished handshake. When the handshake is confirmed, client side +:type:`ngtcp2_conn` will call +:member:`ngtcp2_callbacks.handshake_confirmed` callback function. +Server confirms handshake when it declares handshake completion, +therefore, separate handshake confirmation callback is not called. + +Read and write packets +---------------------- + +`ngtcp2_conn_read_pkt()` processes the incoming QUIC packets. In +order to write QUIC packets, call `ngtcp2_conn_writev_stream()` or +`ngtcp2_conn_write_pkt()`. The *destlen* parameter must be at least +the value returned from `ngtcp2_conn_get_max_tx_udp_payload_size()`. + +In order to send stream data, the application has to first open a +stream. Use `ngtcp2_conn_open_bidi_stream()` to open bidirectional +stream. For unidirectional stream, call +`ngtcp2_conn_open_uni_stream()`. Call `ngtcp2_conn_writev_stream()` +to send stream data. + +An application should pace sending packets. +`ngtcp2_conn_get_send_quantum()` returns the number of bytes that can +be sent without packet spacing. After one or more calls of +`ngtcp2_conn_writev_stream()` (it can be called multiple times to fill +the buffer sized up to `ngtcp2_conn_get_send_quantum()` bytes), call +`ngtcp2_conn_update_pkt_tx_time()` to set the timer when the next +packet should be sent. The timer is integrated into +`ngtcp2_conn_get_expiry()`. + +Packet handling on server side +------------------------------ + +Any incoming UDP datagram should be first processed by +`ngtcp2_pkt_decode_version_cid()`. It can handle Connection ID more +than 20 bytes which is the maximum length defined in QUIC v1. If the +function returns :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`, server +should send Version Negotiation packet. Use +`ngtcp2_pkt_write_version_negotiation()` for this purpose. If +`ngtcp2_pkt_decode_version_cid()` succeeds, then check whether the UDP +datagram belongs to any existing connection by looking up connection +tables by Destination Connection ID. If it belongs to an existing +connection, pass the UDP datagram to `ngtcp2_conn_read_pkt()`. If it +does not belong to any existing connection, it should be passed to +`ngtcp2_accept()`. If it returns :macro:`NGTCP2_ERR_RETRY`, the +server should send Retry packet (use `ngtcp2_crypto_write_retry()` to +create Retry packet). If it returns an other negative error code, +just drop the packet to the floor and take no action, or send +Stateless Reset packet (use `ngtcp2_pkt_write_stateless_reset()` to +create Stateless Reset packet). Otherwise, the UDP datagram is +acceptable as a new connection. Create :type:`ngtcp2_conn` object and +pass the UDP datagram to `ngtcp2_conn_read_pkt()`. + +Dealing with early data +----------------------- + +Client application has to load resumed TLS session. It also has to +set the remembered transport parameters using +`ngtcp2_conn_set_early_remote_transport_params()` function. + +Other than that, there is no difference between early data and 1RTT +data in terms of API usage. + +If early data is rejected by a server, client must call +`ngtcp2_conn_early_data_rejected`. All connection states altered +during early data transmission are undone. The library does not +retransmit early data to server as 1RTT data. If an application +wishes to resend data, it has to reopen streams and writes data again. +See `ngtcp2_conn_early_data_rejected`. + +Stream data ownership +-------------------------------- + +Stream data passed to :type:`ngtcp2_conn` must be held by application +until :member:`ngtcp2_callbacks.acked_stream_data_offset` callbacks is +invoked, telling that the those data are acknowledged by the remote +endpoint and no longer used by the library. + +Timers +------ + +The library does not ask an operating system for any timestamp. +Instead, an application has to supply timestamp to the library. The +type of timestamp in ngtcp2 library is :type:`ngtcp2_tstamp` which is +nanosecond resolution. The library only cares the difference of +timestamp, so it does not have to be a system clock. A monotonic +clock should work better. It should be same clock passed to +:member:`ngtcp2_settings.initial_ts`. The duration in ngtcp2 library +is :type:`ngtcp2_duration` which is also nanosecond resolution. + +`ngtcp2_conn_get_expiry()` tells an application when timer fires. +When it fires, call `ngtcp2_conn_handle_expiry()`. If it returns +:macro:`NGTCP2_ERR_IDLE_CLOSE`, it means that an idle timer has fired +for this particular connection. In this case, drop the connection +without calling `ngtcp2_conn_write_connection_close()`. Otherwise, +call `ngtcp2_conn_writev_stream()`. After calling +`ngtcp2_conn_handle_expiry()` and `ngtcp2_conn_writev_stream()`, new +expiry is set. The application should call `ngtcp2_conn_get_expiry()` +to get a new deadline. + +Please note that :type:`ngtcp2_tstamp` of value ``UINT64_MAX`` is +treated as an invalid timestamp. Do not pass ``UINT64_MAX`` to any +ngtcp2 functions which take :type:`ngtcp2_tstamp` unless it is +explicitly allowed. + +Connection migration +-------------------- + +In QUIC, client application can migrate to a new local address. +`ngtcp2_conn_initiate_immediate_migration()` migrates to a new local +address without checking reachability. On the other hand, +`ngtcp2_conn_initiate_migration()` migrates to a new local address +after a new path is validated (thus reachability is established). + +Closing connection abruptly +--------------------------- + +In order to close QUIC connection abruptly, call +`ngtcp2_conn_write_connection_close()` and get a terminal packet. +After the call, the connection enters the closing state. + +The closing and draining state +------------------------------ + +After the successful call of `ngtcp2_conn_write_connection_close()`, +the connection enters the closing state. When +`ngtcp2_conn_read_pkt()` returns :macro:`NGTCP2_ERR_DRAINING`, the +connection has entered the draining state. In these states, +`ngtcp2_conn_writev_stream()` and `ngtcp2_conn_read_pkt()` return an +error (either :macro:`NGTCP2_ERR_CLOSING` or +:macro:`NGTCP2_ERR_DRAINING` depending on the state). +`ngtcp2_conn_write_connection_close()` returns 0 in these states. If +an application needs to send a packet containing CONNECTION_CLOSE +frame in the closing state, resend the packet produced by the first +call of `ngtcp2_conn_write_connection_close()`. Therefore, after a +connection has entered one of these states, the application can +discard :type:`ngtcp2_conn` object. The closing and draining state +should persist at least 3 times the current PTO. + +Error handling in general +------------------------- + +In general, when error is returned from the ngtcp2 library function, +call `ngtcp2_conn_write_connection_close()` to get terminal packet. +If the successful call of the function creates non-empty packet, the +QUIC connection enters the closing state. + +If :macro:`NGTCP2_ERR_DROP_CONN` is returned from +`ngtcp2_conn_read_pkt`, a connection should be dropped without calling +`ngtcp2_conn_write_connection_close()`. Similarly, if +:macro:`NGTCP2_ERR_IDLE_CLOSE` is returned from +`ngtcp2_conn_handle_expiry`, a connection should be dropped without +calling `ngtcp2_conn_write_connection_close()`. If +:macro:`NGTCP2_ERR_DRAINING` is returned from `ngtcp2_conn_read_pkt`, +a connection has entered the draining state, and no further packet +transmission is allowed. + +The following error codes must be considered as transitional, and +application should keep connection alive: + +* :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` +* :macro:`NGTCP2_ERR_STREAM_SHUT_WR` +* :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` +* :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + +Version negotiation +------------------- + +Version negotiation is configured with the following +:type:`ngtcp2_settings` fields: + +* :member:`ngtcp2_settings.preferred_versions` and + :member:`ngtcp2_settings.preferred_versionslen` +* :member:`ngtcp2_settings.other_versions` and + :member:`ngtcp2_settings.other_versionslen` +* :member:`ngtcp2_settings.original_version` + +*client_chosen_version* passed to `ngtcp2_conn_client_new` also +influence the version negotiation process. + +By default, client sends *client_chosen_version* passed to +`ngtcp2_conn_client_new` in other_versions field of +version_information QUIC transport parameter. That means there is no +chance for server to select the other compatible version. Meanwhile, +ngtcp2 supports QUIC v2 draft version +(:macro:`NGTCP2_PROTO_VER_V2_DRAFT`). Including both +:macro:`NGTCP2_PROTO_VER_V1` and :macro:`NGTCP2_PROTO_VER_V2_DRAFT` in +:member:`ngtcp2_settings.other_versions` field allows server to choose +:macro:`NGTCP2_PROTO_VER_V2_DRAFT` which is compatible to +:macro:`NGTCP2_PROTO_VER_V1`. + +By default, server sends :macro:`NGTCP2_PROTO_VER_V1` in +other_versions field of version_information QUIC transport parameter. +Because there is no particular preferred versions specified, server +will accept any supported version. In order to set the version +preference, specify :member:`ngtcp2_settings.preferred_versions` +field. If it is specified, server sends them in other_versions field +of version_information QUIC transport parameter unless +:member:`ngtcp2_settings.other_versionslen` is not zero. Specifying +:member:`ngtcp2_settings.other_versions` overrides the above mentioned +default behavior. Even if there is no overlap between +:member:`ngtcp2_settings.preferred_versions` and other_versions field +plus *client_chosen_version* from client, as long as +*client_chosen_version* is supported by server, server accepts +*client_chosen_version*. + +If client receives Version Negotiation packet from server, +`ngtcp2_conn_read_pkt` returns +:macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION`. +:member:`ngtcp2_callbacks.recv_version_negotiation` is also invoked if +set. It will provide the versions contained in the packet. Client +then either gives up the connection attempt, or selects the version +from Version Negotiation packet, and starts new connection attempt +with that version. In the latter case, the initial version that used +in the first connection attempt must be set to +:member:`ngtcp2_settings.original_version`. The client version +preference that is used when selecting a version from Version +Negotiation packet must be set to +:member:`ngtcp2_settings.preferred_versions`. +:member:`ngtcp2_settings.other_versions` must include the selected +version. The selected version becomes *client_chosen_version* in the +second connection attempt, and must be passed to +`ngtcp2_conn_client_new`. + +Server never know whether client reacted upon Version Negotiation +packet or not, and there is no particular setup for server to make +this incompatible version negotiation work. + +Thread safety +------------- + +ngtcp2 library is thread-safe as long as a single :type:`ngtcp2_conn` +object is accessed by a single thread at a time. For multi-threaded +applications, it is recommended to create :type:`ngtcp2_conn` objects +per thread to avoid locks. diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..d6141d9 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,39 @@ +FROM debian:11 as build + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git g++ clang-11 make binutils autoconf automake autotools-dev libtool \ + pkg-config libev-dev libjemalloc-dev \ + ca-certificates mime-support && \ + git clone --depth 1 -b OpenSSL_1_1_1s+quic https://github.com/quictls/openssl && \ + cd openssl && ./config --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ + git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ + cd nghttp3 && autoreconf -i && \ + ./configure --enable-lib-only CC=clang-11 CXX=clang++-11 && \ + make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \ + git clone --depth 1 https://github.com/ngtcp2/ngtcp2 && \ + cd ngtcp2 && autoreconf -i && \ + ./configure \ + CC=clang-11 \ + CXX=clang++-11 \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -pthread" \ + LIBEV_LIBS="-l:libev.a" \ + JEMALLOC_LIBS="-l:libjemalloc.a -lm" && \ + make -j$(nproc) && \ + strip examples/client examples/server && \ + cp examples/client examples/server /usr/local/bin && \ + cd .. && rm -rf ngtcp2 && \ + apt-get -y purge \ + git g++ clang-11 make binutils autoconf automake autotools-dev libtool \ + pkg-config libev-dev libjemalloc-dev \ + ca-certificates && \ + apt-get -y autoremove --purge && \ + rm -rf /var/log/* + +FROM gcr.io/distroless/cc-debian11:latest-amd64 + +COPY --from=build /usr/local/bin/client /usr/local/bin/server /usr/local/bin/ +COPY --from=build /etc/mime.types /etc/ + +CMD ["/usr/local/bin/client"] |