From 17e81f2cd1843f01838245eae7b5ed5edf83d6be Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 09:30:55 +0200 Subject: Adding upstream version 0.12.1+dfsg. Signed-off-by: Daniel Baumann --- doc/source/.gitignore | 9 + doc/source/conf.py.in | 95 +++++++++ doc/source/index.rst | 22 ++ doc/source/programmers-guide.rst | 446 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 572 insertions(+) create mode 100644 doc/source/.gitignore create mode 100644 doc/source/conf.py.in create mode 100644 doc/source/index.rst create mode 100644 doc/source/programmers-guide.rst (limited to 'doc/source') 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 + `_ +* 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_crypto_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data `: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt `: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt `: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask `: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`recv_retry `: + `ngtcp2_crypto_recv_retry_cb()` can be passed directly. +* :member:`rand ` +* :member:`get_new_connection_id + ` +* :member:`update_key `: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + `: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + `: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + `: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`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_crypto_recv_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data `: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt `: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt `: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask `: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`rand ` +* :member:`get_new_connection_id + ` +* :member:`update_key `: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + `: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + `: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + `: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + `: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +``ngtcp2_crypto_*`` functions are a part of :doc:`ngtcp2 crypto API +` 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 ` 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. -- cgit v1.2.3