From 367637731a7898b7a33bceec0fe5309da80486e5 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 12:27:16 +0300 Subject: [PATCH 01/14] remove DSDL; build broken --- .gitmodules | 3 - dsdl | 1 - render_dsdl.py | 296 -------- render_list_of_void_and_primitive_types.py | 15 - specification/Cyphal_Specification.tex | 3 - specification/dsdl/architecture.tex | 272 -------- specification/dsdl/attributes.tex | 199 ------ specification/dsdl/compatibility.tex | 374 ---------- specification/dsdl/conventions.tex | 138 ---- specification/dsdl/directives.tex | 171 ----- specification/dsdl/dsdl.tex | 19 - specification/dsdl/expression_types.tex | 212 ------ specification/dsdl/grammar.parsimonious | 182 ----- specification/dsdl/grammar.tex | 271 -------- specification/dsdl/serializable_types.tex | 695 ------------------- specification/dsdl/serialization.tex | 749 --------------------- specification/sdt/sdt.tex | 21 - 17 files changed, 3621 deletions(-) delete mode 160000 dsdl delete mode 100755 render_dsdl.py delete mode 100755 render_list_of_void_and_primitive_types.py delete mode 100644 specification/dsdl/architecture.tex delete mode 100644 specification/dsdl/attributes.tex delete mode 100644 specification/dsdl/compatibility.tex delete mode 100644 specification/dsdl/conventions.tex delete mode 100644 specification/dsdl/directives.tex delete mode 100644 specification/dsdl/dsdl.tex delete mode 100644 specification/dsdl/expression_types.tex delete mode 100644 specification/dsdl/grammar.parsimonious delete mode 100644 specification/dsdl/grammar.tex delete mode 100644 specification/dsdl/serializable_types.tex delete mode 100644 specification/dsdl/serialization.tex delete mode 100644 specification/sdt/sdt.tex diff --git a/.gitmodules b/.gitmodules index a4a41543..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "dsdl"] - path = dsdl - url = https://github.com/UAVCAN/public_regulated_data_types diff --git a/dsdl b/dsdl deleted file mode 160000 index f9f67906..00000000 --- a/dsdl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f9f67906cc0ca5d7c1b429924852f6b28f313cbf diff --git a/render_dsdl.py b/render_dsdl.py deleted file mode 100755 index f436cc27..00000000 --- a/render_dsdl.py +++ /dev/null @@ -1,296 +0,0 @@ -#!/usr/bin/env python3 -# -# This script accepts the name of a DSDL definition, or a DSDL namespace followed by a glob asterisk, -# and prints its LaTeX representation into stdout. Usage examples: -# -# ./render_dsdl.py uavcan.node.* --index-only -# Prints an index table of the namespace "uavcan.node". The index table will be accessible via the -# reference label "table:dsdl:uavcan.node". -# -# ./render_dsdl.py uavcan.node.Heartbeat -# Prints the source text of the definition "uavcan.node.Heartbeat" and prepends it with a brief -# explanatory text. -# -# ./render_dsdl.py uavcan.* -# Generates an index table of the namespace "uavcan", and then provides a sequence of sections -# containing the source text of the data type definitions contained in this namespace. -# Each definition is provided with a label of the form like "sec:dsdl:uavcan.node.Heartbeat". -# -# For more info just read the code. -# - -import os -import re -import sys -import pydsdl -import pickle -import subprocess -from fnmatch import fnmatch -from functools import partial -from collections import OrderedDict - -SOURCE_ROOT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) - -ROOT_NAMESPACE_SUPERDIRECTORY = os.path.join(SOURCE_ROOT_DIRECTORY, 'dsdl') - -CACHE_FILE_NAME_SUFFIX = '.dsdl.cache' - - -sys.stderr = sys.stdout - - -def die(why: str) -> None: - print(why, file=sys.stderr) - exit(1) - - -def escape(s: str) -> str: - return s.replace('_', r'\_') - - -def get_dsdl_submodule_commit_hash() -> str: - return subprocess.check_output(['git', 'rev-parse', 'HEAD'], - cwd=ROOT_NAMESPACE_SUPERDIRECTORY).decode('ascii').strip() - - -def render_dsdl_info(t: pydsdl.CompositeType) -> str: - if isinstance(t, pydsdl.ServiceType): - return ( - r'\begin{itemize}' + - r'\item Request: ' + render_dsdl_info(t.request_type) + - r'\item Response: ' + render_dsdl_info(t.response_type) + - r'\end{itemize}' - ) - - fin = t.inner_type if isinstance(t, pydsdl.DelimitedType) else t - bls_bytes = {(x + 7) // 8 for x in fin.bit_length_set} - length = str(max(bls_bytes)) if len(bls_bytes) == 1 else (r'$%d\ldots{}%d$' % (min(bls_bytes), max(bls_bytes))) - - if isinstance(t, pydsdl.DelimitedType): - return 'Size without delimiter header: %s bytes; extent %d bytes.' % ( - length, - t.extent / 8, - ) - else: - return 'Size %s bytes; sealed.' % ( - length, - ) - -def render_dsdl_definition(t: pydsdl.CompositeType) -> str: - minted_params = r'fontsize=\scriptsize, numberblanklines=true, baselinestretch=0.9, autogobble=false' - return '\n'.join([ - r'\begin{minted}[%s]{python}' % minted_params, - open(t.source_file_path).read(), - r'\end{minted}', - ]) - - -try: - index_only = False - if '--index-only' in sys.argv: - sys.argv.remove('--index-only') - index_only = True - - pattern = sys.argv[1] -except IndexError: - die('Expected full type name glob, e.g., "uavcan.node.Heartbeat" or "uavcan.file.*"') - -# Find out which namespace directories to read -root_namespaces = list(filter(os.path.isdir, - map(partial(os.path.join, ROOT_NAMESPACE_SUPERDIRECTORY), - os.listdir(ROOT_NAMESPACE_SUPERDIRECTORY)))) - -# Read all namespaces and join the results into one big list -cache_file_name = get_dsdl_submodule_commit_hash() + CACHE_FILE_NAME_SUFFIX -try: - types = pickle.load(open(cache_file_name, 'rb')) -except Exception as ex: - if not isinstance(ex, FileNotFoundError): - with open('cache_read_error.tmp', 'w') as f: - f.write(repr(ex)) - types = sum([pydsdl.read_namespace(ns, root_namespaces) for ns in root_namespaces], []) - pickle.dump(types, open(cache_file_name, 'wb')) - -# Filter according to the specified pattern -matching = list(filter(lambda t: fnmatch(t.full_name, pattern), types)) -if not matching: - die('No types match the pattern: %s' % pattern) - -# Natural sorting by name and version (numerical ordering, not lexicographical); -# newest version first, oldest version last. -matching.sort(key=lambda t: ([int(c) if c.isdigit() else c for c in re.split(r'(\d+)', t.full_name)] + - [-t.version.major, -t.version.minor])) - -# See if we were asked to render a particular type only. -# If that is the case, output an abridged form, provide a reference, and then exit. -# The output should contain the latest non-deprecated definition. -if '*' not in pattern: - matching_except_deprecated = list(filter(lambda t: not t.deprecated, matching)) - if not matching_except_deprecated: - die('All versions of the type %s are deprecated, nothing to display' % pattern) - - t = matching_except_deprecated[0] # Due to sorting, newest version ends up first - service_or_subject = 'service' if isinstance(t, pydsdl.ServiceType) else 'subject' - print(r'The DSDL source text of \verb|%s|' % t.full_name) - print('version %d.%d' % t.version) - if len(matching) > 2: - print('(there are', len(matching) - 1, 'older versions)') - elif len(matching) > 1: - print('(there is one older version)') - else: - print('(this is the only version)') - - if t.has_fixed_port_id: - print('with a fixed', service_or_subject, 'ID', t.fixed_port_id) - else: - print('without fixed', service_or_subject, 'ID') - - print(r'is provided below.') - print(r'More information is available in') - print(r'section \ref{sec:dsdl:%s} on page \pageref{sec:dsdl:%s}.' % (t.full_name, t.full_name)) - print(r'\pagebreak[2]{}') # This is needed to discourage page breaks within the listings - print(render_dsdl_definition(t)) - exit(0) - -# Group by namespace and by type name (i.e., all versions grouped together by name). -# We re-sort (remember, the sorting is stable) to move types that have at least one version with a fixed port ID -# to the top. -grouped = OrderedDict() -for t in sorted(matching, key=lambda t: not t.has_fixed_port_id): - grouped.setdefault(t.full_namespace, OrderedDict()).setdefault(t.full_name, []).append(t) - -# Render short reference -naked_pattern = pattern.strip('.*') -is_nested_namespace = '.' in naked_pattern - -# We avoid using longtabu unless we really have to because it's a bloody disaster. Its caption is always misaligned -# vertically (requires hacking with \abovecaptionskip to fix) and it tends to run over text and footnotes below it. -table_environment = 'tabu' if is_nested_namespace else 'longtabu' - -if is_nested_namespace: - # Confine nested namespace indexes to one page, assuming that they are always compact enough to fit. - print(r'{\parindent=-\leftskip\begin{minipage}{\textwidth}\centering') - -print(r'\begin{ThreePartTable}') -print(r"\captionof{table}{Index of the %s namespace ``%s''}%%" % - ('nested' if is_nested_namespace else 'root', naked_pattern)) -print(r'\label{table:dsdl:%s}%%' % naked_pattern) -print(r'\footnotesize\setlength\tabcolsep{3pt}\setlength{\tabulinesep}{-1pt}\setlength{\extrarowsep}{-1pt}%') -print(r'\begin{%s}{|l r r|c c|l|}\rowfont{\bfseries}\hline' % table_environment) -print(r'Namespace tree & Ver. & FPID &', - r'max(BLS) bytes &', - r'Extent bytes &', - r'Full name \\\hline') -prefix = '.' -at_least_one_type_emitted = False -INDENT_BLOCK = r'\quad{}' -for namespace, ns_type_mapping in grouped.items(): - # Hint LaTeX that it's a good place to begin a new page if necessary because we're beginning a new namespace - if at_least_one_type_emitted: - print(r'\pagebreak[2]{}') - - # Walk up and down the tree levels, emitting tree mark rows in the process - current_prefix = '.' + namespace + '.' - while prefix != current_prefix: - if current_prefix.startswith(prefix): - new_comp = current_prefix[len(prefix):].strip('.').split('.')[0] - print(INDENT_BLOCK * (prefix.count('.') - 1) + r'\texttt{%s}' % escape(new_comp), r'&&&&&\\', sep='') - prefix += new_comp - else: - prefix = '.' + '.'.join(prefix.strip('.').split('.')[:-1]) - - prefix += '.' - - # Render all types in this namespace - for type_name, versions in ns_type_mapping.items(): - # Render all versions of this type, sorted newest first - versions.sort(key=lambda t: -t.version.major * 1000 - t.version.minor) - for index, t in enumerate(versions): - is_first = index == 0 - is_service = isinstance(t, pydsdl.ServiceType) - at_least_one_type_emitted = True - - # Allow page breaks only when switching namespaces - print(r'\nopagebreak[4]{}') - - # Layout information - b2b = lambda x: (x + 7) // 8 - is_sealed = lambda t: not isinstance(t, pydsdl.DelimitedType) - annotate_sealing = lambda t, x: r'\textit{sealed}' if is_sealed(t) else str(x) - if is_service: - ser_max_bytes = r'$%d \rightleftharpoons{} %d$' % ( - b2b(max(t.request_type.bit_length_set)), - b2b(max(t.response_type.bit_length_set)) - ) - extent_bytes = r'$%s \rightleftharpoons{} %s$' % ( - annotate_sealing(t.request_type, b2b(t.request_type.extent)), - annotate_sealing(t.response_type, b2b(t.response_type.extent)) - ) - else: - ser_max_bytes = r'$%d$' % b2b(max(t.bit_length_set)) - extent_bytes = r'$%s$' % annotate_sealing(t, b2b(t.extent)) - - weak = lambda s: r'\emph{\color{gray}%s}' % s - - if is_first: - print((INDENT_BLOCK * (prefix.count('.') - 1)) + r'\texttt{%s}' % t.short_name, '&') - else: - print((INDENT_BLOCK * prefix.count('.')) + weak('older version'), '&') - - print('%d.%d' % t.version, '&', - t.fixed_port_id if t.has_fixed_port_id else '', '&', - ser_max_bytes, '&', - extent_bytes, '&') - - if is_first: - print(r'\hyperref[sec:dsdl:%s]{\texttt{%s}}' % (t.full_name, escape(t.full_name)), r'\\') - else: - print(weak(r'$\cdots{}$'), r'\\') - -print(r'\hline\end{%s}' % table_environment) -print(r'\end{ThreePartTable}') -if is_nested_namespace: - print(r'\end{minipage}}') - -if index_only: - exit(0) - -# Render definitions -labeled_namespaces = set() -for namespace, children in grouped.items(): - # New section for each new sub-root namespace - if namespace.strip('. ').count('.') < 2: - print(r'\clearpage') - - print(r'\section{%s}' % escape(namespace)) - - # Generate labels for all namespaces, starting from the root one. - # Each label points to the first appearance of its namespace. - for ns in ['.'.join(namespace.split('.')[:i]) for i in range(1, namespace.count('.') + 2)]: - if ns not in labeled_namespaces: - labeled_namespaces.add(ns) - print(r'\label{sec:dsdl:%s}' % ns) - - for full_name, versions in children.items(): - is_service = isinstance(versions[0], pydsdl.ServiceType) - - print(r'\pagebreak[3]{}') - print(r'\subsection{%s}' % full_name.split(pydsdl.CompositeType.NAME_COMPONENT_SEPARATOR)[-1]) - print(r'\label{sec:dsdl:%s}' % full_name) - print(r'Full %s type name: {\bfseries\texttt{%s}}' % - ('service' if is_service else 'message', escape(full_name))) - - for t in versions: - title = 'Version %d.%d' % t.version - - if t.has_fixed_port_id: - service_or_subject = 'service' if is_service else 'subject' - title += ', fixed %s ID %d' % (service_or_subject, t.fixed_port_id) - - if t.deprecated: - title += ', DEPRECATED' - - print(r'\subsubsection{%s}' % title) - print(render_dsdl_info(t)) - print(r'\pagebreak[2]{}') # This is needed to discourage page breaks within the listings - print(render_dsdl_definition(t)) diff --git a/render_list_of_void_and_primitive_types.py b/render_list_of_void_and_primitive_types.py deleted file mode 100755 index ca435b33..00000000 --- a/render_list_of_void_and_primitive_types.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python -# -# This script simply outputs the full set of void and primitive types ordered by bit length. -# - -print(r'\begin{enumerate}') -for i in range(1, 65): - print(' ' * 4 + r'\item ' + r' \quad{} '.join(filter(None, [ - (r'\verb|void%-3d|') % i, - (r'\verb|int%-3d|' % i) if i >= 2 else None, - (r'\verb|uint%-3d|' % i) if i >= 1 else None, - (r'\verb|float%-3d|' % i) if i in (16, 32, 64) else None, - (r'\verb|bool|') if i == 1 else None, - ]))) -print(r'\end{enumerate}') diff --git a/specification/Cyphal_Specification.tex b/specification/Cyphal_Specification.tex index c15d2016..f4821d01 100644 --- a/specification/Cyphal_Specification.tex +++ b/specification/Cyphal_Specification.tex @@ -114,10 +114,7 @@ \section*{Limitation of liability} \input{introduction/introduction.tex} \input{basic/basic.tex} -\input{dsdl/dsdl.tex} \input{transport/transport.tex} -\input{application/application.tex} -\input{sdt/sdt.tex} \input{appendices/appendices.tex} \end{document} diff --git a/specification/dsdl/architecture.tex b/specification/dsdl/architecture.tex deleted file mode 100644 index bc888d4d..00000000 --- a/specification/dsdl/architecture.tex +++ /dev/null @@ -1,272 +0,0 @@ -\section{Architecture} - -\subsection{General principles} - -In accordance with the Cyphal architecture, DSDL allows users to define data types of two kinds: -message types and service types. -Message types are used to exchange data over publish-subscribe one-to-many message links identified by subject-ID, -and service types are used to perform request-response one-to-one exchanges (like RPC) identified by service-ID. -A service type is composed of exactly two inner data types: -one of them is the request type (its instances are transferred from client to server), -and the other is the response type (its instances are transferred from the server back to the client). - -Following the deterministic nature of Cyphal, the size of a serialized representation of any -message or service object is bounded within statically known limits. -Variable-size entities always have a fixed size limit defined by the data type designer. - -DSDL definitions are strongly statically typed. - -DSDL provides a well-defined means of data type versioning, which enables data type maintainers to introduce changes -to released data types while ensuring backward compatibility with fielded systems. - -DSDL is designed to support extensive static analysis. Important properties of data type definitions such as -backward binary compatibility and data field layouts can be checked and validated by automatic software tools -before the systems utilizing them are fielded. - -DSDL definitions can be used to automatically generate serialization (and deserialization) source code -for any data type in a target programming language. -A tool that is capable of generating serialization code based on a DSDL definition is called a \emph{DSDL compiler}. -More generically, a software tool designed for working with DSDL definitions is called a -\emph{DSDL processing tool}. - -\subsection{Data types and namespaces} - -Every data type is located inside a \emph{namespace}. -Namespaces may be included into higher-level namespaces, forming a tree hierarchy. - -A namespace that is at the root of the tree hierarchy (i.e., not nested within another one) -is called a \emph{root namespace}. -A namespace that is located inside another namespace is called a \emph{nested namespace}. - -A data type is uniquely identified by its namespaces and its \emph{short name}. -The short name of a data type is the name of the type itself excluding the containing namespaces. - -A \emph{full name} of a data type consists of its short name and all of its namespace names. -The short name and the namespace names included in a full name are called \emph{name components}. -Name components are ordered: the root namespace is always the first component of the name, -followed by the nested namespaces, if there are any, in the order of their nesting; -the short name is always the last component of the full name. -The full name is formed by joining its name components via the ASCII dot character ``\verb|.|'' (ASCII code 46). - -A \emph{full namespace} name is the full name without the short name and its component separator. - -A \emph{sub-root namespace} is a nested namespace that is located immediately under its root namespace. -Data types that reside directly under their root namespace do not have a sub-root namespace. - -The name structure is illustrated in figure~\ref{fig:dsdl_data_type_name_structure}. - -\begin{figure}[H] - $$ - \overbrace{ - \underbrace{ - \underbrace{\texttt{\huge{uavcan}}}_{\substack{\text{root} \\ \text{namespace}}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{node}}}_{\substack{\text{nested, also} \\ \text{sub-root} \\ \text{namespace}}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{port}}}_{\substack{\text{nested} \\ \text{namespace}}}% - }_{\text{full namespace}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{GetInfo}}}_{\text{short name}} - }^{\text{full name}} - $$ - \caption{Data type name structure\label{fig:dsdl_data_type_name_structure}} -\end{figure} - -A set of full namespace names and a set of full data type names shall not intersect\footnote{% - For example, a namespace ``\texttt{vendor.example}'' and a data type ``\texttt{vendor.example.1.0}'' - are mutually exclusive. - Note the data type name shown in this example violates the naming conventions - which are reviewed in a separate section. -}. - -Data type names and namespace names are case-sensitive. -However, names that differ only in letter case are not permitted\footnote{% - Because that may cause problems with case-insensitive file systems. -}. -In other words, a pair of names which differ only in letter case is considered to constitute a name collision. - -A name component consists of alphanumeric ASCII characters (which are: \verb|A-Z|, \verb|a-z|, and \verb|0-9|) -and underscore (``\verb|_|'', ASCII code 95). -An empty string is not a valid name component. -The first character of a name component shall not be a digit. -A name component shall not match any of the reserved word patterns, -which are listed in table~\ref{table:dsdl_reserved_word_patterns}. - -The length of a full data type name shall not exceed 255 -characters\footnote{This includes the name component separators, but not the version.}. - -Every data type definition is assigned a major and minor version number pair. -In order to uniquely identify a data type definition, its version numbers shall be specified. -In the following text, the term \emph{version} without a majority qualifier refers to -a pair of major and minor version numbers. - -Valid data type version numbers range from 0 to 255, inclusively. -A data type version where both major and minor components are zero is not allowed. - -\subsection{File hierarchy} - -DSDL data type definitions are contained in UTF-8 encoded text files with a file name extension \verb|.dsdl|. - -One file defines exactly one version of a data type, -meaning that each combination of major and minor version numbers shall be unique per data type name. -There may be an arbitrary number of versions of the same data type defined alongside each other, -provided that each version is defined at most once. -Version number sequences can be non-contiguous, -meaning that it is allowed to skip version numbers or remove existing definitions that are neither oldest nor newest. - -A data type definition may have an optional fixed port-ID\footnote{Chapter~\ref{sec:basic}.} value specified. - -The name of a data type definition file is constructed from the following entities -joined via the ASCII dot character ``\verb|.|'' (ASCII code 46), in the specified order: -\begin{itemize} - \item Fixed port-ID in decimal notation, unless a fixed port-ID is not provided for this definition. - \item Short name of the data type (mandatory, always non-empty). - \item Major version number in decimal notation (mandatory). - \item Minor version number in decimal notation (mandatory). - \item File name extension ``\verb|dsdl|'' (mandatory). -\end{itemize} - -\begin{figure}[H] - $$ - \overbrace{% - \underbrace{\texttt{\huge{432}}}_{\substack{\text{fixed} \\ \text{port-ID}}}% - \texttt{\huge{.}}% - }^{\text{optional}}% - \overbrace{% - \underbrace{\texttt{\huge{GetInfo}}}_{\substack{\text{short name}}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{1.0}}}_{\substack{\text{version} \\ \text{numbers}}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{dsdl}}}_{\text{file extension}}% - }^{\text{mandatory}} - $$ - \caption{Data type definition file name structure\label{fig:dsdl_definition_file_name_structure}} -\end{figure} - -DSDL namespaces are represented as directories, where one directory defines exactly one namespace\footnote{% - While a single directory can define only one namespace, the inverse is not prohibited; namespaces can be - defined as the union of the contents of multiple directories that share the same name and folder hierarchy (up to - the root namespace folder). The rules for how distributed definitions such as these are resolved are specific to any - tools that manage them. This specification applies only to the resulting union assuming it is equivalent to a single - file tree. -}, possibly nested. -The name of the directory defines the name of its data type name component. -One directory cannot define more than one level of -nesting\footnote{% - For example, ``\texttt{foo.bar}'' is not a valid directory name. - The valid representation would be ``\texttt{bar}'' nested in ``\texttt{foo}''. -}. - -\begin{remark} - \begin{figure}[H] - \begin{tabu}{|l|X|} \hline - \rowfont{\bfseries} - Directory tree & Entry description \\\hline - - \texttt{vendor\_x/} & - Root namespace \texttt{vendor\_x}. \\\cline{2-2} - - \texttt{\qquad{}foo/} & - Nested namespace (also sub-root) \texttt{vendor\_x.foo}. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}100.Run.1.0.dsdl} & - Data type definition v1.0 with fixed service-ID 100. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}100.Status.1.0.dsdl} & - Data type definition v1.0 with fixed subject-ID 100. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}ID.1.0.dsdl} & - Data type definition v1.0 without fixed port-ID. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}ID.1.1.dsdl} & - Data type definition v1.1 without fixed port-ID. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}bar\_42/} & - Nested namespace \texttt{vendor\_x.foo.bar\_42}. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}\qquad{}101.List.1.0.dsdl} & - Data type definition v1.0 with fixed service-ID 101. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}\qquad{}102.List.2.0.dsdl} & - Data type definition v2.0 with fixed service-ID 102. \\\cline{2-2} - - \texttt{\qquad{}\qquad{}\qquad{}ID.1.0.dsdl} & - Data type definition v1.0 without fixed port-ID. \\\hline - \end{tabu} - \caption{DSDL directory structure example}\label{fig:dsdl_directory_structure_example} - \end{figure} -\end{remark} - -\subsection{Elements of data type definition}\label{sec:dsdl_elements_of_data_type_definition} - -A data type definition file contains an exhaustive description of a particular version of the said data type in the -\emph{data structure description language} (DSDL). - -A data type definition contains an ordered, possibly empty collection of \emph{field attributes} and/or -unordered, possibly empty collection of \emph{constant attributes}. - -A data type may describe either a \emph{structure object} or a \emph{tagged union object}. -The value of a structure object is a function of the values of all of its field attributes. -A tagged union object is formed from at least two field attributes, -but it is capable of holding exactly one field attribute value at any given time. -The value of a tagged union object is a function of which field attribute value -it is holding at the moment and the value of said field attribute. - -A field attribute represents a named dynamically assigned value of a statically defined type -that can be exchanged over the network as a member of its containing object. -A padding field attribute is a special kind of field attribute which is used for data alignment purposes; -such field attributes are not named. - -A constant attribute represents a named statically defined value of a statically defined type. -Constants are never exchanged over the network, since they are assumed to be known to all involved nodes -by virtue of them sharing compatible definitions of the data type. - -Constant values are defined via \emph{DSDL expressions}, -which are evaluated at the time of DSDL definition processing. -There is a special category of types called \emph{expression types}, -instances of which are used only during expression evaluation -and cannot be exchanged over the network. - -Data type definitions can also contain various auxiliary elements reviewed later, -such as deprecation markers (notifying its users that the data type is no longer recommended for new designs) -or assertions (special statements introduced by data type designers -which are statically validated by DSDL processing tools). - -Service type definitions are a special case: -they cannot be instantiated or serialized, they do not contain attributes, -and they are composed of exactly two inner data type definitions\footnote{ - A service type can be thought of as a specialized namespace that contains two types and - has some of the properties of a type, such as name and version. -}. -These inner types are the service request type and the service response type, -separated by the \emph{service response marker}. -They are otherwise ordinary data types except that they are unutterable\footnote{% - Cannot be referred to. Another commonly used term is ``Voldemort type''. -} -and they derive some of their properties\footnote{Like version numbers or deprecation status.} -from their \emph{parent service type}. - -\subsection{Serialization} - -Every object that can be exchanged between Cyphal nodes has a well-defined \emph{serialized representation}. -The value and meaning of an object can be unambiguously recovered from its serialized representation, -provided that the type of the object is known. -Such recovery process is called \emph{deserialization}. - -\label{sec:dsdl_bit_length_set} -A serialized representation is a sequence of binary digits (bits); -the number of bits in a serialized representation is called its \emph{bit length}. -A \emph{bit length set} of a data type refers to the set of bit length values of all possible -serialized representations of objects that are instances of the data type. - -A data type whose bit length set contains more than one element is said to be \emph{variable length}. -The opposite case is referred to as \emph{fixed length}. - -The data type of a serialized message or service object exchanged over the network -is recovered from its subject-ID or service-ID, respectively, -which is attached to the serialized object, along with other metadata, in a manner dictated by the applicable -transport layer specification (chapter~\ref{sec:transport}). -For more information on port identifiers and data type mapping refer to section~\ref{sec:basic_subjects_and_services}. - -The bit length set is not defined on service types (only on their request and response types) -because they cannot be instantiated. diff --git a/specification/dsdl/attributes.tex b/specification/dsdl/attributes.tex deleted file mode 100644 index 762885cd..00000000 --- a/specification/dsdl/attributes.tex +++ /dev/null @@ -1,199 +0,0 @@ -\section{Attributes}\label{sec:dsdl_attributes} - -An \emph{attribute} is a named (excepting padding fields) entity associated with a particular object or type. - -\subsection{Composite type attributes} - -A composite type attribute that has a value assigned at the data type definition time is called a -\emph{constant attribute}; -a composite type attribute that does not have a value assigned at the definition time is called a -\emph{field attribute}. - -The name of a composite type attribute shall be unique within the data type definition that contains it, -and it shall not match any of the reserved name patterns specified in the table -\ref{table:dsdl_reserved_word_patterns}. -This requirement does not apply to padding fields. - -\subsubsection{Field attributes} - -A field attribute represents a named dynamically assigned value of a statically defined type -that can be exchanged over the network as a member of its containing object. -The data type of a field attribute shall be of the serializable type category -(section~\ref{sec:dsdl_serializable_types}), -excepting the void type category, which is not allowed. - -Exception applies to the special kind of field attributes --- \emph{padding fields}. -The type of a padding field attribute shall be of the void category. -A padding field attribute may not have a name. - -A pair of field attributes is considered equal if, and only if, both field attributes are of the same type, -and both share the same name or both are padding field attributes. - -\begin{remark} - Example: - \begin{minted}{python} - uint8[<=10] regular_field # A field named "regular field" - void16 # A padding field; no name is permitted - \end{minted} -\end{remark} - -\subsubsection{Constant attributes} - -A constant attribute represents a named statically assigned value of a statically defined type. -Values of constant attributes are never exchanged over the network, -since they are assumed to be known to all involved nodes -by virtue of them sharing the same definition of the data type. - -The data type of a constant attribute shall be of the primitive type category -(section~\ref{sec:dsdl_serializable_types}). - -The value of the constant attribute is determined at the DSDL definition processing time by evaluating its -\emph{initialization expression}. -The expression shall yield a compatible type upon its evaluation in order to initialize -the value of its constant attribute. -The set of compatible types depends on the type of the initialized constant attribute, -as specified in table~\ref{table:dsdl_constant_init_pattern}. - -\begin{CyphalSimpleTable}[wide]{Permitted constant attribute value initialization patterns}{|l | X | X[2] | X[2]|} - \diagbox[font=\footnotesize]{Constant\\type\\category}{Expression\\type} & - \texttt{bool} & \texttt{rational} & \texttt{string} \\ - - \textbf{Boolean} & - Allowed. & - Not allowed. & - Not allowed. \\ - - \textbf{Integer} & - Not allowed. & - Allowed if the denominator equals one and the numerator value is within the range of the constant type. & - Allowed if the target type is \texttt{uint8} and the source string contains one symbol whose code point falls - into the range $[0, 127]$. \\ - - \textbf{Floating point} & - Not allowed. & - Allowed if the source value does not exceed the finite range of the constant type. - The final value is computed as the quotient of the numerator and the denominator - with implementation-defined accuracy. & - Not allowed. \label{table:dsdl_constant_init_pattern}\\ - -\end{CyphalSimpleTable} - -Due to the value of a constant attribute being defined at the data type definition time, -the cast mode of primitive-typed constants has no observable effect. - -\begin{remark} - A real literal \verb|1234.5678| is represented internally as - $\frac{6172839}{5000}$, which can be used to initialize a \verb|float16| value, - resulting in $1235.0$. - - The specification states that the value of a floating-point constant should be computed - with an implementation-defined accuracy. Cyphal avoids strict accuracy requirements in order to - ensure compatibility with implementations that rely on non-standard floating point formats. - Such laxity in the specification is considered acceptable since the uncertainty is always - confined to a single division expression per constant; all preceding computations, if any, - are always performed by the DSDL compiler using exact rational arithmetic. -\end{remark} - -\subsection{Local attributes}\label{sec:dsdl_local_attributes} - -Local attributes are available at the DSDL definition processing time. - -As defined in section~\ref{sec:dsdl_grammar}, -a DSDL definition is an ordered collection of statements; -a statement may contain DSDL expressions. -An expression contained in a statement number $E$ may refer to a -composite type attribute introduced in a statement number $A$ by its name, -where $A < E$ and both statements belong to the same data type definition\footnote{ - Per \ref{sec:dsdl_elements_of_data_type_definition}, - in case of services, this applies only to their request and response types. -}. -The representation of the referred attribute in the context of the referring DSDL expression -is specified in table~\ref{table:dsdl_local_attribute_representation}. - -\begin{CyphalSimpleTable}{Local attribute representation}{|l X X|}\label{table:dsdl_local_attribute_representation}% - Attribute category & Value type & Value \\ - - Constant attribute & - Type of the constant attribute & - Value of the constant attribute \\ - -% Field attribute & -% \texttt{metaserializable} & -% Type of the field attribute \\ - Field attribute & - Illegal & - Illegal \\ - -\end{CyphalSimpleTable} - -\begin{remark} - \begin{minted}{python} - uint8 FOO = 123 - uint16 BAR = FOO ** 2 - @assert BAR == 15129 - --- # The request type ends here; its attributes are no longer accessible. - #uint16 BAZ = BAR # Would fail - BAR is not accessible here. - float64 FOO = 3.14 - @assert FOO == 3.14 - \end{minted} -\end{remark} - -\subsection{Intrinsic attributes} - -Intrinsic attributes are available in any expression. -Their values are constructed by the DSDL processing tool depending on the context, -as specified in this section. - -\subsubsection{Offset attribute} - -The offset attribute is referred to by its identifier ``\verb|_offset_|''. -Its value is of type $\texttt{set}_\texttt{}$. - -In the following text, the term \emph{referring statement} denotes a statement -containing an expression which refers to the offset attribute. -The term \emph{bit length set} is defined in section~\ref{sec:dsdl_bit_length_set}. - -The value of the attribute is a function of the field attribute declarations preceding the referring statement -and the category of the containing definition. - -If the current definition belongs to the tagged union category, -the referring statement shall be located after the last field attribute definition. -A field attribute definition following the referring statement renders the current definition invalid. -For tagged unions, the value of the offset attribute is defined as the -cumulative bit length set\footnote{Section \ref{sec:dsdl_composite_alignment_cumulative_bls}.} -of the union's fields, where each element of the set is incremented by the bit length of the implicit union tag field -(section \ref{sec:dsdl_serialization_composite}). - -If the current data definition does not belong to the tagged union category, -the referring statement may be located anywhere within the current definition. -The value of the offset attribute is defined as the -cumulative bit length set\footnote{Section \ref{sec:dsdl_composite_alignment_cumulative_bls}.} -of the fields defined in statements preceding the referring statement -(see section~\ref{sec:dsdl_grammar} on statement ordering). - -\begin{remark} - \begin{minted}{python} - @union - uint8 a - #@print _offset_ # Would fail: it's a tagged union, _offset_ is undefined until after the last field - uint16 b - @assert _offset_ == {8 + 8, 8 + 16} - --- - @assert _offset_ == {0} - float16 a - @assert _offset_ == {16} - void4 - @assert _offset_ == {20} - int4 b - @assert _offset_ == {24} - uint8[<4] c - @assert _offset_ == 8 + {24, 32, 40, 48} - @assert _offset_ % 8 == {0} - # One of the main usages for _offset_ is statically proving that the following field is byte-aligned - # for all possible valid serialized representations of the preceding fields. It is done by computing - # a remainder as shown above. If the field is aligned, the remainder set will equal {0}. If the - # remainder set contains other elements, the field may be misaligned under some circumstances. - # If the remainder set does not contain zero, the field is never aligned. - uint8 well_aligned # Proven to be byte-aligned. - \end{minted} -\end{remark} diff --git a/specification/dsdl/compatibility.tex b/specification/dsdl/compatibility.tex deleted file mode 100644 index e7261a14..00000000 --- a/specification/dsdl/compatibility.tex +++ /dev/null @@ -1,374 +0,0 @@ -\section{Compatibility and versioning}\label{sec:dsdl_versioning} - -\subsection{Rationale} - -Data type definitions may evolve over time as they are refined to better address the needs of their applications. -Cyphal defines a set of rules that allow data type designers to modify and advance their -data type definitions while ensuring backward compatibility and functional safety. - -\subsection{Semantic compatibility}\label{sec:dsdl_semantic_compatibility} - -A data type $A$ is \emph{semantically compatible} with a data type $B$ -if relevant behavioral properties of the application are invariant under the substitution of $A$ with $B$. -The property of semantic compatibility is commutative. - -\begin{remark}[breakable] - The following two definitions are semantically compatible and can be used interchangeably: - - \begin{minted}{python} - uint16 FLAG_A = 1 - uint16 FLAG_B = 256 - uint16 flags - @extent 16 - \end{minted} - - \begin{minted}{python} - uint8 FLAG_A = 1 - uint8 FLAG_B = 1 - uint8 flags_a - uint8 flags_b - @extent 16 - \end{minted} - - It should be noted here that due to different set of field and constant attributes, - the source code auto-generated from the provided definitions may be not drop-in replaceable, - requiring changes in the application; - however, source-code-level application compatibility is orthogonal to data type compatibility. - - The following supertype may or may not be semantically compatible with the above - depending on the semantics of the removed field: - - \begin{minted}{python} - uint8 FLAG_A = 1 - uint8 flags_a - @extent 16 - \end{minted} -\end{remark} - -\begin{remark} - Let node $A$ publish messages of the following type: - - \begin{minted}{python} - float32 foo - float64 bar - @extent 128 - \end{minted} - - Let node $B$ subscribe to the same subject using the following data type definition: - - \begin{minted}{python} - float32 foo - float64 bar - int16 baz # Extra field; implicit zero extension rule applies. - @extent 128 - \end{minted} - - Let node $C$ subscribe to the same subject using the following data type definition: - - \begin{minted}{python} - float32 foo - # The field 'bar' is missing; implicit truncation rule applies. - @extent 128 - \end{minted} - - Provided that the semantics of the added and omitted fields allow it, - the nodes will be able to interoperate successfully despite using different data type definitions. -\end{remark} - -\subsection{Versioning} - -\subsubsection{General assumptions} - -The concept of versioning applies only to composite data types. -As such, unless specifically stated otherwise, every reference to ``data type'' -in this section implies a composite data type. - -A data type is uniquely identified by its full name, -assuming that every root namespace is uniquely named. -There is one or more versions of every data type. - -A data type definition is uniquely identified by its full name and the version number pair. -In other words, there may be multiple definitions of a data type differentiated by their version numbers. - -\subsubsection{Versioning principles} - -Every data type definition has a pair of version numbers --- -a major version number and a minor version number, following the principles of semantic versioning. - -For the purposes of the following definitions, a \emph{release} of a data type definition stands for -the disclosure of the data type definition to its intended users or to the general public, -or for the commencement of usage of the data type definition in a production system. - -In order to ensure a deterministic application behavior and ensure a robust migration path -as data type definitions evolve, all data type definitions that share the same -full name and the same major version number shall be semantically compatible with each other. - -The versioning rules do not extend to scenarios where the name of a data type is changed, -because that would essentially construe the release of a new data type, -which relieves its designer from all compatibility requirements. -When a new data type is first released, -the version numbers of its first definition shall be assigned ``1.0'' (major 1, minor 0). - -In order to ensure predictability and functional safety of applications that leverage Cyphal, -it is recommended that once a data type definition is released, -its DSDL source text, name, version numbers, fixed port-ID, extent, sealing, and other properties cannot undergo any -modifications whatsoever, with the following exceptions: -\begin{itemize} - \item Whitespace changes of the DSDL source text are allowed, - excepting string literals and other semantically sensitive contexts. - - \item Comment changes of the DSDL source text are allowed as long as such changes - do not affect semantic compatibility of the definition. - - \item A deprecation marker directive (section~\ref{sec:dsdl_directives}) can be added or removed\footnote{% - Removal is useful when a decision to deprecate a data type definition is withdrawn. - }. -\end{itemize} -Addition or removal of the fixed port identifier is not permitted after a data type definition -of a particular version is released. - -Therefore, substantial changes can be introduced only by releasing new definitions (i.e., new versions) -of the same data type. -If it is desired and possible to keep the same major version number for a new definition of the data type, -the minor version number of the new definition shall be one greater than the newest existing minor version -number before the new definition is introduced. -Otherwise, the major version number shall be incremented by one and the minor version shall be set to zero. - -An exception to the above rules applies when the major version number is zero. -Data type definitions bearing the major version number of zero are not subjected to any compatibility requirements. -Released data type definitions with the major version number of zero are permitted to change in arbitrary -ways without any regard for compatibility. -It is recommended, however, to follow the principles of immutability, releasing every subsequent definition -with the minor version number one greater than the newest existing definition. - -For any data type, there shall be at most one definition per version. -In other words, there shall be exactly one or zero definitions -per combination of data type name and version number pair. - -All data types under the same name shall be also of the same kind. -In other words, if the first released definition of a data type is of the message kind, -all other versions shall also be of the message kind. - -All data types under the same name and major version number should share the same extent and the same sealing status. -It is therefore advised to: -\begin{itemize} - \item Avoid marking types sealed, especially complex types, - because it is likely to render their evolution impossible. - - \item When the first version is released, its extent should be sufficiently large - to permit addition of new fields in the future. - Since the value of extent does not affect the network traffic, it is safe to pick a large value - without compromising the temporal properties of the system. -\end{itemize} - -\subsubsection{Fixed port identifier assignment constraints} - -The following constraints apply to fixed port-ID assignments: -\begin{align*} - \exists P(x_{a.b}) &\rightarrow \exists P(x_{a.c}) - &\mid&\ b < c;\ x \in (M \cup S) - \\ - \exists P(x_{a.b}) &\rightarrow P(x_{a.b}) = P(x_{a.c}) - &\mid&\ b < c;\ x \in (M \cup S) - \\ - \exists P(x_{a.b}) \land \exists P(x_{c.d}) &\rightarrow P(x_{a.b}) \neq P(x_{c.d}) - &\mid&\ a \neq c;\ x \in (M \cup S) - \\ - \exists P(x_{a.b}) \land \exists P(y_{c.d}) &\rightarrow P(x_{a.b}) \neq P(y_{c.d}) - &\mid&\ x \neq y;\ x \in T;\ y \in T;\ T = \left\{ M, S \right\} -\end{align*} -where $t_{a.b}$ denotes a data type $t$ version $a.b$ ($a$ major, $b$ minor); -$P(t)$ denotes the fixed port-ID (whose existence is optional) of data type $t$; -$M$ is the set of message types, and $S$ is the set of service types. - -\subsubsection{Data type version selection} - -DSDL compilers should compile every available data type version separately, -allowing the application to choose from all available major and minor version combinations. - -When emitting a transfer, the major version of the data type is chosen at the discretion of the application. -The minor version should be the newest available one under the chosen major version. - -When receiving a transfer, the node deduces which major version of the data type to use -from its port identifier (either fixed or non-fixed). -The minor version should be the newest available one under the deduced major version\footnote{% - Such liberal minor version selection policy poses no compatibility risks since all definitions under the same - major version are compatible with each other. -}. - -It follows from the above two rules that when a node is responding to a service request, -the major data type version used for the response transfer shall be the same that is used for the request transfer. -The minor versions may differ, which is acceptable due to the major version compatibility requirements. - -\begin{remark}[breakable] - A simple usage example is provided in this intermission. - - Suppose a vendor named ``Sirius Cybernetics Corporation'' is contracted to design a - cryopod management data bus for a colonial spaceship ``Golgafrincham B-Ark''. - Having consulted with applicable specifications and standards, an engineer came up with the following - definition of a cryopod status message type (named \verb|sirius_cyber_corp.b_ark.cryopod.Status|): - - \begin{minted}{python} - # sirius_cyber_corp.b_ark.cryopod.Status.0.1 - - float16 internal_temperature # [kelvin] - float16 coolant_temperature # [kelvin] - - uint8 FLAG_COOLING_SYSTEM_A_ACTIVE = 1 - uint8 FLAG_COOLING_SYSTEM_B_ACTIVE = 2 - # Status flags in the lower bits. - uint8 FLAG_PSU_MALFUNCTION = 32 - uint8 FLAG_OVERHEATING = 64 - uint8 FLAG_CRYOBOX_BREACH = 128 - # Error flags in the higher bits. - uint8 flags # Storage for the above defined flags (this is not the recommended practice). - - @extent 1024 * 8 # Pick a large extent to allow evolution. Does not affect network traffic. - \end{minted} - - The definition is then deployed to the first prototype for initial laboratory testing. - Since the definition is experimental, the major version number is set to zero in order to signify the - tentative nature of the definition. - Suppose that upon completion of the first trials it is identified that the units should track their - power consumption in real time for each of the three redundant power supplies independently. - - It is easy to see that the amended definition shown below is not semantically compatible - with the original definition; however, it shares the same major version number of zero, because the backward - compatibility rules do not apply to zero-versioned data types to allow for low-overhead experimentation - before the system is deployed and fielded. - - \begin{minted}{python} - # sirius_cyber_corp.b_ark.cryopod.Status.0.2 - - truncated float16 internal_temperature # [kelvin] - truncated float16 coolant_temperature # [kelvin] - - saturated float32 power_consumption_0 # [watt] Power consumption by the redundant PSU 0 - saturated float32 power_consumption_1 # [watt] likewise for PSU 1 - saturated float32 power_consumption_2 # [watt] likewise for PSU 2 - # breaking compatibility with Status.0.1 is okay because the major version is 0 - - uint8 FLAG_COOLING_SYSTEM_A_ACTIVE = 1 - uint8 FLAG_COOLING_SYSTEM_B_ACTIVE = 2 - # Status flags in the lower bits. - uint8 FLAG_PSU_MALFUNCTION = 32 - uint8 FLAG_OVERHEATING = 64 - uint8 FLAG_CRYOBOX_BREACH = 128 - # Error flags in the higher bits. - uint8 flags # Storage for the above defined flags (this is not the recommended practice). - - @extent 512 * 8 # Extent can be changed freely because v0.x does not guarantee compatibility. - \end{minted} - - The last definition is deemed sufficient and is deployed to the production system - under the version number of 1.0: \verb|sirius_cyber_corp.b_ark.cryopod.Status.1.0|. - - Having collected empirical data from the fielded systems, the Sirius Cybernetics Corporation has - identified a shortcoming in the v1.0 definition, which is corrected in an updated definition. - Since the updated definition, which is shown below, is semantically compatible\footnote{% - The topic of data serialization is explored in detail in section~\ref{sec:dsdl_data_serialization}. - } with v1.0, the major version number is kept the same and the minor version number is incremented by one: - - \begin{minted}{python} - # sirius_cyber_corp.b_ark.cryopod.Status.1.1 - - saturated float16 internal_temperature # [kelvin] - saturated float16 coolant_temperature # [kelvin] - - float32[3] power_consumption # [watt] Power consumption by the PSU - - bool flag_cooling_system_a_active - bool flag_cooling_system_b_active - # Status flags (this is the recommended practice). - - void3 # Reserved for other flags - - bool flag_psu_malfunction - bool flag_overheating - bool flag_cryobox_breach - # Error flags (this is the recommended practice). - - @extent 512 * 8 # Extent is to be kept unchanged now to avoid breaking compatibility. - \end{minted} - - Since the definitions v1.0 and v1.1 are semantically compatible, - Cyphal nodes using either of them can successfully interoperate on the same bus. - - Suppose further that at some point a newer version of the cryopod module, - equipped with better temperature sensors, is released. - The definition is updated accordingly to use \verb|float32| for the temperature fields instead of \verb|float16|. - Seeing as that change breaks the compatibility, the major version number has to be incremented by one, - and the minor version number has to be reset back to zero: - - \begin{minted}{python} - # sirius_cyber_corp.b_ark.cryopod.Status.2.0 - - float32 internal_temperature # [kelvin] - float32 coolant_temperature # [kelvin] - - float32[3] power_consumption # [watt] Power consumption by the PSU - - bool flag_cooling_system_a_active - bool flag_cooling_system_b_active - void3 - bool flag_psu_malfunction - bool flag_overheating - bool flag_cryobox_breach - - @extent 768 * 8 # Since the major version number is different, extent can be changed. - \end{minted} - - Imagine that later it was determined that the module should report additional status information - relating to the coolant pump. - Thanks to the implicit truncation (section \ref{sec:dsdl_serialization_implicit_truncation}), - implicit zero extension (section \ref{sec:dsdl_serialization_implicit_zero_extension}), - and the delimited serialization (section \ref{sec:dsdl_serialization_composite_non_sealed}), - the new fields can be introduced in a semantically-compatible way without releasing - a new major version of the data type: - - \begin{minted}{python} - # sirius_cyber_corp.b_ark.cryopod.Status.2.1 - - float32 internal_temperature # [kelvin] - float32 coolant_temperature # [kelvin] - - float32[3] power_consumption # [watt] Power consumption by the PSU - - bool flag_cooling_system_a_active - bool flag_cooling_system_b_active - void3 - bool flag_psu_malfunction - bool flag_overheating - bool flag_cryobox_breach - - float32 rotor_angular_velocity # [radian/second] (usage of RPM would be non-compliant) - float32 volumetric_flow_rate # [meter^3/second] - # Coolant pump fields (extension over v2.0; implicit truncation/extension rules apply) - # If zero, assume that the values are unavailable. - - @extent 768 * 8 - \end{minted} - - It is also possible to add an optional field at the end wrapped into a variable-length - array of up to one element, or a tagged union where the first field is empty - and the second field is the wrapped value. - In this way, the implicit truncation/extension rules would automatically make such optional field - appear/disappear depending on whether it is supported by the receiving node. - - Nodes using v1.0, v1.1, v2.0, and v2.1 definitions can coexist on the same network, - and they can interoperate successfully as long as they all support at least v1.x or v2.x. - The correct version can be determined at runtime from the port identifier assignment as described in - section~\ref{sec:basic_subjects_and_services}. - - In general, nodes that need to maximize their compatibility are likely to employ all existing major versions of - each used data type. - If there are more than one minor versions available, the highest minor version within the major version should - be used in order to take advantage of the latest changes in the data type definition. - It is also expected that in certain scenarios some nodes may resort to publishing the same message type - using different major versions concurrently to circumvent compatibility issues - (in the example reviewed here that would be v1.1 and v2.1). - - The examples shown above rely on the primitive scalar types for reasons of simplicity. - Real applications should use the type-safe physical unit definitions available in the SI namespace instead. - This is covered in section~\ref{sec:application_functions_si}. -\end{remark} diff --git a/specification/dsdl/conventions.tex b/specification/dsdl/conventions.tex deleted file mode 100644 index 74ace5f3..00000000 --- a/specification/dsdl/conventions.tex +++ /dev/null @@ -1,138 +0,0 @@ -\section{Conventions and recommendations} - -This section is dedicated to conventions and recommendations -intended to help data type designers maintain a consistent style across the ecosystem -and avoid some common pitfalls. -All of the conventions and recommendations provided in this section are optional (not mandatory to follow). - -\subsection{Naming recommendations} - -The DSDL naming recommendations follow those that are widely accepted in the general software development industry. - -\begin{itemize} - \item Namespaces and field attributes should be named in the \verb|snake_case|. - \item Constant attributes should be named in the \verb|SCREAMING_SNAKE_CASE|. - \item Data types (excluding their namespaces) should be named in the \verb|PascalCase|. - \item Names of message types should form a declarative phrase or a noun. For example, - \verb|BatteryStatus| or \verb|OutgoingPacket|. - \item Names of service types should form an imperative phrase or a verb. For example, - \verb|GetInfo| or \verb|HandleIncomingPacket|. - \item Short names, unnecessary abbreviations, and uncommon acronyms should be avoided. -\end{itemize} - -\subsection{Comments} - -Every data type definition file should begin with a header comment that provides an exhaustive description -of the data type, its purpose, semantics, usage patterns, any related data exchange patterns, -assumptions, constraints, and all other information that may be necessary or generally useful for the usage of the -data type definition. - -Every attribute of the data type definition, and especially every field attribute of it, -should have an associated comment explaining the purpose of the attribute, its semantics, usage patterns, -assumptions, constraints, and any other pertinent information. -Exception applies to attributes supplied with sufficiently descriptive and unambiguous names. - -A comment should be placed after the entity it is intended to describe; -either on the same line (in which case it should be separated from the preceding text with at least two spaces) -or on the next line (without blank lines in between). -This recommendation does not apply to the file header comment. - -% Field comment placement https://forum.opencyphal.org/t/dsdl-documentation-comments/407 - -\subsection{Optional value representation} - -Data structures may include optional field attributes that are not always populated. - -The recommended approach for representing optional field attributes -is to use variable-length arrays with the capacity of one element. - -Alternatively, such one-element variable-length arrays can be replaced with two-field unions, -where the first field is empty and the second field contains the desired optional value. -The described layout is semantically compatible with the one-element array described above, -provided that the field attributes are not swapped. - -Floating-point-typed field attributes may be assigned the value of not-a-number (NaN) per IEEE 754 -to indicate that the value is not specified; -however, this pattern is discouraged because the value would still have to be transferred over the bus -even if not populated, and special case values undermine type safety. - -\begin{remark}[breakable] - Array-based optional field: - - \begin{minted}{python} - MyType[<=1] optional_field - \end{minted} - - Union-based optional field: - - \begin{minted}{python} - @sealed # Sic! - @union # The implicit tag is one byte long. - uavcan.primitive.Empty none # Represents lack of value, unpopulated field. - MyType some # The field of interest; field ordering is important. - \end{minted} - - The defined above union can be used as follows (suppose it is named \verb|MaybeMyType|): - - \begin{minted}{python} - MaybeMyType optional_field - \end{minted} - - The shown approaches are semantically compatible. -\end{remark} - -\begin{remark}[breakable] - The implicit truncation and the implicit zero extension rules allow one to freely add such optional fields - at the end of a definition while retaining semantic compatibility. - The implicit truncation rule will render them invisible to nodes that utilize older data type definitions - which do not contain them, whereas nodes that utilize newer definitions will be able to correctly process - objects serialized using older definitions because the implicit zero extension rule guarantees - that the optional fields will appear unpopulated. - - For example, let the following be the old message definition: - - \begin{minted}{python} - float64 foo - float32 bar - \end{minted} - - The new message definition with the new field is as follows: - - \begin{minted}{python} - float64 foo - float32 bar - MyType[<=1] my_new_field - \end{minted} - - Suppose that one node is publishing a message using the old definition, - and another node is receiving it using the new definition. - The implicit zero extension rule guarantees that the optional field array will - appear empty to the receiving node because the implicit length field will be read as zero. - Same is true if the message was nested inside another one, thanks to the delimiter header. -\end{remark} - -\subsection{Bit flag representation} - -The recommended approach to defining a set of bit flags is to dedicate a \verb|bool|-typed field attribute for each. -Representations based on an integer sum of powers of two\footnote{Which are popular in programming.} -are discouraged due to their obscurity and failure to express the intent clearly. - -\begin{remark} - Recommended approach: - - \begin{minted}{python} - void5 - bool flag_foo - bool flag_bar - bool flag_baz - \end{minted} - - Not recommended: - - \begin{minted}{python} - uint8 flags # Not recommended - uint8 FLAG_BAZ = 1 - uint8 FLAG_BAR = 2 - uint8 FLAG_FOO = 4 - \end{minted} -\end{remark} diff --git a/specification/dsdl/directives.tex b/specification/dsdl/directives.tex deleted file mode 100644 index 467cb768..00000000 --- a/specification/dsdl/directives.tex +++ /dev/null @@ -1,171 +0,0 @@ -\section{Directives}\label{sec:dsdl_directives} - -Per the DSDL grammar specification (section~\ref{sec:dsdl_grammar}), -a directive may or may not have an associated expression. -In this section, it is assumed that a directive does not expect an expression unless explicitly stated otherwise. - -If the expectation of an associated directive expression or lack thereof is violated, -the containing DSDL definition is malformed. - -The effect of a directive is confined to the data type definition that contains it. -That means that for service types, unless specifically stated otherwise, -a directive placed in the request (response) type affects only the request (response) type. - -\subsection{Tagged union marker} - -The identifier of the tagged union marker directive is ``\verb|union|''. -Presence of this directive in a data type definition indicates that the -data type definition containing this directive belongs to the tagged union category -(section~\ref{sec:dsdl_composite_tagged_unions}). - -Usage of this directive is subject to the following constraints: -\begin{itemize} - \item The directive shall not be used more than once per data type definition. - \item The directive shall be placed before the first composite type attribute definition in the current definition. -\end{itemize} - -\begin{remark} - \begin{minted}{python} - uint8[<64] name # Request is not a union - --- - @union # Response is a union - uint64 natural - #@union # Would fail - @union is not allowed after the first attribute definition - float64 real - \end{minted} -\end{remark} - -\subsection{Extent specifier}\label{sec:dsdl_directive_extent} - -The identifier of the extent specification directive is ``\verb|extent|''. -This directive declares the current data type definition to be delimited (non-sealed) -and specifies its extent (section \ref{sec:dsdl_composite_extent_and_sealing}). -The extent value is obtained by evaluating the provided expression. -The expression shall be present and it shall yield a non-negative integer value of type -``\verb|rational|'' (section~\ref{sec:dsdl_primitive_types}) upon its evaluation. - -Usage of this directive is subject to the following constraints (otherwise, the definition is malformed): -\begin{itemize} - \item The directive shall not be used more than once per data type definition. - \item The directive shall be placed after the last attribute definition in the current data type\footnote{% - This constraint is to help avoid issues where the extent is defined as a function of the offset past - the last field of the type, and a new field is mistakenly added after the extent directive. - }. - \item The value shall satisfy the constraints given in section \ref{sec:dsdl_composite_extent_and_sealing}. - \item The data type shall not be sealed. -\end{itemize} - -\begin{remark} - \begin{minted}{python} - uint64 foo - @extent 256 * 8 # Make the extent 256 bytes large - #@sealed # Would fail -- mutually exclusive directives - \end{minted} - - \begin{minted}{python} - uint64[<=64] bar - @extent _offset_.max * 2 - #float32 baz # Would fail (protects against incorrectly computing - # the extent when it is a function of _offset_) - \end{minted} -\end{remark} - -\subsection{Sealing marker}\label{sec:dsdl_directive_sealed} - -The identifier of the sealing marker directive is ``\verb|sealed|''. -This directive marks the current data type sealed (section \ref{sec:dsdl_composite_extent_and_sealing}). - -Usage of this directive is subject to the following constraints (otherwise, the definition is malformed): -\begin{itemize} - \item The directive shall not be used more than once per data type definition. - \item The extent directive shall not be used in this data type definition. -\end{itemize} - -\begin{remark} - \begin{minted}{python} - uint64 foo - @sealed # The request type is sealed. - #@extent 128 # Would fail -- cannot specify extent for sealed type - --- - float64 bar # The response type is not sealed. - @extent 4000 * 8 - \end{minted} -\end{remark} - -\subsection{Deprecation marker} - -The identifier of the deprecation marker directive is ``\verb|deprecated|''. -Presence of this directive in a data type definition indicates that the current version of the data type definition -is nearing the end of its life cycle and may be removed soon. -The data type versioning principles are explained in section~\ref{sec:dsdl_versioning}. - -Code generation tools should use this directive to reflect the impending removal of the current data type version -in the generated code. - -Usage of this directive is subject to the following constraints: -\begin{itemize} - \item The directive shall not be used more than once per data type definition. - \item The directive shall be placed before the first composite type attribute definition in the definition. - \item In case of service types, this directive may only be placed in the request type, - and it affects the response type as well. -\end{itemize} - -\begin{remark} - \begin{minted}{python} - @deprecated # Applies to the entire definition - uint8 FOO = 123 - #@deprecated # Would fail - shall be placed before the first attribute definition - --- - #@deprecated # Would fail - shall be placed in the request type - \end{minted} - - A C++ class generated from the above definition could be annotated with the \verb|[[deprecated]]| attribute. - - A Rust structure generated from the above definition could be annotated with the \verb|#[deprecated]| attribute. - - A Python class generated from the above definition could raise \verb|DeprecationWarning| upon usage. -\end{remark} - -\subsection{Assertion check} - -The identifier of the assertion check directive is ``\verb|assert|''. -The assertion check directive expects an expression which shall yield a value of type -``\verb|bool|'' (section~\ref{sec:dsdl_primitive_types}) upon its evaluation. - -If the expression yields truth, the assertion check directive has no effect. - -If the expression yields falsity, a value of type other than ``\verb|bool|'', or fails to evaluate, -the containing DSDL definition is malformed. - -\begin{remark} - \begin{minted}{python} - float64 real - @assert _offset_ == {32} # Would fail: {64} != {32} - \end{minted} -\end{remark} - -\subsection{Print} - -The identifier of the print directive is ``\verb|print|''. -The print directive may or may not be provided with an associated expression. - -If the expression is not provided, the behavior is implementation-defined. - -If the expression is provided, it is evaluated and its result is displayed by the DSDL processing tool in -a human-readable implementation-defined form. -Implementations should strive to produce textual representations that form valid DSDL expressions themselves, -so that they would produce the same value if evaluated by a DSDL processing tool. - -If the expression is provided but cannot be evaluated, the containing DSDL definition is malformed. - -\begin{remark} - \begin{minted}{python} - float64 real - @print _offset_ / 6 # Possible output: {32/3} - @print uavcan.node.Heartbeat.1.0 # Possible output: uavcan.node.Heartbeat.1.0 - @print bool[<4] # Possible output: saturated bool[<=3] - @print float64 # Possible output: saturated float64 - @print {123 == 123, false} # Possible output: {true, false} - @print 'we all float64 down here\n' # Possible output: 'we all float64 down here\n' - \end{minted} -\end{remark} diff --git a/specification/dsdl/dsdl.tex b/specification/dsdl/dsdl.tex deleted file mode 100644 index 18da2da5..00000000 --- a/specification/dsdl/dsdl.tex +++ /dev/null @@ -1,19 +0,0 @@ -\chapter{Data structure description language}\label{sec:dsdl} - -The data structure description language, or \emph{DSDL}, is a simple domain-specific language designed for -defining composite data types. -The defined data types are used for exchanging data between Cyphal nodes via one of the standard Cyphal -transport protocols\footnote{The standard transport protocols are documented in chapter~\ref{sec:transport}. -Cyphal doesn't prohibit users from defining their own application-specific transports as well, -although users doing that are likely to encounter compatibility issues and possibly a suboptimal -performance of the protocol.}. - -\input{dsdl/architecture.tex} -\input{dsdl/grammar.tex} -\input{dsdl/expression_types.tex} -\input{dsdl/serializable_types.tex} -\input{dsdl/attributes.tex} -\input{dsdl/directives.tex} -\input{dsdl/serialization.tex} -\input{dsdl/compatibility.tex} -\input{dsdl/conventions.tex} diff --git a/specification/dsdl/expression_types.tex b/specification/dsdl/expression_types.tex deleted file mode 100644 index 8c9ff964..00000000 --- a/specification/dsdl/expression_types.tex +++ /dev/null @@ -1,212 +0,0 @@ -\section{Expression types}\label{sec:dsdl_expression_types} - -Expression types are a special category of data types whose instances can only exist and be operated upon -at the time of DSDL definition processing. -As such, expression types cannot be used to define attributes, -and their instances cannot be exchanged between nodes. - -Expression types are used to represent values of constant expressions which are evaluated -when a DSDL definition is processed. -Results of such expressions can be used to define various constant properties, -such as array length boundaries or values of constant attributes. - -Expression types are specified in this section. -Each expression type has a formal DSDL name for completeness; -even if such types can't be used to define attributes, -a well-defined formal name allows DSDL processing tools to emit well-formed -and understandable diagnostic messages. - -\subsection{Rational number}\label{sec:dsdl_rational} - -At the time of DSDL definition processing, integer and real numbers are represented internally as rational numbers -where the range of numerator and denominator is unlimited\footnote{% - Technically, the range may only be limited by the memory resources available to the DSDL processing tool. -}. -DSDL processing tools are not permitted to introduce any implicit rational number transformations that -may result in a loss of information. - -The DSDL name of the rational number type is ``\verb|rational|''. - -Rational numbers are assumed to be stored in a normalized form, where the denominator is positive -and the greatest common divisor of the numerator and the denominator is one. - -A rational number can be used in a context where an integer value is expected only if its denominator equals one. - -Implicit conversions between boolean-valued entities and rational numbers are not allowed. - -\begin{CyphalSimpleTable}{Operators defined on instances of rational numbers}{|l l X X|} - Op & Type & Constraints & Description - \label{table:dsdl_operators_rational} \\ - - \texttt{\textbf{+}} & $(\texttt{rational}) \rightarrow \texttt{rational}$ & & - No effect. \\ - \texttt{\textbf{-}} & $(\texttt{rational}) \rightarrow \texttt{rational}$ & & - Negation. \\ - - \texttt{\textbf{**}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Power denominator equals one & - Exact exponentiation. \\ - - \texttt{\textbf{**}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Power denominator greater than one & - Exponentiation with imp\-lem\-en\-ta\-ti\-on-de\-fin\-ed accuracy. \\ - - \texttt{\textbf{*}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & & - Exact multiplication. \\ - - \texttt{\textbf{/}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Non-zero divisor & - Exact division. \\ - - \texttt{\textbf{\%}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Non-zero divisor & - Exact modulo. \\ - - \texttt{\textbf{+}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & & - Exact addition. \\ - - \texttt{\textbf{-}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & & - Exact subtraction. \\ - - \texttt{\textbf{|}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Denominators equal one & - Bitwise or. \\ - - \texttt{\textbf{\textasciicircum{}}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Denominators equal one & - Bitwise xor. \\ - - \texttt{\textbf{\&}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{rational}$ & - Denominators equal one & - Bitwise and. \\ - - \texttt{\textbf{!=}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{bool}$ & & Exact inequality. \\ - \texttt{\textbf{==}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{bool}$ & & Exact equality. \\ - \texttt{\textbf{<=}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{bool}$ & & Less or equal. \\ - \texttt{\textbf{>=}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{bool}$ & & Greater or equal. \\ - \texttt{\textbf{<}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{bool}$ & & Strictly less. \\ - \texttt{\textbf{>}} & $(\texttt{rational}, \texttt{rational}) \rightarrow \texttt{bool}$ & & Strictly greater. \\ - -\end{CyphalSimpleTable} - -\subsection{Unicode string}\label{sec:dsdl_string} - -This type contains a sequence of Unicode characters. -It is used to represent string literals internally. - -The DSDL name of the Unicode string type is ``\verb|string|''. - -A Unicode string containing one symbol whose code point is within $[0, 127]$ -(i.e., an ASCII character) is implicitly convertible into a \verb|uint8|-typed constant attribute value, -where the value of the constant is to be equal the code point of the symbol. - -\begin{CyphalSimpleTable}{Operators defined on instances of Unicode strings}{|l l X|} - Op & Type & Description - \label{table:dsdl_operators_string} \\ - - \texttt{\textbf{+}} & $(\texttt{string}, \texttt{string}) \rightarrow \texttt{string}$ & - Concatenation. \\ - - \texttt{\textbf{!=}} & $(\texttt{string}, \texttt{string}) \rightarrow \texttt{bool}$ & - Inequality of Unicode NFC normalized forms. - NFC stands for \emph{Normalization Form Canonical Composition} -- - one of standard Unicode normalization forms where characters are recomposed by canonical equivalence. \\ - - \texttt{\textbf{==}} & $(\texttt{string}, \texttt{string}) \rightarrow \texttt{bool}$ & - Equality of Unicode NFC normalized forms. \\ - -\end{CyphalSimpleTable} - -The set of operations and conversions defined for Unicode strings is to be extended in future versions of -the specification. - -\subsection{Set}\label{sec:dsdl_set} - -A set type represents an unordered collection of unique objects. -All objects shall be of the same type. -Uniqueness of elements is determined by application of the equality operator ``\verb|==|''. - -The DSDL name of the set type is ``\verb|set|''. - -A set can be constructed from a set literal, in which case such set shall contain at least one element. - -The attributes and operators defined on set instances are listed in the tables~\ref{table:dsdl_set_attributes} -and~\ref{table:dsdl_set_operators}, where $E$ represents the set element type. - -\begin{CyphalSimpleTable}{Attributes defined on instances of sets}{|l l X X|} - Name & Type & Constraints & Description - \label{table:dsdl_set_attributes} \\ - - \texttt{min} & $E$ & - Operator ``\texttt{<}'' is defined \mbox{$(E, E) \rightarrow \texttt{bool}$} & - Smallest element in the set determined by sequential application of the operator ``\texttt{<}''. \\ - - \texttt{max} & $E$ & - Operator ``\texttt{<}'' is defined \mbox{$(E, E) \rightarrow \texttt{bool}$} & - Greatest element in the set determined by sequential application of the operator ``\texttt{<}''. \\ - - \texttt{count} & \texttt{rational} & & - Cardinality. \\ - -\end{CyphalSimpleTable} - -\newcommand\SetElementwiseOperator[1]{% - \texttt{\textbf{#1}} & $(\texttt{set}_\texttt{}, E) \rightarrow \texttt{set}_\texttt{}$ & $E$ is not a set & - Elementwise $(E, E) \rightarrow R$.\\ - - \texttt{\textbf{#1}} & $(E, \texttt{set}_\texttt{}) \rightarrow \texttt{set}_\texttt{}$ & $E$ is not a set & - Elementwise $(E, E) \rightarrow R$.\\ -} - -\begin{CyphalSimpleTable}{Operators defined on instances of sets}{|l l l X|}% - \label{table:dsdl_set_operators}% - Op & Type & Constraints & Description \\ - - \texttt{\textbf{==}} & $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{bool}$ & & - Left equals right. \\ - - \texttt{\textbf{!=}} & $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{bool}$ & & - Left does not equal right. \\ - - \texttt{\textbf{<=}} & $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{bool}$ & & - Left is a subset of right. \\ - - \texttt{\textbf{>=}} & $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{bool}$ & & - Left is a superset of right. \\ - - \texttt{\textbf{<}} & $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{bool}$ & & - Left is a proper subset of right. \\ - - \texttt{\textbf{>}} & $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{bool}$ & & - Left is a proper superset of right. \\ - - \texttt{\textbf{|}} & - $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{set}_\texttt{}$ & & - Union. \\ - - \texttt{\textbf{\textasciicircum{}}} & - $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{set}_\texttt{}$ & & - Disjunctive union. \\ - - \texttt{\textbf{\&}} & - $(\texttt{set}_\texttt{}, \texttt{set}_\texttt{}) \rightarrow \texttt{set}_\texttt{}$ & & - Intersection. \\ - - \SetElementwiseOperator{**} - \SetElementwiseOperator{*} - \SetElementwiseOperator{/} - \SetElementwiseOperator{\%} - \SetElementwiseOperator{+} - \SetElementwiseOperator{-} - -\end{CyphalSimpleTable} - -\subsection{Serializable metatype}\label{sec:dsdl_metaserializable} - -Serializable types (which are reviewed in section~\ref{sec:dsdl_serializable_types}) -are instances of the serializable metatype. -This metatype is convenient for expression of various relations and attributes defined on serializable types. - -The DSDL name of the serializable metatype is ``\verb|metaserializable|''. - -Available attributes are defined on a per-instance basis. diff --git a/specification/dsdl/grammar.parsimonious b/specification/dsdl/grammar.parsimonious deleted file mode 100644 index 27bdcd4c..00000000 --- a/specification/dsdl/grammar.parsimonious +++ /dev/null @@ -1,182 +0,0 @@ -definition = line (end_of_line line)* # An empty file is a valid definition. Trailing end-of-line is optional. -line = statement? _? comment? # An empty line is a valid line. -comment = ~r"#[^\r\n]*" -end_of_line = ~r"\r?\n" # Unix/Windows -_ = ~r"[ \t]+" # Whitespace - -identifier = ~r"[a-zA-Z_][a-zA-Z0-9_]*" - -# ==================================================== Statements ==================================================== - -statement = statement_directive - / statement_service_response_marker - / statement_attribute - -statement_attribute = statement_constant - / statement_field - / statement_padding_field - -statement_constant = type _ identifier _? "=" _? expression -statement_field = type _ identifier -statement_padding_field = type_void "" # The trailing empty symbol is to prevent the node from being optimized away. - -statement_service_response_marker = ~r"---+" # Separates request/response, specifies that the definition is a service. - -statement_directive = statement_directive_with_expression - / statement_directive_without_expression -statement_directive_with_expression = "@" identifier _ expression # The expression type shall match the directive. -statement_directive_without_expression = "@" identifier - -# ==================================================== Data types ==================================================== - -type = type_array - / type_scalar - -type_array = type_array_variable_inclusive - / type_array_variable_exclusive - / type_array_fixed - -type_array_variable_inclusive = type_scalar _? "[" _? "<=" _? expression _? "]" # Expression shall yield integer. -type_array_variable_exclusive = type_scalar _? "[" _? "<" _? expression _? "]" -type_array_fixed = type_scalar _? "[" _? expression _? "]" - -type_scalar = type_versioned - / type_primitive - / type_void - -type_versioned = identifier ("." identifier)* "." type_version_specifier -type_version_specifier = literal_integer_decimal "." literal_integer_decimal - -type_primitive = type_primitive_boolean - / type_primitive_byte - / type_primitive_utf8 - / type_primitive_truncated - / type_primitive_saturated - -type_primitive_boolean = "bool" -type_primitive_byte = "byte" -type_primitive_utf8 = "utf8" -type_primitive_truncated = "truncated" _ type_primitive_name -type_primitive_saturated = ("saturated" _)? type_primitive_name # Defaults to this. - -type_primitive_name = type_primitive_name_unsigned_integer - / type_primitive_name_signed_integer - / type_primitive_name_floating_point - -type_primitive_name_unsigned_integer = "uint" type_bit_length_suffix -type_primitive_name_signed_integer = "int" type_bit_length_suffix -type_primitive_name_floating_point = "float" type_bit_length_suffix - -type_void = "void" type_bit_length_suffix - -type_bit_length_suffix = ~r"[1-9]\d*" - -# ==================================================== Expressions ==================================================== - -expression = ex_logical # Aliased for clarity. - -expression_list = (expression (_? "," _? expression)*)? # May be empty. - -expression_parenthesized = "(" _? expression _? ")" # Used for managing precedence. - -expression_atom = expression_parenthesized # Ordering matters. - / type - / literal - / identifier - -# Operators. The precedence relations are expressed in the rules; the order here is from lower to higher. -# Operators that share common prefix (e.g. < and <=) are arranged so that the longest form is specified first. -ex_logical = ex_logical_not (_? op2_log _? ex_logical_not)* -ex_logical_not = op1_form_log_not / ex_comparison -ex_comparison = ex_bitwise (_? op2_cmp _? ex_bitwise)* -ex_bitwise = ex_additive (_? op2_bit _? ex_additive)* -ex_additive = ex_multiplicative (_? op2_add _? ex_multiplicative)* -ex_multiplicative = ex_inversion (_? op2_mul _? ex_inversion)* -ex_inversion = op1_form_inv_pos / op1_form_inv_neg / ex_exponential -ex_exponential = ex_attribute (_? op2_exp _? ex_inversion)? # Right recursion -ex_attribute = expression_atom (_? op2_attrib _? identifier)* - -# Unary operator forms are moved into separate rules for ease of parsing. -op1_form_log_not = "!" _? ex_logical_not # Right recursion -op1_form_inv_pos = "+" _? ex_exponential -op1_form_inv_neg = "-" _? ex_exponential - -# Logical operators; defined for booleans. -op2_log = op2_log_or / op2_log_and -op2_log_or = "||" -op2_log_and = "&&" - -# Comparison operators. -op2_cmp = op2_cmp_equ / op2_cmp_geq / op2_cmp_leq / op2_cmp_neq / op2_cmp_lss / op2_cmp_grt # Ordering is important. -op2_cmp_equ = "==" -op2_cmp_neq = "!=" -op2_cmp_leq = "<=" -op2_cmp_geq = ">=" -op2_cmp_lss = "<" -op2_cmp_grt = ">" - -# Bitwise integer manipulation operators. -op2_bit = op2_bit_or / op2_bit_xor / op2_bit_and -op2_bit_or = "|" -op2_bit_xor = "^" -op2_bit_and = "&" - -# Additive operators. -op2_add = op2_add_add / op2_add_sub -op2_add_add = "+" -op2_add_sub = "-" - -# Multiplicative operators. -op2_mul = op2_mul_mul / op2_mul_div / op2_mul_mod # Ordering is important. -op2_mul_mul = "*" -op2_mul_div = "/" -op2_mul_mod = "%" - -# Exponential operators. -op2_exp = op2_exp_pow -op2_exp_pow = "**" - -# The most tightly bound binary operator - attribute reference. -op2_attrib = "." - -# ===================================================== Literals ===================================================== - -literal = literal_set # Ordering is important to avoid ambiguities. - / literal_real - / literal_integer - / literal_string - / literal_boolean - -# Set. -literal_set = "{" _? expression_list _? "}" - -# Integer. -literal_integer = literal_integer_binary - / literal_integer_octal - / literal_integer_hexadecimal - / literal_integer_decimal -literal_integer_binary = ~r"0[bB](_?(0|1))+" -literal_integer_octal = ~r"0[oO](_?[0-7])+" -literal_integer_hexadecimal = ~r"0[xX](_?[0-9a-fA-F])+" -literal_integer_decimal = ~r"(0(_?0)*)+|([1-9](_?[0-9])*)" - -# Real. Exponent notation is defined first to avoid ambiguities. -literal_real = literal_real_exponent_notation - / literal_real_point_notation -literal_real_exponent_notation = (literal_real_point_notation / literal_real_digits) literal_real_exponent -literal_real_point_notation = (literal_real_digits? literal_real_fraction) / (literal_real_digits ".") -literal_real_fraction = "." literal_real_digits -literal_real_exponent = ~r"[eE][+-]?" literal_real_digits -literal_real_digits = ~r"[0-9](_?[0-9])*" - -# String. -literal_string = literal_string_single_quoted - / literal_string_double_quoted -literal_string_single_quoted = ~r"'[^'\\]*(\\[^\r\n][^'\\]*)*'" -literal_string_double_quoted = ~r'"[^"\\]*(\\[^\r\n][^"\\]*)*"' - -# Boolean. -literal_boolean = literal_boolean_true - / literal_boolean_false -literal_boolean_true = "true" -literal_boolean_false = "false" diff --git a/specification/dsdl/grammar.tex b/specification/dsdl/grammar.tex deleted file mode 100644 index ebf497cb..00000000 --- a/specification/dsdl/grammar.tex +++ /dev/null @@ -1,271 +0,0 @@ -\section{Grammar}\label{sec:dsdl_grammar} - -This section contains the formal definition of the DSDL grammar. -Its notation is introduced beforehand. -The meaning of each element of the grammar and their semantics will be explained in the following sections. - -\subsection{Notation} - -The following definition relies on the PEG\footnote{Parsing expression grammar.} -notation described in table~\ref{table:dsdl_grammar_definition_notation}% -\footnote{% - Inspired by Parsimonious -- an MIT-licensed software product authored by Erik Rose; - its sources are available at \url{https://github.com/erikrose/parsimonious}. -}. -The text of the formal definition contains comments which begin with an octothorp and last until the end of the line. - -\begin{CyphalSimpleTable}{Notation used in the formal grammar definition}{|l X|} - \label{table:dsdl_grammar_definition_notation} - Pattern & Description \\ - - \texttt{"text"} & - Denotes a terminal string of ASCII characters. - The string is case-sensitive. \\ - - \emph{(space)} & - Concatenation. - E.g., \texttt{korovan paukan excavator} matches a sequence where the specified tokens - appear in the defined order. \\ - - \texttt{abc / ijk / xyz} & - Alternatives. - The leftmost matching alternative is accepted. \\ - - \texttt{abc?} & - Optional greedy match. \\ - - \texttt{abc*} & - Zero or more expressions, greedy match. \\ - - \texttt{abc+} & - One or more expressions, greedy match. \\ - - \texttt{\textasciitilde{}r"regex"} & - An IEEE POSIX Extended Regular Expression pattern defined between the double quotes. - The expression operates on the ASCII character set and is always case-sensitive. - ASCII escape sequences ``\texttt{\textbackslash{}r}'', ``\texttt{\textbackslash{}n}'', and - ``\texttt{\textbackslash{}t}'' are used to denote ASCII carriage return (code 13), - line feed (code 10), and tabulation (code 9) characters, respectively. \\ - - \texttt{\textasciitilde{}r'regex'} & - As above, with single quotes instead of double quotes. \\ - - \texttt{(abc xyz)} & - Parentheses are used for grouping. \\ -\end{CyphalSimpleTable} - -\subsection{Definition} - -At the top level, a DSDL definition file is an ordered collection of statements; -the order is determined by the relative placement of statements inside the DSDL source file: -statements located closer the beginning of the file precede those that are located closer to the end of the file. - -From the top level down to the expression rule, the grammar is a valid regular grammar, -meaning that it can be parsed using standard regular expressions. - -The grammar definition provided here assumes lexerless parsing; -that is, it applies directly to the unprocessed source text of the definition. - -All characters used in the definition belong to the ASCII character set. - -\clearpage\inputminted[fontsize=\scriptsize]{python}{dsdl/grammar.parsimonious} - -\subsection{Expressions} - -Symbols representing operators belong to the ASCII (basic Latin) character set. - -Operators of the same precedence level are evaluated from left to right. - -The attribute reference operator is a special case: it is defined for an instance of any type -on its left side and an attribute identifier on its right side. -The concept of ``attribute identifier'' is not otherwise manifested in the type system. -The attribute reference operator is not explicitly documented for any data type; -instead, the documentation specifies the set of available attributes for instances of said type, -if there are any. - -\begin{CyphalSimpleTable}{Unary operators}{|l l X|} - Symbol & Precedence & Description \\ - \texttt{\textbf{+}} & 3 & Unary plus \\ - \texttt{\textbf{-}} (hyphen-minus) & 3 & Unary minus \\ - \texttt{\textbf{!}} & 8 & Logical not \\ -\end{CyphalSimpleTable} - -\begin{CyphalSimpleTable}{Binary operators}{|l l X|} - Symbol & Precedence & Description \\ - \texttt{\textbf{.}} (full stop) & 1 & Attribute reference - (parent object on the left side, - attribute identifier on the right side) \\ - - \texttt{\textbf{**}} & 2 & Exponentiation - (base on the left side, power on the right side) \\ - - \texttt{\textbf{*}} & 4 & Multiplication \\ - \texttt{\textbf{/}} & 4 & Division \\ - \texttt{\textbf{\%}} & 4 & Modulo \\ - - \texttt{\textbf{+}} & 5 & Addition \\ - \texttt{\textbf{-}} (hyphen-minus) & 5 & Subtraction \\ - - \texttt{\textbf{|}} (vertical line) & 6 & Bitwise or \\ - \texttt{\textbf{\textasciicircum{}}} (circumflex accent) & 6 & Bitwise xor \\ - \texttt{\textbf{\&}} & 6 & Bitwise and \\ - - \texttt{\textbf{==}} (dual equals sign) & 7 & Equality \\ - \texttt{\textbf{!=}} & 7 & Inequality \\ - \texttt{\textbf{<=}} & 7 & Less or equal \\ - \texttt{\textbf{>=}} & 7 & Greater or equal \\ - \texttt{\textbf{<}} & 7 & Less \\ - \texttt{\textbf{>}} & 7 & Greater \\ - - \texttt{\textbf{||}} (dual vertical line) & 9 & Logical or \\ - \texttt{\textbf{\&\&}} & 9 & Logical and \\ -\end{CyphalSimpleTable} - -\subsection{Literals} - -Upon its evaluation, a literal yields an object of a particular type depending on the syntax of the literal, -as specified in this section. - -\subsubsection{Boolean literals} - -A boolean literal is denoted by the keyword ``\verb|true|'' or ``\verb|false|'' -represented by an instance of primitive type ``\verb|bool|'' (section~\ref{sec:dsdl_primitive_types}) -with an appropriate value. - -\subsubsection{Numeric literals} - -Integer and real literals are represented as instances of type ``\verb|rational|'' (section~\ref{sec:dsdl_rational}). - -The digit separator character ``\verb|_|'' (underscore) does not affect the interpretation of numeric literals. - -The significand of a real literal is formed by the integer part, the optional decimal point, -and the optional fraction part; -either the integer part or the fraction part (not both) can be omitted. -The exponent is optionally specified after the letter ``\verb|e|'' or ``\verb|E|''; -it indicates the power of 10 by which the significand is to be scaled. -Either the decimal point or the letter ``\verb|e|''/``\verb|E|'' with the following exponent -(not both) can be omitted from a real literal. - -\begin{remark} - An integer literal \verb|0x123| is represented internally as $\frac{291}{1}$. - - A real literal \verb|.3141592653589793e+1| is represented internally as - $\frac{3141592653589793}{1000000000000000}$. -\end{remark} - -\subsubsection{String literals} - -String literals are represented as instances of type ``\verb|string|'' (section~\ref{sec:dsdl_string}). - -A string literal is allowed to contain an arbitrary sequence of Unicode characters, -excepting escape sequences defined in table~\ref{table:dsdl_string_literal_escape} -which shall follow one of the specified therein forms. -An escape sequence begins with the ASCII backslash character ``\verb|\|''. - -\begin{CyphalSimpleTable}{String literal escape sequences}{|l X|} - Sequence & Interpretation - \label{table:dsdl_string_literal_escape} \\ - - \texttt{\textbackslash{}\textbackslash{}} & Backslash, ASCII code 92. Same as the escape character. \\ - \texttt{\textbackslash{}r} & Carriage return, ASCII code 13. \\ - \texttt{\textbackslash{}n} & Line feed, ASCII code 10. \\ - \texttt{\textbackslash{}t} & Horizontal tabulation, ASCII code 9. \\ - - \texttt{\textbackslash{}\textquotesingle{}} & - Apostrophe (single quote), ASCII code 39. Regardless of the type of quotes around the literal. \\ - - \texttt{\textbackslash{}\textquotedbl{}} & - Quotation mark (double quote), ASCII code 34. Regardless of the type of quotes around the literal. \\ - - \texttt{\textbackslash{}u????} & - Unicode symbol with the code point specified by a four-digit hexadecimal number. - The placeholder ``\texttt{?}'' represents a hexadecimal character \texttt{[0-9a-fA-F]}. \\ - - \texttt{\textbackslash{}U????????} & - Like above, the code point is specified by an eight-digit hexadecimal number. \\ - -\end{CyphalSimpleTable} - -\begin{remark} - \begin{minted}{python} - @assert "oh,\u0020hi\U0000000aMark" == 'oh, hi\nMark' - \end{minted} -\end{remark} - -\subsubsection{Set literals} - -Set literals are represented as instances of type ``\verb|set|'' (section~\ref{sec:dsdl_set}) -parameterized by the type of the contained elements which is determined automatically. - -A set literal declaration shall specify at least one element, -which is used to determine the element type of the set. - -The elements of a set literal are defined as DSDL expressions which are evaluated before a set is constructed -from the corresponding literal. - -\begin{remark} - \begin{minted}{python} - @assert {"cells", 'interlinked'} == {"inter" + "linked", 'cells'} - \end{minted} -\end{remark} - -\subsection{Reserved identifiers}\label{sec:dsdl_reserved_identifiers} - -DSDL identifiers and data type name components that match any of the -case-insensitive patterns specified in table~\ref{table:dsdl_reserved_word_patterns} -cannot be used to name new entities. -The semantics of such identifiers is predefined by the DSDL specification, -and as such, they cannot be used for other purposes. -Some of the reserved identifiers do not have any functions associated with them -in this version of the DSDL specification, but this may change in the future. - -\begin{CyphalSimpleTable}{Reserved identifier patterns (POSIX ERE notation, ASCII character set, case-insensitive)}% - {|l l X|}% - \label{table:dsdl_reserved_word_patterns}% - POSIX ERE ASCII pattern & Example & Special meaning \\ - \texttt{truncated} & & Cast mode specifier \\ - \texttt{saturated} & & Cast mode specifier \\ - \texttt{true} & & Boolean literal \\ - \texttt{false} & & Boolean literal \\ - \texttt{bool} & & Primitive type \\ - \texttt{utf8} & & Primitive type \\ - \texttt{byte} & & Primitive type \\ - \texttt{u?int\textbackslash{}d*} & \texttt{uint8} & Primitive type category \\ - \texttt{float\textbackslash{}d*} & \texttt{float} & Primitive type category \\ - \texttt{u?q\textbackslash{}d+\_\textbackslash{}d+} & \texttt{q16\_8} & Primitive type category (future) \\ - \texttt{void\textbackslash{}d*} & \texttt{void} & Void type category \\ - \texttt{optional} & & Reserved for future use \\ - \texttt{aligned} & & Reserved for future use \\ - \texttt{const} & & Reserved for future use \\ - \texttt{struct} & & Reserved for future use \\ - \texttt{super} & & Reserved for future use \\ - \texttt{template} & & Reserved for future use \\ - \texttt{enum} & & Reserved for future use \\ - \texttt{self} & & Reserved for future use \\ - \texttt{and} & & Reserved for future use \\ - \texttt{or} & & Reserved for future use \\ - \texttt{not} & & Reserved for future use \\ - \texttt{auto} & & Reserved for future use \\ - \texttt{type} & & Reserved for future use \\ - \texttt{con} & & Compatibility with Microsoft Windows \\ - \texttt{prn} & & Compatibility with Microsoft Windows \\ - \texttt{aux} & & Compatibility with Microsoft Windows \\ - \texttt{nul} & & Compatibility with Microsoft Windows \\ - \texttt{com\textbackslash{}d} & \texttt{com1} & Compatibility with Microsoft Windows \\ - \texttt{lpt\textbackslash{}d} & \texttt{lpt9} & Compatibility with Microsoft Windows \\ - \texttt{\_.*\_} & \texttt{\_offset\_}& Special-purpose intrinsic entities \\ -\end{CyphalSimpleTable} - -\subsection{Reserved comment forms}\label{sec:dsdl_reserved_comment} - -Line comments that match the following regular expression are reserved for vendor-specific language extensions: -\verb|^\s*#\[.+\]\s*$| - -\begin{remark} - \begin{minted}{python} - # The following line matches the reserved form: - #[canadensis(enum)] - # After the newline this comment is now a regular DSDL comment. - #[canadensis(enum)] This is not reserved because it contains extra text after the bracket - \end{minted} -\end{remark} diff --git a/specification/dsdl/serializable_types.tex b/specification/dsdl/serializable_types.tex deleted file mode 100644 index 1b93bd16..00000000 --- a/specification/dsdl/serializable_types.tex +++ /dev/null @@ -1,695 +0,0 @@ -\section{Serializable types}\label{sec:dsdl_serializable_types} - -\subsection{General principles} - -Values of the serializable type category can be exchanged between nodes over the Cyphal network. -This is opposed to the expression types (section~\ref{sec:dsdl_expression_types}), -instances of which can only exist while DSDL definitions are being evaluated. -The data serialization rules are defined in section~\ref{sec:dsdl_data_serialization}. - -\subsubsection{Alignment and padding}\label{sec:dsdl_serializable_alignment_padding} - -For any serializable type, -its \emph{alignment} $A$ is defined as some positive integer number of bits such that the offset of a -serialized representation of an instance of this type relative to the origin of the -containing serialized representation (if any) is an integer multiple of $A$. - -Given an instance of type whose alignment is $A$, -it is guaranteed that its serialized representation is always an integer multiple of $A$ bits long. - -When constructing a serialized representation, -the alignment and length requirements are satisfied by means of \emph{padding}, -which refers to the extension of a bit sequence with zero bits until -the resulting alignment or length requirements are satisfied. -During deserialization, the padding bits are ignored (skipped) irrespective of their value -(also see related section \ref{sec:dsdl_serialization_implicit_truncation}). - -\begin{remark} - For example, given a variable-length entity whose length varies between 1 and 3 bits, - followed by a field whose type has the alignment requirement of 8, - one may end up with 5, 6, or 7 bits of padding inserted before the second field at runtime. - - The exact amount of such padding cannot always be determined statically because it depends on the size of the - preceding entity; - however, it is guaranteed that it is always strictly less than the alignment requirement of the following field - or, if this is the last field in a group, the alignment requirement of its container. -\end{remark} - -\subsection{Void types} - -Void types are used for padding purposes. -The alignment of void types is 1 bit (i.e., no alignment). - -Void-typed field attributes are set to zero when an object is serialized and ignored when it is deserialized. -Void types can be used to reserve space in data type definitions for possible use in later versions of the data type. - -The DSDL name pattern for void types is as follows: ``\verb|void[1-9]\d*|'', -where the trailing integer represents its width, in bits, -ranging from 1 to 64, inclusive. - -Void types can be referred to directly by their name from any namespace. - -\subsection{Primitive types}\label{sec:dsdl_primitive_types} - -Primitive types are assumed to be known to DSDL processing tools a priori, -and as such, they need not be defined by the user. -Primitive types can be referred to directly by their name from any namespace. - -The alignment of primitive types is 1 bit (i.e., no alignment). - -\subsubsection{Hierarchy} - -The hierarchy of primitive types is documented below. - -\begin{itemize} - \item \textbf{Boolean types.} A boolean-typed value represents a variable of the Boolean algebra. - A Boolean-typed value can have two values: true and false. - The corresponding DSDL data type name is ``\verb|bool|''. - - \item \textbf{Algebraic types.} Those are types for which conventional algebraic operators are defined. - \begin{itemize} - \item \textbf{Integer types} are used to represent signed and unsigned integer values. - See table~\ref{table:dsd_integer_properties}. - \begin{itemize} - \item \textbf{Signed integer types.} These are used to represent values which can be negative. - The corresponding DSDL data type name pattern is ``\verb|int[1-9]\d*|'', - where the trailing integer represents the length of the - serialized representation of the value, in bits, ranging from 2 to 64, inclusive. - - \item \textbf{Unsigned integer types.} These are used to represent non-negative values. - The corresponding DSDL data type name pattern is ``\verb|uint[1-9]\d*|'', - where the trailing integer represents the length of the - serialized representation of the value, in bits, ranging from 1 to 64, inclusive. - \begin{itemize} - \item \textbf{UTF-8 octet.} This type is used as an element type of variable-length - arrays (section \ref{sec:dsdl_array_types}) containing UTF-8 encoded strings. - The DSDL name is ``\verb|utf8|''. - The only valid use of this type is as an element type of a variable-length array. - - \item \textbf{Byte.} This type is used as an element type of fixed-length or variable-length - arrays (section \ref{sec:dsdl_array_types}) containing an arbitrary sequence of bytes. - The DSDL name is ``\verb|byte|''. - The only valid use of this type is as an element type of an array. - \end{itemize} - \end{itemize} - - \item \textbf{Floating point types} are used to approximately represent real values. - The underlying serialized representation follows the IEEE 754 standard. - The corresponding DSDL data type name pattern is ``\verb~float(16|32|64)~'', where the trailing - integer represents the type of the IEEE 754 representation. - See table~\ref{table:dsd_floating_point_properties}. - \end{itemize} -\end{itemize} - -\begin{CyphalSimpleTable}{Properties of integer types}{|l X l|}% - \label{table:dsd_integer_properties}% - Category & - DSDL names & - Range, $X$ is bit length \\ - - Signed integers & - \texttt{int2}, \texttt{int3}, \texttt{int4} \ldots{} \texttt{int62}, \texttt{int63}, \texttt{int64} & - $\left[-\frac{2^{X}}{2},\frac{2^{X}}{2}-1\right]$ \\ - - Unsigned integers & - \texttt{uint1}, \texttt{uint2}, \texttt{uint3} \ldots{} \texttt{uint62}, \texttt{uint63}, \texttt{uint64} & - $\left[0,2^{X}-1\right]$ \\ -\end{CyphalSimpleTable} - -\begin{CyphalSimpleTable}{Properties of floating point types}{|X X X X|} - DSDL name & Representation & Approximate epsilon & Approximate range - \label{table:dsd_floating_point_properties} \\ - - \texttt{float16} & IEEE 754 binary16 & $0.001$ & $\pm{}65504$ \\ - \texttt{float32} & IEEE 754 binary32 & $10^{-7}$ & $\pm{}10^{39}$ \\ - \texttt{float64} & IEEE 754 binary64 & $2 \times{} 10^{-16}$ & $\pm{}10^{308}$ \\ -\end{CyphalSimpleTable} - -\subsubsection{Cast mode} - -The concept of \emph{cast mode} is defined for all primitive types. -The cast mode defines the behavior when a primitive-typed entity is assigned a value that exceeds its range. -Such assignment requires some of the information to be discarded; -due to the loss of information involved, it is called a \emph{lossy assignment}. - -The following cast modes are defined: -\begin{description} - \item[Truncated mode] --- denoted with the keyword ``\verb|truncated|'' placed before the primitive type name. - \item[Saturated mode] --- denoted with the optional keyword ``\verb|saturated|'' - placed before the primitive type name. If neither cast mode is specified, saturated mode is assumed by default. - This essentially makes the ``\verb|saturated|'' keyword redundant; it is provided only for consistency. -\end{description} - -When a primitive-typed entity is assigned a value that exceeds its range, -the resulting value is chosen according to the lossy assignment rules -specified in table~\ref{table:dsdl_cast_mode}. -Cases that are marked as illegal are not permitted in DSDL definitions. - -\begin{CyphalSimpleTable}[wide]{Lossy assignment rules per cast mode}{|l X X|} - Type category & Truncated mode & Saturated mode (default) - \label{table:dsdl_cast_mode} \\ - - Signed integer & - Illegal: signed integer types with truncated cast mode are not allowed. & - Nearest reachable value. \\ - - Unsigned integer & - Most significant bits are discarded. & - Nearest reachable value. \\ - - Floating point & - Infinity with the same sign, unless the original value is not-a-number, in which case it will be preserved. & - If the original value is finite, the nearest finite value will be used. - Otherwise, in the case of infinity or not-a-number, the original value will be preserved. \\ -\end{CyphalSimpleTable} - -Rules of conversion between values of different type categories do not affect compatibility at the protocol level, -and as such, they are to be implementation-defined. - -\subsubsection{Expressions} - -At the time of DSDL definition processing, -values of primitive types are represented as instances of the \verb|rational| type (section~\ref{sec:dsdl_rational}), -with the exception of the type \verb|bool|, -instances of which are usable in DSDL expressions as-is. - -\begin{CyphalSimpleTable}{Operators defined on instances of type boolean}{|l X X|} - Op & Type & Description \\ - - \texttt{\textbf{!}} & $(\texttt{bool}) \rightarrow \texttt{bool}$ & Logical not. \\ - - \texttt{\textbf{||}} & $(\texttt{bool}, \texttt{bool}) \rightarrow \texttt{bool}$ & Logical or. \\ - \texttt{\textbf{\&\&}} & $(\texttt{bool}, \texttt{bool}) \rightarrow \texttt{bool}$ & Logical and. \\ - - \texttt{\textbf{==}} & $(\texttt{bool}, \texttt{bool}) \rightarrow \texttt{bool}$ & Equality. \\ - \texttt{\textbf{!=}} & $(\texttt{bool}, \texttt{bool}) \rightarrow \texttt{bool}$ & Inequality. \\ - -\end{CyphalSimpleTable} - -\subsubsection{Reference list} - -An exhaustive list of all void and primitive types -ordered by bit length is provided below for reference. -Note that the cast mode specifier is omitted intentionally. - -\immediate\write18{rm -f ../latex.tmp} -\immediate\write18{../render_list_of_void_and_primitive_types.py > ../latex.tmp} -\immediate\input{../latex.tmp} - -\subsection{Array types}\label{sec:dsdl_array_types} - -An array type represents an ordered collection of values. -All values in the collection share the same type, which is referred to as \emph{array element type}. -The array element type can be any type except: -\begin{itemize} - \item void type; - \item array type\footnote{% - Meaning that nested arrays are not allowed; - however, the array element type can be a composite type which in turn may contain arrays. - In other words, indirect nesting of arrays is permitted. - }. -\end{itemize} - -The number of elements in the array can be specified as a constant expression at the data type definition time, -in which case the array is said to be a \emph{fixed-length array}. -Alternatively, the number of elements can vary between zero and some static limit specified -at the data type definition time, -in which case the array is said to be a \emph{variable-length array}. -Variable-length arrays with unbounded maximum number of elements are not allowed. - -Arrays are defined by adding a pair of square brackets after the array element type specification, -where the brackets contain the \emph{array capacity expression}. -The array capacity expression shall yield a positive integer of type ``\verb|rational|'' upon its evaluation; -any other value or type renders the current DSDL definition invalid. - -The array capacity expression can be prefixed with the following character sequences in order to define -a variable-length array: -\begin{itemize} - \item ``\verb|<|'' (ASCII code 60) --- indicates that the integer value yielded by the array capacity expression - specifies the non-inclusive upper boundary of the number of elements. - In this case, the integer value yielded by the array capacity expression shall be greater than one. - - \item ``\verb|<=|'' (ASCII code 60 followed by 61) --- same as above, but the upper boundary is inclusive. -\end{itemize} -If neither of the above prefixes are provided, the resultant definition is that of a fixed-length array. - -The alignment of an array equals the alignment of its element type\footnote{ - E.g., the alignment of \texttt{uint5[<=3]} or \texttt{int64[3]} is 1 bit (that is, no alignment). -}. - -\subsection{Composite types}\label{sec:dsdl_composite_types} - -\subsubsection{Kinds} - -There are two kinds of composite type definitions: message types and service types. -All versions of a data type shall be of the same kind\footnote{% - For example, if a data type version 0.1 is of a message kind, all later versions of it shall be messages, too. -}. - -A service type defines two inner data types: -one for service request object, and one for service response object, in that order. -The two types are separated by the service response marker (``\verb|---|'') on a separate line. - -The presence of the service response marker indicates that the data type definition at hand is of the service kind. -At most one service response marker shall appear in a given definition. - -\subsubsection{Dependencies} - -In order to refer to a composite type from another composite type definition -(e.g., for nesting or for referring to an external constant), -one has to specify the full data type name of the referred data type followed by its -major and minor version numbers separated by the namespace separator character, -as demonstrated on figure~\ref{fig:dsdl_nested_reference}. - -To facilitate look-up of external dependencies, -implementations are expected to obtain from external sources\footnote{% - For example, from user-provided configuration options. -} the list of directories that are the roots of namespaces containing the referred dependencies. - -\begin{figure}[H] - $$ - \underbrace{\texttt{\huge{uavcan.node.Heartbeat}}}_{\text{full data type name}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{1.0}}}_{\substack{\text{version} \\ \text{numbers}}}% - $$ - \caption{Reference to an external composite data type definition\label{fig:dsdl_nested_reference}} -\end{figure} - -If the referred data type and the referring data type share the same full namespace name, -it is allowed to omit the namespace from the referred data type specification -leaving only the short data type name, as demonstrated on figure~\ref{fig:dsdl_nested_reference_short}. -In this case, the referred data type will be looked for in the namespace of the referrer. -Partial omission of namespace components is not permitted. - -\begin{figure}[H] - $$ - \underbrace{\texttt{\huge{Heartbeat}}}_{\text{short data type name}}% - \texttt{\huge{.}}% - \underbrace{\texttt{\huge{1.0}}}_{\substack{\text{version} \\ \text{numbers}}}% - $$ - \caption{Reference to an external composite data type definition located in the same namespace - \label{fig:dsdl_nested_reference_short}} -\end{figure} - -Circular dependencies are not permitted. -A circular dependency renders all of the definitions involved in the dependency loop invalid. - -If any of the referred definitions are marked as deprecated, -the referring definition shall be marked deprecated as well\footnote{% - Deprecation is indicated with the help of a special directive, as explained in section~\ref{sec:dsdl_directives}. -}. -If a non-deprecated definition refers to a deprecated definition, -the referring definition is malformed\footnote{% - This tainting behavior is designed to prevent unexpected breakage of - type hierarchies when one of the deprecated dependencies reaches its end of life. -}. - -When a data type is referred to from within an expression context, -it constitutes a literal of type ``\verb|metaserializable|'' (section~\ref{sec:dsdl_metaserializable}). -If the referred data type is of the message kind, -its attributes are accessible in the referring expression through application of the -attribute reference operator ``\verb|.|''. -The available attributes and their semantics are documented in the section~\ref{sec:dsdl_local_attributes}. - -\begin{remark} - \begin{minted}{python} - uint64 MY_CONSTANT = vendor.MessageType.1.0.OTHER_CONSTANT - uint64 MY_CONSTANT = MessageType.1.0.OTHER_CONSTANT - # The above is valid if the referring definition and the referred definition - # are located inside the root namespace "vendor". - @print MessageType.1.0 - \end{minted} -\end{remark} - -\subsubsection{Tagged unions}\label{sec:dsdl_composite_tagged_unions} - -Any data type definition can be supplied with a special directive (section~\ref{sec:dsdl_directives}) -indicating that it forms a tagged union. - -A tagged union type shall contain at least two field attributes. -A tagged union shall not contain padding field attributes. - -The value of a tagged union object is a function of the field attribute which value it is currently holding -and the value of the field attribute itself. - -To avoid ambiguity, a data type that is not a tagged union is referred to as a \emph{structure}. - -\subsubsection{Alignment and cumulative bit length set}\label{sec:dsdl_composite_alignment_cumulative_bls} - -The alignment of composite types is one byte (8 bits)\footnote{% - Regardless of the content. - It follows that empty composites can be inserted arbitrarily to force byte alignment - of the next field(s) at runtime. -}. - -Per the definitions given in \ref{sec:dsdl_serializable_alignment_padding}, -a serialized representation of a composite type is padded to 8 bits by inserting padding bits -after its last element until the resulting length is a multiple of 8 bits. - -Given a set of field attributes, their \emph{cumulative bit length set} is computed -by evaluating every permutation of their respective bit length sets plus the required padding. -\begin{itemize} - \item For tagged unions, this amounts to the union of the bit length sets of each field - plus the bit length set of the implicit union tag. - \item Otherwise, the cumulative bit length set is the Cartesian product of the bit length sets of each field - plus the required inter-field padding. -\end{itemize} -Related specifics are given in section \ref{sec:dsdl_data_serialization} on data serialization. - -\subsubsection{Extent and sealing}\label{sec:dsdl_composite_extent_and_sealing} - -As detailed in section \ref{sec:dsdl_versioning}, -data types may evolve over time to accommodate new design requirements, new features, to rectify issues, etc. -In order to allow gradual migration of deployed systems to revised data types, -it is desirable to ensure that they can be modified in a way that does not render new definitions -incompatible with their earlier versions. -In this context there are two related concepts: - -\begin{description} - \item[Extent] --- the minimum amount of memory, in bits, that shall be allocated to store the serialized - representation of the type. - The extent of any type is always greater than or equal the maximal value of its bit length set. - It is always a multiple of 8. - - \item[Sealing] --- a type that is \emph{sealed} is non-evolvable and its extent equals the maximal value - of its bit length set\footnote{% - I.e., the smallest possible extent. - }. - A type that is not sealed is also referred to as \emph{delimited}. -\end{description} - -The extent is the growth limit for the maximal bit length of the type as it evolves. -The extent should be at least as large as the longest serialized representation of any compatible version of the type, -which ensures that an agent leveraging a particular version can correctly process any other compatible -version due to the avoidance of unintended truncation of serialized representations. - -Serialized representations of evolvable definitions may carry additional metadata which introduces a certain overhead. -This may be undesirable in some scenarios, especially in cases where serialized representations of the -definition are expected to be highly compact, thereby making the overhead comparatively more significant. -In such cases, the designer may opt out of the extensibility by declaring the definition sealed. -Serialized representations of sealed definitions do not incur the aforementioned overhead. - -The related mechanics are described in section \ref{sec:dsdl_serialization_composite_non_sealed}. - -\begin{figure}[H] - $$ - \overbrace{% - \underbrace{% - \blacksquare\blacksquare\blacksquare\blacksquare\blacksquare\blacksquare% - \blacksquare\blacksquare\blacksquare\blacksquare\blacksquare\blacksquare% - }_{\substack{\text{Longest serialized} \\ \text{representation}}}% - \underbrace{% - \boxtimes\boxtimes\boxtimes\boxtimes\boxtimes\boxtimes\boxtimes\boxtimes% - }_{\substack{\text{Memory reserve} \\ \text{(none if sealed)}}}% - }^{\substack{% - \text{Extent} \\ - \text{(memory requirement)} - }} - $$ - \caption{Serialized representation and extent\label{fig:dsdl_extent}} -\end{figure} - -\begin{remark} - Because of Cyphal's commitment to determinism, memory buffer allocation can become an issue. - When using a flat composite type (where each field is of a primitive type) with the implicit truncation rule, - it is clear that the last defined fields are to be truncated out shall the allocated buffer be too small - to accommodate the serialized representation in its entirety. - If there is a composite-typed field, this behavior can no longer be relied on. - The technical details explaining this are given in section \ref{sec:dsdl_serialization_composite_non_sealed}. - - Conventional protocols manage this simply by delaying the memory requirement identification until runtime, - which is unacceptable to Cyphal. - The solution for Cyphal is to allow the data type author to require implementations to reserve more memory - than required by the data type definition unless it is \verb|@sealed| - (or unless the implementation does use dynamic memory allocation). -\end{remark} - -The extent shall be set explicitly using the directive described in section \ref{sec:dsdl_directive_extent}, -unless the definition is declared sealed using the directive described in section \ref{sec:dsdl_directive_sealed}. -The directives are mutually exclusive. - -It is allowed for a sealed composite type to nest non-sealed (delimited) composite types, and vice versa. - -\subsubsection{Bit length set} - -The bit length set of a sealed composite type equals the cumulative bit length set -of its fields plus the final padding (see section \ref{sec:dsdl_composite_alignment_cumulative_bls}). - -\begin{remark} - The bit length set of the following is $\left\{ 8, 24, 40, 56 \right\}$: - \begin{minted}{python} - uint16[<=3] foo - @sealed - \end{minted} - - The bit length set of the following is $\left\{ 16, 32, 48, 64 \right\}$: - \begin{minted}{python} - uint16[<=3] foo - int2 bar - @sealed - \end{minted} - - The bit length set of the following is $\left\{ 8, 16 \right\}$: - \begin{minted}{python} - bool[<=3] foo - @sealed - \end{minted} -\end{remark} - -The bit length set of a non-sealed (delimited) composite type is dependent only on its extent $X$, -and is defined as follows: -$$ - \left\{ B_\text{DH} + 8b \mid b \in \mathbb{Z},\ 0 \leq b \leq \frac{X}{8} \right\} -$$ -where $B_\text{DH}$ is the bit length of the \emph{delimiter header} -as defined in section \ref{sec:dsdl_serialization_composite_non_sealed}. - -\begin{remark} - This is intentionally not dependent on the fields of the composite because the definition of delimited - composites should be possible to change without violating the backward compatibility. - - If the bit length set was dependent on the field composition, then a composite $A$ that nests another composite - $B$ could have made a fragile assumption about the offset of the fields that follow $B$ - that could be broken when $B$ is modified. Example: - - \begin{minted}{python} - # A.1.0 - B.1.0 x - float32 assume_aligned # B.1.0 contains a single uint64, assume this field is 32-bit aligned? - @sealed - \end{minted} - - \begin{minted}{python} - # B.1.0 - uint64 x - @extent 17 * 8 - \end{minted} - - Imagine then that \verb|B.1.0| is replaced with the following: - - \begin{minted}{python} - # B.1.1 - uint64 x - bool[<=64] y - @extent 17 * 8 - \end{minted} - - Once this modification is introduced, the fragile assumption about the alignment of - \verb|A.1.0.assume_aligned| would be violated. - To avoid this issue, the bit length set definition of delimited types intentionally discards the information - about its field composition, forcing dependent types to avoid any non-trivial assumptions. -\end{remark} - -When serializing an object, the amount of memory needed for storing its serialized representation -may be taken as the maximal value of its bit length set minus the size of the delimiter header, -since this bound is tighter than the extent yet guaranteed to be sufficient. -This optimization is not applicable to deserialization since the actual type of the object may not be known. - -\subsubsection{Type polymorphism and equivalency} - -Type polymorphism is supported in DSDL through structural subtyping. -The following definition relies on the concept of \emph{field attribute}, -which is introduced in section~\ref{sec:dsdl_attributes}. - -Polymorphic relations are not defined on service types. - -Let $B$ and $D$ be DSDL types that define $b$ and $d$ field attributes, respectively. -Let each field attribute be assigned a sequential index according to its position in the DSDL definition -(see section~\ref{sec:dsdl_grammar} on statement ordering). - -\begin{enumerate} - % ----------------------------------------------------------------------------------------------------------------- - \item Structure subtyping rule --- $D$ is a \emph{structural subtype} of $B$ if all conditions are satisfied: - \begin{itemize} - \item neither $B$ nor $D$ define a tagged union\footnote{% - This is because tagged unions are serialized differently. - }; - \item neither $B$ nor $D$ are sealed\footnote{% - Sealed types are serialized in-place as if their definition was directly copied into the outer - (containing) type (if any). - This circumstance effectively renders them non-modifiable because that may break the bit layout - of the outer types (if any). - More on this in section \ref{sec:dsdl_serialization_composite_non_sealed}. - }; - \item the extent of $B$ is not less than the extent of $D$\footnote{% - This is to uphold the Liskov substitution principle. - A deserializer expecting an instance of $B$ in a serialized representation should be invariant - to the replacement $B \leftarrow{} D$. - If the extent of $D$ was larger, then its serialized representation could spill beyond the allocated - container, possibly resulting in the truncation of the following data, which in turn could result in - incorrect deserialization. - See \ref{sec:dsdl_data_serialization}. - }; - \item $B$ is not $D$; - \item $b \leq d$; - \item for each field attribute of $B$ at index $i$ there is an equal\footnote{% - Field attribute equality is defined in section~\ref{sec:dsdl_attributes}. - } field attribute in $D$ at index $i$. - \end{itemize} - - % ----------------------------------------------------------------------------------------------------------------- - \item Tagged union subtyping rule --- $D$ is a structural subtype of $B$ if all conditions are satisfied: - \begin{itemize} - \item both $B$ and $D$ define tagged unions; - \item neither $B$ nor $D$ are sealed; - \item the extent of $B$ is not less than the extent of $D$; - \item $B$ is not $D$; - \item $b \leq d$; - \item $2^{\lceil\log_2 \text{max}\left(8, \lceil\log_2 b\rceil\right)\rceil} = - 2^{\lceil\log_2 \text{max}\left(8, \lceil\log_2 d\rceil\right)\rceil}$\footnote{% - I.e., the length of the implicit union tag field should be the same. - }; - \item for $i \in \left[0, b\right)$, the type of the field attribute of $D$ at index $i$ - is the same or is a subtype of the type of the field attribute of $B$ at index $i$. - \item for $i \in \left[0, b\right)$, the name of the field attribute of $D$ at index $i$ - is the same as the name of the field attribute of $B$ at index $i$. - \end{itemize} - - % ----------------------------------------------------------------------------------------------------------------- - \item Empty type subtyping rule --- $D$ is a structural subtype of $B$ if all conditions are satisfied: - \begin{itemize} - \item $b = 0$\footnote{% - If $B$ contains no field attributes, the applicability of the Liskov substitution principle is invariant to - whether its subtypes are tagged union types or not. - }; - \item neither $B$ nor $D$ are sealed; - \item the extent of $B$ is not less than the extent of $D$; - \item $B$ is not $D$. - \end{itemize} - - % ----------------------------------------------------------------------------------------------------------------- - \item Header subtyping rule --- $D$ is a structural subtype of $B$ if all conditions are satisfied: - \begin{itemize} - \item neither $B$ nor $D$ define a tagged union; - \item both $B$ and $D$ are sealed; - \item the first field attribute of $D$ is of type $B$. - \end{itemize} -\end{enumerate} - -If $D$ is a structural subtype of $B$, then $B$ is a \emph{structural supertype} of $D$. - -$D$ and $B$ are \emph{structurally equivalent} if $D$ is a structural subtype and a structural supertype of $B$. - -A \emph{type hierarchy} is an ordered set of data types such that for each pair of its members -one type is a subtype of another, and for any member its supertypes are located on the left. - -\begin{remark} - Subtyping example for structure (non-union) types. First type: - - \begin{minted}{python} - float64 a # Index 0 - int16[<=9] b # Index 1 - @extent 32 * 8 - \end{minted} - - The second type is a structural subtype of the first type: - - \begin{minted}{python} - float64 a # Index 0 - int16[<=9] b # Index 1 - uint8 foo # Index 2 - @extent 32 * 8 - \end{minted} - - Subtyping example for union types. First type: - - \begin{minted}{python} - @union # The implicit union tag field is 8 bits wide - uavcan.primitive.Empty.1.0 foo - float16 bar - uint8 zoo - @extent 128 * 8 - \end{minted} - - The second type is a structural subtype of the first type: - - \begin{minted}{python} - @union # The implicit union tag field is 8 bits wide - uavcan.diagnostic.Record.1.0 foo # Subtype - float16 bar # Same - uint8 zoo # Same - int64[<=64] baz # New field - @extent 128 * 8 - \end{minted} - - A structure type that defines zero fields is a structural supertype of any other structure type, - regardless of either or both being a union, provided that its extent is sufficient. - A structure type may have an arbitrary number of supertypes as long as the field equality constraint is satisfied. - - Header subtyping example. The first type is named \verb|A.1.0|: - - \begin{minted}{python} - float64 a - int16[<=9] b - @sealed - \end{minted} - - The second type is a structural subtype of the first type: - - \begin{minted}{python} - A.1.0 base - uint8 foo - @sealed - \end{minted} -\end{remark} - -\begin{remark}[breakable] - The following example in C demonstrates the concept of polymorphic compatibility detached from DSDL. - - \begin{samepage} - \begin{minted}{c} - struct base - { - int a; - float b; - }; - - struct derived_first - { - int a; - float b; - double c; - }; - - struct derived_second - { - int a; - float b; - short d; - }; - - float compute(struct base* value) - { - return (float)value->a + value->b; - } - - int main() - { - struct derived_first foo = { .a = 123, .b = -456.789F, .c = 123.456 }; - struct derived_second bar = { .a = -123, .b = 456.789F, .d = -123 }; - // Both derived_first and derived_second are structural subtypes of base. The program returns zero. - return compute(&foo) + compute(&bar); - } - \end{minted} - \end{samepage} -\end{remark} diff --git a/specification/dsdl/serialization.tex b/specification/dsdl/serialization.tex deleted file mode 100644 index abfee627..00000000 --- a/specification/dsdl/serialization.tex +++ /dev/null @@ -1,749 +0,0 @@ -\section{Data serialization}\label{sec:dsdl_data_serialization} - -\newcommand{\hugett}[1]{\texttt{\huge{#1}}} - -\subsection{General principles} - -\subsubsection{Design goals} - -The main design principle behind the serialized representations described in this section is -the maximization of compatibility with native representations used by currently existing and -likely future computer microarchitectures. -The goal is to ensure that the serialized representations defined by DSDL match internal data representations of -modern computers, so that, ideally, a typical system will not have to perform any data conversion whatsoever while -exchanging data over a Cyphal network. - -The implicit truncation and implicit zero extension rules introduced in this section are designed to -facilitate structural subtyping and to enable extensibility of data types while retaining backward compatibility. -This is a conscious trade-off between runtime type checking and long-term stability guarantees. -This model assumes that data type compatibility is determined statically and is not, normally, enforced at runtime. - -\subsubsection{Bit and byte ordering} - -The smallest atomic data entity is a bit. -Eight bits form one byte; -within the byte, the bits are ordered so that the least significant bit is considered first (0-th index), -and the most significant bit is considered last (7-th index). - -Numeric values consisting of multiple bytes are arranged so that the least significant byte is encoded first; -such format is also known as little-endian. - -\begin{figure}[H] - $$ - \overset{\text{bit index}}{% - \underbrace{% - \overset{\text{M}}{\overset{7}{\hugett{0}}} - \overset{6}{\hugett{1}} - \overset{5}{\hugett{0}} - \overset{4}{\hugett{1}} - \overset{3}{\hugett{0}} - \overset{2}{\hugett{1}} - \overset{1}{\hugett{0}} - \overset{\text{L}}{\overset{0}{\hugett{1}}} - }_\text{least significant byte}% - } - \hugett{\ldots} - \overset{\text{bit index}}{% - \underbrace{% - \overset{\text{M}}{\overset{7}{\hugett{0}}} - \overset{6}{\hugett{1}} - \overset{5}{\hugett{0}} - \overset{4}{\hugett{1}} - \overset{3}{\hugett{0}} - \overset{2}{\hugett{1}} - \overset{1}{\hugett{0}} - \overset{\text{L}}{\overset{0}{\hugett{1}}} - }_\text{most significant byte}% - } - $$ - \caption{Bit and byte ordering\label{fig:dsdl_serialization_bit_ordering}} -\end{figure} - -\subsubsection{Implicit truncation of excessive data}\label{sec:dsdl_serialization_implicit_truncation} - -When a serialized representation is deserialized, implementations shall ignore -any excessive (unused) data or padding bits remaining upon deserialization\footnote{% - The presence of unused data should not be considered an error. -}. -The total size of the serialized representation is reported either by the underlying transport layer, or, -in the case of nested objects, by the \emph{delimiter header} -(section \ref{sec:dsdl_serialization_composite_non_sealed}). - -As a consequence of the above requirement the transport layer can introduce -additional zero padding bits at the end of a serialized representation -to satisfy data size granularity constraints. -Non-zero padding bits are not allowed\footnote{% - Because padding bits may be misinterpreted as part of the serialized representation. -}. - -\begin{remark} - Because of implicit truncation a serialized representation constructed from an instance of type $B$ can be - deserialized into an instance of type $A$ as long as $B$ is a structural subtype of $A$. - - Let $x$ be an instance of data type $B$, which is defined as follows: - - \begin{minted}{python} - float32 parameter - float32 variance - \end{minted} - - Let $A$ be a structural supertype of $B$, being defined as follows: - - \begin{minted}{python} - float32 parameter - \end{minted} - - Then the serialized representation of $x$ can be deserialized into an instance of $A$. - The topic of data type compatibility is explored in detail in section~\ref{sec:dsdl_versioning}. -\end{remark} - -\subsubsection{Implicit zero extension of missing data}\label{sec:dsdl_serialization_implicit_zero_extension} - -For the purposes of deserialization routines, -the serialized representation of any instance of a data type shall \emph{implicitly} end with an -infinite sequence of bits with a value of zero (0).\footnote{% - This can be implemented by checking for out-of-bounds access during deserialization and returning zeros - if an out-of-bounds access is detected. This is where the name ``implicit zero extension rule'' is derived - from. -}. - -Despite this rule, implementations are not allowed to intentionally truncate trailing zeros -upon construction of a serialized representation of an object\footnote{% - Intentional truncation is prohibited because a future revision of the specification may remove the implicit zero - extension rule. - If intentional truncation were allowed, removal of this rule would break backward compatibility. -}. - -The total size of the serialized representation is reported either by the underlying transport layer, or, -in the case of nested objects, by the \emph{delimiter header} -(section \ref{sec:dsdl_serialization_composite_non_sealed}). - -\begin{remark} - The implicit zero extension rule enables extension of data types by introducing additional fields - without breaking backward compatibility with existing deployments. - The topic of data type compatibility is explored in detail in section~\ref{sec:dsdl_versioning}. - - The following example assumes that the reader is familiar with the variable-length array serialization rules, - explained in section~\ref{sec:dsdl_serialized_variable_length_array}. - - Let the data type $A$ be defined as follows: - - \begin{minted}{python} - uint8 scalar - \end{minted} - - Let $x$ be an instance of $A$, where the value of \verb|scalar| is 4. - Let the data type $B$ be defined as follows: - - \begin{minted}{python} - uint8[<256] array - \end{minted} - - Then the serialized representation of $x$ can be deserialized into an instance of $B$ where the field - \verb|array| contains a sequence of four zeros: $0, 0, 0, 0$. -\end{remark} - -\subsubsection{Error handling}\label{sec:dsdl_serialized_error} - -In this section and further, an object that nests other objects is referred to as an \emph{outer object} -in relation to the nested object. - -Correct Cyphal types shall have no serialization error states. - -A deserialization process may encounter a serialized representation that does not belong to the -set of serialized representations of the data type at hand. -In such case, the invalid serialized representation shall be discarded and the implementation -shall explicitly report its inability to complete the deserialization process for the given input. -Correct Cyphal types shall have no other deserialization error states. - -Failure to deserialize a nested object renders the outer object invalid\footnote{% - Therefore, failure in a single deeply nested object propagates upward, rendering the entire structure invalid. - The motivation for such behavior is that it is likely that if an inner object cannot be deserialized, - then the outer object is likely to be also invalid. -}. - -\subsection{Void types}\label{sec:dsdl_serialized_void} - -The serialized representation of a void-typed field attribute is constructed as a sequence of zero bits. -The length of the sequence equals the numeric suffix of the type name. - -When a void-typed field attribute is deserialized, the values of respective bits are ignored; -in other words, any bit sequence of correct length is a valid serialized representation -of a void-typed field attribute. -This behavior facilitates usage of void fields as placeholders for non-void fields -introduced in newer versions of the data type (section~\ref{sec:dsdl_versioning}). - -\begin{remark} - The following data type will be serialized as a sequence of three zero bits $000_2$: - \begin{minted}{python} - void3 - \end{minted} - The following bit sequences are valid serialized representations of the type: - $000_2$, - $001_2$, - $010_2$, - $011_2$, - $100_2$, - $101_2$, - $110_2$, - $111_2$. - - Shall the padding field be replaced with a non-void-typed field in a future version of the data type, - nodes utilizing the newer definition may be able to retain compatibility with nodes using older types, - since the specification guarantees that padding fields are always initialized with zeros: - - \begin{minted}{python} - # Version 1.1 - float64 a - void64 - \end{minted} - - \begin{minted}{python} - # Version 1.2 - float64 a - float32 b # Messages v1.1 will be interpreted such that b = 0.0 - void32 - \end{minted} -\end{remark} - -\subsection{Primitive types} - -\subsubsection{General principles} - -Implementations where native data formats are incompatible with those adopted by Cyphal shall perform -conversions between the native formats and the corresponding Cyphal formats during -serialization and deserialization. -Implementations shall avoid or minimize information loss and/or distortion caused by such conversions. - -Serialized representations of instances of the primitive type category that are longer than one byte (8 bits) -are constructed as follows. -First, only the least significant bytes that contain the used bits of the value are preserved; -the rest are discarded following the lossy assignment policy selected by the specified cast mode. -Then the bytes are arranged in the least-significant-byte-first order\footnote{Also known as ``little endian''.}. -If the bit width of the value is not an integer multiple of eight (8) then the next value in the type will begin -starting with the next bit in the current byte. If there are no further values then the remaining bits -shall be zero (0). - -\begin{remark} - The value $1110\,1101\,1010_2$ (3802 in base-10) of type \verb|uint12| is encoded as follows. - The bit sequence is shown in the base-2 system, where bytes (octets) are comma-separated: - $$ - \overset{\text{byte 0}}{% - \underbrace{% - \overset{7}{\hugett{1}} - \overset{6}{\hugett{1}} - \overset{5}{\hugett{0}} - \overset{4}{\hugett{1}} - \overset{3}{\hugett{1}} - \overset{2}{\hugett{0}} - \overset{1}{\hugett{1}} - \overset{0}{\hugett{0}} - }_{\substack{\text{Least significant 8} \\ \text{bits of }3802_{10}}}% - }% - \hugett{,}% - \overset{\text{byte 1}}{% - \underbrace{ - \overset{7}{\hugett{?}} - \overset{6}{\hugett{?}} - \overset{5}{\hugett{?}} - \overset{4}{\hugett{?}} - }_{\substack{\text{Next object} \\ \text{or zero} \\ \text{padding bits}}}% - \underbrace{ - \overset{3}{\hugett{1}} - \overset{2}{\hugett{1}} - \overset{1}{\hugett{1}} - \overset{0}{\hugett{0}} - }_{\substack{\text{Most} \\ \text{significant} \\ \text{4 bits of} \\ \text{3802}_{10}}}% - } - $$ -\end{remark} - -\subsubsection{Boolean types}\label{sec:dsdl_serialized_bool} - -The serialized representation of a value of type \verb|bool| is a single bit. -If the value represents falsity, the value of the bit is zero (0); otherwise, the value of the bit is one (1). - -\subsubsection{Unsigned integer types}\label{sec:dsdl_serialized_unsigned_integer} - -The serialized representation of an unsigned integer value of length $n$ bits -(which is reflected in the numerical suffix of the data type name) -is constructed as if the number were to be written in base-2 numerical system -with leading zeros preserved so that the total number of binary digits would equal $n$. - -\begin{remark} - The serialized representation of integer 42 of type \verb|uint7| is $0101010_2$. -\end{remark} - -\subsubsection{Signed integer types} - -The serialized representation of a non-negative value of a signed integer type is constructed as described -in section~\ref{sec:dsdl_serialized_unsigned_integer}. - -The serialized representation of a negative value of a signed integer type is computed by -applying the following transformation: -$$2^n + x$$ -where $n$ is the bit length of the serialized representation -(which is reflected in the numerical suffix of the data type name) -and $x$ is the value whose serialized representation is being constructed. -The result of the transformation is a positive number, -whose serialized representation is then constructed as described in section~\ref{sec:dsdl_serialized_unsigned_integer}. - -The representation described here is widely known as \emph{two's complement}. - -\begin{remark} - The serialized representation of integer -42 of type \verb|int7| is $1010110_2$. -\end{remark} - -\subsubsection{Floating point types} - -The serialized representation of floating point types follows the IEEE 754 series of standards as follows: - -\begin{itemize} - \item \verb|float16| --- IEEE 754 binary16; - \item \verb|float32| --- IEEE 754 binary32; - \item \verb|float64| --- IEEE 754 binary64. -\end{itemize} - -Implementations that model real numbers using any method other than IEEE 754 shall be able to model -positive infinity, negative infinity, signaling NaN\footnote{% - Per the IEEE 754 standard, NaN stands for - ``not-a-number'' -- a set of special bit patterns that represent lack of a meaningful value. -}, and quiet NaN. - -\subsection{Array types} - -\subsubsection{Fixed-length array types} - -Serialized representations of a fixed-length array of $n$ elements of type $T$ and -a sequence of $n$ field attributes of type $T$ are equivalent. - -\begin{remark} - Serialized representations of the following two data type definitions are equivalent: - - \begin{minted}{python} - AnyType[3] array - \end{minted} - - \begin{minted}{python} - AnyType item_0 - AnyType item_1 - AnyType item_2 - \end{minted} -\end{remark} - -\subsubsection{Variable-length array types}\label{sec:dsdl_serialized_variable_length_array} - -A serialized representation of a variable-length array consists of two segments: -the implicit length field immediately followed by the array elements. - -The implicit length field is of an unsigned integer type. -The serialized representation of the implicit length field -is injected in the beginning of the serialized representation of its array. -The bit length of the unsigned integer value is first determined as follows: - -$$b=\lceil{}\log_2 (c + 1)\rceil{}$$ - -where $c$ is the capacity (i.e., the maximum number of elements) of the variable-length array and -$b$ is the minimum number of bits needed to encode $c$ as an unsigned integer. An additional transformation -of $b$ ensures byte alignment of this implicit field when serialized\footnote{Future updates to the specification -may allow this second step to be modified but the default action will always be to byte-align the implicit -length field.}: - -$$2^{\lceil{}\log_2 (\text{max}(8, b))\rceil{}}$$ - -The number of elements $n$ contained in the variable-length array is encoded -in the serialized representation of the implicit length field -as described in section~\ref{sec:dsdl_serialized_unsigned_integer}. -By definition, $n \leq c$; therefore, bit sequences where the implicit length field contains values -greater than $c$ do not belong to the set of serialized representations of the array. - -The rest of the serialized representation is constructed as if the variable-length array was -a fixed-length array of $n$ elements\footnote{% - Observe that the implicit array length field, per its definition, - is guaranteed to never break the alignment of the following array elements. - There may be no padding between the implicit array length field and its elements. -}. - -\begin{remark} - Data type authors must take into account that variable-length arrays with a capacity of $\leq{}255$ elements will - consume an additional 8 bits of the serialized representation - (where a capacity of $\leq 65535$ will consume 16 bits and so on). - For example: - - \begin{minted}{python} - uint8 first - uint8[<=6] second # The implicit length field is 8 bits wide - @assert _offset_.max / 8 <= 7 # This would fail. - \end{minted} - - In the above example the author attempted to fit the message into a single Classic CAN frame but - did not account for the implicit length field. The correct version would be: - - \begin{minted}{python} - uint8 first - uint8[<=5] second # The implicit length field is 8 bits wide - @assert _offset_.max / 8 <= 7 # This would pass. - \end{minted} - - If the array contained three elements, the resulting set of its serialized representations would - be equivalent to that of the following definition: - - \begin{minted}{python} - uint8 first - uint8 implicit_length_field # Set to 3, because the array contains three elements - uint8 item_0 - uint8 item_1 - uint8 item_2 - \end{minted} -\end{remark} - -\subsection{Composite types}\label{sec:dsdl_serialization_composite} - -\subsubsection{Sealed structure} - -A serialized representation of an object of a sealed composite type that is not a tagged union -is a sequence of serialized representations of its field attribute values joined into a bit sequence, -separated by padding if such is necessary to satisfy the alignment requirements. -The ordering of the serialized representations of the field attribute values follows the order -of field attribute declaration. - -\begin{remark} - Consider the following definition, - where the fields are assigned runtime values shown in the comments: - - \begin{minted}{python} - # decimal bit sequence comment - truncated uint12 first # +48858 1011_1110_1101_1010 overflow, MSB truncated - saturated int3 second # -1 111 two's complement - saturated int4 third # -5 1011 two's complement - saturated int2 fourth # -1 11 two's complement - truncated uint4 fifth # +136 1000_1000 overflow, MSB truncated - @sealed - \end{minted} - - It can be seen that the bit layout is rather complicated because the field boundaries do not align with byte - boundaries, which makes it a good case study. - The resulting serialized byte sequence is shown below in the base-2 system: - $$ - \underbrace{% - \overbrace{% - \underset{7}{\overset{7}{\hugett{1}}}% - \underset{6}{\overset{6}{\hugett{1}}}% - \underset{5}{\overset{5}{\hugett{0}}}% - \underset{4}{\overset{4}{\hugett{1}}}% - \underset{3}{\overset{3}{\hugett{1}}}% - \underset{2}{\overset{2}{\hugett{0}}}% - \underset{1}{\overset{1}{\hugett{1}}}% - \underset{0}{\overset{0}{\hugett{0}}}% - }^{\texttt{first}}% - }_{\texttt{byte 0}}% - \hugett{,}% - \underbrace{% - \overbrace{% - \underset{7}{\overset{0}{\hugett{1}}}% - }^{\texttt{third}}% - \overbrace{% - \underset{6}{\overset{2}{\hugett{1}}}% - \underset{5}{\overset{1}{\hugett{1}}}% - \underset{4}{\overset{0}{\hugett{1}}}% - }^{\texttt{second}}% - \overbrace{% - \underset{3}{\overset{11}{\hugett{1}}}% - \underset{2}{\overset{10}{\hugett{1}}}% - \underset{1}{\overset{9}{\hugett{1}}}% - \underset{0}{\overset{8}{\hugett{0}}}% - }^{\texttt{first}}% - }_{\texttt{byte 1}}% - \hugett{,}% - \underbrace{% - \overbrace{% - \underset{7}{\overset{2}{\hugett{0}}}% - \underset{6}{\overset{1}{\hugett{0}}}% - \underset{5}{\overset{0}{\hugett{0}}}% - }^{\texttt{fifth}}% - \overbrace{% - \underset{4}{\overset{1}{\hugett{1}}}% - \underset{3}{\overset{0}{\hugett{1}}}% - }^{\texttt{fourth}}% - \overbrace{% - \underset{2}{\overset{3}{\hugett{1}}}% - \underset{1}{\overset{2}{\hugett{0}}}% - \underset{0}{\overset{1}{\hugett{1}}}% - }^{\texttt{third}}% - }_{\texttt{byte 2}}% - \hugett{,}% - \underbrace{% - \overbrace{% - \underset{7}{\overset{?}{\hugett{?}}}% - \underset{6}{\overset{?}{\hugett{?}}}% - \underset{5}{\overset{?}{\hugett{?}}}% - \underset{4}{\overset{?}{\hugett{?}}}% - \underset{3}{\overset{?}{\hugett{?}}}% - \underset{2}{\overset{?}{\hugett{?}}}% - \underset{1}{\overset{?}{\hugett{?}}}% - }^{\substack{\text{Next object or} \\ \text{zero padding bits}}} - \overbrace{% - \underset{0}{\overset{3}{\hugett{1}}}% - }^{\texttt{fifth}}% - }_{\texttt{byte 3}}% - $$ - - Note that some of the complexity of the above illustration stems from the modern convention of representing - numbers with the most significant components on the left moving to the least significant component of the - number of the right. If you were to reverse this convention the bit sequences for each type in the composite - would seem to be continuous as they crossed byte boundaries. Using this reversed representation, however, is - not recommended because the convention is deeply ingrained in most readers, tools, and technologies. -\end{remark} - -\subsubsection{Sealed tagged union} - -Similar to variable-length arrays, a serialized representation of a sealed tagged union consists of two segments: -the implicit \emph{union tag} value followed by the selected field attribute value. - -The implicit union tag is an unsigned integer value whose serialized representation -is implicitly injected in the beginning of the serialized representation of its tagged union. -The bit length of the implicit union tag is determined as follows: -$$b=\lceil{}\log_2 n\rceil{}$$ -where $n$ is the number of field attributes in the union, $n \geq 2$ and $b$ is the minimum number of bits needed -to encode $n$ as an unsigned integer. An additional transformation of $b$ ensures byte alignment of this implicit -field when serialized\footnote{Future updates to the specification may allow this second step to be modified but -the default action will always be to byte-align the implicit length field.}: - -$$2^{\lceil{}\log_2 (\text{max}(8, b))\rceil{}}$$ - -Each of the tagged union field attributes is assigned an index according to the order of their definition; -the order follows that of the DSDL statements (see section~\ref{sec:dsdl_grammar} on statement ordering). -The first defined field attribute is assigned the index 0 (zero), -the index of each following field attribute is incremented by one. - -The index of the field attribute whose value is currently held by the tagged union is encoded -in the serialized representation of the implicit union tag as described in section -\ref{sec:dsdl_serialized_unsigned_integer}. -By definition, $i < n$, where $i$ is the index of the current field attribute; -therefore, bit sequences where the implicit union tag field contains values -that are greater than or equal $n$ do not belong to the set of serialized representations of the tagged union. - -The serialized representation of the implicit union tag is immediately followed by -the serialized representation of the currently selected field attribute value\footnote{% - Observe that the implicit union tag field, per its definition, - is guaranteed to never break the alignment of the following field. - There may be no padding between the implicit union tag field and the selected field. -}. - -\begin{remark} - Consider the following example: - - \begin{minted}{python} - @sealed - @union # In this case, the implicit union tag is one byte wide - uint16 FOO = 42 # A regular constant attribute - uint16 a # Field index 0 - uint8 b # Field index 1 - uint32 BAR = 42 # Another regular constant attribute - float64 c # Field index 2 - \end{minted} - - In order to serialize the field \verb|b|, the implicit union tag shall be assigned the value 1. - The following type will have an identical layout: - - \begin{minted}{python} - @sealed - uint8 implicit_union_tag # Set to 1 - uint8 b # The actual value - \end{minted} - - Suppose that the value of \verb|b| is 7. - The resulting serialized representation is shown below in the base-2 system: - $$% - \overset{\text{byte 0}}{% - \underbrace{\hugett{00000001}}_{\substack{\text{union} \\ \text{tag}}}% - }% - \hugett{,}% - \overset{\text{byte 1}}{% - \underbrace{\hugett{00000111}}_{\text{field }\texttt{b}}% - } - $$ - -\end{remark} - -\begin{remark} - Let the following data type be defined under the short name \verb|Empty| and version 1.0: - - \begin{minted}{python} - # Empty. The only valid serialized representation is an empty bit sequence. - @sealed - \end{minted} - - Consider the following union: - - \begin{minted}{python} - @sealed - @union - Empty.1.0 none - AnyType.1.0 some - \end{minted} - - The set of serialized representations of the union given above is equivalent to - that of the following variable-length array: - - \begin{minted}{python} - @sealed - AnyType.1.0[<=1] maybe_some - \end{minted} -\end{remark} - -\subsubsection{Delimited types}\label{sec:dsdl_serialization_composite_non_sealed} - -Objects of delimited (non-sealed) composite types that are nested inside other objects\footnote{% - Of any type, not necessarily composite; e.g., arrays. -} -are serialized into opaque containers that consist of two parts: -the fixed-length \emph{delimiter header}, -immediately followed by the serialized representation of the object as if it was of a sealed type. - -Objects of delimited composite types that are \emph{not} nested inside other objects (i.e., top-level objects) -are serialized as if they were of a sealed type (without the delimiter header). -The delimiter header, therefore, logically belongs to the container object rather than the contained one. - -\begin{remark} - Top-level objects do not require the delimiter header because the change in their length does not necessarily - affect the backward compatibility thanks to the implicit truncation rule - (section \ref{sec:dsdl_serialization_implicit_truncation}) and the implicit zero extension rule - (section \ref{sec:dsdl_serialization_implicit_zero_extension}). -\end{remark} - -The delimiter header is an implicit field of type \verb|uint32| that encodes the length of the -serialized representation it precedes in bytes\footnote{% - Remember that by virtue of the padding requirement (section \ref{sec:dsdl_composite_alignment_cumulative_bls}), - the length of the serialized representation of a composite type is always an integer number of bytes. -}. -During deserialization, if the length of the serialized representation reported by its delimiter header -does not match the expectation of the deserializer, -the implicit truncation (section \ref{sec:dsdl_serialization_implicit_truncation}) -and the implicit zero extension (section \ref{sec:dsdl_serialization_implicit_zero_extension}) -rules apply. - -The length encoded in a delimiter header cannot exceed the number of bytes remaining between the delimiter header -and the end of the serialized representation of the outer object. -Otherwise, the serialized representation of the outer object is invalid and is to be discarded -(section \ref{sec:dsdl_serialized_error}). - -It is allowed for a sealed composite type to nest non-sealed composite types, and vice versa. -No special rules apply in such cases. - -\begin{remark} - The resulting serialized representation of a delimited composite is identical to \verb|uint8[<2**32]| - (sans the higher alignment requirement). - The implicit array length field is like the delimiter header, - and the array content is the serialized representation of the composite as if it was sealed. - - The following illustrates why this is necessary for robust extensibility. - Suppose that some composite $C$ contains two fields whose types are $A$ and $B$. - The fields of $A$ are $a_0,\ a_1$; - likewise, $B$ contains $b_0,\ b_1$. - - Suppose that $C^\prime$ is modified such that $A^\prime$ contains an extra field $a_2$. - If $A$ (and $A^\prime$) were sealed, this would result in the breakage of compatibility between $C$ and $C^\prime$ - as illustrated in figure \ref{fig:dsdl_sealed_non_extensibility} because the positions of the fields of $B$ - (which is sealed) would be shifted by the size of $a_2$. - - The use of opaque containers allows the implicit truncation and the implicit zero extension rules to apply - at any level of nesting, enabling agents expecting $C$ to truncate $a_2$ away, - and enabling agents expecting $C^\prime$ to zero-extend $a_2$ - if it is not present, as shown in figure \ref{fig:dsdl_non_sealed_extensibility}, - where $H_A$ is the delimiter header of $A$. - Observe that it is irrelevant whether $C$ (same as $C^\prime$) is sealed or not. - - \begin{figure}[H] - \centering - \begin{tabular}{r c c c c c} - \cline{2-5} - $C$ & - \multicolumn{1}{|c|}{$a_0$} & \multicolumn{1}{c|}{$a_1$} - &\multicolumn{1}{c|}{$b_0$} & \multicolumn{1}{c|}{$b_1$} & - \\\cline{2-5} - & $\checkmark$ & $\checkmark$ & $\times$ & $\times$ & $\times$ \\ - \cline{2-6} - $C^\prime$ & - \multicolumn{1}{|c|}{$a_0$} & \multicolumn{1}{c|}{$a_1$} & \multicolumn{1}{c|}{$a_2$} - &\multicolumn{1}{c|}{$b_0$} & \multicolumn{1}{c|}{$b_1$} - \\\cline{2-6} - \end{tabular} - \caption{Non-extensibility of sealed types} - \label{fig:dsdl_sealed_non_extensibility} - \end{figure} - - \begin{figure}[H] - \centering - \begin{tabular}{r c c c c c c} - \cline{2-7} - $C$ & - \multicolumn{1}{|c|}{$H_A$} & \multicolumn{1}{c|}{$a_0$} & \multicolumn{1}{c|}{$a_1$} - &\multicolumn{1}{c|}{\footnotesize{$\ldots$}} - &\multicolumn{1}{c|}{$b_0$} & \multicolumn{1}{c|}{$b_1$} - \\\cline{2-7} - & $\checkmark$ & $\checkmark$ & $\checkmark$ & $\checkmark$ & $\checkmark$ & $\checkmark$ \\ - \cline{2-7} - $C^\prime$ & - \multicolumn{1}{|c|}{$H_A$} & \multicolumn{1}{c|}{$a_0$} & \multicolumn{1}{c|}{$a_1$} & - \multicolumn{1}{c|}{$a_2$} - &\multicolumn{1}{c|}{$b_0$} & \multicolumn{1}{c|}{$b_1$} - \\\cline{2-7} - \end{tabular} - \caption{Extensibility of delimited types with the help of the delimiter header} - \label{fig:dsdl_non_sealed_extensibility} - \end{figure} - - This example also illustrates why the extent is necessary. - Per the rules set forth in \ref{sec:dsdl_composite_extent_and_sealing}, - it is required that the extent (i.e., the buffer memory requirement) of $A$ shall be large enough to accommodate - serialized representations of $A^\prime$, and, therefore, - the extent of $C$ is large enough to accommodate serialized representations of $C^\prime$. - If that were not the case, then an implementation expecting $C$ would be unable to correctly process $C^\prime$ - because the implicit truncation rule would have cut off $b_1$, which is unexpected. - - The design decision to make the delimiter header of a fixed width may not be obvious so it's worth explaining. - There are two alternatives: making it variable-length and making the length a function of the extent - (section \ref{sec:dsdl_composite_extent_and_sealing}). - The first option does not align with the rest of the specification because DSDL does not make use of - variable-length integers (unlike some other formats, like Google Protobuf, for example), - and because a variable-length length {\footnotesize{(sic!)}} prefix would have somewhat complicated the - bit length set computation. - The second option would make nested hierarchies (composites that nest other composites) possibly highly fragile - because the change of the extent of a deeply nested type may inadvertently move the delimiter header of an - outer type into a different length category, which would be disastrous for compatibility and hard to spot. - There is an in-depth discussion of this issue (and other related matters) on the forum. - - The fixed-length delimiter header may be considered large, - but delimited types tend to also be complex, which makes the overhead comparatively insignificant, - whereas sealed types that tend to be compact and overhead-sensitive do not contain the delimiter header. -\end{remark} - -\begin{remark} - In order to efficiently serialize an object of a delimited type, - the implementation may need to perform a second pass to reach the delimiter header - after the object is serialized, because before that, the value of the delimiter header cannot be known - unless the object is of a fixed-size (i.e., the cardinality of the bit length set is one). - - Consider: - \begin{minted}{python} - uint8[<=4] x - \end{minted} - Let $\texttt{x} = \left[ 4, 2 \right]$, - then the nested serialized representation would be constructed as: - \begin{enumerate} - \item Memorize the current memory address $M_\text{origin}$. - \item Skip 32 bits. - \item Encode the length: 2 elements. - \item Encode $x_0 = 4$. - \item Encode $x_1 = 2$. - \item Memorize the current memory address $M_\text{current}$. - \item Go back to $M_\text{origin}$. - \item Encode a 32-bit wide value of $(M_\text{current} - M_\text{origin})$. - \item Go back to $M_\text{current}$. - \end{enumerate} - - However, if the object is known to be of a constant size, the above can be simplified, - because there may be only one possible value of the delimiter header. - Automatic code generation tools should take advantage of this knowledge. -\end{remark} diff --git a/specification/sdt/sdt.tex b/specification/sdt/sdt.tex deleted file mode 100644 index 3890401d..00000000 --- a/specification/sdt/sdt.tex +++ /dev/null @@ -1,21 +0,0 @@ -\chapter{List of standard data types}\label{sec:sdt} - -This chapter contains the full list of standard data types defined by the Cyphal specification\footnote{% - The standard root namespace is named \texttt{uavcan}, not \texttt{cyphal}, for historical reasons. -}. -The source text of the DSDL data type definitions provided here is also available via the -official project website at \mbox{\url{https://opencyphal.org}}. - -Regulated non-standard definitions\footnote{% - I.e., public definitions contributed by vendors and other users - of the specification, as explained in section~\ref{sec:basic_data_type_regulation}. -} are not included in this list. - -In the table, \emph{BLS} stands for bit length set. -The extent is not shown for sealed entities -- that would be redundant because sealing implies -that the extent equals the maximum bit length set. -For service types, the parameters pertaining to the request and response are shown separately. - -The index table~\ref{table:dsdl:uavcan} is provided before the definitions for ease of navigation. - -\clearpage\DSDL{uavcan.*} From 7b65cf0da83c733aec08738f8470f270928a4550 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 12:28:24 +0300 Subject: [PATCH 02/14] remove application and appendices --- specification/Cyphal_Specification.tex | 1 - specification/appendices/appendices.tex | 2 - specification/appendices/crc.tex | 258 ------ specification/application/NED_ECEF.eps | 666 -------------- specification/application/NED_ECEF.svg | 362 -------- .../application/aircraft_principal_axes.eps | 838 ----------------- .../application/aircraft_principal_axes.svg | 865 ------------------ specification/application/application.tex | 27 - specification/application/conventions.tex | 232 ----- specification/application/functions.tex | 313 ------- specification/application/requirements.tex | 84 -- 11 files changed, 3648 deletions(-) delete mode 100644 specification/appendices/appendices.tex delete mode 100644 specification/appendices/crc.tex delete mode 100644 specification/application/NED_ECEF.eps delete mode 100644 specification/application/NED_ECEF.svg delete mode 100644 specification/application/aircraft_principal_axes.eps delete mode 100644 specification/application/aircraft_principal_axes.svg delete mode 100644 specification/application/application.tex delete mode 100644 specification/application/conventions.tex delete mode 100644 specification/application/functions.tex delete mode 100644 specification/application/requirements.tex diff --git a/specification/Cyphal_Specification.tex b/specification/Cyphal_Specification.tex index f4821d01..bf15fac7 100644 --- a/specification/Cyphal_Specification.tex +++ b/specification/Cyphal_Specification.tex @@ -115,6 +115,5 @@ \section*{Limitation of liability} \input{introduction/introduction.tex} \input{basic/basic.tex} \input{transport/transport.tex} -\input{appendices/appendices.tex} \end{document} diff --git a/specification/appendices/appendices.tex b/specification/appendices/appendices.tex deleted file mode 100644 index 4c29fbd6..00000000 --- a/specification/appendices/appendices.tex +++ /dev/null @@ -1,2 +0,0 @@ -\appendix -\input{appendices/crc.tex} diff --git a/specification/appendices/crc.tex b/specification/appendices/crc.tex deleted file mode 100644 index 1f93b872..00000000 --- a/specification/appendices/crc.tex +++ /dev/null @@ -1,258 +0,0 @@ -\chapter{CRC algorithm implementations} - -\section{CRC-16/CCITT-FALSE}\label{sec:appendix_crc16ccitt_false} - -This algorithm is also known as CRC-16/AUTOSAR or CRC-16/IBM-3740. -Not to be confused with CRC-16/KERMIT. - -This algorithm has the following parameters: -\begin{itemize} - \item width: 16 bits; - \item polynomial: $\mathrm{1021}_{16}$; - \item initial value: $\mathrm{FFFF}_{16}$; - \item input not reflected; - \item output not reflected; - \item no output XOR; - \item the native byte order is big endian. -\end{itemize} - -The value for the input sequence $\left(49, 50, \ldots, 56, 57\right)$ is $\mathrm{29B1}_{16}$. - -\subsection{C++, bitwise} - -\begin{samepage} -\begin{minted}{cpp} -#include -#include -#include - -class CRC16_CCITT_False final -{ -public: - void add(const std::uint8_t byte) - { - value_ ^= static_cast(byte) << 8U; - for (std::uint8_t bit = 8; bit > 0; --bit) - { - if ((value_ & 0x8000U) != 0) - { - value_ = (value_ << 1U) ^ 0x1021U; - } - else - { - value_ = value_ << 1U; - } - } - } - - void add(const std::uint8_t* bytes, std::size_t length) - { - while (length --> 0) - { - add(*bytes++); - } - } - - [[nodiscard]] std::uint16_t get() const { return value_; } - - [[nodiscard]] std::array getBytes() const noexcept - { - const auto x = get(); - return {static_cast(x >> 8U), static_cast(x & 0xFFU)}; - } - -private: - std::uint16_t value_ = 0xFFFFU; -}; -\end{minted} -\end{samepage} - -\subsection{Python, bytewise} - -\begin{samepage} -\begin{minted}{python} -class CRC16CCITT: - def __init__(self) -> None: - self._value = 0xFFFF - - def add(self, data: bytes | bytearray | memoryview) -> None: - val = self._value - for x in data: - val = ((val << 8) & 0xFFFF) ^ self._TABLE[(val >> 8) ^ x] - self._value = val - - def check_residue(self) -> bool: - return self._value == 0 - - @property - def value(self) -> int: - return self._value - - @property - def value_as_bytes(self) -> bytes: - return self.value.to_bytes(2, "big") - - _TABLE = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, - 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, - 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, - 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, - 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, - 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, - 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, - 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, - 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, - 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, - 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, - 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, - 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, - 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, - 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, - 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, - 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, - 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, - 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, - 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, - 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, - 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, - 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, - ] -\end{minted} -\end{samepage} - -\newpage -\section{CRC-32C}\label{sec:appendix_crc32c} - -This algorithm is also known as CRC-32/ISCSI, CRC-32/CASTAGNOLI, CRC-32/BASE91-C, or CRC-32/INTERLAKEN. - -This algorithm has the following parameters: -\begin{itemize} - \item width: 32 bits; - \item polynomial: $\mathrm{1EDC6F41}_{16}$; - \item initial value: $\mathrm{FFFFFFFF}_{16}$; - \item input reflected; - \item output reflected; - \item output XOR: $\mathrm{FFFFFFFF}_{16}$; - \item residue: $\mathrm{B798B438}_{16}$ before output XOR, $\mathrm{48674BC7}_{16}$ after output XOR; - \item the native byte order is little endian. -\end{itemize} - -The value for the input sequence $\left(49, 50, \ldots, 56, 57\right)$ is $\mathrm{E3069283}_{16}$. - -\subsection{C++, bitwise} - -\begin{samepage} -\begin{minted}{cpp} -#include -#include -#include - -class CRC32C final -{ -public: - static constexpr std::size_t Size = 4; - - void update(const std::uint8_t b) noexcept - { - value_ ^= static_cast(b); - for (auto i = 0U; i < 8U; i++) - { - value_ = ((value_ & 1U) != 0) ? ((value_ >> 1U) ^ ReflectedPoly) : (value_ >> 1U); - } - } - - [[nodiscard]] std::uint32_t get() const noexcept { return value_ ^ Xor; } - - [[nodiscard]] std::array getBytes() const noexcept - { - const auto x = get(); - return { - static_cast(x >> (8U * 0U)), - static_cast(x >> (8U * 1U)), - static_cast(x >> (8U * 2U)), - static_cast(x >> (8U * 3U)), - }; - } - - [[nodiscard]] auto isResidueCorrect() const noexcept { return value_ == Residue; } - -private: - static constexpr std::uint32_t Xor = 0xFFFF'FFFFUL; - static constexpr std::uint32_t ReflectedPoly = 0x82F6'3B78UL; - static constexpr std::uint32_t Residue = 0xB798'B438UL; - - std::uint32_t value_ = Xor; -}; -\end{minted} -\end{samepage} - -\subsection{Python, bytewise} - -\begin{samepage} -\begin{minted}{python} -class CRC32C: - def __init__(self) -> None: - self._value = 0xFFFFFFFF - - def add(self, data: bytes | bytearray | memoryview) -> None: - val = self._value - for x in data: - val = (val >> 8) ^ self._TABLE[x ^ (val & 0xFF)] - self._value = val - - def check_residue(self) -> bool: - return self._value == 0xB798B438 # Checked before the output XOR is applied. - - @property - def value(self) -> int: - return self._value ^ 0xFFFFFFFF - - @property - def value_as_bytes(self) -> bytes: - return self.value.to_bytes(4, "little") - - _TABLE = [ - 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, - 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, - 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, - 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, - 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, - 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, - 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, - 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, - 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, - 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, - 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, - 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, - 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, - 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, - 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, - 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, - 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, - 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, - 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, - 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, - 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, - 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, - 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, - 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, - 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, - 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, - 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, - 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, - 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, - 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, - 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, - 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, - ] -\end{minted} -\end{samepage} diff --git a/specification/application/NED_ECEF.eps b/specification/application/NED_ECEF.eps deleted file mode 100644 index 44cac08c..00000000 --- a/specification/application/NED_ECEF.eps +++ /dev/null @@ -1,666 +0,0 @@ -%!PS-Adobe-3.0 EPSF-3.0 -%%Creator: cairo 1.14.6 (http://cairographics.org) -%%CreationDate: Thu Dec 13 10:54:03 2018 -%%Pages: 1 -%%DocumentData: Clean7Bit -%%LanguageLevel: 2 -%%BoundingBox: 0 -1 413 391 -%%EndComments -%%BeginProlog -save -50 dict begin -/q { gsave } bind def -/Q { grestore } bind def -/cm { 6 array astore concat } bind def -/w { setlinewidth } bind def -/J { setlinecap } bind def -/j { setlinejoin } bind def -/M { setmiterlimit } bind def -/d { setdash } bind def -/m { moveto } bind def -/l { lineto } bind def -/c { curveto } bind def -/h { closepath } bind def -/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto - 0 exch rlineto 0 rlineto closepath } bind def -/S { stroke } bind def -/f { fill } bind def -/f* { eofill } bind def -/n { newpath } bind def -/W { clip } bind def -/W* { eoclip } bind def -/BT { } bind def -/ET { } bind def -/pdfmark where { pop globaldict /?pdfmark /exec load put } - { globaldict begin /?pdfmark /pop load def /pdfmark - /cleartomark load def end } ifelse -/BDC { mark 3 1 roll /BDC pdfmark } bind def -/EMC { mark /EMC pdfmark } bind def -/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def -/Tj { show currentpoint cairo_store_point } bind def -/TJ { - { - dup - type /stringtype eq - { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse - } forall - currentpoint cairo_store_point -} bind def -/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore - cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def -/Tf { pop /cairo_font exch def /cairo_font_matrix where - { pop cairo_selectfont } if } bind def -/Td { matrix translate cairo_font_matrix matrix concatmatrix dup - /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point - /cairo_font where { pop cairo_selectfont } if } bind def -/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def - cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def -/g { setgray } bind def -/rg { setrgbcolor } bind def -/d1 { setcachedevice } bind def -%%EndProlog -%%BeginSetup -%%EndSetup -%%Page: 1 1 -%%BeginPageSetup -%%PageBoundingBox: 0 -1 413 391 -%%EndPageSetup -q 0 -1 413 392 rectclip q -0.501961 g -1 w -1 J -1 j -[ 4 8] 0 d -4 M q 0 1 -1 0 0 390.770447 cm --220.051 -340.762 m -218.969 -340.762 l -218.012 -340.398 l -216.93 -339.922 - l -215.969 -339.199 l -215.012 -338.359 l -214.051 -337.281 l -212.969 --335.961 l -212.012 -334.52 l -211.051 -332.84 l -210.09 -331.039 l -209.129 - -329 l -208.289 -326.719 l -207.332 -324.32 l -206.371 -321.68 l -205.531 - -318.922 l -204.691 -316.039 l -203.852 -312.922 l -203.012 -309.68 l -201.332 - -302.719 l -200.609 -298.879 l -199.891 -295.039 l -199.172 -290.961 l --198.449 -286.879 l -197.73 -282.559 l -197.129 -278.121 l -196.531 -273.559 - l -195.93 -268.879 l -195.332 -264.082 l -194.73 -259.148 l -194.25 -254.121 - l -193.289 -243.801 l -192.93 -238.52 l -192.57 -233.121 l -192.211 -227.719 - l -191.852 -222.199 l -191.609 -216.68 l -191.371 -211.039 l -191.129 -205.398 - l -190.891 -199.641 l -190.77 -193.879 l -190.652 -188.121 l -190.531 -182.359 - l -190.531 -159.199 l -190.652 -153.441 l -190.77 -147.559 l -190.891 -141.922 - l -191.129 -136.148 l -191.371 -130.52 l -191.609 -124.879 l -191.852 -119.359 - l -192.211 -113.84 l -192.57 -108.441 l -192.93 -103.039 l -193.289 -97.762 - l -194.25 -87.441 l -194.73 -82.398 l -195.332 -77.48 l -195.93 -72.68 -l -196.531 -68 l -197.129 -63.441 l -197.73 -59 l -198.449 -54.68 l -199.172 - -50.602 l -199.891 -46.52 l -200.609 -42.559 l -201.332 -38.84 l -202.172 - -35.238 l -203.012 -31.879 l -203.852 -28.641 l -204.691 -25.52 l -205.531 - -22.52 l -206.371 -19.762 l -207.332 -17.238 l -208.289 -14.84 l -209.129 - -12.559 l -210.09 -10.52 l -211.051 -8.719 l -212.012 -7.039 l -212.969 - -5.602 l -214.051 -4.281 l -215.012 -3.199 l -215.969 -2.359 l -216.93 --1.641 l -218.012 -1.148 l -218.969 -0.801 l -220.051 -0.801 l S Q -1.6 w -[] 0.0 d -q 0 1 -1 0 0 390.770447 cm --220.051 -0.801 m -221.012 -0.801 l -221.969 -1.148 l -223.051 -1.641 l - -224.012 -2.359 l -224.969 -3.199 l -226.051 -4.281 l -227.012 -5.602 l - -227.969 -7.039 l -228.93 -8.719 l -229.891 -10.52 l -230.852 -12.559 l - -231.809 -14.84 l -232.652 -17.238 l -233.609 -19.762 l -234.449 -22.52 - l -235.289 -25.52 l -236.25 -28.641 l -236.969 -31.879 l -237.809 -35.238 - l -238.652 -38.84 l -239.371 -42.559 l -240.211 -46.52 l -240.93 -50.602 - l -241.531 -54.68 l -242.25 -59 l -242.852 -63.441 l -243.57 -68 l -244.172 - -72.68 l -244.652 -77.48 l -245.25 -82.398 l -245.73 -87.441 l -246.691 - -97.762 l -247.051 -103.039 l -247.41 -108.441 l -247.77 -113.84 l -248.129 - -119.359 l -248.371 -124.879 l -248.73 -130.52 l -248.852 -136.148 l -249.09 - -141.922 l -249.211 -147.559 l -249.332 -153.441 l -249.449 -159.199 l --249.449 -164.961 l -249.57 -170.719 l -249.449 -176.602 l -249.449 -182.359 - l -249.332 -188.121 l -249.211 -193.879 l -249.09 -199.641 l -248.852 -205.398 - l -248.73 -211.039 l -248.371 -216.68 l -248.129 -222.199 l -247.77 -227.719 - l -247.41 -233.121 l -247.051 -238.52 l -246.691 -243.801 l -245.73 -254.121 - l -245.25 -259.148 l -244.652 -264.082 l -244.172 -268.879 l -243.57 -273.559 - l -242.852 -278.121 l -242.25 -282.559 l -241.531 -286.879 l -240.93 -290.961 - l -240.211 -295.039 l -239.371 -298.879 l -238.652 -302.719 l -237.809 --306.199 l -236.969 -309.68 l -236.25 -312.922 l -235.289 -316.039 l -234.449 - -318.922 l -233.609 -321.68 l -232.652 -324.32 l -231.809 -326.719 l -230.852 - -329 l -229.891 -331.039 l -228.93 -332.84 l -227.969 -334.52 l -227.012 - -335.961 l -226.051 -337.281 l -224.969 -338.359 l -224.012 -339.199 l --223.051 -339.922 l -221.969 -340.398 l -221.012 -340.762 l -220.051 -340.762 - l S Q -q 0 1 -1 0 0 390.770447 cm --220.051 -0.801 m -228.93 -0.922 l -237.809 -1.641 l -246.57 -2.84 l -255.332 - -4.398 l -263.969 -6.559 l -272.492 -9.082 l -280.891 -12.082 l -289.172 - -15.441 l -297.211 -19.281 l -305.012 -23.48 l -312.57 -28.148 l -319.891 - -33.199 l -326.969 -38.602 l -333.809 -44.359 l -340.172 -50.602 l -346.41 - -56.961 l -352.172 -63.801 l -357.57 -70.879 l -362.609 -78.199 l -367.289 - -85.762 l -371.492 -93.559 l -375.332 -101.602 l -378.691 -109.879 l -381.691 - -118.281 l -384.211 -126.801 l -386.371 -135.441 l -387.93 -144.199 l -389.129 - -152.961 l -389.852 -161.84 l -389.969 -170.719 l -389.852 -179.719 l -389.129 - -188.602 l -387.93 -197.359 l -386.371 -206.121 l -384.211 -214.762 l -381.691 - -223.281 l -378.691 -231.68 l -375.332 -239.961 l -371.492 -248 l -367.289 - -255.801 l -362.609 -263.359 l -357.57 -270.68 l -352.172 -277.762 l -346.41 - -284.602 l -340.172 -290.961 l -333.809 -297.082 l -326.969 -302.961 l --319.891 -308.359 l -312.57 -313.398 l -305.012 -318.082 l -297.211 -322.281 - l -289.172 -326.121 l -280.891 -329.48 l -272.492 -332.48 l -263.969 -335 - l -255.332 -337.039 l -246.57 -338.719 l -237.809 -339.922 l -228.93 -340.52 - l -220.051 -340.762 l S Q -q 0 1 -1 0 0 390.770447 cm --50.012 -170.719 m -50.012 -169.762 l -50.371 -168.801 l -50.852 -167.719 - l -51.57 -166.762 l -52.41 -165.801 l -53.492 -164.719 l -54.809 -163.762 - l -56.25 -162.801 l -57.93 -161.84 l -59.852 -160.879 l -61.891 -159.922 - l -64.051 -158.961 l -66.57 -158.121 l -69.09 -157.148 l -71.852 -156.32 - l -74.852 -155.359 l -77.969 -154.52 l -81.211 -153.68 l -84.691 -152.84 - l -88.289 -152.121 l -92.012 -151.281 l -95.969 -150.559 l -99.93 -149.84 - l -104.129 -149.121 l -108.449 -148.52 l -113.012 -147.801 l -117.57 -147.199 - l -122.25 -146.602 l -127.051 -146 l -131.969 -145.52 l -137.012 -145.039 - l -142.172 -144.559 l -147.332 -144.082 l -152.609 -143.602 l -158.012 --143.238 l -163.531 -142.879 l -169.051 -142.641 l -174.57 -142.281 l -180.211 - -142.039 l -185.852 -141.801 l -191.609 -141.68 l -197.371 -141.559 l -203.129 - -141.441 l -209.012 -141.32 l -214.77 -141.199 l -220.531 -141.199 l -226.41 - -141.32 l -232.172 -141.32 l -238.051 -141.441 l -243.809 -141.559 l -249.57 - -141.68 l S Q -0.0509804 0.384314 0.6 rg -1.6 w -q 1 0 0 -1 0 390.770447 cm -369.082 220.051 m 170.719 220.051 l 136.879 254.492 l S Q -360.711 174.591 m 371.199 170.735 l 360.711 166.88 l 362.387 169.157 362.375 - 172.27 360.711 174.591 c h -360.711 174.591 m f* -0.6 w -0 J -q -1 0 0 1 0 390.770447 cm --360.711 -216.18 m -371.199 -220.035 l -360.711 -223.891 l -362.387 -221.613 - -362.375 -218.5 -360.711 -216.18 c h --360.711 -216.18 m S Q -145.508 139.536 m 135.406 134.759 l 140.008 144.942 l 140.457 142.153 142.684 - 139.974 145.508 139.536 c h -145.508 139.536 m f* -0.427976 w -q 0.982578 1 1 -0.982578 0 390.770447 cm --55.082 199.63 m -62.563 196.879 l -55.081 194.129 l -56.275 195.752 -56.271 - 197.975 -55.082 199.63 c h --55.082 199.63 m S Q -0.501961 g -1.6 w -1 J -q 0 1 -1 0 0 390.770447 cm --50.012 -170.719 m -50.129 -174.68 l -50.609 -178.641 l -51.57 -182.602 - l -52.77 -186.441 l -54.332 -190.281 l -56.25 -194.121 l -58.41 -197.961 - l -61.051 -201.68 l -63.93 -205.281 l -67.172 -208.879 l -70.652 -212.359 - l -74.492 -215.84 l -78.57 -219.082 l -83.012 -222.32 l -87.691 -225.441 - l -92.73 -228.441 l -97.891 -231.32 l -103.41 -234.199 l -109.172 -236.84 - l -115.051 -239.238 l -121.289 -241.641 l -127.652 -243.922 l -134.129 --245.961 l -140.969 -247.879 l -147.809 -249.559 l -154.891 -251.238 l -162.09 - -252.68 l -169.289 -253.879 l -176.73 -254.961 l -184.172 -255.922 l -191.73 - -256.641 l -199.41 -257.238 l -207.09 -257.602 l -214.77 -257.84 l -222.449 - -257.84 l -230.129 -257.719 l -237.809 -257.359 l -245.492 -256.879 l S Q -0 0.721569 0 rg -0.72 w -10 M q 0 1 -1 0 0 390.770447 cm --165.449 -278.84 m -165.449 -230.121 l -115.289 -216.559 l -115.289 -265.281 - l h --165.449 -278.84 m S Q -1.6 w -q 1 0 0 -1 0 390.770447 cm -246.57 141.48 m 202.555 187.074 l S Q -206.996 208.298 m 206.918 212.825 l 201.441 202.544 l 211.523 208.38 l -h -206.996 208.298 m f* -0.575566 w -0 J -0 j -4 M q 0.965361 1 1 -0.965361 0 390.770447 cm -8.982 198.325 m 11.287 196.022 l 3.228 198.325 l 11.287 200.627 l h -8.982 198.325 m S Q -1 g -230.121 225.321 m 216.559 275.481 l 265.281 275.481 l 278.84 225.321 l -h -230.121 225.321 m f -0 0.721569 0 rg -1.6 w -1 J -1 j -10 M q 1 0 0 -1 0 390.770447 cm -230.121 165.449 m 216.559 115.289 l 265.281 115.289 l 278.84 165.449 l -h -230.121 165.449 m S Q -0.501961 g -1.6 w -4 M q 0 1 -1 0 0 390.770447 cm --220.051 -340.762 m -211.051 -340.52 l -202.172 -339.922 l -193.41 -338.719 - l -184.652 -337.039 l -176.012 -335 l -167.492 -332.48 l -159.09 -329.48 - l -150.809 -326.121 l -142.77 -322.281 l -134.969 -318.082 l -127.41 -313.398 - l -120.09 -308.359 l -113.012 -302.961 l -106.172 -297.082 l -99.809 -290.961 - l -93.691 -284.602 l -87.809 -277.762 l -82.41 -270.68 l -77.371 -263.359 - l -72.691 -255.801 l -68.492 -248 l -64.652 -239.961 l -61.289 -231.68 -l -58.289 -223.281 l -55.77 -214.762 l -53.73 -206.121 l -52.051 -197.359 - l -50.852 -188.602 l -50.25 -179.719 l -50.012 -170.719 l -50.25 -161.84 - l -50.852 -152.961 l -52.051 -144.199 l -53.73 -135.441 l -55.77 -126.801 - l -58.289 -118.281 l -61.289 -109.879 l -64.652 -101.602 l -68.492 -93.559 - l -72.691 -85.762 l -77.371 -78.199 l -82.41 -70.879 l -87.809 -63.801 -l -93.691 -56.961 l -99.809 -50.602 l -106.172 -44.359 l -113.012 -38.602 - l -120.09 -33.199 l -127.41 -28.148 l -134.969 -23.48 l -142.77 -19.281 - l -150.809 -15.441 l -159.09 -12.082 l -167.492 -9.082 l -176.012 -6.559 - l -184.652 -4.398 l -193.41 -2.84 l -202.172 -1.641 l -211.051 -0.922 l - -220.051 -0.801 l S Q -0.0509804 0.384314 0.6 rg -1.6 w -q 1 0 0 -1 0 390.770447 cm -170.719 220.051 m 170.719 21.691 l S Q -166.848 360.712 m 170.703 371.2 l 174.562 360.712 l 172.285 362.388 169.168 - 362.376 166.848 360.712 c h -166.848 360.712 m f* -0.6 w -0 J -q 0 -1 -1 0 0 390.770447 cm -30.059 -166.848 m 19.57 -170.703 l 30.059 -174.562 l 28.383 -172.285 28.395 - -169.168 30.059 -166.848 c h -30.059 -166.848 m S Q -0 0.721569 0 rg -1.6 w -1 J -10 M q 1 0 0 -1 0 390.770447 cm -246.422 141.48 m 343.859 141.48 l S Q -337.461 249.29 m 334.262 246.091 l 345.461 249.29 l 334.262 252.489 l h -337.461 249.29 m f* -0.8 w -0 J -0 j -4 M q -1 0 0 1 0 390.770447 cm --337.461 -141.48 m -334.262 -144.68 l -345.461 -141.48 l -334.262 -138.281 - l h --337.461 -141.48 m S Q -1.6 w -1 J -1 j -10 M q 1 0 0 -1 0 390.770447 cm -246.422 141.48 m 219.301 41.398 l S Q -220.977 343.192 m 224.902 340.942 l 218.883 350.915 l 218.723 339.267 l - h -220.977 343.192 m f* -0.772152 w -0 J -0 j -4 M q 0.270983 -1 -1 -0.270983 0 390.770447 cm -100.108 -193.849 m 103.195 -196.938 l 92.385 -193.848 l 103.196 -190.758 - l h -100.108 -193.849 m S Q -0 g -165.973 390.618 m 176.863 390.618 l 176.863 389.196 l 168.098 378.352 l - 177.066 378.352 l 177.066 376.774 l 165.77 376.774 l 165.77 378.196 l 174.52 - 389.056 l 165.973 389.056 l h -165.973 390.618 m f -381.004 170.032 m 384.348 176.454 l 386.254 176.454 l 381.848 168.423 l - 381.848 163.657 l 380.16 163.657 l 380.16 168.423 l 375.738 176.454 l 377.676 - 176.454 l h -381.004 170.032 m f -131.762 121.724 m 134.84 126.63 l 136.824 126.63 l 132.777 120.286 l 136.918 - 113.833 l 134.918 113.833 l 131.762 118.833 l 128.605 113.833 l 126.605 - 113.833 l 130.762 120.286 l 126.699 126.63 l 128.684 126.63 l h -131.762 121.724 m f -235.008 348.013 m 237.414 352.419 l 240.445 352.419 l 236.711 346.075 l - 240.539 339.622 l 237.477 339.622 l 235.008 344.106 l 232.555 339.622 l - 229.477 339.622 l 233.305 346.075 l 229.586 352.419 l 232.617 352.419 l - h -235.008 348.013 m f -246.035 346.122 4.953 -2.047 re f -268 339.622 m 265.359 339.622 l 260.234 348.044 l 260.234 339.622 l 257.594 - 339.622 l 257.594 352.419 l 260.234 352.419 l 265.375 343.981 l 265.375 - 352.419 l 268 352.419 l h -268 339.622 m f -269.637 344.466 m 269.637 345.411 269.816 346.251 270.184 346.981 c 270.547 - 347.72 271.066 348.294 271.746 348.7 c 272.434 349.106 273.23 349.31 274.137 - 349.31 c 275.418 349.31 276.465 348.911 277.277 348.122 c 278.09 347.341 - 278.543 346.278 278.637 344.935 c 278.652 344.278 l 278.652 342.817 278.238 - 341.645 277.418 340.763 c 276.605 339.888 275.512 339.45 274.137 339.45 - c 272.77 339.45 271.676 339.888 270.855 340.763 c 270.043 341.645 269.637 - 342.845 269.637 344.356 c h -272.184 344.278 m 272.184 343.38 272.348 342.692 272.684 342.216 c 273.027 - 341.735 273.516 341.497 274.152 341.497 c 274.766 341.497 275.246 341.731 - 275.59 342.2 c 275.934 342.669 276.105 343.423 276.105 344.466 c 276.105 - 345.349 275.934 346.032 275.59 346.513 c 275.246 347.001 274.762 347.247 - 274.137 347.247 c 273.512 347.247 273.027 347.001 272.684 346.513 c 272.348 - 346.032 272.184 345.286 272.184 344.278 c h -272.184 344.278 m f -285.469 346.747 m 285.125 346.798 284.82 346.825 284.562 346.825 c 283.602 - 346.825 282.973 346.497 282.672 345.841 c 282.672 339.622 l 280.125 339.622 - l 280.125 349.138 l 282.531 349.138 l 282.609 347.997 l 283.109 348.872 - 283.812 349.31 284.719 349.31 c 285 349.31 285.266 349.27 285.516 349.2 - c h -285.469 346.747 m f -290.137 351.466 m 290.137 349.138 l 291.762 349.138 l 291.762 347.263 l - 290.137 347.263 l 290.137 342.528 l 290.137 342.173 290.203 341.919 290.34 - 341.763 c 290.473 341.614 290.734 341.544 291.121 341.544 c 291.402 341.544 - 291.645 341.563 291.855 341.606 c 291.855 339.669 l 291.363 339.524 290.855 - 339.45 290.324 339.45 c 288.543 339.45 287.637 340.345 287.605 342.138 -c 287.605 347.263 l 286.215 347.263 l 286.215 349.138 l 287.605 349.138 -l 287.605 351.466 l h -290.137 351.466 m f -295.598 348.091 m 296.273 348.903 297.121 349.31 298.145 349.31 c 300.207 - 349.31 301.246 348.11 301.27 345.716 c 301.27 339.622 l 298.738 339.622 - l 298.738 345.638 l 298.738 346.188 298.617 346.595 298.379 346.856 c 298.148 - 347.114 297.762 347.247 297.223 347.247 c 296.473 347.247 295.93 346.958 - 295.598 346.388 c 295.598 339.622 l 293.066 339.622 l 293.066 353.122 l - 295.598 353.122 l h -295.598 348.091 m f -328.691 262.755 m 331.348 268.52 l 334.223 268.52 l 330.035 260.364 l 330.035 - 255.724 l 327.348 255.724 l 327.348 260.364 l 323.145 268.52 l 326.035 -268.52 l h -328.691 262.755 m f -339.594 262.224 4.953 -2.047 re f -358.855 261.27 m 353.793 261.27 l 353.793 257.849 l 359.73 257.849 l 359.73 - 255.724 l 351.152 255.724 l 351.152 268.52 l 359.715 268.52 l 359.715 266.38 - l 353.793 266.38 l 353.793 263.333 l 358.855 263.333 l h -358.855 261.27 m f -366.523 255.724 m 366.406 255.95 366.324 256.239 366.273 256.583 c 365.656 - 255.895 364.855 255.552 363.867 255.552 c 362.938 255.552 362.168 255.817 - 361.555 256.349 c 360.938 256.888 360.633 257.571 360.633 258.395 c 360.633 - 259.403 361.008 260.177 361.758 260.708 c 362.508 261.247 363.586 261.524 - 364.992 261.536 c 366.164 261.536 l 366.164 262.083 l 366.164 262.52 366.047 - 262.868 365.82 263.13 c 365.602 263.399 365.246 263.536 364.758 263.536 - c 364.328 263.536 363.992 263.431 363.742 263.224 c 363.5 263.013 363.383 - 262.731 363.383 262.38 c 360.852 262.38 l 360.852 262.931 361.016 263.442 - 361.352 263.911 c 361.695 264.38 362.172 264.743 362.789 265.005 c 363.414 - 265.274 364.117 265.411 364.898 265.411 c 366.062 265.411 366.992 265.114 - 367.68 264.52 c 368.367 263.935 368.711 263.106 368.711 262.036 c 368.711 - 257.927 l 368.719 257.02 368.844 256.337 369.086 255.88 c 369.086 255.724 - l h -364.43 257.489 m 364.805 257.489 365.148 257.571 365.461 257.739 c 365.773 - 257.903 366.008 258.13 366.164 258.411 c 366.164 260.052 l 365.211 260.052 - l 363.938 260.052 363.262 259.606 363.18 258.724 c 363.18 258.583 l 363.18 - 258.259 363.289 257.993 363.508 257.786 c 363.734 257.587 364.043 257.489 - 364.43 257.489 c h -364.43 257.489 m f -375.738 258.349 m 375.738 258.661 375.582 258.903 375.27 259.083 c 374.965 - 259.259 374.477 259.419 373.801 259.567 c 371.539 260.036 370.41 260.993 - 370.41 262.442 c 370.41 263.286 370.758 263.989 371.457 264.552 c 372.152 - 265.122 373.07 265.411 374.207 265.411 c 375.414 265.411 376.379 265.122 - 377.098 264.552 c 377.816 263.989 378.176 263.255 378.176 262.349 c 375.645 - 262.349 l 375.645 262.7 375.523 262.997 375.285 263.239 c 375.055 263.477 - 374.691 263.599 374.191 263.599 c 373.762 263.599 373.43 263.497 373.191 - 263.302 c 372.961 263.114 372.848 262.868 372.848 262.567 c 372.848 262.286 - 372.977 262.056 373.238 261.88 c 373.508 261.712 373.961 261.567 374.598 - 261.442 c 375.23 261.317 375.762 261.177 376.191 261.02 c 377.535 260.52 - 378.207 259.665 378.207 258.458 c 378.207 257.591 377.832 256.888 377.082 - 256.349 c 376.34 255.817 375.383 255.552 374.207 255.552 c 373.414 255.552 - 372.707 255.692 372.082 255.974 c 371.465 256.255 370.98 256.638 370.629 - 257.13 c 370.285 257.63 370.113 258.169 370.113 258.755 c 372.52 258.755 - l 372.539 258.294 372.707 257.946 373.02 257.708 c 373.332 257.466 373.746 - 257.349 374.27 257.349 c 374.746 257.349 375.113 257.442 375.363 257.63 - c 375.613 257.817 375.738 258.056 375.738 258.349 c h -375.738 258.349 m f -382.789 267.567 m 382.789 265.239 l 384.414 265.239 l 384.414 263.364 l - 382.789 263.364 l 382.789 258.63 l 382.789 258.274 382.855 258.02 382.992 - 257.864 c 383.125 257.716 383.387 257.645 383.773 257.645 c 384.055 257.645 - 384.297 257.665 384.508 257.708 c 384.508 255.77 l 384.016 255.626 383.508 - 255.552 382.977 255.552 c 381.195 255.552 380.289 256.446 380.258 258.239 - c 380.258 263.364 l 378.867 263.364 l 378.867 265.239 l 380.258 265.239 - l 380.258 267.567 l h -382.789 267.567 m f -186.227 184.688 m 192.68 184.688 l 192.68 182.563 l 182.93 182.563 l 182.93 - 184.11 l 189.258 193.22 l 182.945 193.22 l 182.945 195.36 l 192.539 195.36 - l 192.539 193.845 l h -186.227 184.688 m f -198.508 189.063 4.953 -2.047 re f -210.066 182.563 m 210.066 195.36 l 214.004 195.36 l 215.129 195.36 216.133 - 195.102 217.02 194.595 c 217.914 194.095 218.605 193.376 219.098 192.438 - c 219.598 191.501 219.848 190.438 219.848 189.251 c 219.848 188.657 l 219.848 - 187.47 219.602 186.411 219.113 185.485 c 218.621 184.556 217.93 183.837 - 217.035 183.329 c 216.148 182.829 215.148 182.571 214.035 182.563 c h -212.707 193.22 m 212.707 184.688 l 213.973 184.688 l 215.004 184.688 215.789 - 185.02 216.332 185.688 c 216.883 186.364 217.164 187.329 217.176 188.579 - c 217.176 189.267 l 217.176 190.567 216.902 191.552 216.363 192.22 c 215.832 - 192.884 215.043 193.22 214.004 193.22 c h -212.707 193.22 m f -221.102 187.407 m 221.102 188.352 221.281 189.192 221.648 189.923 c 222.012 - 190.661 222.531 191.235 223.211 191.642 c 223.898 192.048 224.695 192.251 - 225.602 192.251 c 226.883 192.251 227.93 191.852 228.742 191.063 c 229.555 - 190.282 230.008 189.22 230.102 187.876 c 230.117 187.22 l 230.117 185.759 - 229.703 184.587 228.883 183.704 c 228.07 182.829 226.977 182.392 225.602 - 182.392 c 224.234 182.392 223.141 182.829 222.32 183.704 c 221.508 184.587 - 221.102 185.786 221.102 187.298 c h -223.648 187.22 m 223.648 186.321 223.812 185.634 224.148 185.157 c 224.492 - 184.677 224.98 184.438 225.617 184.438 c 226.23 184.438 226.711 184.673 - 227.055 185.142 c 227.398 185.61 227.57 186.364 227.57 187.407 c 227.57 - 188.29 227.398 188.974 227.055 189.454 c 226.711 189.942 226.227 190.188 - 225.602 190.188 c 224.977 190.188 224.492 189.942 224.148 189.454 c 223.812 - 188.974 223.648 188.227 223.648 187.22 c h -223.648 187.22 m f -239.871 186.017 m 241.121 192.079 l 243.574 192.079 l 241.137 182.563 l - 239.012 182.563 l 237.215 188.548 l 235.418 182.563 l 233.293 182.563 l - 230.871 192.079 l 233.324 192.079 l 234.559 186.032 l 236.293 192.079 l - 238.137 192.079 l h -239.871 186.017 m f -247.027 192.079 m 247.105 190.97 l 247.781 191.821 248.691 192.251 249.84 - 192.251 c 250.848 192.251 251.598 191.954 252.09 191.36 c 252.578 190.767 - 252.828 189.88 252.84 188.704 c 252.84 182.563 l 250.309 182.563 l 250.309 - 188.642 l 250.309 189.181 250.188 189.571 249.949 189.813 c 249.719 190.063 - 249.332 190.188 248.793 190.188 c 248.074 190.188 247.535 189.88 247.184 - 189.267 c 247.184 182.563 l 244.637 182.563 l 244.637 192.079 l h -247.027 192.079 m f -185.309 376.864 m 181.605 376.864 l 181.605 373.849 l 185.902 373.849 l - 185.902 372.927 l 180.48 372.927 l 180.48 381.458 l 185.855 381.458 l 185.855 - 380.536 l 181.605 380.536 l 181.605 377.786 l 185.309 377.786 l h -185.309 376.864 m f -193.582 375.63 m 193.477 374.731 193.145 374.036 192.582 373.536 c 192.02 - 373.044 191.273 372.802 190.348 372.802 c 189.336 372.802 188.527 373.161 - 187.926 373.88 c 187.32 374.606 187.02 375.583 187.02 376.802 c 187.02 -377.614 l 187.02 378.403 187.16 379.102 187.441 379.708 c 187.723 380.31 - 188.121 380.774 188.645 381.099 c 189.164 381.419 189.762 381.583 190.441 - 381.583 c 191.348 381.583 192.07 381.325 192.613 380.817 c 193.164 380.306 - 193.488 379.606 193.582 378.724 c 192.457 378.724 l 192.352 379.399 192.137 - 379.888 191.816 380.192 c 191.492 380.493 191.035 380.645 190.441 380.645 - c 189.723 380.645 189.16 380.38 188.754 379.849 c 188.348 379.317 188.145 - 378.56 188.145 377.583 c 188.145 376.755 l 188.145 375.825 188.336 375.087 - 188.723 374.536 c 189.105 373.993 189.648 373.724 190.348 373.724 c 190.973 - 373.724 191.449 373.864 191.785 374.145 c 192.117 374.435 192.34 374.931 - 192.457 375.63 c h -193.582 375.63 m f -199.934 376.864 m 196.23 376.864 l 196.23 373.849 l 200.527 373.849 l 200.527 - 372.927 l 195.105 372.927 l 195.105 381.458 l 200.48 381.458 l 200.48 380.536 - l 196.23 380.536 l 196.23 377.786 l 199.934 377.786 l h -199.934 376.864 m f -206.645 376.692 m 203.051 376.692 l 203.051 372.927 l 201.926 372.927 l - 201.926 381.458 l 207.223 381.458 l 207.223 380.536 l 203.051 380.536 l - 203.051 377.614 l 206.645 377.614 l h -206.645 376.692 m f -390.539 162.747 m 386.836 162.747 l 386.836 159.731 l 391.133 159.731 l - 391.133 158.81 l 385.711 158.81 l 385.711 167.341 l 391.086 167.341 l 391.086 - 166.419 l 386.836 166.419 l 386.836 163.669 l 390.539 163.669 l h -390.539 162.747 m f -398.812 161.513 m 398.707 160.614 398.375 159.919 397.812 159.419 c 397.25 - 158.927 396.504 158.685 395.578 158.685 c 394.566 158.685 393.758 159.044 - 393.156 159.763 c 392.551 160.489 392.25 161.466 392.25 162.685 c 392.25 - 163.497 l 392.25 164.286 392.391 164.985 392.672 165.591 c 392.953 166.192 - 393.352 166.657 393.875 166.981 c 394.395 167.302 394.992 167.466 395.672 - 167.466 c 396.578 167.466 397.301 167.208 397.844 166.7 c 398.395 166.188 - 398.719 165.489 398.812 164.606 c 397.688 164.606 l 397.582 165.282 397.367 - 165.77 397.047 166.075 c 396.723 166.376 396.266 166.528 395.672 166.528 - c 394.953 166.528 394.391 166.263 393.984 165.731 c 393.578 165.2 393.375 - 164.442 393.375 163.466 c 393.375 162.638 l 393.375 161.708 393.566 160.97 - 393.953 160.419 c 394.336 159.876 394.879 159.606 395.578 159.606 c 396.203 - 159.606 396.68 159.747 397.016 160.028 c 397.348 160.317 397.57 160.813 - 397.688 161.513 c h -398.812 161.513 m f -405.164 162.747 m 401.461 162.747 l 401.461 159.731 l 405.758 159.731 l - 405.758 158.81 l 400.336 158.81 l 400.336 167.341 l 405.711 167.341 l 405.711 - 166.419 l 401.461 166.419 l 401.461 163.669 l 405.164 163.669 l h -405.164 162.747 m f -411.875 162.575 m 408.281 162.575 l 408.281 158.81 l 407.156 158.81 l 407.156 - 167.341 l 412.453 167.341 l 412.453 166.419 l 408.281 166.419 l 408.281 - 163.497 l 411.875 163.497 l h -411.875 162.575 m f -144.387 113.485 m 140.684 113.485 l 140.684 110.47 l 144.98 110.47 l 144.98 - 109.548 l 139.559 109.548 l 139.559 118.079 l 144.934 118.079 l 144.934 - 117.157 l 140.684 117.157 l 140.684 114.407 l 144.387 114.407 l h -144.387 113.485 m f -152.66 112.251 m 152.555 111.352 152.223 110.657 151.66 110.157 c 151.098 - 109.665 150.352 109.423 149.426 109.423 c 148.414 109.423 147.605 109.782 - 147.004 110.501 c 146.398 111.227 146.098 112.204 146.098 113.423 c 146.098 - 114.235 l 146.098 115.024 146.238 115.724 146.52 116.329 c 146.801 116.931 - 147.199 117.395 147.723 117.72 c 148.242 118.04 148.84 118.204 149.52 118.204 - c 150.426 118.204 151.148 117.946 151.691 117.438 c 152.242 116.927 152.566 - 116.227 152.66 115.345 c 151.535 115.345 l 151.43 116.02 151.215 116.509 - 150.895 116.813 c 150.57 117.114 150.113 117.267 149.52 117.267 c 148.801 - 117.267 148.238 117.001 147.832 116.47 c 147.426 115.938 147.223 115.181 - 147.223 114.204 c 147.223 113.376 l 147.223 112.446 147.414 111.708 147.801 - 111.157 c 148.184 110.614 148.727 110.345 149.426 110.345 c 150.051 110.345 - 150.527 110.485 150.863 110.767 c 151.195 111.056 151.418 111.552 151.535 - 112.251 c h -152.66 112.251 m f -159.012 113.485 m 155.309 113.485 l 155.309 110.47 l 159.605 110.47 l 159.605 - 109.548 l 154.184 109.548 l 154.184 118.079 l 159.559 118.079 l 159.559 - 117.157 l 155.309 117.157 l 155.309 114.407 l 159.012 114.407 l h -159.012 113.485 m f -165.723 113.313 m 162.129 113.313 l 162.129 109.548 l 161.004 109.548 l - 161.004 118.079 l 166.301 118.079 l 166.301 117.157 l 162.129 117.157 l - 162.129 114.235 l 165.723 114.235 l h -165.723 113.313 m f -138.418 214.509 m 126.984 215.118 l 127.211 219.435 l 127.254 220.192 127.32 - 220.77 127.414 221.169 c 127.539 221.724 127.742 222.185 128.023 222.552 - c 128.305 222.915 128.691 223.204 129.176 223.407 c 129.66 223.614 130.188 - 223.704 130.754 223.673 c 131.727 223.622 132.535 223.267 133.176 222.614 - c 133.816 221.958 134.094 220.821 134.008 219.204 c 133.852 216.27 l 138.5 - 216.024 l h -132.5 216.345 m 132.656 219.302 l 132.711 220.278 132.566 220.981 132.223 - 221.411 c 131.879 221.841 131.379 222.075 130.719 222.11 c 130.242 222.138 - 129.824 222.036 129.473 221.813 c 129.117 221.591 128.875 221.282 128.746 - 220.892 c 128.664 220.642 128.605 220.173 128.57 219.485 c 128.414 216.56 - l h -132.5 216.345 m f -139.031 224.985 m 130.742 225.36 l 130.801 226.626 l 132.059 226.567 l -131.484 226.919 131.109 227.235 130.934 227.517 c 130.762 227.798 130.68 - 228.102 130.695 228.431 c 130.715 228.903 130.891 229.376 131.215 229.852 - c 132.496 229.31 l 132.277 228.974 132.16 228.638 132.145 228.294 c 132.129 - 227.985 132.211 227.708 132.383 227.454 c 132.555 227.2 132.805 227.013 - 133.129 226.895 c 133.621 226.716 134.164 226.614 134.758 226.587 c 139.094 - 226.388 l h -139.031 224.985 m f -129.461 230.946 m 127.848 231.044 l 127.934 232.45 l 129.547 232.349 l -h -139.277 230.345 m 130.996 230.852 l 131.082 232.255 l 139.363 231.751 l - h -139.277 230.345 m f -139.453 233.895 m 131.18 234.524 l 131.277 235.778 l 132.438 235.692 l -132.051 235.981 131.75 236.352 131.539 236.802 c 131.324 237.251 131.238 - 237.751 131.277 238.302 c 131.324 238.915 131.492 239.407 131.777 239.782 - c 132.059 240.153 132.438 240.403 132.906 240.528 c 131.988 241.259 131.57 - 242.145 131.652 243.196 c 131.715 244.017 131.988 244.63 132.477 245.036 - c 132.965 245.442 133.68 245.61 134.625 245.54 c 140.305 245.106 l 140.199 - 243.712 l 134.988 244.11 l 134.426 244.149 134.02 244.138 133.766 244.063 - c 133.512 243.993 133.301 243.841 133.133 243.614 c 132.965 243.388 132.867 - 243.114 132.844 242.79 c 132.797 242.208 132.953 241.712 133.312 241.298 - c 133.672 240.884 134.273 240.645 135.125 240.579 c 139.934 240.216 l 139.828 - 238.813 l 134.453 239.22 l 133.828 239.267 133.352 239.188 133.023 238.985 - c 132.695 238.778 132.512 238.419 132.473 237.899 c 132.441 237.505 132.516 - 237.13 132.699 236.782 c 132.883 236.431 133.168 236.165 133.555 235.981 - c 133.945 235.802 134.516 235.685 135.266 235.626 c 139.559 235.298 l h -139.453 233.895 m f -138.414 253.099 m 138.727 254.528 l 139.551 254.22 140.168 253.739 140.578 - 253.079 c 140.984 252.415 141.145 251.606 141.059 250.645 c 140.945 249.438 - 140.484 248.517 139.672 247.876 c 138.863 247.239 137.785 246.981 136.445 - 247.106 c 135.055 247.235 134.008 247.692 133.309 248.481 c 132.605 249.267 - 132.309 250.231 132.414 251.372 c 132.52 252.477 132.977 253.345 133.793 - 253.974 c 134.613 254.606 135.703 254.856 137.066 254.731 c 137.148 254.724 - 137.273 254.708 137.438 254.688 c 136.867 248.528 l 137.777 248.493 138.496 - 248.685 139.023 249.102 c 139.547 249.52 139.844 250.071 139.906 250.763 - c 139.953 251.274 139.859 251.727 139.625 252.114 c 139.387 252.501 138.984 - 252.829 138.414 253.099 c h -135.723 248.712 m 136.152 253.325 l 135.449 253.325 134.914 253.2 134.539 - 252.942 c 133.957 252.544 133.633 251.993 133.566 251.282 c 133.508 250.638 - 133.672 250.079 134.062 249.599 c 134.453 249.122 135.004 248.825 135.723 - 248.712 c h -135.723 248.712 m f -141.734 260.622 m 130.348 261.88 l 130.598 264.145 l 138.957 265.95 l 139.734 - 266.114 140.316 266.235 140.703 266.306 c 140.305 266.481 139.719 266.751 - 138.945 267.114 c 131.324 270.716 l 131.551 272.743 l 142.934 271.485 l - 142.773 270.032 l 133.246 271.083 l 142.406 266.724 l 142.258 265.364 l - 132.203 263.145 l 141.895 262.071 l h -141.734 260.622 m f -141.383 279.724 m 141.75 281.142 l 142.562 280.802 143.16 280.294 143.543 - 279.618 c 143.926 278.938 144.051 278.126 143.926 277.169 c 143.766 275.966 - 143.266 275.063 142.434 274.458 c 141.598 273.852 140.512 273.638 139.176 - 273.817 c 137.789 274.001 136.766 274.501 136.094 275.313 c 135.426 276.13 - 135.168 277.102 135.32 278.239 c 135.465 279.341 135.961 280.188 136.801 - 280.786 c 137.645 281.38 138.742 281.591 140.102 281.407 c 140.184 281.395 - 140.309 281.38 140.473 281.352 c 139.652 275.216 l 140.562 275.149 141.289 - 275.313 141.832 275.708 c 142.371 276.102 142.688 276.645 142.781 277.329 - c 142.848 277.841 142.773 278.294 142.551 278.692 c 142.332 279.091 141.941 - 279.435 141.383 279.724 c h -138.516 275.446 m 139.129 280.04 l 138.43 280.071 137.887 279.966 137.504 - 279.72 c 136.906 279.349 136.562 278.81 136.469 278.102 c 136.383 277.462 - 136.523 276.895 136.895 276.403 c 137.266 275.907 137.805 275.591 138.516 - 275.446 c h -138.516 275.446 m f -144.508 282.544 m 136.305 283.79 l 136.496 285.04 l 137.738 284.852 l 137.203 - 285.259 136.867 285.61 136.723 285.911 c 136.578 286.208 136.527 286.52 - 136.578 286.845 c 136.648 287.313 136.871 287.767 137.242 288.204 c 138.461 - 287.532 l 138.207 287.22 138.055 286.895 138.004 286.556 c 137.957 286.255 - 138.008 285.966 138.156 285.696 c 138.301 285.427 138.527 285.216 138.836 - 285.063 c 139.309 284.833 139.836 284.677 140.422 284.587 c 144.719 283.935 - l h -144.508 282.544 m f -135.547 289.294 m 133.945 289.532 l 134.152 290.923 l 135.75 290.688 l -h -145.277 287.856 m 137.07 289.071 l 137.273 290.462 l 145.48 289.247 l h -145.277 287.856 m f -146.77 296.669 m 145.738 296.852 l 146.457 296.188 146.727 295.356 146.547 - 294.352 c 146.43 293.7 146.145 293.134 145.688 292.649 c 145.234 292.169 - 144.656 291.833 143.961 291.649 c 143.262 291.462 142.496 291.446 141.66 - 291.595 c 140.844 291.739 140.129 292.009 139.516 292.395 c 138.898 292.786 - 138.461 293.286 138.203 293.892 c 137.949 294.497 137.879 295.138 138 295.81 - c 138.086 296.302 138.27 296.72 138.543 297.067 c 138.82 297.415 139.145 - 297.681 139.523 297.864 c 135.477 298.583 l 135.723 299.958 l 146.996 297.954 - l h -141.914 293.017 m 142.961 292.829 143.781 292.911 144.379 293.263 c 144.973 - 293.61 145.324 294.083 145.434 294.685 c 145.539 295.29 145.383 295.849 - 144.965 296.36 c 144.543 296.868 143.828 297.216 142.812 297.395 c 141.695 - 297.595 140.836 297.524 140.234 297.188 c 139.637 296.852 139.281 296.368 - 139.168 295.735 c 139.059 295.122 139.219 294.563 139.648 294.06 c 140.074 - 293.556 140.832 293.208 141.914 293.017 c h -141.914 293.017 m f -137.746 302.032 m 136.16 302.345 l 136.43 303.724 l 138.016 303.415 l h -147.398 300.142 m 139.258 301.739 l 139.527 303.118 l 147.668 301.524 l - h -147.398 300.142 m f -148.273 309.134 m 148.594 308.528 148.793 307.974 148.867 307.462 c 148.941 - 306.954 148.918 306.427 148.801 305.888 c 148.605 304.997 148.238 304.36 - 147.699 303.981 c 147.156 303.599 146.551 303.481 145.871 303.63 c 145.477 - 303.716 145.133 303.884 144.844 304.138 c 144.555 304.392 144.344 304.685 - 144.211 305.02 c 144.078 305.356 144 305.72 143.977 306.106 c 143.965 306.392 - 143.988 306.817 144.039 307.376 c 144.152 308.513 144.172 309.364 144.105 - 309.927 c 143.918 309.974 143.797 310.001 143.746 310.013 c 143.188 310.138 - 142.766 310.095 142.48 309.884 c 142.094 309.602 141.824 309.114 141.672 - 308.423 c 141.527 307.778 141.535 307.274 141.695 306.919 c 141.855 306.56 - 142.207 306.243 142.746 305.97 c 142.27 304.669 l 141.723 304.915 141.301 - 305.22 141.008 305.575 c 140.719 305.935 140.535 306.399 140.461 306.97 - c 140.383 307.54 140.426 308.173 140.578 308.872 c 140.73 309.563 140.934 - 310.106 141.191 310.505 c 141.449 310.899 141.723 311.173 142.016 311.321 - c 142.305 311.47 142.648 311.544 143.043 311.544 c 143.289 311.54 143.715 - 311.47 144.324 311.333 c 146.156 310.935 l 147.434 310.653 148.246 310.505 - 148.598 310.489 c 148.949 310.474 149.301 310.517 149.648 310.622 c 149.332 - 309.188 l 149.016 309.106 148.664 309.091 148.273 309.134 c h -145.18 309.692 m 145.273 309.149 145.281 308.364 145.207 307.333 c 145.164 - 306.751 145.168 306.333 145.219 306.075 c 145.27 305.821 145.379 305.606 - 145.547 305.435 c 145.715 305.267 145.918 305.153 146.156 305.102 c 146.523 - 305.02 146.859 305.095 147.164 305.317 c 147.469 305.54 147.68 305.919 -147.797 306.454 c 147.914 306.981 147.902 307.477 147.758 307.942 c 147.617 - 308.403 147.367 308.778 147.008 309.06 c 146.73 309.274 146.289 309.446 - 145.684 309.579 c h -145.18 309.692 m f -150.031 312.321 m 142.02 314.481 l 142.348 315.704 l 143.488 315.395 l -142.766 316.224 142.555 317.192 142.855 318.302 c 142.984 318.786 143.191 - 319.204 143.473 319.563 c 143.758 319.923 144.066 320.165 144.402 320.29 - c 144.738 320.415 145.109 320.466 145.52 320.442 c 145.785 320.423 146.23 - 320.329 146.859 320.161 c 151.785 318.833 l 151.418 317.474 l 146.547 318.79 - l 145.992 318.938 145.566 318.997 145.262 318.966 c 144.961 318.935 144.691 - 318.806 144.453 318.579 c 144.219 318.352 144.051 318.06 143.953 317.7 -c 143.797 317.122 143.848 316.571 144.102 316.052 c 144.355 315.532 144.996 - 315.138 146.02 314.86 c 150.395 313.681 l h -150.031 312.321 m f -0 0.721569 0 rg -1.6 w -1 J -1 j -[ 1.6 4.8] 0 d -10 M q 1 0 0 -1 0 390.770447 cm -246.57 141.48 m 228.656 160.035 l S Q -Q Q -showpage -%%Trailer -end restore -%%EOF diff --git a/specification/application/NED_ECEF.svg b/specification/application/NED_ECEF.svg deleted file mode 100644 index c824e583..00000000 --- a/specification/application/NED_ECEF.svg +++ /dev/null @@ -1,362 +0,0 @@ - - - -image/svg+xmlZ -Y -X -X - North -Y - East -Z - Down -ECEF -ECEF -ECEF - \ No newline at end of file diff --git a/specification/application/aircraft_principal_axes.eps b/specification/application/aircraft_principal_axes.eps deleted file mode 100644 index f37ce3e9..00000000 --- a/specification/application/aircraft_principal_axes.eps +++ /dev/null @@ -1,838 +0,0 @@ -%!PS-Adobe-3.0 EPSF-3.0 -%%Creator: cairo 1.14.6 (http://cairographics.org) -%%CreationDate: Wed Dec 12 18:59:30 2018 -%%Pages: 1 -%%DocumentData: Clean7Bit -%%LanguageLevel: 2 -%%BoundingBox: 80 79 1469 1173 -%%EndComments -%%BeginProlog -save -50 dict begin -/q { gsave } bind def -/Q { grestore } bind def -/cm { 6 array astore concat } bind def -/w { setlinewidth } bind def -/J { setlinecap } bind def -/j { setlinejoin } bind def -/M { setmiterlimit } bind def -/d { setdash } bind def -/m { moveto } bind def -/l { lineto } bind def -/c { curveto } bind def -/h { closepath } bind def -/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto - 0 exch rlineto 0 rlineto closepath } bind def -/S { stroke } bind def -/f { fill } bind def -/f* { eofill } bind def -/n { newpath } bind def -/W { clip } bind def -/W* { eoclip } bind def -/BT { } bind def -/ET { } bind def -/pdfmark where { pop globaldict /?pdfmark /exec load put } - { globaldict begin /?pdfmark /pop load def /pdfmark - /cleartomark load def end } ifelse -/BDC { mark 3 1 roll /BDC pdfmark } bind def -/EMC { mark /EMC pdfmark } bind def -/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def -/Tj { show currentpoint cairo_store_point } bind def -/TJ { - { - dup - type /stringtype eq - { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse - } forall - currentpoint cairo_store_point -} bind def -/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore - cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def -/Tf { pop /cairo_font exch def /cairo_font_matrix where - { pop cairo_selectfont } if } bind def -/Td { matrix translate cairo_font_matrix matrix concatmatrix dup - /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point - /cairo_font where { pop cairo_selectfont } if } bind def -/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def - cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def -/g { setgray } bind def -/rg { setrgbcolor } bind def -/d1 { setcachedevice } bind def -%%EndProlog -%%BeginSetup -%%EndSetup -%%Page: 1 1 -%%BeginPageSetup -%%PageBoundingBox: 80 79 1469 1173 -%%EndPageSetup -q 80 79 1389 1094 rectclip q -1 g -594.77 703.146 m 512.316 804.681 485.828 837.681 486.355 838.205 c 486.582 - 838.435 493.59 835.611 501.926 831.927 c 512.168 827.408 517.742 824.361 - 519.117 822.529 c 520.238 821.041 543.926 790.626 571.758 754.939 c 627.383 - 683.615 624.848 687.642 617.305 682.65 c 613.473 680.115 l h -594.77 703.146 m f -0 g -927.996 499.888 m 944.832 485.509 959.504 497.212 967.078 516.255 c 957.859 - 510.001 939.52 500.623 927.996 499.888 c h -927.996 499.888 m f* -2.4 w -0 J -0 j -[] 0.0 d -4 M q 1 0 0 -1 0 1251.427246 cm -927.996 751.539 m 944.832 765.918 959.504 754.215 967.078 735.172 c 957.859 - 741.426 939.52 750.805 927.996 751.539 c h -927.996 751.539 m S Q -645.371 444.689 m 663.41 431.849 676.992 444.802 682.859 464.435 c 674.227 - 457.392 656.789 446.435 645.371 444.689 c h -645.371 444.689 m f* -q 1 0 0 -1 0 1251.427246 cm -645.371 806.738 m 663.41 819.578 676.992 806.625 682.859 786.992 c 674.227 - 794.035 656.789 804.992 645.371 806.738 c h -645.371 806.738 m S Q -q 1 0 0 -1 0 1251.427246 cm -836.164 403.883 m 912.734 407.309 l 1052.164 352.453 l 1052.164 352.453 - 1140.188 296.98 1160.734 280.453 c 1171.746 265.039 1235.59 111.309 1235.59 - 111.309 c 1261.113 89.402 1282.637 87.5 1307.02 81.594 c 1273.879 278.164 - l 1282.828 282.543 1278.641 290.352 1265.305 298.734 c 1304.734 287.305 - l 1304.734 287.305 1423.391 340.332 1422.445 351.305 c 1415.609 365.727 - 1384.051 386.445 1357.305 391.875 c 1323.879 388.75 1228.738 355.02 1185.871 - 343.305 c S Q -q 1 0 0 -1 0 1251.427246 cm -1209.281 351.305 m 1048.051 502.926 l S Q -q 1 0 0 -1 0 1251.427246 cm -937.738 452.145 m 1465.953 695.305 l 1465.953 695.305 1389.43 756.547 1347.02 - 758.16 c 1340.164 758.422 1022.449 653.02 1022.449 653.02 c 1022.449 653.02 - 795.562 549.922 776.16 541.586 c S Q -q 1 0 0 -1 0 1251.427246 cm -970.449 630.168 m 812.164 686.168 l 785.875 701.027 l S Q -q 1 0 0 -1 0 1251.427246 cm -786.531 701.555 m 741.914 721.258 694.508 740.02 643.02 750.836 c 627.352 - 751.285 613.305 753.754 598.445 750.164 c 580.402 745.375 567.008 738.363 - 551.387 727.512 c S Q -q 1 0 0 -1 0 1251.427246 cm -530.227 699.105 m 521.441 685.887 517.906 662.973 529.324 647.328 c 557.133 - 616.211 584.945 592.363 622.453 567.305 c 631.074 536.219 637.273 514.02 - 655.598 486.16 c 444.973 392.305 l 223.812 265.809 l 223.812 265.809 203.969 - 256.07 211.691 236.766 c 224.086 205.766 303.93 188.348 303.93 188.348 -c 484.484 254.535 656.953 323.141 840.738 403.879 c S Q -q 1 0 0 -1 0 1251.427246 cm -1112.004 312.059 m 1052.828 272.254 l 1045.449 267.254 1037.543 265.016 - 1036.633 255.168 c 1062.156 230.047 1081.215 229.98 1108.352 222.637 c -1176.336 244.211 l S Q -q 1 0 0 -1 0 1251.427246 cm -480.516 409.098 m 482.801 413.668 611.699 571.824 611.699 571.824 c S Q -q 1 0 0 -1 0 1251.427246 cm -519.984 427.328 m 623.281 559.809 l S Q -q 1 0 0 -1 0 1251.427246 cm -955.02 623.883 m 812.164 675.883 l S Q -q 1 0 0 -1 0 1251.427246 cm -1185.844 342.738 m 1186.984 327.879 1265.27 298.738 1265.27 298.738 c S Q -q 1 0 0 -1 0 1251.427246 cm -1244.723 306.738 m 1403.012 369.023 l S Q -q 1 0 0 -1 0 1251.427246 cm -1278.48 105.023 m 1277.34 109.023 1227.625 297.594 1227.625 297.594 c S Q -q 1 0 0 -1 0 1251.427246 cm -1233.281 117.594 m 1249.281 125.023 1305.852 87.879 1305.852 87.879 c S Q -q 1 0 0 -1 0 1251.427246 cm -1052.164 353.023 m 1049.305 353.023 981.875 399.312 981.875 399.312 c 975.574 - 403.047 976.312 409.125 985.875 405.598 c 1272.164 279.309 l S Q -q 1 0 0 -1 0 1251.427246 cm -1067.602 233.594 m 1071.031 235.879 1163.035 277.023 1163.035 277.023 c - S Q -1.6 w -q 1 0 0 -1 0 1251.427246 cm -775.922 541.16 m 769.711 534.492 777.863 518.207 784.477 511.164 c 807.777 - 520.105 1018.828 618.645 1018.828 618.645 c 1347.734 735.012 l S Q -q 1 0 0 -1 0 1251.427246 cm -1336.402 748.746 m 1332.363 727.734 1434.188 687.328 1434.188 687.328 c - S Q -q 1 0 0 -1 0 1251.427246 cm -1415.602 693.793 m 1131.145 560.449 l S Q -q 1 0 0 -1 0 1251.427246 cm -1149.762 549.137 m 1149.762 549.137 1119.055 568.531 1116.629 565.301 c - 1114.207 562.066 896.012 465.09 896.012 465.09 c 896.012 465.09 920.32 -459.906 938.035 452.16 c 944.664 445.086 935.809 442.031 924.297 434.383 - c 913.258 427.047 840.25 403.676 840.25 403.676 c S Q -q 1 0 0 -1 0 1251.427246 cm -216.367 251.754 m 213.133 233.164 309.301 198.418 309.301 198.418 c S Q -q 1 0 0 -1 0 1251.427246 cm -223.637 237.211 m 468.504 372.977 l 468.504 372.977 633.359 438.84 681.848 - 464.297 c 672.617 468.609 667.324 471.535 655.594 485.66 c S Q -2.4 w -q 1 0 0 -1 0 1251.427246 cm -773.16 417.418 m 741.312 426.047 707.172 442.672 677.609 461.016 c S Q -1.6 w -q 1 0 0 -1 0 1251.427246 cm -627.699 313.168 m 623.656 313.977 580.02 324.484 580.02 324.484 c 580.02 - 324.484 782.859 417.418 786.09 414.996 c 789.324 412.57 840.035 403.68 -840.035 403.68 c S Q -q 1 0 0 -1 0 1251.427246 cm -592.137 320.441 m 301.219 202.457 l S Q -q 1 0 0 -1 0 1251.427246 cm -730.051 669.016 m 699.742 689.898 626.074 727.008 600.457 714.941 c S Q -2.4 w -q 1 0 0 -1 0 1251.427246 cm -523.73 657.527 m 566.832 637.172 668.281 731.535 597.727 750.426 c S Q -1.6 w -q 1 0 0 -1 0 1251.427246 cm -614.18 573.953 m 667.23 569.871 733.449 614.828 731.137 659.848 c 729.539 - 691.488 725.324 717.812 717.246 728.32 c S Q -q 1 0 0 -1 0 1251.427246 cm -797.402 664.707 m 797.402 660.664 799.492 553.938 799.492 553.938 c S Q -2.4 w -q 1 0 0 -1 0 1251.427246 cm -786.801 700.176 m 782.461 689.77 798.746 674.023 811.91 676.348 c S Q -q 1 0 0 -1 0 1251.427246 cm -885.648 469.512 m 853.801 478.141 819.66 494.77 790.098 513.109 c S Q -q 1 0 0 -1 0 1251.427246 cm -674.457 467.863 m 779.922 516.09 l S Q -0.8 w -q 1 0 0 -1 0 1251.427246 cm -678.035 484.281 m 768.891 525.438 l S Q -2.4 w -q 1 0 0 -1 0 1251.427246 cm -622.488 566.297 m 663.898 560.809 716.625 600.195 732.465 609.211 c 750.543 - 610.797 767.824 612.461 788.395 599.891 c 789.773 580.652 789.52 580.676 - 790.105 548.062 c S Q -0.8 w -q 1 0 0 -1 0 1251.427246 cm -634.676 558.777 m 649.617 532.613 669.207 542.988 705.973 553.809 c 743.168 - 564.754 764.168 589.25 760.734 602.961 c S Q -q 1 0 0 -1 0 1251.427246 cm -718.969 532.695 m 684.805 546.879 l S Q -q 1 0 0 -1 0 1251.427246 cm -672.867 494.77 m 671.047 542.855 l S Q -q 1 0 0 -1 0 1251.427246 cm -661.371 519.387 m 661.035 541.914 l S Q -0.784314 g -715.012 792.826 m 725.285 797.716 744.809 804.486 757.285 809.427 c 762.852 - 811.529 786.219 810.455 775.582 801.38 c 760.273 795.197 744.004 785.791 - 725.758 779.923 c 696.102 780.763 710.969 792.552 715.012 792.826 c h -715.012 792.826 m f* -0 g -1.6 w -q 1 0 0 -1 0 1251.427246 cm -715.012 458.602 m 725.285 453.711 744.809 446.941 757.285 442 c 762.852 - 439.898 786.219 440.973 775.582 450.047 c 760.273 456.23 744.004 465.637 - 725.758 471.504 c 696.102 470.664 710.969 458.875 715.012 458.602 c h -715.012 458.602 m S Q -2.4 w -q 1 0 0 -1 0 1251.427246 cm -926.875 694.305 m 912.305 701.164 898.875 742.305 915.305 750.875 c 934.289 - 754.918 977.34 733.043 991.16 713.59 c 1002.73 697.305 1004.875 676.59 -991.16 676.16 c 976.59 680.449 961.715 684.402 948.73 684.16 c 940.246 684.004 - 934.016 689.59 926.875 694.305 c h -926.875 694.305 m S Q -q 1 0 0 -1 0 1251.427246 cm -647.945 750.625 m 625.285 759.953 612.516 806.105 635.566 807.785 c 665.277 - 806.637 687.953 781.488 702.254 770.465 c 723.309 754.234 712.527 731.398 - 712.527 731.398 c S Q -q 1 0 0 -1 0 1251.427246 cm -921.809 608.984 m 915.234 618.348 901.129 624.125 895.469 630.711 c 898.895 - 638.711 901.168 642.406 901.168 642.406 c S Q -q 1 0 0 -1 0 1251.427246 cm -867.984 647.648 m 868.113 633.266 890.383 629.566 896.906 628.434 c S Q -q 1 0 0 -1 0 1251.427246 cm -715.121 746.801 m 689.461 771.723 649.531 802.973 623.914 794.949 c S Q -q 1 0 0 -1 0 1251.427246 cm -998.961 697.16 m 974.512 717.438 920.035 746.262 907.953 732.379 c S Q -q 1 0 0 -1 0 1251.427246 cm -884.684 660.457 m 885.691 662.074 918.219 702.074 918.219 702.074 c S Q -q 1 0 0 -1 0 1251.427246 cm -907.914 652.379 m 908.926 655.004 931.348 690.762 931.348 690.762 c S Q -0.784314 g -764.754 769.275 m 775.027 774.162 792.938 784.166 805.41 789.107 c 810.98 - 791.208 835.562 789.728 824.922 780.658 c 809.613 774.474 793.746 762.24 - 775.5 756.373 c 745.844 757.212 760.715 769.001 764.754 769.275 c h -764.754 769.275 m f* -0 g -1.6 w -q 1 0 0 -1 0 1251.427246 cm -764.754 482.152 m 775.027 477.266 792.938 467.262 805.41 462.32 c 810.98 - 460.219 835.562 461.699 824.922 470.77 c 809.613 476.953 793.746 489.188 - 775.5 495.055 c 745.844 494.215 760.715 482.426 764.754 482.152 c h -764.754 482.152 m S Q -2.4 w -q 1 0 0 -1 0 1251.427246 cm -855.074 660.168 m 865.82 647.508 873.977 643.684 879.953 651.461 c S Q -q 1 0 0 -1 0 1251.427246 cm -523.145 723.48 m 542.133 727.523 570.188 731.039 579.809 719.531 c 590.617 - 706.605 569.887 676.066 556.172 675.637 c 543.48 678.457 524.301 703.043 - 523.145 723.48 c h -523.145 723.48 m S Q -q 1 0 0 -1 0 1251.427246 cm -575.457 724.043 m 612.688 737.777 606.707 711.227 578.086 695.152 c S Q -q 1 0 0 -1 0 1251.427246 cm -536.18 689.176 m 530.059 681.5 529.777 665.453 550.523 677.461 c S Q -0.784314 g -845.449 846.689 m 851.879 846.548 912.164 844.263 912.164 844.263 c 943.449 - 832.689 l 885.594 832.404 l h -845.449 846.689 m f* -0 g -1.6 w -q 1 0 0 -1 0 1251.427246 cm -845.449 404.738 m 851.879 404.879 912.164 407.164 912.164 407.164 c 943.449 - 418.738 l 885.594 419.023 l h -845.449 404.738 m S Q -0.784314 g -902.875 826.978 m 903.73 826.833 947.906 830.22 953.305 827.693 c 998.16 - 806.693 985.16 793.408 979.016 780.693 c 971.305 782.833 938.305 799.408 - 938.305 799.408 c 938.305 799.408 951.445 803.833 919.16 819.693 c 908.59 - 824.904 906.871 824.037 902.875 826.978 c h -902.875 826.978 m f* -0 g -q 1 0 0 -1 0 1251.427246 cm -902.875 424.449 m 903.73 424.594 947.906 421.207 953.305 423.734 c 998.16 - 444.734 985.16 458.02 979.016 470.734 c 971.305 468.594 938.305 452.02 -938.305 452.02 c 938.305 452.02 951.445 447.594 919.16 431.734 c 908.59 -426.523 906.871 427.391 902.875 424.449 c h -902.875 424.449 m S Q -16 w -q 1 0 0 -1 0 1251.427246 cm -232.844 331.84 m 813.52 592.176 l 813.52 1036.586 l S Q -199.148 934.697 m 262.754 941.248 l 236.57 882.849 l h -199.148 934.697 m f* -7.29991 w -q -1 0.448336 0.448336 1 0 1251.427246 cm --284.054 -189.379 m -334.569 -160.18 l -334.567 -218.579 l h --284.054 -189.379 m S Q -813.52 177.916 m 781.52 233.275 l 845.52 233.275 l h -813.52 177.916 m f* -8 w -q 0 -1 -1 0 0 1251.427246 cm -1073.512 -813.52 m 1018.152 -781.52 l 1018.152 -845.52 l h -1073.512 -813.52 m S Q -16 w -q 1 0 0 -1 0 1251.427246 cm -203.352 880.992 m 813.52 592.176 l S Q -169.973 354.638 m 206.32 407.244 l 233.703 349.4 l h -169.973 354.638 m f* -7.230884 w -q -1 -0.473333 -0.473333 1 0 1251.427246 cm -207.923 -798.372 m 157.886 -769.451 l 157.884 -827.296 l h -207.923 -798.372 m S Q -1 g -620.328 540.048 m 617.098 549.837 601.953 567.537 587.676 578.212 c 578.172 - 585.318 564.32 592.548 554.203 595.685 c 548.52 597.443 544.176 598.044 - 537.168 598.033 c 531.996 598.025 527.762 598.333 527.762 598.72 c 527.762 - 601.458 558.312 632.31 575.07 646.498 c 584.52 654.498 608.711 672.978 -612.539 675.119 c 613.852 675.853 618.344 676.267 623.906 676.162 c 664.137 - 675.412 710.176 647.33 725.086 614.447 c 728.684 606.509 730.723 594.873 - 729.703 588.087 c 729.012 583.466 728.66 583.037 722.184 578.912 c 695.531 - 561.931 659.039 545.099 635.793 539.06 c 622.82 535.689 621.746 535.759 - 620.328 540.048 c h -620.328 540.048 m f -619.059 542.541 m 611.426 558.951 590.426 578.865 569.754 589.294 c 557.59 - 595.435 548.562 597.88 537.676 597.986 c 532.223 598.037 527.762 598.369 - 527.762 598.724 c 527.762 600.087 543.227 616.896 554.309 627.58 c 567.465 - 640.259 579.367 650.451 594.883 662.326 c 613.578 676.63 613.145 676.423 - 623.832 676.193 c 664.285 675.314 709.547 647.806 725.047 614.685 c 728.809 - 606.646 730.922 593.83 729.551 587.341 c 728.645 583.044 728.172 582.58 - 718.531 576.564 c 695.09 561.935 659.855 545.771 638.09 539.658 c 622.598 - 535.306 622.41 535.333 619.059 542.541 c h -619.059 542.541 m f -607.086 501.677 m 607.086 502.228 608.203 503.189 609.57 503.81 c 614.102 - 505.876 620.16 512.22 621.867 516.685 c 622.969 519.572 623.426 523.212 - 623.195 527.255 c 622.848 533.427 l 630.473 535.107 l 642.508 537.763 657.164 - 542.876 673.875 550.244 c 689.094 556.955 715.562 571.064 724.621 577.294 - c 729.262 580.486 l 728.57 573.017 l 726.715 552.888 722.031 532.81 717.688 - 526.365 c 715.602 523.271 713.832 522.443 698.609 517.439 c 689.379 514.408 - 673.137 509.724 662.508 507.033 c 644.332 502.427 642.113 502.095 625.137 - 501.408 c 613.5 500.939 607.086 501.033 607.086 501.677 c h -607.086 501.677 m f -604.129 536.654 m 602.785 537.193 599.039 540.517 595.809 544.041 c 592.578 - 547.568 587.652 552.017 584.863 553.931 c 582.07 555.845 578.973 558.783 - 577.98 560.462 c 574.984 565.513 567.223 573.236 562.949 575.416 c 560.059 - 576.888 557.785 577.306 554.816 576.908 c 552.117 576.548 548.66 577.041 - 544.809 578.341 c 541.379 579.494 537.855 580.056 536.367 579.681 c 530.852 - 578.298 529.254 571.576 532.797 564.63 c 534.629 561.033 534.656 560.556 - 533.164 558.275 c 532.273 556.919 531.074 555.966 530.5 556.158 c 529.922 - 556.353 528.32 559.458 526.941 563.06 c 525.004 568.13 524.41 571.689 524.32 - 578.767 c 524.172 590.38 524.914 593.087 528.484 593.982 c 533.215 595.169 - 545.27 594.654 551.66 592.99 c 569.645 588.306 592.84 572.587 606.211 556.025 - c 611.223 549.818 618.27 538.162 618.27 536.083 c 618.27 535.158 606.699 - 535.626 604.129 536.654 c h -604.129 536.654 m f -583.695 526.576 m 581.738 527.134 580.035 527.689 579.914 527.806 c 579.797 - 527.923 580.824 530.248 582.203 532.97 c 584.93 538.353 585.254 541.302 - 583.711 546.673 c 583.164 548.587 582.91 550.353 583.148 550.591 c 584.055 - 551.498 592.914 543.294 595.906 538.775 c 600.059 532.501 600.641 529.845 - 598.402 527.369 c 596.387 525.142 589.938 524.794 583.695 526.576 c h -583.695 526.576 m f -547.086 526.056 m 537.973 526.962 527.004 528.654 525.922 529.326 c 524.156 - 530.416 526.988 540.384 531.492 548.955 c 535.809 557.169 546.734 569.384 - 552.312 572.228 c 557.594 574.923 560.227 574.08 567.477 567.365 c 575.465 - 559.97 580.645 549.892 580.645 541.736 c 580.645 538.173 580.039 535.138 - 579.051 533.724 c 576.758 530.451 570.676 527.654 563.223 526.443 c 556.867 - 525.412 554.211 525.349 547.086 526.056 c h -547.086 526.056 m f -590.016 505.58 m 582.621 508.099 568.406 514.943 560.918 519.591 c 556.953 - 522.048 l 561.934 522.767 l 564.672 523.158 568.609 523.974 570.684 524.576 - c 573.652 525.439 575.707 525.318 580.41 523.998 c 589.02 521.583 596.969 - 521.783 600.43 524.505 c 602.742 526.326 603.141 527.322 602.836 530.537 - c 602.473 534.341 602.504 534.376 605.539 533.712 c 607.227 533.345 610.988 - 533.033 613.898 533.021 c 618.316 533.005 619.273 532.669 619.73 530.97 - c 621.41 524.716 619.254 516.947 614.402 511.775 c 611.855 509.06 600.746 - 502.974 598.441 503.029 c 597.883 503.044 594.09 504.193 590.016 505.58 - c h -590.016 505.58 m f -625.391 461.849 m 625.391 464.384 626.344 469.302 627.512 472.783 c 632.844 - 488.685 643.176 499.103 656.406 501.919 c 663.512 503.427 697.473 513.15 - 705.914 516.087 c 711.695 518.103 l 712.781 513.044 l 713.375 510.263 713.855 - 507.439 713.844 506.767 c 713.805 504.087 687.395 481.837 673.695 472.939 - c 657.051 462.134 644.184 457.24 632.402 457.24 c 625.391 457.24 l h -625.391 461.849 m f -720.984 525.564 m 720.984 525.74 721.875 528.232 722.965 531.103 c 729.418 - 548.072 734.258 591.447 731.191 604.755 c 723.617 637.587 682.539 669.802 - 638.684 677.298 c 633.414 678.197 626.547 678.951 623.43 678.966 c 617.762 - 678.998 l 621.832 681.58 l 625.637 683.99 626.469 684.119 634.836 683.541 - c 657.047 682.009 678.965 673.037 714.125 651.076 c 722.906 645.591 731.816 - 640.705 733.926 640.212 c 740.969 638.58 758.52 639.146 767.5 641.298 c - 778.797 644.005 790.098 649.701 790.246 652.763 c 790.305 654.013 790.648 - 664.955 791.008 677.072 c 791.367 689.193 791.945 699.392 792.293 699.74 - c 792.641 700.083 794.18 699.794 795.719 699.095 c 798.516 697.822 l 797.895 - 673.888 l 796.25 610.556 796.008 586.392 797.023 586.392 c 798.762 586.392 - 799.207 595.896 799.902 647.623 c 800.566 697.158 l 803.484 695.65 l 806.406 - 694.138 l 806.406 678.087 l 806.406 659.423 806.914 657.919 812.891 658.888 - c 814.945 659.22 823.926 663.021 832.844 667.333 c 849.062 675.173 l 884.801 - 659.021 l 920.543 642.869 l 916 638.783 l 906.965 630.658 897.637 625.138 - 889.367 623.025 c 875.805 619.556 868.797 614.626 866.746 607.111 c 866.246 - 605.275 863.289 601.599 859.516 598.119 c 853.398 592.47 852.297 591.923 - 832.621 584.736 c 821.336 580.615 811.133 577.24 809.949 577.24 c 805.375 - 577.24 797.492 574.08 793.469 570.638 c 787.602 565.615 784.879 560.83 -784.797 555.384 c 784.73 550.845 l 766.488 543.119 l 742.254 532.857 720.98 - 524.646 720.984 525.564 c h -720.984 525.564 m f -1344.883 495.33 m 1344.32 495.544 1335.566 498.271 1325.43 501.388 c 1287.879 - 512.931 1246.145 526.486 1107.422 572.205 c 1023.523 599.853 l 920.812 -646.404 l 864.32 672.005 809.176 696.931 798.27 701.794 c 787.363 706.658 - 777.84 711.212 777.109 711.919 c 774.473 714.455 775.441 721.353 779.59 - 729.595 c 781.758 733.9 784.277 737.548 785.203 737.72 c 787.629 738.166 - 868.773 701.33 993.016 643.38 c 1016 632.662 1026.562 628.763 1174.035 -576.533 c 1260.168 546.025 1333.277 520.162 1336.5 519.056 c 1342.355 517.048 - l 1339.238 513.111 l 1336.074 509.115 1334.012 501.986 1336.023 501.986 - c 1336.609 501.986 1337.375 503.279 1337.723 504.857 c 1338.59 508.81 1344.07 - 515.208 1346.59 515.208 c 1347.898 515.208 1348.465 515.705 1348.137 516.568 - c 1347.188 519.044 1375.074 536.205 1395.113 545.478 c 1399.367 547.447 - 1409.934 552.173 1418.594 555.982 c 1427.254 559.794 1434.5 563.388 1434.695 - 563.974 c 1435.281 565.732 1433.043 565.208 1424.863 561.685 c 1417.086 - 558.33 l 1286.406 619.615 l 1214.535 653.322 1150.738 683.224 1144.637 -686.064 c 1133.547 691.228 l 1141.238 695.728 l 1148.93 700.232 l 1170.633 - 690.224 l 1182.57 684.72 1252.746 652.423 1326.574 618.451 c 1400.406 584.478 - 1461.172 556.349 1461.609 555.943 c 1462.605 555.025 1436.758 536.423 1419.238 - 525.451 c 1393.199 509.142 1365.406 496.849 1351.492 495.49 c 1348.414 -495.189 1345.441 495.115 1344.883 495.33 c h -1344.883 495.33 m f -1180.137 577.103 m 1017.422 634.802 l 960.984 661.076 l 884.766 696.548 - 802.91 734.08 795.238 737.068 c 792.848 737.998 793.18 738.322 801.34 743.103 - c 823.996 756.376 852.16 769.298 874.332 776.591 c 880.648 778.666 885.828 - 780.666 885.848 781.033 c 885.977 783.638 884.199 783.501 874.105 780.123 - c 849.426 771.865 828.375 762.353 804.473 748.654 c 790.82 740.83 788.602 - 739.861 786.508 740.818 c 784.484 741.74 783.828 741.537 782.234 739.509 - c 780.348 737.115 l 729.246 760.564 l 678.148 784.017 l 681.516 785.478 - l 684.816 786.912 684.836 786.962 682.594 788.009 c 681.336 788.595 680.305 - 789.353 680.305 789.689 c 680.305 790.544 701.945 802.693 715.477 809.439 - c 731.238 817.294 745.758 823.353 760.898 828.396 c 769.238 831.173 773.863 - 833.22 773.863 834.13 c 773.863 836.212 772.73 835.986 757.918 830.962 -c 734.414 822.99 709.336 811.259 685.57 797.119 c 676.094 791.482 l 671.336 - 793.83 l 655.211 801.783 606.672 823.044 564.883 840.455 c 534.117 853.271 - 472.793 878.259 472.098 878.259 c 471.031 878.259 228.777 1012.88 228.777 - 1013.47 c 228.777 1015.806 263.836 1033.525 286.98 1042.884 c 299.93 1048.119 - l 444.84 989.376 l 589.746 930.63 l 583.668 929.181 l 580.328 928.388 577.594 - 927.466 577.594 927.138 c 577.594 926.81 581.824 924.638 587 922.314 c -592.172 919.99 614.254 909.994 636.066 900.099 c 710.277 866.439 766.664 - 841.685 779.645 837.068 c 784.836 835.22 785.059 835.228 793.883 837.357 - c 807.793 840.716 834.812 845.716 839.055 845.716 c 843.016 845.716 889.668 - 830.193 909.625 822.236 c 915.777 819.783 924.848 815.126 929.777 811.892 - c 937.641 806.728 938.699 805.685 938.422 803.369 c 938.148 801.134 936.988 - 800.287 930.852 797.857 c 926.863 796.279 917.254 793.302 909.496 791.24 - c 891.586 786.478 892.488 786.744 892.957 786.341 c 893.176 786.154 909.371 - 778.884 928.949 770.185 c 986.547 744.591 1055.887 713.333 1087.969 698.498 - c 1120.688 683.369 1118.266 683.994 1128.336 688.13 c 1131.352 689.369 -1136.098 687.232 1271.98 623.533 c 1349.27 587.302 1412.5 557.373 1412.488 - 557.025 c 1412.48 556.677 1403.922 552.275 1393.477 547.24 c 1374.355 538.025 - 1356.566 527.958 1349.449 522.322 c 1347.336 520.646 1344.984 519.302 1344.227 - 519.337 c 1343.469 519.373 1269.625 545.365 1180.137 577.103 c h -783.965 758.298 m 787.961 759.998 797.863 765.033 805.969 769.486 c 814.078 - 773.939 821.121 777.58 821.617 777.58 c 822.113 777.58 823.953 778.787 -825.703 780.259 c 832.797 786.228 826.574 791.087 812.465 790.595 c 803.781 - 790.291 l 782.465 779.751 l 770.742 773.958 760.121 768.271 758.863 767.119 - c 754.336 762.97 756.77 758.107 764.254 756.353 c 771.66 754.619 776.418 - 755.087 783.965 758.298 c h -735.004 782.033 m 739.879 783.919 750.879 788.724 759.457 792.708 c 768.031 - 796.693 775.457 799.955 775.957 799.955 c 776.453 799.955 777.551 801.287 - 778.395 802.916 c 779.801 805.634 779.766 806.076 778.004 808.255 c 776.309 - 810.345 775.035 810.673 767.367 810.99 c 758.711 811.345 758.539 811.302 - 740.582 804.458 c 717.605 795.705 711.871 793.173 709.035 790.537 c 702.93 - 784.865 709.551 779.177 722.801 778.712 c 724.641 778.65 730.133 780.142 - 735.004 782.033 c h -735.004 782.033 m f -550.027 814.107 m 445.684 860.666 l 333.926 924.615 l 272.461 959.787 220.48 - 990.087 218.414 991.951 c 211.324 998.345 209.859 1007.466 214.488 1016.404 - c 216.77 1020.806 225.281 1029.193 232.148 1033.802 c 243.715 1041.568 -268.797 1051.857 290.789 1057.857 c 303.664 1061.373 l 339.188 1048.22 l - 438.902 1011.302 622.141 939.755 620.691 938.306 c 620.402 938.017 614.133 - 936.373 606.762 934.662 c 595.488 932.041 592.957 931.744 590.816 932.787 - c 589.418 933.47 529.922 957.673 458.609 986.572 c 387.297 1015.474 323.297 - 1041.435 316.391 1044.263 c 303.836 1049.408 l 307.164 1050.81 l 309.348 - 1051.728 310.145 1052.552 309.488 1053.208 c 306.926 1055.771 246.301 1028.833 - 230.891 1018.287 c 228.418 1016.591 225.746 1015.208 224.953 1015.208 c - 224.164 1015.208 223.109 1014.138 222.613 1012.833 c 222.117 1011.529 221.355 - 1010.458 220.922 1010.455 c 220.488 1010.451 219.105 1008.935 217.848 1007.087 - c 215.488 1003.623 214.781 999.275 216.574 999.275 c 217.137 999.275 217.594 - 1000.13 217.594 1001.177 c 217.594 1002.224 219.332 1005.044 221.461 1007.439 - c 225.328 1011.798 l 346.371 944.669 l 460.426 881.416 468.609 877.056 -488.18 869.138 c 567.398 837.076 632.105 809.56 662.227 795.126 c 676.688 - 788.193 680.184 785.716 675.508 785.716 c 673.035 785.716 663.859 778.228 - 659.379 772.548 c 657.184 769.767 655.16 767.505 654.883 767.521 c 654.602 - 767.533 607.418 788.498 550.027 814.107 c h -550.027 814.107 m f -1181.152 576.638 m 1019.457 633.896 l 963.016 660.111 l 880.336 698.509 - 810.039 730.771 796.301 736.619 c 792.809 738.103 l 801.387 743.162 l 811.043 - 748.853 835.551 761.345 847.086 766.451 c 851.277 768.306 861.555 772.197 - 869.918 775.091 c 883.879 779.923 888.941 782.666 883.906 782.666 c 881.293 - 782.666 861.211 775.845 849.625 771.021 c 835.906 765.31 817.676 756.228 - 802.684 747.638 c 791.668 741.326 788.766 740.076 786.289 740.572 c 783.973 - 741.033 782.938 740.693 781.824 739.103 c 780.41 737.083 779.145 737.595 - 731.102 759.654 c 704.004 772.095 680.922 782.787 679.812 783.408 c 677.895 - 784.482 677.938 784.576 680.574 785.24 c 683.773 786.041 684.125 787.22 - 681.586 788.638 c 679.387 789.869 678.836 789.416 690.91 796.353 c 714.176 - 809.72 738.992 821.08 762.168 828.962 c 768.32 831.052 773.555 832.943 -773.797 833.158 c 774.039 833.369 773.777 834.005 773.215 834.568 c 769.902 - 837.88 719.914 816.869 689.641 799.443 c 676.098 791.646 l 660.66 798.771 - l 638.527 808.978 620.816 816.744 587.031 831.052 c 555.027 844.603 473.059 - 878.259 472.059 878.259 c 470.895 878.259 228.777 1012.845 228.777 1013.494 - c 228.777 1015.83 263.859 1033.548 287.016 1042.904 c 300 1048.15 l 444.938 - 989.388 l 589.875 930.63 l 583.734 929.181 l 580.355 928.388 577.598 927.47 - 577.602 927.15 c 577.609 926.826 579.324 925.818 581.418 924.908 c 583.508 - 924.001 610.16 911.951 640.645 898.13 c 741.594 852.365 781.879 835.115 - 786.066 835.869 c 787.465 836.123 799.012 838.439 811.723 841.021 c 824.438 - 843.603 836.633 845.716 838.828 845.716 c 843.047 845.716 888.371 830.748 - 908.383 822.744 c 925.836 815.763 938.609 807.572 938.609 803.357 c 938.609 - 801.416 937.656 800.49 934.098 798.962 c 928.602 796.607 912.195 791.533 - 902.508 789.189 c 898.594 788.244 895.027 787.146 894.582 786.751 c 894.137 - 786.357 907.41 779.943 924.074 772.501 c 992.941 741.74 1057.902 712.439 - 1086.664 699.162 c 1103.492 691.392 1117.961 685.037 1118.816 685.037 c - 1119.672 685.037 1122.723 686.013 1125.594 687.208 c 1130.812 689.376 l - 1135.645 687.337 l 1139.426 685.74 1339.473 592.037 1400.051 563.49 c 1406.902 - 560.259 1412.508 557.337 1412.508 556.994 c 1412.508 556.65 1405.305 552.943 - 1396.496 548.755 c 1378.527 540.208 1359.215 529.423 1350.969 523.333 c - 1347.949 521.099 1344.883 519.298 1344.16 519.326 c 1343.438 519.353 1270.086 - 545.142 1181.152 576.638 c h -791.387 762.142 m 811.07 772.025 824.938 779.505 826.938 781.314 c 829.559 - 783.685 829.215 786.4 826.027 788.49 c 823.879 789.9 821.152 790.291 813.535 - 790.291 c 803.793 790.291 l 783.488 780.185 l 772.324 774.623 761.699 768.994 - 759.883 767.673 c 751.68 761.716 759.488 754.83 773.59 755.591 c 777.734 - 755.818 781.676 757.267 791.387 762.142 c h -735.109 782.111 m 749.836 788.005 777.188 800.783 778.078 802.189 c 781.469 - 807.533 776.289 811.138 765.23 811.138 c 759.449 811.138 756.102 810.205 - 740.027 804.107 c 716.992 795.365 711.926 793.142 709.035 790.501 c 704.176 - 786.068 707.172 781.435 716.02 779.697 c 723.672 778.193 726.07 778.494 - 735.109 782.111 c h -735.109 782.111 m f -1103.863 693.763 m 1088.332 701.435 1043.266 721.919 961.492 758.474 c -929.051 772.978 902.074 785.267 901.543 785.787 c 900.938 786.38 901.168 - 786.732 902.16 786.732 c 904.531 786.732 924.059 792.462 931.141 795.24 - c 937.348 797.669 l 1041.727 749.619 l 1146.109 701.568 l 1138.988 697.337 - l 1130.914 692.537 1119.625 687.033 1118.102 687.154 c 1117.543 687.197 - 1111.137 690.169 1103.863 693.763 c h -1103.863 693.763 m f -767.762 844.603 m 748.641 852.541 729.246 861.001 693.523 876.99 c 668.535 - 888.177 584.375 926.201 584.008 926.47 c 583.676 926.716 624.805 936.248 - 626.152 936.236 c 628.617 936.212 740.895 889.166 810.832 858.845 c 835.09 - 848.33 l 813.469 844.013 l 801.578 841.642 790.48 839.224 788.805 838.642 - c 787.133 838.056 785.488 837.595 785.152 837.611 c 784.816 837.626 776.992 - 840.775 767.762 844.603 c h -767.762 844.603 m f -788.102 555.876 m 788.102 562.876 799.594 573.173 807.406 573.173 c 810.109 - 573.173 950.723 623.462 954.883 625.919 c 955.906 626.525 958.07 625.978 - 961.492 624.251 c 966.574 621.689 l 963.016 620.427 l 961.059 619.732 926.277 - 607.419 885.727 593.064 c 811.641 566.837 811.355 566.724 788.863 554.158 - c 788.445 553.923 788.102 554.697 788.102 555.876 c h -788.102 555.876 m f -877.594 604.189 m 876.195 604.99 873.781 605.662 872.227 605.681 c 869.418 - 605.716 869.414 605.732 870.984 608.771 c 873.117 612.892 879.297 616.802 - 887.098 618.966 c 893.547 620.751 l 896.297 615.146 l 897.809 612.064 898.844 - 609.337 898.594 609.091 c 897.883 608.38 882.285 602.669 881.152 602.705 - c 880.594 602.724 878.992 603.392 877.594 604.189 c h -877.594 604.189 m f -902.582 571.322 m 892.16 584.228 888.051 590.021 888.98 590.513 c 893.379 - 592.841 907.43 596.931 907.766 595.982 c 907.977 595.38 912.75 587.47 918.367 - 578.4 c 923.988 569.333 928.438 561.525 928.258 561.048 c 927.941 560.212 - 918.625 552.833 917.883 552.833 c 917.68 552.833 910.793 561.154 902.582 - 571.322 c h -902.582 571.322 m f -911.66 518.373 m 910.543 519.033 909.531 519.638 909.418 519.72 c 908.621 - 520.267 912.531 534.349 914.965 539.693 c 918.863 548.259 922.094 551.919 - 931.113 558.005 c 940.82 564.556 942.199 565.021 955.07 566.068 c 961.047 - 566.556 971.457 568.353 978.199 570.064 c 992.668 573.732 993.91 573.806 - 996.574 571.138 c 998.121 569.591 998.609 567.751 998.609 563.447 c 998.609 - 560.333 998.137 556.904 997.559 555.822 c 995.895 552.708 970.039 536.119 - 956.527 529.494 c 949.75 526.169 940.543 522.216 936.066 520.712 c 926.938 - 517.642 914.855 516.482 911.66 518.373 c h -911.66 518.373 m f -913.426 504.275 m 912.242 505.533 910.863 508.392 910.363 510.63 c 909.449 - 514.697 l 919.199 514.701 l 927.23 514.701 930.414 515.201 937.254 517.552 - c 949.414 521.724 969.988 532.22 983.316 541.048 c 989.727 545.294 995.145 - 548.767 995.359 548.767 c 996.762 548.767 989.168 538.013 983.992 532.677 - c 964.555 512.615 921.766 495.396 913.426 504.275 c h -913.426 504.275 m f -631.25 446.595 m 629.719 447.447 627.801 449.423 626.988 450.998 c 625.512 - 453.853 l 632.824 453.861 l 651.543 453.892 675.52 466.955 705.098 493.236 - c 713.559 500.751 714.453 500.322 709.25 491.24 c 707.008 487.333 690.035 - 472.15 677.254 462.619 c 660.34 450.005 638.695 442.466 631.25 446.595 -c h -631.25 446.595 m f -981.32 849.06 m 983.859 851.626 1036.539 887.134 1056.352 899.63 c 1083.43 - 916.712 1132.848 949.048 1146.574 958.666 c 1161.832 969.353 l 1167.133 - 979.486 l 1173.852 992.318 1197.051 1044.572 1217.801 1093.595 c 1233.836 - 1131.478 l 1238.363 1131.478 l 1244.824 1131.478 1253.621 1133.978 1265.383 - 1139.154 c 1271 1141.626 1275.68 1143.541 1275.785 1143.412 c 1275.988 -1143.162 1226.234 954.263 1225.816 953.689 c 1224.945 952.494 985.184 847.466 - 982.34 847.029 c 978.777 846.486 l h -981.32 849.06 m f -1230 956.818 m 1230.262 957.517 1241.457 999.732 1254.879 1050.63 c 1268.301 - 1101.529 1279.625 1143.97 1280.047 1144.943 c 1280.672 1146.392 1302.676 - 1160.134 1303.402 1159.533 c 1303.758 1159.236 1273.281 978.615 1272.441 - 976.052 c 1271.816 974.15 1267.586 971.869 1251.32 964.658 c 1240.137 959.697 - 1230.652 955.619 1230.25 955.591 c 1229.852 955.568 1229.734 956.119 1230 - 956.818 c h -1230 956.818 m f -1236.137 1136.818 m 1236.527 1138.076 1237.152 1139.333 1237.523 1139.615 - c 1237.891 1139.892 1240.688 1142.025 1243.742 1144.345 c 1250.703 1149.65 - 1260.555 1155.052 1269.117 1158.271 c 1275.699 1160.744 1300.168 1167.072 - 1303.148 1167.072 c 1307.062 1167.072 1303.797 1164.005 1293.258 1157.783 - c 1269.945 1144.025 1247.852 1134.529 1239.152 1134.529 c 1235.75 1134.529 - 1235.484 1134.732 1236.137 1136.818 c h -1236.137 1136.818 m f -1353.508 903.537 m 1325.809 914.525 1291.062 928.267 1276.293 934.08 c -1249.434 944.646 l 1256.988 947.666 l 1261.145 949.33 1273.559 953.326 1284.582 - 956.552 c 1304.621 962.416 l 1324.242 953.341 l 1368.699 932.775 1408.242 - 911.83 1417.566 903.9 c 1421.102 900.896 l 1418.125 896.599 l 1415.5 892.81 - 1405.324 883.162 1404.262 883.451 c 1404.043 883.513 1381.203 892.552 1353.508 - 903.537 c h -1353.508 903.537 m f -1350.473 862.126 m 1334.188 864.81 1303.297 873.525 1241.152 892.978 c -1222.695 898.755 1203.246 904.751 1197.934 906.306 c 1192.617 907.857 1188.074 - 909.283 1187.832 909.47 c 1186.816 910.267 1194.902 917.38 1201.523 921.521 - c 1208.711 926.013 1234.922 938.916 1241.77 941.333 c 1245.367 942.603 -1246.902 942.056 1321.094 912.869 c 1362.703 896.501 1397.891 882.669 1399.289 - 882.134 c 1401.832 881.162 l 1397.762 878.638 l 1389.289 873.376 1374.059 - 866.201 1365.93 863.634 c 1361.285 862.169 1357.168 861.025 1356.777 861.087 - c 1356.387 861.154 1353.551 861.623 1350.473 862.126 c h -1350.473 862.126 m f -1116.809 996.982 m 1090.855 1008.744 1073.176 1017.298 1073.711 1017.833 - c 1074.215 1018.337 1082.258 1020.611 1091.586 1022.888 c 1108.551 1027.029 - l 1142.035 1016.458 l 1160.449 1010.642 1175.527 1005.525 1175.539 1005.083 - c 1175.582 1003.47 1162.457 976.904 1161.648 976.97 c 1161.191 977.005 -1141.012 986.013 1116.809 996.982 c h -1116.809 996.982 m f -1078.445 963.794 m 1059.434 976.615 1042.891 988.126 1041.688 989.376 c - 1037.105 994.126 1037.547 995.478 1045.859 1002.162 c 1050.07 1005.552 -1056.559 1010.025 1060.273 1012.103 c 1066.09 1015.357 1067.406 1015.732 - 1069.688 1014.798 c 1071.145 1014.201 1087.898 1006.642 1106.914 997.998 - c 1125.934 989.357 1145.473 980.501 1150.336 978.322 c 1155.199 976.142 - 1159.289 973.904 1159.426 973.345 c 1159.559 972.787 1157.18 970.455 1154.141 - 968.162 c 1147.527 963.173 1113.883 940.251 1113.375 940.388 c 1113.176 - 940.439 1097.461 950.97 1078.445 963.794 c h -1078.445 963.794 m f -535.332 567.564 m 533.07 572.97 534.531 576.24 539.18 576.189 c 541.012 - 576.169 543.539 575.58 544.797 574.88 c 547.066 573.623 547.039 573.56 -542.027 568.626 c 536.969 563.642 l h -535.332 567.564 m f -0 g -107.641 340.509 m 119.641 362.572 l 134.797 362.572 l 116.172 330.837 l - 135.281 298.587 l 119.938 298.587 l 107.641 320.994 l 95.328 298.587 l -80 298.587 l 99.109 330.837 l 80.484 362.572 l 95.641 362.572 l h -107.641 340.509 m f -133.02 955.158 m 146.332 983.986 l 160.738 983.986 l 139.738 943.205 l -139.738 920.001 l 126.332 920.001 l 126.332 943.205 l 105.332 983.986 l -119.785 983.986 l h -133.02 955.158 m f -805.477 90.595 m 837.727 90.595 l 837.727 80.001 l 788.992 80.001 l 788.992 - 87.736 l 820.633 133.314 l 789.039 133.314 l 789.039 143.986 l 837.023 -143.986 l 837.023 136.423 l h -805.477 90.595 m f -0 0 1 rg -290.406 276.701 m 282.016 276.701 l 282.016 257.966 l 271.469 257.966 l - 271.469 309.154 l 290.484 309.154 l 296.523 309.154 301.188 307.802 304.469 - 305.107 c 307.75 302.419 309.391 298.611 309.391 293.685 c 309.391 290.193 - 308.633 287.283 307.125 284.951 c 305.613 282.615 303.32 280.755 300.25 - 279.373 c 311.328 258.466 l 311.328 257.966 l 300.016 257.966 l h -282.016 285.248 m 290.516 285.248 l 293.16 285.248 295.207 285.919 296.656 - 287.263 c 298.113 288.615 298.844 290.474 298.844 292.841 c 298.844 295.255 - 298.156 297.154 296.781 298.529 c 295.414 299.912 293.316 300.607 290.484 - 300.607 c 282.016 300.607 l h -282.016 285.248 m f -315.203 277.341 m 315.203 281.111 315.926 284.47 317.375 287.419 c 318.832 - 290.376 320.926 292.662 323.656 294.279 c 326.383 295.904 329.551 296.716 - 333.156 296.716 c 338.289 296.716 342.477 295.142 345.719 291.998 c 348.969 - 288.861 350.781 284.595 351.156 279.201 c 351.234 276.607 l 351.234 270.763 - 349.602 266.076 346.344 262.544 c 343.082 259.021 338.707 257.263 333.219 - 257.263 c 327.738 257.263 323.363 259.017 320.094 262.529 c 316.832 266.048 - 315.203 270.833 315.203 276.888 c h -325.359 276.607 m 325.359 272.99 326.035 270.224 327.391 268.31 c 328.754 - 266.404 330.703 265.451 333.234 265.451 c 335.691 265.451 337.613 266.392 - 339 268.279 c 340.383 270.173 341.078 273.193 341.078 277.341 c 341.078 - 280.88 340.383 283.623 339 285.56 c 337.613 287.505 335.664 288.482 333.156 - 288.482 c 330.676 288.482 328.754 287.513 327.391 285.576 c 326.035 283.646 - 325.359 280.658 325.359 276.607 c h -325.359 276.607 m f -358.094 311.966 10.188 -54 re f -377.219 311.966 10.188 -54 re f -234.594 734.919 m 234.594 716.888 l 224.047 716.888 l 224.047 768.076 l - 244.016 768.076 l 247.859 768.076 251.238 767.373 254.156 765.966 c 257.07 - 764.56 259.312 762.56 260.875 759.966 c 262.445 757.38 263.234 754.439 -263.234 751.138 c 263.234 746.115 261.516 742.158 258.078 739.263 c 254.648 - 736.365 249.898 734.919 243.828 734.919 c h -234.594 743.466 m 244.016 743.466 l 246.797 743.466 248.922 744.123 250.391 - 745.435 c 251.859 746.748 252.594 748.623 252.594 751.06 c 252.594 753.568 - 251.852 755.595 250.375 757.138 c 248.895 758.689 246.852 759.486 244.25 - 759.529 c 234.594 759.529 l h -234.594 743.466 m f -280.5 716.888 m 270.312 716.888 l 270.312 754.935 l 280.5 754.935 l h -269.703 764.779 m 269.703 766.298 270.211 767.548 271.234 768.529 c 272.254 - 769.517 273.645 770.013 275.406 770.013 c 277.133 770.013 278.516 769.517 - 279.547 768.529 c 280.578 767.548 281.094 766.298 281.094 764.779 c 281.094 - 763.224 280.57 761.955 279.531 760.966 c 278.488 759.986 277.113 759.498 - 275.406 759.498 c 273.695 759.498 272.316 759.986 271.266 760.966 c 270.223 - 761.955 269.703 763.224 269.703 764.779 c h -269.703 764.779 m f -301.062 764.279 m 301.062 754.935 l 307.578 754.935 l 307.578 747.482 l - 301.062 747.482 l 301.062 728.498 l 301.062 727.091 301.332 726.08 301.875 - 725.466 c 302.414 724.861 303.445 724.56 304.969 724.56 c 306.094 724.56 - 307.086 724.638 307.953 724.794 c 307.953 717.107 l 305.961 716.494 303.914 - 716.185 301.812 716.185 c 294.688 716.185 291.051 719.783 290.906 726.982 - c 290.906 747.482 l 285.359 747.482 l 285.359 754.935 l 290.906 754.935 - l 290.906 764.279 l h -301.062 764.279 m f -329.047 724.373 m 330.922 724.373 332.441 724.888 333.609 725.919 c 334.785 - 726.951 335.398 728.326 335.453 730.044 c 344.984 730.044 l 344.953 727.458 - 344.242 725.095 342.859 722.951 c 341.484 720.802 339.594 719.138 337.188 - 717.951 c 334.789 716.775 332.141 716.185 329.234 716.185 c 323.797 716.185 - 319.504 717.912 316.359 721.373 c 313.223 724.83 311.656 729.607 311.656 - 735.701 c 311.656 736.373 l 311.656 742.224 313.211 746.896 316.328 750.388 - c 319.441 753.888 323.719 755.638 329.156 755.638 c 333.914 755.638 337.727 - 754.283 340.594 751.576 c 343.469 748.865 344.93 745.263 344.984 740.763 - c 335.453 740.763 l 335.398 742.732 334.785 744.33 333.609 745.56 c 332.441 - 746.787 330.898 747.404 328.984 747.404 c 326.617 747.404 324.832 746.537 - 323.625 744.81 c 322.414 743.091 321.812 740.298 321.812 736.435 c 321.812 - 735.388 l 321.812 731.47 322.406 728.654 323.594 726.935 c 324.789 725.224 - 326.609 724.373 329.047 724.373 c h -329.047 724.373 m f -360.688 750.779 m 363.383 754.017 366.773 755.638 370.859 755.638 c 379.109 - 755.638 383.289 750.841 383.406 741.248 c 383.406 716.888 l 373.25 716.888 - l 373.25 740.966 l 373.25 743.154 372.781 744.767 371.844 745.81 c 370.906 - 746.849 369.344 747.373 367.156 747.373 c 364.188 747.373 362.031 746.22 - 360.688 743.919 c 360.688 716.888 l 350.531 716.888 l 350.531 770.888 l - 360.688 770.888 l h -360.688 750.779 m f -888.57 375.865 m 899.227 398.927 l 910.758 398.927 l 893.945 366.302 l -893.945 347.74 l 883.227 347.74 l 883.227 366.302 l 866.43 398.927 l 877.992 - 398.927 l h -888.57 375.865 m f -934.633 347.74 m 934.164 348.654 933.82 349.791 933.602 351.146 c 931.141 - 348.404 927.945 347.037 924.008 347.037 c 920.277 347.037 917.188 348.115 - 914.742 350.271 c 912.293 352.427 911.07 355.146 911.07 358.427 c 911.07 - 362.458 912.562 365.552 915.555 367.708 c 918.543 369.865 922.859 370.951 - 928.508 370.974 c 933.18 370.974 l 933.18 373.162 l 933.18 374.919 932.727 - 376.326 931.82 377.38 c 930.922 378.431 929.5 378.958 927.555 378.958 c - 925.844 378.958 924.5 378.544 923.523 377.724 c 922.555 376.912 922.07 -375.787 922.07 374.349 c 911.914 374.349 l 911.914 376.556 912.59 378.599 - 913.945 380.474 c 915.309 382.349 917.23 383.818 919.711 384.88 c 922.199 - 385.951 924.992 386.49 928.086 386.49 c 932.773 386.49 936.492 385.306 -939.242 382.943 c 942 380.587 943.383 377.279 943.383 373.021 c 943.383 -356.537 l 943.402 352.919 943.906 350.185 944.898 348.333 c 944.898 347.74 - l h -926.227 354.802 m 927.727 354.802 929.105 355.134 930.367 355.802 c 931.637 - 356.478 932.574 357.38 933.18 358.505 c 933.18 365.037 l 929.383 365.037 - l 924.297 365.037 921.594 363.279 921.273 359.771 c 921.227 359.162 l 921.227 - 357.9 921.668 356.857 922.555 356.037 c 923.449 355.212 924.672 354.802 - 926.227 354.802 c h -926.227 354.802 m f -984.336 361.552 m 989.336 385.787 l 999.133 385.787 l 989.43 347.74 l 980.93 - 347.74 l 973.727 371.677 l 966.508 347.74 l 958.039 347.74 l 948.336 385.787 - l 958.148 385.787 l 963.102 361.599 l 970.07 385.787 l 977.414 385.787 -l h -984.336 361.552 m f -1 g -326.09 449.794 m 344.672 420.634 l 316.797 402.873 l 298.215 432.033 l -h -326.09 449.794 m f -0 0 1 rg -13.139154 w -1 J -q -0.694894 1 -1 -0.694894 0 1251.427246 cm --766.819 203.31 m -753.706 181.96 -730.198 169.227 -705.15 169.903 c -680.105 - 170.581 -657.321 184.565 -645.377 206.593 c S Q -205.172 458.419 m 263.723 432.716 l 256.668 496.326 l h -205.172 458.419 m f* -7.951226 w -0 J -q -1 -0.110932 0.110932 -1 0 1251.427246 cm --115.778 805.851 m -170.8 837.658 l -170.802 774.049 l h --115.778 805.851 m S Q -1 g -307.023 870.611 m 326.152 899.416 l 353.684 881.13 l 334.559 852.326 l -h -307.023 870.611 m f -794.102 284.423 m 828.344 289.232 l 832.941 256.505 l 798.699 251.693 l - h -794.102 284.423 m f -0 0 1 rg -13.105011 w -1 J -q 0.700438 1 1 -0.700438 0 1251.427246 cm --156.332 431.443 m -143.252 410.148 -119.804 397.447 -94.824 398.121 c --69.842 398.799 -47.117 412.744 -35.205 434.714 c S Q -446.742 907.521 m 388.098 882.041 l 395.391 945.623 l h -446.742 907.521 m f* -7.947882 w -0 J -q 1 -0.114709 -0.114709 -1 0 1251.427246 cm -479.877 288.86 m 424.879 320.649 l 424.878 257.068 l h -479.877 288.86 m S Q -15.805467 w -1 J -q -1 -0.157377 -0.157377 1 0 1251.427246 cm --715.112 -1041.972 m -699.335 -1067.657 -671.061 -1082.973 -640.931 -1082.161 - c -610.798 -1081.344 -583.391 -1064.523 -569.026 -1038.024 c S Q -709.953 332.287 m 769.004 307.751 l 718.168 268.873 l h -709.953 332.287 m f* -6.354568 w -0 J -q -0.764802 1 1 0.764802 0 1251.427246 cm --922.515 4.412 m -966.49 29.83 l -966.49 -21.005 l h --922.515 4.412 m S Q -0.784314 g -674.449 783.833 m 652.246 770.513 632.93 728.021 622.734 684.978 c 667.211 - 690.123 714.543 650.978 732.168 642.123 c 757.883 637.74 773.598 643.361 - 787.883 651.263 c 790.168 702.693 l 778.453 708.978 l 774.262 710.787 773.785 - 714.025 773.598 718.693 c 773.598 718.693 775.312 725.837 777.312 729.548 - c 778.469 731.701 780.168 735.263 780.168 735.263 c 674.457 783.837 l h -674.449 783.833 m f* -0 g -2.4 w -q 1 0 0 -1 0 1251.427246 cm -674.449 467.594 m 652.246 480.914 632.93 523.406 622.734 566.449 c 667.211 - 561.305 714.543 600.449 732.168 609.305 c 757.883 613.688 773.598 608.066 - 787.883 600.164 c 790.168 548.734 l 778.453 542.449 l 774.262 540.641 773.785 - 537.402 773.598 532.734 c 773.598 532.734 775.312 525.59 777.312 521.879 - c 778.469 519.727 780.168 516.164 780.168 516.164 c 674.457 467.59 l h -674.449 467.594 m S Q -0.784314 g -807.914 695.009 m 808.117 689.455 808.016 668.544 808.219 663.669 c 808.418 - 659.603 810.539 659.857 813.977 660.666 c 823.875 663.896 841.25 674.201 - 848.523 676.826 c f* -0 g -q 1 0 0 -1 0 1251.427246 cm -807.914 556.418 m 808.117 561.973 808.016 582.883 808.219 587.758 c 808.418 - 591.824 810.539 591.57 813.977 590.762 c 823.875 587.531 841.25 577.227 - 848.523 574.602 c S Q -Q Q -showpage -%%Trailer -end restore -%%EOF diff --git a/specification/application/aircraft_principal_axes.svg b/specification/application/aircraft_principal_axes.svg deleted file mode 100644 index 369a5754..00000000 --- a/specification/application/aircraft_principal_axes.svg +++ /dev/null @@ -1,865 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - X - Y - Z - Roll - Pitch - Yaw - - - - - - - - - diff --git a/specification/application/application.tex b/specification/application/application.tex deleted file mode 100644 index 82eccefd..00000000 --- a/specification/application/application.tex +++ /dev/null @@ -1,27 +0,0 @@ -\chapter{Application layer}\label{sec:application} - -Previous chapters of this specification define a set of basic concepts that are the foundation of the protocol: -they allow one to define data types and exchange data objects over the bus in a robust and deterministic manner. -This chapter is focused on higher-level concepts: rules, conventions, and standard functions that are to be -respected by applications utilizing Cyphal to maximize cross-vendor compatibility, avoid ambiguities, and -prevent some common design pitfalls. - -The rules, conventions, and standard functions defined in this chapter are designed to be an acceptable middle -ground for any sensible aerospace or robotic system. -Cyphal favors no particular domain or kind of system among targeted applications. - -\begin{itemize} - \item Section~\ref{sec:application_requirements} contains a set of mandatory rules that shall be - followed by all Cyphal implementations. - - \item Section~\ref{sec:application_conventions} contains a set of conventions and recommendations that - are not mandatory to follow. Every deviation, however, should be justified and well-documented. - - \item Section~\ref{sec:application_functions} contains a full list of high-level functions defined on - top of Cyphal. Formal specification of such functions is provided in the DSDL data type definition files that - those functions are based on (see chapter~\ref{sec:sdt}). -\end{itemize} - -\clearpage\input{application/requirements.tex} -\clearpage\input{application/conventions.tex} -\clearpage\input{application/functions.tex} diff --git a/specification/application/conventions.tex b/specification/application/conventions.tex deleted file mode 100644 index d8cd62a0..00000000 --- a/specification/application/conventions.tex +++ /dev/null @@ -1,232 +0,0 @@ -\section{Application-level conventions}\label{sec:application_conventions} - -This section describes a set of high-level conventions designed to enhance compatibility -of applications leveraging Cyphal. -The conventions described here are not mandatory to follow; -however, every deviation should be justified and documented. - -\subsection{Node identifier distribution} - -An overview of related concepts is provided in chapter~\ref{sec:basic}. - -Valid values of node-ID range from 0 up to a transport-specific upper boundary -which is guaranteed to be above 127 for any transport. - -Node-ID values of 126 and 127 are reserved for diagnostic and debugging tools. -These values should not be used in fielded systems. - -\subsection{Service latency} - -If the server uses a significant part of the timeout period to process the request, -the client might drop the request before receiving the response. -Servers should minimize the request processing time; that is, -the time between reception of a service request transfer and -the transmission of the corresponding service response transfer. - -The worst-case request processing time should be documented for each server-side service port. - -\subsection{Coordinate frames} - -Cyphal follows the conventions that are widely accepted in relevant applications. -Adherence to the coordinate frame conventions described here maximizes compatibility and -reduces the amount of computations for conversions between incompatible coordinate systems and -representations. -It is recognized, however, that some applications may find the advised conventions unsuitable, -in which case deviations are permitted. -Any such deviations shall be explicitly documented. - -All coordinate systems defined in this section are right-handed. -If application-specific coordinate systems are introduced, they should be right-handed as well. - -\begin{figure}[hbt] - \centering - % The source image is released under CC0, public domain, no attribution required: - % https://commons.wikimedia.org/wiki/File:ECEF_ENU_Longitude_Latitude_relationships.svg - \includegraphics[width=0.45\textwidth]{application/NED_ECEF} - % The source image is released under CC0, public domain, no attribution required: - % https://pixabay.com/en/airplane-plane-aircraft-propeller-40374/ - % The final image is drawn by me. The source Inkscape SVG file is located in the same directory. - \includegraphics[width=0.45\textwidth]{application/aircraft_principal_axes} - North-East-Down (NED) frame and body frame conventions. All systems are right-handed. - \caption{Coordinate frame conventions\label{fig:application_conventions_coordinate_frame}} -\end{figure} - -\subsubsection{World frame} - -For world fixed frames, the \emph{North-East-Down} (NED) right-handed notation is preferred: -X -- northward, Y -- eastward, Z -- downward. - -\subsubsection{Body frame} - -In relation to a body, the convention is as follows, right-handed\footnote{% - This convention is widely used in aeronautic applications. -}: -X -- forward, Y -- rightward, Z -- downward. - -\subsubsection{Optical frame} - -In the case of cameras, the following right-handed convention is preferred\footnote{% - This convention is widely used in various applications involving computer vision systems. -}: -X -- rightward, Y -- downward, Z -- towards the scene along the optical axis. - -\subsection{Rotation representation} - -All applications should represent rotations using quaternions with the elements ordered as follows\footnote{% - Assuming $w + x\boldsymbol{i} + y\boldsymbol{j} + z\boldsymbol{k}$. -}: W, X, Y, Z. -Other forms of rotation representation should be avoided. - -Angular velocities should be represented using the right-handed, fixed-axis (extrinsic) convention: -X (roll), Y (pitch), Z (yaw). - -\begin{remark} - Quaternions are considered to offer the optimal trade-off between bandwidth efficiency, - computation complexity, and explicitness: - \begin{itemize} - \item Euler angles are not self-contained, requiring applications to agree on a particular - convention beforehand; a convention would be difficult to establish considering different - demands of various use cases. - - \item Euler angles and fixed axis rotations typically cannot be used for computations directly - due to angular interpolation issues and singularities; thus, to make use of such - representations, one often has to convert them to a different form (e.g., quaternion); - such conversions are computationally heavy. - - \item Rotation matrices are highly redundant. - \end{itemize} -\end{remark} - -\subsection{Matrix representation} - -\subsubsection{General} - -Matrices should be represented as flat arrays in the row-major order. - -\begin{remark} - $ - \begin{bmatrix} - x_{11} & x_{12} & x_{13} \\ - x_{21} & x_{22} & x_{23} \\ - \end{bmatrix} \rightarrow \left(x_{11}, x_{12}, x_{13}, x_{21}, x_{22}, x_{23}\right) - $ -\end{remark} - -\subsubsection{Square matrices} - -There are standard compressed representations of an $n \times n$ square matrix. - -An array of size $n^2$ represents a full square matrix. -This is equivalent to the general case reviewed above. - -An array of $\frac{(1 + n) n}{2}$ elements represents a symmetric matrix, -where array members represent the elements of the upper-right triangle arranged in the row-major order. -\begin{remark} - $ - \begin{bmatrix} - a & b & c \\ - b & d & e \\ - c & e & f \\ - \end{bmatrix} \rightarrow \left(a, b, c, d, e, f\right) - $ - - This form is well-suited for covariance matrix representation. -\end{remark} - -An array of $n$ elements represents a diagonal matrix, -where an array member at position $i$ (where $i=1$ for the first element) -represents the matrix element $x_{i, i}$ (where $x_{1, 1}$ is the upper-left element). -\begin{remark} - $ - \begin{bmatrix} - a & 0 & 0 \\ - 0 & b & 0 \\ - 0 & 0 & c \\ - \end{bmatrix} \rightarrow \left(a, b, c\right) - $ -\end{remark} - -An array of one element represents a scalar matrix. -\begin{remark} - $ - \begin{bmatrix} - a & 0 & 0 \\ - 0 & a & 0 \\ - 0 & 0 & a \\ - \end{bmatrix} \rightarrow a - $ -\end{remark} - -An empty array represents a zero matrix. - -\subsubsection{Covariance matrices} - -A zero covariance matrix represents an unknown covariance\footnote{% - As described above, an empty array represents a zero matrix, - from which follows that an empty array represents unknown covariance. -}. - -Infinite error variance means that the associated value is undefined. - -\subsection{Physical quantity representation} - -\subsubsection{Units} - -All units should be SI\footnote{International System of Units.} units (base or derived). -Usage of any other units is strongly discouraged. - -When defining data types, fields and constants that represent unscaled quantities in SI units -should not have suffixes indicating the unit, since that would be redundant. - -On the other hand, fields and constants that contain quantities in -non-SI units\footnote{E.g., degree Celsius instead of kelvin.} -or scaled SI units\footnote{E.g., microsecond instead of second.} -should be suffixed with the standard abbreviation of the unit\footnote{E.g., kg for kilogram, J for joule.} -and its metric prefix\footnote{E.g., M for mega, n for nano.} -(if any), maintaining the proper letter case of the abbreviation. -In other words, the letter case of the suffix is independent of the letter case of -the attribute it is attached to. - -Scaling coefficients should not be chosen arbitrarily; -instead, the choice should be limited to the standard metric prefixes defined by the -International System of Units. - -All standard metric prefixes have well-defined abbreviations that are constructed from ASCII characters, -except for one: the micro prefix is abbreviated as a Greek letter ``\textmu{}'' (mu). -When defining data types, ``\textmu{}'' should be replaced with the lowercase Latin letter ``u''. - -Irrespective of the suffix, it is recommended to always specify units for every field in the comments. - -\begin{remark} - \begin{minted}{python} - float16 temperature # [kelvin] Suffix not needed because an unscaled SI unit is used here. - - uint24 delay_us # [microsecond] Scaled SI unit, suffix required. Mu replaced with "u". - uint24 MAX_DELAY_us = 600000 # [microsecond] Notice the letter case. - - float32 kinetic_energy_GJ # [gigajoule] Notice the letter case. - - float16 estimated_charge_mAh # [milliampere hour] Scaled non-SI unit. Discouraged, use coulomb. - float16 MAX_CHARGE_mAh = 1e4 # [milliampere hour] Notice the letter case. - \end{minted} -\end{remark} - -\subsubsection{Enhanced type safety} - -In the interest of improving type safety and reducing the possibility of a human error, -it is recommended to avoid reliance on raw scalar types (such as \verb|float32|) -when defining fields containing physical quantities. -Instead, the explicitly typed alternatives defined in the standard DSDL namespace -\DSDLReference{uavcan.si.unit} (also see section~\ref{sec:application_functions_si}) should be used. - -\begin{remark} - \begin{minted}{python} - float32[4] kinetic_energy # [joule] Not recommended. - uavcan.si.unit.energy.Scalar.1.0[4] kinetic_energy # This is the recommended practice. - # Kinetic energy of four bodies. - - float32[3] velocity # [meter/second] Not recommended. - uavcan.si.unit.velocity.Vector3.1.0 # This is the recommended practice. - # 3D velocity vector. - \end{minted} -\end{remark} diff --git a/specification/application/functions.tex b/specification/application/functions.tex deleted file mode 100644 index a906aace..00000000 --- a/specification/application/functions.tex +++ /dev/null @@ -1,313 +0,0 @@ -\section{Application-level functions}\label{sec:application_functions} - -This section documents the high-level functionality defined by Cyphal. -The common high-level functions defined by the specification span across different application domains. -All of the functions defined in this section are optional (not mandatory to implement), -except for the node heartbeat feature (section~\ref{sec:application_functions_heartbeat}), -which is mandatory for all Cyphal nodes. - -The detailed specifications for each function are provided in the DSDL comments of the data type definitions -they are built upon, whereas this section serves as a high-level overview and index. - -\subsection{Node initialization} - -Cyphal does not require that nodes undergo any specific initialization upon connection to the bus --- -a node is free to begin functioning immediately once it is powered up. -The operating mode of the node (such as initialization, normal operation, maintenance, and so on) -is to be reflected via the mandatory heartbeat message described in section~\ref{sec:application_functions_heartbeat}. - -\subsection{Node heartbeat}\label{sec:application_functions_heartbeat} - -Every non-anonymous Cyphal node shall report its status and presence by periodically publishing messages of type -\DSDLReference{uavcan.node.Heartbeat} at a fixed rate specified in the message definition using the fixed subject-ID. -Anonymous nodes shall not publish to this subject. - -This is the only high-level protocol function that Cyphal nodes are required to support. -All other data types and application-level functions are optional. - -\DSDL{uavcan.node.Heartbeat} - -\subsection{Generic node information} - -The service \DSDLReference{uavcan.node.GetInfo} can be used to obtain generic information about the node, -such as the structured name of the node (which includes the name of its vendor), -a 128-bit globally unique identifier, the version information about its hardware and software, -version of the Cyphal specification implemented on the node, and the optional certificate of authenticity. - -While the service is, strictly speaking, optional, omitting its support is highly discouraged, -since it is instrumental for network discovery, firmware update, and various maintenance and diagnostic needs. - -\DSDL{uavcan.node.GetInfo} - -\subsection{Bus data flow monitoring} - -Interfaces defined in the namespace \DSDLReference{uavcan.node.port} (see table~\ref{table:dsdl:uavcan.node.port}) -facilitate network inspection and monitoring. - -By comparing the data obtained with the help of these interfaces from each node on the bus, -the caller can reconstruct the data exchange graph for the entire bus -(assuming that every node on the bus supports the services in question; they are not mandatory). - -\DSDL{uavcan.node.port.* --index-only} - -\subsection{Network-wide time synchronization} - -Cyphal provides a simple and robust method of time synchronization% -\footnote{The ability to accurately synchronize time between nodes is instrumental for building distributed -real-time data processing systems such as various robotic applications, autopilots, autonomous driving solutions, -and so on.} that is built upon the work -``Implementing a Distributed High-Resolution Real-Time Clock using the CAN-Bus'' -published by M.~Gergeleit and H.~Streich% -\footnote{Proceedings of the 1st international CAN-Conference 94, Mainz, -13.-14. Sep. 1994, CAN in Automation e.V., Erlangen.}. -The detailed specification of the time synchronization algorithm is provided in the documentation -for the message type \DSDLReference{uavcan.time.Synchronization}. - -\DSDLReference{uavcan.time.GetSynchronizationMasterInfo} provides nodes with information about -the currently used time system and related data like the number of leap seconds added. - -Redundant time synchronization masters are supported for the benefit of high-reliability applications. - -\begin{remark} - Time synchronization with explicit sensor feed timestamping should be preferred over inferior alternatives - such as sensor lag reporting that are sometimes found in simpler systems because such alternatives - are difficult to scale and they do not account for the delays introduced by communication interfaces. - - It is the duty of every node that publishes timestamped data to account for its own internal delays; - for example, if the data latency of a local sensor is known, - it needs to be accounted for in the reported timestamp value. -\end{remark} - -\DSDL{uavcan.time.* --index-only} - -\subsection{Primitive types and physical quantities} - -The namespaces \DSDLReference{uavcan.si} and \DSDLReference{uavcan.primitive} -included in the standard data type set are designed to provide a generic and flexible -method of real-time data exchange. However, these are not bandwidth-efficient. - -Generally, applications where the bus bandwidth and latency are important should minimize their reliance -on these generic data types and favor more specialized types instead that are custom-designed for their -particular use cases; e.g., vendor-specific types or application-specific types, either -designed in-house, published by third parties\footnote{As long as the license permits.}, or supplied by -vendors of COTS equipment used in the application. - -Vendors of COTS equipment are recommended to ensure that some minimal functionality is available -via these generic types without reliance on their vendor-specific types (if there are any). -This is important for reusability, because some of the systems where such COTS nodes are -to be integrated may not be able to easily support vendor-specific types. - -\subsubsection{SI namespace}\label{sec:application_functions_si} - -The \verb|si| namespace is named after the International System of Units (SI). -The namespace contains a collection of scalar and vector value types that describe most commonly used -physical quantities in SI; for example, velocity, mass, energy, angle, and time. -The objective of these types is to permit construction of arbitrarily complex distributed control systems without -reliance on any particular vendor-specific data types. - -The namespace \DSDLReference{uavcan.si.unit} contains basic units that can be used as type-safe wrappers -over \verb|float32| and other scalar and array types. -The namespace \DSDLReference{uavcan.si.sample} contains time-stamped versions of the same. - -Each message type defined in the namespace \verb|uavcan.si.sample| contains a timestamp field of type -\DSDLReference{uavcan.time.SynchronizedTimestamp}. -Every emitted message should be timestamped in order to allow subscribers to identify which of the messages -relate to the same event or to the same instant. -Messages that are emitted in bulk in relation to the same event or the same instant should contain -exactly the same value of the timestamp to simplify the task of timestamp matching for the subscribers. - -The exact strategy of matching related messages by timestamp employed by subscribers is entirely -implementation-defined. -The specification does not concern itself with this matter because it is expected that different applications -will opt for different design trade-offs and different tactics to suit their constraints. -Such diversity is not harmful, because its effects are always confined to the local node and cannot affect -operation of other nodes or their compatibility. - -Tables~\ref{table:dsdl:uavcan.si.unit} and~\ref{table:dsdl:uavcan.si.sample} -provide a high-level overview of the SI namespace. -Please follow the references for details. - -\DSDL{uavcan.si.unit.* --index-only} - -\DSDL{uavcan.si.sample.* --index-only} - -\subsubsection{Primitive namespace} - -The primitive namespace contains a collection of primitive types: -integer types, floating point types, bit flag, string, raw block of bytes, and an empty value. -Integer, floating point, and bit flag types are available in two categories: scalar and array; -the latter are limited so that their serialized representation is never larger than 257 bytes. - -The primitive types are designed to complement the SI namespace with an even simpler set of basic types -that do not make any assumptions about the meaning of the data they describe. -The primitive types provide a very high degree of flexibility, but due to their lack of semantic information, -their use carries the risk of creating suboptimal interfaces that are difficult to use, validate, and scale. - -Normally, the use of primitive types should be limited to very basic vendor-neutral interfaces for COTS -equipment and software, debug and diagnostic purposes, and whenever there is a need to exchange data the -type of which cannot be determined statically.\footnote{% - An example of the latter use case is the register protocol described - in section~\ref{sec:application_functions_register}. -} - -Table~\ref{table:dsdl:uavcan.primitive} provides a high-level overview of the primitive namespace. -Please follow the references for details. - -\DSDL{uavcan.primitive.* --index-only} - -\subsection{Remote file system interface}\label{sec:application_functions_file_system} - -The set of standard data types contains a collection of services for manipulation of remote file systems -(namespace \DSDLReference{uavcan.file}, see table~\ref{table:dsdl:uavcan.file}). -All basic file system operations are supported, including file reading and writing, -directory listing, metadata retrieval (size, modification time, etc.), moving, renaming, creating, and deleting. - -The set of supported operations may be extended in future versions of the protocol. - -Implementers of file servers are strongly advised to always support services \verb|Read| and \verb|GetInfo|, -as that allows clients to make assumptions about the minimal degree of available service. -If write operations are required, all of the defined services should be supported. - -\DSDL{uavcan.file.* --index-only} - -\subsection{Generic node commands}\label{sec:application_functions_generic_commands} - -Commonly used node-level application-agnostic auxiliary commands -(such as: restart, power off, factory reset, emergency stop, etc.) -are supported via the standard service \DSDLReference{uavcan.node.ExecuteCommand}. -The service also allows vendors to define vendor-specific commands alongside the standard ones. - -It is recommended to support this service in all nodes. - -\subsection{Node software update} - -A simple software\footnote{Or firmware -- Cyphal does not distinguish between the two.} -update protocol is defined on top of the remote file system interface -(section~\ref{sec:application_functions_file_system}) -and the generic node commands (section~\ref{sec:application_functions_generic_commands}). - -The software update process involves the following data types: - -\begin{itemize} - \item \DSDLReference{uavcan.node.ExecuteCommand} -- used to initiate the software update process. - \item \DSDLReference{uavcan.file.Read} -- used to transfer the software image file(s) - from the file server to the updated node. -\end{itemize} - -The software update protocol logic is described in detail in the documentation for the data type -\DSDLReference{uavcan.node.ExecuteCommand}. -The protocol is considered simple enough to be usable in embedded bootloaders with -small memory-constrained microcontrollers. - -\subsection{Register interface}\label{sec:application_functions_register} - -Cyphal defines the concept of \emph{named register} -- a scalar, vector, or string value with an associated -human-readable name that is stored on a Cyphal node locally and is accessible via -Cyphal\footnote{And, possibly, other interfaces.} for reading and/or modification -by other nodes on the bus. - -Named registers are designed to serve the following purposes: -\begin{description} - \item[Node configuration parameter management] --- Named registers can be used to expose persistently stored - values that define behaviors of the local node. - - \item[Diagnostics and monitoring] --- Named registers can be used to expose internal states (variables) of - the node's decision-making and data processing logic (implemented in software or hardware) to provide - insights about its inner processes. - - \item[Advanced node information reporting] --- Named registers can store any invariants provided by the vendor, - such as calibration coefficients or unique identifiers. - - \item[Special functions] --- Non-persistent named registers can be used to trigger specific behaviors or - start predefined operations when written. - - \item[Advanced debugging] --- Registers following a specific naming pattern can be used to provide direct read - and write access to the local node's application software to facilitate in-depth debugging and monitoring. -\end{description} - -The register protocol rests upon two service types defined in the namespace \DSDLReference{uavcan.register}; -the namespace index is shown in table~\ref{table:dsdl:uavcan.register}. -Data types supported by the register protocol are defined in the nested data structure -\DSDLReference{uavcan.register.Value}. - -The Cyphal specification defines several standard naming patterns to facilitate cross-vendor compatibility -and provide a framework of common basic functionality. - -\DSDL{uavcan.register.* --index-only} - -\subsection{Diagnostics and event logging} - -The message type \DSDLReference{uavcan.diagnostic.Record} is designed to facilitate emission of -human-readable diagnostic messages and event logging, -both for the needs of real-time display\footnote{E.g., messages displayed to a human operator/pilot in real time.} -and for long-term storage\footnote{E.g., flight data recording.}. - -\subsection{Plug-and-play nodes}\label{sec:application_functions_pnp} - -Every Cyphal node shall have a node-ID that is unique within the network (excepting anonymous nodes). -Normally, such identifiers are assigned by the network designer, integrator, some automatic external tool, -or another entity that is external to the network. -However, there exist circumstances where such manual assignment is either difficult or undesirable. - -Nodes that can join any Cyphal network automatically without any prior manual configuration -are called \emph{plug-and-play nodes} (or \emph{PnP nodes} for brevity). - -Plug-and-play nodes automatically obtain a node-ID and deduce all necessary parameters of the physical layer -such as the bit rate. - -Cyphal defines an automatic node-ID allocation protocol that is built on top of the data types defined in the -namespace \DSDLReference{uavcan.pnp} (where \emph{pnp} stands for ``plug-and-play'') -(see table~\ref{table:dsdl:uavcan.pnp}). -The protocol is described in the documentation for the data type \DSDLReference{uavcan.pnp.NodeIDAllocationData}. - -The plug-and-play node-ID allocation protocol relies on anonymous messages reviewed in section -\ref{sec:transport_route_specifier}. -Remember that the plug-and-play feature is entirely optional and it is expected that applications where a -high degree of determinism and robustness is expected are unlikely to benefit from it. - -This feature derives from the work -``In search of an understandable consensus algorithm''% -\footnote{Proceedings of USENIX Annual Technical Conference, p. 305-320, 2014.} -by Diego Ongaro and John Ousterhout. - -\DSDL{uavcan.pnp.* --index-only} - -\subsection{Internet/LAN forwarding interface} - -Data types defined in the namespace \DSDLReference{uavcan.internet} (see table~\ref{table:dsdl:uavcan.internet}) -are designed for establishing robust direct connectivity between local Cyphal nodes and hosts on the Internet -or on a local area network (LAN) using \emph{modem nodes}\footnote{% - Usually such modem nodes are implemented using on-board cellular, radio frequency, - or satellite communication hardware. -} (possibly redundant). - -This basic support for world-wide communication provided at the protocol level allows any component -of a vehicle equipped with modem nodes to reach external resources or exchange arbitrary data globally -without depending on an application-specific means of communication\footnote{% - Information security and other security-related concerns are outside of the scope of this specification. -}. - -The set of supported Internet/LAN protocols may be extended in future revisions of the specification. - -\begin{remark} - Some of the major applications for this feature are as follows: - \begin{enumerate} - \item Direct telemetry transmission from Cyphal nodes to a remote data collection server. - \item Implementation of remote API for on-board equipment (e.g., web interface). - \item Reception of real-time correction data streams (e.g., RTCM RC-104) - for precise positioning applications. - \item Automatic upgrades directly from the vendor's Internet resources. - \end{enumerate} -\end{remark} - -\DSDL{uavcan.internet.* --index-only} - -\subsection{Meta-transport}\label{sec:application_functions_metatransport} - -Data types defined in the namespace \DSDLReference{uavcan.metatransport} -(see table~\ref{table:dsdl:uavcan.metatransport}) -are designed for tunneling transport frames\footnote{Section~\ref{sec:transport_model}.} -over Cyphal subjects, -as well as logging Cyphal traffic in the form of serialized Cyphal message objects. - -\DSDL{uavcan.metatransport.* --index-only} diff --git a/specification/application/requirements.tex b/specification/application/requirements.tex deleted file mode 100644 index d29e1dd4..00000000 --- a/specification/application/requirements.tex +++ /dev/null @@ -1,84 +0,0 @@ -\section{Application-level requirements}\label{sec:application_requirements} - -This section describes a set of high-level rules that shall be obeyed by all Cyphal implementations. - -\subsection{Port identifier distribution} - -An overview of related concepts is provided in chapter~\ref{sec:basic}. - -The subject and service identifier values are segregated into three ranges: -\begin{itemize} - \item unregulated port identifiers that can be freely chosen by users and integrators (both fixed and non-fixed); - \item regulated fixed identifiers for non-standard data type definitions - that are assigned by the Cyphal maintainers for publicly released data types; - \item regulated identifiers of the standard data types that are an integral part of the Cyphal specification. -\end{itemize} - -More information on the subject of data type regulation is provided in section~\ref{sec:basic_data_type_regulation}. - -The ranges are summarized in table~\ref{table:application_requirements_port_id_distribution}. -The ranges may be expanded, but not contracted, in a future version of the document. - -\begin{CyphalSimpleTable}{Port identifier distribution}{|l l X|}% - \label{table:application_requirements_port_id_distribution} - Subject-ID & Service-ID & Purpose \\ - $[0, 6143]$ & $[0, 255]$ & Unregulated identifiers (both fixed and non-fixed). \\ - $[6144, 7167]$ & $[256, 383]$ & Non-standard fixed regulated identifiers (i.e., vendor-specific). \\ - $[7168, 8191]$ & $[384, 511]$ & Standard fixed regulated identifiers. \\ -\end{CyphalSimpleTable} - -\subsection{Port compatibility} - -% https://github.com/OpenCyphal/specification/pull/64#discussion_r357771739 - -The system integrator shall ensure that nodes participating in data exchange via a given port\footnote{% - I.e., subject or service. -} use data type definitions that are sufficiently congruent so that the resulting behavior of the involved nodes -is predictable and the possibility of unintended behaviors caused by misinterpretation of exchanged serialized -objects is eliminated. - -\begin{remark} - Let there be type $A$: - - \begin{minted}{python} - void1 - uint7 demand_factor_pct # [percent] - # Values above 100% are not allowed. - \end{minted} - - And type $B$: - - \begin{minted}{python} - uint8 demand_factor_pct # [percent] - # Values above 100% indicate overload. - \end{minted} - - The data types are not semantically compatible, but they can be used on the same subject nevertheless: - a subscriber expecting $B$ can accept $A$. - The reverse is not true, however. - - This example shows that even semantically incompatible types can facilitate - behaviorally correct interoperability of nodes. -\end{remark} - -\begin{remark} - Compatibility of subjects and services is completely independent from the names of the involved data types. - Data types can be moved between namespaces and freely renamed and re-versioned - without breaking compatibility with existing deployments. - Nodes provided by different vendors that utilize differently named data types may - still interoperate if such data types happen to be compatible. - The duty of ensuring the compatibility lies on the system integrator. -\end{remark} - -\subsection{Standard namespace} - -An overview of related concepts is provided in chapter~\ref{sec:dsdl}. - -This specification defines a set of standard regulated DSDL data types located under -the root namespace named ``\verb"uavcan"''% -\footnote{The standard root namespace is named \texttt{uavcan}, not \texttt{cyphal}, for historical reasons.} -(section~\ref{sec:sdt}). - -Vendor-specific, user-specific, or any other data types not defined by this specification -shall not be defined inside the standard root namespace\footnote{Custom data type definitions shall be located -inside vendor-specific or user-specific namespaces instead.}. From 0a2fc10c7643476b8704272b892c4abf7652e3d2 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 12:30:29 +0300 Subject: [PATCH 03/14] cleanup --- README.md | 6 - requirements.txt | 3 - ...cyclic_transfer_id_redundant_transport.pdf | Bin 11439 -> 0 bytes specification/transport/serial/serial.tex | 232 ------------------ specification/transport/transport.tex | 1 - 5 files changed, 242 deletions(-) delete mode 100644 requirements.txt delete mode 100644 specification/transport/cyclic_transfer_id_redundant_transport.pdf delete mode 100644 specification/transport/serial/serial.tex diff --git a/README.md b/README.md index 6dab1e4d..ddd0f6a0 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,6 @@ Avoid introduction of new acronyms unless you really must. It is better to say "transfer-ID" or "data type hash" rather than TID or DTH. Excessive reliance on acronyms may impede comprehension. -### Rigging - -In order to refer to a DSDL definition, use macro `\DSDLReference{}`. -To include a DSDL definition or a namespace index, use macro `\DSDL{}`. -Refer to the existing usages for an example. - ### Structure and cross-referencing Each chapter is located in a dedicated directory, whose name is a single lowercase word. diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 99144485..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# This file lists the Python dependencies that are necessary to build the specification. -pygments -pydsdl ~= 1.16 diff --git a/specification/transport/cyclic_transfer_id_redundant_transport.pdf b/specification/transport/cyclic_transfer_id_redundant_transport.pdf deleted file mode 100644 index 89e98daa809b886956eb8da6a26031b8ece856e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11439 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 diff --git a/specification/transport/serial/serial.tex b/specification/transport/serial/serial.tex deleted file mode 100644 index 04e41b6a..00000000 --- a/specification/transport/serial/serial.tex +++ /dev/null @@ -1,232 +0,0 @@ -\section{Cyphal/serial (experimental)}\label{sec:transport_serial} - -\hyphenation{Cyphal/serial} % Disable hyphenation. - -\subsection{Overview} - -This section specifies a concrete transport that operates on top of raw byte-level communication channels, -such as TCP/IP connections, SSL, UART, RS-232, RS-422, USB CDC ACM, -and any similar communication links that allow exchange of unstructured byte streams. -Cyphal/serial may also be used to store Cyphal frames in files. -\textbf{% - As of this version, the Cyphal/serial specification remains experimental. - Breaking changes affecting wire compatibility are possible. -} - -As Cyphal/serial is designed to operate over unstructured byte streams, -it defines a custom framing protocol, custom frame header format, and a custom integrity checking mechanism. - -\begin{CyphalSimpleTable}{Cyphal/serial transport capabilities\label{table:transport_serial_capabilities}}{|l X l|} - Parameter & Value & References \\ - - Maximum node-ID value & - 65534 (16 bits wide). & - \ref{sec:basic} \\ - - Transfer-ID mode & - Monotonic, 64 bits wide. & - \ref{sec:transport_transfer_id} \\ - - Number of transfer priority levels & - 8 (no additional levels). & - \ref{sec:transport_transfer_priority} \\ - - Largest single-frame transfer payload & - Unlimited. & - \ref{sec:transport_transfer_payload} \\ - - Anonymous transfers & - Available. & - \ref{sec:transport_route_specifier} \\ -\end{CyphalSimpleTable} - -\subsection{Framing} - -Cyphal/serial uses the ``Consistent Overhead Byte Stuffing'' (COBS) encapsulation method\footnote{% - Stuart Cheshire and Mary Baker. 1999. Consistent overhead Byte stuffing. - IEEE/ACM Trans. Netw. 7, 2 (April 1999), 159--172. \mbox{\url{https://doi.org/10.1109/90.769765}}. - The COBS overhead is 1 byte in every 254 bytes of encapsulated data, which is about 0.4\%. -} with zero byte as the frame delimiter. -Due to the nature of COBS, the frame delimiter will not appear in the frame payload. -A frame delimiter may terminate a frame and/or indicate the start of a new frame. -The number of frame delimiters between adjacent frames may exceed one. - -\begin{figure}[H] - \centering - $$ - \texttt{\huge{...}}% - \underbrace{\texttt{\huge{0}}}_{\substack{\text{frame} \\ \text{delimiter}}}% - \underbrace{\texttt{\huge{}}}_{\substack{\text{COBS-encoded} \\ \text{frame contents}}}% - \underbrace{\texttt{\huge{0}}}_{\substack{\text{frame} \\ \text{delimiter}}}% - \underbrace{\texttt{\huge{}}}_{\substack{\text{COBS-encoded} \\ \text{frame contents}}}% - \underbrace{\texttt{\huge{0}}}_{\substack{\text{frame} \\ \text{delimiter}}}% - \texttt{\huge{...}}% - $$ - \caption{COBS framing\label{fig:transport_serial_cobs}} -\end{figure} - -A frame consists of two parts: -the fixed-size header (section~\ref{sec:transport_serial_header}) -immediately followed by the payload (section~\ref{sec:transport_serial_payload}). -The header contains a dedicated header-CRC field which allows implementations to detect frame corruption early. - -Neither the underlying medium nor the Cyphal/serial transport layer impose any restrictions on the maximum frame size, -which allows all Cyphal/serial transfers to be single-frame transfers\footnote{% - Omitting multi-frame transfers as a requirement is expected to simplify implementations. -}. - -\subsection{Header}\label{sec:transport_serial_header} - -The layout of the Cyphal/serial header is shown in the following snippet in DSDL notation -(section~\ref{sec:dsdl}). - -\begin{samepage} -\begin{minted}{python} -# This 24-byte header can be aliased as a C structure with each field being naturally aligned: -# -# uint8_t version; -# uint8_t priority; -# uint16_t source_node_id; -# uint16_t destination_node_id; -# uint16_t data_specifier_snm; -# uint64_t transfer_id; -# uint32_t frame_index_eot; -# uint16_t user_data; -# uint8_t header_crc16_big_endian[2]; - -uint4 version -# The version of the header format. This document specifies version 1. -# Packets with an unknown version number must be ignored. - -void4 - -uint3 priority -# The values are assigned from 0 (HIGHEST priority) to 7 (LOWEST priority). -# The numerical priority identifiers are chosen to be consistent with Cyphal/CAN. - -void5 - -uint16 source_node_id -# The node-ID of the source node. -# Value 65535 represents anonymous transfers. - -uint16 destination_node_id -# The node-ID of the destination node. -# Value 65535 represents broadcast transfers. - -uint15 data_specifier -# If this is a message transfer, this value equals the subject-ID. -# If this is a service response transfer, this value equals the service-ID. -# If this is a service request transfer, this value equals 16384 + service-ID. - -bool service_not_message -# If true, this is a service transfer. If false, this is a message transfer. - -@assert _offset_ == {64} -uint64 transfer_id -# The monotonic transfer-ID value of the current transfer (never overflows). - -uint31 frame_index -# Transmit zero. Drop frame if received non-zero. - -bool end_of_transfer -# Transmit true. Drop frame if received false. - -uint16 user_data -# Opaque application-specific data with user-defined semantics. -# Generic implementations should emit zero and ignore this field upon reception. - -uint8[2] header_crc16_big_endian -# CRC-16/CCITT-FALSE of the preceding serialized header data in the big endian byte order. -# Application of the CRC function to the entire header shall yield zero, otherwise the header is malformed. - -@assert _offset_ / 8 == {24} -@sealed # The payload data follows. -\end{minted} -\end{samepage} - -\subsection{Payload}\label{sec:transport_serial_payload} - -The transfer payload is appended with a transfer CRC field. -The transfer CRC function is \textbf{CRC-32C} (section~\ref{sec:appendix_crc32c}), -and its value is serialized in the little-endian byte order. -The transfer CRC function is applied to the entire transfer payload and only transfer payload (header not included). - -A node receiving a transfer should verify the correctness of its transfer CRC. - -\subsection{Examples} - -% $ ncat --broker --listen -p 50905 -vv -% -% $ nc localhost 50905 | xxd -g1 -% -% $ export UAVCAN__SERIAL__IFACE='socket://127.0.0.1:50905' -% $ export UAVCAN__NODE__ID=1234 -% $ y pub -N1 1234:uavcan.primitive.string '"012345678"' -\begin{remark} - The snippet given below contains the hexadecimal dump of the following Cyphal/serial transfer: - - \begin{description} - \item[Priority] nominal - \item[Transfer-ID] 0 - \item[Transfer kind] message with the subject-ID 1234 - \item[Source node-ID] 1234 - \item[Destination node-ID] None - \item[Header user data] 0 - \item[Transfer payload] \verb|uavcan.primitive.String.1| containing string ``\verb|012345678|'' - \end{description} - - The payload is shown in segments for clarity: - - \begin{itemize} - \item The first byte is the starting delimiter of the first frame. - \item The second byte is a COBS overhead byte (one for the entire transfer). - \item The following block of 24 bytes is the COBS-encoded header. - \item The third-to-last block is the COBS-encoded transfer payload, - containing the two bytes of the array length prefix followed by the string data. - \item The second-to-last block of four bytes is the COBS-encoded transfer-CRC. - \item The last byte is the ending frame delimiter. - \end{itemize} - - \begin{verbatim} -00 -09 -01 04 d2 04 ff ff d2 04 01 01 01 01 01 01 01 01 01 01 02 80 01 04 08 12 -09 0e 30 31 32 33 34 35 36 37 38 -84 a2 2d e2 -00 - \end{verbatim} -\end{remark} - -\begin{remark} - The snippet given below contains the hexadecimal dump of the following Cyphal/serial transfer: - - \begin{description} - \item[Priority] nominal - \item[Transfer-ID] 0 - \item[Transfer kind] message with the subject-ID 1234 - \item[Source node-ID] 4321 - \item[Destination node-ID] None - \item[Header user data] 0 - \item[Transfer payload] \verb|uavcan.primitive.Empty.1| - \end{description} - - The payload is shown in segments for clarity: - - \begin{itemize} - \item The first byte is the starting delimiter of the first frame. - \item The second byte is a COBS overhead byte (one for the entire transfer). - \item The following block of 24 bytes is the COBS-encoded header. - \item The second-to-last block of four bytes is the COBS-encoded transfer-CRC, - which is zero as the payload is empty. - \item The last byte is the ending frame delimiter. - \end{itemize} - - \begin{verbatim} -00 -09 -01 04 e1 10 ff ff d2 04 01 01 01 01 01 01 01 01 01 01 02 80 01 03 93 70 -01 01 01 01 -00 - \end{verbatim} -\end{remark} diff --git a/specification/transport/transport.tex b/specification/transport/transport.tex index cda7792e..b0e7c01f 100644 --- a/specification/transport/transport.tex +++ b/specification/transport/transport.tex @@ -22,4 +22,3 @@ \chapter{Transport layer}\label{sec:transport} \clearpage\input{transport/abstract.tex} \clearpage\input{transport/can/can.tex} \clearpage\input{transport/udp/udp.tex} -\clearpage\input{transport/serial/serial.tex} From 146e27e89a320b322069b5e8b4a0c811d6de27bf Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 13:32:53 +0300 Subject: [PATCH 04/14] removing old parts --- specification/Cyphal_Specification.tex | 37 +- specification/basic/basic.tex | 281 ------- specification/introduction/introduction.tex | 73 +- specification/transport/abstract.tex | 805 -------------------- specification/transport/transport.tex | 162 +++- specification/transport/udp/udp.tex | 300 +------- 6 files changed, 195 insertions(+), 1463 deletions(-) delete mode 100644 specification/basic/basic.tex delete mode 100644 specification/transport/abstract.tex diff --git a/specification/Cyphal_Specification.tex b/specification/Cyphal_Specification.tex index bf15fac7..e4cdaa82 100644 --- a/specification/Cyphal_Specification.tex +++ b/specification/Cyphal_Specification.tex @@ -15,29 +15,7 @@ \urlstyle{same} -% This macro embeds the selected DSDL definition or the contents of a DSDL namespace into the document. -% It accepts one mandatory argument which is either a full DSDL type name, e.g. uavcan.node.Heartbeat, -% or a full type name glob expression, e.g., uavcan.node.*. -\newcommand{\DSDL}[1]{% - % Clean up beforehand to ensure clean initial state. - \immediate\write18{rm -f ../*.tmp}% - % Invoke the target command and save the useful output into a file; ignore error output - \immediate\write18{../render_dsdl.py #1 > ../dsdl.tmp}% - % Now, if the above command has failed, the output file would be empty. We remove empty file to escalate error. - % Escalation is very important as it allows us to abort compilation on failure instead of generating invalid - % documents silently. - \immediate\write18{find .. -type f -name '*.tmp' -size 0 -delete}% - % Read the file. This command fails if the file was empty, which is exactly want we want. - \immediate\input{../dsdl.tmp}% - % Clean up afterwards to prevent accidental reuse if the command fails the next time we invoke it. - \immediate\write18{rm -f ../*.tmp}% -} - -\newcommand{\DSDLReference}[1]{% We use detokenize to permit underscores - \mbox{\texttt{\detokenize{#1}}} (section~\ref{sec:dsdl:#1} on page~\pageref{sec:dsdl:#1})% -} - -\title{Specification v1.0} +\title{Specification v1.1 DRAFT} \hbadness=10000 @@ -56,18 +34,14 @@ \section*{Overview} Features: \begin{itemize} - \item Democratic network -- no bus master, no single point of failure. + \item Democratic network -- no central coordinator, no single point of failure. \item Publish/subscribe and request/response (RPC\footnote{Remote procedure call.}) com\-mu\-ni\-ca\-tion semantics. \item Efficient exchange of large data structures with automatic decomposition and reassembly. \item Lightweight, deterministic, easy to implement, and easy to validate. \item Suitable for deeply embedded, resource constrained, hard real-time systems. \item Supports dual and triply modular redundant transports. - \item Supports high-precision network-wide time syn\-chro\-ni\-za\-tion. - \item Provides rich data type and interface abstractions -- an interface description language is a core part of - the technology which allows deeply embedded sub-systems to interface with higher-level systems directly and - in a maintainable manner while enabling simulation and functional testing. \item The specification and high quality reference implementations in popular programming languages are free, - open source, and available for commercial use under the permissive MIT license. + open source, and available for commercial use under permissive licenses. \end{itemize} \BeginRightColumn @@ -107,13 +81,12 @@ \section*{Limitation of liability} \end{titlepage} \tableofcontents -\clearpage\onecolumn\listoftables -\clearpage\onecolumn\listoffigures +\listoftables +\listoffigures \mainmatter \input{introduction/introduction.tex} -\input{basic/basic.tex} \input{transport/transport.tex} \end{document} diff --git a/specification/basic/basic.tex b/specification/basic/basic.tex deleted file mode 100644 index fe49bd3d..00000000 --- a/specification/basic/basic.tex +++ /dev/null @@ -1,281 +0,0 @@ -\chapter{Basic concepts}\label{sec:basic} - -\section{Main principles} - -\subsection{Communication} - -\subsubsection{Architecture} - -A Cyphal network is a decentralized peer network, where each peer (node) has a unique -numeric identifier\footnote{% - Here and elsewhere in this specification, \emph{ID} and \emph{identifier} are used - interchangeably unless specifically indicated otherwise. -} --- \emph{node-ID} --- ranging from 0 up to a transport-specific upper boundary which is guaranteed to be -not less than 127. -Nodes of a Cyphal network can communicate using the following communication methods: - -\begin{description} - \item[Message publication] --- The primary method of data exchange with one-to-many publish/subscribe semantics. - \item[Service invocation] --- The communication method for one-to-one request/response - interactions\footnote{Like remote procedure call (RPC).}. -\end{description} - -For each type of communication, a predefined set of data types is used, -where each data type has a unique name. -Additionally, every data type definition has a pair of major and minor version numbers, -which enable data type definitions to evolve in arbitrary ways while ensuring a well-defined -migration path if backward-incompatible changes are introduced. -Some data types are standard and defined by the protocol specification (of which only a small -subset are required); others may be specific to a particular application or vendor. - -\subsubsection{Subjects and services}\label{sec:basic_subjects_and_services} - -Message exchanges between nodes are grouped into \emph{subjects} by the semantic meaning of the message. -Message exchanges belonging to the same subject pertain to the same function or process within the system. - -Request/response exchanges between nodes are grouped into \emph{services} by the semantic meaning -of the request and response, like messages are grouped into subjects. -Requests and their corresponding responses that belong to the same service pertain to -the same function or process within the system. - -Each message subject is identified by a unique natural number -- a \emph{subject-ID}; -likewise, each service is identified by a unique \emph{service-ID}. -An umbrella term \emph{port-ID} is used to refer either to a subject-ID or to a service-ID -(port identifiers have no direct manifestation in the construction of the protocol, -but they are convenient for discussion). -The sets of subject-ID and service-ID are orthogonal. - -Port identifiers are assigned to various functions, processes, or data streams within the network -at the system definition time. -Generally, a port identifier can be selected arbitrarily by a system integrator -by changing relevant configuration parameters of connected nodes, -in which case such port identifiers are called \emph{non-fixed port identifiers}. -It is also possible to permanently associate any data type definition with a particular port identifier -at a data type definition time, -in which case such port identifiers are called \emph{fixed port identifiers}; -their usage is governed by rules and regulations described in later sections. - -A port-ID used in a given Cyphal network shall not be shared between functions, processes, or data streams -that have different semantic meaning. - -A data type of a given major version can be used simultaneously with -an arbitrary number of non-fixed different port identifiers, -but not more than one fixed port identifier. - -\subsection{Data types} - -\subsubsection{Data type definitions} - -Message and service types -are defined using the \emph{data structure description language} (DSDL) (chapter~\ref{sec:dsdl}). -A DSDL definition specifies the name, major version, minor version, attributes, -and an optional fixed port-ID of the data type among other less important properties. -Service types define two inner data types: one for request, and the other for response. - -\subsubsection{Regulation}\label{sec:basic_data_type_regulation} - -Data type definitions can be created by the Cyphal specification maintainers or by its users, -such as equipment vendors or application designers. -Irrespective of the origin, data types can be included into the set of data type definitions maintained -and distributed by the Cyphal specification maintainers; -definitions belonging to this set are termed \emph{regulated data type definitions}. -The specification maintainers undertake to keep regulated definitions well-maintained and may occasionally -amend them and release new versions, if such actions are believed to benefit the protocol. -User-created (i.e., vendor-specific or application-specific) data type definitions that are -not included into the aforementioned set are called \emph{unregulated data type definitions}. - -Unregulated definitions that are made available for reuse by others are called -\emph{unregulated public data type definitions}; -those that are kept closed-source for private use by their authors are called -\emph{(unregulated) private data type definitions}\footnote{% - The word ``unregulated'' is redundant because private data types cannot be regulated, by definition. - Likewise, all regulated definitions are public, so the word ``public'' can be omitted. -}. - -Data type definitions authored by the specification maintainers for the purpose of supporting and advancing -this specification are called \emph{standard data type definitions}. -All standard data type definitions are regulated. - -Fixed port identifiers can be used only with regulated data type definitions or with private definitions. -Fixed port identifiers shall not be used with public unregulated data types, -since that is likely to cause unresolvable port identifier collisions\footnote{% - Any system that relies on data type definitions with fixed port identifiers provided by - an external party (i.e., data types and the system in question are designed by different parties) - runs the risk of encountering port identifier conflicts that cannot be resolved without resorting to help - from said external party since the designers of the system do not have control over their fixed port identifiers. - Because of this, the specification strongly discourages the use of fixed unregulated private port identifiers. - If a data type definition is ever disclosed to any other party (i.e., a party that did not author it) - or to the public at large it is important that the data type \emph{not} include a fixed port-identifier. -}. -This restriction shall be followed at all times by all compliant implementations and -systems\footnote{% - In general, private unregulated fixed port identifiers are collision-prone by their nature, so they should - be avoided unless there are very strong reasons for their usage and the authors fully understand the risks. -}. - -\begin{CyphalSimpleTable}{Data type taxonomy}{|l|X|X|} - & Regulated & Unregulated \\ - \bfseries{Public} - & - Standard and contributed (e.g., vendor-specific) definitions.\newline - Fixed port identifiers are allowed; they are called \emph{regulated port-ID}. - & - Definitions distributed separately from the Cyphal specification.\newline - Fixed port identifiers are \emph{not allowed}. - \\ - - \bfseries{Private} - & - Nonexistent category. - & - Definitions that are not available to anyone except their authors.\newline - Fixed port identifiers are permitted (although not recommended); - they are called \emph{unregulated fixed port-ID}. - \\ -\end{CyphalSimpleTable} - -DSDL processing tools shall prohibit unregulated fixed port identifiers by default, -unless they are explicitly configured otherwise. - -Each of the two sets of port identifiers (which are subject identifiers and service identifiers) are -segregated into three categories: - -\begin{itemize} - \item Application-specific port identifiers. - These can be assigned by changing relevant configuration parameters of the connected nodes - (in which case they are called \emph{non-fixed}), - or at the data type definition time (in which case they are called \emph{fixed unregulated}, - and they generally should be avoided due to the risks of collisions as explained earlier). - - \item Regulated non-standard fixed port identifiers. - These are assigned by the specification maintainers for non-standard contributed - vendor-specific public data types. - - \item Standard fixed port identifiers. These are assigned by the specification maintainers - for standard regulated public data types. -\end{itemize} - -Data type authors that want to release regulated data type definitions or contribute to the standard data -type set should contact the Cyphal maintainers for coordination. -The maintainers will choose unoccupied fixed port identifiers for use with the new definitions, if necessary. -Since the set of regulated definitions is maintained in a highly centralized manner, -it can be statically ensured that no identifier collisions will take place within it; -also, since the identifier ranges used with regulated definitions are segregated, -regulated port-IDs will not conflict with any other compliant Cyphal node or system\footnote{% - The motivation for the prohibition of fixed port identifiers in unregulated public data types is - derived directly from the above: since there is no central repository of unregulated definitions, - collisions would be likely. -}. - -\subsubsection{Serialization} - -A DSDL description can be used to automatically generate the serialization and deserialization code -for every defined data type in a particular programming language. -Alternatively, a DSDL description can be used to construct appropriate serialization code manually by a human. -DSDL ensures that the memory footprint and computational complexity per data type -are constant and easily predictable. - -Serialized message and service objects\footnote{% - An \emph{object} means a value that is an instance of a well-defined type. -} -are exchanged by means of the transport layer (chapter~\ref{sec:transport}), -which implements automatic decomposition of long transfers into several transport frames\footnote{% - A \emph{transport frame} means a block of data that can be atomically exchanged over the transport layer network, - e.g., a CAN frame. -} and reassembly from these transport frames -back into a single atomic data block, allowing nodes to exchange serialized objects of -arbitrary size (DSDL guarantees, however, that the minimum and maximum size of the serialized representation -of any object of any data type is always known statically). - -\subsection{High-level functions} - -On top of the standard data types, Cyphal defines a set of standard high-level functions including: -node health monitoring, node discovery, time synchronization, firmware update, -plug-and-play node support, and more (section~\ref{sec:application_functions}). - -\begin{figure}[hbt] - \centering - \begin{tabular}{|c|c|l|c|l|c|} - \hline - \multicolumn{6}{|c|}{Applications} \\ \hline - - \qquad{} & Required functions & - \qquad{} & Standard functions & - \qquad{} & Custom functions \\ - \cline{2-2} \cline{4-4} \cline{6-6} - - \multicolumn{2}{|c|}{Required data types} & - \multicolumn{2}{c|}{Standard data types} & - \multicolumn{2}{c|}{Custom data types} \\ \hline - - \multicolumn{6}{|c|}{Serialization} \\ \hline - - \multicolumn{6}{|c|}{Transport} \\ \hline - \end{tabular} - \caption{Cyphal architectural diagram\label{fig:basic_architecture}} -\end{figure} - -\section{Message publication} - -Message publication refers to the transmission of a serialized message object over the network to other nodes. -This is the primary data exchange mechanism used in Cyphal; -it is functionally similar to raw data exchange with minimal overhead, -additional communication integrity guarantees, and automatic decomposition and reassembly of long payloads -across multiple transport frames. -Typical use cases may include transfer of the following kinds of data (either cyclically or on an ad-hoc basis): -sensor measurements, actuator commands, equipment status information, and more. - -Information contained in a published message is summarized in table~\ref{table:basic_message}. - -\begin{CyphalSimpleTable}{Published message properties}{|l X|}\label{table:basic_message} - Property & Description \\ - Payload & The serialized message object. \\ - Subject-ID & Numerical identifier that indicates how the payload should be interpreted. \\ - Source node-ID & The node-ID of the transmitting node (excepting anonymous messages). \\ - Transfer-ID & An integer value that is used for message sequence monitoring, - multi-frame transfer reassembly, deduplication, automatic management of redundant transports, - and other purposes (section~\ref{sec:transport_transfer_id}). \\ -\end{CyphalSimpleTable} - -\subsection{Anonymous message publication} - -Nodes that don't have a unique node-ID can publish only \emph{anonymous messages}. -An anonymous message is different from a regular message in that it doesn't contain a source node-ID. - -Cyphal nodes will not have an identifier initially until they are assigned one, -either statically (which is generally the preferred option for applications where a high degree of -determinism and high safety assurances are required) or automatically (i.e., plug-and-play). -Anonymous messages are used to facilitate the plug-and-play function (section~\ref{sec:application_functions_pnp}). - -\section{Service invocation} - -Service invocation is a two-step data exchange operation between exactly two nodes: a client and a server. -The steps are\footnote{% - The request/response semantic is facilitated by means of hardware (if available) - or software acceptance filtering and higher-layer logic. - No additional support or non-standard transport layer features are required. -}: - -\begin{enumerate} - \item The client sends a service request to the server. - \item The server takes appropriate actions and sends a response to the client. -\end{enumerate} - -Typical use cases for this type of communication include: -node configuration parameter update, firmware update, an ad-hoc action request, file transfer, -and other functions of similar nature. - -Information contained in service requests and responses is summarized in table~\ref{table:basic_service}. -Both the request and the response contain same values for all listed fields except payload, -where the content is application-defined. - -\begin{CyphalSimpleTable}{Service request/response properties}{|l X|}\label{table:basic_service} - Property & Description \\ - Payload & The serialized request/response object. \\ - Service-ID & Numerical identifier that indicates how the service should be handled. \\ - Client node-ID & Source node-ID during request transfer, destination node-ID during response transfer. \\ - Server node-ID & Destination node-ID during request transfer, source node-ID during response transfer. \\ - Transfer-ID & An integer value that is used for request/response matching, - multi-frame transfer reassembly, deduplication, automatic management of redundant transports, - and other purposes (section~\ref{sec:transport_transfer_id}). \\ -\end{CyphalSimpleTable} diff --git a/specification/introduction/introduction.tex b/specification/introduction/introduction.tex index 53a2e0ce..ce533cb9 100644 --- a/specification/introduction/introduction.tex +++ b/specification/introduction/introduction.tex @@ -11,10 +11,6 @@ \section{Overview} It is created to address the challenge of deterministic on-board data exchange between systems and components of next-generation intelligent vehicles: manned and unmanned aircraft, spacecraft, robots, and cars. -Cyphal can be approximated as a highly deterministic decentralized object request broker -with a specialized interface description language and a highly efficient data serialization format -suitable for use in real-time safety-critical systems with optional modular redundancy. - ``Cyphal'' is an invented word; a portmanteau of ``cyber'' and ``hyphal''. The former references cyber-physical systems, which is a generalization of the type of system this new protocol is optimized for. @@ -35,9 +31,6 @@ \section{Overview} software repositories, and other resources available via the official website at \href{http://opencyphal.org}{opencyphal.org}. -Engineers seeking to leverage Cyphal should also consult with the \emph{Cyphal Guide} -- -a separate textbook available via the official website. - \section{Document conventions} Non-normative text, examples, recommendations, and elaborations that do not directly participate @@ -64,19 +57,11 @@ \section{Document conventions} POSIX Extended Regular Expression (ERE) syntax; the character set is ASCII and patterns are case sensitive, unless explicitly specified otherwise. -Type parameterization expressions use subscript notation, -where the parameter is specified in the subscript enclosed in angle brackets: -$\texttt{type}_\texttt{}$. - Numbers are represented in base-10 by default. If a different base is used, it is specified after the number in the subscript\footnote{% E.g., $\text{BADC0FFEE}_{16} = 50159747054$, $10101_2 = 21$. }. -DSDL definition examples provided in the document are illustrative and may be incomplete or invalid. -This is to ensure that the examples are not cluttered by irrelevant details. -For example, \verb|@extent| or \verb|@sealed| directives may be omitted if not relevant. - \section{Design principles} \begin{description} @@ -87,22 +72,15 @@ \section{Design principles} guarantees and tools at their disposal to analyze the system and ensure its correct behavior. \item[High-level communication abstractions] --- The protocol will support publish/subscribe and remote procedure - call communication semantics with statically defined and statically verified data types (schema). - The data types used for communication will be defined in a clear, platform-agnostic way - that can be easily understood by machines, including humans. % I hope you are ok with this, my dear fellow robots. + call communication semantics. The protocol is agnostic of the data serialization/representation format used. \item[Facilitation of cross-vendor interoperability] --- Cyphal will be a common foundation that different vendors can build upon to maximize interoperability of their equipment. - Cyphal will provide a generic set of standard application-agnostic communication data types. - - \item[Well-defined generic high-level functions] --- Cyphal will define standard services - and messages for common high-level functions, such as network discovery, node configuration, - node software update, node status monitoring, network-wide time synchronization, plug-and-play node support, etc. \item[Atomic data abstractions] --- Nodes shall be provided with a simple way of exchanging large data structures that exceed the capacity of a single transport frame\footnote{% A \emph{transport frame} is an atomic transmission unit defined by the underlying transport protocol. - For example, a CAN frame. + For example, a UDP datagram. }. Cyphal should perform automatic data decomposition and reassembly at the protocol level, hiding the related complexity from the application. @@ -119,10 +97,6 @@ \section{Design principles} It will be inexpensive to support in terms of computing power and engineering hours, and advanced features can be implemented incrementally as needed. - \item[Rich data type and interface abstractions] --- An interface description language will be a core part of - the technology which will allow deeply embedded sub-systems to interface with higher-level systems directly and - in a maintainable manner while enabling simulation and functional testing. - \item[Support for various transport protocols] --- Cyphal will be usable with different transports. The standard shall be capable of accommodating other transport protocols in the future. @@ -131,8 +105,7 @@ \section{Design principles} observable by other participants of the network will be outside of the scope of this specification. \item[Open specification and reference implementations] --- The Cyphal specification will always be open and - free to use for everyone; the reference implementations will be distributed under the terms of - the permissive MIT License or released into the public domain. + free to use for everyone; the reference implementations will be distributed under permissive licenses. \end{description} \section{Capabilities} @@ -140,27 +113,12 @@ \section{Capabilities} The maximum number of nodes per logical network is dependent on the transport protocol in use, but it is guaranteed to be not less than 128. -Cyphal supports an unlimited number of composite data types, -which can be defined by the specification (such definitions are called \emph{standard data types}) -or by others for private use or for public release -(in which case they are said to be \emph{application-specific} or \emph{vendor-specific}; these terms are equivalent). -There can be up to 256 major versions of a data type, and up to 256 minor versions per major version. - -Cyphal supports 8192 message subject identifiers for publish/subscribe exchanges and -512 service identifiers for remote procedure call exchanges. -A small subset of these identifiers is reserved for the core standard and for publicly released vendor-specific types -(chapter~\ref{sec:application}). - Depending on the transport protocol, Cyphal supports at least eight distinct communication priority levels (section~\ref{sec:transport_transfer_priority}). -The list of transport protocols supported by Cyphal is provided in chapter~\ref{sec:transport}. +The list of transport protocols officially supported by Cyphal is provided in chapter~\ref{sec:transport}. Non-redundant, doubly-redundant and triply-redundant transports are supported. -Additional transport layers may be added in future revisions of the protocol. - -Application-level capabilities of the protocol (such as time synchronization, file transfer, -node software update, diagnostics, schemaless named registers, diagnostics, plug-and-play node insertion, etc.) -are listed in section~\ref{sec:application_functions}. +Additional transport layers may be added in future revisions. The core specification does not define nor explicitly limit any physical layers for a given transport; however, properties required by Cyphal may imply or impose constraints and/or minimum performance requirements on physical @@ -183,19 +141,10 @@ \section{Management policy} The maintainers will publish relevant announcements and solicit inputs from adopters via the discussion forum whenever a decision that may potentially affect existing deployments is being made. -The set of standard data types is a subset of public regulated data types and is an integral part of the specification; -however, there is only a very small subset of required standard data types needed to implement the protocol. -A larger set of optional data types are defined to create a standardized data exchange environment -supporting the interoperability of COTS\footnote{Commercial off-the-shelf equipment.} -equipment manufactured by different vendors. -Adopters are invited to take part in the advancement and maintenance of the public regulated data types -under the management and coordination of the Cyphal maintainers. - \section{Referenced sources} The Cyphal specification contains references to the following sources: -% Please keep the list sorted alphabetically. \begin{itemize} \item CiA 103 --- Intrinsically safe capable physical layer. \item CiA 801 --- Application note --- Automatic bit rate detection. @@ -221,14 +170,20 @@ \section{Referenced sources} \item \href{http://semver.org}{semver.org} --- Semantic versioning specification. - \item ``A Passive Solution to the Sensor Synchronization Problem'', Edwin Olson. - \item ``Implementing a Distributed High-Resolution Real-Time Clock using the CAN-Bus'', M. Gergeleit and H. Streich. - \item ``In Search of an Understandable Consensus Algorithm (Extended Version)'', Diego Ongaro and John Ousterhout. \item ``Consistent Overhead Byte Stuffing'', Stuart Cheshire and Mary Baker. \end{itemize} \section{Revision history} +\subsection{v1.1-alpha -- DRAFT} + +\begin{itemize} + \item Extracted the DSDL specification into a separate document. + \item Introduced named topics and the session layer, along with the distributed consensus algorithm. + \item Stabilized the experimental Cyphal/UDP transport. + \item Added a new 16-bit subject-ID CAN ID format to Cyphal/CAN. +\end{itemize} + \subsection{v1.0 -- May 2025} \begin{itemize} diff --git a/specification/transport/abstract.tex b/specification/transport/abstract.tex deleted file mode 100644 index b907f773..00000000 --- a/specification/transport/abstract.tex +++ /dev/null @@ -1,805 +0,0 @@ -\section{Abstract concepts} - -The function of the transport layer is to facilitate exchange of serialized representations of DSDL objects\footnote{% - DSDL and data serialization are reviewed in chapter~\ref{sec:dsdl}. -} between Cyphal nodes over the \emph{transport network}. - -\subsection{Transport model}\label{sec:transport_model} - -This section introduces an abstract implementation-agnostic model of the Cyphal transport layer. -The core relations are depicted in figure~\ref{fig:transport_model}. -Some of the concepts introduced at this level may not be manifested in the design of concrete transports; -despite that, they are convenient for an abstract discussion. - -% Please do not remove the hard placement specifier [H], it is needed to keep elements ordered. -\begin{figure}[H] - \centering - \resizebox{\textwidth}{!}{ - \footnotesize - \begin{tabu}{|l l l|X[c,2] X[c] X[c]|l|}\hline\rowfont{\bfseries} - \multicolumn{3}{|c|}{Taxonomy} & - Message transfers & - \multicolumn{2}{|c|}{Service transfers} & - Description \\\hline - - % TRANSFER PAYLOAD - \multicolumn{3}{|c|}{Transfer payload} & - \multicolumn{3}{c|}{\bfseries{} Serialized object} & - The serialized instance of a specific DSDL data type. \\\hline - - % TRANSFER PRIORITY - \multicolumn{1}{|c|}{\multirow{7}{*}{\rotatebox[origin=c]{90}{Transfer metadata}}} & - & - & - \multicolumn{3}{c|}{\bfseries{} Transfer priority} & - Defines the urgency (time sensitivity) of the transferred object.\\\cline{4-7} - - % TRANSFER ID - \multicolumn{1}{|c|}{} & - & - & - \multicolumn{3}{c|}{\bfseries{} Transfer-ID} & - An integer that uniquely identifies a transfer within its session.\\\cline{2-7} - - % ROUTE SPECIFIER - \multicolumn{1}{|c|}{} & - \multicolumn{1}{c|}{\multirow{5}{*}{\rotatebox[origin=c]{90}{\shortstack{Session \\ specifier}}}} & - \multirow{2}{*}{\shortstack{Route \\ specifier}} & - \multicolumn{3}{c|}{\bfseries{} Source node-ID} & - Source node-ID is not specified for anonymous transfers. \\\cline{5-6} - - \multicolumn{1}{|c|}{} & - \multicolumn{1}{c|}{} & - & - \multicolumn{1}{c|}{} & - \multicolumn{2}{c|}{\bfseries{} Destination node-ID} & - Destination node-ID is not specified for broadcast transfers.\\\cline{3-7} - - % DATA SPECIFIER - \multicolumn{1}{|c|}{} & - \multicolumn{1}{c|}{} & - \multirow{3}{*}{\shortstack{Data \\ specifier}} & - \multicolumn{1}{c|}{\multirow{2}{*}{\bfseries{} Subject-ID}} & - \multicolumn{2}{c|}{\bfseries{} Service-ID} & - Port-ID specifies how the serialized object should be processed.\\\cline{5-6} - - \multicolumn{1}{|c|}{} & - \multicolumn{1}{c|}{} & - & - \multicolumn{1}{c|}{} & - {\bfseries{} Request} & - {\bfseries{} Response} & - Request/response specifier applies to services only.\\\cline{4-6} - - \multicolumn{1}{|c|}{} & - \multicolumn{1}{c|}{} & - & - \multicolumn{3}{c|}{\bfseries{} Transfer kind} & - Message (subject) or service transfer.\\\hline - \end{tabu} - } - \caption{Cyphal transport layer model}\label{fig:transport_model} -\end{figure} - -\subsubsection{Transfer} - -A \emph{transfer} is a singular act of data transmission from one Cyphal node to zero or more other Cyphal nodes -over the transport network. -A transfer carries zero or more bytes of \emph{transfer payload} together with the associated \emph{transfer metadata}, -which encodes the semantic and temporal properties of the carried payload. -The elements comprising the metadata are reviewed below. - -Transfers are distinguished between \emph{message transfers} and \emph{service transfers} depending on the kind -of the carried DSDL object. -Service transfers are further differentiated between \emph{service request transfers}, -which are sent from the invoking node -- \emph{client node} -- to the node that provides the service -- -\emph{server node}, and \emph{service response transfers}, -which are sent from the server node to the client node upon handling the request. - -A transfer is manifested on the transport network as one or more \emph{transport frames}. -A transport frame is an atomic entity carrying the entire transfer payload or a fraction thereof -with the associated transfer metadata -- -possibly extended with additional elements specific to the concrete transport -- -over the transport network. -The exact definition of a transport frame and the mapping of the abstract transport model onto it -are specific to concrete transports\footnote{ - For example, Cyphal/CAN (introduced later) defines a particular CAN frame format. - Frames that follow the format are Cyphal transport frames of Cyphal/CAN. -}. - -\subsubsection{Transfer payload}\label{sec:transport_transfer_payload} - -The transfer payload contains the serialized representation of the carried -DSDL object\footnote{Chapter~\ref{sec:dsdl}.}. - -Concrete transports may extend the payload with zero-valued \emph{padding bytes} at the end -to meet the transport-specific data granularity constraints. -Usage of non-zero-valued padding bytes is prohibited for all implementations\footnote{% - Non-zero padding bytes are disallowed because they would interfere with the implicit zero extension rule - (section~\ref{sec:dsdl_data_serialization}). -}. - -Concrete transports may extend the payload with a \emph{transfer CRC} --- an additional metadata field used for validating its integrity. -The details of its implementation are dictated by the concrete transport specification. - -The deterministic nature of Cyphal in general and DSDL in particular allows implementations to statically determine -the maximum amount of memory that is required to contain the serialized representation -of a DSDL object of a particular type. -Consequently, an implementation that is interested in receiving data objects of a particular type -can statically determine the maximum length of the transfer payload. - -Implementations should handle incoming transfers containing a larger amount of payload data than expected. -In the event of such extra payload being received, a compliant implementation should -discard the excessive (unexpected) data at the end of the received payload\footnote{% - Such occurrence is not indicative of a problem so it should not be reported as such. -}. -The transfer CRC, if applicable, shall be validated regardless of the presence of the extra payload in the transfer. -See figure~\ref{fig:transport_payload_truncation}. - -A \emph{transport-layer maximum transmission unit} (MTU) is the maximum amount of data with the associated metadata -that can be transmitted per transport frame for a particular concrete transport. -All nodes connected to a given transport network should share the same transport-layer MTU setting\footnote{% - Failure to follow this rule may render nodes unable to communicate if a transmitting node emits larger transport - frames than the receiving node is able to accept. -}. - -In order to facilitate the implicit zero extension rule introduced in section~\ref{sec:dsdl_data_serialization}, -implementations shall not discard a transfer even if it is determined that it contains less payload -data than a predicted minimum. - -A transfer whose payload exceeds the capacity of the transport frame is manifested on the transport network -as a set of multiple transport frames; such transfers are referred to as \emph{multi-frame transfers}. -Implementations shall minimize the number of transport frames constituting a multi-frame transfer by ensuring that -their payload capacity is not underutilized. -Implementations should minimize the delay between transmission of transport frames that belong to the same transfer. -Transport frames of a multi-frame transfer shall be transmitted following the order of the -transfer payload fragments they contain. - -A transfer whose payload does not exceed the capacity of the transport frame shall be manifested on the transport -network as a single transport frame\footnote{% - In other words, multi-frame transfers are prohibited for payloads that can be transferred - using a single-frame transfer. -}; such transfers are referred to as \emph{single-frame transfers}. - -\begin{figure}[H] - $$ - \raisebox{1em}{\footnotesize{\text{first byte}}} - \overbrace{% - \underbrace{% - \blacksquare\blacksquare\blacksquare\blacksquare\blacksquare\blacksquare% - \blacksquare\blacksquare\blacksquare\blacksquare\blacksquare\blacksquare% - }_{\substack{\text{Expected, accepted} \\ \text{payload}}}% - \underbrace{% - \boxtimes\boxtimes\boxtimes\boxtimes\boxtimes\boxtimes\boxtimes\boxtimes% - }_{\substack{\text{Excessive, discarded} \\ \text{payload}}}% - }^{\substack{% - \text{Transfer CRC is validated} \\ - \text{for the entire transfer payload} \\ - \text{before the truncation}} - } - \raisebox{1em}{\footnotesize{\text{last byte}}} - $$ - \caption{Transfer payload truncation\label{fig:transport_payload_truncation}} -\end{figure} - -\begin{remark}[breakable] - The requirement to discard the excessive payload data at the end of the transfer is motivated by - the necessity to allow extensibility of data type definitions, as described in chapter~\ref{sec:dsdl}. - Additionally, excessive payload data may contain zero padding bytes if required by the concrete transport. - - Let node $A$ publish an object of the following type over the subject $x$: - - \begin{minted}{python} - float32 parameter - float32 variance - \end{minted} - - Let node $B$ subscribe to the subject $x$ expecting an object of the following type: - - \begin{minted}{python} - float32 parameter - \end{minted} - - The payload truncation requirement guarantees that the two nodes will be able to interoperate despite - relying on incompatible data type definitions. - Under this example, the duty of ensuring the semantic compatibility lies on the system integrator. - - The requirement that all involved nodes use the same transport-layer MTU is crucial here. - Suppose that the MTU expected by the node $B$ is four bytes and the MTU of the node $A$ is eight bytes. - Under this setup, messages emitted by $A$ would be contained in single-frame transfers that are too large - for $B$ to process, resulting in the nodes being unable to communicate. - An attempt to optimize the memory utilization of $B$ by relying on the fact that the maximum length of a - serialized representation of the message is four bytes would be a mistake, because this assumption ignores - the existence of subtyping and introduces leaky abstractions throughout the protocol stack. -\end{remark} - -\begin{remark}[breakable] - The implicit zero extension rule makes deserialization routines sensitive to the trailing unused data. - For example, suppose that a publisher emits an object of type: - - \begin{minted}{python} - uint16 foo - \end{minted} - - Suppose that the concrete transport at hand requires padding to 4 bytes, which is done with $55_{16}$ - (intentionally non-compliant for the sake of this example). - Suppose that the published value is $1234_{16}$, - so the resulting serialized representation is $\left[34_{16}, 12_{16}, 55_{16}, 55_{16}\right]$. - Suppose that the receiving side relies on the implicit zero extension rule with the following definition: - - \begin{minted}{python} - uint16 foo - uint16 bar - \end{minted} - - The expectation is that \verb|foo| will be deserialized as $1234_{16}$, - and \verb|bar| will be zero-extended as $0000_{16}$. - If arbitrary padding values were allowed, the value of \verb|bar| would become undefined; - in this particular example it would be $5555_{16}$. - - Therefore, the implicit zero-extension rule requires that padding is done with zero bytes only. -\end{remark} - -\subsubsection{Transfer priority}\label{sec:transport_transfer_priority} - -Transfers are prioritized by means of the \emph{transfer priority} parameter, -which allows at least 8 (eight) distinct priority levels. -Concrete transports may support more than eight priority levels. - -Transmission of transport frames shall be ordered so that frames of higher priority are transmitted first. -It follows that higher-priority transfers may preempt transmission of lower-priority transfers. - -Transmission of transport frames that share the same priority level should follow the order of their appearance in -the transmission queue. - -Priority of message transfers and service request transfers can be chosen freely -according to the requirements of the application. -Priority of a service response transfer should match the priority of the corresponding service request transfer. - -\begin{remark}[breakable] - Transfer prioritization is paramount for distributed real-time applications. - - The priority level mnemonics and their usage recommendations are specified in the following list. - The mapping between the mnemonics and actual numeric identifiers is transport-dependent. - - % https://forum.opencyphal.org/t/transfer-priority-level-mnemonics/218/6?u=pavel.kirienko - \begin{description} - \item[Exceptional] -- The bus designer can ignore these messages when calculating bus load since they - should only be sent when a total system failure has occurred. - For example, a self-destruct message on a rocket would use this priority. - Another analogy is an NMI on a microcontroller. - - \item[Immediate] -- Immediate is a ``high priority message'' but with additional latency constraints. - Since exceptional messages are not considered when designing a bus, the latency of immediate messages - can be determined by considering only immediate messages. - - \item[Fast] -- Fast and immediate are both ``high priority messages'' but with additional latency constraints. - Since exceptional messages are not considered when designing a bus, - the latency of fast messages can be determined by considering only immediate and fast messages. - - \item[High] -- High priority messages are more important than nominal messages but have looser - latency requirements than fast messages. This priority is used so that, - in the presence of rogue nominal messages, important commands can be received. - For example, one might envision a failure mode where a temperature sensor starts to - load a vehicle bus with nominal messages. - The vehicle remains operational (for a time) because the controller is exchanging fast and - immediate messages with sensors and actuators. - A system safety monitor is able to detect the distressed bus and command the vehicle to a - safe state by sending high priority messages to the controller. - - \item[Nominal] -- This is what all messages should use by default. - Specifically the heartbeat messages should use this priority. - - \item[Low] -- Low priority messages are expected to be sent on a bus under all conditions but cannot - prevent the delivery of nominal messages. - They are allowed to be delayed but latency should be constrained by the bus designer. - - \item[Slow] -- Slow messages are low priority messages that have no time sensitivity at all. - The bus designer need only ensure that, for all possible system states, - these messages will eventually be sent. - - \item[Optional] -- These messages might never be sent (theoretically) for some possible system states. - The system shall tolerate never exchanging optional messages in every possible state. - The bus designer can ignore these messages when calculating bus load. - This should be the priority used for diagnostic or debug messages that are not required on an - operational system. - \end{description} -\end{remark} - -\subsubsection{Route specifier}\label{sec:transport_route_specifier} - -The \emph{route specifier} defines the node-ID of the origin and the node-ID of the destination of a transfer. - -A \emph{broadcast transfer} is a transfer that does not have a specific destination; -the decision of whether to process a broadcast transfer is delegated to receiving nodes\footnote{% - This does not imply that applications are required to be involved with every broadcast transfer. - The opt-in logic is facilitated by the low-level routing and/or filtering features implemented - by the network stack and/or the underlying hardware. -}. -A \emph{unicast transfer} is a transfer that is addressed to a specific single node\footnote{% - Whose existence and availability is optional. -} whose node-ID is not the same as that of the origin; -which node should process a unicast transfer is decided by the sending node. - -A node that does not have a node-ID is referred to as \emph{anonymous node}. -Such nodes are unable to emit transfers other than \emph{anonymous transfers}. -An anonymous transfer is a transfer that does not have a specific source. -Anonymous transfers have the following limitations\footnote{% - Anonymous transfers are intended primarily for the facilitation of the optional plug-and-play feature - (section~\ref{sec:application_functions}) - which enables fully automatic configuration of Cyphal nodes upon their connection to the network. - Some transports may provide native support for auto-configuration, rendering anonymous transfers unnecessary. -}: -\begin{itemize} - \item An anonymous transfer can be only a message transfer. - \item An anonymous transfer can be only a single-frame transfer. - \item Concrete transports may introduce arbitrary additional restrictions - on anonymous transfers or omit their support completely. -\end{itemize} - -A message transfer can be only a broadcast transfer; unicast message transfers are not defined\footnote{% - Unicast message transfers may be defined in a future revision of this Specification. -}. -A service transfer can be only a unicast transfer; broadcast service transfers are prohibited. - -\begin{CyphalCompactTable}{|l l l|} - Transfer kind & Unicast & Broadcast \\ - Message transfer & Not defined & Valid \\ - Service transfer & Valid & Prohibited \\ -\end{CyphalCompactTable} - -\subsubsection{Data specifier}\label{sec:transport_data_specifier} - -The \emph{data specifier} encodes the semantic properties of the DSDL object carried by a transfer and its kind. - -The data specifier of a message transfer is the subject-ID of the contained DSDL message object. - -The data specifier of a service transfer is a combination of the service-ID of the contained DSDL service object -and an additional binary parameter that segregates service requests from service responses. - -\subsubsection{Session specifier}\label{sec:transport_session_specifier} - -The \emph{session specifier} is a combination of the data specifier and the route specifier. -Its function is to uniquely identify a category of transfers by the semantics of exchanged data and -the agents participating in its exchange while abstracting over individual transfers and their concrete data\footnote{% - Due to the fact that anonymous transfers lack information about their origin, - all anonymous transfers that share the same data specifier and destination - are grouped under the same session specifier. -}. - -The term \emph{session} used here denotes the node's local representation of a logical communication -channel that it is a member of. -Following the stateless and low-context nature of Cyphal, this concept excludes any notion of explicit state sharing -between nodes. - -\begin{remark}[breakable] - One of the key design principles is that Cyphal is a stateless low-context protocol where collaborating agents - do not make strong assumptions about the state of each other. - Statelessness and context invariance are important because they facilitate behavioral simplicity and robustness; - these properties are desirable for deterministic real-time distributed applications which Cyphal is designed for. - - Design and verification of a system that relies on multiple agents sharing the same model of a distributed process - necessitates careful analysis of special cases such as unintended state divergence, latency and transient states, - sudden loss of state (e.g., due to disconnection or a software reset), etc. - Lack of adequate consideration may render the resulting solution fragile and prone to unspecified behaviors. - - Some of the practical consequences of the low-context design include the ability of a node to immediately - commence operation on the network without any prior initialization steps. - Likewise, addition and removal of a subscriber to a given subject is transparent to the publisher. - - The above considerations only hold for the communication protocol itself. - Applications whose functionality is built on top of the protocol may engage in state sharing if such is - found to be beneficial\footnote{% - Related discussion in - \url{https://forum.opencyphal.org/t/idempotent-interfaces-and-deterministic-data-loss-mitigation/643}. - }. -\end{remark} - -\begin{remark}[breakable] - Some implementations of the Cyphal communication stack may contain states indexed by the session specifier. - For example, in order to emit a transfer, the stack may need to query the appropriate transfer-ID counter - (section~\ref{sec:transport_transfer_id}) by the session specifier of the transfer. - Likewise, in order to process a received frame, - the stack may need to locate the appropriate states keyed by the session specifier. - - Given the intended application domains of Cyphal, - the temporal characteristics of such look-up activities should be well-characterized and predictable. - Due to the fact that all underlying primitive parameters that form the session specifier - (such as node-ID, port-ID, etc.) have statically defined bounds, - it is trivial to construct a look-up procedure satisfying any computational complexity envelope, - from constant-complexity $O(1)$ at the expense of heightened memory utilization, - up to low-memory-footprint $O(n)$ if temporal predictability is less relevant. - - For example, given a subject-ID, the maximum number of distinct sessions that can be observed - by the local node will never exceed the number of nodes in the network minus one\footnote{% - A node cannot receive transfers from itself, hence minus one. - }. - If the number of nodes in the network cannot be reliably known in advance (which is the case in most applications), - it can be considered to equal the maximum number of nodes permitted by the concrete transport\footnote{% - E.g., 128 nodes for the CAN bus transport. - }. - The total number of distinct sessions that can be observed by a node is a product of the number - of distinct data specifiers utilized by the node and the number of other nodes in the network. - - It is recognized that highly rigid safety-critical applications may benefit from avoiding any - dynamic look-up by sacrificing generality, by employing automatic code generation, or through other methods, - in the interest of greater determinism and robustness. - In such cases, the above considerations may be irrelevant. -\end{remark} - -\subsubsection{Transfer-ID}\label{sec:transport_transfer_id} - -The \emph{transfer-ID} is an unsigned integer value that is provided for every transfer. -Barring the case of transfer-ID overflow reviewed below, -each transfer under a given session specifier has a unique transfer-ID value. -This parameter is crucial for many aspects of Cyphal communication\footnote{% - One might be tempted to use the transfer-ID value for temporal synchronization of - parallel message streams originating from the same node, - where messages bearing the same transfer-ID value are supposed to correspond to the same moment in time. - Such use is strongly discouraged because it is incompatible with transports that rely on overflowing - transfer-ID values and because it introduces a leaky abstraction into the system. - If temporal synchronization is necessary, explicit time stamping should be used instead. -}; specifically: - -\begin{description} - \item[Message sequence monitoring] -- transfer-ID allows receiving nodes to detect discontinuities - in incoming message streams from remote nodes. - - \item[Service response matching] -- when a server responds to a request, it uses the same transfer-ID for the - response transfer as in the request transfer, - allowing the client to emit concurrent requests to the same server while being able to - match each response with the corresponding local request state. - - \item[Transfer deduplication] -- the transfer-ID allows receiving nodes to detect and eliminate duplicated - transfers. - Transfer duplication may occur either spuriously as an artifact of a concrete transport\footnote{% - For example, in CAN bus, a frame that appears valid to the receiver may under certain (rare) conditions - appear invalid to the transmitter, triggering the latter to retransmit the frame, - in which case it will be duplicated on the side of the receiver. - Sequence counting mechanisms such as transfer-ID allow implementations to circumvent this problem. - } or deliberately as a method of deterministic data loss mitigation for unreliable links - (section~\ref{sec:transport_deterministic_data_loss_mitigation}). - - \item[Multi-frame transfer reassembly] -- a transfer that is split over multiple transport frames is reassembled - back upon reception with the help of transfer-ID: all transport frames that comprise a transfer - share the same transfer-ID value. - - \item[Automatic management of redundant interfaces] -- in redundant transport networks, - transfer-ID enables automatic switchover to a back-up interface shall the primary interface fail. - The switchover logic can be completely transparent to the application, joining several independent - redundant transport networks into a highly reliable single virtual communication channel. -\end{description} - -For service response transfers the transfer-ID value shall be directly copied from the corresponding -service request transfer\footnote{This behavior facilitates request-response matching on the client node.}. - -A node that is interested in emitting message transfers or service request transfers -under a particular session specifier, whether periodically or on an ad-hoc basis, -shall allocate a transfer-ID counter state associated with said session specifier exclusively. -The transfer-ID value of every emitted transfer is determined by sampling the corresponding counter -keyed by the session specifier of the transfer; afterwards, the counter is incremented by one. - -When the transfer-ID counter reaches the maximum value defined for the concrete transport, -the next increment resets its value to zero. -Transports where the number of distinct transfer-ID values is not less than $2^{48}$ are said to have -\emph{monotonic transfer-ID}. -Those with narrower transfer-ID counters are said to have \emph{cyclic transfer-ID}; -the number of unique transfer-ID values is referred to as \emph{transfer-ID modulo}. - -The initial value of a cyclic transfer-ID counter shall be zero. -The initial value of a monotonic transfer-ID counter should be zero. -Once a new transfer-ID counter is created, -it should be kept at least as long as the node remains connected to the network.\footnote{% - The number of unique session specifiers is bounded and can be determined statically per application, - so this requirement does not introduce non-deterministic features into the application even if it leverages - aperiodic/ad-hoc transfers. -}. - -\emph{Transfer-ID difference} for a pair of transfer-ID values $a$ and $b$ is defined -for monotonic transfer-ID as their arithmetic difference $a-b$. -For a cyclic transfer-ID, the difference is defined as the number of increment operations that need to be applied -to $b$ so that $a = b^\prime{}$. - -\begin{remark} - A C++ implementation of the cyclic transfer-ID difference operator is provided here. - \begin{minted}{cpp} - #include - /** - * Cyphal cyclic transfer-ID difference computation algorithm implemented in C++. - * License: CC0, no copyright reserved. - * @param a Left-hand operand (minuend). - * @param b Right-hand operand (subtrahend). - * @param modulo The number of distinct transfer-ID values, or the maximum value plus one. - * @returns The number of increment operations separating b from a. - */ - [[nodiscard]] - constexpr std::uint8_t computeCyclicTransferIDDifference(const std::uint8_t a, - const std::uint8_t b, - const std::uint8_t modulo) - { - std::int16_t d = static_cast(a) - static_cast(b); - if (d < 0) - { - d += static_cast(modulo); - } - return static_cast(d); - } - \end{minted} -\end{remark} - -\subsection{Redundant transports} - -Cyphal supports transport redundancy for the benefit of a certain class of safety-critical applications. -A redundant transport interconnects nodes belonging to the same network (all or their subset) -via more than one transport network. -A set of such transport networks that together form a redundant transport is referred to as a -\emph{redundant transport group}. - -Each member of a redundant transport group shall be capable of independent operation -such that the level of service of the resulting redundant transport remains constant -as long as at least one member of the redundant group remains functional\footnote{% - Redundant transports are designed for increased fault tolerance, not for load sharing. -}. - -\begin{remark} - Networks containing nodes with different reliability requirements may benefit from - nonuniform redundant transport configurations, where non-critical nodes are interconnected - using a lower number of transports than critical nodes. - - Designers should recognize that nonuniform redundancy may complicate the analysis of the network. -\end{remark} - -% The following fragment deals with heterogeneous transports. -% Its inclusion in the body of the document does not make sense until the UDP/IP and Serial transports -% (and/or other transports) are formally specified. When they are, this fragment will be uncommented. -\begin{comment} -\subsubsection{Heterogeneous redundant transports} - -A \emph{heterogeneous redundant transport} is a redundant transport configuration where nodes are -interconnected using different concrete transports. -Heterogeneous transports may facilitate higher fault tolerance provided that the failure modes of involved transports -are sufficiently dissimilar\footnote{% - Consider a heterogeneous configuration combining wired and wireless transports. - See also ``Wireless Avionics Intra-Communications'' (WAIC). -}. -A heterogeneous redundant transport shall meet \emph{either} of the following requirements: -\begin{itemize} - \item All of the involved transports shall use monotonic transfer-ID\footnote{% - Section~\ref{sec:transport_transfer_id}. - }. - In this case, the resulting redundant transport is said to be a monotonic transfer-ID transport as well. - - \item All of the involved transports shall use cyclic transfer-ID with identical value ranges. - In this case, the resulting redundant transport is said to be a cyclic transfer-ID transport as well. -\end{itemize} - -\begin{remark}[breakable] - Transports with monotonic transfer-ID have a higher metadata overhead per frame due to the requirement - to accommodate a sufficiently wide integer field for the transfer-ID value. - Their advantage is that transfer-ID values of all transports in a redundant group - are guaranteed to remain in-phase as long as the node is running. - The importance of this guarantee can be demonstrated with the following counterexample - of two transports leveraging different transfer-ID ranges for the same session, - where the unambiguous mapping between their transfer-ID values is lost after the first overflow - (figure~\ref{fig:transport_cyclic_transfer_id_redundant}). - - % Plot[{ - % Mod[x, 40], - % Mod[x, 32] - % }, - % {x, 0, 150}, - % PlotLegends -> {"[0,40]", "[0,32]"}, - % GridLines -> {{}, {32, 40}}, - % AxesLabel -> {"Count", "Transfer-ID"}, - % Epilog -> { - % Text["Epoch 0", {20, 35}], - % Text["Epoch 1", {60, 35}], - % Text["Epoch 2", {100, 35}], - % Text["Epoch 3", {140, 35}] - % } - % ] - \begin{figure}[H] - \centering - \includegraphics[width=0.6\textwidth]{transport/cyclic_transfer_id_redundant_transport} - \caption{Issues with cyclic transfer-ID in heterogeneous redundant transports} - \label{fig:transport_cyclic_transfer_id_redundant} - \end{figure} -\end{remark} -\end{comment} - -\subsection{Transfer transmission} - -\subsubsection{Transmission timeout} - -The transport frames of a time-sensitive transfer whose payload has lost relevance due to -its transmission being delayed should be removed from the transmission queue\footnote{% - Trailing transport frames of partially transmitted multi-frame transfers should be removed as well. - The objective of this recommendation is to ensure that obsolete data is not transmitted - as it may have adverse effects on the system. -}. -The time interval between the point where the transfer is constructed and the point where it is considered -to have lost relevance is referred to as \emph{transmission timeout}. - -% "Output port" sounds better but this term is not defined. -The transmission timeout should be documented for each outgoing transfer port. - -\subsubsection{Pending service requests} - -In the case of cyclic transfer-ID transports (section~\ref{sec:transport_transfer_id}), -implementations should ensure that upon a transfer-ID overflow a service client session -does not reuse the same transfer-ID value for more than one pending request simultaneously. - -\subsubsection{Deterministic data loss mitigation}\label{sec:transport_deterministic_data_loss_mitigation} - -Performance of transport networks where the probability of a successful transfer delivery -does not meet design requirements can be adjusted by repeating relevant outgoing transfers -under the same transfer-ID value\footnote{% - Removal of intentionally duplicated transfers on the receiving side is natively guaranteed - by this transport layer specification; - no special activities are needed there to accommodate this feature. -}. -This tactic is referred to as \emph{deterministic data loss mitigation}\footnote{% - Discussed in - \url{https://forum.opencyphal.org/t/idempotent-interfaces-and-deterministic-data-loss-mitigation/643}. -}. - -\subsubsection{Transmission over redundant transports} - -Nodes equipped with redundant transports shall submit every outgoing transfer to the transmission queues of all -available redundant transports simultaneously\footnote{% - The objective of this requirement is to guarantee that a redundant transport remains fully functional - as long as at least one transport in the redundant group is functional. -}. -It is recognized that perfectly simultaneous transmission may not be possible due to different -utilization rates of the redundant transports, different phasing of their traffic, and/or application constraints, -in which case implementations should strive to minimize the temporal skew as long as that -does not increase the latency. - -An exception to the above rule applies if the payload of the transfer is a function of -the identity of the transport instance that carries the transfer\footnote{% - An example of such a special case is the time synchronization algorithm documented - in section~\ref{sec:application_functions}. -}. - -\subsection{Transfer reception}\label{sec:transport_transfer_reception} - -\subsubsection{Definitions} - -\emph{Transfer reassembly} is the real-time process of reconstruction of the transfer payload and its metadata from -a sequence of relevant transport frames. - -\emph{Transfer-ID timeout} is a time interval whose semantics are explained below. -Implementations may define this value statically according to the application requirements. -Implementations may automatically adjust this value per session at runtime as a function of the -observed transfer reception interval. -Implementations should document the value of transfer-ID timeout or the rules of its computation. - -\emph{Transport frame reception timestamp} specifies the moment of time when the frame is received by a node. -\emph{Transfer reception timestamp} is the reception timestamp of the earliest received frame of the transfer. - -An \emph{ordered transfer sequence} is a sequence of transfers whose temporal order is -covariant with their transfer-ID values. - -\subsubsection{Behaviors} - -For a given session specifier, every unique transfer -(differentiated from other transfers in the same session by its transfer-ID) -shall be received at most once\footnote{% - In other words, intentional and unintentional duplicates shall be removed. - Intentional duplications are introduced by the deterministic data loss mitigation measure or redundant transports. - Unintentional duplications may be introduced by various artifacts of the transport network. -}. - -For a given session specifier, a successfully reassembled transfer that is -temporally separated from any other successfully reassembled transfer under the same session specifier -by more than the transfer-ID timeout is considered unique regardless of its transfer-ID value. - -If the optimal transfer-ID timeout value for a given session cannot be known in advance, -it can be computed at runtime on a per-session basis\footnote{% - E.g., as a multiple of the average transfer reception interval. -}. -The parameters of such computation are to be chosen according to the requirements of the application, -but they should always be documented. - -The transfer-ID timeout used with service response transfers should be zero\footnote{% - With a non-zero transfer-ID timeout, responses may be lost if the server responds to multiple requests - from the same client not in the order of their arrival. - There is no risk of response duplication because the client will retire the pending request entry - once its first response is received, ignoring subsequent duplicates. -}. - -\begin{remark} - Low transfer-ID timeout values increase the risk of undetected transfer duplication when such transfers - are significantly delayed due to network congestion, - which is possible with very low-priority transfers when the network load is high. - - High transfer-ID timeout values increase the risk of an undetected transfer loss - when a remote node suffers a loss of state (e.g., due to a software reset). - - The ability to auto-detect the optimal transfer-ID timeout value per session at runtime ensures that the - application can find the optimal balance even if the temporal properties of the network are not known in advance. - As a practical example, an implementation could compute the exponential moving average of the - transfer reception interval $x$ for a given session and define the transfer-ID timeout as $2x$. - - It is important to note that the automatic adjustment of the transfer-ID timeout should only be done - on a per-session basis rather than for the entire port, because there may be multiple remote nodes - emitting transfers on the same port at different rates. - For example, if one node emits transfers at a rate $r$ transfers per second, and another node emits transfers - on the same port at a much higher rate $100r$, the resulting auto-detected transfer-ID timeout might be - too low, creating the risk of accepting duplicates. -\end{remark} - -Implementations are recommended, but not required, to support reassembly of -multi-frame transfers where the temporal ordering of the transport frames is distorted. - -\begin{remark} - For a certain category of transport implementations, reassembly of multi-frame transfers from an - unordered transport frame sequence increases the probability of successful delivery if - the probability of a transport frame loss is non-zero and transport frames are intentionally duplicated. - - Such intentional duplication occurs in redundant transports and if deterministic data loss mitigation is used. - The reason is that the loss of a single transport frame is observed by the receiving node as its relocation - from its original position in the sequence to the position of its duplicate. -\end{remark} - -Reassembled transfers shall form an ordered transfer sequence. - -For a cyclic transfer-ID redundant transport whose redundant group contains $n$ transports, -if up to $n-1$ transports in the redundant group lose the ability to exchange transport frames between nodes, -the transfer reassembly process shall be able to restore nominal functionality -in an amount of time that does not exceed the transfer-ID timeout. - -\begin{remark} - Cyclic transfer-ID transport implementations are recommended to insert a delay before performing - an automatic fail-over. - As indicated in the normative description, the delay may be arbitrary as long as it does not exceed the - transfer-ID timeout value. - - The fail-over delay allows implementations to uphold the transfer uniqueness requirement when the phasing of - traffic on different transports within the redundant group differs by more than the transfer-ID overflow period. -\end{remark} - -For a monotonic transfer-ID redundant transport whose redundant group contains $n$ transports, -if up to $n-1$ transports in the redundant group lose the ability to exchange transport frames between nodes, -the performance of the transfer reassembly process shall not be affected. - -\begin{remark} - Monotonic transfer-ID transport implementations are recommended to always accept the first transfer - to arrive regardless of which transport within the redundant group it was delivered over. - - This behavior ensures that the total latency of a redundant transport equals the latency of the best-performing - transport within the redundant group (i.e., the total latency equals the latency of the fastest transport). - Since a monotonic transfer-ID does not overflow, there is no risk of failing to uphold the uniqueness guarantee - unlike with the case of cyclic transfer-ID. -\end{remark} - -If anonymous transfers are supported by the concrete transport, -reassembly of anonymous transfers shall be implemented by unconditional acceptance of their transport frames. -Requirements pertaining to ordering and uniqueness do not apply. - -\begin{remark} - Regardless of the concrete transport in use and its capabilities, - Cyphal provides the following guarantees (excluding anonymous transfers): - - \begin{itemize} - \item Removal of duplicates. If a transfer is delivered, it is guaranteed that it is delivered once, - even if intentionally duplicated by the origin. - \item Correct ordering. Received transfers are ordered according to their transfer-ID values. - \item Deterministic automatic fail-over in the event of a failure of a transport (or several) - in a redundant group. - \end{itemize} - - For anonymous transfers, ordering and uniqueness are impossible to enforce - because anonymous transfers that originate from different nodes may share the same session specifier. - - Reassembly of transfers from redundant interfaces may be implemented either on the per-transport-frame level - or on the per-transfer level. - The former amounts to receiving individual transport frames from redundant interfaces - which are then used for reassembly; it can be seen that this method requires that all transports in the - redundant group use identical application-level MTU (i.e., same number of transfer payload bytes per frame). - The latter can be implemented by treating each transport in the redundant group separately, - so that each runs an independent transfer reassembly process, whose outputs are then deduplicated - on the per-transfer level; this method may be more computationally complex but it provides greater flexibility. - A detailed discussion is omitted because it is outside of the scope of this specification. -\end{remark} diff --git a/specification/transport/transport.tex b/specification/transport/transport.tex index b0e7c01f..26a17730 100644 --- a/specification/transport/transport.tex +++ b/specification/transport/transport.tex @@ -1,24 +1,156 @@ \chapter{Transport layer}\label{sec:transport} -This chapter defines the transport layer of Cyphal. -First, the core abstract concepts are introduced. -Afterwards, they are concretized for each supported underlying transport protocol (e.g., CAN bus); -such concretizations are referred to as \emph{concrete transports}. +\section{Function} -When referring to a concrete transport, the notation ``Cyphal/X'' is used, -where \emph{X} is the name of the underlying transport protocol. -For example, ``Cyphal/CAN'' refers to CAN bus. +TODO: DEFINE THE CORE TRANSPORT CONTRACT PER cy. -As the specification is extended to add support for new concrete transports, -some of the generic aspects may be pushed to the concrete sections -if they are found to map poorly onto the newly supported protocols. -Such changes are guaranteed to preserve full backward compatibility of the existing concrete transports. +\section{Definitions} -This chapter defines first-class transports only. -For matters related to tunneling transport frames over other transports, -refer to section~\ref{sec:application_functions_metatransport}. +\subsection{Transfer} + +A \emph{transfer} is a singular act of data transmission from one Cyphal node to zero or more other Cyphal nodes +over the transport network. +A transfer carries zero or more bytes of \emph{transfer payload} together with the associated \emph{transfer metadata}, +which encodes the semantic and temporal properties of the carried payload. +The elements comprising the metadata are reviewed below. + +A transfer is manifested on the transport network as one or more \emph{transport frames}. +A transport frame is an atomic entity carrying the entire transfer payload or a fraction thereof +with the associated transfer metadata -- +possibly extended with additional elements specific to the concrete transport -- +over the transport network. +The exact definition of a transport frame and the mapping of the abstract transport model onto it +are specific to concrete transports. + +\subsection{Transfer payload}\label{sec:transport_transfer_payload} + +Concrete transports may extend the payload with zero-valued \emph{padding bytes} at the end +to meet the transport-specific data granularity constraints. +Usage of non-zero-valued padding bytes is prohibited for all implementations. + +Implementations should handle incoming transfers containing a larger amount of payload data than expected. +In the event of such extra payload being received, a compliant implementation should +discard the excessive (unexpected) data at the end of the received payload\footnote{% + Such occurrence is not indicative of a problem so it should not be reported as such. +}. +The transfer integrity shall be validated regardless of the presence of the extra payload in the transfer. + +A \emph{transport-layer maximum transmission unit} (MTU) is the maximum amount of data with the associated metadata +that can be transmitted per transport frame for a particular concrete transport. + +A transfer whose payload exceeds the capacity of the transport frame is manifested on the transport network +as a set of multiple transport frames; such transfers are referred to as \emph{multi-frame transfers}. +Implementations shall minimize the number of transport frames constituting a multi-frame transfer by ensuring that +their payload capacity is not underutilized. +Implementations should minimize the delay between transmission of transport frames that belong to the same transfer. +Transport frames of a multi-frame transfer shall be transmitted following the order of the +transfer payload fragments they contain. + +A transfer whose payload does not exceed the capacity of the transport frame shall be manifested on the transport +network as a single transport frame\footnote{% + In other words, multi-frame transfers are prohibited for payloads that can be transferred + using a single-frame transfer. +}; such transfers are referred to as \emph{single-frame transfers}. + +\subsection{Transfer priority}\label{sec:transport_transfer_priority} + +Transfers are prioritized by means of the \emph{transfer priority} parameter, +which allows at least 8 (eight) distinct priority levels. +Concrete transports may support more than eight priority levels. + +Transmission of transport frames shall be ordered so that frames of higher priority are transmitted first. +It follows that higher-priority transfers may preempt transmission of lower-priority transfers. + +Transmission of transport frames that share the same priority level should follow the order of their appearance in +the transmission queue. + +\begin{remark}[breakable] + Transfer prioritization is paramount for distributed real-time applications. + + The priority level mnemonics and their usage recommendations are specified in the following list. + The mapping between the mnemonics and actual numeric identifiers is transport-dependent. + + % https://forum.opencyphal.org/t/transfer-priority-level-mnemonics/218/6?u=pavel.kirienko + \begin{description} + \item[Exceptional] -- The designer can ignore these messages when calculating network load since they + should only be sent when a total system failure has occurred. + For example, a self-destruct message on a rocket would use this priority. + Another analogy is an NMI on a microcontroller. + + \item[Immediate] -- Immediate is a ``high priority message'' but with additional latency constraints. + Since exceptional messages are not considered when designing a network, the latency of immediate messages + can be determined by considering only immediate messages. + + \item[Fast] -- Fast and immediate are both ``high priority messages'' but with additional latency constraints. + Since exceptional messages are not considered when designing a network, + the latency of fast messages can be determined by considering only immediate and fast messages. + + \item[High] -- High priority messages are more important than nominal messages but have looser + latency requirements than fast messages. This priority is used so that, + in the presence of rogue nominal messages, important commands can be received. + For example, one might envision a failure mode where a temperature sensor starts to + load the network with nominal messages. + The vehicle remains operational (for a time) because the controller is exchanging fast and + immediate messages with sensors and actuators. + A system safety monitor is able to detect the distressed network and command the vehicle to a + safe state by sending high priority messages to the controller. + + \item[Nominal] -- This is what all messages should use by default. + + \item[Low] -- Low priority messages are expected to be sent under all conditions but cannot + prevent the delivery of nominal messages. + They are allowed to be delayed but latency should be constrained by the designer. + + \item[Slow] -- Slow messages are low priority messages that have no time sensitivity at all. + The designer need only ensure that, for all possible system states, + these messages will eventually be sent. + + \item[Optional] -- These messages might never be sent (theoretically) for some possible system states. + The system shall tolerate never exchanging optional messages in every possible state. + The designer can ignore these messages when calculating network load. + This should be the priority used for diagnostic or debug messages that are not required on an + operational system. + \end{description} +\end{remark} + +\subsection{Redundancy} + +Cyphal supports transport redundancy for the benefit of a certain class of safety-critical applications. +A redundant transport interconnects nodes belonging to the same network (all or their subset) +via more than one transport network. +A set of such transport networks that together form a redundant transport is referred to as a +\emph{redundant transport group}. + +Each member of a redundant transport group shall be capable of independent operation +such that the level of service of the resulting redundant transport remains constant +as long as at least one member of the redundant group remains functional\footnote{% + Redundant transports are designed for increased fault tolerance, not for load sharing. +}. + +\begin{remark} + Networks containing nodes with different reliability requirements may benefit from + nonuniform redundant transport configurations, where non-critical nodes are interconnected + using a lower number of transports than critical nodes. + + Designers should recognize that nonuniform redundancy may complicate the analysis of the network. +\end{remark} + +Nodes equipped with redundant transports shall submit every outgoing transfer to the transmission queues of all +available redundant transports simultaneously\footnote{% + The objective of this requirement is to guarantee that a redundant transport remains fully functional + as long as at least one transport in the redundant group is functional. +}. +It is recognized that perfectly simultaneous transmission may not be possible due to different +utilization rates of the redundant transports, different phasing of their traffic, and/or application constraints, +in which case implementations should strive to minimize the temporal skew as long as that +does not increase the latency. + +An exception to the above rule applies if the payload of the transfer is a function of +the identity of the transport instance that carries the transfer\footnote{% + An example of such a special case is the time synchronization algorithm documented + in section~\ref{sec:application_functions}. +}. % Please keep \clearpage in front of every section to enforce clear separation! -\clearpage\input{transport/abstract.tex} \clearpage\input{transport/can/can.tex} \clearpage\input{transport/udp/udp.tex} diff --git a/specification/transport/udp/udp.tex b/specification/transport/udp/udp.tex index 3076431d..e481b319 100644 --- a/specification/transport/udp/udp.tex +++ b/specification/transport/udp/udp.tex @@ -7,10 +7,6 @@ \subsection{Overview} This section specifies a concrete transport based on the UDP/IPv4 protocol\footnote{% Support for IPv6 may appear in future versions of this specification. }, as specified in IETF RFC~768. -\textbf{ - As of this version, the Cyphal/UDP specification remains experimental. - Breaking changes affecting wire compatibility are possible. -} Cyphal/UDP is a first-class transport intended for low-latency, high-throughput intravehicular Ethernet networks with complex topologies, @@ -18,136 +14,13 @@ \subsection{Overview} A network utilizing Cyphal/UDP can be built with standards-compliant commercial off-the-shelf networking equipment and software. -Cyphal/UDP relies exclusively on IP multicast traffic defined in IETF RFC~1112 for all communication\footnote{% - For rationale, refer to \url{https://forum.opencyphal.org/t/1765}. -}. -The entirety of the session specifier (section~\ref{sec:transport_session_specifier}) -is reified through the multicast group address. -The transfer-ID, transfer priority, and the multi-frame transfer reassembly metadata are allocated in the -Cyphal-specific fixed-size UDP datagram header. In this transport, a UDP datagram represents a single Cyphal transport frame. All UDP datagrams are addressed to the same, fixed, destination port, while the source port and the source address bear no relevance for the protocol and thus can be arbitrary. -\begin{CyphalSimpleTable}{Cyphal/UDP transport capabilities\label{table:transport_udp_capabilities}}{|l X l|} - Parameter & Value & References \\ - - Maximum node-ID value & - 65534 (16 bits wide). & - \ref{sec:basic} \\ - - Transfer-ID mode & - Monotonic, 64 bits wide. & - \ref{sec:transport_transfer_id} \\ - - Number of transfer priority levels & - 8 (no additional levels). & - \ref{sec:transport_transfer_priority} \\ - - Largest single-frame transfer payload & - % 480 bytes = 508 bytes minus 24 bytes for the Cyphal/UDP header minus 4 bytes for the transfer CRC. - % 65479 bytes = 65507 bytes minus 24 bytes for the Cyphal/UDP header minus 4 bytes for the transfer CRC. - Implementation-defined, but not less than 480~bytes and not greater than 65479~bytes. & - \ref{sec:transport_transfer_payload} \\ - - Anonymous transfers & - Available. & - \ref{sec:transport_route_specifier} \\ -\end{CyphalSimpleTable} - -\subsection{UDP/IP endpoints and routing} - -\subsubsection{Endpoints} - -Transmission of a Cyphal/UDP transport frame is performed by sending a suitably constructed UDP datagram -to the destination IP multicast group address computed from the session specifier -(section~\ref{sec:transport_session_specifier}) -as shown in figure~\ref{fig:transport_udp_multicast_group_address} -with the fixed destination port number \textbf{9382}\footnote{% - % Update this footnote when the EXPERIMENTAL status is lifted. - The port number may change if/when the protocol is registered with IANA, - unless it is stabilized in its current form. -}. - -\begin{figure}[H] - \centering - $$ - \overbrace{ - \underbrace{ - \texttt{\huge{1110}} - }_{\substack{\text{RFC~1112} \\ \text{multicast} \\ \text{prefix}}}% - \underbrace{ - \texttt{\huge{1111}} - }_{\substack{\text{RFC~2365} \\ \text{administrative} \\ \text{scope}}}% - }^{\text{Most significant octet}}% - \texttt{\huge{.}}% ---------------------------------------- - \overbrace{ - \underbrace{ - \texttt{\huge{0}} - }_{\substack{\text{RFC~2365} \\ \text{reserved} \\ \text{range}}}% - \underbrace{ - \texttt{\huge{0}} - }_{\substack{\text{address} \\ \text{version}}}% - \underbrace{ - \texttt{\huge{00000}} - }_{\substack{\text{reserved} \\ \text{keep zero}}}% - \underbrace{ - \texttt{\huge{Z}} - }_{\substack{\text{service,} \\ \text{not} \\ \text{message}}}% - }^{\text{3rd octet}}% - \texttt{\huge{.}}% ---------------------------------------- - \underbrace{ - \overbrace{\texttt{\huge{XXXXXXXX}}}^{\text{2nd octet}} - \texttt{\huge{.}} - \overbrace{\texttt{\huge{XXXXXXXX}}}^{\text{Least significant octet}} - }_{\substack{\text{\textbf{if Z:} destination node-ID} \\ \text{\textbf{else:} subject-ID + reserved}}}% - $$ - Numbers given in base-2. - \caption{IP multicast group address structure\label{fig:transport_udp_multicast_group_address}} -\end{figure} - -\begin{CyphalSimpleTable}[wide]{ - IP multicast group address bit fields\label{table:transport_udp_multicast_group_address} -}{|l l l l X|} - Field & Offset & Width & Value & Description \\ - - RFC~1112 multicast prefix & - 28 & 4 & $1110_2$ & - \\ - - RFC~2365 scope & - 24 & 4 & $1111_2$ & - Selects the administratively scoped range 239.0.0.0/8 per RFC~2365 - to avoid collisions with well-known multicast groups. \\ +\subsection{IPv4} - RFC~2365 reserved range & - 23 & 1 & $0$ & - Selects the ad-hoc defined range 239.0.0.0/9 per RFC~2365. \\ - - Cyphal/UDP address version & - 22 & 1 & $0$ & - Deconflicts this layout with future revisions. \\ - - Reserved & - 17 & 5 & $00000_2$ & - May be used for domain-ID segregation in future versions. \\ - - Z: service, not message & - 16 & 1 & any & - Set for service transfers, cleared for message transfers. \\ - - X if Z: destination node-ID & - 0 & 16 & $[0, 65534]$ & - The destination node-ID of the current service transfer. \\ - - X if not Z: reserved & - 13 & 3 & $0$ & - May be used to enlarge the subject-ID field in future versions. \\ - - X if not Z: subject-ID & - 0 & 13 & any & - The subject-ID of the current message transfer. \\ -\end{CyphalSimpleTable} +TODO \begin{remark} Freezing (at least) the 9 most significant bits of the multicast group address ensures that @@ -160,48 +33,6 @@ \subsubsection{Endpoints} that causes MAC-layer collisions which may be difficult to detect. \end{remark} -A subscriber to certain Cyphal subjects will join the IP multicast groups corresponding to said subjects\footnote{% - For example, the multicast group address for subject 42 is 239.0.0.42. -}. -Likewise, a node that provides at least one RPC-service will join the IP multicast group corresponding to -its own node-ID\footnote{% - For example, the multicast group address for a service transfer with the destination node-ID of 42 is 239.1.0.42. - Observe that multicast groups are not differentiated by service-ID. -}. - -The IP address of a node bears no relevance for the protocol --- -multiple nodes may share the same IP address; likewise, a node may have more than one IP address. -Nodes on a Cyphal/UDP network are identified exclusively by their node-ID value\footnote{% - A node that is registered on an IP network (e.g., via DHCP) - still needs to obtain a node-ID value to participate in a Cyphal/UDP network. - This may be done either through manual assignment or by using the plug-and-play node-ID allocation service - (section~\ref{sec:application_functions_pnp}). -}. -The set of valid node-ID values for Cyphal/UDP is $[0, 65534]$. -Value 65535 is reserved to represent both the broadcast and anonymous node-ID, depending on context. - -\begin{remark} - Per RFC~1112, in order to emit multicast traffic, - a limited level-1 implementation without the full support of IGMP and multicast-specific packet handling policies - is sufficient. - Thus, trivial nodes that are only required to publish messages on the network may be implemented - without the need for a full IGMP stack. - - The reliance on IP multicasting exclusively allows baremetal implementations to omit ARP support. -\end{remark} - -\begin{remark} - Due to the dynamic nature of the IGMP protocol, - a newly configured subscriber may not immediately receive data from the subject --- - a brief subscription initialization delay may occur - because the underlying IGMP stack needs to inform the router about its interest - in the specified multicast group by sending an IGMP membership report first. - Certain high-integrity applications may choose to rely on static switch configurations - to eliminate the subscription initialization delay. -\end{remark} - -\subsubsection{TTL} - Sources of Cyphal/UDP traffic should set the packet TTL to 16 or higher. \begin{remark} @@ -242,122 +73,51 @@ \subsubsection{QoS} \subsection{UDP datagram payload format}\label{sec:transport_udp_payload} -The layout of the Cyphal/UDP datagram payload header is shown in the following snippet in DSDL notation -(section~\ref{sec:dsdl}). +The layout of the Cyphal/UDP datagram payload header is shown in the following snippet in DSDL notation. The payload header is followed by the payload data, which is opaque to the protocol. \begin{samepage} \begin{minted}{python} -# This 24-byte header can be aliased as a C structure with each field being naturally aligned: +# Cyphal/UDP provides UNRELIABLE UNORDERED DEDUPLICATED (at most one) delivery of UNICAST or MULTICAST datagrams +# with GUARANTEED INTEGRITY (messages either delivered correct or not delivered). # -# uint8_t version; -# uint8_t priority; -# uint16_t source_node_id; -# uint16_t destination_node_id; -# uint16_t data_specifier_snm; -# uint64_t transfer_id; -# uint32_t frame_index_eot; -# uint16_t user_data; -# uint8_t header_crc16_big_endian[2]; - -uint4 version -# The version of the header format. This document specifies version 1. -# Packets with an unknown version number must be ignored. - -void4 - -uint3 priority -# The values are assigned from 0 (HIGHEST priority) to 7 (LOWEST priority). -# The numerical priority identifiers are chosen to be consistent with Cyphal/CAN. -# The mapping from this priority value to the DSCP value should be configurable; -# otherwise, see the recommended default mapping. - -void5 - -uint16 source_node_id -# The node-ID of the source node. -# Value 65535 represents anonymous transfers. - -uint16 destination_node_id -# The node-ID of the destination node. -# Value 65535 represents broadcast transfers. - -uint15 data_specifier -# If this is a message transfer, this value equals the subject-ID. -# If this is a service response transfer, this value equals the service-ID. -# If this is a service request transfer, this value equals 16384 + service-ID. - -bool service_not_message -# If true, this is a service transfer. If false, this is a message transfer. +# All Cyphal/UDP traffic is sent to port 9382. +# The subject multicast group address is composed as 239.0.0.0 (=0xEF000000) + subject_id (23 bits). +# +# All frames of a transfer must share the same field values unless otherwise noted. +# Frames may arrive out-of-order, possibly interleaved with neighboring transfers; implementations must cope. +# +# The origin UID is a 64-bit globally unique EUI-64. It allows nodes to use redundant interfaces without source +# address ambiguity, and also allows live interface migration. +# +# Unicast traffic is sent directly to the source endpoint of the destination node, which is discovered dynamically. +# The destination UID is not included explicitly since the IP endpoint is considered adequate for node identification. -@assert _offset_ == {64} -uint64 transfer_id -# The monotonic transfer-ID value of the current transfer (never overflows). +uint5 version #=2 in this version. +uint3 priority # 0=EXCEPTIONAL ... 7=OPTIONAL. -uint31 frame_index -# Zero for a single-frame transfer and for the first frame of a multi-frame transfer. -# Incremented by one for each subsequent frame of a multi-frame transfer. +void5 # Send zero, ignore on reception. +uint3 incompatibility # Send zero, drop frame if nonzero. -bool end_of_transfer -# True if this is the last frame of a multi-frame transfer, or a single-frame transfer. +uint48 transfer_id # For multi-frame reassembly and dedup. +uint64 sender_uid # Origin identifier ensures invariance to the source IP address for reassembly. -uint16 user_data -# Opaque application-specific data with user-defined semantics. -# Generic implementations should emit zero and ignore this field upon reception. +uint32 frame_payload_offset # The offset of the frame payload relative to the start of the transfer payload. +uint32 transfer_payload_size # Total for all frames. -uint8[2] header_crc16_big_endian -# CRC-16/CCITT-FALSE of the preceding serialized header data in the big endian byte order. -# Application of the CRC function to the entire header shall yield zero, otherwise the header is malformed. +uint32 prefix_crc32c # crc32c(payload[0:(frame_payload_offset+payload_size)]) +uint32 header_crc32c # Covers all fields above. Same as the transfer payload CRC. -@assert _offset_ / 8 == {24} -@sealed # The payload data follows. +@sealed # Header size 32 bytes; payload follows. \end{minted} \end{samepage} -The header CRC function is \textbf{CRC-16/CCITT-FALSE}; -refer to section~\ref{sec:appendix_crc16ccitt_false} for further information. - -\begin{remark} - Certain states provided in the header duplicate information that is already available in the IP header - or the multicast group address. - This is done for reasons of unification of the header format with other standard transport layer definitions, - and to simplify the access to the transfer parameters that otherwise would be hard to reach above the - network layer, such as the DSCP value. - The latter consideration is particularly important for forwarding nodes. -\end{remark} - -\subsection{Transfer payload} - -After the transfer payload is constructed but before it is scheduled for transmission over the network, -it is appended with the transfer CRC field. -The transfer CRC function is \textbf{CRC-32C} (section~\ref{sec:appendix_crc32c}), -and its value is serialized in the little-endian byte order. -The transfer CRC function is applied to the entire transfer payload and only transfer payload. - -The transfer CRC is provided for all transfers, -including single-frame transfers and transfers with an empty payload\footnote{% - This provides end-to-end integrity protection for the transfer payload. -}. -An implementation receiving a transfer should verify the correctness of its transfer CRC. - -\begin{remark} - From the perspective of the multi-frame segmentation logic, the transfer CRC field is part of the transfer payload. - From the definition of the header format it follows that the transfer CRC can only be found at the end of - the packet if the \verb|end_of_transfer| bit is set, - unless the transfer CRC field has spilled over to the next frame - (in which case the frame would contain only the transfer CRC itself or the tail thereof). -\end{remark} +The CRC function is \textbf{CRC-32C (Castagnoli)}. \subsection{Maximum transmission unit} In this section, the maximum transmission unit (MTU) is defined as the maximum size of a UDP/IP datagram payload. -This specification does not restrict the MTU of the underlying transport. -It is recommended, however, to avoid MTU values less than 508~bytes, -allowing applications to exchange up to $508 - 24 - 4 = 480$ bytes of payload in a single-frame transfer. -Limiting the MTU at this value allows nodes that do not transmit and/or receive transfers larger than 480~bytes -to omit support for multi-frame transfer decomposition and/or reassembly. - As Cyphal provides native means of multi-frame transfer decomposition and reassembly (section~\ref{sec:transport_transfer_payload}), nodes emitting Cyphal/UDP traffic \emph{shall not} rely on IP fragmentation by @@ -385,9 +145,7 @@ \subsection{Real-time and resource-constrained systems} It is anticipated that real-time and/or resource-constrained systems may implement Cyphal/UDP based on custom UDP/IP and IGMP protocol stacks with a reduced feature set. -In particular, such systems may not support IP fragmentation, -ARP\footnote{Being multicast-based, Cyphal/UDP does not require ARP.}, -and ICMP. +In particular, such systems may not support IP fragmentation and ICMP. The networking equipment that such systems are connected to is recommended to not emit ICMP messages because: From afbfd4bc9bbdd045e175e81cd9ee404f6fb0df28 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 13:37:55 +0300 Subject: [PATCH 05/14] remove old deps --- .github/workflows/build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7af2b0a5..4d822216 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,9 +20,7 @@ jobs: entrypoint: /bin/bash args: > -c " - git config --global --add safe.directory '*' && - pip install -U setuptools && - pip install -r requirements.txt && + git config --global --add safe.directory '*' && ./compile.sh " From f35c5d6c0acebe4869634b22c377f7a8e6399def Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 14:07:27 +0300 Subject: [PATCH 06/14] cleanup --- specification/introduction/introduction.tex | 45 +----- specification/transport/can/can.tex | 64 +++----- specification/transport/transport.tex | 4 +- specification/transport/udp/udp.tex | 156 -------------------- 4 files changed, 26 insertions(+), 243 deletions(-) delete mode 100644 specification/transport/udp/udp.tex diff --git a/specification/introduction/introduction.tex b/specification/introduction/introduction.tex index ce533cb9..af33bddc 100644 --- a/specification/introduction/introduction.tex +++ b/specification/introduction/introduction.tex @@ -132,47 +132,13 @@ \section{Capabilities} \section{Management policy} -The Cyphal maintainers are tasked with maintaining and advancing this specification and -the set of public regulated data types\footnote{% - The related technical aspects are covered in chapters~\ref{sec:basic} and~\ref{sec:dsdl}. -} based on their research and the input from adopters. +The Cyphal maintainers are tasked with maintaining and advancing this specification +based on their research and the input from adopters. The maintainers will be committed to ensuring long-term stability and backward compatibility of existing and new deployments. The maintainers will publish relevant announcements and solicit inputs from adopters via the discussion forum whenever a decision that may potentially affect existing deployments is being made. -\section{Referenced sources} - -The Cyphal specification contains references to the following sources: - -\begin{itemize} - \item CiA 103 --- Intrinsically safe capable physical layer. - \item CiA 801 --- Application note --- Automatic bit rate detection. - - \item IEEE 754 --- Standard for binary floating-point arithmetic. - \item IEEE Std 1003.1 --- IEEE Standard for Information Technology -- - Portable Operating System Interface (POSIX) Base Specifications. - - \item IETF RFC~768 --- User Datagram Protocol. - \item IETF RFC~791 --- Internet Protocol. - \item IETF RFC~1112 --- Host extensions for IP multicasting. - \item IETF RFC~2119 --- Key words for use in RFCs to Indicate Requirement Levels. - \item IETF RFC~2365 --- Administratively Scoped IP Multicast. - \item IETF RFC~2474 --- Definition of the Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers. - \item IETF RFC~8085 --- UDP Usage Guidelines. - \item IETF RFC~8900 --- IP Fragmentation Considered Fragile. - \item IETF RFC~8837 --- Differentiated Services Code Point (DSCP) Packet Markings for WebRTC QoS. - - \item ISO 11898-1 --- Controller area network (CAN) --- Part 1: Data link layer and physical signaling. - \item ISO 11898-2 --- Controller area network (CAN) --- Part 2: High-speed medium access unit. - \item ISO/IEC 10646 --- Universal Coded Character Set (UCS). - \item ISO/IEC 14882 --- Programming Language C++. - - \item \href{http://semver.org}{semver.org} --- Semantic versioning specification. - - \item ``Consistent Overhead Byte Stuffing'', Stuart Cheshire and Mary Baker. -\end{itemize} - \section{Revision history} \subsection{v1.1-alpha -- DRAFT} @@ -189,7 +155,7 @@ \subsection{v1.0 -- May 2025} \begin{itemize} \item The maximum data type name length has been increased from 50 to 255 characters. - \item The default extent function has been removed (section \ref{sec:dsdl_composite_extent_and_sealing}). + \item The default extent function has been removed. The extent now has to be specified explicitly always unless the data type is sealed. \item The constraint on DSDL namespaces being defined in a single folder was removed. Namespaces can be hosted @@ -211,12 +177,11 @@ \subsection{v1.0-beta -- Sep 2020} \item The subject-ID range reduced from $[0, 32767]$ down to $[0, 8191]$. This change may be reverted in a future edition of the standard, if found practical. - \item Added support for delimited serialization; introduced related concepts of \emph{extent} and \emph{sealing} - (section \ref{sec:dsdl_composite_extent_and_sealing}). + \item Added support for delimited serialization; introduced related concepts of \emph{extent} and \emph{sealing}. This change enables one to easily evolve networked services in a backward-compatible way. \item Enabled the automatic runtime adjustment of the transfer-ID timeout on a per-subject basis - as a function of the transfer reception rate (section \ref{sec:transport_transfer_reception}). + as a function of the transfer reception rate. \end{itemize} \subsection{v1.0-alpha -- Jan 2020} diff --git a/specification/transport/can/can.tex b/specification/transport/can/can.tex index b30151f1..aa594a97 100644 --- a/specification/transport/can/can.tex +++ b/specification/transport/can/can.tex @@ -6,36 +6,10 @@ \section{Cyphal/CAN}\label{sec:transport_can} Throughout this section, ``CAN'' implies both Classic CAN 2.0 and CAN FD, unless specifically noted otherwise. CAN FD should be considered the primary transport protocol. -\begin{CyphalSimpleTable}{Cyphal/CAN transport capabilities}{|l X l|} - \label{table:transport_can_capabilities} - Parameter & Value & References \\ - - Maximum node-ID value & - 127 (7 bits wide). & - \ref{sec:basic} \\ - - Transfer-ID mode & - Cyclic, modulo 32. & - \ref{sec:transport_transfer_id} \\ - - Number of transfer priority levels & - 8 (no additional levels). & - \ref{sec:transport_transfer_priority} \\ - - Largest single-frame transfer payload & - Classic CAN -- 7~bytes, CAN FD -- up to 63~bytes. & - \ref{sec:transport_transfer_payload} \\ - - Anonymous transfers & - Supported with non-deterministic collision resolution policy. & - \ref{sec:transport_route_specifier} \\ -\end{CyphalSimpleTable} - \subsection{CAN ID field} Cyphal/CAN transport frames are CAN 2.0B frames. -The 29-bit CAN ID encodes the session specifier\footnote{Section~\ref{sec:transport_session_specifier}.} -of the transfer it belongs to along with its priority. +The 29-bit CAN ID encodes the addressing and priority information of the transfer it belongs to. The CAN data field of every frame contains the transfer payload (or, in the case of multi-frame transfers, a fraction thereof), the transfer-ID, and other metadata. @@ -283,8 +257,7 @@ \subsubsection{Source node-ID field in anonymous transfers}\label{sec:transport_ The described principles make anonymous transfers highly non-deterministic and inefficient. This is considered acceptable because the scope of anonymous transfers is limited to a very narrow set of use - cases which tolerate their downsides. The Cyphal specification employs anonymous transfers only for the - plug-and-play feature defined in section~\ref{sec:application_functions}. + cases which tolerate their downsides. Deterministic applications are advised to avoid reliance on anonymous transfers completely. None of the above considerations affect nodes that do not transmit anonymous transfers. @@ -316,7 +289,7 @@ \subsubsection{Layout} section~\ref{sec:transport_can_toggle_bit}. \\\hline 4 & & \multicolumn{2}{c|}{} \\ 3 & & \multicolumn{2}{c|}{Modulo 32 (range [0, 31])} \\ - 2 & \textbf{Transfer-ID} & \multicolumn{2}{c|}{section~\ref{sec:transport_transfer_id}} \\ + 2 & \textbf{Transfer-ID} & \multicolumn{2}{c|}{} \\ 1 & & \multicolumn{2}{c|}{} \\ 0 & & \multicolumn{2}{c|}{\footnotesize{(least significant bit)}} \\ \hline @@ -353,7 +326,6 @@ \subsubsection{Transfer payload decomposition} Usage of padding bytes implies that when a serialized message is being deserialized by a receiving node, the byte sequence used for deserialization may be longer than the actual byte sequence generated by the emitting node during serialization. - This behavior is compatible with the DSDL specification. The weak MTU requirement for CAN FD is designed to avoid compatibility issues. \end{remark} @@ -370,14 +342,15 @@ \subsubsection{Transfer CRC}\label{sec:transport_can_transfer_crc} This is the native byte order for this CRC function. }. -The transfer CRC function is \textbf{CRC-16/CCITT-FALSE} (section~\ref{sec:appendix_crc16ccitt_false}). +The transfer CRC function is \textbf{CRC-16/CCITT-FALSE}: +polynomial $0x1021$, initial value $0xFFFF$, no input or output reflection, no final XOR. \subsection{Examples} \begin{remark}[breakable] - Heartbeat from node-ID 42, nominal priority level, - uptime starting from 0 and then incrementing by one every transfer, health status is 0, - operating mode is 1, vendor-specific status code 161 ($A1_{16}$): + A periodic 7-byte status message from node-ID 42 at nominal priority level. + The four consecutive transfers differ only in the first payload byte and in the transfer-ID + field of the tail byte: \begin{CyphalCompactTable}{|l l|} CAN ID (hex) & CAN data (hex) \\ @@ -387,9 +360,10 @@ \subsection{Examples} \texttt{107D552A} & \texttt{03 00 00 00 00 01 A1 E3} \\ \end{CyphalCompactTable} - \verb|uavcan.primitive.String.1.0| under subject-ID 4919 ($1337_{16}$) published by an anonymous node, - the string is ``\verb|Hello world!|'' (ASCII); one byte of zero padding can be seen between - the payload and the tail byte: + A 14-byte payload under subject-ID 4919 ($1337_{16}$) published by an anonymous node. + The payload is a two-byte little-endian length prefix ($000C_{16}$) followed by the ASCII + string ``\verb|Hello world!|''; + one byte of zero padding can be seen between the payload and the tail byte: \begin{CyphalCompactTable}{|l l|} CAN ID (hex) & CAN data (hex) \\ @@ -399,8 +373,9 @@ \subsection{Examples} \texttt{11133775} & \texttt{0C 00 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 00 E3} \\ \end{CyphalCompactTable} - Node info request from node 123 to node 42 via Classic CAN, then response; - notice how the transfer CRC is scattered across two frames: + A zero-payload service request from node 123 to node 42 via Classic CAN, + followed by a multi-frame response carrying a variable-length descriptive string; + notice how the transfer CRC is scattered across the last two frames: \begin{CyphalCompactTable}{|l l l X|} CAN ID (hex) & CAN data (hex) & ASCII & Comment \\ @@ -442,8 +417,9 @@ \subsection{Examples} Transfer CRC, LSB. \\ \end{CyphalCompactTable} - \verb|uavcan.primitive.array.Natural8.1.0| under subject-ID 4919 ($1337_{16}$) published by node 59, - the array contains an arithmetic sequence $\left(0, 1, 2, \ldots{}, 89, 90, 91\right)$; + A 92-byte payload under subject-ID 4919 ($1337_{16}$) published by node 59. + The payload is a two-byte little-endian length prefix ($005C_{16}$) followed by the + arithmetic sequence $\left(0, 1, 2, \ldots{}, 89, 90, 91\right)$; the transport MTU is 64 bytes: \begin{CyphalCompactTable}{|l X[2] X|} @@ -479,8 +455,8 @@ \subsubsection{Ordered transmission} \subsubsection{Transmission timestamping} \begin{remark}[breakable] - Certain application-level functions of Cyphal may require the driver to timestamp outgoing transport frames, - e.g., the time synchronization function. + Some applications may require the driver to timestamp outgoing transport frames, + e.g., to measure end-to-end transmission latency or to support time synchronization. A sensible approach to transmission timestamping is built around the concept of \emph{loop-back frames}, which is described here. diff --git a/specification/transport/transport.tex b/specification/transport/transport.tex index 26a17730..79229242 100644 --- a/specification/transport/transport.tex +++ b/specification/transport/transport.tex @@ -147,10 +147,8 @@ \subsection{Redundancy} An exception to the above rule applies if the payload of the transfer is a function of the identity of the transport instance that carries the transfer\footnote{% - An example of such a special case is the time synchronization algorithm documented - in section~\ref{sec:application_functions}. + Specific examples include time synchronization algorithms or diagnostic probes. }. % Please keep \clearpage in front of every section to enforce clear separation! \clearpage\input{transport/can/can.tex} -\clearpage\input{transport/udp/udp.tex} diff --git a/specification/transport/udp/udp.tex b/specification/transport/udp/udp.tex deleted file mode 100644 index e481b319..00000000 --- a/specification/transport/udp/udp.tex +++ /dev/null @@ -1,156 +0,0 @@ -\section{Cyphal/UDP (experimental)}\label{sec:transport_udp} - -\hyphenation{Cyphal/UDP} % Disable hyphenation. - -\subsection{Overview} - -This section specifies a concrete transport based on the UDP/IPv4 protocol\footnote{% - Support for IPv6 may appear in future versions of this specification. -}, as specified in IETF RFC~768. - -Cyphal/UDP is a first-class transport intended for low-latency, -high-throughput intravehicular Ethernet networks with complex topologies, -which may be switched, multi-drop, or mixed. -A network utilizing Cyphal/UDP can be built with standards-compliant commercial off-the-shelf -networking equipment and software. - -In this transport, a UDP datagram represents a single Cyphal transport frame. -All UDP datagrams are addressed to the same, fixed, destination port, -while the source port and the source address bear no relevance for the protocol and thus can be arbitrary. - -\subsection{IPv4} - -TODO - -\begin{remark} - Freezing (at least) the 9 most significant bits of the multicast group address ensures that - the variability is confined to the 23 least significant bits of the address only, - which is desirable because the IPv4 Ethernet MAC layer does not differentiate beyond the - 23 least significant bits of the multicast group address. - That is, addresses that differ only in the 9 MSb collide at the MAC layer, - which is unacceptable in a real-time system; see RFC~1112 section 6.4. - Without this limitation, an engineer designing a network might inadvertently create a configuration - that causes MAC-layer collisions which may be difficult to detect. -\end{remark} - -Sources of Cyphal/UDP traffic should set the packet TTL to 16 or higher. - -\begin{remark} - RFC~1112 prescribes a default TTL of 1, - but this is not sufficient as Cyphal/UDP networks may often exceed the diameter of 1 hop. -\end{remark} - -\subsubsection{QoS} - -The DSCP\footnote{RFC~2474} field of outgoing IP packets \emph{should} -be populated based on the Cyphal transfer priority level (section~\ref{sec:transport_transfer_priority}) -as specified\footnote{% - The recommended DSCP mapping is derived from RFC~8837. - The implementation of suitable network policies is outside of the scope of this document. - RFC~4594 provides a starting point for the design of such policies. -} in table \ref{table:transport_udp_priority}. -Implementations \emph{should} provide a means to override the default DSCP mapping per node\footnote{ - E.g., via configuration registers described in section~\ref{sec:application_functions_register}. -} by the system integrator (user). -All nodes in a Cyphal/UDP network \emph{shall} be configured such that the applied DSCP mapping -is consistent with the QoS policies implemented in the network\footnote{% - This requirement is intended to prevent inconsistent QoS treatment of Cyphal/UDP traffic and priority inversion. -}. - -\begin{CyphalSimpleTable}{ - Default mapping from Cyphal priority level to DSCP\label{table:transport_udp_priority} -}{|l l l l|} - Cyphal priority & Header priority value (sec. \ref{sec:transport_udp_payload}) & DSCP class & DSCP value \\ - Exceptional & 0 & DF & 0 \\ - Immediate & 1 & DF & 0 \\ - Fast & 2 & DF & 0 \\ - High & 3 & DF & 0 \\ - Nominal & 4 & DF & 0 \\ - Low & 5 & DF & 0 \\ - Slow & 6 & DF & 0 \\ - Optional & 7 & DF & 0 \\ -\end{CyphalSimpleTable} - -\subsection{UDP datagram payload format}\label{sec:transport_udp_payload} - -The layout of the Cyphal/UDP datagram payload header is shown in the following snippet in DSDL notation. -The payload header is followed by the payload data, which is opaque to the protocol. - -\begin{samepage} -\begin{minted}{python} -# Cyphal/UDP provides UNRELIABLE UNORDERED DEDUPLICATED (at most one) delivery of UNICAST or MULTICAST datagrams -# with GUARANTEED INTEGRITY (messages either delivered correct or not delivered). -# -# All Cyphal/UDP traffic is sent to port 9382. -# The subject multicast group address is composed as 239.0.0.0 (=0xEF000000) + subject_id (23 bits). -# -# All frames of a transfer must share the same field values unless otherwise noted. -# Frames may arrive out-of-order, possibly interleaved with neighboring transfers; implementations must cope. -# -# The origin UID is a 64-bit globally unique EUI-64. It allows nodes to use redundant interfaces without source -# address ambiguity, and also allows live interface migration. -# -# Unicast traffic is sent directly to the source endpoint of the destination node, which is discovered dynamically. -# The destination UID is not included explicitly since the IP endpoint is considered adequate for node identification. - -uint5 version #=2 in this version. -uint3 priority # 0=EXCEPTIONAL ... 7=OPTIONAL. - -void5 # Send zero, ignore on reception. -uint3 incompatibility # Send zero, drop frame if nonzero. - -uint48 transfer_id # For multi-frame reassembly and dedup. -uint64 sender_uid # Origin identifier ensures invariance to the source IP address for reassembly. - -uint32 frame_payload_offset # The offset of the frame payload relative to the start of the transfer payload. -uint32 transfer_payload_size # Total for all frames. - -uint32 prefix_crc32c # crc32c(payload[0:(frame_payload_offset+payload_size)]) -uint32 header_crc32c # Covers all fields above. Same as the transfer payload CRC. - -@sealed # Header size 32 bytes; payload follows. -\end{minted} -\end{samepage} - -The CRC function is \textbf{CRC-32C (Castagnoli)}. - -\subsection{Maximum transmission unit} - -In this section, the maximum transmission unit (MTU) is defined as the maximum size of a UDP/IP datagram payload. - -As Cyphal provides native means of multi-frame transfer decomposition and reassembly -(section~\ref{sec:transport_transfer_payload}), -nodes emitting Cyphal/UDP traffic \emph{shall not} rely on IP fragmentation by -limiting the size of the UDP payload accordingly\footnote{ - This requirement is consistent with RFC~8085 and RFC~8900. - Transfers larger than the MTU limit will be transmitted as multi-frame Cyphal transfers. - The preference towards Cyphal fragmentation over IP fragmentation is to remove the limitation on the - maximum transfer size imposed by the UDP protocol and - to permit preemption of long transfers by higher-priority transfers. -}. -Support for IP fragmentation is optional for Cyphal/UDP receivers\footnote{% - Network equipment such as routers may perform IP fragmentation, - which is allowed as long as such behavior is opaque to the Cyphal/UDP end systems. -}. - -In multi-frame transfers, the payload size of all frames except the last one shall be the same. -The payload size of the last frame shall be greater than zero and not greater than that of the preceding -frames\footnote{% - The same-MTU constraint for all frames except the last is necessary to enable efficient reassembly of - multi-frame transfers with frames arriving out-of-order, including the case of frames interleaving between - adjacent transfers. -}. - -\subsection{Real-time and resource-constrained systems} - -It is anticipated that real-time and/or resource-constrained systems may implement Cyphal/UDP -based on custom UDP/IP and IGMP protocol stacks with a reduced feature set. -In particular, such systems may not support IP fragmentation and ICMP. - -The networking equipment that such systems are connected to is recommended to not emit ICMP messages because: - -\begin{enumerate} - \item This increases the network load which can modify the timing of the system - and which may be considered an attack vector for some systems. - \item Cyphal/UDP nodes are not required to support ICMP and may therefore not be able to process ICMP messages. -\end{enumerate} From fc30aeee896767228cfa4ee668c838435b37b4ed Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 14:47:03 +0300 Subject: [PATCH 07/14] can.tex update --- specification/transport/can/can.tex | 118 +++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 21 deletions(-) diff --git a/specification/transport/can/can.tex b/specification/transport/can/can.tex index aa594a97..25bda34e 100644 --- a/specification/transport/can/can.tex +++ b/specification/transport/can/can.tex @@ -6,6 +6,25 @@ \section{Cyphal/CAN}\label{sec:transport_can} Throughout this section, ``CAN'' implies both Classic CAN 2.0 and CAN FD, unless specifically noted otherwise. CAN FD should be considered the primary transport protocol. +\subsection{Definitions} + +\begin{description} + \item[Node-ID] A 7-bit numerical identifier of a node, in the range $[0, 127]$. + Nodes on the same Cyphal/CAN network shall have distinct node-IDs. + + \item[Subject-ID] A 16-bit numerical identifier of a publish-subscribe subject, in the range $[0, 65535]$. + Cyphal/CAN v1.0 supported only the 13-bit subject-ID range $[0, 8191]$. + + \item[Service-ID] A 9-bit numerical identifier of a remote procedure call service, in the range $[0, 511]$. + Cyphal v1.1 only uses service-ID 511 for unicast exchanges; the remaining IDs are only useful for interaction + with pre-v1.1 nodes. + + \item[Transfer-ID] A cyclic counter taken modulo 32 that distinguishes consecutive transfers originated by + the same source node on the same subject or service. Used for duplicate suppression and for ordering the + frames of a multi-frame transfer. The sender increments it once per transfer; the receiver applies a timeout + to bound its state. +\end{description} + \subsection{CAN ID field} Cyphal/CAN transport frames are CAN 2.0B frames. @@ -18,11 +37,18 @@ \subsection{CAN ID field} However, future revisions of Cyphal/CAN may utilize CAN 2.0A as well, so backward compatibility with other high-level CAN bus protocols is not guaranteed. -Cyphal/CAN utilizes two different CAN ID bit layouts for message transfers and service transfers. -The bit layouts are summarized on figure~\ref{fig:transport_can_id_structure}. -Tables~\ref{table:transport_can_id_fields_message_transfer} and~\ref{table:transport_can_id_fields_service_transfer} -summarize the purpose of each field and their permitted values -for message transfers and service transfers, respectively. +Cyphal/CAN defines three CAN ID bit layouts, summarized on figure~\ref{fig:transport_can_id_structure}: +the \textbf{v1.1 message transfer} format with a 16-bit subject-ID; +the \textbf{v1.0 message transfer} format with a 13-bit subject-ID, retained for wire compatibility with +Cyphal/CAN v1.0 nodes; and the \textbf{service transfer} format, shared by both versions. +The two message formats are discriminated by the value of CAN ID bit~7: +v1.0 messages have bit~7 cleared, while v1.1 messages have bit~7 set. +A v1.0 receiver silently discards v1.1 message frames because they fail the v1.0 reserved-bit check at bit~7, +which allows a v1.1 network to coexist with legacy v1.0 nodes on the same bus. +Tables~\ref{table:transport_can_id_fields_message_transfer_v11}, +\ref{table:transport_can_id_fields_message_transfer_v10}, +and~\ref{table:transport_can_id_fields_service_transfer} +summarize the fields of each layout. % Please do not remove the hard placement specifier [H], it is needed to keep elements ordered. \begin{figure}[H] @@ -31,13 +57,54 @@ \subsection{CAN ID field} \footnotesize \begin{tabular}{|l|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|} \hline % - % Message transfer + % v1.1 16-bit message transfer % - \multirow{2}{*}{\textbf{Message}} & + \multirow{2}{*}{\textbf{Message 16-bit}} & + \multicolumn{4}{c|}{Service, not message} & + \multicolumn{1}{c|}{\multirow{2}{*}{R}} & + \multicolumn{16}{c|}{\multirow{2}{*}{Subject-ID}} & + \multicolumn{1}{c|}{\multirow{2}{*}{V}} & + \multicolumn{7}{c|}{\multirow{2}{*}{Source node-ID}} + \\\cline{2-4} + + & + \multicolumn{3}{c|}{Priority} + & + & + & + \multicolumn{16}{c|}{} & + & + \multicolumn{7}{c|}{} + \\ + + \textbf{Values} & + \multicolumn{3}{c|}{$[0, 7]$} & + $0$ & + $0$ & + \multicolumn{16}{c|}{$[0, 65535]$} & + $1$ & + \multicolumn{7}{c|}{$[0, 127]$} + \\\hline + + \textbf{CAN ID bit} & + 28 & 27 & 26 & 25 & 24 & 23 & 22 & 21 & 20 & 19 & 18 & 17 & 16 & 15 & + 14 & 13 & 12 & 11 & 10 & 9 & 8 & 7 & 6 & 5 & 4 & 3 & 2 & 1 & 0 + \\\hline + + \textbf{CAN ID byte} & + \multicolumn{5}{c|}{3} & \multicolumn{8}{c|}{2} & \multicolumn{8}{c|}{1} & \multicolumn{8}{c|}{0} + \\\hline + + \multicolumn{30}{c}{} \\ \hline % Table separator + + % + % v1.0 13-bit message transfer + % + \multirow{2}{*}{\textbf{Message 13-bit}} & \multicolumn{4}{c|}{Service, not message} & \multicolumn{4}{c|}{Anonymous} & \multicolumn{13}{c|}{\multirow{2}{*}{Subject-ID}} & - \multicolumn{1}{c|}{\multirow{2}{*}{R}} & + \multicolumn{1}{c|}{\multirow{2}{*}{V}} & \multicolumn{7}{c|}{\multirow{2}{*}{Source node-ID}} \\\cline{2-4} \cline{7-9} @@ -121,8 +188,27 @@ \subsection{CAN ID field} \caption{CAN ID bit layout}\label{fig:transport_can_id_structure} \end{figure} -\begin{CyphalSimpleTable}[wide]{CAN ID bit fields for message transfers}{|l l l X|} - \label{table:transport_can_id_fields_message_transfer} +\begin{CyphalSimpleTable}[wide]{CAN ID bit fields for 16-bit v1.1 message transfers}{|l l l X|} + \label{table:transport_can_id_fields_message_transfer_v11} + Field & Width & Valid values & Description \\ + + Transfer priority & 3 & $[0, 7]$ (any) & Section~\ref{sec:transport_transfer_priority}. \\ + + Service, not message & 1 & $0$ & Always zero for message transfers. \\ + + Reserved bit 24 & 1 & $0$ & Discard frame if this field has a different value. + Occupied the anonymous flag in Cyphal/CAN v1.0; + anonymous transfers are not supported in v1.1. \\ + + Subject-ID & 16 & $[0, 65535]$ (any) & Subject-ID of the current message transfer. \\ + + Version bit 7 & 1 & $1$ & Always one for v1.1 message transfers. \\ + + Source node-ID & 7 & $[0, 127]$ (any) & Node-ID of the origin. \\ +\end{CyphalSimpleTable} + +\begin{CyphalSimpleTable}[wide]{CAN ID bit fields for 13-bit v1.0 message transfers}{|l l l X|} + \label{table:transport_can_id_fields_message_transfer_v10} Field & Width & Valid values & Description \\ Transfer priority & 3 & $[0, 7]$ (any) & Section~\ref{sec:transport_transfer_priority}. \\ @@ -134,22 +220,12 @@ \subsection{CAN ID field} Reserved bit 23 & 1 & $0$ & Discard frame if this field has a different value. \\ - % The asymmetric requirement to transmit only 1 and accept any value is due to the need to ensure compatibility - % with the implementations following the v1-alpha spec, where the width of the subject-ID field was 15 bit - % (two bits wider). We keep the most significant bits set to ensure the compatibility of the regulated subject-IDs - % between v1-alpha and v1-beta. Ensuring the compatibility of unregulated subject-IDs is far less important because - % generally, they are easily changeable. - % - % Such backward-compatible change renders these two bits unusable for the possible future expansion of the - % subject-ID field, but this is fine, because shall the expansion become imminent, we can always flip R7 from 0 - % to 1, and thus pave the way for a completely new bit layout format with a wider subject-ID. Meanwhile, R21 and - % R22 can be leveraged for some additional optional features. Reserved bit 22 & 1 & $1$, any & Transmit $1$; ignore (do not check) when receiving. \\ Reserved bit 21 & 1 & $1$, any & Transmit $1$; ignore (do not check) when receiving. \\ Subject-ID & 13 & $[0, 8191]$ (any) & Subject-ID of the current message transfer. \\ - Reserved bit 7 & 1 & $0$ & Discard frame if this field has a different value. \\ + Version bit 7 & 1 & $0$ & Always zero for v1.0 legacy message transfers. \\ Source node-ID & 7 & $[0, 127]$ (any) & Node-ID of the origin. For anonymous transfers, this field contains a pseudo-ID instead, From da3667fe00caa1bdccef47687f946d5e4d347f79 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 15:09:46 +0300 Subject: [PATCH 08/14] transport updates --- specification/transport/can/can.tex | 74 +++++++++++++++++++++++++++ specification/transport/transport.tex | 45 +++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/specification/transport/can/can.tex b/specification/transport/can/can.tex index 25bda34e..d9528798 100644 --- a/specification/transport/can/can.tex +++ b/specification/transport/can/can.tex @@ -255,6 +255,53 @@ \subsection{CAN ID field} client if request, server if response. \\ \end{CyphalSimpleTable} +\subsubsection{Format selection}\label{sec:transport_can_format_selection} + +The two message formats differ not only in the CAN ID bit layout but also in the payload carried on the wire. +The v1.1 16-bit format carries a full session-layer message including its session-layer header; +the v1.0 legacy 13-bit format carries only the raw application payload without any session-layer wrapping, +which is what makes it wire-compatible with Cyphal/CAN v1.0 nodes that predate the session layer. + +A sender shall use the v1.0 legacy 13-bit format for a message transfer if and only if both of the following hold: +(a) the subject-ID lies in the pinned range $[0, 8191]$, and +(b) the message is a best-effort publication. +In all other cases -- auto-allocated subject-IDs, the broadcast subject, gossip shards, reliable messages, +gossip messages, service discovery, and RPC -- the sender shall use the v1.1 16-bit format. + +When transmitting on a pinned subject using the 13-bit format, the sender shall strip the session-layer +header from the outgoing message before enqueuing the frame, so that only the raw application payload +appears on the wire. This is what makes a 13-bit frame parseable by Cyphal/CAN v1.0 nodes, +which are unaware of the session layer. + +When receiving a 13-bit-format frame on a subscribed pinned subject, the receiver shall reconstruct a +session-layer header consistent with the matching pinned topic and prepend it to the received payload +before forwarding the message to the session layer. The reconstructed header shall declare the message as +best-effort and shall carry a message tag drawn from a monotonically increasing per-subscription counter, +so that session-layer duplicate suppression continues to function. +The reconstructed message shall be otherwise indistinguishable from an equivalent message originated by +a v1.1 node on the same pinned topic. + +\begin{remark} + This rule confines every v1.1-specific feature to the 16-bit format, which v1.0 receivers unconditionally + discard at the bit~7 check. A message reaching a v1.0 receiver is therefore always in a form that the + receiver can parse: a plain application payload on a pinned subject-ID. +\end{remark} + +\subsubsection{Unicast transfers}\label{sec:transport_can_unicast} + +Cyphal/CAN does not define a dedicated CAN ID layout for unicast delivery. +A unicast transfer shall be represented as an RPC-service \emph{request} to the reserved service-ID $511$ +addressed to the destination node. +The session layer treats service-ID $511$ as an opaque unicast channel; +no service response is issued at the transport layer, and higher-layer reply semantics, if any, +are carried in subsequent unicast requests in the opposite direction. + +The sender shall maintain a $128$-element array of transfer-ID counters indexed by the destination node-ID, +and increment the entry corresponding to the destination of each outgoing unicast transfer. +This ensures that the receiver observes a monotonic transfer-ID stream on every $(\text{source}, \text{destination})$ +pair, so that the standard transfer-ID timeout check +(section~\ref{sec:transport_can_transfer_id_timeout}) applies without modification. + \subsubsection{Transfer priority} Valid values for transfer priority range from 0 to 7, inclusively, @@ -421,6 +468,33 @@ \subsubsection{Transfer CRC}\label{sec:transport_can_transfer_crc} The transfer CRC function is \textbf{CRC-16/CCITT-FALSE}: polynomial $0x1021$, initial value $0xFFFF$, no input or output reflection, no final XOR. +\subsubsection{Transfer-ID timeout}\label{sec:transport_can_transfer_id_timeout} + +The \emph{transfer-ID timeout} governs the duration over which a receiver retains the last-admitted +transfer-ID of a reassembly session for duplicate suppression. A start-of-transfer frame whose transfer-ID +matches the last-admitted value within the timeout window is discarded as a duplicate; +after the timeout elapses, the next admission is accepted unconditionally. +The recommended default value is $2$ seconds. + +The timeout applies to the following session kinds: +\begin{itemize} + \item Message (subject) sessions, in both the v1.1 16-bit and the v1.0 legacy 13-bit formats. + \item Service request sessions, including the unicast channel defined in + section~\ref{sec:transport_can_unicast}. +\end{itemize} + +A transfer-ID timeout of \emph{zero} shall be used for service \emph{response} sessions. +The transfer-ID of a service response is not generated by the server -- it is echoed from the request being +answered. Because the transfer-ID field is only five bits wide, the client may legitimately re-use the same +transfer-ID value for two different requests to the same server within a short interval as its counter wraps; +with a nonzero timeout, the later matching response would be rejected as a duplicate of the earlier one. +Setting the timeout to zero disables transport-level duplicate suppression on response sessions, +leaving the receiving application to correlate responses with outstanding requests by other +means\footnote{% + See \href{https://github.com/OpenCyphal/libcanard/issues/247}{OpenCyphal/libcanard issue~247} + for the original discussion. +}. + \subsection{Examples} \begin{remark}[breakable] diff --git a/specification/transport/transport.tex b/specification/transport/transport.tex index 79229242..82b559fd 100644 --- a/specification/transport/transport.tex +++ b/specification/transport/transport.tex @@ -2,7 +2,26 @@ \chapter{Transport layer}\label{sec:transport} \section{Function} -TODO: DEFINE THE CORE TRANSPORT CONTRACT PER cy. +A Cyphal \emph{transport} provides unreliable, unordered, deduplicated delivery of integrity-checked messages +between Cyphal nodes. A message is either delivered intact or not delivered at all; the transport performs +neither retransmission nor ordering recovery. Reliable delivery, ordering, and session semantics, where required, +are provided by higher layers on top of this minimal contract. + +A transport shall provide two delivery modes: +\begin{description} + \item[Multicast] Delivery to the group of subscribers on a \emph{subject}, identified by a numerical + \emph{subject-ID}\footnote{In IP networks this analogous to multicast groups.}. + \item[Unicast] Delivery to a single \emph{remote node}, identified by its \emph{node-ID}. +\end{description} + +Messages may be of arbitrary size not larger than 4 gibibytes. +A transport shall perform segmentation and reassembly transparently to the higher layers, +so that a received message is always delivered as a single unit. +A transport may offer redundant interfaces for fault tolerance, with transparent failover, +which is invisible to the higher layers. + +Participant discovery is provided implicitly as a side effect of joining the broadcast subject defined in +section~\ref{sec:transport_subject_id_ranges}; no separate discovery protocol is required. \section{Definitions} @@ -150,5 +169,29 @@ \subsection{Redundancy} Specific examples include time synchronization algorithms or diagnostic probes. }. +\subsection{Subject-ID ranges}\label{sec:transport_subject_id_ranges} + +Subject-ID values range from zero up to a transport-specific maximum and are partitioned as follows: + +\begin{description} + \item[{$[0,\, 8191]$ -- pinned subjects}] Statically-assigned subject-IDs. + This range is the only one used by Cyphal/CAN v1.0 and is retained as the primary path for interoperation + with v1.0 nodes. + + \item[{$[8192,\, 8191{+}M]$ -- auto-allocated subjects}] Subject-IDs assigned dynamically by the session layer, + where $M$ is a transport-specific prime \emph{modulus} satisfying $M \bmod 4 = 3$. + + \item[Maximum value -- broadcast subject] Used by the session layer for low-rate broadcast traffic such as + topic allocation gossip and service discovery scouts. + + \item[Remaining values -- gossip shards] Subject-IDs strictly between $(8191{+}M)$ and the broadcast subject + are used as sharded subjects for background gossip propagation. +\end{description} + +On the broadcast subject and on the gossip shard subjects, the transport is permitted to relax two requirements: +(a) deduplication is not mandatory, and (b) support for messages exceeding a single transport frame is not required. +The session layer tolerates occasional duplication on these subjects and constrains its own protocol messages +to fit within a single transport frame. + % Please keep \clearpage in front of every section to enforce clear separation! \clearpage\input{transport/can/can.tex} From a62b5216e3bae50d3fa18e1bb16a89b7465b1983 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 15:26:02 +0300 Subject: [PATCH 09/14] CAN update --- specification/transport/can/can.tex | 41 +++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/specification/transport/can/can.tex b/specification/transport/can/can.tex index d9528798..e0bee980 100644 --- a/specification/transport/can/can.tex +++ b/specification/transport/can/can.tex @@ -275,11 +275,8 @@ \subsubsection{Format selection}\label{sec:transport_can_format_selection} When receiving a 13-bit-format frame on a subscribed pinned subject, the receiver shall reconstruct a session-layer header consistent with the matching pinned topic and prepend it to the received payload -before forwarding the message to the session layer. The reconstructed header shall declare the message as -best-effort and shall carry a message tag drawn from a monotonically increasing per-subscription counter, -so that session-layer duplicate suppression continues to function. -The reconstructed message shall be otherwise indistinguishable from an equivalent message originated by -a v1.1 node on the same pinned topic. +before forwarding the message to the session layer. The reconstructed message shall be otherwise +indistinguishable from an equivalent message originated by a v1.1 node on the same pinned topic. \begin{remark} This rule confines every v1.1-specific feature to the 16-bit format, which v1.0 receivers unconditionally @@ -287,6 +284,40 @@ \subsubsection{Format selection}\label{sec:transport_can_format_selection} receiver can parse: a plain application payload on a pinned subject-ID. \end{remark} +\subsubsection{Session header reconstruction}\label{sec:transport_can_v10_header_reconstruction} + +The reconstructed header shall be a session-layer message header +whose fields are populated as listed in table~\ref{table:transport_can_v10_header_reconstruction}, +with $S$ denoting the pinned subject-ID of the received frame. All other header fields shall take their +default values for a best-effort message. The resulting message shall be otherwise indistinguishable from +an equivalent message originated by a v1.1 node on the same pinned topic. + +\begin{CyphalSimpleTable}{Session header field values for reconstructed 13-bit message transfers}{|l X|} + \label{table:transport_can_v10_header_reconstruction} + Field & Value \\ + + Header type & Message, best-effort. \\ + + Log-age & $-1$, signalling that no causality information is available. \\ + + Eviction counter & The canonical pinned-topic encoding of the subject-ID $S$. \\ + + Topic hash & $\mathrm{rapidhash}(D(S))$, where $D(S)$ is the ASCII decimal representation of $S$ + without leading zeros (e.g.\ $S=42$ yields the two-byte string \texttt{"42"}; + $S=0$ yields the one-byte string \texttt{"0"}), and $\mathrm{rapidhash}$ is the 64-bit + rapidhash function invoked with the default seed of zero. \\ + + Message tag & A monotonically increasing per-subscription counter that is incremented once for every + reconstructed message, so that session-layer duplicate suppression continues to function. + The initial value is implementation-defined. \\ +\end{CyphalSimpleTable} + +A symmetric rule applies on the transmit side: a sender using the 13-bit format shall strip the session-layer +header from the outgoing message and transmit only the remaining application payload. +The session layer is expected to ensure that the stripped header corresponds to a best-effort message +on a pinned topic, which is the only case in which the 13-bit format is used +(section~\ref{sec:transport_can_format_selection}). + \subsubsection{Unicast transfers}\label{sec:transport_can_unicast} Cyphal/CAN does not define a dedicated CAN ID layout for unicast delivery. From 176ebe9f77e8a709655b693f24c9846e286e6bd1 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 15:45:52 +0300 Subject: [PATCH 10/14] udp --- specification/transport/transport.tex | 1 + specification/transport/udp/udp.tex | 212 ++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 specification/transport/udp/udp.tex diff --git a/specification/transport/transport.tex b/specification/transport/transport.tex index 82b559fd..d9eca597 100644 --- a/specification/transport/transport.tex +++ b/specification/transport/transport.tex @@ -195,3 +195,4 @@ \subsection{Subject-ID ranges}\label{sec:transport_subject_id_ranges} % Please keep \clearpage in front of every section to enforce clear separation! \clearpage\input{transport/can/can.tex} +\clearpage\input{transport/udp/udp.tex} diff --git a/specification/transport/udp/udp.tex b/specification/transport/udp/udp.tex new file mode 100644 index 00000000..92137780 --- /dev/null +++ b/specification/transport/udp/udp.tex @@ -0,0 +1,212 @@ +\section{Cyphal/UDP}\label{sec:transport_udp} + +\hyphenation{Cyphal/UDP} % Disable hyphenation. + +\subsection{Overview} + +This section specifies a concrete transport based on the UDP/IPv4 protocol\footnote{% + Support for IPv6 may appear in a future revision of this specification. +} as specified in IETF RFC~768. +Cyphal/UDP is the recommended transport for intravehicular Ethernet networks with complex topologies, +which may be switched, multi-drop, or mixed, and can be built with standards-compliant commercial +off-the-shelf networking equipment and software. + +A UDP datagram represents a single Cyphal transport frame. +The maximum subject-ID supported by Cyphal/UDP is $2^{23}-1 = 8388607$, which matches the addressable space +of the IPv4 multicast MAC layer; see section~\ref{sec:transport_udp_endpoints} for details. +The maximum transfer payload size is $2^{32}-1$ bytes. +Implementations shall provide a UDP/IPv4 stack with IGMP v2 (or later) and passive ARP support. + +A Cyphal/UDP node is identified on the network by a $64$-bit \emph{node UID}, which shall be globally unique. +The UID enables a node to freely change its IP address or migrate between network interfaces at runtime. +A common choice is an EUI-64 identifier, optionally drawn randomly per startup for short-lived software nodes. + +\subsection{Datagram addressing}\label{sec:transport_udp_endpoints} + +\subsubsection{Multicast} + +Multicast transfers carry one Cyphal subject each. +A subject with subject-ID $s$ shall be mapped to the IPv4 multicast group +$239.0.0.0 \;|\; (s \;\&\; \text{0x7FFFFF})$\footnote{% + $|$ denotes bitwise OR, $\&$ bitwise AND. +} on the reserved UDP destination port $9382$. +The $9$ most significant bits of the multicast address are thus fixed at $11101111\,0_2$. + +\begin{remark} + Freezing the $9$ most significant bits of the multicast group address confines address variability to + the $23$ least significant bits. This is necessary because the IPv4 Ethernet MAC layer does not + differentiate beyond the $23$ least significant bits of the multicast group address + (RFC~1112 section~6.4): addresses that differ only in the $9$ most significant bits alias at the MAC + layer, which is unacceptable in a real-time system. + Without this constraint, an engineer designing a network might inadvertently create a configuration + that causes MAC-layer collisions that are difficult to detect. +\end{remark} + +Sources of Cyphal/UDP multicast traffic \emph{should} set the IP packet TTL to $16$ or higher. + +\begin{remark} + RFC~1112 prescribes a default TTL of $1$, which is not sufficient because Cyphal/UDP networks may + exceed a diameter of one hop. +\end{remark} + +\subsubsection{Unicast} + +Cyphal/UDP does not reserve a dedicated endpoint for unicast delivery. +A unicast transfer shall be addressed to the recipient's UDP/IP endpoint as observed on an earlier +datagram originated by that recipient on the same interface. +A receiving node is expected to retain the $(\text{interface}, \text{source endpoint})$ observation for +each remote UID in order to enable subsequent unicast replies. + +\begin{remark} + This convention allows Cyphal/UDP nodes to change their IP addresses and even migrate between + network interfaces dynamically, provided they continue to publish on subjects + (section~\ref{sec:transport_subject_id_ranges}) so that their peers can rediscover them. +\end{remark} + +\subsection{QoS}\label{sec:transport_udp_qos} + +The DSCP\footnote{RFC~2474} field of outgoing IP packets \emph{should} be populated based on the Cyphal +transfer priority level of the corresponding transfer. +Implementations \emph{should} provide a means for the integrator to configure the mapping from Cyphal +priority to DSCP per node. +The integrator \emph{shall} ensure that the applied mapping is consistent with the QoS policies +implemented in the network\footnote{% + This requirement is intended to prevent inconsistent QoS treatment of Cyphal/UDP traffic and priority + inversion. RFC~4594 provides a starting point for the design of network QoS policies; + RFC~8837 provides a useful reference for real-time traffic classes. +}. + +In the absence of an integrator-provided mapping, a conforming implementation \emph{should} default to the +conservative mapping shown in table~\ref{table:transport_udp_priority}, which places all Cyphal/UDP traffic +into the Default Forwarding class. This default is safe in that it does not depend on any network QoS +configuration; where QoS differentiation is desired, the integrator is expected to configure the mapping +explicitly. + +\begin{CyphalSimpleTable}{ + Default mapping from Cyphal priority level to DSCP\label{table:transport_udp_priority} +}{|l l l l|} + Cyphal priority & Header priority value & DSCP class & DSCP value \\ + Exceptional & 0 & DF & 0 \\ + Immediate & 1 & DF & 0 \\ + Fast & 2 & DF & 0 \\ + High & 3 & DF & 0 \\ + Nominal & 4 & DF & 0 \\ + Low & 5 & DF & 0 \\ + Slow & 6 & DF & 0 \\ + Optional & 7 & DF & 0 \\ +\end{CyphalSimpleTable} + +\subsection{Datagram payload format}\label{sec:transport_udp_payload} + +Each UDP datagram payload begins with a fixed-size $32$-byte Cyphal/UDP header followed by the transport +frame payload, which is opaque to the transport. The header layout is shown in the following snippet in +pseudo-DSDL notation; multi-byte integers are little-endian. + +\begin{samepage} +\begin{minted}{python} +uint5 version # =2 in this version. +uint3 priority # 0=highest, 7=lowest. + +void5 # Send zero, ignore on reception. +uint3 incompatibility # Send zero, drop frame if nonzero. + +uint48 transfer_id # For multi-frame reassembly and deduplication. +uint64 sender_uid # The 64-bit UID of the originating node. + +uint32 frame_payload_offset # Offset of this frame's payload within the transfer payload. +uint32 transfer_payload_size # Total transfer payload size, identical for every frame of the transfer. + +uint32 prefix_crc32c # crc32c(transfer_payload[0 : frame_payload_offset + frame_payload_size]) +uint32 header_crc32c # crc32c(first 28 bytes of this header) + +# Header size is 32 bytes; the transport frame payload follows and is opaque to the protocol. +\end{minted} +\end{samepage} + +All frames belonging to the same transfer shall carry identical values of \texttt{priority}, +\texttt{transfer\_id}, \texttt{sender\_uid}, and \texttt{transfer\_payload\_size}. +A receiver shall discard any frame whose \texttt{version} field does not equal the expected value, +and any frame whose \texttt{incompatibility} field is non-zero. +Frames may arrive out-of-order and may interleave with neighboring transfers; +implementations shall cope with both conditions. + +The CRC function is \textbf{CRC-32C (Castagnoli)}: polynomial $0x1EDC6F41$, initial value $0xFFFFFFFF$, +input reflected, output reflected, final XOR $0xFFFFFFFF$. +The \texttt{header\_crc32c} field protects the first $28$ bytes of the header and is verified +independently on every received frame, allowing malformed frames to be rejected without parsing the body. +The \texttt{prefix\_crc32c} field holds a running CRC computed over the accumulated transfer payload bytes +from offset zero up to and including the current frame's payload. On single-frame transfers +(\texttt{frame\_payload\_offset} equal to zero and \texttt{frame\_payload\_size} equal to +\texttt{transfer\_payload\_size}) it therefore doubles as the end-to-end transfer integrity check; +on multi-frame transfers, the receiver uses it to validate the reassembled transfer as each frame is +admitted and to detect errors early. + +\subsection{Maximum transmission unit}\label{sec:transport_udp_mtu} + +The maximum transmission unit (MTU) is defined as the maximum size of a UDP/IP datagram payload, +inclusive of the $32$-byte Cyphal/UDP header. + +Because Cyphal provides native segmentation and reassembly (section~\ref{sec:transport}), +nodes emitting Cyphal/UDP traffic \emph{shall not} rely on IP fragmentation; +implementations shall limit the size of the UDP payload accordingly\footnote{% + This requirement is consistent with RFC~8085 and RFC~8900. Transfers larger than the MTU limit shall + be emitted as multi-frame transfers. The preference for Cyphal segmentation over IP fragmentation + removes the $\sim$65~KiB transfer size ceiling imposed by the UDP protocol and permits preemption of + long transfers by higher-priority traffic. +}. +Support for IP fragmentation is optional for receivers. +Intermediate network equipment may perform IP fragmentation as long as the behavior is opaque to the +Cyphal/UDP end systems. + +In multi-frame transfers, all frames except the last shall carry the same amount of frame payload. +The last frame shall carry a non-zero amount of frame payload not greater than that of the preceding +frames\footnote{% + This constraint enables efficient reassembly of multi-frame transfers with frames arriving + out-of-order, including the case of frame interleaving between adjacent transfers. +}. + +\subsection{Transfer-ID initialization}\label{sec:transport_udp_transfer_id} + +The sender shall assign a unique $48$-bit \texttt{transfer\_id} to every transfer it emits and shall +increment the counter once per emitted transfer. +The initial value of the counter \emph{shall} be drawn randomly at each node startup such that it is +unlikely to coincide with any transfer-ID value recently used by the same sender UID. +A uniformly-distributed $48$-bit random draw provides sufficient entropy for this purpose with +overwhelming probability. + +Random initialization obviates the need for a transfer-ID timeout at the receiver: because sender UIDs +are globally unique and the transfer-ID counter does not wrap within the effective lifetime of the +protocol, stale transfers from an earlier epoch do not collide with fresh ones. +This simplifies the receive pipeline, but it places a correctness requirement on the quality of the +random source used to seed the counter. + +The random source used to seed the transfer-ID counter \emph{shall} produce an initial state that is +distinct across reboots in quick succession, and \emph{should} mix the seed with the local node UID for +additional entropy. +A hardware true-random generator is preferred. +In its absence, a well-seeded pseudorandom generator is acceptable; suitable seed sources for embedded +systems without a TRNG include: + +\begin{itemize} + \item A counter held in battery-backed memory or a \texttt{.noinit} RAM section that survives resets. + \item A hash of uninitialized SRAM contents sampled at startup. + \item Sampled ADC or clock noise. + \item The current value of an RTC combined with a persistent counter. +\end{itemize} + +A single well-seeded random draw at startup is sufficient; the counter itself need not come from a +cryptographic source once initialized. + +\subsection{Real-time and resource-constrained systems} + +Real-time or resource-constrained systems may implement Cyphal/UDP using a custom UDP/IP and IGMP stack +with a reduced feature set; in particular, they may omit support for IP fragmentation and ICMP. + +Networking equipment connected to such systems is \emph{recommended} to suppress ICMP emission, because: + +\begin{enumerate} + \item ICMP traffic increases network load and may perturb the timing of the system; in some + deployments it can also be considered an attack vector. + \item Cyphal/UDP nodes are not required to support ICMP and may therefore be unable to process + incoming ICMP messages. +\end{enumerate} From 1f8a1f74a5095aa7ce9f6a9391a0b95fd1977b61 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 21:29:42 +0300 Subject: [PATCH 11/14] wip --- specification/Cyphal_Specification.tex | 6 +- specification/appendices/appendices.tex | 6 + specification/appendices/core_tla.tex | 256 +++++++++++++++ specification/appendices/eviction.tex | 88 +++++ specification/appendices/proof.tex | 173 ++++++++++ specification/appendices/rapidhash.tex | 88 +++++ specification/introduction/introduction.tex | 58 ++-- specification/session/crdt.tex | 239 ++++++++++++++ specification/session/gossip.tex | 152 +++++++++ specification/session/messages.tex | 294 +++++++++++++++++ specification/session/names.tex | 337 ++++++++++++++++++++ specification/session/parameters.tex | 40 +++ specification/session/rpc.tex | 173 ++++++++++ specification/session/session.tex | 118 +++++++ specification/transport/can/can.tex | 134 ++++---- specification/transport/transport.tex | 45 +-- specification/transport/udp/udp.tex | 23 +- 17 files changed, 2067 insertions(+), 163 deletions(-) create mode 100644 specification/appendices/appendices.tex create mode 100644 specification/appendices/core_tla.tex create mode 100644 specification/appendices/eviction.tex create mode 100644 specification/appendices/proof.tex create mode 100644 specification/appendices/rapidhash.tex create mode 100644 specification/session/crdt.tex create mode 100644 specification/session/gossip.tex create mode 100644 specification/session/messages.tex create mode 100644 specification/session/names.tex create mode 100644 specification/session/parameters.tex create mode 100644 specification/session/rpc.tex create mode 100644 specification/session/session.tex diff --git a/specification/Cyphal_Specification.tex b/specification/Cyphal_Specification.tex index e4cdaa82..155ce60b 100644 --- a/specification/Cyphal_Specification.tex +++ b/specification/Cyphal_Specification.tex @@ -81,12 +81,14 @@ \section*{Limitation of liability} \end{titlepage} \tableofcontents -\listoftables -\listoffigures +\clearpage\listoftables +\BeginRightColumn\listoffigures \mainmatter \input{introduction/introduction.tex} +\input{session/session.tex} \input{transport/transport.tex} +\input{appendices/appendices.tex} \end{document} diff --git a/specification/appendices/appendices.tex b/specification/appendices/appendices.tex new file mode 100644 index 00000000..7ed4b05e --- /dev/null +++ b/specification/appendices/appendices.tex @@ -0,0 +1,6 @@ +\appendix + +\input{appendices/eviction.tex} +\input{appendices/core_tla.tex} +\input{appendices/proof.tex} +\input{appendices/rapidhash.tex} diff --git a/specification/appendices/core_tla.tex b/specification/appendices/core_tla.tex new file mode 100644 index 00000000..4731d386 --- /dev/null +++ b/specification/appendices/core_tla.tex @@ -0,0 +1,256 @@ +\chapter{\texorpdfstring{TLA\textsuperscript{+}}{TLA+} model of the allocation CRDT}\label{sec:appendix_core_tla} + +This appendix reproduces the complete contents of +\texttt{model/tlaplus/Core.tla} from the Cy reference implementation. +Core.tla is the primary source of truth for the semantics of the topic +allocation CRDT described in section~\ref{sec:session_crdt}; where this +specification and Core.tla disagree, Core.tla is authoritative. +The \verb|Check_| statements have been removed from this transcript. + +\begin{minted}[fontsize=\scriptsize]{text} +------------------------------ MODULE Core ------------------------------ +\* Key operators defined on Cyphal named topics. This is the core part of the formal protocol specification. +\* This is pure TLA+ without PlusCal. +\* +\* The decentralized topic subject-ID allocation protocol in Cyphal solves the problem of allocating unique subject-IDs +\* per topic name. The objective is to find a bijective configuration that is conflict-free +\* (one topic name per subject-ID) and non-divergent (one subject-ID per topic name). +\* +\* There is no central coordinator. Nodes eventually find the consensus by periodically broadcasting gossip messages. +\* Each gossip message carries a single topic->subject allocation entry (nodes apply simple rules to choose which entry +\* to gossip next). Every node only keeps the entries that it needs (publishers/subscribers that the application needs +\* to use); no node is required to keep the full set of entries. Every node must be *eventually* able to receive and +\* send gossip messages for the protocol to make progress; transient inability to do so will not break the protocol +\* but may delay the consensus (resource-limited nodes may choose to drop some gossip messages to reduce processing +\* burden; this does not violate the core assumptions of the protocol as long as such nodes are able to *eventually* +\* receive each live allocation entry). +\* +\* The allocation protocol does not operate on topic names directly, substituting them with numerical hashes instead. +\* The application layer is expected to attach names to hashes but this is of no relevance to the core protocol. +\* From the standpoint of the core allocation protocol, "topic name" and "topic hash" can be used interchangeably. +\* +\* Each allocation entry contains three fields: the topic hash, the topic eviction count, and the topic age. +\* A pair of entries can be compared to each other to determine if the pair constitutes a collision +\* (different names, same subject) or a divergence (same name, different subjects); +\* other comparison outcomes are of no interest. +\* +\* The eviction count is used as a Lamport clock and hence implementations ensure that it cannot overflow. Since +\* evictions are static on a quiescent network, even relatively narrow representations (e.g., 32 bit) may suffice. +\* +\* The subject-ID assigned to a topic is defined as some function of its hash and eviction count. One possible way +\* to define the function is, assuming 64-bit unsigned arithmetics: +\* +\* subject_id = 8192 + ((hash + evictions**2) % 8380403) +\* +\* For the background on the subject of open addressing schemes and why the specific values were chosen this way, see: +\* - https://en.wikipedia.org/wiki/Quadratic_probing +\* - https://github.com/OpenCyphal-Garage/cy/issues/12 +\* +\* The eviction count of a topic is incremented whenever the topic is involved in a collision and loses arbitration. +\* Arbitration is defined as two functions, defined here as LeftWinsCollision and LeftWinsDivergence. +\* The topic age is incremented monotonically as some kind of real or logical clock, such that each node that holds +\* this topic is expected to increment its age at approximately the same rate (the protocol does not require the +\* increment rate to match accurately, but better rate matching results in faster worst-case convergence). +\* The age is used to determine which topic wins arbitration in the event of a collision or a divergence, +\* with the core idea being that older topics win arbitration to avoid disturbances to established networks. +\* +\* The topic-subject allocation table described above is a kind of CRDT (conflict-free replicated data type). +\* A CRDT only guarantees eventual convergence while the system is quiescent, which seemingly contradicts the +\* use of the age counter that increments continuously. To work around this, the protocol uses the binary +\* logarithm of topic ages for topic comparison instead of the actual age value, which naturally slows down +\* the increment rate over time, approaching quiescence. +\* +\* When a node creates a new topic (i.e., when the local application creates a new subscription or publication), +\* its age and eviction counter are set to zero; the initial subject-ID is obtained using the function above. +\* The topic is commissioned to use immediately without the need to wait for the network to converge, +\* despite the fact that it is known that the initial allocation may be conflicting or divergent. +\* To avoid the risk of data misinterpretation should a collision occur, the transport layer carries the +\* topic hash per transfer, and the transfer is rejected should the hash be incorrect. +\* Each local topic is reconfigured as necessary should the allocation protocol require so, transparently for +\* the application. +\* +\* Upon reception of a gossip, the age of the matching local topic, if there is one, is updated as: +\* local_age = max(local_age, 2**remote_age_log2). +\* +\* Pavel Kirienko , MIT license + +EXTENDS Utils +LOCAL INSTANCE Integers +LOCAL INSTANCE TLC +LOCAL INSTANCE Sequences +LOCAL INSTANCE FiniteSets + +\* Computes floor(log2(x)), with the special case floor(log2(0)) == -1 +RECURSIVE Log2Floor(_) +Log2Floor(x) == IF x > 1 THEN 1 + Log2Floor(x \div 2) ELSE x - 1 + +\* Special case: Pow2(-1) = 0 +Pow2(x) == IF x > 1 THEN 2^x ELSE x + 1 + +FloorToPow2(x) == Pow2(Log2Floor(x)) + +\*********************************************************************************************************************** +\* Subject-ID mapping function. The ring size is the total number of distinct subject-IDs. +\* TODO: Switch to quadratic probing: https://github.com/OpenCyphal-Garage/cy/issues/12 +SubjectIDRing == 10 +SubjectID(hash, evictions) == (hash + evictions) % SubjectIDRing + +\*********************************************************************************************************************** +\* COLLISION is when topics with different hashes land on the same subject-ID (see the subject_id mapping). +LeftWinsCollision(a, b) == IF Log2Floor(a.age) # Log2Floor(b.age) + THEN Log2Floor(a.age) > Log2Floor(b.age) + ELSE a.hash < b.hash + +\* DIVERGENCE is when topics with the same hash are found to have different eviction counters: +\* the eviction counters have to match, so we need to decide which one is the correct one. +LeftWinsDivergence(a, b) == \/ Log2Floor(a.age) > Log2Floor(b.age) + \/ (Log2Floor(a.age) = Log2Floor(b.age) /\ a.evictions > b.evictions) + +\*********************************************************************************************************************** +\* The result is Nothing if no such topic exists. +\* Topics are stored in sets because all operations on them are ordering-invariant, +\* which is a basic prerequisite for CRDT operations. +GetByHash(hash, topics) == Get(topics, LAMBDA x: x.hash = hash) +GetBySubjectID(subject_id, topics) == Get(topics, LAMBDA x: SubjectID(x.hash, x.evictions) = subject_id) + +\*********************************************************************************************************************** +\* A set of topics without the specified one. Same set if the specified topic is not a member. +RemoveTopic(hash, topics) == { t \in topics : t.hash # hash } + +\* Add or replace a topic into a set of topics. +\* Use this to mutate a topic state in a set. +ReplaceTopic(new, topics) == {new} \cup RemoveTopic(new.hash, topics) + +\* A set of topics extended with the specified one, and the existing topics possibly altered. +\* Uniqueness is guaranteed; if the topic is in the set already, it will be modified. +\* This can also be used to model state update of the local topic table. +RECURSIVE AllocateTopic(_, _) +AllocateTopic(t, topics) == + LET ts == RemoveTopic(t.hash, topics) + x == GetBySubjectID(SubjectID(t.hash, t.evictions), ts) + Evicted(z) == [hash |-> z.hash, evictions |-> 1 + z.evictions, age |-> z.age] + IN IF x = Nothing THEN {t} \cup ts + ELSE IF LeftWinsCollision(t, x) THEN AllocateTopic(Evicted(x), {t} \cup ts) + ELSE AllocateTopic(Evicted(t), ts) \* Retry with evictions+1 + +\*********************************************************************************************************************** +\* Constructs a conflict-free topic set. This is meant for constructing the initial node state. +\* Each hash can occur at most once. +RECURSIVE AllocateTopics(_, _) +AllocateTopics(new, topics) == + IF Cardinality(new) = 0 THEN topics + ELSE LET t == CHOOSE x \in new : TRUE + IN AllocateTopics(new \ {t}, AllocateTopic(t, topics)) + +\*********************************************************************************************************************** +\* Implementation of the divergence resolution rule with CRDT age merge. +LOCAL AcceptGossip_Divergence(remote, topics) == + LET hash == remote.hash + local == GetByHash(hash, topics) + \* The serialization protocol floors the remote age to pow2 before transmission. + new_age == Max({IF local # Nothing THEN local.age ELSE 0, FloorToPow2(remote.age)}) + IN + IF local # Nothing THEN + IF local.evictions # remote.evictions /\ LeftWinsDivergence(remote, local) + THEN AllocateTopic([hash|->hash, evictions|->remote.evictions, age|->new_age], topics) + ELSE {[hash|->hash, evictions|-> local.evictions, age|->new_age]} \cup RemoveTopic(hash, topics) + ELSE topics + +\* Implementation of the collision resolution rule. +LOCAL AcceptGossip_Collision(remote, topics) == + LET local == GetBySubjectID(SubjectID(remote.hash, remote.evictions), topics) + IN IF local # Nothing /\ LeftWinsCollision(remote, local) + THEN AllocateTopic([hash |-> local.hash, evictions |-> local.evictions + 1, age |-> local.age], topics) + ELSE topics + +\* An updated sequence of topics based on a received gossip message. +\* The sequence may be modified if the gossiped entry is observed to diverge or collide with a local entry, +\* and the affected local entry loses arbitration to the gossiped one. +\* The naive implementation is as simple as: +\* +\* AcceptGossip_Collision(remote, AcceptGossip_Divergence(remote, topics)) +\* +\* which is correct and converges eventually; however, it is not optimal because it may cause a local entry +\* to lose a collision arbitration to a divergent remote entry, which will cause unnecessary eviction counter +\* increments and thus delay convergence. To avoid this, we ignore collisions if the remote entry is seen to be +\* divergent from a local entry and the local entry wins the divergence arbitration. +AcceptGossip(remote, topics) == + IF GetByHash(remote.hash, topics) # Nothing \* If the topic exists locally + THEN AcceptGossip_Divergence(remote, topics) \* Divergence resolver will ensure collision-freedom as well + ELSE AcceptGossip_Collision(remote, topics) \* We don't know this topic, just ensure collision-freedom + +\*********************************************************************************************************************** +\* Divergent allocation detector operating on a function node_id -> {topic_0, topic_1, ..., topic_n} +\* A divergent allocation occurs when topic records with the same hash have distinct eviction counters. +\* If no divergences are found, the result is an empty set. +FindDivergent(node_topics) == + LET hashes == { t.hash : t \in UNION Range(node_topics) } + flat == { [hash |-> t.hash, evictions |-> t.evictions] : t \in UNION Range(node_topics) } + IN { h \in hashes : Cardinality({ s \in flat : s.hash = h }) > 1 } + +\*********************************************************************************************************************** +\* Allocation collision detector operating on a function node_id -> {topic_0, topic_1, ..., topic_n} +\* A collision occurs when topic records with distinct hashes have identical subject-ID. +\* If no collisions are found, the result is an empty set. +FindCollisions(node_topics) == + LET flat == { [hash |-> t.hash, subject_id |-> SubjectID(t.hash, t.evictions)] : t \in UNION Range(node_topics) } + subject_ids == { t.subject_id : t \in flat } + IN { s \in subject_ids : Cardinality({ t \in flat : t.subject_id = s }) > 1 } + +\*********************************************************************************************************************** +\* Whether the network has converged to a stable state. +\* Once a stable state is reached, the topics that are involved in it will no longer be altered as long as +\* no older topics are introduced to the network (such as in the event of network departitioning). +\* Appearance of young topics will not affect the existing entries by design. +Converged(node_topics) == + /\ FindDivergent(node_topics) = {} + /\ FindCollisions(node_topics) = {} + +======================================================================================================================== +\end{minted} + +\begin{minted}[fontsize=\scriptsize]{text} +------------------------------ MODULE Utils ------------------------------ +\* General-purpose utilities not specific to this problem. +\* There are a few utilities here copied over from https://github.com/tlaplus/CommunityModules (MIT license) + +LOCAL INSTANCE Integers +LOCAL INSTANCE Naturals +LOCAL INSTANCE TLC +LOCAL INSTANCE Sequences +LOCAL INSTANCE FiniteSets + +CONSTANT Nothing + +\* From https://github.com/tlaplus/CommunityModules (MIT license) +PrintVal(id, exp) == Print(<>, TRUE) +IsInjective(f) == \A a,b \in DOMAIN f : f[a] = f[b] => a = b +SeqToSet(s) == {s[i] : i \in DOMAIN s} +SetToSeq(S) == CHOOSE f \in [1..Cardinality(S) -> S] : IsInjective(f) +SetToSeqs(S) == LET D == 1..Cardinality(S) IN { f \in [D -> S] : \A i,j \in D : i # j => f[i] # f[j] } +Range(f) == { f[x] : x \in DOMAIN f } + +Min(S) == CHOOSE s \in S : \A t \in S : s <= t +Max(S) == CHOOSE s \in S : \A t \in S : s >= t + +\* The first element in a sequence where test(e) is TRUE; Nothing if the test is FALSE for all elements. +FirstMatch(haystack, test(_)) == + LET i == CHOOSE i \in 1..(Len(haystack)+1): (i > Len(haystack)) \/ test(haystack[i]) + IN IF i > Len(haystack) THEN Nothing ELSE haystack[i] + +\* The set element where test(e) is TRUE; Nothing if the test is false for all elements. +Get(haystack, test(_)) == LET matches == { x \in haystack : test(x) } + IN IF matches = {} THEN Nothing ELSE CHOOSE x \in matches : TRUE + +\* A sequence that contains zero needles. +SeqWithout(haystack, needle) == SelectSeq(haystack, LAMBDA x: x # needle) + +\* <<1, 2, 3>> ==> <<2, 3, 1>> +SeqRotLeft(seq) == IF Len(seq) > 0 THEN Tail(seq) \o <> ELSE seq + +\* Converts {<>, <>, ...} into a function [k |-> v]. Keys must be unique. +FunFromTupleSet(S) == [ + k \in {p[1] : p \in S} |-> CHOOSE v \in {r[2] : r \in S} : <> \in S +] +======================================================================================================================== +\end{minted} diff --git a/specification/appendices/eviction.tex b/specification/appendices/eviction.tex new file mode 100644 index 00000000..2441f466 --- /dev/null +++ b/specification/appendices/eviction.tex @@ -0,0 +1,88 @@ +\chapter{Eviction counter recovery}\label{sec:appendix_eviction_solver} + +Given a topic hash, a subject-ID, and the subject-ID modulus, the eviction +counter of a non-pinned topic can be reconstructed in constant time. +This works because the subject-ID mapping +(section~\ref{sec:session_subject_id_mapping}) +uses quadratic probing modulo a prime $M$ with $M \bmod 4 = 3$, which makes +the probing sequence invertible via the closed-form square root +$r = d^{(M+1)/4} \bmod M$ for quadratic residues $d$. + +The recovery is useful for diagnostic tools -- for example, to cross-check a +running network against the mapping formula, or to display a topic's +eviction history in a monitoring dashboard -- but it is not required for +protocol participation. Any implementation that participates in the +consensus protocol already has the eviction counter on hand. + +\begin{remark} + The solver below has been exhaustively brute-force verified for the + moduli $57203$, $131071$, and $8388607$. + Implementations that use it for diagnostics should treat a + \texttt{UINT32\_MAX} return value as \emph{the subject-ID was not + produced from the given hash by the mapping function}, which is a valid + outcome when the diagnostic tool is probing an unrelated subject. + The solver assumes that the eviction counter does not exceed + $\lfloor M / 2 \rfloor$; above that bound the probing sequence starts + to repeat and the recovery is no longer unique. +\end{remark} + +\begin{minted}[fontsize=\scriptsize]{cpp} +// Pavel Kirienko , MIT license + +// a**e mod m +static uint32_t pow_mod_u32(uint32_t a, uint32_t e, const uint32_t m) +{ + uint32_t r = 1 % m; + a %= m; + while (e) { + if (e & 1U) { + r = (uint32_t)(((uint64_t)r * (uint64_t)a) % m); + } + a = (uint32_t)(((uint64_t)a * (uint64_t)a) % m); + e >>= 1U; + } + return r; +} + +// Legendre symbol: a^((p-1)/2) mod p is 1 for residues, p-1 for non-residues, 0 for a==0. +static bool is_quadratic_residue_prime(const uint32_t a, const uint32_t p) +{ + return (a == 0) || (pow_mod_u32(a, (p - 1U) / 2U, p) == 1U); +} + +// Derives evictions from a non-pinned subject-ID. For pinned subject-IDs returns 0. +// Assumes subject_id_modulus is a prime with (subject_id_modulus % 4) == 3. +// Returns UINT32_MAX if the subject-ID was obtained using distinct parameters. +// If evictions > floor(modulus/2), the subject-ID sequence repeats, leading to non-unique solutions; +// the solver assumes that evictions stay below that bound. Complexity is O(1). +static uint32_t topic_evictions_from_subject_id(const uint64_t hash, + const uint32_t subject_id, + const uint32_t subject_id_modulus) +{ + const uint32_t p = subject_id_modulus; + assert((p > 3) && ((p & 3U) == 3U)); + if (subject_id <= 8191U) { + return 0; // Pinned subject; eviction count is not meaningful here. + } + const uint32_t base = subject_id - 8192U; + if (base >= p) { + return UINT32_MAX; + } + const uint32_t delta = (uint32_t)(((uint64_t)base + (uint64_t)p - (hash % p)) % p); + if (!is_quadratic_residue_prime(delta, p)) { + return UINT32_MAX; + } + // sqrt(delta) mod p (since p % 4 == 3): r = delta^((p+1)/4) mod p + const uint32_t r1 = (delta == 0U) ? 0U : pow_mod_u32(delta, (p + 1U) / 4U, p); + const uint32_t r2 = (r1 == 0U) ? 0U : (p - r1); + uint32_t best = UINT32_MAX; + const uint32_t roots[2] = { r1, r2 }; + for (unsigned i = 0; i < 2; i++) { + const uint64_t s = roots[i]; + if ((s <= (p / 2U)) && (s < best)) { + best = (uint32_t)s; + } + } + return best; +} +\end{minted} diff --git a/specification/appendices/proof.tex b/specification/appendices/proof.tex new file mode 100644 index 00000000..1e287fde --- /dev/null +++ b/specification/appendices/proof.tex @@ -0,0 +1,173 @@ +\chapter{Convergence proof for the allocation CRDT}\label{sec:appendix_proof} + +This appendix proves that the topic allocation CRDT described in +section~\ref{sec:session_crdt} converges to a collision-free, non-divergent +state in finite time, under the assumptions A1--A9 restated below. +The proof applies both to the formal operators in +appendix~\ref{sec:appendix_core_tla} (\texttt{AllocateTopic}, +\texttt{AcceptGossip}, \texttt{Converged}) and to the C implementation in +\texttt{cy.c} (\texttt{topic\_allocate}, \texttt{on\_gossip\_known\_topic}, +\texttt{on\_gossip\_unknown\_topic}) under the stated assumptions. + +\section{Model}\label{sec:appendix_proof_model} + +Let: + +\begin{itemize} + \item $H$ be the finite set of active topic hashes, with $|H| = N$. + \item $U$ be the finite set of auto-assigned subject-IDs, with $|U| = M$. + \item $S : H \times \mathbb{N} \to U$ be the deterministic subject-ID + mapping function. + \item A topic record be a triple $(h, e, a)$ where $h \in H$, + $e \in \mathbb{N}$ is the eviction counter, and $a \in \mathbb{N}$ is + the age. + \item $\ell(a) = \lfloor \log_2 a \rfloor$ with $\ell(0) = -1$ be the + log-age function. +\end{itemize} + +Two records are in \emph{collision} when they have different hashes but +identical subject-IDs. Two records are in \emph{divergence} when they share +a hash but carry different eviction counters. A state is \emph{converged} +when it contains no collisions and no divergences. + +\section{Assumptions}\label{sec:appendix_proof_assumptions} + +Some of the following are structural properties of the mapping function and +are satisfied by construction; others are deployment constraints that must +be upheld by compliant implementations and system integrators. + +\begin{description} + \item[A1. Common deterministic mapping.] All nodes use the same + deterministic $S(h, e)$ and the same mapping parameters -- in particular, + the same modulus. + + \item[A2. Hash identity.] Active topic names are hash-unique + (equivalently: equal hash means the same logical topic). + Hash collision probability is negligible and is ignored by the protocol. + + \item[A3. Bounded escape property.] There exists $K \in \mathbb{N}_{>0}$ + such that + \[ + \forall h \in H,\ \forall x \in \mathbb{N},\ \forall B \subseteq U,\ + |B| < K,\ \exists d \in \{0, \dots, K-1\} : + S(h,\ x + d) \notin B. + \] + In prose: from any current eviction value, a topic can escape any + blocked subject-ID set smaller than $K$ within at most $K$ probing + increments. + + \item[A4. Capacity bound.] $N \le K$. + + \item[A5. Gossip fairness.] Every live allocation entry is eventually + received and processed by every relevant node, infinitely often. + + \item[A6. Progress fairness.] Each node keeps executing the protocol + loop -- no permanent local halt while the node is considered active. + + \item[A7. Resource sufficiency.] Memory, stack, and scheduler resources + are sufficient for processing each local allocation cascade to + completion. + + \item[A8. Eviction counter safety.] Eviction counters do not overflow + in reachable executions. + + \item[A9. Stability window.] There exists a repair bound + $T_{\mathrm{repair}}$ and time windows of length greater than + $T_{\mathrm{repair}}$ during which the active topic set $H$ is + unchanged, the mapping parameters are unchanged, and pairwise + arbitration outcomes do not flip. Equivalently: arbitration-priority + flips driven by $\lfloor \log_2 a \rfloor$ increments occur much less + frequently than the gossip-plus-repair dynamics. +\end{description} + +\section{Theorem 1: Convergence in one stability window} + +Under assumptions A1--A9, any execution restricted to a single stability +window converges in finite time to a collision-free, non-divergent state. + +\subsection*{Lemma 1: Local allocation termination under bounded escape} + +Fix a node and a moving topic hash $h$ with current eviction $x$. Let $B$ +be the set of subject-IDs currently occupied by other local topics. +If $|B| < K$, then by A3 there exists $d < K$ such that +$S(h, x+d) \notin B$. +\texttt{AllocateTopic} increments the moving topic's eviction counter by +one each time it loses arbitration on its current candidate subject-ID. +Therefore within at most $d$ losses the topic reaches a subject-ID not in +$B$, and the insertion terminates. + +Local allocation terminates whenever $|B| < K$. By A4, $|B| \le N - 1 < K$, +so allocation always terminates. + +\subsection*{Lemma 2: Divergence repair converges for each hash} + +Fix a hash $h$. \texttt{AcceptGossip} on the known-hash path compares the +local and remote replicas using divergence arbitration and either keeps the +local evictions (merging the age) or adopts the remote evictions and +re-allocates. + +Under A5 and within a stable window (A9), every node repeatedly observes +the same finite set of contenders for $h$ and eventually agrees on the same +winning replica -- the same eviction value and a consistent merged +log-age. Hence all divergences for $h$ disappear in finite time. Applying +this reasoning independently to each $h \in H$, all divergences disappear +in finite time. + +\subsection*{Lemma 3: Collision repair converges under stable priorities} + +In a stability window (A9), collision arbitration induces a fixed strict +total order on the active hashes: +\[ + h_1 \succ h_2 \succ \dots \succ h_N. +\] +When $h_i$ collides with any $h_j$ where $j < i$, $h_i$ loses and is +evicted; $h_j$ is unchanged. Higher-priority topics are therefore never +displaced by lower-priority topics. + +Induction on the priority rank: +\begin{itemize} + \item \textbf{Base $i = 1$.} $h_1$ cannot be displaced by anyone, so + once allocated it remains fixed. + \item \textbf{Inductive step.} Assume $h_1, \dots, h_{i-1}$ are fixed + at a subject-ID set $B_i$ with $|B_i| = i - 1 < K$ (by A4). By A3, + from the current eviction value $x_i$ topic $h_i$ has some $d_i < K$ + with $S(h_i,\ x_i + d_i) \notin B_i$. Each loss against $B_i$ + increments the eviction counter by one, so after at most $d_i$ losses + $h_i$ reaches a subject-ID outside $B_i$ and cannot be displaced by + any lower-priority topic. +\end{itemize} + +Every $h_i$ stabilizes in finite time, so all collisions disappear. + +\subsection*{Conclusion} + +By Lemma~2 divergences vanish in finite time. By Lemma~3 collisions vanish +in finite time. The state therefore becomes converged -- no collisions, no +divergences -- in finite time. + +\section{Theorem 2: Convergence with occasional priority flips} + +Assume A1--A8 and that executions consist of stability windows as in A9, +each longer than $T_{\mathrm{repair}}$. Then the network converges within +each window; non-converged periods are transient and confined to window +transitions (including the effects of in-flight stale gossip). + +\subsection*{Proof sketch} + +Apply Theorem~1 independently to each stability window. Window transitions +may temporarily reintroduce conflicts, but by construction the next stable +window is long enough to repair them before the following flip +($\text{window length} > T_{\mathrm{repair}}$). + +\section{Instantiating A3/A4 for common probing laws} + +\paragraph{Linear probing} $S(h, e) = h + e \bmod M$ satisfies A3 with +$K = M$. This is the instantiation used in the TLA\textsuperscript{+} +model of appendix~\ref{sec:appendix_core_tla} for legibility. + +\paragraph{Quadratic probing} This is the instantiation used by the actual +Cyphal v1.1 implementation (section~\ref{sec:session_subject_id_mapping}). +The protocol relies on the deployment bound that the number of active +non-pinned topics does not exceed the collision-free probing capacity; +practically this is the half-ring bound $K \approx \lfloor M / 2 \rfloor + 1$ +and consequently $N \le \lfloor M / 2 \rfloor$. diff --git a/specification/appendices/rapidhash.tex b/specification/appendices/rapidhash.tex new file mode 100644 index 00000000..e7e7f425 --- /dev/null +++ b/specification/appendices/rapidhash.tex @@ -0,0 +1,88 @@ +\chapter{Rapidhash reference implementation}\label{sec:appendix_rapidhash} + +This appendix gives the normative Python transcription of the rapidhash V3 +topic hash function pinned by Cyphal v1.1 +(section~\ref{sec:session_rapidhash}). It is bit-identical to the reference +C implementation on every platform and may be used directly to reproduce +the test vectors in section~\ref{sec:session_rapidhash}. +The C implementation is distributed under the MIT license by Nicolas De +Carli and is derived from the wyhash algorithm by Wang Yi. +All arithmetic is modulo $2^{64}$; intermediate results must be masked to +64 bits where the Python semantics do not do so automatically. + +\begin{minted}[fontsize=\scriptsize]{python} +MASK64 = (1 << 64) - 1 +SECRET = [ 0x2d358dccaa6c78a5, 0x8bb84b93962eacc9, 0x4b33a62ed433d4a3, 0x4d5a2da51de1aa47, + 0xa0761d6478bd642f, 0xe7037ed1a0b428db, 0x90ed1765281c388c, 0xaaaaaaaaaaaaaaaa] + +def _read64(b, o): return int.from_bytes(b[o:o+8], "little") +def _read32(b, o): return int.from_bytes(b[o:o+4], "little") + +def _mum(a, b): + r = (a * b) & ((1 << 128) - 1) + return (r & MASK64, (r >> 64) & MASK64) + +def _mix(a, b): + lo, hi = _mum(a, b) + return lo ^ hi + +def rapidhash(key: bytes, seed: int = 0) -> int: + p, n = key, len(key) + seed ^= _mix(seed ^ SECRET[2], SECRET[1]) + seed &= MASK64 + a = b = 0 + i = n + if n <= 16: + if n >= 4: + seed ^= n + if n >= 8: + a = _read64(p, 0) + b = _read64(p, n - 8) + else: + a = _read32(p, 0) + b = _read32(p, n - 4) + elif n > 0: + a = (p[0] << 45) | p[n - 1] + b = p[n >> 1] + else: + off = 0 + if i > 112: + see1 = see2 = see3 = see4 = see5 = see6 = seed + while True: + seed = _mix(_read64(p, off) ^ SECRET[0], _read64(p, off + 8) ^ seed) + see1 = _mix(_read64(p, off + 16) ^ SECRET[1], _read64(p, off + 24) ^ see1) + see2 = _mix(_read64(p, off + 32) ^ SECRET[2], _read64(p, off + 40) ^ see2) + see3 = _mix(_read64(p, off + 48) ^ SECRET[3], _read64(p, off + 56) ^ see3) + see4 = _mix(_read64(p, off + 64) ^ SECRET[4], _read64(p, off + 72) ^ see4) + see5 = _mix(_read64(p, off + 80) ^ SECRET[5], _read64(p, off + 88) ^ see5) + see6 = _mix(_read64(p, off + 96) ^ SECRET[6], _read64(p, off +104) ^ see6) + off += 112 + i -= 112 + if i <= 112: + break + seed ^= see1 + see2 ^= see3 + see4 ^= see5 + seed ^= see6 + see2 ^= see4 + seed ^= see2 + seed &= MASK64 + if i > 16: + seed = _mix(_read64(p, off) ^ SECRET[2], _read64(p, off + 8) ^ seed) + if i > 32: + seed = _mix(_read64(p, off + 16) ^ SECRET[2], _read64(p, off + 24) ^ seed) + if i > 48: + seed = _mix(_read64(p, off + 32) ^ SECRET[1], _read64(p, off + 40) ^ seed) + if i > 64: + seed = _mix(_read64(p, off + 48) ^ SECRET[1], _read64(p, off + 56) ^ seed) + if i > 80: + seed = _mix(_read64(p, off + 64) ^ SECRET[2], _read64(p, off + 72) ^ seed) + if i > 96: + seed = _mix(_read64(p, off + 80) ^ SECRET[1], _read64(p, off + 88) ^ seed) + a = _read64(p, off + i - 16) ^ i + b = _read64(p, off + i - 8) + a ^= SECRET[1] + b ^= seed + lo, hi = _mum(a, b) + return _mix(lo ^ SECRET[7], hi ^ SECRET[1] ^ i) +\end{minted} diff --git a/specification/introduction/introduction.tex b/specification/introduction/introduction.tex index af33bddc..ff09e7f4 100644 --- a/specification/introduction/introduction.tex +++ b/specification/introduction/introduction.tex @@ -53,52 +53,37 @@ \section{Document conventions} A byte is a group of eight (8) bits. -Textual patterns are specified using the standard -POSIX Extended Regular Expression (ERE) syntax; -the character set is ASCII and patterns are case sensitive, unless explicitly specified otherwise. - -Numbers are represented in base-10 by default. -If a different base is used, it is specified after the number in the subscript\footnote{% - E.g., $\text{BADC0FFEE}_{16} = 50159747054$, $10101_2 = 21$. -}. - \section{Design principles} \begin{description} + \item[Simplicity] --- If anything can be removed, it probably should be. Cyphal targets a wide variety of systems, + from high-performance computers to extremely resource-constrained microcontrollers. + It will be cheap to implement, verify, and support in terms of engineering hours and computing power, + and advanced features can be implemented incrementally as needed. + \item[Democratic network] --- There will be no master node. - All nodes in the network will have the same communication rights; there should be no single point of failure. + All nodes in the network will have the same communication rights and responsibilities; + there will be no single point of failure. + + \item[Resource efficiency and determinism] --- Cyphal will add a very low overhead to the underlying + transport protocol, which will ensure high throughput and low latency, rendering the protocol well-suited + for hard real-time applications. + + \item[High-level communication abstractions] --- The protocol will support conventional publish/subscribe + and remote procedure call communication semantics that engineers are accustomed to. + The protocol is agnostic of the data serialization/representation format used. \item[Facilitation of functional safety] --- A system designer relying on Cyphal will have the necessary guarantees and tools at their disposal to analyze the system and ensure its correct behavior. - \item[High-level communication abstractions] --- The protocol will support publish/subscribe and remote procedure - call communication semantics. The protocol is agnostic of the data serialization/representation format used. - \item[Facilitation of cross-vendor interoperability] --- Cyphal will be a common foundation that different vendors can build upon to maximize interoperability of their equipment. - \item[Atomic data abstractions] --- Nodes shall be provided with a simple way of exchanging large - data structures that exceed the capacity of a single transport frame\footnote{% - A \emph{transport frame} is an atomic transmission unit defined by the underlying transport protocol. - For example, a UDP datagram. - }. - Cyphal should perform automatic data decomposition and reassembly at the protocol level, - hiding the related complexity from the application. - - \item[High throughput, low latency, determinism] --- Cyphal will add a very low overhead to the underlying - transport protocol, which will ensure high throughput and low latency, rendering the protocol well-suited - for hard real-time applications. - \item[Support for redundant interfaces and redundant nodes] --- Cyphal shall be suitable for use in applications that require modular redundancy. - \item[Simple logic, low computational requirements] --- Cyphal targets a wide variety of embedded systems, - from high-performance on-board computers to extremely resource-constrained microcontrollers. - It will be inexpensive to support in terms of computing power and engineering hours, - and advanced features can be implemented incrementally as needed. - - \item[Support for various transport protocols] --- Cyphal will be usable with different transports. - The standard shall be capable of accommodating other transport protocols in the future. + \item[Support for various transport protocols] --- Cyphal will be usable with different transports, + presenting a clear transport layer contract that is inexpensive to satisfy. \item[API-agnostic standard] --- Unlike some other networking standards, Cyphal will not attempt to describe the application program interface (API). Any details that do not affect the behavior of an implementation @@ -110,8 +95,11 @@ \section{Design principles} \section{Capabilities} -The maximum number of nodes per logical network is dependent on the transport protocol in use, -but it is guaranteed to be not less than 128. +The protocol aspires to scale well from tiny deployments involving a few nodes and topics +up to very large networks hosting multiple hundreds of nodes and topics. +However, specific transport layer implementations may introduce their own scalability constraints\footnote{% + For example, Cyphal/CAN may not scale above a hundred nodes due to physical and logical limitations. +}. Depending on the transport protocol, Cyphal supports at least eight distinct communication priority levels (section~\ref{sec:transport_transfer_priority}). @@ -128,8 +116,6 @@ \section{Capabilities} Additional standards specifying physical-layer requirements, including connectors, may be required to utilize this standard in a vehicle system. -The capabilities of the protocol will never be reduced within a major version of the specification but may be expanded. - \section{Management policy} The Cyphal maintainers are tasked with maintaining and advancing this specification diff --git a/specification/session/crdt.tex b/specification/session/crdt.tex new file mode 100644 index 00000000..8244e5e9 --- /dev/null +++ b/specification/session/crdt.tex @@ -0,0 +1,239 @@ +\section{Topic allocation CRDT}\label{sec:session_crdt} + +The session layer maintains the topic-to-subject-ID mapping through a conflict-free +replicated data type (CRDT) shared among all nodes by periodic gossip. +Under the assumptions listed below, every node's local copy of the mapping converges +to the same collision-free, non-divergent configuration within a bounded time after +the last state-changing event. + +This section is normative for the arbitration semantics of the mapping. +The primary source of truth is the TLA$^+$ model in \texttt{model/tlaplus/Core.tla} +of the Cy reference implementation; the operators and theorems described here are +a faithful transcription of that model, and the C reference implementation has been +shown to match it under the stated assumptions. + +\subsection{Topic record} + +Each topic the node is aware of is represented by a triple +\[ + t = (\mathit{hash},\ \mathit{evictions},\ \mathit{age}) +\] +where: + +\begin{description} + \item[\textit{hash}] is the 64-bit topic hash defined in + section~\ref{sec:session_rapidhash}. + \item[\textit{evictions}] is a 32-bit unsigned integer incremented each time the + topic loses arbitration on its current candidate subject-ID. It serves as a + Lamport clock. The reserved range at the top of the counter encodes pinned + topics as described in section~\ref{sec:session_subject_id_mapping}. + \item[\textit{age}] is a local real or logical clock counting roughly seconds + since the topic was first seen on the network. It is not transmitted directly + on the wire; gossip instead carries its base-2 logarithm (see below). +\end{description} + +The subject-ID of a topic is the pure function +$\mathit{subject\_id}(\mathit{hash}, \mathit{evictions})$ defined in +section~\ref{sec:session_subject_id_mapping}. + +\subsection{Log-age} + +CRDT operators use the quantity +\[ + \mathit{lage}(t) = \lfloor \log_2(\mathit{age\_seconds}(t)) \rfloor, + \qquad \mathit{lage}(t) = -1 \text{ when } \mathit{age\_seconds}(t) = 0 +\] +in place of the raw age. +Taking the floor of the logarithm slows the arbitration-relevant clock as topics +grow older, so that the CRDT is stable on long timescales even though the underlying +age counter never stops advancing. The encoded range is $[-1,\ 35]$, where +$\mathit{lage} = 35$ corresponds to approximately $2^{35}$ seconds, or over a +thousand years. + +\begin{remark} + The naive approach of using the raw age in arbitration would make any two + quiescent nodes disagree on priorities every time a clock edge crosses an + integer second boundary, which breaks the CRDT's eventual consistency. + Reducing the comparison quantity to $\lfloor \log_2(\mathit{age}) \rfloor$ + makes the set of points where priority can change sparse on quiescent states, + satisfying the stability assumption A9 below. +\end{remark} + +Gossip messages carry a topic's $\mathit{lage}$ value rather than its age. +On reception, a node merges the incoming $\mathit{lage}$ into its local age by +adjusting the local age field so that +\[ + \mathit{local\_age} \leftarrow \max\left(\mathit{local\_age},\ 2^{\mathit{remote\_lage}}\right). +\] +This is a monotonic, idempotent join: repeated application is a no-op, and the +result is independent of the order in which gossip messages are processed. + +\subsection{Arbitration} + +Two topics are in \emph{collision} when they have distinct hashes but map to the +same subject-ID. Two records with the same hash but different eviction counters +are in \emph{divergence}; these are different opinions about the same logical topic +that must be reconciled. + +\begin{description} + \item[Collision arbitration] Older $\mathit{lage}$ wins. On a tie, the record + with the numerically lower hash wins. + \item[Divergence arbitration] Older $\mathit{lage}$ wins. On a tie, the record + with the higher eviction count wins, because a higher counter means the record + has observed more prior conflicts and is therefore more up to date. +\end{description} + +These two predicates are defined as follows, verbatim from +\texttt{model/tlaplus/Core.tla}. \texttt{Log2Floor(x)} is +$\lfloor \log_2 x \rfloor$ with the special case $\mathrm{Log2Floor}(0) = -1$; +\texttt{\#} is the TLA\textsuperscript{+} inequality operator, $\mathtt{=}$ is +equality, $\mathtt{/\char`\\}$ is logical AND, and $\mathtt{\char`\\/}$ is +logical OR. + +\begin{minted}{text} +LeftWinsCollision(a, b) == IF Log2Floor(a.age) # Log2Floor(b.age) + THEN Log2Floor(a.age) > Log2Floor(b.age) + ELSE a.hash < b.hash + +LeftWinsDivergence(a, b) == \/ Log2Floor(a.age) > Log2Floor(b.age) + \/ (Log2Floor(a.age) = Log2Floor(b.age) /\ a.evictions > b.evictions) +\end{minted} + +\subsection{AllocateTopic operator} + +\texttt{AllocateTopic} inserts a topic into the node's local topic set, resolving +any resulting collision by cascading evictions through the hash space. +The operator is recursive; termination follows from the bounded-escape property in +assumption A3 below. The TLA\textsuperscript{+} definition below is verbatim +from \texttt{model/tlaplus/Core.tla} and is reproduced in full in +appendix~\ref{sec:appendix_core_tla}. + +\begin{minted}{text} +RECURSIVE AllocateTopic(_, _) +AllocateTopic(t, topics) == + LET ts == RemoveTopic(t.hash, topics) + x == GetBySubjectID(SubjectID(t.hash, t.evictions), ts) + Evicted(z) == [hash |-> z.hash, evictions |-> 1 + z.evictions, age |-> z.age] + IN IF x = Nothing THEN {t} \cup ts + ELSE IF LeftWinsCollision(t, x) THEN AllocateTopic(Evicted(x), {t} \cup ts) + ELSE AllocateTopic(Evicted(t), ts) +\end{minted} + +The operator is invoked whenever a new local topic is created, whenever a gossip +forces the local record for a topic to adopt new eviction values, and whenever +a cascade from another topic displaces the current record. + +\subsection{AcceptGossip operator} + +\texttt{AcceptGossip} applies a received gossip message to the node's local topic +set. The operator distinguishes two cases: the gossiped topic is known locally +(its hash matches a local record) or it is not. The TLA\textsuperscript{+} +definitions below are verbatim from \texttt{model/tlaplus/Core.tla}. +\texttt{Nothing} denotes the absence of a matching record; +\texttt{FloorToPow2(x)} returns $2^{\lfloor \log_2 x \rfloor}$ with the +special case $\mathrm{FloorToPow2}(0) = 0$. + +\begin{minted}{text} +AcceptGossip(remote, topics) == + IF GetByHash(remote.hash, topics) # Nothing \* If the topic exists locally + THEN AcceptGossip_Divergence(remote, topics) \* Divergence resolver ensures collision-freedom + ELSE AcceptGossip_Collision(remote, topics) \* We don't know this topic, just ensure collision-freedom + +AcceptGossip_Divergence(remote, topics) == + LET hash == remote.hash + local == GetByHash(hash, topics) + \* The serialization protocol floors the remote age to pow2 before transmission. + new_age == Max({IF local # Nothing THEN local.age ELSE 0, FloorToPow2(remote.age)}) + IN + IF local # Nothing THEN + IF local.evictions # remote.evictions /\ LeftWinsDivergence(remote, local) + THEN AllocateTopic([hash|->hash, evictions|->remote.evictions, age|->new_age], topics) + ELSE {[hash|->hash, evictions|-> local.evictions, age|->new_age]} \cup RemoveTopic(hash, topics) + ELSE topics + +AcceptGossip_Collision(remote, topics) == + LET local == GetBySubjectID(SubjectID(remote.hash, remote.evictions), topics) + IN IF local # Nothing /\ LeftWinsCollision(remote, local) + THEN AllocateTopic([hash |-> local.hash, evictions |-> local.evictions + 1, age |-> local.age], topics) + ELSE topics +\end{minted} + +\begin{remark}[breakable] + A naive implementation could always run the collision resolver after the + divergence resolver, i.e.\ compute + \texttt{AcceptGossip\_Collision(remote, AcceptGossip\_Divergence(remote, topics))}. + This is correct but suboptimal: the local record may already win the + divergence arbitration against the remote, in which case the remote record + should be ignored entirely; running the collision resolver afterwards can make + a local record lose a collision to a stale remote record and trigger + unnecessary eviction-counter increments. The divergence-first rule above + avoids that outcome -- when the local record wins the divergence arbitration, + the collision pass is skipped because divergence resolution implicitly keeps + the local mapping collision-free. +\end{remark} + +\subsection{Pinned topics} + +Pinned topics are ordinary CRDT records with their eviction counter pre-set to +$\text{UINT32\_MAX} - \text{pinned\_subject\_id}$, as defined in +section~\ref{sec:session_subject_id_mapping}. +They participate in arbitration on the same terms as non-pinned topics: an older +non-pinned topic can displace a younger pinned newcomer, and an older pinned topic +can displace a younger conflicting pinned topic. +Pinning is therefore a \emph{request}, not a guarantee. + +\subsection{Assumptions} + +The convergence guarantee of the allocation protocol rests on the following +assumptions. Some are structural properties of the mapping function and are +satisfied by construction; others are deployment constraints that compliant +implementations and system integrators are required to uphold. The numbering is +consistent with the accompanying proof in \texttt{model/proof.md}. + +\begin{description} + \item[A1. Common deterministic mapping] All nodes use the same subject-ID + mapping function and the same parameters -- in particular, the same modulus. + \item[A2. Hash identity] Active topic names are hash-unique. The probability of + a 64-bit hash collision is negligible and is ignored by the protocol. + \item[A3. Bounded escape] For any hash, any eviction value, and any blocked + subject-ID set smaller than the capacity bound $K$, there exists a small + increment within $K$ steps that places the topic at a subject-ID outside the + blocked set. For quadratic probing on a prime modulus $M$ with $M \bmod 4 = 3$ + this holds with $K \approx \lfloor M / 2 \rfloor + 1$. + \item[A4. Capacity bound] The number of active non-pinned topics $N$ satisfies + $N \le K$. + \item[A5. Gossip fairness] Every live topic record is eventually received and + processed by every relevant node infinitely often. + \item[A6. Progress fairness] Each node keeps executing the protocol; resource + exhaustion or scheduling starvation does not halt a live node. + \item[A7. Resource sufficiency] Each node has enough memory and CPU to process + and complete the local cascade triggered by any single allocation step. + \item[A8. Eviction counter safety] Eviction counters do not overflow in + reachable executions. + \item[A9. Stability window] Stable windows longer than the worst-case repair + time $T_{\mathrm{repair}}$ exist, during which the active topic set and + pairwise arbitration priorities do not flip. +\end{description} + +\subsection{Convergence} + +Under assumptions A1--A9, any execution restricted to a single stability window +converges in finite time to a state that is free of collisions and divergences. +The argument is given in full in \texttt{model/proof.md}; the key idea is that +higher-priority topics are never displaced by lower-priority topics, so the +arbitration induces a fixed total order during the window and each topic +stabilizes by induction on its priority rank. + +When the stability window ends -- for example, because a long-lived topic's +$\mathit{lage}$ steps up and flips a tie -- some transient churn may briefly +reappear. Assumption A9 guarantees that the next stable window is long enough to +repair the churn before the next flip, and the overall execution remains eventually +consistent. + +\subsection{Capacity} + +For a quadratic probing sequence on a prime modulus $M$ with $M \bmod 4 = 3$ the +collision-free probing capacity is approximately $\lfloor M / 2 \rfloor$ +(assumption A3). The number of active non-pinned topics should not exceed this +bound; above it, the arbitration behaviour remains well-defined but convergence +within a single stability window is no longer guaranteed. diff --git a/specification/session/gossip.tex b/specification/session/gossip.tex new file mode 100644 index 00000000..ed838033 --- /dev/null +++ b/specification/session/gossip.tex @@ -0,0 +1,152 @@ +\section{Gossip dissemination}\label{sec:session_gossip} + +Nodes keep their local CRDT states (section~\ref{sec:session_crdt}) aligned with +each other by exchanging \emph{gossip} messages. +A gossip carries a single topic record and is sent over one of four carriers +described below. + +\subsection{Gossip carriers} + +\begin{description} + \item[Broadcast gossip] Multicast on the broadcast subject + (section~\ref{sec:session_subject_id_mapping}). Every node is expected to be + attached to this subject, so a broadcast gossip is eventually visible to every + participant; broadcast traffic is the primary mechanism for escaping + partitioned or newly-joined subsets. The broadcast subject is also used for + scout messages (section~\ref{sec:session_scout}). + + \item[Sharded gossip] Multicast on a \emph{gossip shard} subject-ID. + A topic is assigned to the shard + \[ + \mathit{shard\_index} = \mathit{hash} \bmod \mathit{gossip\_shard\_count} + \] + where $\mathit{gossip\_shard\_count}$ is the number of subject-IDs between + the auto-allocated range and the broadcast subject (see + section~\ref{sec:session_subject_id_mapping}). A node joins every gossip shard + that any of its local topics maps into, and no others. + + \item[Inline gossip] A gossip state carried in the header of a + \texttt{msg\_be} or \texttt{msg\_rel} application message + (section~\ref{sec:session_headers}). Inline gossip is observed by every + subscriber of the topic and converges the CRDT state for that topic without + spending a separate message on it. Handling of inline gossip is recommended + but not required for correctness. + + \item[Unicast gossip] A single gossip sent to a specific remote node, + used for scout responses and for targeted repair of a known divergence. +\end{description} + +\subsection{Schedules} + +A node that holds at least one explicit topic emits gossip on three schedules: +periodic, urgent, and inline. Implicit topics +(section~\ref{sec:session_patterns}) do not emit gossip. + +\paragraph{Periodic gossip} Every explicit topic emits its current state at a +randomized interval around a nominal period (recommended default: $5$ seconds, +randomized by $\pm$ one eighth). +Every $N$-th emission goes on the broadcast subject rather than the topic's +gossip shard, so the broadcast lane sees a slow but guaranteed sweep of all +topics on the network (recommended $N = 10$). +Between broadcast sweeps the topic's shard is used, which scales better on large +networks because each node only joins a fraction of the shards. + +\paragraph{Urgent gossip} Whenever a topic's local subject-ID occupancy changes +-- a new topic is created, an incoming gossip forces a re-allocation, or a +cascade evicts a record -- a broadcast gossip is scheduled with a small random +delay $W_u$ (section~\ref{sec:session_gossip_delays}). If an equivalent or +dominating gossip from another node arrives before the timer fires, the pending +urgent gossip is suppressed. + +\paragraph{Inline gossip} A node that publishes a \texttt{msg\_be} or +\texttt{msg\_rel} message piggybacks the current gossip state of the message's +topic in the header. A receiver that elects to process inline gossip applies it +to its local CRDT in the usual way; this is the fastest possible CRDT update on +active topics because it consumes no additional network capacity. + +\subsection{Suppression rules}\label{sec:session_gossip_suppression} + +Gossip schedules are designed so that the network-wide rate of gossip traffic on +any given topic stays roughly constant regardless of the number of nodes holding +it. To that end, nodes apply two suppression rules when incoming gossip indicates +that some other node is already covering the same information: + +\begin{itemize} + \item \textbf{Cancel a pending urgent gossip} when an incoming gossip arrives + on the broadcast subject whose arbitration priority dominates the state the + urgent gossip was intended to publish. The incoming broadcast already + carries the information forward, so repeating it would waste network + capacity. + + \item \textbf{Slow down periodic gossip} when an incoming non-inline gossip + matches the local $\mathit{lage}$ and eviction counter for the same topic. + Another node is already covering the topic at the current cadence; the + local node can afford to reschedule its next emission with a longer + interval. +\end{itemize} + +\begin{remark} + Inline gossip is deliberately excluded from the periodic slowdown rule. + Because inline gossip is piggybacked on application messages, its rate is + determined by the application's publication rate, not by the gossip + scheduler; allowing it to suppress periodic gossip would couple the CRDT + repair rate to the application traffic pattern. +\end{remark} + +\subsection{Randomized delay windows}\label{sec:session_gossip_delays} + +The urgent window $W_u$ and the startup window $W_s$ are both tunable. The +recommended orders of magnitude below are derived from the analytic model in +\texttt{model/gossip\_propagation.ipynb} of the reference implementation. + +Let $\delta_{\max}$ be the worst-case propagation and processing delay for a +gossip between two nodes on the network. + +\begin{description} + \item[Urgent window $W_u$] Controls the amount of jitter inserted before an + urgent gossip. If $m_u$ nodes independently schedule an urgent repair for + the same event, the expected number of redundant urgent emissions is + approximately $(m_u - 1) \cdot \delta_{\max} / W_u$. + In typical deployments $m_u$ is small because a given collision or + divergence is observed by only a few nodes; setting + $W_u \approx \delta_{\max}$ is usually sufficient. + + \item[Startup window $W_s$] Controls the amount of jitter inserted when a + node comes online and schedules its first round of gossip. + Wider windows reduce the probability of simultaneous emissions from many + startups but saturate quickly; recommended range is + $[\delta_{\max},\ 10 \cdot \delta_{\max}]$. +\end{description} + +On a typical local network with $\delta_{\max}$ on the order of tens of +milliseconds, choosing $W_u$ on the order of tens of milliseconds and $W_s$ on +the order of tens to a few hundred milliseconds is sufficient. + +\subsection{Implicit topic retirement} + +An \emph{implicit} topic is one that a node holds only because a local pattern +subscription (section~\ref{sec:session_patterns}) matched a remote gossip. An +implicit topic does not emit gossip, and it is automatically retired once it has +observed no messages and no gossip activity for the implicit-topic retention +window. The recommended retention window is ten minutes; it should be much +longer than the worst-case periodic gossip interval so that transient silence +on an active topic does not cause premature retirement. + +\section{Scout}\label{sec:session_scout} + +The scout mechanism lets a node pull in topics that match a pattern without +waiting for periodic gossip rotation. A scout is a session-layer message whose +payload is a pattern string (section~\ref{sec:session_names}). + +A scout message is broadcast on the broadcast subject using the +\texttt{scout} header (section~\ref{sec:session_headers}). +Every node that receives the message checks its local topics against the +pattern; for each match, the receiver replies with a \texttt{gossip} header +describing the matched topic. Replies are typically unicast back to the +originating node, using the unicast context provided by the transport with the +incoming scout, but broadcasting is also permitted if that is easier for the +responder. + +A scout with an empty pattern is reserved for a future extension -- a request +for a network-wide health summary -- and shall be discarded by current +implementations. diff --git a/specification/session/messages.tex b/specification/session/messages.tex new file mode 100644 index 00000000..51dca629 --- /dev/null +++ b/specification/session/messages.tex @@ -0,0 +1,294 @@ +\section{Session headers and framing}\label{sec:session_headers} + +Every session-layer message begins with a fixed-size header of 24 bytes, +followed by a type-specific payload. Multi-byte integer fields are encoded in +little-endian byte order and are positioned to favour natural alignment on +four- and eight-byte boundaries. The wire layouts below are given in DSDL +notation. +Fields marked \emph{incompatibility} shall be transmitted as +zero, and any non-zero value on reception indicates a protocol revision the +receiver does not understand: the receiver shall discard the message. + +\subsection{Header types} + +The first byte of every header is the header type. Types are allocated as +follows; values 10 through 255 are reserved. + +{\footnotesize +\begin{CyphalSimpleTable}[wide]{Session-layer header types}{|r l l X|} + Value & Name & Direction & Purpose \\ + 0 & \texttt{msg\_be} & publisher $\to$ subscriber(s), multicast or unicast & Best-effort message publication. \\ + 1 & \texttt{msg\_rel} & publisher $\to$ subscriber(s), multicast or unicast & Reliable message publication, acknowledged. \\ + 2 & \texttt{msg\_ack} & subscriber $\to$ publisher, unicast & Positive acknowledgement of a reliable message. \\ + 3 & \texttt{msg\_nack} & subscriber $\to$ publisher, unicast & Negative acknowledgement; no matching subscriber.\\ + 4 & \texttt{rsp\_be} & responder $\to$ publisher, unicast & Best-effort response to a received message. \\ + 5 & \texttt{rsp\_rel} & responder $\to$ publisher, unicast & Reliable response, acknowledged. \\ + 6 & \texttt{rsp\_ack} & publisher $\to$ responder, unicast & Positive acknowledgement of a reliable response.\\ + 7 & \texttt{rsp\_nack} & publisher $\to$ responder, unicast & Negative acknowledgement; application no longer listening. \\ + 8 & \texttt{gossip} & any, multicast or unicast & Topic allocation CRDT gossip. \\ + 9 & \texttt{scout} & any, broadcast & Pattern service-discovery query. \\ +\end{CyphalSimpleTable} +} + +\subsection{Message headers}\label{sec:session_header_msg} + +The \texttt{msg\_be} and \texttt{msg\_rel} headers share the same layout. Each +message carries not only the application payload but also the sender's +current CRDT state for the topic, so that the message doubles as an inline +gossip for its own topic (section~\ref{sec:session_gossip}). + +\begin{minted}{python} +uint8 type # 0 for msg_be, 1 for msg_rel. +void8 +uint8 incompatibility +int8 topic_log_age # floor(log2(topic_age_seconds)) if topic_age_seconds > 0, else -1. +uint32 evictions # For pinned topics, 0xFFFFFFFF - subject_id. +uint64 topic_hash # Subject allocation collision detection and immediate consensus updates. +uint64 tag # Random-initialized, wraps around. Used for acknowledgement + # correlation and, optionally, for ordering recovery per remote. +# Payload follows. +\end{minted} + +\subsection{Message acknowledgement headers}\label{sec:session_header_msg_ack} + +The \texttt{msg\_ack} and \texttt{msg\_nack} headers are sent in response to a +\texttt{msg\_rel}; they carry no payload. The priority level of the +acknowledgement shall match the priority level of the original message. + +\begin{minted}{python} +uint8 type # 2 for msg_ack, 3 for msg_nack. +void24 +uint32 incompatibility +uint64 topic_hash # From the acknowledged message. +uint64 tag # From the acknowledged message. +\end{minted} + +\subsection{Response headers}\label{sec:session_header_rsp} + +The \texttt{rsp\_be} and \texttt{rsp\_rel} headers carry a response to a +message previously received by the responder. The \texttt{rsp\_ack} and +\texttt{rsp\_nack} headers acknowledge a reliable response and carry no +payload. All four share the same layout. + +\begin{minted}{python} +uint8 type # 4 for rsp_be, 5 for rsp_rel, 6 for rsp_ack, 7 for rsp_nack. +uint8 tag # Chosen by the responder arbitrarily for acknowledgement correlation. +uint48 seqno # Starts at zero for the first response to a given message and + # increments by one per successive response, enabling streaming. +uint64 topic_hash # Topic hash of the published message this response pertains to. +uint64 message_tag # Tag of the published message this response pertains to. +# Payload follows, except for rsp_ack and rsp_nack. +\end{minted} + +\subsection{Gossip header}\label{sec:session_header_gossip} + +The \texttt{gossip} header carries one CRDT record +(section~\ref{sec:session_crdt}) together with the topic's normalized +resolved name. The message has no additional payload. + +\begin{minted}{python} +uint8 type # 8. +void16 +int8 topic_log_age # floor(log2(topic_age_seconds)) if topic_age_seconds > 0, else -1. +uint32 incompatibility +uint64 topic_hash +uint32 topic_evictions # For pinned topics, 0xFFFFFFFF - subject_id. +void24 # Reserved, may extend the eviction counter in a future revision. +utf8[<=200] topic_name # Length-prefixed (one byte prefix); normalized resolved name. +# Total wire size is 24 bytes plus the length of the topic name. +\end{minted} + +\subsection{Scout header}\label{sec:session_header_scout} + +The \texttt{scout} header carries a pattern string +(section~\ref{sec:session_names}). The receiver matches the pattern against +its local topics and, for each match, replies with a \texttt{gossip} message +(section~\ref{sec:session_scout}). + +\begin{minted}{python} +uint8 type # 9. +void24 +uint32 incompatibility +uint64 incompatibility1 +void56 +utf8[<=200] pattern # Length-prefixed (one byte prefix); pattern applied to normalized names. +# Total wire size is 24 bytes plus the length of the pattern. +\end{minted} + +\subsection{Message tags}\label{sec:session_tags} + +The 64-bit \texttt{tag} field carried in \texttt{msg\_be} and \texttt{msg\_rel} +is a per-publisher counter that is randomly initialized when a publisher is +created and is incremented by one for each message published on the topic. +The random initialization ensures that a node rebooting mid-operation does not +accidentally reuse tags that were emitted before the reboot; the counter is +permitted to wrap around. + +The PRNG used for tag initialization shall have distinct state across quick +reboots. Nodes without a true random-number generator should seed a pseudorandom +generator by hashing the node's unique identifier with whatever entropy the +platform makes available -- for example, analog-to-digital converter noise, +real-time clock drift, or values stored in battery-backed memory. + +Tags are not used for duplicate suppression on best-effort messages, which rely +on the transport layer's deduplication instead. They are used for duplicate +suppression and acknowledgement correlation on reliable messages +(section~\ref{sec:session_reliable}). + +\section{Best-effort message exchange}\label{sec:session_best_effort} + +A best-effort publication is a single \texttt{msg\_be} transfer. The publisher +performs the following steps atomically: + +\begin{enumerate} + \item Assign the next tag from the publisher's sequence. + \item Serialize a \texttt{msg\_be} header whose inline CRDT fields carry + the topic's current $\mathit{lage}$, eviction counter, and hash. + \item Append the application payload. + \item Submit the message to the transport for delivery on the topic's + current subject-ID. +\end{enumerate} + +The publisher retains no state after the submission returns. + +On reception of a \texttt{msg\_be} transfer, a subscriber shall: + +\begin{enumerate} + \item Validate that the header's \texttt{(topic\_hash, evictions)} pair + maps (via section~\ref{sec:session_subject_id_mapping}) to the subject-ID + on which the message arrived. A mismatch indicates a malfunctioning + sender; the message shall be discarded. + \item Apply the inline gossip to the local CRDT state as described in + section~\ref{sec:session_crdt}. + \item Deliver the application payload to every matching local subscriber, + if any. +\end{enumerate} + +Best-effort messages do not require receive-side deduplication at the session +layer, because the transport already deduplicates transfers and the publisher +never retransmits. + +\section{Reliable message delivery}\label{sec:session_reliable} + +A reliable publication is carried by a \texttt{msg\_rel} transfer and is +acknowledged by every intended recipient with a \texttt{msg\_ack}. The +publisher retransmits until every known recipient has acknowledged or the +deadline expires. + +\subsection{Associations} + +A publisher tracks its active recipients in an \emph{association set}. Each +association records: + +\begin{description} + \item[\textit{remote\_id}] The network-wide identifier of the remote node. + \item[\textit{unicast\_ctx}] The transport-specific metadata needed to send + a unicast message back to the remote, as supplied by the transport layer + with each incoming acknowledgement. + \item[\textit{last\_seen}] Timestamp of the most recent acknowledgement + received from the remote. + \item[\textit{slack}] A miss counter incremented each time the remote + fails to acknowledge a reliable message within the deadline. The + association is forgotten when \textit{slack} exceeds a small limit + (recommended default: $2$). +\end{description} + +Associations are created opportunistically on receipt of a positive +acknowledgement for a reliable message, and are removed when they age out of +the session lifetime (section~\ref{sec:session_parameters}) or when their +slack counter exceeds the limit. + +\subsection{First publication} + +When a publisher sends its first reliable message on a topic it may have no +associations at all. The first message is multicast on the topic's subject-ID +and the publisher treats the arrival of \emph{any} positive acknowledgement as +success; further acknowledgements arriving after the first continue to +populate the association set in the background. + +\subsection{Retransmission policy} + +The publisher retransmits a reliable message using an exponential backoff +schedule. The first retransmission is scheduled one \emph{ACK baseline +timeout} after the initial attempt; each subsequent retransmission doubles the +timeout. The final attempt is capped by the publication deadline supplied with +the publication. Implementations may scale the ACK baseline timeout by the +message's priority level, for example to give higher-priority publications +tighter timeouts. + +Initial attempts are always multicast, since the publisher does not yet know +which recipients are live. As acknowledgements arrive and unacknowledged +recipients remain, the publisher may elect to unicast the remaining +retransmissions directly to the holdouts using the transport unicast metadata +recorded in the association entries. This multicast-to-unicast fallback is a +heuristic chosen to reduce network and subscriber load; it is not required for +interoperability. + +\subsection{Acknowledgements} + +A \texttt{msg\_ack} is sent by any subscriber that accepts a reliable message +for processing. The acknowledgement is unicast back to the publisher at the +priority of the original message, and references the message by its topic +hash and tag. + +A \texttt{msg\_nack} is sent only in response to a reliable \emph{unicast} +message for which the receiver has no matching subscriber. It tells the +publisher to prune the association promptly so that further retransmissions +are not wasted on a recipient that cannot accept the topic. \emph{Multicast} +reliable messages do not generate NACKs, because subscription membership is +implicit in the transport's multicast join state: a subscriber that is no +longer interested simply stops participating in the multicast group. + +\begin{remark} + The asymmetry between unicast and multicast NACKs is intentional. For + multicast delivery, the publisher learns that a remote has left by + observing its absence from future acknowledgements; for unicast delivery, + no such implicit signal exists, so an explicit NACK is required. +\end{remark} + +\subsection{Receive-side deduplication} + +A reliable message may be delivered more than once by the transport if an +acknowledgement is lost and the publisher retransmits the same tag. The +receiver eliminates such duplicates by keeping a per-remote cache keyed by +\texttt{(remote\_id, tag)} and discarding any tag that has already been +accepted. Dedup state is soft and is aged out after the session lifetime +(section~\ref{sec:session_parameters}). + +\subsection{Observable states of a reliable publication} + +A reliable publication transitions through the following observable states, +which are named here to describe protocol behaviour rather than any API: + +\begin{description} + \item[\textsc{pending}] The initial state; set when the first attempt is + submitted to the transport. The publication remains pending until a + terminal event occurs. + \item[\textsc{done, success}] Reached when at least one positive + acknowledgement has been received and all remaining associations have + either acknowledged or aged out of the publication's deadline. + \item[\textsc{done, delivery failure}] Reached when the publication + deadline expires without any positive acknowledgement. + \item[\textsc{done, explicit rejection}] Reached on receipt of a + \texttt{msg\_nack} for a unicast reliable message. + \item[\textsc{done, transient lag}] Reached when the publication + deadline is about to expire but the publisher cannot determine whether + the retransmission schedule actually delivered the message due to local + scheduler pressure. This state is informational: the application is + warned that reachability information for this publication is + incomplete. +\end{description} + +\section{Deduplication and ordering}\label{sec:session_dedup} + +Session-layer deduplication applies to reliable messages only, as described in +section~\ref{sec:session_reliable}. Best-effort messages are deduplicated by +the transport and no additional session-layer work is required. + +A subscriber may optionally enable \emph{per-remote ordered delivery}. +When enabled, the subscriber uses the monotonic per-publisher \texttt{tag} +field to reconstruct the publication order within a bounded reorder window. +Messages whose tag lies within the window are delivered in increasing tag +order; messages whose tag lies outside the window because they arrived too +late are dropped. Ordering is guaranteed per remote publisher only, not across +remotes, because there is no global clock. diff --git a/specification/session/names.tex b/specification/session/names.tex new file mode 100644 index 00000000..c7a51168 --- /dev/null +++ b/specification/session/names.tex @@ -0,0 +1,337 @@ +\section{Topic names}\label{sec:session_names} + +A \emph{topic name} is a string of printable ASCII characters used by the application +to identify a publish-subscribe channel. +The session layer carries names only in gossip and scout messages; every other +message identifies topics by their 64-bit hash (section~\ref{sec:session_identity}). + +\subsection{Character set and normalization} + +A valid name consists of characters in the ASCII range $[33, 126]$; the space +character (ASCII $32$) and all control characters are excluded. +Names are case-sensitive. +The forward-slash character \texttt{/} (ASCII $47$) acts as the \emph{separator} +between name segments. + +A \emph{normalized} name contains no leading separator, no trailing separator, and +no consecutive separators. Names are normalized before hashing and before being sent +on the wire. The maximum length of a normalized name is 200 bytes. + +\subsection{Pattern tokens} + +Subscribers may register under pattern names that contain the following special +tokens; publishers may not. A publisher whose resolved name contains any pattern +token shall be rejected by the session layer. + +\begin{description} + \item[\texttt{*} (ASCII $42$)] Matches exactly one name segment. + For example, the pattern \texttt{sensors/*/data} matches \texttt{sensors/imu/data} + and \texttt{sensors/gps/data} but not \texttt{sensors/imu/raw/data}. + + \item[\texttt{>} (ASCII $62$)] Matches zero or more trailing segments. + Must appear as the last token in the pattern. + For example, the pattern \texttt{sensors/>} matches \texttt{sensors}, + \texttt{sensors/imu}, and \texttt{sensors/imu/raw/data}. +\end{description} + +Pattern tokens are not permitted in intermediate positions outside of these forms; +for example, \texttt{sen*ors} is a literal name, not a pattern. + +\subsection{Pin suffix} + +A name may be pinned to a specific subject-ID using the suffix +\texttt{\#\textit{NNN}}, where \texttt{\#} is ASCII $35$ and \textit{NNN} is a +decimal number in the range $[0, 8191]$ without leading zeros. +Pinning is permitted only on verbatim names; a pattern name shall not carry a +pin suffix. +The pin suffix is stripped from the name before hashing and normalization, so a +pinned topic and its verbatim counterpart share the same topic hash but request a +different subject-ID (section~\ref{sec:session_identity}). + +\subsection{Home and namespace expansion} + +A name may begin with the \emph{home} token \texttt{\textasciitilde} (tilde, +ASCII $126$), which is replaced at resolution time with the node's home name. +A name beginning with \texttt{/} is \emph{absolute} and is used as-is, ignoring +the namespace prefix. All other names are relative and are prefixed with the +node's namespace at resolution time. + +\subsection{Remapping} + +A node may carry a \emph{remap table} of \texttt{\textit{from}}$\to$\texttt{\textit{to}} +pairs that rewrite names before namespace and home expansion. +A matching rule replaces the input name with its \texttt{\textit{to}} string and then +feeds the result into the rest of the resolution pipeline. +If the matched rule's \texttt{\textit{to}} string carries a pin suffix, that pin is +honoured; any pin supplied in the user's original name is discarded. +Software implementations should accept the remap table from the environment variables +\texttt{CYPHAL\_NAMESPACE} and \texttt{CYPHAL\_REMAP} to allow integrators to adjust +names without modifying the application. + +\subsection{Resolution pipeline} + +The following pipeline is normative. Given a user-supplied name, a node resolves it +as follows, in order: + +\begin{enumerate} + \item \textbf{Remap} the input name through the remap table, if any. + \item \textbf{Expand} the home token \texttt{\textasciitilde} if present, or + prefix the namespace if the name is relative and the absolute form is not used. + \item \textbf{Extract} any pin suffix, recording the pinned subject-ID separately. + \item \textbf{Normalize} the remaining name by collapsing consecutive separators + and trimming leading and trailing separators. +\end{enumerate} + +The result is the normalized resolved name (used for hashing and for inclusion in +gossip and scout messages) together with the optional pinned subject-ID. +Resolution fails if the normalized name is empty, exceeds 200 bytes, or contains a +character outside the permitted set. + +\begin{remark} + The intended usage pattern is that applications hardcode verbatim names and let + integrators introduce patterns and pinning through the remap table at deployment + time. This keeps the application code free of site-specific routing decisions. +\end{remark} + +\subsection{Resolution examples} + +The following examples illustrate the resolution pipeline when no remap table +is installed. \texttt{NAMESPACE} and \texttt{HOME} are per-node configuration; +\texttt{RESOLVED} is the normalized name that appears on the wire; +\texttt{PIN} is the pinned subject-ID, if any; +\texttt{VERBATIM?} is \emph{yes} if the resolved name has no pattern tokens. + +\begin{CyphalSimpleTable}[wide]{Name resolution without remap}{|l l l l r l|} + Name & Namespace & Home & Resolved & Pin & Verbatim? \\ + \texttt{foo/bar} & \texttt{ns1} & \texttt{me} & \texttt{ns1/foo/bar} & -- & yes \\ + \texttt{\textasciitilde/foo/bar} & \texttt{ns1} & \texttt{me} & \texttt{me/foo/bar} & -- & yes \\ + \texttt{/foo//bar/} & \texttt{ns1} & \texttt{me} & \texttt{foo/bar} & -- & yes \\ + \texttt{foo/bar/} & \texttt{\textasciitilde/ns1} & \texttt{me} & \texttt{me/ns1/foo/bar} & -- & yes \\ + \texttt{foo\#123} & \texttt{ns1\#456} & \texttt{me} & \texttt{ns1/foo} & $123$ & yes \\ + \texttt{foo/\#123} & \texttt{ns1\#456} & \texttt{me} & \texttt{ns1/foo} & $123$ & yes \\ + \texttt{*/foo} & \texttt{ns1} & \texttt{me} & \texttt{ns1/*/foo} & -- & no \\ + \texttt{\textasciitilde/*/foo/} & \texttt{ns1} & \texttt{me} & \texttt{me/*/foo} & -- & no \\ + \texttt{/\textasciitilde/*/foo/} & \texttt{ns1} & \texttt{me} & \texttt{\textasciitilde/*/foo} & -- & no \\ +\end{CyphalSimpleTable} + +Note in the last row that an absolute name beginning with \texttt{/} +short-circuits home expansion, so the tilde is preserved as a literal +character in the resolved name. + +Next, a remap table is installed. A matched remap rule replaces the user-supplied +name with the rule's \texttt{to} string, which is then fed through the same +pipeline; a matched rule also discards any pin suffix the user may have +supplied. The pin on a rule's \texttt{to} string, if present, is honoured +instead. + +\begin{CyphalSimpleTable}[wide]{Name resolution with remap}{|l l l l l r X|} + Name & From & To & Namespace & Home & Pin & Remark \\ + \texttt{foo/bar} & \texttt{foo/bar} & \texttt{zoo} & \texttt{ns} & \texttt{me} & -- & Relative remap; resolved name is \texttt{ns/zoo}. \\ + \texttt{foo/bar} & \texttt{foo/bar} & \texttt{zoo\#123} & \texttt{ns} & \texttt{me} & $123$ & Pinned relative remap; resolved name is \texttt{ns/zoo}. \\ + \texttt{foo/bar\#456} & \texttt{foo/bar} & \texttt{zoo} & \texttt{ns} & \texttt{me} & -- & User pin discarded by the matched rule; resolved name is \texttt{ns/zoo}. \\ + \texttt{foo/bar} & \texttt{foo/bar} & \texttt{/zoo} & \texttt{ns} & \texttt{me} & -- & Absolute remap; namespace is ignored; resolved name is \texttt{zoo}. \\ + \texttt{foo/bar} & \texttt{foo/bar} & \texttt{\textasciitilde/zoo} & \texttt{ns} & \texttt{me} & -- & Homeful remap; the tilde expands to \texttt{me}; resolved name is \texttt{me/zoo}. \\ +\end{CyphalSimpleTable} + +The following inputs cause resolution to fail: + +\begin{description} + \item[\texttt{"foo bar\textbackslash{}nbaz"}] Contains a space (ASCII $32$) + and a control character (ASCII $10$); non-printable and space characters + are not allowed. + \item[\texttt{"foo/*/bar\#123"}] A pattern name cannot be pinned; the + \texttt{\#} suffix is not permitted on names containing pattern tokens. + \item[\texttt{""} (empty)] An empty name is not allowed. + \item[\texttt{"\#1234"}] After the pin expression is stripped, the + remaining name is empty, which is not allowed. + \item[A name longer than 200 bytes after normalization] Rejected regardless + of how much buffer space is available. +\end{description} + +\section{Topic identity and subject-ID mapping}\label{sec:session_identity} + +Every topic has a deterministic 64-bit \emph{topic hash} derived from its normalized +resolved name and a transport-visible \emph{subject-ID} derived from the hash and +its eviction counter. +The mapping is purely arithmetic and uses no shared state other than the parameters +normalized in this section; every node computes the same value from the same inputs. + +\subsection{The topic hash function}\label{sec:session_rapidhash} + +The topic hash is computed as $\mathrm{rapidhash}(\mathit{name},\ \mathit{seed}=0)$, +where \textit{name} is the UTF-8 byte sequence of the normalized resolved name +(no NUL terminator) and $\mathrm{rapidhash}$ is the 64-bit +\textbf{rapidhash V3} function\footnote{% + rapidhash is a fast, high-quality, platform-independent hash function + developed by Nicolas De Carli and distributed under the MIT license at + \href{https://github.com/Nicoshev/rapidhash}{github.com/Nicoshev/rapidhash}. + It is derived from the wyhash algorithm by Wang Yi. + Cyphal v1.1 pins the V3 variant of rapidhash as its normative hash function; + the Python transcription below has been verified bit-identical to the + reference C implementation for every test vector in this section. +} with the default secret specified below. + +The default secret consists of eight 64-bit constants: + +\begin{CyphalSimpleTable}{Default secret for the rapidhash V3 topic hash function}{|l l|} + Index & Value (hexadecimal) \\ + 0 & \texttt{0x2d358dccaa6c78a5} \\ + 1 & \texttt{0x8bb84b93962eacc9} \\ + 2 & \texttt{0x4b33a62ed433d4a3} \\ + 3 & \texttt{0x4d5a2da51de1aa47} \\ + 4 & \texttt{0xa0761d6478bd642f} \\ + 5 & \texttt{0xe7037ed1a0b428db} \\ + 6 & \texttt{0x90ed1765281c388c} \\ + 7 & \texttt{0xaaaaaaaaaaaaaaaa} \\ +\end{CyphalSimpleTable} + +Multi-byte integers within the hash function are read from the input in +\emph{little-endian} byte order on every platform; big-endian implementations shall +emulate this ordering. Wherever the resulting 64-bit hash integer appears in a +session-layer header, it is serialized as little-endian. + +\begin{remark} + Multiple rapidhash variants exist in the wild + (V1, V2, V3, V3-micro, V3-nano, wyhash, \ldots) and they are not wire-compatible + with one another. Cyphal v1.1 pins down the full V3 variant only, with the + default seed and the default secret listed above. +\end{remark} + +A normative Python transcription of the reference C implementation is given in +appendix~\ref{sec:appendix_rapidhash}. It is bit-identical to the reference +implementation on every platform and may be used directly to reproduce the +test vectors below. + +The following test vectors are authoritative. An implementation that fails to +reproduce any of them is not interoperable with the rest of the Cyphal network and +cannot participate in the topic allocation protocol. + +\begin{CyphalSimpleTable}{Rapidhash V3 test vectors}{|l r l|} + Input (ASCII) & Length & \texttt{rapidhash(input)} \\ + \texttt{""} (empty) & 0 & \texttt{0x0338dc4be2cecdae} \\ + \texttt{"a"} & 1 & \texttt{0x599f47df33a2e1eb} \\ + \texttt{"abc"} & 3 & \texttt{0xcb475beafa9c0da2} \\ + \texttt{"topic"} & 5 & \texttt{0x0164a16804e84989} \\ + \texttt{"sensors/imu"} & 11 & \texttt{0xbb402fd6c693c60b} \\ + \texttt{"sensors/engine/temperature"} & 26 & \texttt{0x32d2c4bd5c5485e7} \\ + \texttt{"head/camera/upper\_left"} & 22 & \texttt{0x28f52efde90dec0a} \\ + \texttt{"system/primary/sensor/0/data"} & 28 & \texttt{0x57627b4b782b47af} \\ + \texttt{"ins/0/data"} & 10 & \texttt{0xdf3339ab99373b98} \\ + \texttt{"1234"} & 4 & \texttt{0xa5ae810dbac9be9b} \\ + \texttt{"65535"} & 5 & \texttt{0x2c5a542e44fa7c20} \\ + 200 $\times$ \texttt{'x'} & 200 & \texttt{0x25adb114ca9ea76a} \\ +\end{CyphalSimpleTable} + +\subsection{Subject-ID mapping}\label{sec:session_subject_id_mapping} + +The subject-ID assigned to a non-pinned topic is a function of its hash $h$, its +eviction counter $e$, and the transport's subject-ID modulus $M$: +\begin{equation*} + \mathit{subject\_id}(h, e) = 8192 + ((h + e \cdot e) \bmod M) +\end{equation*} +All arithmetic is performed on 64-bit unsigned integers with wraparound; +implementations using signed types shall take care not to sign-extend the hash. +The modulus $M$ is a prime satisfying $M \bmod 4 = 3$, chosen per +section~\ref{sec:session_modulus}; its selection ensures that the +$\{e^2 \bmod M : e \in \mathbb{N}\}$ sequence is a valid quadratic-probing +sequence. + +A \emph{pinned} topic is one whose eviction counter lies in the reserved range +$[\text{UINT32\_MAX}-8191,\ \text{UINT32\_MAX}]$. +Its subject-ID is obtained by $\mathit{subject\_id} = \text{UINT32\_MAX} - e$, +which lies in the pinned range $[0, 8191]$. +Pinned topics otherwise behave as ordinary CRDT records and participate in +arbitration on equal terms with non-pinned topics. + +\begin{remark} + The high-eviction encoding of pinned topics serves a second purpose: it acts as a + wrap-around guard for the eviction counter itself. + Under any pathological churn that drove a non-pinned topic's eviction counter + past $2^{32} - 8192$, the topic would enter the pinned range and be arrested at + subject-ID 8191 rather than overflowing the counter and disrupting consensus. + The 32-bit counter is wide enough that such growth is not reachable in practice: + at a sustained one eviction per second it takes over 136 years to overflow. +\end{remark} + +\subsection{Subject-ID ranges}\label{sec:session_subject_id_ranges} + +The session layer partitions the entire subject-ID space exposed by the +transport into four disjoint ranges, described in the table below. +All values refer to the numerical subject-ID carried in the transport header; +the topic $\to$ subject-ID mapping is defined in +section~\ref{sec:session_subject_id_mapping}. + +\begin{CyphalSimpleTable}{Subject-ID space partition}{|l X|} + Range & Usage \\ + $[0,\ 8191]$ & Pinned topics. Retained as the primary path for interoperation with Cyphal/CAN v1.0 nodes, which only understand this range. \\ + $[8192,\ 8191{+}M]$ & Auto-allocated topics. $M$ is a transport-specific prime modulus satisfying $M \bmod 4 = 3$, defined in section~\ref{sec:session_modulus}. \\ + $(8191{+}M,\ B)$ & Gossip shard subjects used for background CRDT dissemination (section~\ref{sec:session_gossip}). \\ + $B$ & The \emph{broadcast subject}, used for broadcast gossip and for scout service discovery (section~\ref{sec:session_scout}). \\ +\end{CyphalSimpleTable} + +where $B = 2^{\lceil\log_2(8191 + M + 1)\rceil} - 1$ is the highest subject-ID +representable in the transport's subject-ID width. +Every node is expected to subscribe to the broadcast subject; the transport's +implicit participant discovery mechanism (section~\ref{sec:transport}) is a +side effect of that universal subscription. + +On the broadcast subject and on any gossip shard subject, the transport layer +is permitted to relax two of the requirements stated in +section~\ref{sec:transport}: deduplication of incoming transfers is not +mandatory, and support for transfers exceeding a single transport frame is not +required. The session layer tolerates occasional duplication on these +subjects, and constrains its own protocol messages on them to fit within a +single transport frame. + +\begin{remark} + The relaxation is important for scalability on resource-constrained + transports. Deduplication state on the broadcast subject would otherwise + grow linearly with the number of peers, which is hostile to small nodes; + multi-frame reassembly buffers on the gossip shards would impose a + per-shard memory cost that a large network cannot afford. +\end{remark} + +\subsection{Subject-ID modulus}\label{sec:session_modulus} + +The modulus $M$ depends on the width of the subject-ID field supported by the +transport. It shall be prime, satisfy $M \bmod 4 = 3$, and be the largest such +prime not exceeding $(\text{transport maximum subject-ID}) - 8191$. +The following values are normative: + +\begin{CyphalSimpleTable}{Subject-ID moduli for supported transport widths}{|l l r X|} + Name & Width & Modulus $M$ & Notes \\ + 16-bit & 16 bits & $57203$ & Usable on all Cyphal transports, including Cyphal/CAN. \\ + 23-bit & 23 bits & $8378431$ & Usable on Cyphal/UDP; not compatible with Cyphal/CAN. \\ + 32-bit & 32 bits & $4294954663$ & Reserved for future 32-bit transports; not compatible with the above. \\ +\end{CyphalSimpleTable} + +All nodes on the same logical Cyphal network shall use the same modulus. +In a heterogeneously redundant deployment where different transports are used in +parallel, the smallest applicable modulus shall be chosen so that every subject-ID +is representable on every member transport. + +For the 16-bit modulus the derived subject-ID ranges are $[0, 8191]$ for pinned +topics, $[8192, 65394]$ for auto-allocated topics, $[65395, 65534]$ for the +140 gossip shards, and $65535$ for the broadcast subject. +For the 23-bit modulus the ranges are $[0, 8191]$, $[8192, 8386622]$, +$[8386623, 8388606]$ ($1984$ gossip shards), and $8388607$ respectively. + +The following test vectors lock down the mapping formula for typical inputs. +The evictions column traces the first three entries of the quadratic-probing +sequence $(e = 0, 1, 2)$; subsequent entries step by $5, 7, 9, \ldots$. + +\begin{CyphalSimpleTable}{Subject-ID test vectors}{|l l r r r|} + Name & Hash & $e$ & $M=57203$ & $M=8378431$ \\ + \texttt{""} & \texttt{0x0338dc4be2cecdae} & 0 & $21600$ & $6520572$ \\ + \texttt{""} & \texttt{0x0338dc4be2cecdae} & 1 & $21601$ & $6520573$ \\ + \texttt{""} & \texttt{0x0338dc4be2cecdae} & 2 & $21604$ & $6520576$ \\ + \texttt{"topic"} & \texttt{0x0164a16804e84989} & 0 & $38082$ & $1049799$ \\ + \texttt{"topic"} & \texttt{0x0164a16804e84989} & 1 & $38083$ & $1049800$ \\ + \texttt{"topic"} & \texttt{0x0164a16804e84989} & 2 & $38086$ & $1049803$ \\ + \texttt{"sensors/imu"} & \texttt{0xbb402fd6c693c60b} & 0 & $54214$ & $8263034$ \\ + \texttt{"sensors/imu"} & \texttt{0xbb402fd6c693c60b} & 1 & $54215$ & $8263035$ \\ + \texttt{"sensors/imu"} & \texttt{0xbb402fd6c693c60b} & 2 & $54218$ & $8263038$ \\ + \texttt{"ins/0/data"} & \texttt{0xdf3339ab99373b98} & 0 & $28748$ & $8266023$ \\ + \texttt{"ins/0/data"} & \texttt{0xdf3339ab99373b98} & 1 & $28749$ & $8266024$ \\ + \texttt{"ins/0/data"} & \texttt{0xdf3339ab99373b98} & 2 & $28752$ & $8266027$ \\ +\end{CyphalSimpleTable} diff --git a/specification/session/parameters.tex b/specification/session/parameters.tex new file mode 100644 index 00000000..75121ac4 --- /dev/null +++ b/specification/session/parameters.tex @@ -0,0 +1,40 @@ +\section{Parameters and sizing}\label{sec:session_parameters} + +The following parameters govern the wire behaviour of the session layer. Values +marked \emph{normative} are required for interoperability; values marked +\emph{recommended} may be adjusted by compliant implementations as long as the +CRDT invariants in section~\ref{sec:session_crdt} and the transport contract in +section~\ref{sec:transport} remain satisfied. Nodes that use different +recommended values remain interoperable with one another. + +{\footnotesize +\begin{CyphalSimpleTable}[wide]{Session-layer parameters}{|l l l X|} + Parameter & Unit & Value & Notes \\ + Session header size & bytes & 24 & Normative; section~\ref{sec:session_headers}. \\ + Maximum topic name length & bytes & 200 & Normative; UTF-8, after normalization. \\ + Subject-ID modulus, 16-bit width & -- & 57203 & Normative; section~\ref{sec:session_modulus}. \\ + Subject-ID modulus, 23-bit width & -- & 8378431 & Normative; section~\ref{sec:session_modulus}. \\ + Subject-ID modulus, 32-bit width & -- & 4294954663 & Normative; reserved for future 32-bit transports. \\ + Eviction counter width & bits & 32 & Normative; pinned range starts at $\text{UINT32\_MAX} - 8191 = \text{0xFFFFE000}$. \\ + Message tag width & bits & 64 & Normative; random-initialized, permitted to wrap. \\ + Response sequence number width & bits & 48 & Normative; monotonic per request, starts at zero. \\ + Log-age range & -- & $[-1,\ 35]$ & Normative; $-1$ encodes an age below one second. \\ + Default gossip period & s & $\sim 5$ & Recommended; randomized by $\pm 1/8$ of the period. \\ + Broadcast-to-shard ratio & -- & $\sim 1 : 10$ & Recommended; one in $N$ periodic gossips goes on the broadcast subject. \\ + Urgent gossip delay window $W_u$ & ms & $\sim 10$ & Recommended; see section~\ref{sec:session_gossip_delays}. \\ + Startup gossip delay window $W_s$ & ms & tens to hundreds & Recommended; see section~\ref{sec:session_gossip_delays}. \\ + Reliable ACK baseline timeout & ms & $\sim 16$ & Recommended; retransmissions use exponential backoff. \\ + Session lifetime (soft state) & s & $\sim 60$ & Recommended; association and deduplication entries expire after this. \\ + Implicit topic retention window & s & $\sim 600$ & Recommended; inactive implicit topics are retired after this interval. \\ + Association slack limit & -- & $2$ & Recommended; consecutive missed acks before an association is forgotten. \\ +\end{CyphalSimpleTable} +} + +Auxiliary material that does not fit in the main body of the chapter is +collected in the appendices. Appendix~\ref{sec:appendix_eviction_solver} +gives a constant-time diagnostic solver for recovering the eviction counter +from a subject-ID. Appendix~\ref{sec:appendix_core_tla} reproduces the +complete TLA\textsuperscript{+} model of the allocation CRDT that this +chapter paraphrases; appendix~\ref{sec:appendix_proof} contains the +convergence proof. Appendix~\ref{sec:appendix_rapidhash} contains the pinned +rapidhash reference source. diff --git a/specification/session/rpc.tex b/specification/session/rpc.tex new file mode 100644 index 00000000..db817f13 --- /dev/null +++ b/specification/session/rpc.tex @@ -0,0 +1,173 @@ +\section{RPC and streaming}\label{sec:session_rpc} + +The session layer builds remote procedure calls on top of ordinary pub-sub +messages rather than introducing a separate service-ID abstraction. An RPC +request is a normal reliable publication whose recipients may emit one or more +unicast \emph{responses} back to the publisher. The number of responses per +request is unrestricted; streaming responses are therefore a zero-cost +extension of single-response RPC. + +\subsection{Breadcrumbs} + +When a subscriber receives a message, the transport supplies the unicast +metadata needed to reach the originating node. The subscriber captures this +information in a \emph{breadcrumb} consisting of: + +\begin{itemize} + \item \textit{remote\_id}: the network identifier of the original + publisher; + \item \textit{topic\_hash}: the hash of the topic the original message + was delivered on; + \item \textit{message\_tag}: the tag of the original message; + \item \textit{seqno}: a 48-bit counter, initialized to zero, that is + incremented by the subscriber once for each response it sends in reply + to this message; + \item \textit{unicast\_ctx}: the transport's unicast metadata for the + original publisher. +\end{itemize} + +A breadcrumb is sufficient to emit any number of responses to the original +publisher at a later time, without keeping the original message alive. + +\subsection{Responses} + +A response is carried by a \texttt{rsp\_be} or \texttt{rsp\_rel} transfer +(section~\ref{sec:session_header_rsp}). The response header carries: + +\begin{itemize} + \item \textit{topic\_hash} and \textit{message\_tag}, copied from the + breadcrumb, which together identify the request; + \item \textit{seqno}, the 48-bit response sequence number; + \item \textit{response\_tag}, an arbitrary byte chosen by the responder + for acknowledgement correlation (reliable responses only). +\end{itemize} + +Streaming is achieved simply by sending multiple responses to the same +request, each with an incremented \textit{seqno}. The publisher observes a +monotonic stream of responses per remote responder. + +\subsection{Reliable responses} + +A responder that wishes to detect whether the original publisher is still +listening shall send its responses reliably (\texttt{rsp\_rel}). The +publisher acknowledges a reliable response with either a \texttt{rsp\_ack} +or a \texttt{rsp\_nack}: + +\begin{description} + \item[\texttt{rsp\_ack}] The publisher's application is still accepting + responses for this request. The responder may continue to stream. + + \item[\texttt{rsp\_nack}] The publisher's application has closed the + request and will discard any further responses. The responder should + cease streaming promptly. +\end{description} + +\subsection{Client-side deduplication and ordering} + +The publisher tracks, for each remote responder, a small bitmap of recently +seen \textit{seqno} values and rejects duplicates within the bitmap window. +Responses whose \textit{seqno} falls outside the window because they are too +old are rejected. + +Ordering is not reconstructed by default: a response with a larger +\textit{seqno} may be delivered before one with a smaller \textit{seqno} +on the same stream. An application that requires strict ordering within a +stream may implement a reorder buffer keyed on \textit{seqno}; pattern-based +subscribers may opt into such a buffer via the subscriber's ordering +configuration. + +\subsection{Liveness monitoring} + +The publisher may attach a \emph{liveness timeout} to a pending request. +If no response has been received from \emph{any} remote for longer than the +timeout, the publisher's observable state is moved into a \textsc{liveness} +warning that alerts the application and is automatically cleared when the +next response arrives. Liveness state is independent of the response queue: +a pending response that has not yet been consumed does not inhibit the +liveness warning, and clearing the warning does not drop the pending +response. + +\subsection{Zombie requests} + +After the publisher's application has closed a request, the session layer +may retain a small amount of state for the request briefly in order to +correctly handle duplicates of reliable responses that arrive after the +application has ceased to listen. A duplicate that arrives in this zombie +window is acknowledged with the same positive or negative acknowledgement +as the first time, without involving the application. The zombie window +expires with the session lifetime (section~\ref{sec:session_parameters}). + +\begin{remark} + The zombie mechanism exists purely to absorb benign duplicates. It is + not a mechanism for deferring application cleanup or for re-dispatching + responses to a replacement handler. +\end{remark} + +\section{Pattern subscriptions}\label{sec:session_patterns} + +Pattern subscriptions are indistinguishable from verbatim subscriptions on +the wire. The distinction exists only in how the local node decides which +topics to join. + +\subsection{Discovery} + +A node with a local pattern subscription discovers matching topics in two +complementary ways: + +\begin{description} + \item[Passive discovery] A received gossip carries the topic name (see + section~\ref{sec:session_header_gossip}). If the received name matches a + local pattern, the node creates an \emph{implicit topic} for it and + attaches a subscriber. Passive discovery is automatic and requires no + additional traffic. + + \item[Active discovery] A node that has just installed a new pattern + subscription may broadcast a scout message + (section~\ref{sec:session_scout}) with the pattern as the payload. Other + nodes respond with gossip messages for any of their topics that match, + which drives passive discovery. +\end{description} + +\subsection{Substitutions} + +When a pattern matches a name, the pattern's wildcards $(\texttt{*},\ \texttt{>})$ +are paired with the segments they matched. The result is an ordered list of +\emph{substitutions}, each recording the matched substring together with its +ordinal position in the pattern. + +\begin{remark} + For example, the pattern \texttt{ins/*/data/>} matching the name + \texttt{ins/0/data/foo/456} yields three substitutions: + \verb|"0"|, \verb|"foo"|, and \verb|"456"|, at ordinals $0$, $1$, and $1$ + respectively (the second and third substitutions share an ordinal + because they are both consumed by the trailing \texttt{>} token). +\end{remark} + +The substitutions are computed once, when the pattern first matches the +topic, and are made available to the receiving application alongside each +arriving message. No string re-matching is performed per message. + +\subsection{Implicit topic lifecycle} + +An implicit topic behaves like any other topic for arbitration and message +delivery, with two differences: + +\begin{itemize} + \item It does not emit gossip of its own. Its CRDT state is updated by + incoming gossip, by inline gossip from incoming application messages, or + by scout-triggered passive discovery. + \item It is automatically retired after the retention window elapses + without any activity (no messages, no gossips). +\end{itemize} + +The retention window should be much longer than the worst-case periodic +gossip interval so that a silent but alive topic is not prematurely +retired. A recommended default is ten minutes; see +section~\ref{sec:session_parameters}. + +\subsection{Publisher invariant} + +A publisher's topic name shall not contain any pattern tokens. A resolved +publisher name that contains \texttt{*} or \texttt{>} is invalid and shall +be rejected by the session layer at publisher creation time. This invariant +ensures that every topic on the wire has a deterministic, unambiguous name. diff --git a/specification/session/session.tex b/specification/session/session.tex new file mode 100644 index 00000000..f9b34e5d --- /dev/null +++ b/specification/session/session.tex @@ -0,0 +1,118 @@ +\chapter{Session layer}\label{sec:session} + +The Cyphal session layer provides the named publish-subscribe abstractions that +applications use directly. +It sits above the transport layer (chapter~\ref{sec:transport}), treating it as a narrow, +unreliable, deduplicated, unordered message courier that carries multicast and unicast +transfers between nodes. +Every feature that requires more than that contract -- naming, subject-ID allocation, +reliable delivery, remote procedure calls, streaming, service discovery -- is +constructed by the session layer from a small number of session-layer headers and +state machines described in this chapter. + +\section{Function and position in the stack}\label{sec:session_function} + +The session layer provides, on top of the transport contract defined in +section~\ref{sec:transport} and summarized below: + +\begin{itemize} + \item A \emph{named topic} abstraction with decentralized subject-ID allocation, + replacing the static subject-ID tables used by Cyphal v1.0. + \item Reliable one-way message delivery with acknowledgements and retransmission. + \item Remote procedure calls with optional response streaming, built as a pattern on + top of ordinary pub-sub transfers rather than as a separate service-ID construct. + \item Pattern-based subscriptions that double as a service discovery mechanism. + \item A gossip-based consensus protocol that keeps every node's view of the + topic-to-subject-ID map consistent with every other node's view. +\end{itemize} + +The transport layer is required only to provide unreliable, deduplicated, unordered +delivery of integrity-checked messages; segmentation and reassembly of large payloads; +and participant discovery as a side effect of joining the broadcast subject. +These requirements are stated in full in section~\ref{sec:transport}; +the session layer does not depend on anything else. + +\begin{remark} + A consequence of this division is that the session layer is portable across + transports without modification. + A new Cyphal transport is made interoperable with the rest of the ecosystem by + providing the transport contract only; every session-layer behavior described in + this chapter is expected to work on top of it without change. +\end{remark} + +\section{Model and core objects}\label{sec:session_model} + +The session layer operates on the following objects. +Each object is described in terms of the state it carries and the wire messages it +exchanges; no application programming interface is implied. + +\begin{description} + \item[Node] A participant in a Cyphal network. + A node owns a nonempty \emph{home name} that should be unique in the network, + a \emph{namespace} prefix used when resolving topic names, + and an optional \emph{remap table} that rewrites topic names at resolution time. + A node exposes publishers and subscribers and participates in the allocation + consensus protocol by exchanging gossip messages with other nodes. + + \item[Topic] A named publish-subscribe channel. + A topic is identified on the wire by a 64-bit \emph{topic hash} and a + transport-visible \emph{subject-ID}; the mapping between them is maintained by + the allocation protocol described in section~\ref{sec:session_crdt}. + Locally, a topic carries a small CRDT state record $(\mathit{hash},\ + \mathit{evictions},\ \mathit{age})$ whose semantics are defined in + section~\ref{sec:session_crdt}. + + \item[Publisher and subscriber] Per-topic endpoints through which the application + emits and consumes messages. They are named here only because their wire + behavior differs (publishers emit messages, subscribers emit acknowledgements); + they are not otherwise observable to other nodes. + A single topic may have several local publishers and subscribers simultaneously. + + \item[Future] A conceptual handle representing the progress of an asynchronous + operation -- a reliable publication waiting for an acknowledgement, a request + waiting for responses, or a subscriber waiting for an arrival. + Futures are used in this chapter only to describe observable state transitions + of the protocol; the wire format does not reference them. +\end{description} + +\input{session/names.tex} +\input{session/crdt.tex} +\input{session/gossip.tex} +\input{session/messages.tex} +\input{session/rpc.tex} +\input{session/parameters.tex} + +\section{Interoperation with Cyphal/CAN v1.0}\label{sec:session_v10_interop} + +Cyphal v1.1 nodes interoperate with Cyphal/CAN v1.0 nodes on the same bus. +The mechanism is transport-level rather than session-level: a v1.1 node sending a +best-effort message on a pinned topic in the range $[0, 8191]$ strips its +session-layer header and emits the payload as a legacy 13-bit frame, which v1.0 nodes +parse without modification. +See section~\ref{sec:transport_can_format_selection} and +section~\ref{sec:transport_can_v10_header_reconstruction} for the normative details of +format selection and header reconstruction. + +v1.1 gossip, scout, RPC, and reliable traffic are confined to the 16-bit CAN ID format, +which v1.0 nodes silently discard at their bit~7 check. +v1.0 nodes therefore see only the subset of v1.1 traffic that lies on pinned subject-IDs +and is sent best-effort; everything else is invisible to them and does not affect them. + +Pattern subscriptions cannot discover v1.0-only topics because v1.0 nodes emit neither +gossip nor scout messages. Applications that need to interact with v1.0 endpoints +shall use verbatim pinned subscriptions on the corresponding subject-IDs. +The session layer does not bridge v1.0 RPC: v1.0 service transfers use the +service-ID mechanism of the transport layer, which the session layer does not observe. + +\section{Security and threat model}\label{sec:session_security} + +The session layer assumes that all nodes on a Cyphal network are trusted. +The protocol is designed to remain correct under network faults, packet loss, +reordering, partial partitioning, and resource starvation, but it is not designed to +remain correct under adversarial behavior by a participating node. +A malicious node that is permitted to emit messages on the network can influence +topic allocation, inject gossip, or send acknowledgements on behalf of others. + +Security features -- authentication, encryption, authorization, signed pinning -- may +be introduced as optional extensions once the core protocol has stabilized. +They are not specified here. diff --git a/specification/transport/can/can.tex b/specification/transport/can/can.tex index e0bee980..bc2379d0 100644 --- a/specification/transport/can/can.tex +++ b/specification/transport/can/can.tex @@ -4,13 +4,15 @@ \section{Cyphal/CAN}\label{sec:transport_can} This section specifies a concrete transport based on ISO 11898 CAN bus. Throughout this section, ``CAN'' implies both Classic CAN 2.0 and CAN FD, unless specifically noted otherwise. -CAN FD should be considered the primary transport protocol. +CAN FD should be considered the primary transport protocol, Classic CAN being the legacy compatibility option. \subsection{Definitions} \begin{description} \item[Node-ID] A 7-bit numerical identifier of a node, in the range $[0, 127]$. - Nodes on the same Cyphal/CAN network shall have distinct node-IDs. + Nodes on the same Cyphal/CAN network shall have distinct node-IDs\footnote{% + Possibly allocated automatically through random assignment with occupancy monitoring. + }. \item[Subject-ID] A 16-bit numerical identifier of a publish-subscribe subject, in the range $[0, 65535]$. Cyphal/CAN v1.0 supported only the 13-bit subject-ID range $[0, 8191]$. @@ -20,9 +22,8 @@ \subsection{Definitions} with pre-v1.1 nodes. \item[Transfer-ID] A cyclic counter taken modulo 32 that distinguishes consecutive transfers originated by - the same source node on the same subject or service. Used for duplicate suppression and for ordering the - frames of a multi-frame transfer. The sender increments it once per transfer; the receiver applies a timeout - to bound its state. + the same source node on the same subject or service. Used for duplicate suppression and for multi-frame transfer + reassembly. The sender increments it once per transfer; the receiver applies a timeout to bound its state. \end{description} \subsection{CAN ID field} @@ -44,11 +45,7 @@ \subsection{CAN ID field} The two message formats are discriminated by the value of CAN ID bit~7: v1.0 messages have bit~7 cleared, while v1.1 messages have bit~7 set. A v1.0 receiver silently discards v1.1 message frames because they fail the v1.0 reserved-bit check at bit~7, -which allows a v1.1 network to coexist with legacy v1.0 nodes on the same bus. -Tables~\ref{table:transport_can_id_fields_message_transfer_v11}, -\ref{table:transport_can_id_fields_message_transfer_v10}, -and~\ref{table:transport_can_id_fields_service_transfer} -summarize the fields of each layout. +which allows a v1.1 network to coexist with v1.0 nodes on the same bus. % Please do not remove the hard placement specifier [H], it is needed to keep elements ordered. \begin{figure}[H] @@ -262,11 +259,11 @@ \subsubsection{Format selection}\label{sec:transport_can_format_selection} the v1.0 legacy 13-bit format carries only the raw application payload without any session-layer wrapping, which is what makes it wire-compatible with Cyphal/CAN v1.0 nodes that predate the session layer. -A sender shall use the v1.0 legacy 13-bit format for a message transfer if and only if both of the following hold: +A sender shall use the v1.0 legacy 13-bit format for a message transfer if and only if all of the following hold: (a) the subject-ID lies in the pinned range $[0, 8191]$, and (b) the message is a best-effort publication. In all other cases -- auto-allocated subject-IDs, the broadcast subject, gossip shards, reliable messages, -gossip messages, service discovery, and RPC -- the sender shall use the v1.1 16-bit format. +gossip messages, service discovery, etc -- the sender shall use the v1.1 16-bit format. When transmitting on a pinned subject using the 13-bit format, the sender shall strip the session-layer header from the outgoing message before enqueuing the frame, so that only the raw application payload @@ -275,7 +272,7 @@ \subsubsection{Format selection}\label{sec:transport_can_format_selection} When receiving a 13-bit-format frame on a subscribed pinned subject, the receiver shall reconstruct a session-layer header consistent with the matching pinned topic and prepend it to the received payload -before forwarding the message to the session layer. The reconstructed message shall be otherwise +before forwarding the message to the session layer. The reconstructed message should be otherwise indistinguishable from an equivalent message originated by a v1.1 node on the same pinned topic. \begin{remark} @@ -314,16 +311,19 @@ \subsubsection{Session header reconstruction}\label{sec:transport_can_v10_header A symmetric rule applies on the transmit side: a sender using the 13-bit format shall strip the session-layer header from the outgoing message and transmit only the remaining application payload. -The session layer is expected to ensure that the stripped header corresponds to a best-effort message -on a pinned topic, which is the only case in which the 13-bit format is used -(section~\ref{sec:transport_can_format_selection}). + +\begin{remark} + One implication of this design is that best-effort messages over pinned topics on Cyphal/CAN + cannot be used to initiate RPC/streaming. This limitation may be eventually lifted by amending the + message tag reconstruction algorithm. +\end{remark} \subsubsection{Unicast transfers}\label{sec:transport_can_unicast} Cyphal/CAN does not define a dedicated CAN ID layout for unicast delivery. A unicast transfer shall be represented as an RPC-service \emph{request} to the reserved service-ID $511$ addressed to the destination node. -The session layer treats service-ID $511$ as an opaque unicast channel; +The session layer treats service-ID $511$\footnote{Unused in Cyphal v1.0.} as an opaque unicast channel; no service response is issued at the transport layer, and higher-layer reply semantics, if any, are carried in subsequent unicast requests in the opposite direction. @@ -347,7 +347,6 @@ \subsubsection{Transfer priority} \begin{enumerate} \item Message transfers (the primary method of data exchange in Cyphal networks). - \item Anonymous (message) transfers. \item Service response transfers (preempt requests). \item Service request transfers (responses take precedence over requests to make service calls more atomic and reduce the number of pending states in the system). @@ -369,12 +368,21 @@ \subsubsection{Transfer priority} \end{CyphalCompactTable} Since the value of transfer priority is required to be the same for all frames in a transfer, - it follows that the value of the CAN ID is guaranteed to be the same for all CAN frames of the transfer. - Given a constant transfer priority value, all CAN frames under a given session specifier will be equal. + it follows that the value of the CAN ID is the same for all CAN frames of the transfer. \end{remark} \subsubsection{Source node-ID field in anonymous transfers}\label{sec:transport_can_source_node_pseudo_id} +\begin{remark} + Anonymous transfers are retained for compatibility with Cyphal/CAN v1.0; however, they are no longer recommended + for use in new deployments, and new implementations are not required to support them to claim compliance with + Cyphal/CAN v1.1. + + Anonymous transfers were originally introduced to facilitate automatic node-ID allocation; + the current recommendation is to rely on other methods of node-ID assignment, + such as explicit manual assignment, random probing with occupancy monitoring, or other application-specific methods. +\end{remark} + The source node-ID field of anonymous transfers shall be initialized with a pseudorandom \emph{pseudo-ID} value. The source of the pseudorandom data used for the pseudo-ID shall aim to produce different values for different CAN frame data field values. @@ -509,19 +517,17 @@ \subsubsection{Transfer-ID timeout}\label{sec:transport_can_transfer_id_timeout} The timeout applies to the following session kinds: \begin{itemize} - \item Message (subject) sessions, in both the v1.1 16-bit and the v1.0 legacy 13-bit formats. - \item Service request sessions, including the unicast channel defined in - section~\ref{sec:transport_can_unicast}. + \item Message (subject) sessions, in both the v1.1 16-bit and the v1.0 13-bit formats. + \item Service request sessions, including the unicast channel defined in section~\ref{sec:transport_can_unicast}. \end{itemize} A transfer-ID timeout of \emph{zero} shall be used for service \emph{response} sessions. -The transfer-ID of a service response is not generated by the server -- it is echoed from the request being -answered. Because the transfer-ID field is only five bits wide, the client may legitimately re-use the same +The transfer-ID of a service response is not generated by the server -- it is echoed from the request being answered. +Because the transfer-ID field is only five bits wide, the client may legitimately re-use the same transfer-ID value for two different requests to the same server within a short interval as its counter wraps; with a nonzero timeout, the later matching response would be rejected as a duplicate of the earlier one. Setting the timeout to zero disables transport-level duplicate suppression on response sessions, -leaving the receiving application to correlate responses with outstanding requests by other -means\footnote{% +leaving the receiving application to correlate responses with outstanding requests by other means\footnote{% See \href{https://github.com/OpenCyphal/libcanard/issues/247}{OpenCyphal/libcanard issue~247} for the original discussion. }. @@ -631,6 +637,7 @@ \subsubsection{Ordered transmission} will be transmitted in their order of appearance in the transmission queue\footnote{% This is because multi-frame transfers use identical CAN ID for all frames of the transfer, and Cyphal requires that all frames of a multi-frame transfer shall be transmitted in the correct order. + See related discussion in~\url{https://forum.opencyphal.org/t/1215}. }. \subsubsection{Transmission timestamping} @@ -719,24 +726,17 @@ \subsubsection{Inner priority inversion} getFreeMailboxIndex(newFrame) { chosen_mailbox = -1 // By default, assume that no mailboxes are available - - for i = 0...NumberOfTxMailboxes - { - if isTxMailboxFree(i) - { + for i = 0...NumberOfTxMailboxes { + if isTxMailboxFree(i) { chosen_mailbox = i // Note: cannot break here, shall check all other mailboxes as well. - } - else - { - if not isFramePriorityHigher(newFrame, getFrameFromTxMailbox(i)) - { + } else { + if not isFramePriorityHigher(newFrame, getFrameFromTxMailbox(i)) { chosen_mailbox = -1 break // Denied - shall wait until this mailbox has finished transmitting } } } - return chosen_mailbox } \end{minted} @@ -755,8 +755,8 @@ \subsubsection{Automatic hardware acceptance filter configuration} In the case of the list-based approach, every CAN frame detected on the bus is compared against the set of reference CAN ID values provided by the application; only those frames that are found in the reference set are accepted. - Due to the complex structure of the CAN ID field used by Cyphal, - usage of the list-based filtering method with this protocol is impractical. + Due to the structure of the CAN ID field used by Cyphal, + usage of the list-based filtering method is impractical. Most CAN controller vendors implement mask-based filters, where the behavior of each filter is defined by two parameters: the mask $M$ and the reference ID $R$. @@ -768,40 +768,29 @@ \subsubsection{Automatic hardware acceptance filter configuration} Complex Cyphal applications are often required to operate with more distinct transfers than there are acceptance filters available in the hardware. - That creates the challenge of finding the optimal configuration of the available filters that meets the - following criteria: - \begin{itemize} - \item All CAN frames needed by the application are accepted. - \item The number of irrelevant frames (i.e., not used by the application) accepted from the bus is minimized. - \end{itemize} + That creates the challenge of finding the optimal configuration of the available filters such that + (a) all CAN frames needed by the application are accepted, and + (b) the number of irrelevant frames (i.e., not used by the application) accepted from the bus is minimized. The optimal configuration is a function of the number of available hardware filters, the set of distinct transfers needed by the application, and the expected frequency of occurrence of all possible distinct transfers on the bus. The latter is important because if there are to be irrelevant transfers, it makes sense to optimize the configuration so that the acceptance of less common irrelevant transfers - is preferred over the more common irrelevant transfers, as that reduces the processing load on the application. + is preferred over the more common irrelevant ones. The optimal configuration depends on the properties of the network the node is connected to. - In the absence of the information about the network, + In the absence of information about the network, or if the properties of the network are expected to change frequently, it is possible to resort to a quasi-optimal configuration which assumes that the occurrence of all possible irrelevant transfers is equally probable. - As such, the quasi-optimal configuration is a function of only the number of available hardware filters - and the set of distinct transfers needed by the application. - - The quasi-optimal configuration can be easily found automatically. - Certain implementations of the Cyphal protocol stack include this functionality, - allowing the application to easily adjust the configuration of the hardware acceptance filters - using a very simple API. - - A quasi-optimal hardware acceptance filter configuration algorithm is described below. + The quasi-optimal configuration is a function of only the number of available hardware filters + and the set of distinct transfers needed by the application, and it can be easily found automatically + as described below. The approach was first proposed by P. Kirienko and I. Sheremet in 2015. First, the bitwise \emph{filter merge} operation is defined on filter configurations $A$ and $B$. - The set of CAN frames accepted by the merged filter configuration is a superset of - those accepted by $A$ and $B$. - The definition is as follows: + The set of CAN frames accepted by the merged filter configuration is a superset of those accepted by $A$ and $B$. \begin{equation*} \begin{split} m_M(R_A, R_B, M_A, M_B) & = M_A \land M_B \land \neg (R_A \oplus R_B) \\ @@ -809,33 +798,20 @@ \subsubsection{Automatic hardware acceptance filter configuration} \end{split} \end{equation*} - The \emph{filter rank} is a function of the mask of the filter. - The rank of a filter is a unitless quantity that defines in relative terms how selective the filter - configuration is. - The rank of a filter is proportional to the likelihood that the filter will reject a random CAN ID. - In the context of hardware filtering, this quantity is conveniently representable via the number of bits set in - the filter mask parameter (also known as \emph{population count}): - \begin{equation*} - r(M) = - \begin{cases} - 0 &\mid M < 1 \\ - r(\lfloor\frac{M}{2}\rfloor) &\mid M \bmod 2 = 0 \\ - r(\lfloor\frac{M}{2}\rfloor) + 1 &\mid M \bmod 2 \neq 0 \\ - \end{cases} - \end{equation*} + The \emph{filter rank} is a unitless quantity that defines in relative terms how selective the filter is, + proportional to the likelihood that the filter will reject a random CAN ID. + In the context of hardware filtering, this quantity is conveniently representable as the number of bits set in + the filter mask parameter, also known as \emph{population count}. - Having the low-level operations defined, we can proceed to define the whole algorithm. + Having the low-level operations defined, we can define the whole algorithm. First, construct the initial set of CAN acceptance filter configurations according to the requirements of the application. Then, as long as the number of configurations in the set exceeds the number of available - hardware acceptance filters, repeat the following: + hardware acceptance filters, repeat: \begin{enumerate} \item Find the pair $A$, $B$ of configurations in the set for which $r(m_M(R_A, R_B, M_A, M_B))$ is maximized. \item Remove $A$ and $B$ from the set of configurations. \item Add a new configuration $X$ to the set of configurations, where $M_X = m_M(R_A, R_B, M_A, M_B)$, and $R_X = m_R(R_A, R_B, M_A, M_B)$. \end{enumerate} - - The algorithm reduces the number of filter configurations by one at each iteration, - until the number of available hardware filters is sufficient to accommodate the whole set of configurations. \end{remark} diff --git a/specification/transport/transport.tex b/specification/transport/transport.tex index d9eca597..f14bc13e 100644 --- a/specification/transport/transport.tex +++ b/specification/transport/transport.tex @@ -11,35 +11,30 @@ \section{Function} \begin{description} \item[Multicast] Delivery to the group of subscribers on a \emph{subject}, identified by a numerical \emph{subject-ID}\footnote{In IP networks this analogous to multicast groups.}. - \item[Unicast] Delivery to a single \emph{remote node}, identified by its \emph{node-ID}. + \item[Unicast] Delivery to a single \emph{remote node}, identified by its numerical \emph{node-ID}. \end{description} Messages may be of arbitrary size not larger than 4 gibibytes. A transport shall perform segmentation and reassembly transparently to the higher layers, so that a received message is always delivered as a single unit. -A transport may offer redundant interfaces for fault tolerance, with transparent failover, -which is invisible to the higher layers. +A transport may offer redundant interfaces for fault tolerance, +with transparent failover invisible to the higher layers. Participant discovery is provided implicitly as a side effect of joining the broadcast subject defined in -section~\ref{sec:transport_subject_id_ranges}; no separate discovery protocol is required. +section~\ref{sec:session_subject_id_ranges}; no separate discovery protocol is required. \section{Definitions} \subsection{Transfer} -A \emph{transfer} is a singular act of data transmission from one Cyphal node to zero or more other Cyphal nodes -over the transport network. +A \emph{transfer} is a singular act of unicast or multicast data transmission from one Cyphal node +to zero or more other Cyphal nodes over the transport network. A transfer carries zero or more bytes of \emph{transfer payload} together with the associated \emph{transfer metadata}, which encodes the semantic and temporal properties of the carried payload. -The elements comprising the metadata are reviewed below. A transfer is manifested on the transport network as one or more \emph{transport frames}. A transport frame is an atomic entity carrying the entire transfer payload or a fraction thereof -with the associated transfer metadata -- -possibly extended with additional elements specific to the concrete transport -- -over the transport network. -The exact definition of a transport frame and the mapping of the abstract transport model onto it -are specific to concrete transports. +with the associated transfer metadata over the network. \subsection{Transfer payload}\label{sec:transport_transfer_payload} @@ -78,7 +73,7 @@ \subsection{Transfer priority}\label{sec:transport_transfer_priority} Concrete transports may support more than eight priority levels. Transmission of transport frames shall be ordered so that frames of higher priority are transmitted first. -It follows that higher-priority transfers may preempt transmission of lower-priority transfers. +It follows that higher-priority transfers may preempt transmission of multi-frame lower-priority transfers. Transmission of transport frames that share the same priority level should follow the order of their appearance in the transmission queue. @@ -169,30 +164,6 @@ \subsection{Redundancy} Specific examples include time synchronization algorithms or diagnostic probes. }. -\subsection{Subject-ID ranges}\label{sec:transport_subject_id_ranges} - -Subject-ID values range from zero up to a transport-specific maximum and are partitioned as follows: - -\begin{description} - \item[{$[0,\, 8191]$ -- pinned subjects}] Statically-assigned subject-IDs. - This range is the only one used by Cyphal/CAN v1.0 and is retained as the primary path for interoperation - with v1.0 nodes. - - \item[{$[8192,\, 8191{+}M]$ -- auto-allocated subjects}] Subject-IDs assigned dynamically by the session layer, - where $M$ is a transport-specific prime \emph{modulus} satisfying $M \bmod 4 = 3$. - - \item[Maximum value -- broadcast subject] Used by the session layer for low-rate broadcast traffic such as - topic allocation gossip and service discovery scouts. - - \item[Remaining values -- gossip shards] Subject-IDs strictly between $(8191{+}M)$ and the broadcast subject - are used as sharded subjects for background gossip propagation. -\end{description} - -On the broadcast subject and on the gossip shard subjects, the transport is permitted to relax two requirements: -(a) deduplication is not mandatory, and (b) support for messages exceeding a single transport frame is not required. -The session layer tolerates occasional duplication on these subjects and constrains its own protocol messages -to fit within a single transport frame. - % Please keep \clearpage in front of every section to enforce clear separation! \clearpage\input{transport/can/can.tex} \clearpage\input{transport/udp/udp.tex} diff --git a/specification/transport/udp/udp.tex b/specification/transport/udp/udp.tex index 92137780..69925db4 100644 --- a/specification/transport/udp/udp.tex +++ b/specification/transport/udp/udp.tex @@ -17,9 +17,15 @@ \subsection{Overview} The maximum transfer payload size is $2^{32}-1$ bytes. Implementations shall provide a UDP/IPv4 stack with IGMP v2 (or later) and passive ARP support. -A Cyphal/UDP node is identified on the network by a $64$-bit \emph{node UID}, which shall be globally unique. +A Cyphal/UDP node is identified on the network by a $64$-bit \emph{node UID}, which should be globally unique. The UID enables a node to freely change its IP address or migrate between network interfaces at runtime. -A common choice is an EUI-64 identifier, optionally drawn randomly per startup for short-lived software nodes. +A common choice is an EUI-64\footnote{% + Refer to ``Guidelines for Use of Extended Unique Identifier (EUI), Organizationally Unique Identifier (OUI), + and Company ID (CID)'', published by IEEE. +} identifier, optionally drawn randomly per startup for short-lived software nodes, +or hashed down from a larger unique identifier for hardware nodes\footnote{% + A random EUI-64 offers 62 bits of variability, resulting in a negligible collision risk. +}. \subsection{Datagram addressing}\label{sec:transport_udp_endpoints} @@ -60,7 +66,7 @@ \subsubsection{Unicast} \begin{remark} This convention allows Cyphal/UDP nodes to change their IP addresses and even migrate between network interfaces dynamically, provided they continue to publish on subjects - (section~\ref{sec:transport_subject_id_ranges}) so that their peers can rediscover them. + (section~\ref{sec:session_subject_id_ranges}) so that their peers can rediscover them. \end{remark} \subsection{QoS}\label{sec:transport_udp_qos} @@ -98,9 +104,8 @@ \subsection{QoS}\label{sec:transport_udp_qos} \subsection{Datagram payload format}\label{sec:transport_udp_payload} -Each UDP datagram payload begins with a fixed-size $32$-byte Cyphal/UDP header followed by the transport -frame payload, which is opaque to the transport. The header layout is shown in the following snippet in -pseudo-DSDL notation; multi-byte integers are little-endian. +Each UDP datagram payload begins with a fixed-size 32-byte Cyphal/UDP header followed by the transport +frame payload, which is opaque to the transport. The header layout is shown in the following snippet in DSDL notation. \begin{samepage} \begin{minted}{python} @@ -119,7 +124,7 @@ \subsection{Datagram payload format}\label{sec:transport_udp_payload} uint32 prefix_crc32c # crc32c(transfer_payload[0 : frame_payload_offset + frame_payload_size]) uint32 header_crc32c # crc32c(first 28 bytes of this header) -# Header size is 32 bytes; the transport frame payload follows and is opaque to the protocol. +@sealed # Header size is 32 bytes; the transport frame payload follows and is opaque to the protocol. \end{minted} \end{samepage} @@ -188,7 +193,8 @@ \subsection{Transfer-ID initialization}\label{sec:transport_udp_transfer_id} systems without a TRNG include: \begin{itemize} - \item A counter held in battery-backed memory or a \texttt{.noinit} RAM section that survives resets. + \item A counter held in battery-backed memory or a \texttt{.noinit} RAM section that survives resets + or brief periods of power loss (at least partially). \item A hash of uninitialized SRAM contents sampled at startup. \item Sampled ADC or clock noise. \item The current value of an RTC combined with a persistent counter. @@ -201,7 +207,6 @@ \subsection{Real-time and resource-constrained systems} Real-time or resource-constrained systems may implement Cyphal/UDP using a custom UDP/IP and IGMP stack with a reduced feature set; in particular, they may omit support for IP fragmentation and ICMP. - Networking equipment connected to such systems is \emph{recommended} to suppress ICMP emission, because: \begin{enumerate} From 61c9b8386fffd92ece05f28f7dff4972b9a397c4 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 22:27:37 +0300 Subject: [PATCH 12/14] Add models to the appendix --- specification/appendices/appendices.tex | 2 + specification/appendices/gossip_delays.tex | 184 ++++++++++++++++++ specification/appendices/gossip_model.tex | 155 +++++++++++++++ .../appendices/randomized_gossip_delays.png | Bin 0 -> 145547 bytes .../appendices/topicwise_gossip_load.png | Bin 0 -> 224863 bytes specification/session/gossip.tex | 15 +- specification/session/parameters.tex | 6 + 7 files changed, 357 insertions(+), 5 deletions(-) create mode 100644 specification/appendices/gossip_delays.tex create mode 100644 specification/appendices/gossip_model.tex create mode 100644 specification/appendices/randomized_gossip_delays.png create mode 100644 specification/appendices/topicwise_gossip_load.png diff --git a/specification/appendices/appendices.tex b/specification/appendices/appendices.tex index 7ed4b05e..d5564594 100644 --- a/specification/appendices/appendices.tex +++ b/specification/appendices/appendices.tex @@ -4,3 +4,5 @@ \input{appendices/core_tla.tex} \input{appendices/proof.tex} \input{appendices/rapidhash.tex} +\input{appendices/gossip_model.tex} +\input{appendices/gossip_delays.tex} diff --git a/specification/appendices/gossip_delays.tex b/specification/appendices/gossip_delays.tex new file mode 100644 index 00000000..d68616f8 --- /dev/null +++ b/specification/appendices/gossip_delays.tex @@ -0,0 +1,184 @@ +\chapter{Randomized gossip delays}\label{sec:appendix_gossip_delays} + +This appendix is a non-normative mean-field model that backs the +randomized-delay recommendations in section~\ref{sec:session_gossip_delays}. +It is reproduced from the \texttt{model/gossip\_propagation.ipynb} notebook +of the Cy reference implementation. + +When a node discovers a CRDT consensus issue -- a topic allocation collision +or a divergence -- it has to emit an urgent gossip to reveal the problem and +let the rest of the network catch up. Emitting the urgent gossip immediately +is not optimal: while doing so does not make the protocol incorrect, it +brings two issues. + +\begin{enumerate} + \item \textbf{Network load burstiness} when the same issue is observed + simultaneously by a large number of nodes. Inserting a small random + delay before each emission implicitly designates the node that drew the + smallest delay as the \emph{reporter}. Others will observe the emitted + urgent gossip and suppress their own copies, unless the observed gossip + is causally older than their local pending state (i.e., the observed + state loses arbitration against the local state because the local entry + saw more collisions or has an older log-age). + + \item \textbf{Topic allocation churn}. Multiple gossips emitted + near-simultaneously may not be causally equivalent, potentially causing + listeners to migrate topics multiple times, which wastes eviction + counter values and introduces additional transient network + disturbances. +\end{enumerate} + +The same considerations apply to \emph{startup} gossips -- the first gossip +emitted when a new topic is introduced on a local node and needs to be +synchronized with the network. + +\section{Model}\label{sec:appendix_gossip_delays_model} + +The model assumes the following: + +\begin{itemize} + \item Each contender draws its transmit time uniformly in $[0, W]$, + where $W$ is the configured maximum delay window. + \item A pending transmit is canceled if a causally dominating gossip is + heard at least $\delta$ before its timer fires, where $\delta$ is the + mean suppression latency (network propagation plus local processing on + both ends). + \item $\delta \ll W$. + \item No messages are lost. +\end{itemize} + +A suitable choice of $W$ balances two quantities: + +\begin{itemize} + \item the latency until the first useful gossip, which bounds how long + the network is expected to stay inconsistent; + \item the expected number of redundant or obsolete gossips that escape + suppression and contribute to table churn. +\end{itemize} + +\section{Urgent gossip delay}\label{sec:appendix_gossip_delays_urgent} + +Let $m_u > 0$ be the number of nodes that independently schedule the same +urgent repair (i.e., observe a local collision or divergence) and let $W_u$ +be the maximum urgent delay, configured identically across all nodes. Then +\begin{equation*} + \mathbb{E}\!\left[T_{\text{first,urgent}}\right] \approx \frac{W_u}{m_u + 1} +\end{equation*} +is the expected latency until the first urgent gossip reaches the network. +For $W_u \gg \delta$, the expected number of extra urgent gossips that +escape suppression after the first one is approximately +\begin{equation*} + \mathbb{E}\!\left[C_{\text{extra,urgent}}\right] \approx + \frac{(m_u - 1)\,\delta}{W_u} +\end{equation*} +so the total expected number of urgent emissions is +$1 + \mathbb{E}[C_{\text{extra,urgent}}]$. + +A simple cost function balances latency against extra gossips with a +weighting $\beta_u$ that maps an extra urgent emission into units of time: +\begin{equation*} + J_u(W_u) = \mathbb{E}[T_{\text{first,urgent}}] + + \beta_u\,\mathbb{E}[C_{\text{extra,urgent}}] +\end{equation*} +Differentiating with respect to $W_u$ and setting the derivative to zero +gives the minimizer +\begin{equation*} + W_u^\star \approx \sqrt{\beta_u\,(m_u^2 - 1)\,\delta} +\end{equation*} +It is not clear how $\beta_u$ should be defined in the general case, so a +more practical target is to bound the expected number of extra urgent +gossips to at most $\varepsilon_u$, which yields +\begin{equation*} + W_u \approx \frac{(m_u - 1)\,\delta_u}{\varepsilon_u} +\end{equation*} + +The number of collision observers $m_u$ is usually small because the +subject-ID space is populated sparsely by design -- the total topic count +is much smaller than the subject-ID modulus. Local processing contributes +single to tens of microseconds to $\delta$, so in practice $W_u$ is +dominated by the network propagation delay: networks with larger +propagation delay will see greater allocation churn unless $W_u$ is +increased accordingly. + +\section{Startup gossip delay}\label{sec:appendix_gossip_delays_startup} + +Let: + +\begin{description} + \item[$m_s$] the number of nodes that create and gossip the same topic + concurrently at startup (for example, several nodes that use the same + topic and are started near-simultaneously); + \item[$n_s$] the number of those nodes that already hold the causally + newest state for the topic. The best state is the one that wins CRDT + arbitration against all other states. At startup, log-age is initially + identical, so the tiebreaker is the eviction counter; nodes with more + local topics tend to hold better state because they are more likely to + have observed the relevant collisions; + \item[$W_s$] the maximum startup delay, configured identically across + all nodes. +\end{description} + +The expected time until the first gossip carrying the best startup state is +\begin{equation*} + \mathbb{E}\!\left[T_{\text{best,startup}}\right] \approx \frac{W_s}{n_s + 1} +\end{equation*} +For any stale contender, the probability of firing before the first best +gossip is approximately +\begin{equation*} + \mathbb{P}(\text{stale before best}) \approx \frac{1}{n_s + 1} +\end{equation*} +This term is independent of $W_s$: widening the window does not change the +ordering probabilities. After the first best gossip is sent, an additional +stale gossip can still escape during the suppression window $\delta$, giving +\begin{equation*} + \mathbb{P}(\text{unsuppressed stale after best}) \approx \frac{\delta}{W_s} + \quad\text{for } W_s \gg \delta +\end{equation*} +so the expected number of stale startup gossips is +\begin{equation*} + \mathbb{E}\!\left[C_{\text{stale,startup}}\right] \approx + (m_s - n_s)\left(\frac{1}{n_s + 1} + \frac{\delta}{W_s}\right) +\end{equation*} +The first term is unavoidable regardless of $W_s$ and is around $0.5$ in +practical networks that have few oracle nodes with extensive local +allocation tables. Returns on scaling the second term diminish quickly once +$W_s$ is sufficiently larger than $\delta$. + +\section{Numerical example and conclusions} + +Figure~\ref{fig:appendix_gossip_delays_plot} plots +$\mathbb{E}[C_{\text{extra,urgent}}]$ (solid) and +$\mathbb{E}[C_{\text{stale,startup}}]$ (dashed) as a function of the +configured window $W$, for a representative mean suppression latency +$\delta = 10$~ms and several values of the contender count $m$. Vertical +dotted lines mark $W = \delta$, $W = 2\delta$, and $W = 10\delta$. + +\begin{figure}[H] + \centering + \includegraphics[width=0.85\textwidth]{appendices/randomized_gossip_delays.png} + \caption{Expected extra urgent and stale startup gossips versus window + length for several contender counts $m$, at + $\delta = 10$~ms} + \label{fig:appendix_gossip_delays_plot} +\end{figure} + +The model produces the following guidance, which motivates the recommended +values in section~\ref{sec:session_parameters}: + +\begin{itemize} + \item $W_u$ should be on the order of $m_u \cdot \delta_{\max}$. Since + $m_u$ is typically small, choosing + $W_u \approx \delta_{\max}$ is usually sufficient. + \item The benefit of increasing $W_s$ saturates quickly because the + first term of $\mathbb{E}[C_{\text{stale,startup}}]$ is independent of + $W_s$. Sensible values fall in the range + $\delta_{\max} \lesssim W_s \lesssim 10\,\delta_{\max}$. +\end{itemize} + +\begin{remark} + An interesting direction for future refinement would be to downscale + $W_u$ and $W_s$ with the number of local collisions observed, so that + nodes with more extensive local allocation tables (oracle nodes) are + more likely to gossip first. This is out of scope for the present + specification. +\end{remark} diff --git a/specification/appendices/gossip_model.tex b/specification/appendices/gossip_model.tex new file mode 100644 index 00000000..b1650b7e --- /dev/null +++ b/specification/appendices/gossip_model.tex @@ -0,0 +1,155 @@ +\chapter{Topic-wise gossip scheduling model}\label{sec:appendix_gossip_model} + +This appendix is the analytical model that backs the sharded gossip design +sketched in section~\ref{sec:session_gossip}. It estimates the mean gossip +arrival rate observed at a node as a function of the total topic count, the +number of topics the node joins, and the mean sharing multiplicity of those +topics. It is non-normative and is reproduced from the +\texttt{model/gossip\_propagation.ipynb} notebook of the Cy reference +implementation. + +The session layer defines the gossip rate on a \emph{per-topic} basis. Each +topic induces its own network-wide gossip process with mean rate $r_t$, and +the deduplication rules in section~\ref{sec:session_gossip_suppression} keep +this rate approximately invariant with respect to the number of nodes sharing +the topic. As a consequence, the mean inter-gossip interval for any particular +topic is $1/r_t$ regardless of how many nodes hold that topic, which is an +essential trait that allows resource-constrained nodes to participate in large networks. + +The trade-off is that the \emph{per-node} gossip rate now scales linearly +with the number of topics the node holds, so the per-topic rate $r_t$ must be +kept low. + +\begin{remark} + For better consensus observability, the first gossip of a newly created + local topic is always broadcast, and implementations may elect to + tentatively raise the gossip rate for topics that are new, have recently + seen collisions or divergences, or are joined by a single node. These + refinements do not affect the model below and can be factored in + trivially. +\end{remark} + +\section{Notation} + +Let: + +\begin{description} + \item[$K$] the set of all non-pinned topics present on the network, with + $|K| = K_{\text{total}}$. + \item[$S_x \subseteq K$] the set of topics used by node $x$, with + $K_x = |S_x|$. + \item[$M_t$] the set of nodes using topic $t$, with $m_t = |M_t|$. + \item[$g(t)$] the gossip shard assigned to topic $t$, taken from + $\{1, 2, \ldots, G\}$ where $G$ is the number of gossip shards + (section~\ref{sec:session_subject_id_mapping}). + \item[$T_x$] the set of shards joined by node $x$, i.e. + $T_x = \{g(t) : t \in S_x\}$, with $U_x = |T_x|$. + \item[$r_t$] the mean network-wide gossip rate of topic $t$ after + deduplication. + \item[$b_t$] the fraction of gossip messages for topic $t$ that are sent + on the broadcast subject instead of the topic's shard. + \item[$\pi_{x,t}$] the probability that node $x$ is the emitter when + topic $t$ is gossiped. +\end{description} + +\section{Derivation} + +Under these definitions, the mean arrival rate at node $x$ is +\begin{equation*} + R_x = \sum_{t \in K} r_t\,(1 - \pi_{x,t}) + \left[ b_t + (1 - b_t)\,\mathbf{1}\{g(t) \in T_x\} \right] +\end{equation*} +where $\mathbf{1}\{\cdot\}$ is the indicator function. The first factor +accounts for the fact that node $x$ does not receive gossip it emits itself; +the bracketed term splits the remaining traffic between broadcast gossip +(received regardless of shard membership) and sharded gossip (received only +if node $x$ joins the shard). + +Assuming that the deduplication rules distribute transmission opportunities +fairly among the nodes sharing a topic, +\begin{equation*} + \pi_{x,t} \approx + \begin{cases} + \frac{1}{m_t}, & x \in M_t \\ + 0, & x \notin M_t + \end{cases} +\end{equation*} +Splitting the sum by whether the topic is used by node $x$ gives +\begin{equation*} + R_x \approx + \sum_{t \in S_x} r_t \left(1 - \frac{1}{m_t}\right) + + \sum_{t \notin S_x} r_t + \left[ b_t + (1 - b_t)\,\mathbf{1}\{g(t) \in T_x\} \right] +\end{equation*} +For topics that $x$ holds, the broadcast fraction $b_t$ does not appear +because node $x$ receives every remote gossip of its own topics anyway, on +whichever carrier. + +Assume random shard assignment and no correlation between unrelated topics; +then for a topic $t \notin S_x$, +\begin{equation*} + \mathbb{P}\bigl(g(t) \in T_x\bigr) \approx \frac{U_x}{G} +\end{equation*} +which yields +\begin{equation*} + \mathbb{E}[R_x] \approx + \sum_{t \in S_x} r_t \left(1 - \frac{1}{m_t}\right) + + \sum_{t \notin S_x} r_t + \left[ b_t + (1 - b_t)\,\frac{U_x}{G} \right] +\end{equation*} + +Specializing to a homogeneous network with $r_t = r$ and $b_t = b$: +\begin{equation*} + \mathbb{E}[R_x] \approx + r \sum_{t \in S_x}\left(1 - \frac{1}{m_t}\right) + + r\,(K_{\text{total}} - K_x) + \left[ b + (1 - b)\,\frac{U_x}{G} \right] +\end{equation*} +If the topics $x$ holds have approximately the same mean sharing multiplicity +$\bar m_x$, this simplifies to +\begin{equation*} + \mathbb{E}[R_x] \approx + r\,K_x\left(1 - \frac{1}{\bar m_x}\right) + + r\,(K_{\text{total}} - K_x) + \left[ b + (1 - b)\,\frac{U_x}{G} \right] +\end{equation*} +A higher $\bar m_x$ means node $x$ is more likely to hear its topics +gossiped by somebody else and suppress its own emissions accordingly; the +limit $\bar m_x = 1$ (only node $x$ holds the topic) removes the first term +entirely because no one else generates arrival load for those topics. + +If local shard collisions are rare, $U_x \approx K_x$ and the expression +reduces further to +\begin{equation*} + \mathbb{E}[R_x] \approx + r\,K_x\left(1 - \frac{1}{\bar m_x}\right) + + r\,(K_{\text{total}} - K_x) + \left[ b + (1 - b)\,\frac{K_x}{G} \right] +\end{equation*} +This final form is not directly dependent on the node count $N$; instead it +depends on the total topic count, the number of topics the local node joins, +and the mean sharing multiplicity of the node's own topics. + +\section{Numerical example} + +Figure~\ref{fig:appendix_gossip_model_topicwise} plots the arrival load at a +node as a function of the total topic count $K_{\text{total}}$ and the +number of joined topics $K_x$, with $b = 0.1$, $r = 0.2$~Hz, $G = 1984$, and +the limit case $\bar m_x \to \infty$ (the worst case for receive-only load, +where the local node never gets to suppress an emission of its own). + +\begin{figure}[H] + \centering + \includegraphics[width=\textwidth]{appendices/topicwise_gossip_load.png} + \caption{Topic-wise gossip arrival load model} + \label{fig:appendix_gossip_model_topicwise} +\end{figure} + +The heat map on the left shows the arrival rate as a function of the two +parameters; the middle and right panels slice it along constant $K_x$ and +constant $K_{\text{total}}$ respectively. +The key observation is that this design is compatible with resource-limited nodes because the network participation +load scales primarily with the number of topics that the node joins, +which is a parameter it controls and that is also small in resource-limited nodes, +as opposed to the total number of participants in the network. +This enables resource-limited nodes to participate in large networks. diff --git a/specification/appendices/randomized_gossip_delays.png b/specification/appendices/randomized_gossip_delays.png new file mode 100644 index 0000000000000000000000000000000000000000..d3f80eca1726090fd0a7715af1b6d2155980cd14 GIT binary patch literal 145547 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�`jAva*%ywKBo4e7*+QWK82z)N(Yv_pMNdH(mP z0G}ul?MFykH|G*?G0;(g`fIq4D)^ssMig+hCH`JPO+#}9bSkQY1GvVn8P1v_ca?4)F)hP*G9Q27K);EiE>N(6zS(B5f+bK)zt;oR@MJi3&OwmDgAH{Bg2w_j9tF*N z@P9#*No-9r)KTf+m%aOLX=fLR;92;oVO}}_qiYx#ee&YsO83|>=OoX5Ulos$8H*dAt9}h?2$Ho0q=7XpmV|Y^9FJV(sTfNrNi(3 zD}ZZXgWt30D~)(jb@fAK5I?&2fG8jX7-M*Ai38G-|KGNoP^kTox*-AMoym2Yf4?`C z_Elu23Ex1eff(DM#Uigr3j&6r_DH_^UqAP*hX{^5QRIf@`7SW$4(iO{D1l<3>J9 zBzq+fY#^fv0HfJ=9O$K{#&m02I(p-mcg&}AiUe@E@f zCqx(+c$z@e-}qa$pf)gFpwgUfk;`(>)C5G8_K;I5*O1x*Iz7E<>cs-&xDAM1T&m7c zcSCCy>JAs*6Iv$D=T+SLG=BbLVjU{;!`<@xPf}(FB$yT|o>*E+5-(OjR0QmW3uKV! zRHG-??fY!<17tE!Q$lkS{`Khnu1^m3`fY=y`m^WWrsjMrj;B~lY;4KPf7q#C=Vzz$ zQ#Hv??)?tK&v5zwn3!vQyojEREDe9iMqu`PVQpQ!BL9x;HU|e1Xf_so$M;`L zJk6g$t?0T)x&CXMuM%sov0pANJ$#IO+VRK#cH^2}ZoMyaTLZ_7i;LN1MF4C+f(ICB ziVFz9fHCL;wH~^eL{H!}ivM0Y$e)x^oYcxhAYTQO3EoB8N8HXKTDkzPMsyeGhd%`u z_wIYPKp!9ti9wtCeJc*={ zeE@Zai<_Gnr_+DDd`8;gDlUFz1-M|rUV}n3u`e`?E#2S0BiL~EGa6-ib5d$({*OdWGqEN7T_(FY_E}7l(0X43j4SCF*w>bEjNJA-@q)@Mli;Mfq zz{~S4G_(!suiJ124}_ao$#=-8A$Pjg_o}{Lz`(!&S-McaLYHUl&5n<3ZOFz#floZ0 zn14_{LYG5qjK?!TMa?#<-KnDDhUGv$>sl%kfK`YApncr{)~#XGzgI}F)OkY(M5L=A z-Clmm`*crh+HmT8Uux&8ACEltvOJWX%WL_?=9*q6b4 z&E$jl+iL><&`^7HfikD=?P zx*gi;S!_KbSMnpHqAug&zQ?f^yA5V-K!CR1u^C@toc}+)hB&x0~d&{!!bXYKSu$?_l4{w88FbI}t$RxJz zU3li|*MH>_})$EYM(E6$JIS%}h);4fZSHPU| zAmbdA6Og2MQJ*I#b<%O;q@7l5ZEV6j?=EZpzs!y0hvEL8KO^Gf17JlaeB9-b{NGbd zF$GyWR3|7X%7I2i@+N%4Fog*yr`&7nP@khyn+BSV5X3MbUa2?DH8tgmo=np1~~blw7EaL)@?@!yPFD;;xNOBE_g5?#NYTrc%o<2Y7{;mPw4>HrWFZ91_aYS zOQtInvvUPflN0xE#`*W9ieKcLMFXIswGC_i`sf03eosvkv|Z$U3WdaQ0R<-*OwY`u z9^LzUP}_fhEFAL{4n8I#(*Z$@dIbolxwX|-6ZDQB<;KrY5`uIfs>~1FzJ2@4UdhsE zdG@1cjmTaJ-`<06D>Rt+!D!LvNGEGsZpR7>uTF(K= z&SyK+E1F=)>dV%22^o3%ybF*~DXuByE$|((i={E7rO=Ji&2xJaBBy8ml5xiafSTft zqO5jtz~{?OSx$Dbo}Fhtt8Xl7x|G>|5r3WR)>$vxt#>f^WgL^Ib3qp+$st+O#lFK4 zksMo8cGEA7`|H;~d;iRBx!7GY1l&aWEZ8mSKYGvm>| z?%Nj|MV4zI8q5c6MB5aZe?udH6F?~_X^clkaFMMapZkolO zL?IN(%8m+AnGxBNP>75uL|IYxh-8$OJu5|JMz*X(L}(xx$;>KrJzhG`@9(;P_jMij z^~Zf2_i>zk&+$ETem?K_YdoKi^%Rz=^I1v3wjrW!{VY@8GErcu`nh(js71dD{sHni z2D%b`3l-Eq1CV5>#=x}SgSO|rewV!7F_Vp-&b;Jxl-~aAp!E*s6M+(e2I>1GxS48> zyuVzOo}YfCL9O+2k(G`1W!Uw%O}?9;1mvRz-#nsqMzo#%bE=8|y>rpVRH;Jq43e=0Pm_;&)8_7;-voVZ9!F#Z7e}Cw+frrS`Pls&% zRi3~8_>qp(C!eW7Er$BP*#&qg{~ZFGEET6u2{V~5u82h z+`6VWYuQ&Ff)e+@0TvuLM7u+BqM+I;Z)u^2a9j@O_)-|c24=ChPHC=v&s6nifBhozFvmgH=>5T&hL6IyWuXg!>u9}Pu(GjEm&3>otZgZ=HpD`$q&|*-aa4lYkI0r z@b~ShO{1bwG!dM1fj6S=ucQd?+tWXy)pONEEth_h?K0yzqZ?jo+PTUSmjz^5Tbed0D&cx4Et|!viX=IzyLj+FQJ? z2X3HN@Xec;pbmW{6Z)u(KV8&Fx}YrJyidij{L5pW*DuA4YP_b7df%C#vd*{eO3~u( zmn<*8d>>l3ZS!5N>SIgqGk^4y5;?NX>xdp3TS#sP?P-9GJ{EC%|x5Ec*}~XTC{aS|f2PaOLF{mdVC{tQTXQicofws8XBM_Xbup z4B@ll%b!m&H78HB=L)tKM&^g&yt{nYHSw;^zL0#$RW1n!>^^+|+15VYw^y(b+s3|v(`GL;nK-jr69s`Hp8}?s9|43x@6~0WcuGP6@g=T4v@TX7R6|5B!$+2j@~U6foR0 z#2TbKbKJgB`iQHShx$zS_kvT)d4@E5Eq<)pXS46QvsFV>U2S7o#8>Xw$lq(-AIxXD zHx|iMOADUy&08Fi%zRZCs?fHzw5?(5VR7%b*$zhqez5WeLf6BNMhXcdOK?s(yQJd3 zon9o#VnL1@g@Qbx61*u*P#?+P;os~<(g&yoB^RF~YuI-=LzA6chxoQYU|2+jk7}R< z5Yhn4S}zMO4x9!*>xG|~j6I#c;X3j;%~SWyQ4_m!p+fgwX>{1|4(s0T>YtQ%**`h4 zp6kKgfH%LTdPl20d(Err=>A!JN0iZ8u0L@?p*#U1lT+r|vLTKAJ2^S!d&kiykgUq9 zz;=K*pSG~@12uiR{izA!C!@QZ6vNmyz}E$r4bf&j`VEDPp~Sf13iNvDO}pk1WH~z8 z7zI`Ydf;}1pXItE?X}B+GK)wIqDm)mYS8n-tAZw~r}nIcg(;MOYc_090AqOC%xpPO zTT(uw2S@dVtQ5r)#(VQ2m@27sf%-hze9m14`cU{(c0YTR^m`qBgNrDE2OuOz1%pao zG4X>qg!n|D zCYMv>5o`x5A94=80Duq!4ge77y-9S_7xkO0~8iU?T&~@ z3I)_s7|8TQP`OW`nSS_iH*P9_f0|@}$*|zya&#u-tR4~H4*ng=^~`a`h57lC-f^8b zQ!tYmj)=cgx*6+c`|_AyfGg+Fxxy!a`dm%k)nhrr1W@#0xr%7`Sui zEFWx=B@1#EkPy8|hzT;9pvpfU!YrNXy&4J1ECQGb3Iz?DDK1@-jiy_r z^NIWvqHu+jv&#`7H0@AslNJSn?*y;`Brk}hRFa0h4=wjoka#4=2W|&HaK?hphoAI) zTl_pa5hQlt(uu#2Ym;2VLop?@)T)4a#v19|xo(S#PnwEaHv2C>q~7>oVDQ7{6LTl> z=sZ_epJJ)5+_#ls`cvM*_+C5DmmA!)-F#YT4yUPGrst}uD7vR!PfSb<-O2vTYpFKU zt?0ddq~gZSsMLbfD^1S|u82MLYcyfvfcq>hY9oplq%?yULnEM{KMlR#Qi2e%eiQv3 zi0VzijIiW;P`K-ZZ~|y8Y}L$ZX9SQ2QY}`J;S9nX=J_C8K`I`X5YoIK&vggwnx_zO z>Yv=&z~0}}a}DA%Vq@5;7($_tY7<_KtGot@pm!?Ol_i2~ksDPuVlgZ6kw1RBE98fbmE6$*-V$X#qG&SOrp_QYS2 zEM&nGB9P~-XW35+a4R4%uoPjBfuzy{OskTMc!=fjjS=BHC^p7@b}XQ>mXZ8eI7iSp z&7uarF!@c5AQ$AtWo6CZeF?7;NfaVMK|tZ<@CzRYru(OsO&kb0It^V8N+QU>MB$=B z==GZQ{C7@H;U_6VSk(5g9N>a^LJ~hZUE*B=h}R98aVgH1cFhGy42fU}Hr)!47xwsD z0ujLxLGp8v+jkYug1CP1^_B3<6+>ClpK{mqtBQu}gYaclOgQ)#wH-*0H${(LS zcW(Xp^XCQams1GygRD}oKS%X>CU8lU?Sp1CI_P*a4t~vtdIB*{C-F#dKAJ%10mGv_ z)Qlj&L(#s}Mz(Ivn7#y+EmF-^Q}|C6vtVaN^O1}L)Y-i_cU5xtfXO2-A7yM6PFe(n z2}SXcpMzV3U{8i1jc~JQwaX2FtF&xRT8lqb@5-RWfe9Kzi&c^hl z@mOvoJdM2iT>h`pj~zxs`3*GtY}}>xa_t>Bp1M8fNeC|L>JBynq-xsY|sv~lA`hdPk2D^dQeq{K*w1$90(@z%+fJqJH?8kqN7 zBadgNOP2x0*ZwxQX{X|aCnk8tB#nmW|}^9zQ-N*2ilN5UO_X!xP8Lld2+U4aFI|x0l%uE%0{DU0!{^ zORBW8Sz$zy9KXz-46GSWlvt%1S#qcP&`u?%raFcR@bh2XQVrb<6^TAm8P(5y@dDx3 zG{nw_9mV-q<*O{|HQSnX@%RU6d(>;kmN*)oD zjZao?y{o$+D#@mHdIk&Sh??3;O7iAx9F9AMg*8lsf_Ex~QYc_3mLne_d7L=IksVTs z8dg(7W5>+G)c815#T&k6HUdfl(MUoCQ7WqB>V87qgK~oV5;#XD;Ay@%or!~>T#w;6 z4A3Ax*pU@9I!!~tUaQ`9Uqv>%KO#ThC9n--l$2hk;o3M$o-~}M`4vt&I)AyppZXdN zJ067?21a(#kK{`?xs93CZGTZ<#C|GZqpswa@2gJj+$*3r=+o&IJ5t$v@@Ti!_drdV zMB0H(Yp7zmpyMF%>tDZ?R8^(uoQhMfTq-RH+e9CJ?->VlNZcZ@;?bdRrg$MXhw9+` zn76@qGx|H8&*v1+Hn*0SJ2hdyeuIv^+C0vI1r#~am> z!y7J8HIv=$(klV68I>BA@Zetb=1xnW7GnrdF=rY1gn=+ za)lvJkXYZe%@c(FJ|v!@54v|g8QokPe3OfvB#dt;)Wh!~Cp8MmU7_&_3Gmf{aI03m zuG;kE*ui?#RqZ+m;(Y5-ZqM;Is~qKZYOp z0$4%mb1cGktK1S(EZ?}Yjk@ZU<{#M#d5Ty4yednWbZGGlC(lbgi)Vj_NA8qnx@2g; zV&T_V;kSC5aQyg+ixr@h_Ff zQ;{1?oZ#cM+qfdm@^&(UpsdXwY`l7CPE8gm&4$?tK#YkikPL6Y(k_KevQWw$x*=3A zSFk`bqVQq^j5a z!FZ98liF`f;;FbiMVL_HwH@~HJ9?71e*JQDFU1Viy?LXED+gGsiIr90KZFL3fUnXyp&fD`5VETH|95;M-*9 z(VYqaq`>W^KCef@GRcRB*rOCdjaYn&)i_?Ce+&w;sN%%6vA*Q^i{#M>SM=yKo3{_~ z-1*b{{af(D;on(Z=Ou=8Z+^Z$b*0B6}ibNl&2=E-B^MeQL^n=V3-vABh*FZH*|N zvrh-4@?Iel%;ZMJ#W=fVF{Q2@tl>_4vT z5?>ME-jkF3$cXBW%}MidWKfWhRj@iB?1(Adb$U4c`_rFW{z@H|zkQ!9z;{w7+SP9K zU6EvvB6DoDlG03Q`$vy`dd~WR9nKS@rUla@%)kD$ZPyl3RTxffcL=??bVm$4#0(L? zZOc8e2D>V+=)$gwGR64Y4``Czfz?sGFk7S-{(F3mWk0nHsw56=UTz2M_I_1sWQU1_4p(*UPeT6tx$76spERI}*YuvW)WO z+c!UeG*mo8CeJZh2YGFd>3Ic?j@JB}^pXn6toe1B7&_gS?V-N5~;* zuKdlH4$&thAuA~aAV84KVN~tl!x5OAI40=L@6-#6JV?MG%zNJt(fo zG>Y`0ljNp>!j<2|1m(SMOF36$u8RXO{Bejuo}MalzV2qah<%0H40eHu6d z2~pX&U8a5)CuOc&5H_jhSRA-E*ozl`*^oYuO!`6nWRD|*G#tPuCkNS(=S(k|XZ-yu!le6k<1kca;HlbuwlJvDYDhJ$is{5hcd4 zp1Otx1xP|JAi$PV5_i~}mX_8FUO%4s)JLgR{XQgj90#jUME>TE{O9Gq`?luwZ>(TB zbm7P8%H4xEL_RYv4E>Y~yCeN{vgX*MFoUq?FU26A@}?E`eAAMfdSq0%1uu(ALu?pBFJkK@q2woHj_hH1CTU2pYD6j1kwjh`vT_g(*E7z* zmD1~aAyOVCqX;y~*jSs;5F=^fW{U#~e>73-P!S@+Ww-r828lF5^G^naAT8X7YWb=o z0+>rvh2BCFYW?fyozENNDtrC2e3exAJXPQ zbq7o*t=X8q;~xK_|Mn|~aI_$;(+6d-vH;9y8*$jxEeL!_Jmerd7o>=&IY7Q-J{C-4 zS#W&vBgyonAWC=0bHKjrV-C#6?p@-3q?3;qU72!@j!3DSY#*x!=9j=q8c=M znA`GsX7fm|OUAc?U>on4a3;meVI_fIHZu-N({U}^?A|snHRdw$%1^keRzh~Ek4O+A zcoBTh1XIDmm8PA#5pfcy&z$jrn5{clsJH|@Dl>{|+IyL)hOErnzX?aOioGCBsNtM2jL>RuDPs;9ZRN?87~ zva&bA!`XE$0Q~5Xc2;$XT#cWP4%|$jQXtJJPWT z;JK2+=GH{sEC~?f-(9@_GZ1i~MwS9x9)x=CD)v~i`WoMYfq4fW4X&4)%5OH6mzZ7D zEr}~~nOtROr(os-=g*z*l4)!I@)s1MT0}5Mn!em6C!0Nbrk5deFeKsy^Up2U_ne9h zJLV?E&dVL>W@ot&fBVxp*ZgWN16~Q&O9urn8n54jiE%ytw6?`(5QFzeh_+jHEB5c1Mo6#)j3ajZ0krn zeyYPeCGy+L)W_eO#W?A4>6#u2nX6hevA(Qo^|t)}(Bl0;HK$blvJQIa{{* zV^ZP%h3vG>KQ(y~vpIvg0|rG8OFidyU&rP{5}1bkn*RvyhLTvfTRBj(HF0}R=+k3x+b7Vh7^A22HD>Hqd! z{N?apvi{*q5+Iy@HMox$Hj1Cbd^8w1If#Hj7*ob3)~h`P=(>Vp!1^GF*7k1&ZhAVS zQ>S|Cv1U15mdnnEl$tD$sT!;|K3nTtSvRQ+;vkyPaU)MG^A3lTaNs11+Y`_PX zU0h6X5t8h5)WY`QNgxtjMfe}kz*H2JI^aO>h9ejM4gZBC z@(^HcJcgfA)V3iQJ^0G;fkPG+To~_X{^0mo;{d!Ep8FT?G0G6@(i-+vY0uow@)u;S ziJ4P4$R@AqQ{G5pcG0^@GW5~8j626GssAwK%}#S{`Do}f6Em6|{i@F0pLc6YT#=32 ze0z_QPS&G@#3L`x<1hpk=@S*jfWkWi0}w<1>S~}cu_fjcl3v_1g|tg=Bm`N@#8I1K zj$PNwHM>Lrtw95=JV}*4NRC}F_V#emfjP#}Mulh+a_*o;9Y^p?B4EQWlU8F)MtPV) z2zSWQ7j}3A@M4N>qix##GkvMkxB>LC;PS>PCI(Os=4faa`b$>OVl^X5EaMz>kT5wHS{Z5*O!%jUuVCt z;FR9?VdaO}AK%XJXLDn5G-x<{U&ZMneY*4h{rgKWGYZ&k!A9Q!s((e53ASKQ3O2^e z7F_7QmMOP`0W&6JyOf(+ndpLa-2Vb8>mzZUAF!3)6v13BeQC$HL%z!T0j*CTU-gvw z6C7dBoi+Q-l+%J+MIcNiqzoPBIB*sx`gmuQX)WGWO--8B;j|wAO%wW=8>^z?5He4G zP7e!Ab$z&XHaz-PN@Bos|LWd&o2ktGEt9m@Y*ro>a1?bc`zplsokw(OuaN(-r2ePO ze_v#ADP4liVV$G>e5F0BVv-TS#oL{(f_qokdHKIsO*w5*7xR?PU~sSSe|~c$(?7pi zujV=3{Kxs^2U26hUZDxR2OIeIl^4f(=(pHylGOX2_SA5^J8`KeApes9Lm^QqGv@V_ z%SuCR!Cj+%kzQ?1WYg`qlh^I2Z+X6+_llD{JUdvczDhz>4|W(hMM$Kfk-zLu%74x$ zY_pcbORz*m$&1Vm<~QHoXf?9BJ8LL44XmzW{H9c|xzb*&f z5!eD$rq7|Fcl}GE{3|f|ry~Ed!kx<1bvn{@q&?W|wLpEVrhLJr;YyM86_wS)VT{}N zvK=&D*fcmdC>gROOj$a5f*8{UmmZ8qTivs*??0WYTeu&6%g^!28}bUUz;gLR1a8E{ z@=kSSZLPjA^+Q)R^B|Kns~exb_Cd&ruZpaKmi>S81*M4*;0ARGG(ZA;xu9|#Z@Zri zXA^h(qR}X4+1spWal*k)^qc&f()zKOBMr&|f4;OJOkxAX?tuON3rk{UtzLTg8~+|7 z4DzD}%@OC7UIv$RhUB8_ok0s+H5B?#n8n8Hx6KVJ)9W_k7;jW8@Q>&?JNoP1^cr$t&AMg`?Hc zqII7wUMdU>YPi3ikMkZlgGOf~kRdWL(utM2Y5BkX6nU$ftCtq|4eF!^9?~rjb<~IQ z8(k)BU076Eg)XRSXVV z0gw-)IHUnEBTCx~%nV`k_Pb5<%X5Th%I)%@O(xF=)MYQ+c}o5pSqa$kmx{;n7@b#) zubY4pljyCX55;h~P#BP5{ZgAO{T>khckpn4g$F=M{-S(($Tuj5aLZL2vy^}N;4NATm=zQpOv+= z=!jYvP9Jg?gQ%<8?m2%IAbO4787~m`6u62hCCFv#hQXlIE5MjU7ei!8MS{1q8g#X z(Qw(B5+cH?kn|ZF+|PwZPOb9}b=^6)=Nk%Rhf~^akXMJHIFKKFL5lqe0*^h?&sv7g zISEAd4xZWCko;n^|wJG{EL6!ACE91H0hfe8I_lL7g?q~*c9 ztg9$9$nZKq3?w}hPRWPi?M{JzxqF7CYU9B4pOKdY87@!qA9EgiZ=&PJD=ESJ>hmJ& z%7kV2JU!SZ4H^?&1eX?<*g2H61>FqQFWL2r8elu%9fW7j(Pe8N2HY^&fhXLHetK6<_{GRk~We)V_6*8!R3(_UsW z*qjVq;r3y|2q{|nTk%PF95O@SjO(-_eFW!BTZW)H}?x=i169g!biduHpI z?_4{UJI(ztKXBBAYs%2Tn7O!UR=~oyQC88|?|n`YOb6%SKY~zhm$}k^6gMYe!T@bc zp`bV=nN+O?VgL6^mj<;Yz^BH(PTMi&VmJ4L;2na38xOFl4`te5O=q&iHu__LD}aK$ zK5V?c=;bBo&7Y!7B_=pn3!#WHkja&>W#|`N(vAO{ZI;}I%M9$3FQE)Bp+;kUr%McsK~)X)uNzBZZk?so*>j{ioGBD?7E`qn z=K5mMZ4kDq2uMxLud(yYyMC&3A!ya~2C?EF2v{R*RJ*<>*D~$`xqp|z|5@j9rMPAC z|4!$!%h6;Ur!Sx~VBYL*5au!m&Vj@o=*oZA96W-xRSufY`~gBuD&Q=-fP_e_acYuw zC1sMdduaGPB9;ga0-@!FMi}r(&>A`4A*mvh>C(FpZZJc{;~>nmhUNg5`1ty=^YgDK zBMV8=+}X2zzXk{3d{7! z$6y#|d(rfhR6HPH{)k%s0+`V-AEp=FGRw)pJA@Z>_w^+Q8wp#I*m$Bj=lS6RLn}#D zC%6;&r{q#<#9qO0R{<&G7>aU4<5N;7$t6CWEPJX_Ck=j*MA?#%gfsV;Rn88 z1%fS>fWkurF{Iz+1oJKZrxPD>A-(kYy#?ktd7Pnuc*cf5>;ZD1}w;4fP%HqF!tZ!s2fD%vumXUX+5`q!Z#sIP4;Nn^Z zR-GGH2pJ8>`|%hoNA4aTTz{^?t6fHcYhyJA zNQSHIMHJi$Z0u@OB3V#{kh=;Tb2RgD!D)S0Nov1lkbMR@9=I&#To>{0-{ovEoG@bConS zKrc$U`2PxEl9o+gn)JUP`17mr>R6`f%h3fbMT(b(*LC;{-7CIvV*2a5&GVxje=Z1r z+reli84yY;x{5Jyoc3Y&@7;rp1H)JeLWfPL!*zlk5pcwbiFqVsHa#lH2;NByj!MM0 zrg-7Cc0)@>1`4)0%H3;uOXiU%8@;@x1izIQ#{6Bo7UXKWUGm})?RYES?1hGghQ4pv z7)T!jJ&WoN+Ar97njOJkvD69IK{*WxC0t~&Y2SfC<5jJrh&o!*=OV`rIMwS4; zp$?s``_^sYKh$t1yXM3HDt}QP1YG72Hf8_kr;#fxcKV_&yTVXV&FF7)Y>wReR{*1P zx@wu(#U~$FPj0vEdq6HeF0lu3P2IsV+=9`9?X-DR1g^>`e5g^fIBJ)BuZ*QW__qJp z?1g$;@vYF*7p#k1dSsL%#vu_e+x0$?3xxu6k)CUH0kjcdjEJ#^vIxtOz)&>|p(&IOMHzhS1M%ic`8xR>RM-IaoM zwIyq3mxWjhQ$NngDVw?Vs=7{WPud zFc68TTMj>-;^bzJ{@UGrm0;>ir-j!7Ab?xuKwT6p1aNFsP09J0F}$Yy+FOVdfGqYSQiAamiu zg)gQNBpjK78Kvo>O}CJjcj@51w?V*64x>P@>j>=dmuDMm+#KIZb#FU*)nsh51pNkVU-b_w{oHJh_)`m zZQ@&pGv}$@M@eGPq!6JmEaL?_otg>`Y#S6*M322H{v7l8oTs*y7L+|0>YHQu&Q`XS zb$0&G!59xTY3Vr+1%|3@(|(^_!x>SK;pcKw^;ZV7j7qHLQtZLp`k$GtC;3bW5Cn*= ztW@<`1lN^Nz<%S}b+Kwo38uX`C| zpNy|fJE#QepH{Si`4Y5V2`15~zS5#xBv&lE z+T8d5E@7$aeJ7^-`{CN2y-kRyC)zQuVb-Y$S9;VQmeP9@PQVDwuX` zQj)kku3!W?loT0HhjFIE>$v&CHs-$=PwZ7eHlwG26whp>|0tmQn%e&B+@_E*w)o!H ze4+1;J=RQBW7V;Ll<-vQ;uRAaG{v(Au1F{;Ex|;wpmrgO@iiL0o$)5oyfM!69qXxXip>Z{Pq{qa<6D-mQoC-K;@8bfI-R$Moy}3e3G5?mgZ*zu za6_K~%}8C94fYyqE2XmWbxGAc4Smr?@W z0_1nER+|&@iIGmkOc6$C5vH820cHNxuNZ6XPT9J&n-Sktjy*W3*JDgoJ0cbq5>kPe zBc7usf?-}GS=lQ}LGOxZ7l9RYsNo*hI5q*@J{5X?t-J#~X_f)Us~E{(;p-K81|8O0 z{W6K`EQpRSh-(rhZ3U*XJ%?j*Ogm@LMlO{f{Udf%*E&+SDB|=Jw#@!A-#|9 zCC?iAhVxIs6vv1XwTVcA5M*QKg8vV5oJ8w3 z)6of|R=#eleiNk75|Gx8B-JjHRNMh&MSslAt*wLD<@_iVq_Ohu`+!srVMI`&f#rFW z3P54WseT$g4`wxoHUCglP_VMi6GFWbw%Z_Ttr>a|x0D^wMXK)f8Ch6)-C;zdB9%FQO^48F*@ujq+zJ zd%h8R(Mj<=#{8PYa^F@nj-N^0*g1KT<`?S^e7ENJKXSh3wT@snnxo)zz!fny%D{Tt5z(KnT+wg8f4>O?(Oj z*nvSPpZ$ucA)LttY~VEdtBXgB!hkauWwhmDjFHeVb_{ zMHo7_gILEwpey4L3dr2~2_-DqYm|~mUTtAS3*VD;q9&$G95Cd!pdi0}^ay3b->3uQ z)JV)WJaX;}Q*8quo^d-5w_k;+wFj*3e*2yk5QPPx&5uXf#20Zc*&G@H90*htBxrg8 zy6wRyF)Vyr)wR>c7l{>_PT6)(P_?WHmkqy&kky+D_!^7d)jQ|MgAH$2Cf(U zf&v@8Lutw?sy2(%+c559zXTK*5;;aQG51yR#J+bKku*cI$hrH{*XL{XE`=S?8O}?h z*=LBJnV2X;(1du-0)} zPhG&lMX(4W@gd`7Z>}KpAT$;v?gTGIMF7u^waq(`leA0#{z;@5+yvG5y-CrK?m-)} zo=m(#(pTX3|U7P_aX>4dl6&WuCwyd2@ES23A_jFYtf zfYw=M`L?sM?jR`8oE+V!;|F=^@BIa)A;jQSflZ0c) z$Op3Kes{vH45fY4NoT-wq(&#Ip2c~Wdk2N2D}-T?RzJAlgAZXNc$xJtwEOyOMQJ_N z55rDnu# zuK$;v(>xBH5y_;%QiEr31?rWnINzXRLEQN+GVPnVatO=>JOZKQClWiii(AuU@X%Gb zA>v_|K;`O%Y)^f1|?wK-Cw`1*txTz%jP9oE+R1kaqSKI8(#S@U_nq<_hR$kMi{YT z&Q9VU#}PjUKT9GSL1G3(uZ6?eiva0FKQQNDvUtVd!XZ5OGG_+k|$*6UY5$FAbgbs~d)!v8Z}(Ht*Ng z-1{G$wK7jj+&`J${Zg}?MskuS9m0{%b1+Io0_? z37*l0IRudXt%(~OsvLeat~Ha`D_6@KYxC=KrigJhETwWF4Dnbp7XSEK@IUOsu zT30q)o~ECiV0}Y&{K`Z9EDqc#Q4>3Sgbf^6H_I~K#NyXIe&82d@1cmBg=`Lvj_)kZ zeQ66g8MYAU!mX;lA|$h~AVY1kpkKjI_TB@!qS$)1`nkN3YX0-N_Y&Tx-z{;AamfnO zjm$i0Kr`qQZ|~%kj15F`&OU1*I7R8JB$vZi zhZ#_^1c<)eG31>Eap1VNiM1`7{{#|`NPOq2I53u2jmnKy;%AYSjF*6aJ$aNz_Umm6 z#gQddkfwc>V|=Ezk>ILRI%=I)&Pv&;cy*>;IoC!{3D!T z0EHC`CSm)k1#WQt$}p7~J)*49e`~2V*5=XT5U;)P*4|`3sSMBF=d$`#+FQBDWj*!p z_N-7?rMdr7IuU?NAiYZAbtr1Mrx=Rfu(~z@`H6P`5IW$#g~1q26z;uHSzl^RnERm>lq0fA zk?=QXf0kBPb5|R!khwfY!K|o;RdlNq`1natHKTL4*>?8x+@DQMw|-XWB#4F^UAiQa z{p*M{x7(|z=e#vRIlJ|X9K`Pm*;XOP9#>a}z$C}e$}K5d+V$H9xRc&WAn6>A$7X&S zJjV5ZR|(FfakK|x_%$FEM9Xpa-$&86uTkA!l(pT}?V)u?XrUCNv27lk_Sz%NjM4`V z+{2F4jTkr+SSw#FGbK*5lWpSFw?B!Y+N{#a^aVe`1c}vo)5^yKc>zx0iel z3_=k4D|E-ij!Tzj#8xXbs@&4x7>#gNUt0B#l;+s1M`K^Ql;xl8{~4*6bjHpu6v4BY zjAs3=?W2D6hqGs0Ccdqeqgij>)TdCvUE2`*Qm_cyindBN(~P{m*VI-fr22eWogt-{ zn&6>e_v+>2J2`!CwiKuHsijUm1~y8e7o7imn`N;bZ@P@w*c_Zof_z@1I-w#LW0RZ~WgM0%e(_*59wz|ES^dpjKvZ z$MXgC>AJi%t7=;+A~nU8vQo;KRX#+ji2pfVa@cibBvAcE7+dpya)~6kmkt-7xJ$dH z?BdHz%l2<}S5&uJQ36sv@8TexjEvRs11&s;-U3EBZxdz1bS59X@^};Ay>oV|>`BG0 z3M;9Mw^!?H$s3i#8^ssn*!_2%(CR#Ya)dQi()!&?HkU!(>l89o4yhCU^)Wwphfdt> zKWh;YQ|}n-|Je6&$AMic(Wg$nDOp(VGWP3gPA|1Uor(*-qa0V82zg>>e0B86Q(JcA zV{MN^yLo<-EoVohU3pGsr=|XLlMI(dq4~jQsWfe;4BO;H$?qWlHplmR(Npr|uSUId zk~=3R=VD!$Cbh(E>+TD@&&` z^zooHh;9?nye<6vm&W@`hU!d(qLi=u>Kgyt`9p);yknKO`;rde6b{IFHXA>YZ~4< zlUHOL@!vjt%87A{{e6yFcex((Upx=p@1v8sp%D68n*V+^yIAJkMiZbM(EZ!<)!DMF zwLXfz*5;=;W}X|d;WKsOz-qvZkg9 z#fjAH@&!0CHF)Sx{9=;4Xc?W8k7^Efu%n2Bt+^$%9h|BK3@q>i>J=h$=bvXJp2C%K zk?(`DZO7Hst1q)o8mI^Qq}+FL<BG_2%#H~DX>F(j@i!n9Doo3J-t`IQ%zn1_ zc>oNWbOj`e8hwV#;=BZ!ez@~*;96`#kfg<%d+DRMnN3ngDP;W$A7E9vc>2f37ZtmE zsRE7#Sj-fCT%RF%q_O7Hc8o$M^dFI8oG-+6xk`trq+r|kmK1*lf04z3%fEC-s=vBM z)SQi0@BO_+BC&y-hXp&@M8X+xOPk_e$(VtbJRS{*4szJ9tz1X9Wy{il(1{5$atV`19QDFM> zWtDP1ZEa0`2p-x2490-as2A_YeX8}i`)s#=jGh;x=pfxvBB#}mVfM3*-(F*s#SOnO zFy3dc*d@C+IE5}a9`o4q<(5 z^C!wD3bU=>?fu;GC{;}MB=rdf)58x}x_6G9R=jC0dWT-Ke&+~Bm;>O*Sv?vJHxYh ziy8K-a*#nil7pPr#|Czo%~s-RMo;R>7lr@_bn7sk2!;fbETuCkEiJ7(Nd@Uy14L28 z-+=gdRq;=UVJ1SoML^+}OrvdBy5L)Q4fBK%uu^|t>K3^4uiu_0^GfwPpo5Cj;DPmC zRa~gBdL0q+5;&5eC1}zVg6K(0NK^=3sG!qxm}qHl*kywF0AO+i{WkB3Y))uI=y|n# zK#4qqrvudWJEJxXf3rQ*hb=5(jVsO2@agmCNk_st5W@n$MZQ?uaUObrHi}bMkX@58 zK@1jwfy#IKcjprRR5rc@bf*_sdznA;ju>1Lgbo&ohakT1qHlWmcq^vEV4R^U7QSx3 zJA8dzg_qQOnLd8}sL4YQ;{R;kz0}myp?ge;I8zCi#`!`R6D^K8=EDec+Qw#G9A9sh z3Lk&-SnKgq>DQYdL|qNVoQNk(V*G8`ICz-t%zY684d8xbPJXj%|M*aIDq@dUZ_IX z09)#cn|5b}^q(Hu88eC=3ypLPz(X?b6%cTG^dOR|06UnY+&9d3@WmW8yo_43?eD$Hr&a%)(L+Lrf)JZ;qN6JVbisrM4&-(B++_Xskq>CHaju$U`v7F5!ys~W$FPSbp@jzm#@pF1 zyOsPfh|F&9JI{uNy^D`82#6Ba{uV%fuMhs2U(V@tQU`bgBqAjM+;sZg5vLDn<__IH zzt_wlAEC>q5}OV;+@JH+DC4qzY)I||4cf|I!$Va5SjSp!p~mh=Gf z&V|9qVVrj#Km|SN4Mqv_YvG41iz{|-r_vusZdLVVp9_xCqS6XV_&F%O>#?08fd5L z>=u>!grD40?BS-ns|-pWVqLd~@a3*QPy2gruIZJhXAnJa8FX|KNb&){T8YVF?sF3;2}`i%w6E9n z^!HZ-4$>XP|M{4Y24j`J0vWV{Xv_XKI*vL5$u(6f9z@LCwFpcpo=*HcmVe z7g3JL8RdM3zPwALY*@{EfESY6gkI3_Hldz7{Ymb+W#o0=4MufN=IyV|j~j0vp<3UJc-g3wIi@V(7pEN-YQ%V(e9}`>=vbyc&%_Iw7ix5-=Ny|1;QYmh z#xb7PWn3~kP4%m8{cexyx9QiSSBhv_PJd|IeSFc4F#)43m_Ir@gA3k+;WQMB`rD;I z^L0Mb9M;MKye0~)oMd0afl`ZC+_Eai_H7&$CYrea{*lWfC3CshInH+z6*pX4}@Dl z=I5~9=K$2}PT~+gc6HfGozaNZcE@6NnoG1kN3DX>8IL{{GYc{n_F6UjV0rF=W)`(B z(Q{!Kav!9!c#HZku0XXTvS*JO^vGlr85Ud&gkI26;QTQ}kc;9NHMC7cg~Q~$dsUjQ zDhX;E9SuXyBcK^`czowE21ef?WpTTdxy|FB1>P#8b7}n!^a~!lR(`gtCgIe<^kgb0 z#2!Q?&c?#?aRb9|ij7(;?@Y_qT* zYiy0$(DD3O1K-NJ+qZ^2pT>^#6@C|rd|RMc$(&Wx>3n5&X7#jmyOQSj1XZj33V#6< z?{U6w40d-#*%&3CjF0qQmy=n2-EI<(HKPq+CRs`I$iBpZ3!G#@CyJp4#|Fu*0_)Qa z8W5nacSOs*#NJd zN4l)uGHhXDJr7>Unm?GBq;>T;_lS#|LyQb;OZek^PH-ekLB50)RZ^-JD^#@oTr7U+^h~M14>sL1dY3r*ZeroouiypivZH^C1E|Or}9uH__kN2nhj(3R|47#)PLGo!>$C zje(JZps3 z(kE0``{OPU^L&Ld?s1^itTLZy@Q`X1U{D|OTbV|VwvqbEZRVKc15}+1d^#nKjWpm= zvEgrhb1<)sEUl^{{js-{j2D!3P1wxmp4`gr(}vyfonZg zI-9t0PfN#yqNBgp_iw*^!RxNcKBnVtEL&2TJOj5#m>nU&h6AuN4r$)%M*$%rEl2$@ zcdNCX>Xhe#IDR=rzh6g9t>RdK2nRCa27X`bg+?ST!~(bcCNn10T)1g56YF~tXOJUs zOgOZfjReixbo_V{dyAZZ+!=DRFbZjf9_u6ZI=mwaq@7WHzc0go1;KLi2Q8jwS*uxC zZW;(CKPuU?0=mJBb5^|iPj^tjXEi=g5aQq{`w~iX)4;he$?144b?{G_PEbLjkItlN z#4jU}KmrEODL2^yJafx9FiAb5t;YL&D!2K{uZ1-4w{2#+xQ!%zkj$jg(!;Hl+7^&R z#|_a4C2s1_e&Y$yJ7pn;U{`)=E`i_nZ`BwTa$IPb0)7IA>g)b`$$i0nZPD+}pFe2l z`?2{i?7|ohh&J7ro95;tn-fgsVge`y)GmDjyV5$c^@Y_tKh&fBmIq^$B|;TlK~h=0 zf5e1k_vUH#vh{yzhC@PU@YOQften&%wG=1CjkucKmK}@^`^^}{z!u#dzC)({3X4bN%G#`{&S9q~eNw3JwPFqI6 zbG)b0@ccn;t&Nt_ILBn>$IAI1W0Kx|yH6l?)2a@6CD}zI9`iwWL>F)F)QF2~?tv7; z_Z1bK<&(}o2Q*vb%79e#J!_O96ka5sF13w~P3yaEEZVbR+tPDlw+*stA4}O)H*k4R z=PA8gYQ=hJh{$ZrjA-QtcGlK+EP}70v^~pvQc+ZL`s=+IqgXNCM2FkPZyuc!DsqvK z$ho)pfIZ!TkDEbyh$3sfNLZx*a9v%0y?$uJ4TqFl$tOxJ_(hH6 zFUo3dzq7V>g`Uxg$}#Hl7dh%`@7`Z|KAuk!WD)0x4ALf*HmW-p_RS0Sd%t<87JM|M zH}~i(`=6zn#gE2!RZeVur(N)}T))cP7cHH8a=z<36)};pEX+?O-5*QqcbT{iAq9dh(xI* zBb%%w*`dtrmZm+klf5?~l*-HwQK+mCvMT#|eRQ4Iec#9ZJjZiC$MO7iomYjQ@Aos_ z@7H=C=|AxX<&R3O!OTtNC9b=7?y#XyD5{OW)`SZ$BfqQ9!Ur^H3_FcRQl=EQu4d)* z*ur$1{H&P6@$sMyaxxt9n*BTm{Ss<5vWA1qX)dvc94^&>T#3CF=<{+k;FHda>(R;2 zxe}y++9~Du-dh%kSV4Ucf6#pdw#fu>R<|+znLj2`OjsVRRa2{T4|hwNcb1-;e<)Y= zf=SMoAAQlg_V!>9pGKvbkpTrq8tzDLxqkSIpJ{iVdON*~;nDfMqb;@meh!|z0U$4` z9+KM_7aPE?)qX|%8tCBa`2&t~PwzT|bP~s|8XHZ`jckizbWsUX+shTI9v!00wtx>Z1MKQxR zab3IE)FvAn8x<-)!-t>R4Au0UV@0p(UJ^5^u)20XfIo)wv>DaA5#Qk9#gP~7eaSv+}~qzu8JkfEUCcgT6@-CyaNCbpTdj#an3$uXpp)$&J=7O1*GvK?Z?~f*RPB%%36Hxr8=$dEZYj*`@ndQG z4ix@Ls%)mop8?QZGG{_{qZStBd<~eGsvIUAp3hrO-D?SfNEQM;f!`p&^^~f{bUNA* zYY0j5%;P^>Ak^j$5CjGGK$t`xE-#L|nJn$iW>3?1w0EsBPr%J|h>PojxvQP^RaH%g zW|5^n7o!=?kPe5cdf^KeC!kW>1FG>ug`*UP(IyLixRsP-l051P(wGp(A%^lCW;Le@ ztmm9yRme?N%EEb_G{*nw)2FG|8<;ZPmR*6JNh>HQgqAF#$PfJaGdA53#AEiNP`I-W znxWdsL7SRWr2^HL#ciCpEJ;_e@($$)8 znzlnekQBH8M4UKs(}ZO4<$cRpANBQ8O+?bNY=3pwA=4>&@e^{~8JC><=jhO62PV9= zdiAvCZZyyTdUOidR@m)v`YjpWm>Mvs7On8*$B*}@>vjQU2M+`AFvnraFz-}KPd}ov zAxuK6PneCu`e`6`)4AP|N_f`Bq$OiXgqng&Dnn)~S)@E^%&h0B-> zk4_;Gof}zxsO2CpVr`{*hs!PHQcsziZrYC$r~2m5;K?9YLG*1Ba6Vx90ggar) znL?L5wrBV5M2#F)96tuQCrP|n@>q~xgs}B}i~~s?o$p#Snz@;lnu^#h9u)eoP;R4y z*AcKY&xVRvZ0`I1ya&ELT}J4_krLI9j)P#p_+XP?A27wwoj5QxHMM`=zFW|<#D*P7 z8JzCw$JLRQnYm9;P)(JqxpozjLKFZ`QoSNVCVTsaX4f3QLotg3(|?-7qT0litnBRv zah4ncorI4x2^u@G4vE?%soRvsJ7MOxKVygi{4R$evyriP0|2O+a($1Gg?4ByR78VF zb&SF$LiYMAK#{FtGN(_U4&gJ{Oo48UKz70+6@9%`!-Ki`Vhwppdc4jzJ6$x~fK_bg zyvXy$7Q@&H>>?&6hI^b+Jltv+aU-O6UUT{Nopq)HA2nvgyc)UC98*vx@|}dVK=LCM z*8}Z%!j#9?IHA*{xV&9Hnl(i|cJHSm3w^JK-T7S|@q*keTz4$x-Dz!_{xu^S7;Xo+!b}+1@uQDNDM@c z#lp$yfw8_nfxRmr9T^%kV0aKcZiPh+Tjj;|ud8vSK7-^HaYMlvT&g1uo=NmNLde)gNlC)sq0j>2Ij8z$%pfeqw*r_i^*a9tDu$a)+=+;dEQ z2Ik2%MP+&$pgx*`1i&u1#R$req)j4e14V>HV{KdALM%?OF#GF9k!}7MS3owBl%O~S zU}}z>t!+R>$k1{?vzG+E{3483%*Z|FCTaI~(2XfAMx)fg2Mvsy@ef-@V98G$8fKzB z*mI~p;s7(js2BuIOuXg*dfVi70$R9CS$2T~knzh>? zlui5XiAKmD(D0g{9Y#@cvnum=?eFnK$TuRwNyu2Ap&2)=B~i_QX}zHTX-o@qEJy)Q zCG(s4s|TXCpFoC_1=bKF{c7;_se(P2RSPO%3S&#tR4#h#7WM=BkOL$L68@p~jHu29 zfWA4H28&BL&UX0u06_Baz@Zv|_Asr*yqN&(@)U45!+xcpJHk&m1);gJ0vwKI z^mB4jI!~L=oO;JHUO)>TFDc)Xx8&~PHzal($EfVHuZw$``wn9DEx>z*px;QyX+XRX zL`HQACQ!oAY}&G=MazBNfxPEcKSTt;i0AzP|3T0`@iHw%7`EI-nS zisY1<#FanC31CXM1IrT7Gg|kcueX@cI0ElhOufiU=KDgK*c$rAMA~Yn94X#xePwIG z;i+UWiQc@GmzUSMPpo=>X2PNVh45du!COkuZ$@KBxMuC1!eo}g18WjKYWepO?qw{3%KSQac{o?v< zoCA^QFHr^D0g((wOb?b&cvjDt^^0)e*r-FjOv-Gi(+(Ux8bCCZ=H`dB@;UK2jF9Z4 z!JyRv5*Wo{HpX2*f&LDJ@y(gx73LLsLfNN0%&O}Wvy|ABy$qip#5%)z0vq(fVtPcT zru1|62pAK%l%A=u!#_8GiwNFN4S`T6&199Na{LhJ@YW)sulf6fA*@!_G(j++u}!?< z_{4YstNcPoO&uG}j=dtFEP;?d!w@boFbIBZsb++=C@ruk-)dtAx-rA z9cCTOv0G`6hxb2mPLJ!3%+et`(HMs5V}-{#qIZQdB>r;o3vLJz^EL--P15C!jEwTS z6`S?h${u)F;YI|CvWo(|k4ck+^^s898w;JC4+JqKw*Qo})ns8p+I%x>2)CaT;HhW8 z?S&aQBp68!r&7}UpNINSekN9bIr|$oR{5E}^A8*70!q~te->5hqsd4JWHOOkNHpH#Y=ys0CDDBBFh8_MTSVbq^yd9$9uNn` z_y+@i{k8~75E7$Dz!7Y9dJ+})pGauhjfhVQ5M@U-HJI;l4gJL%5T6o`?J#o1UW6Zm zA6orcAz@J&*-!1q<%<7W>u1^tuSM+0LqZ|9O=mRgjxPSi^K~JFh|Xy+YG|+!&&ght zelOQN#&VR4mwNnM_xES3GJ#E^@qB~L5IkmI1B40T?J~y3%@@ru4!OzTXqpIcQVKvZ z?bh3vtu8>4ra?zb`vyJjd5q$;58zCKR#w+}VjTrYKR_S>xc9z-PK2Gv7)&nyaAkmX zvxf>qd67gp%&@~OQP8~0)7C0S<23fU1gyPBJG)|-GX>?#-i>Cb)IPm_ZFQ7`Tg8*a zog%UZekin`1X}Sh-@^3|+sYf<1El0Rd3!avUckS-{xY9W z0{AzfmdCk6xEtJK@tTH66d0m@LDJCZb5;dz_&J{AraEqUzw7PqLT;+Zf{psgFNyx) zq*Bk<@w>osXQzgvD0WkeddU0GC~GG)X33r#Qu6NA)?xa>a`r zo8ToOX0L{9{t-g+qobpdV$h86NIV~;QtX-Dql|^p_!t^rHWOvPbN?(;DHY7knWDhx z*cL_K@WCy9TZV_aoD<#D$6EJghis>eeBtj zhe=@KlGk_Ip+m8mMF=MOn@CXy31l@8Wf1nd5;oyh7I}UFUW3blx1pD0a+>;LJDgx} zPs%&w(XJR%ujkJUUaL&u&s$v7?VDJEtF3dYxF||z2N?0aD{z-SdiZc3 z*d*M7nP%-=AfLDc=_KgcB62fH*afb8vWY_r4C@#uP}5YxT*t%DzaNrgR4%xO-{22T zdP+;Uu52csYC}=*czt>8mPZdg73AhWEnCJ5M8;RDlz+^%H<4dtnVJ3C!*p!-&5P!j zu4*61sjvHX^3^I)Mkl43{D4R$@*T<*N=&5{9&(=+~6koB- zGbtV${bn#NbK+nPBc5nABwOfdGD>5K(qZt?3 zf4`BP(0x<)^d3EtUsax|7{`yrXwE2zG=`5quFpH%bzi5;z9~(+KxOL6wkv=7%eg5# zg&uz5wj5!OaDCYTuEkfV%9c1(w`xTDZ+?QxZq0IDu9O|o-q!?k%lopN{V)Jm;}AfrK>Eh9E( zM6f-NV&CE_+Blp$9ufb_WJPAj({Y~Q%XdJ^0A3CC1Jz$ZV8cptuQ`YnN3oqda_*4` zo{0o~B)fa^7tIf<=A8`70w&b)tbD-v<-n2?Xxe^zNv&mMR0gAK-7s~^jVxP5xq__B zuVMFlzH>jNK9573{`kK-WE&;DiRwd9kXFf@IkOg^Yy|=U2%ZVCX5di{PLgs1wG__k zzrX)RDtc|(%+C4a+~e1Ob!eQ3`^P~J0_JO@MS+R}l6uKJ{ZHm}6^^;-xm0dj*?N#X zFykO(Q%j|Xqj&oe&Fzp_&5=S1-S%0)(?{IKyHAk2;3TdL+kX_vOJ`{OTv*si`_tz` z4S4aaqj`P3MNBy{pS&oG8K9aK2*G1dFgumP#Y%>?=y@Diwm`zt2P-ceJSHGKj|oI=!_cc3@kF} z5>!$zD)!uN(@{cxOPbKIp?LX z;#&A~3%CBI)jT7gIqiBr)p=C#CtlV))lhA90d-3GM_?B#n?moicPRP&%fDO<(P%1G z!cKP@8)ZJqt|I^F?fhHpsnFXqlvuA5#f*t zq%R19oCG*Aa{RI4VfD=2IyH8$M@oY`r*9gF)-D9y`0{!cjzUTQZ*tQCJVr#u{Xebw zw9K23T0&_14d#w-=k!4ZG{@5k8j2bE>DD2dI*c&zhH2D;_MHqSIk)oz3D7;NoX5SC#l5G(n9D9h=7cVzlotC>EBj9$(!}6 zdtz6NKEFQq#3UU3=F;-wrhdMC7pbv%lw(~bjm8&CY(BMm?y5bO=3u-)Ub?CO=<41tRI`jfruSD62|0WU95F69QDOF=LVtUpZMArPf^-_#= zeJkEEq{wNxxa57_y+g#mfs=`;eeo%b2MWd4M^&bDD_ioB4HA%QtG(>q7~BomaL~b$lpd_5@(&-5E}mp%B`7&0t7xvE z<$A)!LT*`!El~YSoSlDc+M=edbz3RvgEC8R7VrvKnO`%xEkuvAIox?vevgL3waUV~ z3zv74Y3a6uc@d@~Vi$M(du%>R?fn0$b|)it-6d{6dGe$*hCU4`HHZzr;xqVjg}>Yk zm=Ea?ZT7qM&OmMp_hGNw@;p;_cekk9QaE-f=3Ns=%xz&}11zCmQ!+~ZmVK2Q&%3|F zzNLUw{AN)5F5W9^DUb)QSxqY9jibtJ+bL7TWX(HU8&m#>{cgoCiXkTFu>Nq?%z61G z9Mcr$t2C#6B7V`sdX-V@bZOw6Qb%<5UVj7Gf49d~64wY7$|0mbQ{eo(4@~zuSYk3W z68E{ri|fqTG$0J`LFPo%YK!-GNIbL=b(z~vGt;@L@AHv>c!E90W9LtLV)_|JCK=ebbX|qQamXf!CH-RsaOs?&l-8)~B(;KQs1|bVsT>emqk)djY{*yPM zdVsxyv40>J2GGD0xItz_6jg6tI@#zR!6-)Wrce*K4K63FTrN;UJoh_&@B}OWHN+yU zA`q+N1wZ?KDEMD=_R?$UAArNmgo`$qzVdAh?T|*+p){Gi3p3vL)204%Q9ts4LJN$kV8l6B#thYk?HW5r>sMO{If{87C`{;Ah+G$ZJTgWfq0xvh z5@v!j8-i9D2nF%#;uWGLY)0BV!7rK|9K=f^R0&$4clGtuI0;D$4yhN$j*DYh0dG~L zvrA`8skh45pqVLs7-j1THH6D~MRw8WPF=;J5e$FBqr&VR0p zBiVd8&vgS`NxdsD3TK;G`aJK!%>j-2e9X-|x9@t(q`t(s``2_!%grB?moLufd8G1g zxUq==X3YlbpU~1k93R%Z z2%W8_?aRZM7(iU}n1R~>g03UaY$vMQx(Qbp4Jsl0g?dUQYzgB+V3#PzJw%VwivS1j zW%&y>c+TwR(#SqDbFY|L6L>9nNX6RU__vV#y#bbSvDoeiaDnIx*$5gT$^L{YZOsKd z2OB^G-ZhAwHTVb2xFO~jLNtMB1Ia`fJn$HB4P<&9a00XmAa@3_;tfEz zBrk0scG+92*4X^5WHs9|lve5|73b{~L{g%wTPH+S)?|%$kUuiUrRpHJ16w~zefjN3 zhv`3HRKEil8webUBo!rm73jT_;4g^{1$O}uzHpcovVc`pTqMhbpZF`D|-9--U1(iw}L}aQ4zX}2e^VT8|MukFPm~aCH_gfrq@-l{Jlg% zAHwI3BgRMEjWCl;kWQb1KoZHq&#-Zz9teSEh6vCB=@EG=&MN2S#oy4o5RMdx=3V!* zb4Ma_*jZWc17g~S$rPTRR9HcAI3zGB`ycobP`MCJ3kHezi<=r(qk3;0f#2r)$_90J zhx-L1w$RGlfK~z71(1g-hU%O-94JkJt0a7wg`AuldsTUKPdTc6ukI0AQj`01!)OCj z>)giU*2i&0$U;MK2$nSnC^ZW#(#DUC$2AF@Q|`<^YVAC*bvIqA;J8K~f9cPqJ91|Q zdEePSZmZPy1*yqn=ddX9>zkbHz?Qf-oy{K31193Cj5~Iv6v_MPsO^aFE0CJcZ%8S~ zI&i?yrPzJ*#R0a{YT#bZ&J;lks`*J?63VL%A^{~7Ua0)CJse+H?(nY}Z!bInK8QZl(gxKc)%2BT3U1P?P zO4_=!V3LR6RKZf{$20_zQmhY^wMudT?vX;_2Sg@t`~h+y2pIqaNHQNqa?mcA!PG}C z)x3M>jvt?*deOKAXgi?~0S(2VIy~82(gMte+~81z5M30oxn95qBGuNYS@6N0i!9-$ zRF0Nd2c3~DdR?HyXOdK`a899ANYHF=ZKVSw4gqQd^u_>5xY5uT*4tpF4;Zk`ukgh~ zh@|aYfIyZQFhIG*3_Efil$V!>B{3w05(wyp9J7q~geQa^l#W71M&_>BVl+3pUkZ$P zfaziBEBKDfQ0XCcbbnnx0s0XhMBp10ml)DvuvntI=3%t7IB+TCL%6UAbLfmv8-M=5 z#S@44MIj4f#at>z#xPL_{YrXVpq;@c^45a`3pZwImP#jtaJ=LhI7iJlJIPy;1@=~i zHmkuXs=m$YZ_m>|Gjg${Lzs!?OR+^O{pXJd>m1Ss9cXv#3X2(aojkQp%v_nb&Vh+b zxHjU8kVd!KEAI%a0A>!9XHZM~x2+AucxXvHS_aVF^T)<$OmzW>3?Vxlf4k4Rjr%(tez(@^Xr=qUB5Bfz+Yu+K4H|h;A|0Gp4 z`cUFsfXvztWIS$ITdPNE2wC#_m!qvN1vOA(9!${rC?d%Yol{*p=)&jqJ6zNQqQZ z{v0>gP<@IP3`1?!usdO>-jxh7mioj zIm#N`L_qFIc)Xxmp7UZ^(p@*dnbWM;V?T{IPrDJ9{QjuqI^yHTBh2n^+d)8s5|+@B zHi>_mk=R51PwB>~x2ACf1qH#_@c_L~HV8(rf#17|g)keNPnVt_0~Q4hP3{;<4TJB4JSA<%L4JNP=8{Xnv2>qK9N%3{ zbvs70n4FM*_t>PM^UgB+BXp`+1@dLBHzutPb8uL%Xk?FQ1>vC)DhRdB1JIC!#6lER zh?l?b=R7UDxYW!li~i5(@kx>6Gre^gV>UChvuo_`zm%TSUO|U01Qu|6hvkHI=9XD5 z$T?qm8M$p&JRvtPUAJ|^n>auu_zN#b;~a4&(|J7g;HcUDh)s%1@dLY zb?|-sb=;=8oCSVDbnq1CIL#*^ClfPruvU0hSqEfee$0_$2mzd|jXU&eFQs7rWbnhI&GU z`8tlM-?d0Esy*kKBk(<65Y>##%p4jZ$67rzvszRx!U$J`^n3sn79pF#zoBuRfK3OL z0dXV(D;YFqA|OqSw-^f|R8jh^ASQXH?&vc>UMJ7%LhPb&!X+Z2$E}7j8~g3ORr|4! z6SpH(D{fND$+avW=UrPw|M56C8QhF+9qkUH!QbITEJThqUI3|2QDg9-O9BHOVH*Ar zvR)JtIVBDD#rJAzUd`amCE%&z6d@8dsC`XP@8JaO&A=7TkSUF!>HE3-KFVU?#cZg zE5n9_7Yc}+{Nuz*AXWN~*%Z=oe5%Z3o1W?B!9vXYy~=X+F(xaqh`1hch!7yWnn3Qh z%ej9v6bhtt1Z_HBB~z!cnW+ob8Ni{&-%#JzW3+7OH2*(#DOyqzPIzAEM(`1>_`x&F zXndn_B$3Q?+-LMsMVsat#FrC~1h;OWLjyp4Admh0xa9Ezw!8cROYTrfQKKS5QU{G0 zLk+JD@*L@K*m}@GHi)PUUwnpWEho1GTifVkT{~(d1VFB9)LIu9%?^A8Ok^+0=np^v zZV|t6N|=D0!@?h4cs&R35b>Nd0jW}MP3UGiKBg3m;-wca;7f*(!QD!35Vq@pCYX%CUUqSbV!K$U*=g=ZgQ z!>$uL%Xr>oceFf(-i1zTSY!x4Te#axkiJ&fS=~yoN>mqp^{v>TgAn0LTZcdrjWahW z&gFr-d(z&u^0g54sAOmr)H}4>ID{bDWLV4^*GO{B0hDq9(gyi%e--Bkp^1bpDzVm^(?k{h&Z{WjMuo{;%yli|B-jh(*+<(BUId=zW3#`ru8zvm za-6b#;`eW|({WrMYM;k;c8#AuZu8w|*mhEG=qhg8HGUSdl4s7u7JEX0Nr+pv9Ua{fuhK`X(Hx|fs?7KuVBtDvCWetYz) zOTS;I%2v%B*7aK>LtLg>n`CeceWILBK>SQHMMg zcM~H@bx8b#$C{I%?u0io6kQlOM~2%o8jzPi1lS*|3cgJ;RF%Les4fcKmc?+(A`5{3 z+`LTCC+nx88w;560wM3j3=1QS#e0v2hscKT!BuUYbYrFdQB^0o$0XC9$n0R+eS+;k zEOW?Zz^C|%gQgF9HWW_>>NW@4<6hhXfCoKR<$oG)Xj)0*4R|90LSK@;f#X2ZtCfDw z3C93vK$<|i!Mk?`mU)5?;1VOC5H`m3iEn534T<;%e%*YO89g;=i)SYdDArTb6QU0Z z3YtYDi2F!Le(%II)F-5ZPYbOh;okv*)ijIXf}{{hn^`H5EJDJb@;|TGxI)hZh`&-UCYdDGm$0hIZ-98(%IJK0x&`QaS(Y!I9GfGz{DLF_M;rD90cD)2;7vli#)=X=M;qsA7{8|GjeszkM< zY3BV$kLm^BAqS$byxD4+UP0r=TWE&)fPCO3zQZ<99_=L-Dx9Q+MIGHS=V-9{-{Gx- zThd2UgJS$Zoq?RJED*u^iPG-C7VD3Q9la~^V0;d>6A5`l)a5f+s!gXb3%VL#sXDHX zwAHXMMnF|$wV)eLIkF78N*N!DQ}rUELc%uDvhqN$2du`&v=neAaZRhP3g9%5@kcFb zP$aGoPURtHr{7O=h6An~Z`C#kH?omwSuZe1gHHDd`~ZYgHd^t$N$}^2CKHWN_zNi4 zJVtAa44Q21*Mvpc*!v;PS!lCXQqRjj5Y@l{c+c)>$WuZ>{&M_bpyH`$k{^4MVK#tU z-nIIb%bE$Q=Fca6N!`Iq`hFbBWFQey8DbCWSz3DI7Dm&LODP!3^ahL?P|0DUih25P zz);o%?!;P@gEIB7ZmB2=E!bX^u~KAu>|J@z61i! zkcg|-u0<|vt@gM)?I4u>ch}oeD>Gn93i#_d;F`dO4E_YT!m3<&bAp8cMlh3utx`bw z-dSKtPTia?&kjIsga?6MkeFV{-{VYyhpP&b%OM=vMI!)FcHF|{MDm2LjkaGT$v31Z zz!-yONYe1M&O#nth0G>2gk+QmsulcZ_X7h1OM4%4DzMo$4mP8DCMJ6v%vcaV(U8YG zH9>qu2DhLh2T=w^Qn5#9(^{XruqB_*{FnWIvUn^_CZxKDrn-))U^ zRoQc$rURE0mMgF54|=L_sK~|4H??I|4~ABy8(Bt9%*XX~Si8v=)#hHeczwuLJUZN5 z_%KaJK=AVYf1QlqO`|3Y<4@qpE86DyeKsm>qy)&!aIE9~d(N);)8E92gs~-Sh%W^t zH59l=6FAWNkyfMbpFMlz;0QoFXzWe&A5zt~0nGCU!oftK_M@-lfnfqIF@uC)I#&E`z29Z(kp2HgjJ4 z&jbw_2K+;2SHO`%KxM+0At0n#M-6Cdj3gCaS$xV(^3{#|fwnsaja5$`uT^dFIw4Xq z8eEWMtgDD)%?L5PK$3P~3=eV`qQ{R($RrD^=^GRW?sl^AnB6%*OTRZ!Q1_9apWwk2 zF-hc#n4|zApG^lm%k(IZe7YwU@4sUQ1@;z+J-`YebFfk1go;DOZ3Mvq8NRY%!v>vq z1n<8WX?wVJ9V0(uo=I&wSBo;k;uDCZkX4A510`J}9~@a5;>0eWE7&7zevIyc^RmkZ zyDZyir`a)b-Ac-n7{O>QWG#R|=fj9=)Wj@WHf`mrUqL1d1RItjN+Q3)VM2yI!4mHa zCrdMqK16xg>{?M9tbf({p253$Z)#zIq`)WvP}=s&>?;1SW~$#7jsJlI2dZ9OAOFXi z)_P72)^jTzjnT4D)poUs8(rQ2S>$s>?^WLS@8J|TWEEMI=DxR87$!4`Pyc)5rH6O; z>1Z0L7f5<@)zXWt#HrrdsX4OC-D0S}j07(MEC2cPXJh3UdDVK)iUNl#@*jM9=<#H& zU(p+lv4Zf9Izz}H##|78b+Y+O?6*a~9#L_9GtW57xYF&poKhfpL;JsikMTB(Zl~9E z5;fQ9yz;zJqt^t`zTm7jZD$+cN+4PMxGF}nFgxlh(^H1ge|q{5u86pL~xk`L^nEUxaJciOa(=F1a*-NsKT zLWlN7oJ>*~7K~k4v5hZq-;rb^wX|mY?aVe8I=xALLHRSvR6d^>p?2DctyCv*jrP0N zhIHR7iuB*4cg(fq4~O(pO8ASJ$sM%ba$n8p)8!nGqZPHg z7$nV!dyA?q5Kn38w}XVT-s54Tp56T?v-yE(i(Plud4;P!k=r)uUF?2a`qf~%_UNDv zRY^_a=@|MO*VhK$d_LkZtY5`_HaWe3nF7kMCN)U?l9j=Ulb=D&%NJW_9xX`qJdxvm9MWNKll zr1v94){$^MD1xAYQ(4NZ%_Jq>Hr)Aubx^0whh)#mb#W@fa^? zK9yFtwv4I`OJE}n%^<2gGBOUDHI-{%IV`!eFkDAu!_~1lJsTZh-(y^p7U9=YJ-%N zY~xL8;4!TKF5d{7O#;Atp`<$uMhL#L=dcfkr1y@E)#Iv%x9!faHDp1Rf1%@0@Q9>y z%pPiEZOt{e&*yzUw-L~OeC5?nBG+3WokHT?- z=8AS=qds4Dh17ewrD9rpRPe4_54}0RL`5Vi_7CNGJ{j1B@JhgLV z*pqL+k411XsD(WCMo~yBP`m*229^%7WCB!BC~zpWb#!uGdSRLag=P24CTUi({OO~f zx1F8M{I+uTc2xTd$s+*o;mc}lVzI#a%!JClhC&gZPtwr!t7c~0@^tsKc!LdGHT8ca z)?yt#o{ZpGX!g#LN)GFd8ucq7?VuUxL;n#< zlW_>-7!>~-s@gVGuV{f3p50R8BPU4tG0#Na)iTO5t@BV5x?KuX+aY00jx~b!8Wlvm zw+)jqcu-?H^+25|J>e=I%w#gaFS3qEm=HXAg$S+!l>+K}3fYJR7DT&X+!9ZvnyjKX z-d!^1^&Y(y(Z#)Zf$-)g1|akhfvk6+mJdI68)WX=w`)Q1x>Wund4Pw<2g3fQ6m@Rb zxuHW|NcMxe%qyRcj_xeh8dJ;Jr%Z&zwdog93lcmkiisV z*c|<~Xy|E9V$4jg-+8i&Dyk2cDJQG#!1n*9_-Fr#gUw!eK$(yrLpC2UTh7wZV(-8s zz#A7Kb6n8A`~7p89g6KqT&-JF6I^}gOt_+ap1yjY!(X`DS)<^T`_}a`J?*@DyP;ts zGyH&gQanSKLV}B6cd`ONO3frjeA6cF>3i_&l6D;p$FNW?ZUmHW$(J~`Zzd9JgxFZ+ zra|eIgJc5=&HQTwZYD4};SGS;LdpeQ4txp>%Ws@TcG(o8#)A;*7SQ~^@Uyq4C!txI znWD&P(R>vBJ2_fxB#sgxM9?LjnjeNaGvTt7cpPA7C;pICJOPtvQ;ffUz5|QH zP1^bO?h?KOR_FHa)f*X2yH-*XD00D;UdeppSpC@3(5J)4vLK(Qud$V-PscXnbD6WD z2oDb@Q8sXPCk2@y;);-aSP#Us3%}}j5wc`q>ZF20w*ty9;5$Tk3XaAYV!`};YqX$H zf^0kq2P&CD5FdZOCx!8-@kx~AM5>5EqkT}}eZz2@x82>BdMjf%(0A}cML4KJGj=MRdd;U+q@eV)NWY&`8IBP&Z&*CB_WlM4)whmEt=?f`-<7Pwu7SI4 zcrosajI4;*Bd+7cnQ)GFSCq7>p20eiTnP$%%Cm|Bu`GbiTT)Y{l7KQ0)&Qu5HaTQj zJ^>AU3lIYEl3Y6bR>*R;+(Ls599#|IT-G)=eF!mK{RIdDMs{!xMpNCw2@YHgvS}LW zI6rVkE6JKg&Z{?W-1r4o72%6;tY;x45WfkD+XG0{a+Z7`3HlJd)(3h9hH;Kj&txx8 z^uKeIjn%-~y$V8~1m5C!qP&INhp2r_3%Y+2LYLUS{UaW^?AxZLIrU_mThwVv776@Y z=dQ@M^F}+t$}~9oDR%Sdcw9gZycD|~^Go2FhBJ|{a*!(PTUcB`DxLAiOlFAC zFv`cY?!?tGH()GW&@y+77$PA4Z(~f={(n(Dc?(C$i&2& zI|nQ%D?9r+ti{Ca1oFucVI@*rgRHFQ3w+E90~Zs_n02aBn z1N)@X7JkTWjs}kal)+GC3goGsHk!>n1)1QfW~M7pNI>YM5Bdw41M0S~v6G*e7WS2v zz^470xGVqmusnS3shgV$taPCfPYTV4+vvVJ(}jZ%q~UITJD(P+FHoAeyv}T){pZ5y zl*-ipZ!8yWUFggf+neKU7$K)VQ&%o9;{#9HJ!V)~k{OqvX>%Mg`5$Vt8R_4M;h2Q! zm;klNsJuBhoiuDR7>=>4xx+V=H*eWObRICjL;_vShMk9u+v;5ejo^ir6JrgM1Q!7T z<1xGAXyUo@b|bW0cMy|iLBlX2td04a5EmSPgQ+qk*95>i@)jkJ^x(_@x=96k=nB+? zB;t72wNuu_9)PC;-I$lDCJHlfzk|3#kN|QUldgP#xiuri49=zJnq#<(Ff}#|d|ozs zz-#tT0(?O(|37KYSn&anQ?mN%$I18Q<#+JTZUD{#FM+AX0bxwqVzI6pvi)~(w% z&_B7IZSmB<57rZ^`}M~JPQpxUDaJ>;c4-pO5$ z1Cjr;Jfc9Lr~K?2_B*qw>H-Ukn-0ctx0;9x_giy4s_$mWr zM3X3PTwk}{ zXh)H(O0c+t(g7r`4%-pYNz|_(+Bu6t9ZoOh6AvT0X~#pa1F&w((DjD;34bYo+1d!8 zBf~Y3b$b%14~a~`ej`(N&4m4Met3g5;XyJ3Q9B^u4nJ-s=oGSM4|?7^c!Vv5)ZS^Owac#)CB~wU;&(meY-MY|r7U2&Fb-Gu2U{d4yYNCyRBHySKZ*Q?0lY z+>`5KZ*;xiW}Omip3rYj&qkY5dc>?U+@R_Xv6)|LPf?dQo3EC#u{jEV*!nNP&Sj>C zmT?pDSP#g;ai-FO-}}0i6{+ee2>6SaMEP3)e_0cBBy6}_9Rff?Ekp4YgN_SVJed`b z`X?|=AH1{y7AWi%BpnOR-BwKnf;3|z^FXprqS#&MM}>&DGETbCEIK^Q`umqJ&kC%+ zkB`fPhdAai8Oy%rnQ4e3)Yf@paZup2vyxS7wj&S*>H9(x}y%&)!+v04#^?UzD- zf;2)DE_)^Ju_VVuWMU*`R;7d7U>H5W0`+~RSFzp7XkoFBbFR$I*|qo&Bgdz-qr(sO zK@LJpDu3a4b<%I{vUWf%f0V8B7G~OPrQchH0R4{+_J@EFBn8zO1$nc~KKafJ_U@C% ztVqy7>l%YnSx1rjb?r7qr!+rMbeC3j*mcF9tj%bNEXZDS?`q7rQ$xFOtq&xp3-->! zCUVa1(XV5b#%Z&pu#|6rYDjxx(OGs`y>LyR9L##z2XUDm15?js!ljDp?OtulkFbut~ zpRP!`cCsn#dt;E6www}+v7(-Bd?sPJ1uJ-sVvb;eA=r=wWY(E>x)1I=wm5yXPWotz ze)eam7?}S``CdBcpSvn^=`c;4xy+Q@8%u%{ej2}KglNUJXP17<8G8$kIB?SAam~0)GvZ`INs%eLPCWqv>V}^M}tWLg9ZI(cJ{{dl8fh6s0^MC&!Bjl1^Jwv z=W5k%(GjP@uG;&!4=U+TBSxw=Hn}9|(l~A*%=@wQ;&q>_f1$XN?m)+!3OA|-mZ+ro zMb`;?*mdB?IpbY@Z>vdGPOd&A>XfM>J~6k?Y4n!epU)U?$5IWrAyjR_-xgJm{B-uC53$x5f)YPH;UDP8ys^Yb3Rs zI`nB!8^ppapwVdo7a;16^_NliK)8BTX2c+XIc4QJIVRw2VW^8U0kc}2`7HtTUh-oW ze6O@Y7^kijP5HT1Vjq*xo+9mR7Rp;(fiEuD~ZYX7sQ95@OYf`pD@;!AiEMf8L zsOL_hW52%BSMJ@T|1OU%m%a5diUP$lDHju8=6k+XDTqmTI$Zk}b@@z?KFE}pjx(FV zIRUZ8wxfnE<~7Ve1j8p`(MX+(h|o#@g-HT5viS!oR#8x#KXXdOim>CYp7rmq4jvwY zb(qsqC@yzy29w7`AMEP7D6}@DOG=2J{|&r{m>Q6mbt?B@Ob}_qL%95{3V~^QUgg7N z6X>c@On@JxLP-Se_$oh(&I046J1FX#kZJ-okU+M)jQdBD8V z@hQS}{Z@0a&NZ)71Emk5#cgP^v#NdCpl5NgV6T+y`xg>CDP2(pZNqG0VwY@F3-`SL z{4pdnw7JB|zQ}%@`W5g10^DI^enWwYsoK6+a9MCr<;@UZHtt*Nym4IlC_Q~r%wrSq zc9YwQZs~eQ-z=oYIFnCtIM|$ui4YNoBdk|El;PIOpj-!CEHdS;)P>#}rnd*)#^&7L zTHdG;rj--jvL0Z--9U{_AwkSV`e`WcH8I&Y+aV9|f$ z(#n@@c(kOiuTLghe#|###HGmVV?#TQen3%+D@FdbZkg*IDN|LyZcjT3UEQAD{dt+0 z&ouL5$IEeK*Eb0st@~K7ro=X85{k92UE?Ghf3S*&FW)Zh zcA4$(+7jxF*_oVw-X6AvORV!{ z->Dn?96i-Sk^GKDOMtjZJCMLTgn)XV>DUrU&J$$)Cq3ZqUZbE4l<+5&YW=hd znwO{{udf9|o1HrjX%MU|%@JkTajm#3wTw){#u)+GLlC#>Kwy+Thd=O5aO)jQNMKQM z$v<)P=RVI9X7|w_4OZP#&PTt-X3qN0( z!OKT=H*fKsCMktP^MPK45IGcwY0h8cyhg8JKtaU6CSh^8oE_Vg&vv@VpF4EM`b+hy z1&IY4P({8w3cVhA1h6UIK@Yt8ywG!G%=0<>(ZA~HiGoK8S#IIl;}~2!A2K$ME(6ie zLUI1cwmM3!{5Mi+$YOutlOU8?*P)hFnL_6wgMV&4v-Z!SDCg%4*X$uPP2RC-i1;F~ zWa=iC{Q7_G2Ai>+#{mM4k+(NpAoDPeLZe@UWAyuEglm3hhynzOsRQX7YIoZ4~pWp|O0#Fcz z4!(7AT2JhG+I>myD$g;wyoJ{IhRuSdwmFJ6L7)+BL=PzRPS&6%O5e&pq42=tjicEI2D4Gk+$ z-5<3|t@w^!=zXZb69lQWz&nfizUNU{Ab5$)b0%}bP}r|N-yYJ}6P8Xyy(kJ==L3#Z z;j&Cs^Hq6vaupEIKvPDd#zGH7rkg+mjOW6Ln(hqlvx%vxUdSB=0mH5gdPW&$Hh#No;)lz!({AB2e4pm_d3j6O$>07+iG#NP6H%22Z#g`5rCkF7G z^REsg5(o((Cof^L2N~A_Wc04*Xb*aEH@9V*K@7hO0Zx>_(DDp-85w|qB0USzGN29_ zg1Sj6i0iFsfUXnnweA=0XOdt;ysa353lrt}0$cw_k2V2C z6Yg#=wsNFKg0qRj;7=Wb5D`xljz8R>%I4iD(lHP&ad;8G`E_gS{l|_y#W&4CHXW;>Ase_e*i+B?qb)h;#pvb-?ZTAN&oB~*sdaTse0cf{)klBqAGOh5Z zXBilb>AB#3hLI$5y=+AiZxL?TBn29Mf#)=OQNrmyWp2F21`S z{B@2h=xp_30E522L9~O?UOpq!E7CdJ>WtN%?>hC*snp+E-YGF=MYWcg(3dfPX~Awn zC^XPmvS707y1iZH=MOhGux&5*Aqk{NrEo`p9-98HVVDp$8`! z*igO`IGCsaP$k2uT>W3M8j6<6%k_HFfF$0*mW=xu(F=5B_P`#>V*Dqma!3>&G>$}o zN~Y?7;s7E_Mlp5N2;qc<9KRkI1p%`l+=)Ybgsdk&aD9pEHRWq@0If zCtxDN(TbBvt$=XeLa2`HuJwLJ4A!=`BuF>cto=g6dF#smm+ZV#txDA&4+rdO-C^7a zb=)p^v_K}y*=FIwO%l887Yx8XX|8|UGW6?N!*~1f(2pHIZg%YxG`v87D9-D$!*Maj z^l@u`T3u@SA1Q~b(*G$_-`FgwCwy*w#yrxqjN*CpHChY;GP$Cg7D1dhvO=~N+#&2l zJAXeYOlw=j)-=JAX9lN4f`+7iPj)3KB|fg7G<=X1u;Fw~NU&JDO&-&%w#+b-cwtIl zpV-vjxyhcF;Ar(!Io43gF67yn3 z*K)P}o(OFwPZq{ws#oMJ)LWfk-6lbQ=k)CdN~&rnn{Nt@Sf(eS%$)A*dXl$fw&@9V z%ipY$ho4F4A)3!C7-qdDN!zhqxc^CT6+(aT^|-edj;(N3dCSFHUTkfq+C z(a9S>KtJ%OUQw=iX;`F^X-|S&h@hivP!`~05EkGjJ&C4iU;nA&m;svriJc91PsF*e z-XCo(cNUM24ruh{91V6u!rSRp5B}85aO^omSN;d5mhmUtCiwEYUyeY}p-eSl_MPyx zW}7DCl5vu)>R{UW9OW*EBeN4RjYsf2(Q~a@ixK|Dy87iBdKbEx%2+tVGRIzahe?y-R2J zCQ(1r)F*aaqs=822M(}aHf7P_0?#9s3pd8ASFf&R2w^Fqgce|`+E^{TeeP#pH7!Cq zePe8qgX~pYTx_^zQwf|FgdhNjWh$=}>%3?C_A_vId}xD$W{saD{Z4$cdsS3GLWmYg zH*!Hq;0O=P|HamO$K~9=f8(dDB$SAjDAA%pQmM2j4bhO2qAeOUm9`d@BrWaI)KWB5 zREkO(LWK$~4O&Y5o-fz;d*6TjZjZ?}rcbK&Ae1=J6iMW%k*bUBG-~gs7;*Mu0Xt+Ups&WgY8Y znV5&J?6EtWlb*||iwkXda^kYOn1~-fE<+?;N_Hc8E1-wju)c69(I9dJ#9c=^m~&SA zGww=+`j{g%mZTja&iT>05m@k^{1?;nRZ=rwS(16j<(wn7Z)QY}K~a_=FB@3#^?jY` za7%DVuo>(g?(R#bpG3=ly~(nhyfJvfMs{}nk^iFpI0VR&K@|@D4GMV&6u_Oo@u#;} zdANZ@f?DG%g89Ee8T}15hku1R40st&dRRAZ*^D8=$x$ zr5j9R;c4R}z#YG%Hhjpa+?7cBEwmJfxo>m6M!xr8OJESEpw`mdc5XPhVMrJj()s|@ z3<=cpJyyKc+UDntSs8G;q~6X3D^5yR3bZT2SeD2=N^#~r&eN`FlSdq zXWximF5T1p|0jd5zJS}Hm>b&xlm<|*&Tr%IAN$}A2JB{!W0^vF6wp$iQ3C7EIAp1i z8EF6nEL%2i*iiHFBMSsA=bwwx;43JTH%)P8z+vh9Pd5sL?QNk_bH`3jA3Ka+5epu{v#DJYM3bc zsELLc=z+ApBqhYAOrIKO zAz%L0(#BsHHclNLz3sK`9Rg4%_tJ#TNYx|5BgrV-m0Q_u>E98@#=_}B_dkNezV*<9 zgr5eBBHp6`-vZK&O`iS&X7vUNnyJZ2r1Re+#06ZDB^ObO&5vZ~Q@GKPp?a*tq%e_c zbOE^VKchfI(+HybFtlC_bk6h+z9?Dv$+0?#ZV)Z-4Fd7 zHqzYR-lkx)=P>NV|DFU5HZV8-&7S{}$|Bf*9U~8LmgSI{RiP>Ao8e`>FMU zGtYN#$*k0$N$yKDhemef-nN#}WtI?07lbT*>dybp-G-_GEn^V6tSRhS{(OdAyYdV) zjBRXeD1l*NCq?Y=>?z0!XopDZaQF3V*U|ygQ&?eg0suL_hMX<(r9+~vtq4`Pgj?``THz zX#sJ!Z8qosooYc@w|I@49f$*?EdC&vFfXYrzhag#^d5Kg&A-B^8(g0}ths|X2c%@{-;uC%qiw7p)bNp5y% z$l(%y!zE?0nQ#T&MRjXwr;XidPFY>7F3$2LpXy^-hV`rVUmR;1bbeO;rW{>1Iko`H zLsU@Smo8|8u_mo|^0LZJtS_r|7oHn7DCM&o=(GPbY)KNbU+v%T!m@ItcgE;t1<~ns zinXnm`@>dYmuT%@cFwdS3^^~S(-L)-E$_vB{5MtVe%Kq$!V$iJgR|MW9u}t0{Iz!- z=2^I3_@^fy(JlsA1tdGGJ+8`m>H-*UVM9^aCli+bcf3pKRW*eQx`$ehgWc@$rL zM1-%)?8=uK!;RJTLygUGo5Y%%-*=de9v zw2H`m%o8$#YsmCwYOLt1N&)IO&U2UaTnFX@R|fIQ)Eh2w>(4~V?O5#Wk}7_DPL5~G zgJd0E@+lyrYfgNJbfPv@M$8;QN^trgE<9&K(em3ttr&s&iP-Q@G@N<|7$5%0Eys~d z3!=poup0_+AJ_w@aM`yL=G;kpb8Y++7#>uU9%1G$f$d+Ai5Me?G

%yKd?>`y(#hD z$Diqzn#SR3O_)kg+wG*tWNYae()+PyBw?#6-8xp-PmH0lDwGD8t7P_3vHa2j4DM9% zcKl%fxubf|RO>%W4pCUt%7zJUHAoJi0GifO`t@4}a(S+){l?7HzREgFS_erikv_?v>JX!(O*j1e|4Rr^9j=v zVN+p~V#L+$gp|fq;Qq?W_jTBDtUCH;)=X#ef}X`(3E`GzyR^f~Og+d?@VZ)+bCA~> zfP%QYgNd)=AZ$;}?sfG+1_#NA4;dJ5DD$>gwct#^;=OK&gD-z)E)I>J!|Pd#tqh9= zJ~!gy3{y9UbR6!xj`RQL&_3A99xoF@yGDZW*xyXY#&cR;f`65@pd zAwAw4R0&C<6bUIY8Io91$N4U_V)GdKq9lDP8j(UXqvimgin|N!dl94v?FVTK$dH|M zf6@fYp9wP9O#G?eb!<7T)V}olud-)Ika#(5&szNFx`C*nNX5A5Y`lKA{$9^bqIdHE zdJ|0f*WN<1ZKCINDF-ExQzaLEJA8_6Z{$XhUpxA@o0ukYwRhjptt!?Ow6V$#g^KkB1`C$xbM4KMyUuK@dtnHm^I zI4C?eY!fzGe+FN!{vM*nT#%0pYf3%Y` z@eymMN|svdrLn}OX|P`E2mUcSmLIwtz+DF~3Ae^ib|NLfEIFO%sqNo-VmIzNPEw_S zVIm7yFoFIKv`*r4$U4luP`i8*Tp{KVCBUTyM3UE4svLv7tgw^<-V$J_pXL9NB|wYI z=l=?TRtRiKx0TrR-Hsd#Yat{jd5|PfE7Fv1FJmuTryE{;;O|cUuq90Q75(Q32Vb^FD#19lkk{DC;|b@!C9s$?NZn^0T8WqOzQ1NmOPnf-idrH7 zj8}3v(KRS~fyJ4R30w(k9=;bF@z0-il-3{y_WxIJ1X{tC z{O2$v4J4)D7YZRb$>CLo7B#fhhSo0>2_@IS?|Y& zL(P>8cC=U^1a7YxA!JoCe_y8b@~^tpe_z%50aUX8kt>)nRQEI#5n0F_%4d<hCB ztENwyV3r2lW8dYKUV(56b9tGRLAc$y7I@WooG3D#HO@DbZvm8Sc{!*L?7ycNIRU6? z;4S|Pu)d&sNPC!5CF*?CwA^6*O_aeVA|hfI{Ii(OYcz=&11lYa38 z%VXp~_W^Rmqc_*LU&kuX!HF{-VG&L1^GWI@RUs*2iKVzkXsMU8vKYbaB8H6=e&MUS zZ~0kDgE$$VO{l@lZ3a1mfgJbQ{fYcpIAQ28?B`Ij`!hx+ft#jR9;$;I5ejl3aaK<_ z>jT;7Y;ne7QH!r>E%BNe{SgD|w2|w}r%8zg9Hlrw8>t{3j%40#g9d0D|YR-736`+?trG1XZ! zV<{R$(L6G$*X75*DCtGlc4%lNs_WJLgDyD+qu$$<#`yq~9)Ax7eyj-V*vottf=L&L z8h605qU?D{-QA}cpS|+*o3en_I^mKJV3wthAh`ht0$0X>fX$8^B|{KCvjJw?qAoP#oxRGy7T`7Y7|?j0Me?uRt5HZ0R#=596}yZ%bajOtAU z44%-1tgGFNo&rO=-2?aE-3ju1Sa;FW9u}@d(|cW}Di3U;6i<-><%{RvKORZt-2G)f z#W$e1vY)v$+M7}!4j1oh6R3hTrX-S!o9usGx7E%}v-YYT>AszL9%}P5jdkX=TlG(l zgQvz`KU;I+gQs^JACPU-Q=5`4GW^1Ph=Kkq^Tsm7g%vTWS zk7qi6$Nv;xJ{X|U4~Ck5DxJOedh#OP4XWBmLCUy(vhb54Ni#Xl2!>oXR$Rmx&5Xq{!<#zJiVYi-P$($Tm*4rf5`I<9HvnM#@xhem$ zlo1g|r1=tF8fj2D5iL=#t0ZUd1Z4MxRj{~mqN%y2w0Sr*tWH9*S%t#BOXlpkT>gF= zJId5MHZfj?2GER6zfi&Xm4_@AV?Dh$o&=aq8kJYX+D>bYfYM!2+VzqoOlknwO%+bu191a}l zFK7pdh2dy}y`cieQpP=>*4aSgi8c+P$c>M1v*g-QFjY3h+36+H}1I;aP z4Om;--rq5tOTFwG1w#j zp(%g8p=U7bch{%iA`c>D#~T1z#2YbRaD7=A5b(@PLHl_{pr_}k=UE#l*jcuc#k>K^ z$V>GK$Aus)o11+&V7vLpdTV`krfD;nUFJX_3QHEm1x)>BM^TwJc$pC3;x+4Ayz8o=3qC%85|2$xP_)onk-7Nfn`KLP zgVmBJp7`Y(YQDtOuua<)UsA^re+<;Cd)4J{*&(tamlJ}XBkycv8*6vg9`>L!=@Tg_ zfd8=0MM<4`7&Bz>;mB@;N)9_YvPgeFtNa7oc2R=mL|JFDi7cO0MnB!R1b8JIQz7-+ zA|M^n*a=@{73jpYu}X+sWoq{3ueSW9uq)shQ4$Go*d~En*CQUWJfGTn?Disz$ctCJ zJp0oD%aIf;MKNA7l3O{o^(Szhd)a1u-&*VBO|ob^m!Ca$^PDlv(BL9x6a8Ec6fA`i zFXF|2<)C>|I!9re$Z+F8Ne`K)(|>9QoP{Znu+O9=6TU*x%XYPSHv!Q;>^qXbJgtGX zy%)nV8?45y#YN%!QW;d>lku5ZeSrqyN_bli?0x+T<*u)BE))z+YYVtk)5z5Q3cj@~ zNGlCGeMSUrUv4J~iXGD9S5o7*@`5W3G3@24cK$y$Wzm6XfolFO#nNAXG{ajFYeQ19~4wtwvObEKet zJeFLVHVo;ald7X+pkYvPcA6n02@nF*gRSXWEM~HrI1(1Q*kz>B3tIaR%kt^g&aul= z>Yy%($N{$56vs%aa@wk#>9u;GLt}QmXA5o+$5n4BdYhr_K5Ur-beXH^;V)TP*9oHAd6=s)=gYP1?!W6oq-#y?d*hwJ>?dW5 zH{!S$7n9FTJ1O~nAXRWajFxl5j}z(s(eBlcK|+cY&I~)#*y5=v-t8Z(eK=vCfM|yf zfBnbMPSi6`_Y3s=d-jjr1@XBk*}?Qj+n4NgreAZ8Fk*R9)N3v-C+1ZR0`(L`*}rK4 zEqf?X6TXM5uYILG&RDpIT!p-hRN{OMX_ctrQhq4*bP zcdDpnAj8U3pH7yX7Xz z;-cnFHWAD6rIX!R_3FDt*r#_=4PtwS)4(H4Ogx{tX1k&Z<6LBDEWLY08r=)m@K4yn zNtfQ@edf9C4kOq4k`-=kP*XQExCkhlxCSUH=>T)|F}OSD;PE3ogNKR~HocuS2$&a< zh(!dqLaKB|>PrqQk}AX*n~)U=4i>HfMFjM-wLv-)j=A<%B-`XFvt0yEI@5z9@<4(= zQ}j`s5Y4w~RLX-2Qc92V)?d>+IeDR~oR}vC@e8(}=-U+)KMu-+u7bX-BM9WHvWa$P zE+@+tcCU7Nhe52Vv%^lO+3{=p=p8a9HB9)|HXPQXSLj0H73KI@&FE-s8rf09n*~3A za=hPL-Pb#9nA=|W;lzl!3-mXfxeAalZL*SKkhN7B^@PkF+vJ(XXy%CuSx0$x^G#HW zuzAPe%c4a>NsN_*A5}6`ypu|*Fl}Z0&;XHb{!EBX)A;b&;qq(5@l^2ocuC;N*I5CWoQlEu>7w0f7b9?2fe+CxvPv~o9V!xj0u zRH|9CH;~Q(zFS4hO{=uoC?LF~_Qq+%ZA6Kv>nny?Iz=-A zWoPW1i`Zi#!MSO%>}j%a0vBKYqZs5VAkn(nDnoXSQpbQs{Fi2xyI1MGW(Q)@-fmI= zTt4$P1@3!TEw1QF4*4l{a#=T9&M_d;zdH#A^wq7bBZ5!ElFyWT@m^w!GH^RZN&9tP0#HNV{= zSPC^|Ip@6^CoJl9TRFb4Fa)&sn*y6-cr6Bq0*Bvx;x|3YfJ^#$IY$;3rDlFn&mLUB zAbIrmaw#}g?k zqt(m&2ru$Uht`}wKUpV34K&`2<3*&biemsvN8(Ej6Z9hMAVwNtp6g|~r-J*$5fTj0 zWInqU{D>`_sa^jL#@65fDWUO(JU*&UN}R|1Jl8h_=vcE{A=`?^;Z6qaf8?o@ah6!l z${D`8wcQT$kl8f3O48LE=Y+LS)|~K}iBF8nzcQh4DwaS_zdx&#dy-BZ}DZ8S0qe4r^`G6@{>x*1l@o2!>NNpoiDZFxE+%($mtoWAA@vV+O z{~`_MbjcM}(3uL0rla&kv0Iut&nxP8zvqqN(z`?_C37FF8$f1BT``iS7ac-Pm%2_S zdgxrT?nId6_EcKkfVfF@N$iqpM z1Jc*OZnT)txgLQ*`aWdhpw&1uZE6$CpJkJ;8CFe~JuN2HkRu-{nf3Ro9M(lmRQ4TL zPpo;z$OhXcek(GvwxVrnbMi>%=D663~Sn>gbZ*zTxsh9!869b1pcY~)dw%w zr^Q4ZSIIYrY3Dsr%0OOvNoduN%a3S7jRje>a(*PejGUZwEp$w6dYp zLdg(*U-uyC$lclRd9oCGSilq(!8cz`GSj-n-M`%B&HDfN98mjt71o6%d!J5wT}wm7 zI(g8JrFr!RX_9N^=3$0Yo>DjU!j*S0#cx_CbN%)_yhg$@+cLm^IqfTKpjy!7E*k67 zpuXHV`KjJz`TR#4L{HQUysdJ^lOF40j87+1lw#x6)N&I4AO;=mx~f#(vQa&y?+|wGW@fGUn#nCp^tTLqaNWH7}p7`!e8SU zA*=<@bLSWCZVQqdPHzbT+~#bu09(z9x4`nRr^R}SdFpMX5qd)~Xa8P8Olvj*{vq-? zZ)6D9wH=D(ol3I)NS>;car>9wtoE9PS19YC2dN$vib3!53$I)X82+kXNt_hV)yP5u zK@98l=aNfmh-ZB>ZUpo|+sySR$Gq4(h`q{ACY)Qn->A+IS_{tp=!=pWq<}B%sgIX7 z1y@6ZwGeU#xk}NOIKoS0Zp1$i?$2RliTR zhyqRw{KZ36@IV)Eo-+G$izU8Zd)3wj?JpYQ#%9EOks^o9(LPF@Ld{Hw?^%X#mF0OxHS4$^vTG zMUQB!fJe%G+t&8m;GtEI71vW)TIwi0&eXrtkGpn>-4Nb{GD0r`%>|0AUqFdW#jd09 zr3W6h+UZozyfNE zHeA~-e~h^$VrSa5?i>*S%koBI{J-wLI;zUATX$oC3IZY$k_v)KNT@U@h=2&FC?VY{ zU7IdJKtM`5L_$!3O-eUNODf&n-FfGWdcO1h?iuI%``&lB$7Z-|y=%=i<5_dgC$!zu z{gPn`t$S-OLD^q_eW`qFIYj_Sf*!=+^GWAnpxxCggWHu!fq@r}QBc0Jgg z*}ON?nIWV@;rh(z2l|Em)$Ip^R?cIoHwwN%P5W{oJ&uLu_9ALxGqo>1awr2wOPDZJ zfTv&Km#niOI&$3j%~z@6N8fb_&d`Jt;7N0dx~}&MI}OrFXP19Xi;q#sc7R^h-1=zy zO$7kPwSN4FT8}O%P5QwSEh&-j?W>MMRgMqG3{;8xU7Jk~VquQT#K@`JVh7H9%xZ@U z0rP@v{$fW_!-|kSc6tr#TkOmDTW|x=CXk6uOaCoDpb5Z5t#xs$j*Kab6TbvqE(?KYgZQXC#iFc2b*LZ zSI(Dgd`lZfCmAuY0w^5sr$J_|jCKy3MH606k1UdZN{lBVud7?TklBFG)#w&-wS=DK zo;?<4?7Fyt=ZH71RHGa16*PsKK^PI<*+K%=`)9ms2YcnbWIm5ckD=JYA-+enFf= z@ZzsjlTJwo*3Rd9m!9`~usx&!gcX#;F{+kxuS=YkZ#s1S+|+ExCfPTwnAH#8_$e>t zF_dZ-n6~PKFTST7%jlyOIo#RFphli|I`6a0c$@VpM+Xd5I(Od&H)E3t;ghTr+@Flc zBLsach468hSlOb`-S~`@^~YBVnJ!(4*A>g_6AvF=;fp97h=Mje0EzaQi^l=;ao_Lp0G1?Kg30?03Y3 zs#RZZaG4M0>;__1e|;cO23@MOm#Vzs*8u;)m4X@=1Pvp2(8y@QN7ZUn7KP6!T`BQw zT|r&Luc5mqFW3XWsD7kOv+099I%bw`nOI@1oz9P+DEhEgOfb-b!lSZWimSg$zI5jq zbStufPHTBXuS@pzK*t2M4nzA>;F5QZi@S`;pNKf`Xuqp8*R({tA+jrq3(YkAWbn8e zSyj+iPRnZMoOQ=}yBT@uSa}oH=)P{F0#oZ`v}^sY>s!~x-iFJg>3EJ2X1;bb-S_?R z8@@NVjP-kpFP;=3#1k#BCVR2&`iOylMS8HnYg#5B-!+f0@?}@K^d!ef2v23Kbm8Dw zC4mtgQv=mm*SKDC?DmG$idFP>09G~M9hB zidA=A!(Q+4Q@Y1AoJRG6{RR%qd|8xfTx{?DW@yZ2Jm#h0MJV zylsuox_95zFd}JsRCeI*nC_}ZgUQV0+E>R;Z=?+&cYB0lA}-^QN_o^Wz(DM_v~&D` z!`^4qxOz9QXNxpUFrD{s(V>4IMI|R^@nT(^`NA$gT5+!6J=N|L)lwR()YyURKIECv zZrio94=lnBV@=s~^5ywDGb=KVH$3ROQIb-%o<;fPu(Y^yo8-Mu1S_!g*eqiu6dPnd zexKRbPT6}tL{We_H;Uo2E#u34+#eSOr&7Foxw?56`Q&`ZE0_0*B^siQSjVo{@Tkmm zWSL?4s5zYSsoGr)Yhh+ZD@PPCSGJC~(sB^txw}kus^KIJsxFxHT=E?Lc$M4t8BelS zJ$rhEO3wFSEtG}tlk*bEs~`5yj9EAjdxpHNyz46zn&l;3!9lD2T)ium^|IFYdkrpO z6K2ulB=g%iKTlTbpgX$BEljGzDuQ+`JN%j~TMa)JS#nYGlVOal&n%W|zNX-uI26^~ zznpe=a+c;)m(~LZlLs9ig=7|Ct?J=pk7C|_0t5hml5m>i4TXMOMD4_8#( z`PLT+@vZ3aWdW-Jso;_=YHeIr^GeU0naVyzOJZhfT&MI+@xn4&*Z;R)( z-Wp3631n@ilWp;GqL!5|%dRBRTGgdY^z&zmd~Gv2n&aojSne;t?ZBpe?&fHy=)es#pe%|rj2S$Qo)bJZvKqg9|g zIu!r(H(}u?sSI2@kY7@8#3w&*^Knd;3_I`CaK@fC`Q5$~wU_UNTqx5zVWNc9cf#a+ zm8;MmzbA!|(QEBDd&BEkuTsoDXuU0dH(sODy~}qxvt?y8+jlhc7ztU~84E%8 z*W9N^@J^bv?RVy-p)piJF~2S}$>&#W&p3zLWta<)@^une=O;%~D9-nB4~=6ijC4Mk z&pL+|{><=D)7ej0JefeoXU)o&?-ADVq}ZYGA-i(T30j+)I|6I>SH7Pti`kJ~$oe9` zp4^vIAxmVH)X#pq&y#?cjWWH?*{YB=H^!;l zGmaf21mP!8?X*P>wT{`mlXjJ|2SguMLJJ1dLo*)?&?eJ=f73;<5NBmdTFf^zUa(^I zUT7Alc%XTjW{adcJ{&qR^_27IQ=J0Dkg*MRE zj3=ZWmrB%(U=ZrxziDVF=w;StzrQ1hEV@-J z%%>ElvAgTpe(c`X{J^&}V23li`1B;nehakhHB1#C97&LP-)5xrj*{o4QMn7p<{4Y^Qu_L^{4bm?2!oNheq%*N#gy{^2XFM}+42h? z_|*?ZsTKXp)IW-ZyAWkE3tu`F#<2cQD(sSX(cUrU%5yZf3M9IoQ zc)ZP}q@proRJ|htUUPPK_Pxo~)k2!|t3(RfIf*Ys5MC0Z33M+_ptjE0eJ&}TscG9O zau1Czt>laNX8mXrg`nA{uJnQ%n#bGX;^L@dl;|5Fv8u}^E>=odov53&Xu#+69pPcK zMnaX1Zj`@tGuG#}dOi#joaC+CC);gwiSg=bt@91SloFOJi69gSZ=2SEVVplw`X2N~E9gk0A- zGn3=`_3O6EXsf@pO89*@!j7wKsCPVbr-AO&T!Cs?!)8X|Pn%;uH@xMYP^S{xyhZ-- zUu;$n+kJBG)6Cd{MzOVR?0WP`pO)w}`f~ZHE*D=a$F5y$srNfOw!$I(Uk9nTLh|-` zak_m8R0ECmga{%S-o2AjxN}$IlKZfyJ4Z&ZW&e$f0}9a%%yTQxpFcmP7;s~kyp_;H zd)G46yi=>Xgh9o(-?FqxOTo1Fp5Ufe2o(=Q^m<~$YS;W_q0%Ofg5F&7!!FA&n|`UX zomw@a6sm#tgVa&722@jKDVRyrOI5$x46SA!(c(rE`3SQ653L;8d(k{x1=NhXcSkn5 zI>U4bt0ZXqo+Pev6Xt4Jacpw7XW*b>-cDe`LO`cKagA51q`gh_ETySV4NcnYHshXw54BO}HtYMbsJY2YRtL_G z41CI4_7YmljzB|1(B0kLYEwEmIKL4Nq_-?GzATLUZ;g|W<8M8QF9)+4#2Py3L|3;`LUYr)5C?Q>YAgnAZTJDU!9Poh! zgaVu1)6XK9op8KvDdbwG$jy{#A~%I=uKru|)4C2;;~MMsG<+w%6{=oiV)8C2UAx&g zZHw8**wxR=28B-Cx+(SE-5nd{{)xKf#B1*G(85rr-HZlBs5Z zI#^!L2eJL%oa}7zGJPy?uRnZk2Okvuqi^)Cqhe6R4MKu8O~q=xHxN z>xAlW2FX4mR5Maji+^;Lc-~=hY+6ffyx1_kNfC4gq$}K;v}^kzs}w1qX)jaY2d%OL zS`C4X5HiMqrcD6=Muc-J41;ryd;#iD!mCdaOhs~Ij3>^n=|?bxbQ)cI@_2~+F^x|| zqJ&h){*@swuQ(q;E6FYr+z$RzLA+Stn$zkbgP> zwp?ab7EDV#2IEU7=9|!;6B~Q!qQaUkts#;d9yMX+tT%Bzk&4-@zY-)}Xa%!1Ha(n+=98sU&hzdnpb|?a*5CYW~1Gcj) z(tPvT3BAR@PSv^{%q0Ke^9-Dv*Rp*B4$WgOs863h9dY^S)72Bt>TkJa=%f8? z&F^QL1a}Qk2v^Q>kAB(}gVjo!1Am~MJifHu>_na0>mWRP#3uE7@PX4mD>PAiV~j!? z3`c377Bpgu-SqMO? zUecdR)Ng<4ig55L)tf&E01dV|44(YXzpd+09r@oo5|LIBMshH;KW-K;Iw*vQ{$W{h z{=0wRjW8jW&d`+pCzyU9_pX*{;A$Lx|w_0wO@{24#2Zmn|ciR zJd_xQ16vrB19(o`5(W%Q|0(r5!|a}~&PEoU_Vw$$AN<^1 z;9Zusv*SaK3`Wk!ka@nUXZti8yZKKV#Tkc(nmTQ}l_udF@QW6PCrLve94zT|sTnooS_0{dRc@Otiv8)faCvmd-Tv!W%5VNOk4Ec(^t>t}I zS@+!XBDC1o|J#8tNUKw?PuTPr*8DGaNuc^O%r8ijkPlxwU{m%16TlsjMZ9e;)At-;F2XIB={OFxP(!~*X1w)AUu@awKm z)W}t_S9_?wAOxApO)T$cc!(`|9@kK&j82s>xL$idK;!QHUn83-3E>%^DPizeTqLOc z{!jA6`4a5--GSSNJ=s_{WPgj*pIut(|2w|@m((4l#s1yIcsyqdSm-JO=)}O+=0JH6 z&`^KduYL>rZ$yYl4E%K?{?*bx?*zZVGN z8>D;P-wTiDPXhk$VQ~O_|Mw+${*Pn+j~@=a4}t(1h60Lx5iW3AKE6n(&&)6ze}G`a z$6U%D$wPX3f;?~xY%lvcHJ&6@E(^LQTU%eR*_UUk?6Cf;2y~!`iU8O+RlB8foNZ97 zbzhP`c-4%Sv*95GV6m7GC@6d|+nF{A6kxHS*Nckyfo0I*V5hSb-|`#BYebTN1{2mj z{r2tK!w>x77ntPO^qMHa3wZJ3#ZVn%%osE{S2ig>$-h1Jo}04%0P@4FkC;8JN_SO| zlpdSOXfaXIlZ94`69Hn><(bR}TQx#v{g$0)EpGuek*z^b_Df2BYyXuMGwAHkZM{Sb zs0Kl9pIO5rgnX919QQ_1*&9+-aWwjNr<#)hv|iksH+C9@-`yL+5gL+I+i80wZz>Lp zCqYMi<{Tb&U41>&_~2pNEZPk=c;6_g1$ynKPob*3_ih2d;&gQB;r?P!aIh&%V>OFf zA({oKO&EY>6?PUsbu-~FU@c6r`rxpksN4ME2k?y_krE=fD6(dO0k6wxA+{c^4WKJJ z*qLPpdSd6qLMB`HC~nSSE$FPTqO^_?%UrDGs#L++8R%p1K}}MXXT!@e2ksv0OtlIjeTa3*>X`U|-1GT{Osw z4-y;7U2(SCLQATOY*uR<8sf9FgT@;|SN9j4Vm3B5%6VLF3da)42&@yMyh&Jihigco zr51bOq9MvCmm=NO229BI{T-y2EWI2<-=?blvM34+^J_OtQdR3qs2jy=Pts;M+NRQg zs2V!IQTdxu+s&~X6)oS7h>VPH=;`h4hI9{#+Xh1mg^NIae5(;y$nW@u`V1~kA~e7u zI*;Y$Ys{Mk;In*N07h5L{7S}|&x5&Eyx&%wnhf;k%X3-H9FBX|(!A3N=h4bABwo7h z5;(qXdj>;7FAkh7gA=mz9q%Z(e1W=l>UMnabA{T|>^k*933VD)2w|aRr;3=lIVb4& zJ`l+w;N#{8L9+**W*l{3%T~6X{sbWzExy2o8*Z34*sFQ+7#32QHoCgKwP021-9F8KspbN(ak2+4=))rRlYiY+1=?nH zE3Dv$8X6iFk*yC{n6+E%fhI{0)IYFtM**{^*-#l)qD;&{GKU}#ERU|+n&vC>uQqZ< znO2jxT296|31;avPi*u;^o~*4Tk;SZqZr(}FH8<%9||q@ig|TAgE)nRXc{u1^$Deb zZBPxhoe$qao@=M7-8;q))~^9YP>qJlVLTbS=>ZaG%$En0o7TWlx(yq#$pelPRipjj zWi`5GWJUuYB=&74P=wT;&U89-#7#%dPDC0npm(_jtmz8&8HKj%5_-cDU;c;@8a$Q) z4P6@+HyI7yhlY}}Br&$bUTukA-3B3ih1D^iJ2((OFM3aVjC^79cKglLNaK?3V<{BI zMXelCSzJjspoLqbNoks5h|d1v;X&F-1y~Rng68Ky{goV>x2R(N;}(g!VORR4=>u4{ zj_n6yiAVYPI1)xrS$)1~zFnL>2dt$7!V zR&Ae_if3iis`DT#QkP(-1Wqg+&Gso^xul^11(FXeq^8_!$>0^PDKi{3w#M$g)3aK+1O|c_0 zTkm#sNAWcKSC4*7)sBTgaO-2m8BlcZALb$b#-pwtHyujrHB{#K<20l^Ca#3q=*-I#VL z>ahdcJan`9l9Dn8HUL~LUqp?;X7N65`w(_#ILG+FMyj!cH>fDTy*>Mh#_-C+z2-xD z1M~4<`AqBO5i9{HJ@sJ@H&Z*F2XBfWwCyjlF}F}m*d54f;`LTCF19T5V}j$(nmhfQ za2$@Liy`_3v4;PH8Bhl+;%a%Z^7S#?I`O{@)<(I0MW?fKa`0aWuolj7_)-hhC#RLv z>!%gp2MGZt>nQt`I%S~KwO@;;k&65b{{21e`r`RAm*dX;YFp7(1K}2tFWVClzaSu+ zW4r_#O-coVqt=&cIot8wvhv8a-O-|5v`dyt9RcAOK0FvaYyc)(Qi!VjyreGQnlDd+0J}H9><@9?D$B7>=c4jB9e)d zjm)M4^3w&m;KdA4OzjSMU0||asbj=RG^6vWPWSf-ijJm=-rU{qH(FfXMnQvh@*{rZ zHID^gE#U|RPYkBeM>K+0#t@z3^UofEnO}=leIM@lKKiW;F56GM5<%uXiXJ z!;S(g?bVb9bm~VjZJYejO^e4J`Ed*di_v6fnCZ1tY{r9Ix9t}48K-YUQqk^WDUCBI zA_x*KFMmaB0^|Squ~dbO*C3@7&d8b+ZS?4$?nTt8_U^_!3ZV$7nMcPQg|ut#|C~7Y z-)@k9z%;C}2E<0Kli&~8LzoFIdmbS?<$W9Au}BJ&Q*aq+|BhW(R#)>!efbn1`42iE z34v}0n^KCU4xy+)SGo&?qUC&-U~$(ijL)A72)MTi-6hDSECwm45p$joIy!H$WY@OH z;j83wMh*37HHC3Pem>9i4v+Z+5~YGA{gq@3BD)s!kKtCjA{x>PH2f-9+!e540q`0B z$Vf?3Q+6QOzW$p{*))Ct1q-6k`tK`gZ1PV(_jg?QFSq!Ak2e2>X3JTUYwXulf;^Jzfz?UIsn6hfT(_NV;w2;SM*nOX%(oh1Y>q2wQt zT$~yeZn2m^GX0OJ$!=xP{3}ZE~3C^ zj7!2iWxfyoNmHDt_fAo16t__>--6yay3iy2HWK-8T>ShyEUG<4)Vni09;zmtucF=m zty2}Z44hUT+qD;MhRMmvNahksFWQpCT?6l_B{=?Y#r+AHd}x6-nURl=LVWOttaksc zPUTAA{cblBdenaxffqbI6Mpkn-P1m?Y|Vd!PXpyLI9@<%Ug{(i931T2uQ`&E)pU%5 zF@0)kY6z(;0K#f&sF}>`H+mo`8O~?LinzsA{g$MFZ;0oYzKrMto1%>F)_EAlhUHfy z0=fwA@}^=&f?FmL!CbJag*Q6GQO z*0C3)F$OnVxR!7l{}qH5K2&@mi19=uN{94GKv`URy8m}?Qi50g$zQ)(H`@(V<{0*l zgmB%w*#hAPMy1et=1UT{?b=nLt#SgQ4IzsuLWr4myRu)(_N@Ro5A5GH1(vg0ztSAP zgVPSMn{*p5CAW!lGN!=GYQTn3qko~lu)Elf%L)8wBxi&hoC~6j;;j+y?hGxlzLn7$ zzg4)c732Y4VFaYN#RP?fBs#DlUNs_dgiQ!g3F5p0VB7L)%pw2y0TT8K?X>z4%^ZSf z2r<@x2KnUKEuMwqeES@{;l4 zhB5zIi%5!E=?CyTT#)@aHZhT@*G$b?vZWaj5kXYX?YPIyB#-fNzks;uh(`&_fo)u} z&43_~TM%xC-!yn(XsEM3^Q$#|q@F2ltW}Ig2MF$wZ;k>BhAIL~PT6sLJS;JM#qx!* z@#IRwjU;fV4tLTHN4}G)X~UcMHgZdK+7cvCdn2T!L(@qy(gla$$Bn*FVArTP;kc0% z1986b67Z0i?#}17|NL16yHC;%IF)&iHc!-xR~o-5o-2@`0%FnEW_&Vx?-alIuF8?_R2;1u$G28WQ^VeK)hWbM<9D~5MX5u5VE*bxqHcHArv z4;BxbhATb7p*s0S(bBQ@BpE*?rvaNu)A?_mW*gmlS;m8cC4dZ?!d9#{T%DG2;^p1i zLVy9I(*2FRb%ZSaIL#L7NcsxgcECQ?l{oC8w$cuFZXE2E9`Y3!L81c!W!P_3(oDcU zIi6Osa~;8ew_s~VuYLRcF)X{Gxu+_6fbFih5ZI%f4SRDQKYJEz!B@$a2YAI3YGYVHXR4KZz7$9dj{v|G z7vz-#GJX6U*W#~r9;tV{lYcGcql7|6Pva}CmU~{9-coQ1;Sc}?@i?-lP)X6~=Qkgc zF;n|_%wh`hojqtA!xXc05usnLIxj~?9%Lc_V8u6@nm&`>cYP5Se_6$!|E^#cxrRe0-dLvR7< z5TZVJnx7)(DuL^-y}F|p<8a$u10{gV9Kx#d0F<@M9cPG2sGPwp0fjFXo}<;HbBNEE zzqitWg@9UvhX<*Ky1;M}l;qIUeuL9cA(lW-ElyAzy(2zyl1FLwSRtWv@9lKU_ zLjUa???i1aQxs^OM3&0osXbZD>`;FIM`5x@!}tSU>pWz0*FI|f*bC`fcDQd}ovq|P zFpy0U&d_M%z|F{Xl?YdVpd0b#sX`FdM^+zCqd?Zc*R&6&Fz2r*J|ae=L@3TxddFG( ze4t-NjGf_~jVHl$ikR-3g<788@XN-&nV!7E(&TT|i%PTY4L0_Jq$m_B>@_oqH{c0l z7QFBL<5E+lzzYs^$0ku#RrT`n3hXzMS5yoqA|hh2bbRBTmX-z~;`+|I{`ka1y-{v* zV&eT$$NeWD9$&wHwHU5Ad4ZVtDg#4x*W^yyz^iXx6B4w4E}C=AjtrWd6xH2Li#iTJ zNyc&v7oOC;;Mn4Q`}EJy8){G0)Y8C^uCJ}(k=6IB85pF@p$pGF&6`lPwy`;T3Wq`D z>C+%M;HO{z#-G_cU*FUu=jd1>=yZ@06hvHXwODgMqbkG8 zb~^^oiJ!~OnU(L}v#?l8SDPyQ$_kk5ukP+IFxUq(`lPqOluK|gFWKAM2Sr6ONB&5R z7xQ}%DCn@00O1jZzB1NBJ-sCM<`J1x)#B%YKl}SrVLF9+3gG~4bmv#HJRQdJ$4T-c z<03Z?56M92GBm@IslRH6zjEDGF={s3igU9Ubc~n7?4l?4{lZJy094Gf-2OE)<;3yX++)2hLjcyjlyOH+){BHBqz%mb{1ZXZ~< z*_K$5K>DQiFMyHZcenTUCPVXIx)&0Xk`&86TEP(!ud?fT(Q{qtsj=V$T7up`ZS!yQ zHF1U!P)b;Y~<;OJL|J&PM;2-|CE%Z4;}`X)DRGb^@$%80uDP* zNpGnBg!RaN(R#VtAm@@^9(q3F*zx06Bp9Wm9%*XEfiK1AQ5g$3w1EBABZ#|O;lS4i zYl1aNCg%1)Oh7;^RIhyb{8{|huV0zn20iH-t_uUjOkdI>#-W6EbQfn&?9LtMb74hJ zhpGVq0m#@YhacJ4VPfy#;9*ZfNca(ud^l29kxr8R{{8!PR#xg?`40B>YjfS4h66>4>+9<<_Dc85 z`kk0(8XCi4*sgwZwzdV}Q3ZvEe}O3H(l1K6E8anl;F9( z>4k-k-dtmxKIDz(-SN_=exZ)iGoDBeCeXP z)2oRdMUUjBP+u`Xa@}E!3}%`5yB-%5*7B zHIcJ?>7a|!j#L`S9W06ZLrdFldlr3O(7P8t;~~}{R$Lk#9o^Ga`gOw1L5Afq&Cz53 z$Cv+20TR=W`{CJLO z<1uQ+vos51tx~C~I6`;q*;56P>!XRKxMgg7pw6KoTGZzyzezk+F-50tyZv+A7tn*viVv5JN;A5uo_|$0nEXl3(>1b8XTpZL2%*L*zBn*0j)Kf3@qiflT{D zIW&UZ#hsZ$IW-TSQhs%2)WLIkkPeTMY+ownP0h0J8uZP|RKu0I{m z!TS1ooglT<#Yt&By;q+Lm#g+j_}&|=GwZoMRn^vZ4I%wxV#2a~uF_#LGR3g6qRs6~ z7#C-ub>E#_cbsawG^^O`ij}(V>Kry!vWGdPq5DFp^s7^0GcO}bqAkY8_k?}iC(fgA zH8^XhnDgwVvV}HXk@3+!kJZe}MTZX`=9CZT&ddwAo{?R-uJfa_)BIbhVWr10R@Umr zt{P<#30(G5-RBzL-Zd)k^dVguZ_BJk7|hp896v7QHv9RMWZYHLuWE5ueX*ENzJ67F zF-dp8Iz!Z=W@B}uLC=0TSTDP!x@FZswP858&GAM?a8r2n7rpi}VXMv%9&eWEsvmP- z^(F%cZsV2X6<_jUDIRUx^)cYEuwvmiiS@ryI1VEk5}ItZ16UYZGcz-nVQaHBN1>y9 z?_xdIpXBEYbhx%>=~s<5CpV_39On=9^Yt~@M#p3D(IUJ2WWZNr3k!do==ht}es4WD z#1|*Ke6XC|<*szu<%{K=NQTARk&%pWZ(T& zlcJG+Fy_4TL-*B1mUHJ0rKYCRi`cPcWMrIinz{ZWysC*yzl65Rd)EsCS0|^}R$YZQ zj*hj1U&6_y;Y$-8j{=!Q2SyqbZ0_D=pK;h&TQdAuTdR;gIXOu@?%&nbs?VQyK7aoF z?7iPRVkA5RGcrzN%YP1ii_$8-$C{CmzvtOuCZ+&~$icjw;!GN1eiT{9&Kpd27Bs%TxH&O7 z`PaYvuk2h3|Ip1-jtNzoccL}mViou!&FHT*lK~_v`7Z(>d?Z{Y#Qz2z%CvcxmO-azIlh$Vsv&>*!sQu7Juj};Br9RJa1I;YGLo6&Tr2G3%DfdtH zl+sA{C&zk7%F9!Roz_Z-FFsH1nTu)bZopQ+1k@nQa`YMmh;skLpNcUg7 z*pDsr;WMu5ua6czYd1pknC7hG6eICXe2{pz<=L&hQ$MSG_8vOKUS61!a}g1CwfLU# z+u~xDlP9+kPlfk;OcNn&eI(1E+%HzFOO3yT<%;HV>%D!((}$S)Tpv^$#2j_2(%1L& zAz9|{8Hp`dlpY*+T$2dwmiFdgZ-0DJDe}am&!b0=wDN8TSUStdc%yz~OmI(b-o88H zR#$NX=i9e$eKif;HJ%>gm&wpAQqIuIiN8=u<+?cWJW~RN1i{Uskms^s{^1vlMKQ6>BRohqHaWM^krAs|myJ*}O4Nyz+?oSg5c&z~D3mrxcG z=dWG6c5`u}Lo!vpZZ=xXweU@DZg5j3-U-oO%{*QosVylMU|>LOh)S@`6yLDmG(PH1 z(ub}tUAxIn4Wt(7i61#qPZ`hUUU0ufL=hd1{D-P4DUoeB8~gV#i-(YlZGJwSnVWmj zgmct(qRRuvbt|bc*H|IvW;JC&!eI&b6?M0j(d68|rUr=(J(J$BAD*=vn-Ysk9jE21 zBspm*|BPo>)+vva(H;I|M~?^r_LK?<*`{`R=alMNtXi5ndw|+>bxlp(L$`ycwXO$G z%F4+2{r&sbZ908Di=Ls|ezb|T!>dET)FD+VS)D^V@AkJuyS0@C{@ZkZnd0s%a;v>% z&bA#}?`ViROgwAS*46b-*UUcX)P*0cYbR-(I(16cZn{^$vmz)csLg)~r!-vSZ&g)Q zYmx$D3Z*Hm$>UEooy;`VUX{D=cXND`mtSk4rSD$YpeJ^GXZz!Q`}W;ZE8JM@vSw&b zQ;u!QH&c6IDZ$01gp7F3ubtANp=hFBi5t-bxX_PeUVFY~zU z``=OISa$F|kB-jdpR5TuY&`rhX|aMv63K_k(9n>5^6#Htf=NumcV1;wg|z>AVsG;8 zbX<%K56?Eo=PdH_HsQtD&b8ImsnPp%7gip`VY}{NyVBlUJ$Ue7pd-)3yjj1@NyXv# zhQ!7{wSFreoqW0J>m^sjmoGU4ilfFE=_pKAHj5ilKlkhN++dR2LAsEBE%S`qUmF6S zk)DNwJVu^;ndgKo`EYGzJiBR**Qn+-(wlkn^54JL=SLdN+c|f5dU`s!x<;MI)|(q{ zkX2URO?oZmvwP%6PC}<0;xXc!ZPL68_C`)oQNT?5w1%YQCLzloN6TaIKWavIi`tJp zM!DHc3Xx~^Se-1|WbKI;PSMPcskZ|nMg|Bsz^D8PXvxr=9up3Ta4}lMz9q@l!J!7p zl-y2r@boip!IMM|bkRnzZ)%7U+dOkqrwpgsLNUG5`>e;mw{0#Z6^u+wD(Tm_qSbN^ z56q7?H`&cOcind`N17z?5UC%<5!HnJx$eg3*N!mr*0civ+&8SOGWo6Wl06x~`!0qpfF)&Xi(O5woYykGb7p_~B*#WeiGP3v z+?FR81C z^mYj(BQ4*U{r!2z&Ycg6ii+}?QMCS!rDrEn(X6m#{K|EnyNzSVbbv$s{{OZSyO~6; zqu6=1?ai8cb>4kNzSgj0j9dLA5n^Xj^`wl-1iR~9Gz>q0rJ zT3ZhQ6{e`C?gKja0W!Z#5gtkOLL5ebN#rHRi60L<)>h)Mk$@EsB;(#)kyyKRb@|>_ zEKt{{==QTBA`CB1DoLW!^#A-x0qn1yZNS9r`HvSR*5vAX()kTi|K}H{BE;QA7zE89 ztCHp4S66RsP1C4yoi6vuGihK52~Wey{RD)+;Io^le|(&3`}XZl0618ezat4jHbY+z zCCmv4vTPB$)9owV>$LfQT9OOJ_XcM=EwcYB?;NMY1`tjMFa%YEnHd$fByXW z)^+I)OXyc8$sLv*xza9Tb|Y6aN27$T135I(NKSj-CYEI7JYBBTX(_bqI2Ijr@#00( zwv3l;^J%ebz(rI!x0YK|R5p|4LxT;{>*umK1O*QeRnuU6Wunh*P6-7GucGLQviOiL z%y)!!4#~Pd+OhW{8X3fL|Hue&w&y20_J0ZIk-c^;%wU&%cYq@J8#E)^mtcorSd7W#F0~1Vp`r-pg9({mf2WeQ_0j3 z1lW4t*~x&o-mzoHg|0#?-aFlAC;BS;Cam57*ci2>s1OYzu{}dW-X-?qHwQjG@eK+R zw&-Y4$sG6b_BLNrT>JO$JNl>5g(RgY$~;pUwVq^zS59NFn}|6iWxcxI7+gEs{M zByj>c%L`lPUit~QMQ<41%@!S|q@sFJx9{M=Bz|`w<}fZ@?;~O^@A~?f0|NumF-sMg zx6S~*+(dqKu@Wwt%DT#Cg92D!*0Q#7SwW$y%y}+xuBNy5NJT}(NP9LjT8^|~gN;fc zzX8vURc0LGtyivGN%iPN)A2Q0ln%hHT{QdEE5=l1oh-d`XV0EJdh{r%r_@n+xj;@W zU-aI~0oTl_JeT>Ay|=$cJV1R-z}DN?*bcR;c4=Y=;!>M!r*c zczEFdKL+>k4=b3ecIxY^b86!`fRWYKm22MI`X%og8ukEUzbPp2DlHXF)65P;!u;7~ zU0IDzW`X0%jm}a>9wHmPdv^iZ<-cG*CwJqJ1q_?fj^TtySJ|*kzqK;Gj zMC!D$Ia3$HzL}GgvnAWG@?95S8oV_@wnWGxnt^bhqRcG{6P;g{3bqW&O zBRx1z$89JlNHWnqm4Au|yj=Aeiy&9kg5oIe(ubb-ywl7D69Z?T@UNdfJsXnxwn~5Q zUSfXC*s5e1y-Z_gr%0a&maF}3W*HWUg=@OIIM1N=n3$Np&|rIxQSB^Qg-J%gi|DSu zM(|1v3w=As^{d^`lM8K5{uF_A2|Q|dr@f_C+Lvzbx5HGowuJ|mPCm`uyLV5;r*+_xDh znyi9+qFsKr?A-7mB!sqO{_fZj)P(~(ckYzdQMLkrBJ(X0zcQZs4@9rp7k6fA!~o&@p7vu;4!bNTM+5ZSteu>t zQ;>GneI>uGO!?JW7Ck+^dGiM3PhZ>N`caLV4{5RHYM@)9H?n8J0Y>L5Pk;t*z3Sp-kwH=EJ3l9Qes){aL>ose)6Pqn8)<}NU*6Q(HeVay- z`7lH7m9fGXp`one>wk?37B08?;7beqVO#EL-9p91b2Wh2N$0LEjkIM(AfX2WIC*2>uSVQZ@I3?OGQ@e!yZ3Bczx*YH8ARs59MG*#+yFI z8Ox0m8LsczI4tr-xXkWx?HCdUdC0%n*VPWB0=eeYspWiAs)?G)HdytI;?sU^5bNN1f`ZzN)v$qTIRf2em z-@kwN1N~PcyveC21v_$#wjYu3peCW;q>L1@isVt6`lJSym7sUh)KfP(Iy&xPp`oKA zID=!nynBJk=!NevlKO!j+Dp5ojX(PC&1KoEuc!AtN$GG1r{?ZIfByWa^ppUL{5&Qm z@XxQ>8b_o0zr3CY~Cj4sDK1~ z?8J#J;MCCufA|(9eD~kqySGeCej#d1JM-C&~)l*|wX}7oE;Uz+jN7M16fs z^VV%Sx7U%!0uOrR-?Zn(Bh}Q$v9Zip5=voVVGu|C?;dRaR9|0%rl+yU+7MmeAuyXw z5#gx~mH4H2Abf&<=(PaY7;8y=1lI4>*}Iz@r@G&x+_D~amkkFZzfu3{T$2S4503zd zAK+jqV21iwi69^@sT~i<3gO3ugbt!R6&{`6<-KFqu4?QR0gH%o28j6yWE4_$BHO~R z+Mooq!)IOQQ|4W*mBIDBh!HmZej(eQLBRArz|^2_pK3~2D!R9sKYtpF1IH?&Tw)a$ zJX>3+*Cz0Gd#tUkPnAAKLo{^~@C%M&Zi*@UwUy@4sTwAhjSGACk5D_UTB$Rvh?9tNI{q_FQcBFQJ zn>TMBeN40(k5Tf!MhRc(le(4PJ!=geBPS<^bRW3rwWJs2a#I^}#i9sg4eN^Kq=_kY zRFwQXJq%D2{-a>0`+^WNCr+I@E+9br`SWKNeTlldI!RQ@yR*M9;|&u)-4GlnTI9Pk z{TEYvi|og**^RgDLPMKvu)t~M@%QI;0NHB{?79eTAQj>e6RU*IULlH%`nqErAU9hovQ*Te1$7RgnUFfd9q?@WffL~)s zuHbFK*7KUTu$_0F-IA4+<+RYMsZ!zQj8gC;BY^|xvjyRwNTAOvG3TGJ?H-xyNKXP8 zbx~g4Pg<`uZXf`Jh@Wjw$5nJ*lF7=^*!*pz-0|A9T6&VhbZ-FO=NFn>k=2&@1BVa$ zBdHwc=H7ke$Pt38MH=_{`0>(2SJ584JO${2OlU9znMJ)#5$y0Lxf;V1rD%3Nt4>FQei54l?LRSJyAR$yxg` zS|O{>Te;)wTdf=;R$Md3ddpq^PUJNZYlc-MsPLJ^MHx%W(~H=0F!gMq@<6Iv&z(D$ z%l`LioH|Ei@)hO|IXPrJB5iHjv93zARo~jI`jlO3d4D-pj0%~yQ%27x6It!mIknjM1voYKYre@)L|E%rWyyK zAn>n_x}UC?5_4GF)L-OwviKaDqXe9HK+uME9+ehqt6HL;Lbuj5-9W4u`W#8l*fd0a zY9Gz_>^-&nR;JaVDB8KK|2wz-Z|=(I0v7Yu7jPFRH123-Xx+GKqY2&{`?MW=SB6dj zC6{ht2v@s6#_z{F_xDdt1p-wMM{(CZi(>=F;d?Dp=LY&DBI}~_Idk*lj?D3Ye`=YV z=8*7Hl$z5uc?g2Ssxu$@p;5)4Jb53dW)=$4@tKJZex%>M?kn?+3?r&7fOI_RXCO}A zME=dCya{4kd}ZYLXvDwy<|{;apw5zouBb7GPgkrem!u?KWt&-OH#9(=!&L=%FwN#Yy?x1W&E=#R+f(sWAbIj1;I*;qQ?| zUt^C?1Xc4L(UahVe-gP!yfOroW1#6F{X_^^F`(~E6Jl12^#r=07chO+@^@)rp&F|; zyVZoiYd|Y!(ak2bujAyaQ%>BowS{o>?>?UI_p0R*0ufFI1ugEZT85bfptUi`cYU8khhv+G~-%U+`p@**g&=4SrB)tV#8-O3d})(OZa zN$iQc)0+1?J30Mc^%%^}Z;0_tNn{YQOR?MA1^E{dU4!n6OTiXA3$ftfOt^tG8UfiK z`}ho}yKcS!)%DDc4_oHP+xH+!ua$n=zURnwbTkB&6eI5Lx3MuUcLy%*U?I--30BtBHHlZ8?Cd+yt!;vS{Tf(0q>0fV3y!;oN zK|z8N@ex7tr^6)nvKdN>ABy0nXO6kQE0Pt!Oq_9bN!tL9IXQ`El7XL9sgQ+pP!ssJyixbg!G3oGPbdW)8v@6Nqms z2};B^v|{Gt8)@q+<2=OPV$-!s9k__@hzoKvL3}~k*wQauI*takmd?;Eu1`4~g>$#| zbxneu0Gx57up+@_@&F%xaQs4Vp;Z*^kHKHR9-(P4>u$gSB>27CRA2uBzTGf3_Ja6* z1Wik@y$rzc0pb*aQG5m}<@@)m1a`3UaaL8`ck)V%WmB$3rjEv>piJwE9gqWrk=Oh@ z_`9lVuz|4SK{7V-Q<6waa`ZD%!3F?WiZatv5gczV5ZFYH0CV}6IKGj5+nx5>q`LG> z{{H-eAb2kqMx~?S8tE>!tyuC`)8#;&hqC;_z+&u;YR{<)1_nyANyT@6Z3ASub&37S zI-#5#7%s_U0W^LJL0jR0K5$4i3dT$wh}R~-7Ru@Jr6Ytg2{LD($FWbaV_ppqP+CT2 z8_KK?Si~<$sY>$llCk3MiG6K5_n&-i-B)35t>w8mafoR1+25q&=l>3d8`99zvnhlJ z2R}p}CVDUe+py$g$U1Hf#t)_()d~4?(P_Z$F&= zdUCg#XSRkr_;kp%ABmW@?me%qzlfKl2g?jHatG<}P?YryX#E7p|1P1r(L9y^R1f|T zL|TQk8fQ1!wA>UUVozDQzG6`!9tm1hg>u3ixv?!v-ygMLSXV++^e|ewYB1W0d`BO~ z0W|WWg%9D@XMoyLLnzC-6~H8f|AC#I-S^3p_XKZBs5amH7xEwG%cgx&{w$+D+5ddJz8SJ}Hv~Ct7a;^5zqCX-V+h-+M=)f|i zRbrPKa`ME9OBO`&&W#(R5@DQz8cHq`-}q-6FzV-`BSThY2QhhqjEWzGGOH4$sjM7m z=~sk?3U!%EUEDoT;oiMm%O5o`)&wkrO1vQS@!a(lvqKcVNH*q*uFJE=9mRRKKJP0S zg-L~zyo`EPa7RK=P@^S7TTR{*^qrg8?Cu_2a9Gh*tcT?n+*H|UZ^F32(0r$>P)UA# zU?ASXwC%&$jf32JY~;H(HZ%bD%~~~({g3yVq$>js&CJZukRHPj*7+-YY^*iC$tD%p z=^&tDb7ZgjY|LhWZKA1SXy!2}(^_;kmroZ7$xJ;GSrHB_(6;LOd>jn+_tm2Eqfev5 z{-E54x4Hfb5T4f=MpnxGrSaWN7)&$A;~UK^qpMe!D%qfOg~{}>td!c1pUmX<_~#_G z`$;2^@8k6Jbc2m%oF-24>hBj<=IcDMW4V2IkHrUnrB(X^yme(!;!;;gKO~Y|_LY0d zciDYW-7YWd6GuPtLrN^eK&z%<=cfln7*8Hc%)aC0lzd!Hq z$X5V^+ZZ7Pic7$}MNYm89csa-db05r14YrWh=>&4J{0u2PEjC2l_=%*;ZRT~fLnA=*DRs#aB-Y5LrkqHFSjz&?EWq98AdN>?yCYcS4VE!7;t4(jwp z6Wl?X+3p_Tj89LaqKy~C#1RXOEy`U$pE0kKjnD3~5jGybqPyt=^WD`OA(AZjdnbXnzUc>HZ{a^_qQUo?0xH>X&QuQr;M7!5X6|tVGnlJmh%u=23pGUwDsOYQL0X z#r0R~|4V{$&b0Z~?JcuyE)_S85ClF z<$_}w#vFlhkQ+QJKN^GWB+vsIq5{S@UU>q>gvCG-sZB8` zFR9_%O@HP>cBvdN97m7#D#WF+vR`F@^YnbTc98n9whYrrY?iU^i`o^XA?oF;Q>9Hz z+LAjua`@?5f3~#-tRx4Ti?l9qNnc!Iw>|JNh&#^vcd`OqfZ66vn+U)(x&VNAGT?_%1`6|NanhpVjg~9B;zY!r zj*Ya8rEoWhW>S+nLJoup>Gu&j87@!&eWaDEo}{t&7?^x^=XtGbexb z_uq%%E%LO48auaDDx{k0MEMv;Hn(83uZ?i8i)w4CH{tNma%{^?Y>)P$X`;0uoxwTI zY%W^>c>uH)Yyeub@5CURTGG?D=NIs}#>2yS%vs39b-`m%pvL|pQ`srMApVeF+cUPJ zr9Li!^AVQyCw)IHZDO8bG8^sUl1|*!eneuMlcklVB@u55*)UUNXqFpBLtIr*`{nic z@pbeUGJ{4>`oxs%)&dJaa0opLC|R$Gzos{4=KUt$K^DhEu%7LCSXuvcRB5KMAX=upK0&UMyjV=y?F3@ zp67-~bQjymU6r01UF&kPZA2Grpj{`VewxRxFnzyN(B!!?Z+6~wQL#|Z-meq_?5N=H z^s#5pWdj)-UmEC=BH1D6%8uUyi-j)ha<(nct8~7F2YY*a{<)c$&Crb@vN2X0zF08YDdsvVdXI!4K`Y>oSR;qspj`W|KqTlQrZk>l|$p| zzsE_D{hN@=MR-J#$>bX-9ufQ|yH6^=@&%Z#>*P;+z)Huh_o>JO;^r=*As^lNg4nw| z-78iw3ZLg)bZR7?b?b%6E@4B4*1%XW-ro`j1rD^@#$ye3?B3lL*^7<^MhLai3ici} zkOW2od2r_TS9&*`{ao8ophRRN#$ZwRAhL>8<-&OTm0_<2;KwgXfz9@oaKp96I660{ zw3!5~m8o058*R^yZ4{PC^*9AF_SSd7-1s(P)hH}MoP{L)^bZKoSUr4*hUWUL& zI|!}Q{!S5`WvcJ`iOxjCiMw^$H%VMoBT0PYsmhPOaWqk8}x<4{v3PPFYo&UXY%3$rBn_DZ8)X?^;W%I!>_!B$jVtAfi zakwEiJZ9WATmj`i2T+&v4oFr2u;Z=@ml0Gf`=MVDw}C1xxO6X$sCkRnkG+G10Pe`E z|G9a?Gab=Zk1nIB5ER;{k=lDPZv3VvD5U<;(NzA1hu?=+GhAv5#(MS@E4r_H`S>VQ z8i6bWkr!6~Z+`i_hD5d(72TMi`3*a}{P}LiXnvF%mw>Y=B^wCW`!pX;&wUwIwQ4cB zeS54RaE$SnclVl(I%Ua~`({gDS~fJ5Sf;BP75+`&a|AJa{JrS12JjUJEnTOb{mC%@ z-sh}f+@5m}ZC+_T1%&+w&J4Xu52kniD~TnIBtV!m&a=1vp@(hq4JbKr^5l?f_M5R} zZvfH@;oSOf?*5WNBW>Qw(b3ac4Elz(;2hZg_`>o|Q0Av$2z}@8{~O)`!U6*~Y3-&U z2P=-6LS7Fzf=QiSZ)<*3xQi0rI?ixzJcy3$)iZQPL6ZQtq!IDL;z?6|5b}P=pTnFl zSO`T;%(^)#K*gEFll9!o%Zml%D#&wL#NvOpbTnAR%4v69a~_@RQUzjCPrtScKE$mv z1AomA^OE2ES2|h>-0ke|Z1!qbe)DmW<@&N!DolzXR+Yx6({zin>g$b+7I$7h{9Mt; z-efIgz9i4QCl$UO9a+V1t+1VqI=i`@M=u06b9qx}6|Fl}TKfb{k-X@v$_t}KrF>{M zH8wVaM+-MFghNX{xuN&~yyxJ5!8eQ+SgCP-ownz8uQWmn9a_(0G&TYOGxuo4!`Gkkl?c2Zq?%a?P{OJq<41&RAZK9m>HYjBW(riMz%?<7Uw#_*(E9QIO z>l1##nAO$QaRgrq=NQ9{BysLE`aiAqVTsitIVuwkgpAsnC5R!L=VHw(4H zpt_5@3dBVLH;-T3ZQH|Qoxhdic9iYt{qm({IM&<)CIJ_Hx%LI;q@<)eZh3ls`?#!z z^fUCc`kzCeKYsvMJ>2w}Z~VwF%uNC-8mH1m=SQhI%(wO=mpRjhu=yHJh zW)iV`CQ|_gh!3I+c}~ZVcQr&flN{ZkUiM}xqcT{}Q7B1#cye+VXiWCkg1o#r@80Vt zjp$yTGy+R0>*VzI{ovK|I~!=Fv}-F^>`&Qlbv)3vq@$tI+Nd!;3?#Hwq?WL6p$Yg2VmfsS+7(~>8nzK}(D;LghYF6K z{!>qnpwccrJ(|~7XmyxCW^0~fVDI1#NpCOt-_WaRVVz6T&|DhrtE_A}H;Cc^C5e_` zVd2#vKXmP+3ia4?xYv;oA&zE0|TuLGp#_tF`Fy zKaR`$K@#iqu(*u$loq{@c1yZSZ>491ETVhxM=SJ-(|T`dPz7W$M*?hRJUM`Lec6m_ z(B-0mm%F7>V_RGJn9J638c|?GSUH%HQ(qLkUV8>p zY|Xr~IZQLTUR2VrxNB$F5AiLhi?|j>8jllR8xV&SBshAngQRe=e>24N;N&!I^jIR9 z{JFv>%t2i3&xew{b@_We#c%32OTCP@e&5nQsS&j6`XP6f658rvZFPG&wYCOm*~I4r z>rRhOA%x4{&+h{m*)x_u&cG*f1aO~U*CwW-_8^IP#3V(3d*!Nk8%VSc*Qnsp!xWh+szeF((+Dsp4*V5hwmHvLFQs5Tpvr zxdT1{(mO(?^0%6rnj%~jHEw`3tYWe740N&|(RjJaoF$SLnur=VT7a(1US$v&5MQd& zV#3FMJmZVh(b0)CXq@j1d|@ta33xx!k(WG5sr58s<)7NO2K8b2UKte-p3+ZClKpd5 zv!}XyA<;Wb{J0T(ZxGV%vrZPDbdB_r0k@MI?8u~BbmCvn8T+1uNAX1rU6FevKrAuV z({vsJSz6&fLwL;e-r7*yA7o4h^%{32R=la_fpyuRZPzliC^$LSre3nu$BM!9d|gqW z&$_eBJ{Dl$=Z_pC)pZQ(d~)Q~3Kh3{sW)W#1)h82*R|eUr?^jdeAkxk+ZDcO$Q~I1 zRaQ@>2G{B(Y%LfGFM!d0yuewvzi$LOgJ?Fv=K3t`z@V=*kqv7w-@qg0IUMcy23kU9 zv{sl?`B9$MLgyQdx<(bpYdj19cbepOIg8FdZG=#pZ{A9YYIxRZ<|r|h0s~3^2QMns z;RQgv#>C6}iGUU=S5e_@{qrr_!|CXoQN zARFvK6Pnn*lD4{~>_>Ll^kAs)cPUMmV^(M>4KHVj($zR$=T2*%lvw_~|LaTsCCWR6Ar0$I zWCg;FtzK)|{gxMA(fIa}Pxp;u8{j7{fMGKS0lR4snbGg&f$jtQ_C3TW>IYc}o%|hq zuz$c1Z+;fk*@{`b0K@gwrD+AGUaZ*P0e_zSJN%e{I<1yB8o$yY(sqa;|Uf!nhvf9rWJaCB-E?G6cIW~7i zuoAF)2a8?r*2HnWKQl8h36~4_+;ekt%h@s2v?TgEvL3D@t14oMM)g8Q>VM$gMiaF% zaoBm^7lgPAST50YgHEsLm5Km_2oH6{@YJG`?J98%e~iMaa)8W zpDW6)=>Cd`CiruuN}@_ozg+qz_L3hNiR2J=oGW9PZ8BNa1h^b2Z6yBN=29eb^lcYD|E`d2$)>Vw1PQ`Sz?!{s785)u;B zN6youKiW1EKY;b-@|y?fbOcz^#GbEQ_VB#sC4rlta40My5iAJc~hmAC;S`a_D{UeD!m>|Pkf zLiIzrA{peni#SZA2*M45X^JQ-zXr5}FaPB&&CaTJnVFhdr6R?}p(8u%x_J6No$!|F z&H@D!v5TSbNn-!t7~0-nZIaEYrC=Y+p`P;Ec1)HyBtnXpGer-pY1Z$u`(#{iHlhBq zB&ZsuVQAe=El*WcR9v=N(B3u-;r9AdUY|~!lvoo;Dll;=3N2iRQ5lm|GjQxtknZ%B zJ=sci@H%ujv{o>s>;}k+U$WelGz+0UF)3+`G4-_L?_s^M!09h~SNOdKx~B-)&yt2& zjVOu#8x+maW6eGIP)cU82Rg( zs!yN&T>eFduGNn4)o^h>{S~hx;~pQRrlv-G4f4olxwz$oU@ zlE3w|44T1KaLWuTr&w8~OI9yObyKBvra0)(hBu5OYcvT^bf!#sf^1OYJK=DVuqe2A z{HobC8lQZUi1_DZh!MJ`#{Ab%C{)lsj4ps5u**tdxN7`lRh?d4!c{8hIs8treBI2= zCCViRroyIt&D%0CR11f*PrDV&=K1w#y$O$d<|scO->nTJgD@zxpR`~mzJTVR+`iIs zYXAQI##WkElZV_N9t8$Xw08!D63~c7$;|Hgj~`lQIemLPoUSgp-$bw#{U%N(Kp26~rNzyhP2BPKx0#USKB_#tBov5L-o~`sN}b>)xlZp%8vLKW^9tq|s^!WMzx6 zz7Ur(fjO;`MCLl5CLw~+s&`0b`AI~yc z1VwXOTboYuc5chMFl{mle~miv!?0iqPF%8P^7E5v31;KwRGUD`Ezb?_ zJ#r*V&|OD|0kUekgS*j}IF}=0U^Fds-nq3?QQosem%~}=su#^~QvWHiGHFpJYkuDD z9{|2iGbH<)z^5+5sh0uo5($QmYrek(hMF(jd{Ms`-a(;;m#rX4yTCj&q^7o3b}~g0 ze#n#7$4yJ`_a?~h87oo3h-rG zyh^GnE4xyq$C|)U=!+M}k?UE>yQryYG36PSbml*W86tLYlVX&Rl2Hs&9x<$&dmhj= ziMPCBHVnnD?z>v%_aCQ0onx z56QPLhqQxw%lFs~LiRDuP58Q18PKI-o_kk-a~5oGgu)Dk8-(7oCO260M3%(b!5c{f z7WIx&NiHywj^ZMLYBZ;UNrr`%FGFCN^F^Nn$FaH3=iOFNyKl-US+{ z&=(3OjpWwkTLF=7Z447k2(Q(xTZtLU!nk+B&5Y>YxUAtBuPMvT%aaBIyw%mFzMbdk zN@@kUk8Q^LMD&L*+4-$4!>7>Azo-4{c;GK2dIiUULCSf09#b7yX%c zum545{unVMkd_U%(5I~8|Ez{0twK9VKgY+@3(KR%)3Ymrn?x{rfLWpxrQ~tZ5D2vv zeMFW8O>NfUx-h?{3`?aJy0yz)rS7YR@4NWC$HX_$zyefWlMi!tJf_Q3JJCHHynM)> zm{fpSK(!d0Ng}TmVlvSVEF@RUy+JF_!)b6VH|uF4TG=l7NrICUk4R+5ZoX||Lfmd3Z>9^Qlu)$z znkIzqXZlj`;*=~#w_oU+?nT2xw$jMZYD}>@BwxUD*y*m-Dk>yH^E4a_W42q5VvhjEtjIXl!veh}N!SZ;4@gOXSTmNZc0+>dU7$tZ3>a*)`*z`jOd3pH?${u8+ zmLX00TIqsdw3Htm``p8Mt`DIo96Nhfi}Y-*>WsZjILsoLb)p4*R3AQt!uJ8wVe-20 zW(Nf1fI|Ar{ZtsJ)`E2k2>2${nGYx)mLM|wW(VlISs#x!RH6!XNI0OSDX3jQ|4D-@ zM{;eYyR4Oam3<%q`GYb|Rf;i*K1pBL z)h>_YS49jD!SrWrhM82wgEl5#kuu)m9+1-`Zr@*DJO-~#*lh@U@dz;0NO&vFzo`{m zOt#vX?kPRBx|W6106VYXXO9BQ4%Is0qem~mKdw@Eo|jjJm@JY1H9E?UkGWKh{#asIYDI6r;uz?GWauuya7M=pD*P8Z-xA_;j*PI88(SzW zQU>-BLE5G2EUGRz=?T-;#93gMC*TMpZp9KfbK&L#m^@!@%GA!2acFjjcp`A8UGKdk&D5s8AS~6zBpI>j%JukP zUR_TmabPg9rd^Vlc!E%u*)wrR1stB&qLM4tG>O?(_hi|p^l>=`E-?S|-um>+ZLJui zl{f#+{2YMX&CuXQygDkPLYFhFh%HIYOP#qD>f^_X+uxU$m3;)0Ge_=$IddW@?%}$4 zL}c-pStsp@w+g-=%p6D3-)?baJ$bTjfL+9H51pGPX6DO_A1%b)iKY8qMqI zG@lTH{b*TPnWDq5%5$sJ0yz{aN79W~$B^nPcuYfFazxC#0<#f=+(p)XHE5Ozhdr1>%qHH%2RSJsc@I$gBifbk z5Yq|FW7?Fs6HWMCc=@1u>BtYGKY(EMuD)K`N*p%78o~&Q56Z&DwF`9^cXI3?F0aBp zG8nGMKqV7#^^B9#S@`29jXl49gM@{l;%ixYPcRwK5A~8DWAIB1!grpc89R>4w7|8# z1Fud&!rcx0;vf&B2dOdrgIq%JIV2wM5Be~5jJ0P60rge=kD?6NUIQ^0R<{=qk3?=V z!>qu0F|i!8=9A#bXyAV$hGh8o_-vp!<7OYEdyE=>jTHRylpLB@P*9E2PE2+X8-S1} zu0+7Y_)s3;IEp!Wuu3ny$qulINwcPnJ%9Q_b^{p^_q!m;W7UmNTblB>OZ=PJd@b7`fEc#` zlEQ%9N0b;`+^`b^=Wb9A{`PIGCl_SETrSv#lX@3y3F#dcF>wil5Q(-u7#f99dz(dD z265{d())jx8{jf9h*UM$aB>;NyT-;mp4@W&-KMi`(VX4vzU(dhjdO^!7`T^nUx z@x*g|c{}M0F>KylydQ}TWAEi5L&SJ8{PQFd0`enP=g+@o+e zotevvrC!%P!q&Zn#g@u0gBsQ^9>s_=6u7;huYw5;8F5JqCjB|cgul#&Tt>jWa^u7b;`}|SpC#sth!YF*)m2bw`Ia5~aM6j=$~-$* zr4#zHr2E7eJ7R$I`?@2>K#&h>!CX?15V}(64TxKYkbI~hzR1Ccjq`Y?`z-}20@vi6 z4niIx9usB(ANZ<4QhUMa0Jrro6il4X4@4zSwa&=QRJ!IVs6JKjl$hOX%h1LI@m9b9 z8?b)h^)7KFV1%CV?M2Bnb;Gyt7-irlLdgjeFugA0TyD@HO~1dUfWkHx&8(KBcoE!+ zV&NLJ1|!hHxl8KVDx|TlXK5Shvlzr;BK$|_co|4%jB2Pe^osZWUu69WIF@bqJ&xZE zO42|nq(o^zL{dqT45iUfQYcepijvGxhD4Ie&>&?f$`B#5h|GyZNoFZBQ>OT@P4D}8 zzyF`(s5~BS_jO(8dG5W}UTbY&LaqRL-ap#m1t53d&+Qj~ID<>R*y4~UFAV3%VCj9x=YKuxX!k1Ijk`ls8i%&(L#tA8p{9YDTP(9IzqCOo`!72t~q|{ zQQKd2X)UCufu&v!Ej&7NBpAcHzj851yjyIRGcw+yr)Lrtu~ z0GcR|m_wB|j)9e+F#)djYj+|cLMyFmYJcLJP}T|IR9ygcVC(kns~~U%w3m+DUmlr*mbxt5Lk?$Mc4X!w?=MX*M+b+OSQY%Dp_1P-zkfsTC>J8@ zIxioefJedLx0x+>M54D2dgL1E(r;bs=EGUe8$l!lc_XP75U3jwMNyBCUjgCRa0*=a zw~p$bSfg`;y#VT(5&bGUa60QlV{I1=5&aQj>@{ET`jsVA`9VzZ5*(WhRc^J1TpfHO zwLb=}ZBtk7eY`3AnE6sKbqao6@%Zn#Z|SsJV87AV>N^UBEIZQ0CFoY$DSW%H-x8yR z+qDQieeOR@&VWZ3PAa%N?~f!i_v*1Qe@b)uv;$#p4R|gGE-%%qv%CN?OMKexJ{dq_ zqDc8eH0lr#7RILcc~<>Zb%e-0BIC}}f7!NtcA!rZoqxe6Y~G zkPUZ~6n{u-82szdFLRNYOY_vCyBeFC_(>1dty|&6XP7V(=t9w!DBC(EbkzsAHTTu0 zz34lrsb8==c>CX2?X)#`{;`|Uz%3$CFxQ0$4-e1HpOSo@_@rAE1IP%P(7&>D)h0+I zluc4VQC*{B-?=$@7@AfU909qHrEG{I2r|blb8gvX%N{taIuWS`qn(VMp~&>F<3zY0 zR)YMwbc>*i%mk>vI>O#J!hgbNXalxoq@~~>WS?Iy`n0dG{kjJc%HR9a<1R6{GC@AA zvo3V`kKI-^qK=s+a_NB;7LbAszYk4c09kfWYsq&0-TU|7FqKlW@u3@oYv?1PW4^$AnlJ>)NHf>=JeY^W>4tD(hA$ zH~^To@E`EO!uNu(=WJ=Q7QPJLj4ySRtLl$TnitftrCi9%%`H$=SNmfF#xjxq3r7cT z7+Evx)^uCdm9+z2r&?A2(Io!My(H)5xDZ-t@bG*5Dr=qfS6ij}BSB(H)uB)yaVvFm z$jNv|g6Cg#p<~sQklzNuGtobe2wlZz4}On$)++wnE;ABmuqXkeN!L^3wJ6PB*L(Z{wrz)a=jwoBM7Vm zjJg_mx2w0eH%?Y|ARkL$Vo&n(f%`&z~&I6f*7eTQSkeDM(&V`|x3WWK@vMYG1AU3dNEkqWmdbA+BJA^z> zfxZcB2~?tMj4LDf2;)3m_Gkk_@RQBFpOm|iw9c#9VpSP514ob^(zFnu5Clq)wI397 zp0qB|_~H8d^`tt0G(jo@h!D`&+`I~x>av3FlhSjC^miW63B78ayYd|(wh!dI} zS3`~!&?X%RQ^5;I$ufsW9d83CUVMjEc!wBhjBGqS!Z?gK060{D>n@^C0Qg;@Iv;1v z(cOkCPfvEv0)L1a;>DRh$fxy?k~X1$uxs5dudW&x?;=RdH3e{L32ubv6dTS@x`y=L zcgx~`UljqMiy*E_%5{1pkk7L4^72wEtQReOPt<;Vu8mebInRT+m91*#Gsr{$-btvx z2}6OK&l-BjXnDGNd3n*1jnErH@Bvf}O=y)>+qk&I;poppqgND3i~yin!{u9Q#+UAN z7&`2LyekB^kj(z*SA*(6IguC$JudD&WhEtx;bpKCRYyQgXsU=T2o)Mc1_#4z&g|K< z=@i9p&c!0iLy8xE@jDAaF!**a;`QTQw9Zxhm=uJx#H&nZbU%zJ4x#*E0mrrxMg8$4 z)Adge1wS%3o@G^ORbz+~L13Rhxd?lL!n@DRnVK{3`rRArC_b?LE~FngpVrdlLIMIy zo=|uvEkdP7;vkMiYxdvL&MzRa9GQ?18tv;zLZ|ZT^Z{C6NYlt%4F_-*4*Kj^7Yo%0YdS+!}qv**@VZ|Gqe8{6kkiyX|jnGa@B3{FO$qlx;HRzggZnby} ziT&M}yvi8u6SS#Gq1f#p%$F=wjSM&MOt9k`Bx)P}n&aCRgXtnMa$3vr&$F#Ft>@^b zCfyC6iKET7FN`Qe3P`KNy7lpQE}l>9tL7jb8A`LF7VN&7<~lTCrvY@q6(G{f*RKPL zmT+?mjt{r-W+a((HN!`SL~Y zXGM4jx(?(%1Q)r3Ln9-e5Dj^f4PdIIQM=3zz8faiE#i59j(ZadQL9H1cIe|Jo(yGRs>;%zEjFB$?oQWu#SwM{XhXyVTn?5lfP@k&~~SBY1GfG3=(n9 zNb`u;1AV=RM}Zvq7%*Un<^m-4b4fVgqCvRi&PZk3YKL+W-~WFA|Fsm&2*dbJiwRsfRLKj>Z)}N(5dzeufn1-Lf zpeduV{eK_oCGZHPrd9kOvLpu#8yHFd*MQ}rzwgxh{`Yr;D>`U$M5;wc{vmu55DzBp z&Dl7dLDDGE_6YHZUI`d#1Jo&7Ex)q@tm)}#+)a*mU^_*i6$UN$4S?xO!S}1WR}Z#2 zX=>p}#R)zd5#AAI7IJfYlHx@>21w&6WLYrmR)WbIE`>Ib!$7$bEF6q>UAp)GygT|2 z&wByl$<%!%zRCNGsOpCh6UbuO+bAa|XCq)AFo`0x>!8sZ!-hpp{vj}*+-E?{BG1;& z3m_X2bAqg`--|DryxITrmr7wz#VqTL{`cF7iK}DV%7KFcsnTo&T!M~HI{kISYK3o≺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

7(u5mb2MXTJ`wVKP@ppWGsCf{|Oq&pP$+ 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

-twm22&F&i_@E$^qOp!`o+ZV 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 I>DYz;2WbqoLjV8( literal 0 HcmV?d00001 diff --git a/specification/session/gossip.tex b/specification/session/gossip.tex index ed838033..8c7a3508 100644 --- a/specification/session/gossip.tex +++ b/specification/session/gossip.tex @@ -50,6 +50,8 @@ \subsection{Schedules} topics on the network (recommended $N = 10$). Between broadcast sweeps the topic's shard is used, which scales better on large networks because each node only joins a fraction of the shards. +Appendix~\ref{sec:appendix_gossip_model} gives the analytical arrival-load +model that motivates this topic-wise schedule. \paragraph{Urgent gossip} Whenever a topic's local subject-ID occupancy changes -- a new topic is created, an incoming gossip forces a re-allocation, or a @@ -96,8 +98,9 @@ \subsection{Suppression rules}\label{sec:session_gossip_suppression} \subsection{Randomized delay windows}\label{sec:session_gossip_delays} The urgent window $W_u$ and the startup window $W_s$ are both tunable. The -recommended orders of magnitude below are derived from the analytic model in -\texttt{model/gossip\_propagation.ipynb} of the reference implementation. +recommended orders of magnitude below are derived from the mean-field model +in appendix~\ref{sec:appendix_gossip_delays}, which also gives a numerical +example for the typical local-network latency. Let $\delta_{\max}$ be the worst-case propagation and processing delay for a gossip between two nodes on the network. @@ -106,7 +109,8 @@ \subsection{Randomized delay windows}\label{sec:session_gossip_delays} \item[Urgent window $W_u$] Controls the amount of jitter inserted before an urgent gossip. If $m_u$ nodes independently schedule an urgent repair for the same event, the expected number of redundant urgent emissions is - approximately $(m_u - 1) \cdot \delta_{\max} / W_u$. + approximately $(m_u - 1) \cdot \delta_{\max} / W_u$ (see + appendix~\ref{sec:appendix_gossip_delays_urgent}). In typical deployments $m_u$ is small because a given collision or divergence is observed by only a few nodes; setting $W_u \approx \delta_{\max}$ is usually sufficient. @@ -114,8 +118,9 @@ \subsection{Randomized delay windows}\label{sec:session_gossip_delays} \item[Startup window $W_s$] Controls the amount of jitter inserted when a node comes online and schedules its first round of gossip. Wider windows reduce the probability of simultaneous emissions from many - startups but saturate quickly; recommended range is - $[\delta_{\max},\ 10 \cdot \delta_{\max}]$. + startups but saturate quickly (see + appendix~\ref{sec:appendix_gossip_delays_startup}); + recommended range is $[\delta_{\max},\ 10 \cdot \delta_{\max}]$. \end{description} On a typical local network with $\delta_{\max}$ on the order of tens of diff --git a/specification/session/parameters.tex b/specification/session/parameters.tex index 75121ac4..8ec044c7 100644 --- a/specification/session/parameters.tex +++ b/specification/session/parameters.tex @@ -38,3 +38,9 @@ \section{Parameters and sizing}\label{sec:session_parameters} chapter paraphrases; appendix~\ref{sec:appendix_proof} contains the convergence proof. Appendix~\ref{sec:appendix_rapidhash} contains the pinned rapidhash reference source. +Appendix~\ref{sec:appendix_gossip_model} derives the arrival-load model +that backs the topic-wise gossip schedule +(section~\ref{sec:session_gossip}), and +appendix~\ref{sec:appendix_gossip_delays} derives the randomized-delay +bounds for urgent and startup gossip windows +(section~\ref{sec:session_gossip_delays}). From 090d0d747eb641c642434f36661c5a4dfb356443 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 22:48:13 +0300 Subject: [PATCH 13/14] formatting --- CLAUDE.md | 66 +++++++++++++++++++++++++++++ README.md | 9 ++++ specification/transport/can/can.tex | 24 +++++++---- 3 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..8a608be7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# Cyphal specification — notes for AI agents + +Read `README.md` first; it contains the authoritative editing rules, label conventions, +source-formatting style, and build instructions. This file captures the project-specific +context that is easy to miss on a cold read. + +## Key external sources of truth + +These live outside this repository but are the authoritative references for anything +the spec says about the session layer: + +- -- the C reference implementation, design notes, formal models and proofs. +- -- the Cyphal/CAN transport layer C reference implementation. +- -- the Cyphal/UDP transport layer C reference implementation. + +When working on the specification, ensure that these repositories are cloned in the outer directory (`../`); +if not, clone them and check out the main branch. + +Numerical constants (moduli, tag widths, header sizes, etc.) and test vectors in the spec are generated from these +sources and must reproduce bit-for-bit. +When adding or revising a test vector, regenerate it from the reference C implementation. + +## Repository layout + +``` +./ +├── README.md # authoritative editing guide +├── CLAUDE.md # this file +├── compile.sh # builds the PDF +├── clean.sh # removes build artifacts +└── specification/ + ├── Cyphal_Specification.tex # root document; \input's every chapter + ├── cyphaldoc.cls # custom document class; defines remark, CyphalSimpleTable, etc. + ├── introduction/introduction.tex + ├── session/ # main focus of v1.1; composed of several chapter-local files + ├── transport/ + └── appendices/ # proofs, TLA+, formal models, references, ... +``` + +A chapter directory contains one main `.tex` file that `\input`s its subordinate files. + +## Building + +```sh +./compile.sh +``` + +Ensure it builds cleanly and the PDF looks sensible before declaring the work done. + +## Style rules that are easy to forget + +Most of the house style lives in `README.md`. A few points warrant extra emphasis: + +- **Be brief.** v1.0 was too verbose. The spec must be tight: push rationale into `remark` boxes, + avoid restating transport-layer definitions, and cross-reference rather than repeat. +- **No API description.** Describe observable wire behavior and state transitions only. + Words like "future" are allowed as conceptual handles when describing asynchronous + state transitions, but they are not API promises. +- **Lowercase shall / should / may.** The document uses lowercase RFC 2119 keywords in prose. +- **ASCII only inside `minted` bodies.** The `minted` listings use `inputenc` under `pdflatex`, + which rejects Unicode characters like `∪`, `≤`, `→`. + Use ASCII substitutes (`union`, `<=`, `->`) or TeX math in surrounding prose instead. + +Heavy rationale, the CRDT proof, the TLA$^+$ listing, the rapidhash reference +implementation, the eviction solver, and the gossip analytical models all live under +`specification/appendices/` and are referenced from the main body. diff --git a/README.md b/README.md index ddd0f6a0..14be9168 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,15 @@ For example, one could refer to a figure contained inside the chapter "Chapter" The above rules are crucial to follow to keep cross-references usable. Without strict conventions in place, they quickly become unmanageable. +To caption a `CyphalSimpleTable`, put the label inside the caption argument, NOT inside the table body: + +```tex +\begin{CyphalSimpleTable}[wide]{Caption of the table\label{table:chapter_my_table}}{|l l X|} + Column A & Column B & Column C \\ + ... +\end{CyphalSimpleTable} +``` + ### Source formatting - One level of nesting shall be indented with 4 spaces. Tabs shall not be used anywhere in the source. diff --git a/specification/transport/can/can.tex b/specification/transport/can/can.tex index bc2379d0..5bfbffc2 100644 --- a/specification/transport/can/can.tex +++ b/specification/transport/can/can.tex @@ -185,8 +185,10 @@ \subsection{CAN ID field} \caption{CAN ID bit layout}\label{fig:transport_can_id_structure} \end{figure} -\begin{CyphalSimpleTable}[wide]{CAN ID bit fields for 16-bit v1.1 message transfers}{|l l l X|} - \label{table:transport_can_id_fields_message_transfer_v11} +\begin{CyphalSimpleTable}[wide]{% + CAN ID bit fields for 16-bit v1.1 message transfers% + \label{table:transport_can_id_fields_message_transfer_v11}% +}{|l l l X|} Field & Width & Valid values & Description \\ Transfer priority & 3 & $[0, 7]$ (any) & Section~\ref{sec:transport_transfer_priority}. \\ @@ -204,8 +206,10 @@ \subsection{CAN ID field} Source node-ID & 7 & $[0, 127]$ (any) & Node-ID of the origin. \\ \end{CyphalSimpleTable} -\begin{CyphalSimpleTable}[wide]{CAN ID bit fields for 13-bit v1.0 message transfers}{|l l l X|} - \label{table:transport_can_id_fields_message_transfer_v10} +\begin{CyphalSimpleTable}[wide]{% + CAN ID bit fields for 13-bit v1.0 message transfers% + \label{table:transport_can_id_fields_message_transfer_v10}% +}{|l l l X|} Field & Width & Valid values & Description \\ Transfer priority & 3 & $[0, 7]$ (any) & Section~\ref{sec:transport_transfer_priority}. \\ @@ -230,8 +234,10 @@ \subsection{CAN ID field} section~\ref{sec:transport_can_source_node_pseudo_id}. \\ \end{CyphalSimpleTable} -\begin{CyphalSimpleTable}[wide]{CAN ID bit fields for service transfers}{|l l l X|} - \label{table:transport_can_id_fields_service_transfer} +\begin{CyphalSimpleTable}[wide]{% + CAN ID bit fields for service transfers% + \label{table:transport_can_id_fields_service_transfer}% +}{|l l l X|} Field & Width & Valid values & Description \\ Transfer priority & 3 & $[0, 7]$ (any) & Section~\ref{sec:transport_transfer_priority}. \\ @@ -289,8 +295,10 @@ \subsubsection{Session header reconstruction}\label{sec:transport_can_v10_header default values for a best-effort message. The resulting message shall be otherwise indistinguishable from an equivalent message originated by a v1.1 node on the same pinned topic. -\begin{CyphalSimpleTable}{Session header field values for reconstructed 13-bit message transfers}{|l X|} - \label{table:transport_can_v10_header_reconstruction} +\begin{CyphalSimpleTable}{% + Session header field values for reconstructed 13-bit message transfers% + \label{table:transport_can_v10_header_reconstruction}% +}{|l X|} Field & Value \\ Header type & Message, best-effort. \\ From f6a311c513cdd4006e41a693ee67e08ad4dc114a Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sun, 12 Apr 2026 23:07:40 +0300 Subject: [PATCH 14/14] formatting --- specification/session/names.tex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specification/session/names.tex b/specification/session/names.tex index c7a51168..158a74a6 100644 --- a/specification/session/names.tex +++ b/specification/session/names.tex @@ -124,6 +124,7 @@ \subsection{Resolution examples} supplied. The pin on a rule's \texttt{to} string, if present, is honoured instead. +{\footnotesize \begin{CyphalSimpleTable}[wide]{Name resolution with remap}{|l l l l l r X|} Name & From & To & Namespace & Home & Pin & Remark \\ \texttt{foo/bar} & \texttt{foo/bar} & \texttt{zoo} & \texttt{ns} & \texttt{me} & -- & Relative remap; resolved name is \texttt{ns/zoo}. \\ @@ -132,6 +133,7 @@ \subsection{Resolution examples} \texttt{foo/bar} & \texttt{foo/bar} & \texttt{/zoo} & \texttt{ns} & \texttt{me} & -- & Absolute remap; namespace is ignored; resolved name is \texttt{zoo}. \\ \texttt{foo/bar} & \texttt{foo/bar} & \texttt{\textasciitilde/zoo} & \texttt{ns} & \texttt{me} & -- & Homeful remap; the tilde expands to \texttt{me}; resolved name is \texttt{me/zoo}. \\ \end{CyphalSimpleTable} +} The following inputs cause resolution to fail:

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