From 8652e90bfc37c8f631d1838c79878c310a781fdb8d8d3edbd8678cd85e4e1f2e Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Fri, 18 Oct 2024 14:55:25 +0300 Subject: [PATCH] More documentation --- cyac/README | 26 ----- cyac/cmd/lib/printai.c | 15 +++ cyac/cmd/test-vector/test-vector.c | 11 ++- cyac/doc/.gitignore | 2 + cyac/doc/atom.texi | 29 ++++++ cyac/doc/clean | 3 + cyac/doc/cmd.texi | 29 ++++++ cyac/doc/cyac.info.do | 6 ++ cyac/doc/docstringer.log.do | 3 + cyac/doc/docstringer.pl | 85 +++++++++++++++++ cyac/doc/err.texi | 7 ++ cyac/doc/index.texi | 51 ++++++++++ cyac/doc/install.texi | 12 +++ cyac/doc/items.texi | 24 +++++ cyac/doc/tai64.texi | 13 +++ cyac/lib/dec.h | 122 +++++++++++++----------- cyac/lib/dectai.c | 28 ++++-- cyac/lib/dectai.h | 22 ++++- cyac/lib/enc.c | 4 +- cyac/lib/enc.h | 119 ++++++++++++++++++++++- cyac/lib/enctai.c | 30 ++++-- cyac/lib/enctai.h | 18 +++- cyac/lib/err.h | 10 +- cyac/lib/frombe.h | 1 + cyac/lib/items.c | 4 +- cyac/lib/items.h | 148 ++++++++++++++++++++++++----- cyac/lib/leapsecs.c | 3 +- cyac/lib/leapsecs.h | 9 +- cyac/lib/tobe.h | 1 + tyac/README | 2 +- 30 files changed, 689 insertions(+), 148 deletions(-) create mode 100644 cyac/doc/.gitignore create mode 100644 cyac/doc/atom.texi create mode 100755 cyac/doc/clean create mode 100644 cyac/doc/cmd.texi create mode 100644 cyac/doc/cyac.info.do create mode 100644 cyac/doc/docstringer.log.do create mode 100755 cyac/doc/docstringer.pl create mode 100644 cyac/doc/err.texi create mode 100644 cyac/doc/index.texi create mode 100644 cyac/doc/install.texi create mode 100644 cyac/doc/items.texi create mode 100644 cyac/doc/tai64.texi diff --git a/cyac/README b/cyac/README index c303e89..555f049 100644 --- a/cyac/README +++ b/cyac/README @@ -1,28 +1,2 @@ -C99 implementation of the YAC codec. - -* No FLOAT*, INTs greater than 64-bit, TAI64NA and nanoseconds support. - They are stored/decoded just as a raw value -* Actual negative integer maximal size is -2^63-1 - -dec.* contains the actual decoder, YACAtomDecode. Basically that is all -you need in most cases. YACItemType is high-level type of the atom you -decoded. YACErr tells about the possible decoding error. YACAtom is the -decoded atom. - -dectai.* contains converter from TAI64 to UTC. -leapsecs.* contains the leap seconds database itself. iter.* contains helpers that may pass over the iterables. items.* contains a non-streamable parser. - -enc.* contains encoders for various atoms. Containers and blobs must -be made manually, by finishing them with proper EOC/BIN and sorting the -keys. enctai.* contains converter from UTC to TAI64. - -cmd/test-vector.c is the same structure as in tyac/test-vector.tcl. -Their outputs must be the same. - -Project uses redo (http://cr.yp.to/redo.html) build system, but it is -trivial to compile and install manually. conf/* contains the compiler -and linker configuration options. - -cyac is free software: see the file COPYING.LESSER for copying conditions. diff --git a/cyac/cmd/lib/printai.c b/cyac/cmd/lib/printai.c index f762889..afc4e93 100644 --- a/cyac/cmd/lib/printai.c +++ b/cyac/cmd/lib/printai.c @@ -30,6 +30,21 @@ PrintTAI64(const unsigned char *buf, const size_t len) struct timespec tv; enum YACErr err = YACTAI64ToTimespec(&tv, buf, len); #pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-enum" + switch (err) { +#pragma clang diagnostic pop + case YACErrNo: + break; + case YACErrTAI64InPast: + hex = HexEnc(buf, len); + fprintf(stdout, "in past: %s)\n", hex); + free(hex); + return YACErrNo; + default: + return err; + } + err = YACTimespecToUTC(&tv); +#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" switch (err) { #pragma clang diagnostic pop diff --git a/cyac/cmd/test-vector/test-vector.c b/cyac/cmd/test-vector/test-vector.c index ddf9ea5..8230e36 100644 --- a/cyac/cmd/test-vector/test-vector.c +++ b/cyac/cmd/test-vector/test-vector.c @@ -155,14 +155,15 @@ main(int argc, char **argv) &Got, buf + Off, len - Off, (const unsigned char *)"dates", 5)); adder(YACAtomListEncode(&Got, buf + Off, len - Off)); // .dates { - struct timespec tv; - tv.tv_sec = 1234567890; + struct timespec ts; + ts.tv_sec = 1234567890; + assert(YACTimespecToTAI(&ts)); unsigned char tai[12] = {0}; - assert(YACTimespecToTAI64(tai, &tv)); + assert(YACTimespecToTAI64(tai, &ts)); adder(YACAtomTAI64Encode(&Got, buf + Off, len - Off, tai, 8)); - tv.tv_nsec = 456000; - assert(YACTimespecToTAI64(tai, &tv)); + ts.tv_nsec = 456000; + assert(YACTimespecToTAI64(tai, &ts)); adder(YACAtomTAI64Encode(&Got, buf + Off, len - Off, tai, 12)); adder(YACAtomRawEncode( diff --git a/cyac/doc/.gitignore b/cyac/doc/.gitignore new file mode 100644 index 0000000..d86ddd4 --- /dev/null +++ b/cyac/doc/.gitignore @@ -0,0 +1,2 @@ +/build/ +/docstringer.log diff --git a/cyac/doc/atom.texi b/cyac/doc/atom.texi new file mode 100644 index 0000000..0ad1bff --- /dev/null +++ b/cyac/doc/atom.texi @@ -0,0 +1,29 @@ +@node Atom +@cindex atom +@unnumbered Atom + +@anchor{YACAtom} +@DOCSTRING YACAtom@ + +@anchor{YACItemType} +@DOCSTRING YACItemType@ + +@DOCSTRING YACAtomDecode@ + +@DOCSTRING YACAtomEOCEncode@ +@anchor{YACAtomNILEncode} +@DOCSTRING YACAtomNILEncode@ +@DOCSTRING YACAtomBoolEncode@ +@DOCSTRING YACAtomUUIDEncode@ +@DOCSTRING YACAtomUintEncode@ +@DOCSTRING YACAtomSintEncode@ +@DOCSTRING YACAtomListEncode@ +@DOCSTRING YACAtomMapEncode@ +@DOCSTRING YACAtomBlobEncode@ +@DOCSTRING YACAtomStrEncode@ +@anchor{YACAtomBinEncode} +@DOCSTRING YACAtomBinEncode@ +@anchor{YACAtomChunkEncode} +@DOCSTRING YACAtomChunkEncode@ +@DOCSTRING YACAtomTAI64Encode@ +@DOCSTRING YACAtomRawEncode@ diff --git a/cyac/doc/clean b/cyac/doc/clean new file mode 100755 index 0000000..a972274 --- /dev/null +++ b/cyac/doc/clean @@ -0,0 +1,3 @@ +#!/bin/sh -e + +rm -fr build cyac.info docstringer.log diff --git a/cyac/doc/cmd.texi b/cyac/doc/cmd.texi new file mode 100644 index 0000000..e86a9c2 --- /dev/null +++ b/cyac/doc/cmd.texi @@ -0,0 +1,29 @@ +@node Commands +@unnumbered Commands + +@table @command + +@pindex cmd/test-vector +@item cmd/test-vector +Example program that forms the same test vector as +@file{tyac/test-vector.tcl} in a streaming way. + +@pindex cmd/print-itered +@item cmd/test-itered +Example program that decodes the provided file with iterated +functions, that uses pretty printer as a callback. + +@pindex cmd/print-items +@item cmd/test-items +Example program that decodes the provided file into @ref{Items, items} +and pretty prints it. It respects @env{$NO_COLOR} environment variable. +If @env{$DO_ENCODE} is specified, then it encodes the decoded items into +memory again and compares if it has the same representation. + +@pindex cmd/cer-verify +@item cmd/test-verify +Example program that accepts a list of certificate files. First one is +that ought to be verified. Currently it installs GOST R 3410-2012 +cryptographic handlers for signature verification. + +@end table diff --git a/cyac/doc/cyac.info.do b/cyac/doc/cyac.info.do new file mode 100644 index 0000000..5553b54 --- /dev/null +++ b/cyac/doc/cyac.info.do @@ -0,0 +1,6 @@ +redo-ifchange docstringer.log +cd build +MAKEINFO_OPTS="$MAKEINFO_OPTS --set-customization-variable SECTION_NAME_IN_TITLE=1" +MAKEINFO_OPTS="$MAKEINFO_OPTS --set-customization-variable TREE_TRANSFORMATIONS=complete_tree_nodes_menus" +MAKEINFO_OPTS="$MAKEINFO_OPTS --set-customization-variable ASCII_PUNCTUATION=1" +makeinfo $MAKEINFO_OPTS --output ../$3 index.texi diff --git a/cyac/doc/docstringer.log.do b/cyac/doc/docstringer.log.do new file mode 100644 index 0000000..148fbe9 --- /dev/null +++ b/cyac/doc/docstringer.log.do @@ -0,0 +1,3 @@ +redo-ifchange docstringer.pl *.texi ../lib/*.h ../lib/pki/*.h +rm -rf build +./docstringer.pl -v build ../lib . diff --git a/cyac/doc/docstringer.pl b/cyac/doc/docstringer.pl new file mode 100755 index 0000000..8e9764d --- /dev/null +++ b/cyac/doc/docstringer.pl @@ -0,0 +1,85 @@ +#!/usr/bin/env perl +# Simple script for substitute placeholders in Texinfo files with +# docstring values found in source code. +# +# If you C source code contains: +# +# // TEXINFO: SomeKey +# // ... +# // last line of docstring +# some C code +# +# Then under "SomeKey" you will have the whole docstring (starting from +# the line after "TEXINFO", till "last line"). You can include its +# contents (excluding comment characters) in your .texi files placing +# +# @DOCSTRING SomeKey@ + +use strict; +use warnings; + +my $verbose = 0; +if ($ARGV[0] eq "-v") { + $verbose = 1; + shift @ARGV; +} +my $outDir = $ARGV[0]; +my @srcDirs = split /:/, $ARGV[1]; +my @docDirs = split /:/, $ARGV[2]; +my @exts = split / /, (defined $ENV{EXTS}) ? $ENV{EXTS} : "c h h.in"; + +my %docstrings; + +foreach my $srcDir (@srcDirs) { + print "src: $srcDir\n" if $verbose; + opendir(my $dir, $srcDir) or die "can not open $srcDir"; + foreach my $fn (readdir $dir) { + next unless grep { $fn =~ /\.$_$/ } @exts; + open(my $src, "<:encoding(UTF-8)", "$srcDir/$fn") or + die "can not open $srcDir/$fn"; + my $curEntry; + while (<$src>) { + chomp; + if (not /^\/\//) { + if (defined $curEntry) { + undef $curEntry; + } + next; + } + s/^\/\/ ?//; + if (/^TEXINFO: (.*)$/) { + $curEntry = $1; + $docstrings{$curEntry} = ""; + print "\t$fn: $curEntry\n" if $verbose; + next; + } + ($docstrings{$curEntry} .= "$_\n") if defined $curEntry; + } + close $src; + } + closedir $dir; +} + +foreach my $docDir (@docDirs) { + print "doc: $docDir\n" if $verbose; + opendir(my $dir, $docDir) or die "can not open $docDir"; + ($docDir .= "/") unless $docDir =~ /\/$/; + ($docDir = "") if $docDir eq "./"; + foreach my $fn (readdir $dir) { + next unless $fn =~ /\.texi$/; + $fn = $docDir . $fn; + open(my $src, "<:encoding(UTF-8)", $fn) or die "can not open $fn"; + mkdir "$outDir/$docDir"; + open(my $dst, ">:encoding(UTF-8)", "$outDir/$fn") or + die "can not open $outDir/$fn"; + while (<$src>) { + (print($dst $_) and next) unless /^\s*\@DOCSTRING (.*)\@$/; + print "\t$fn: $1\n" if $verbose; + die "unable to find docstring: $1" unless defined $docstrings{$1}; + print $dst $docstrings{$1}; + } + close $src; + close $dst; + } + closedir $dir; +} diff --git a/cyac/doc/err.texi b/cyac/doc/err.texi new file mode 100644 index 0000000..c610cf8 --- /dev/null +++ b/cyac/doc/err.texi @@ -0,0 +1,7 @@ +@node Errors +@cindex errors +@unnumbered Errors + +@DOCSTRING YACErr@ + +@DOCSTRING YACErr2Str@ diff --git a/cyac/doc/index.texi b/cyac/doc/index.texi new file mode 100644 index 0000000..32bd88d --- /dev/null +++ b/cyac/doc/index.texi @@ -0,0 +1,51 @@ +\input texinfo +@settitle cyac + +@copying +Copyright @copyright{} 2024 @email{stargrave@@stargrave.org, Sergey Matveev} +@end copying + +@node Top +@top cyac + +C99 implementation of the @url{http://www.yac.cypherpunks.su, YAC} +codec. + +@itemize +@item No floats support. +@item No unsigned integers support bigger than 64-bit. +@item Negative integers are supported only up to 63-bits. +@item No TAI64NA support. +@end itemize + +cyac is +@url{https://www.gnu.org/philosophy/pragmatic.html, copylefted} +@url{https://www.gnu.org/philosophy/free-sw.html, free software} +licenced under @url{https://www.gnu.org/licenses/lgpl-3.0.html, GNU LGPLv3}. + +@insertcopying + +@include install.texi +@include cmd.texi + +@include err.texi +@include atom.texi +@include tai64.texi +@include items.texi + +@node Indices +@unnumbered Indices + +@node Concepts Index +@section Concepts Index +@printindex cp + +@node Programs Index +@section Programs Index +@printindex pg + +@node Variables Index +@section Variables Index +@printindex vr + +@bye diff --git a/cyac/doc/install.texi b/cyac/doc/install.texi new file mode 100644 index 0000000..cb7861a --- /dev/null +++ b/cyac/doc/install.texi @@ -0,0 +1,12 @@ +@node Install +@unnumbered Install + +Project uses @url{http://cr.yp.to/redo.html, redo} build system. There +are plenty of implementation choices, but +@url{http://www.goredo.cypherpunks.su, goredo} is recommended one. + +However it should be trivial to compile and install it manually, as it +is ordinary C project. + +Whole configuration is in @file{conf/} directory, where you can override +default command invocations and paths. diff --git a/cyac/doc/items.texi b/cyac/doc/items.texi new file mode 100644 index 0000000..d80a750 --- /dev/null +++ b/cyac/doc/items.texi @@ -0,0 +1,24 @@ +@node Items +@cindex items +@unnumbered Items + +Streamable decoding by one atom is not very convenient in many cases. +There is ability to recursively decode the whole structures. + +@anchor{YACItems} +@DOCSTRING YACItems@ +@anchor{YACItem} +@DOCSTRING YACItem@ +@DOCSTRING YACItemsInit@ +@anchor{YACItemsGrow} +@DOCSTRING YACItemsGrow@ +@anchor{YACItemsParse} +@DOCSTRING YACItemsParse@ +@DOCSTRING YACItemsEncode@ +@DOCSTRING YACItemsGetByKeyLen@ +@anchor{YACItemsGetByKey} +@DOCSTRING YACItemsGetByKey@ +@DOCSTRING YACItemsGetByKeyAndType@ +@DOCSTRING YACStrEqual@ +@DOCSTRING YACListHasOnlyType@ +@DOCSTRING YACMapHasOnlyType@ diff --git a/cyac/doc/tai64.texi b/cyac/doc/tai64.texi new file mode 100644 index 0000000..17a3d0a --- /dev/null +++ b/cyac/doc/tai64.texi @@ -0,0 +1,13 @@ +@node Datetime +@cindex TAI64 +@cindex datetime +@unnumbered Datetime + +YAC uses @url{http://cr.yp.to/libtai/tai64.html, TAI64} for datetime +objects. There are helpers to convert it to UTC and vice versa. + +@DOCSTRING YACTAI64ToTimespec@ +@DOCSTRING YACTimespecToUTC@ +@DOCSTRING YACTimespecToTAI64@ +@DOCSTRING YACTimespecToTAI@ +@DOCSTRING YACLeapsecs@ diff --git a/cyac/lib/dec.h b/cyac/lib/dec.h index 1930301..c01ad30 100644 --- a/cyac/lib/dec.h +++ b/cyac/lib/dec.h @@ -6,6 +6,27 @@ #include "err.h" +// TEXINFO: YACItemType +// @deftp {Data type} {enum YACItemType} +// High-level type of the atom. +// @itemize +// @item YACItemEOC +// @item YACItemNIL +// @item YACItemFalse +// @item YACItemTrue +// @item YACItemUUID +// @item YACItemPint -- positive integer +// @item YACItemNint -- negative integer +// @item YACItemList +// @item YACItemMap +// @item YACItemBlob +// @item YACItemFloat +// @item YACItemTAI64 +// @item YACItemBin +// @item YACItemStr +// @item YACItemRaw -- raw value, non-decodable by the library +// @end itemize +// @end deftp enum YACItemType { YACItemEOC = 0, YACItemNIL = 1, @@ -24,65 +45,39 @@ enum YACItemType { YACItemRaw, }; -// @deftypevar struct YACAtom -// @code{.off}set is the length of the whole atom. -// @code{.tag} contains the real type of the atom, its first byte. -// @code{.typ} contains high-level atom type. -// All other fields are interpreted according to the type: +// TEXINFO: YACAtom +// @deftp {Data type} {struct YACAtom} +// Basic unit of the library, describing single TLV-like value. // @table @code -// @item YACItemEOC -// No additional fields are used. -// @item YACItemNIL -// No additional fields are used. -// @item YACItemFalse -// No additional fields are used. -// @item YACItemTrue -// No additional fields are used. -// @item YACItemUUID -// @code{.v.uuid} contains the 16-byte UUID value. -// @item YACItemPint -// @code{.v.pint} contains positive integer's value. -// @item YACItemNint -// @code{.v.nint} contains negative integer's value. -// @item YACItemList -// No additional fields are used, if parsed through -// @code{YACAtomDecode()}. -// If parsed through the @code{YACItemsParse()}, then -// @code{.v.list.len} contains the number of elements in a list. -// @code{.v.list.head} is the items pool index of the first element. -// @code{.v.list.head} equals to -1, if list is empty. -// @item YACItemMap -// No additional fields are used, if parsed through -// @code{YACAtomDecode()}. -// If parsed through the @code{YACItemsParse()}, then -// @code{.v.list.len} contains the number of elements in a map, -// @code{.v.list.head} is the items pool index of the first element's -// key. Key's item @code{.next} points to the value, that points to -// the next key, and so on. -// @code{.v.list.head} equals to -1, if map is empty. -// @item YACItemBlob -// @code{.v.blob.chunkLen} contains the length of the chunk. -// If parsed through the @code{YACItemsParse()}, then -// @code{.v.blob.chunks} contains the number of chunks, including -// the terminating binary string, that may be empty. -// @item YACItemFloat -// @code{.v.TODO} contains float's value. -// @item YACItemTAI64 -// @code{.v.str.len} contains the length of the TAI64, that is -// either 8, 12, or 16 bytes long. @code{.v.str.ptr} points to the -// value itself. -// @item YACItemBin -// @code{.v.str.len} contains the length of the string. -// @code{.v.str.ptr} points to the value itself. -// @item YACItemStr -// @code{.v.str.len} contains the length of the string. -// @code{.v.str.ptr} points to the value itself. -// @item YACItemRaw -// @code{.tag} is the raw value's tag, its first byte. -// @code{.v.str.len} contains the length of its value. -// @code{.v.str.ptr} points to its value. +// @item .tag +// Real value of the atom's tag. May be used during debugging or +// when constructing raw value. +// @item .typ +// High level @ref{YACItemType, type} of the atom's value. As a rule +// you should look solely on it. +// @item .v.uuid +// Pointer to 16-byte UUID value. +// @item .v.pint +// Value of the positive integer. +// @item .v.nint +// Value of the negative integer. +// @item .v.list +// That value is filled only when dealing with @ref{Items, items} +// for lists and maps. +// Its @code{.head} is the index in items list to the first element +// inside the container. If equals to 0, then it is empty. Its +// @code{.len} contains number of the items (key-values pairs in +// case of map) inside it. +// @item .v.blob +// That value is filled only when dealing with @ref{Items, items}. +// @code{.chunkLen} is the length of the chunk. @code{.chunks} is +// the number of chunks, including the terminating binary string. +// @item .v.str +// @code{.ptr} points to the start of the binary/UTF-8 string. +// @code{.len} is its length in bytes. TAI64 datetimes are stored as +// strings too. Raw values use it as a payload. // @end table -// @end deftypevar +// @end deftp struct YACAtom { union { const unsigned char *uuid; @@ -106,10 +101,21 @@ struct YACAtom { char _pad[3]; }; +// TEXINFO: YACAtomDecode +// @deftypefun {enum YACErr} YACAtomDecode( @ +// size_t *got, @ +// struct YACAtom *atom, @ +// const unsigned char *buf, @ +// const size_t len) +// Decode the @ref{YACAtom, @var{atom}} from provided @var{buf} of +// length @var{len}. If error is @code{YACErrNotEnough}, then @var{got} +// will contain how many bytes is missing in the buffer for full atom +// decoding. Otherwise it will contain the full atom's size. +// @end deftypefun enum YACErr YACAtomDecode( size_t *got, - struct YACAtom *atom, + struct YACAtom *, const unsigned char *buf, const size_t len); diff --git a/cyac/lib/dectai.c b/cyac/lib/dectai.c index f36b993..642dc8b 100644 --- a/cyac/lib/dectai.c +++ b/cyac/lib/dectai.c @@ -23,7 +23,7 @@ #include "leapsecs.h" enum YACErr -YACTAI64ToTimespec(struct timespec *tv, const unsigned char *buf, const size_t len) +YACTAI64ToTimespec(struct timespec *ts, const unsigned char *buf, const size_t len) { if (len > 12) { return YACErrTAI64BadAsec; @@ -33,11 +33,26 @@ YACTAI64ToTimespec(struct timespec *tv, const unsigned char *buf, const size_t l if (v <= 0) { return YACErrTAI64InPast; } + if (((uint64_t)1 << ((sizeof(time_t) * 8) - 1)) < (uint64_t)v) { + return YACErrTAI64TooBig; + } + ts->tv_sec = (time_t)v; + if (len > 8) { + uint32_t n = (uint32_t)yacFromBE(buf + 8, 4); + ts->tv_nsec = n; + } + return YACErrNo; +} + +enum YACErr +YACTimespecToUTC(struct timespec *ts) +{ + int64_t v = (int64_t)(ts->tv_sec); { int64_t diff = 0; for (size_t i = 0; i < YACLeapsecsN; i++) { if (v > (YACLeapsecs[i] + (int64_t)YACLeapsecsN - (int64_t)i)) { - diff = YACLeapsecs1972 + (int64_t)YACLeapsecsN - (int64_t)i; + diff = 10 + (int64_t)YACLeapsecsN - (int64_t)i; break; } } @@ -46,13 +61,6 @@ YACTAI64ToTimespec(struct timespec *tv, const unsigned char *buf, const size_t l if (v <= 0) { return YACErrTAI64InPast; } - if (((uint64_t)1 << ((sizeof(time_t) * 8) - 1)) < (uint64_t)v) { - return YACErrTAI64TooBig; - } - tv->tv_sec = (time_t)v; - if (len > 8) { - uint32_t n = (uint32_t)yacFromBE(buf + 8, 4); - tv->tv_nsec = n; - } + ts->tv_sec = (time_t)v; return YACErrNo; } diff --git a/cyac/lib/dectai.h b/cyac/lib/dectai.h index efc573b..4070121 100644 --- a/cyac/lib/dectai.h +++ b/cyac/lib/dectai.h @@ -1,4 +1,3 @@ - #ifndef YAC_DECTAI_H #define YAC_DECTAI_H @@ -7,7 +6,26 @@ #include "err.h" +// TEXINFO: YACTAI64ToTimespec +// @deftypefun {enum YACErr} YACTAI64ToTimespec( @ +// struct timespec *tv, const unsigned char *buf, const size_t len) +// Convert TAI64* value from @var{buf} to @var{ts} structure. +// @code{YACErrTAI64InPast} error is returned if value represents time +// before 1970. Some systems support those dates, but not guaranteed to. +// @code{YACErrTAI64TooBig} error is returned when time is out of bounds +// of @code{time_t} representation. @code{YACErrTAI64BadAsec} is +// returned if TAI64NA is provided, because currently no systems support +// attoseconds. +// @end deftypefun +enum YACErr +YACTAI64ToTimespec(struct timespec *ts, const unsigned char *buf, const size_t len); + +// TEXINFO: YACTimespecToUTC +// @deftypefun {enum YACErr} YACTimespecToUTC(struct timespec *ts) +// Convert TAI stored in @var{ts} structure to UTC. @code{YACErrTAI64InPast} +// error is returned if value represents time before 1970. +// @end deftypefun enum YACErr -YACTAI64ToTimespec(struct timespec *tv, const unsigned char *buf, const size_t len); +YACTimespecToUTC(struct timespec *ts); #endif // YAC_DECTAI_H diff --git a/cyac/lib/enc.c b/cyac/lib/enc.c index 63b350b..737a654 100644 --- a/cyac/lib/enc.c +++ b/cyac/lib/enc.c @@ -102,7 +102,7 @@ yacAtomIntEncode(size_t *len, unsigned char *buf, const size_t cap, const uint64 } bool -YACAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, uint64_t v) +YACAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, const uint64_t v) { bool ok = yacAtomIntEncode(len, buf, cap, v); if (ok) { @@ -112,7 +112,7 @@ YACAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, uint64_t v) } bool -YACAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, int64_t v) +YACAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, const int64_t v) { if (v >= 0) { return YACAtomUintEncode(len, buf, cap, (uint64_t)v); diff --git a/cyac/lib/enc.h b/cyac/lib/enc.h index fcb551d..0282f88 100644 --- a/cyac/lib/enc.h +++ b/cyac/lib/enc.h @@ -5,15 +5,43 @@ #include #include +// TEXINFO: YACAtomEOCEncode +// @deftypefun bool YACAtomEOCEncode( @ +// size_t *len, unsigned char *buf, const size_t cap) +// Encode EOC atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool YACAtomEOCEncode(size_t *len, unsigned char *buf, const size_t cap); +// TEXINFO: YACAtomNILEncode +// @deftypefun bool YACAtomNILEncode( @ +// size_t *len, unsigned char *buf, const size_t cap) +// Encode NUL atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool YACAtomNILEncode(size_t *len, unsigned char *buf, const size_t cap); +// TEXINFO: YACAtomBoolEncode +// @deftypefun bool YACAtomBoolEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, const bool v) +// Encode boolean atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool YACAtomBoolEncode(size_t *len, unsigned char *buf, const size_t cap, const bool v); +// TEXINFO: YACAtomUUIDEncode +// @deftypefun bool YACAtomUUIDEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, const unsigned char v[16]) +// Encode UUID atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool YACAtomUUIDEncode( size_t *len, @@ -21,18 +49,58 @@ YACAtomUUIDEncode( const size_t cap, const unsigned char v[16]); +// TEXINFO: YACAtomUintEncode +// @deftypefun bool YACAtomUintEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, const uint64_t v) +// Encode positive integer atom in provided @var{buf} with capacity of +// @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool -YACAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, uint64_t v); - +YACAtomUintEncode(size_t *len, unsigned char *buf, const size_t cap, const uint64_t v); + +// TEXINFO: YACAtomSintEncode +// @deftypefun bool YACAtomSintEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, const int64_t v) +// Encode signed integer atom in provided @var{buf} with capacity of +// @var{cap}. If it is zero or positive, then it encodes unsigned one. +// Negative one otherwise. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool -YACAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, int64_t v); - +YACAtomSintEncode(size_t *len, unsigned char *buf, const size_t cap, const int64_t v); + +// TEXINFO: YACAtomListEncode +// @deftypefun bool YACAtomListEncode( @ +// size_t *len, unsigned char *buf, const size_t cap) +// Encode LIST atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. Do not forget to add EOC atom at the end. +// @end deftypefun bool YACAtomListEncode(size_t *len, unsigned char *buf, const size_t cap); +// TEXINFO: YACAtomMapEncode +// @deftypefun bool YACAtomMapEncode( @ +// size_t *len, unsigned char *buf, const size_t cap) +// Encode MAP atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. Do not forget to add EOC atom at the end. +// @end deftypefun bool YACAtomMapEncode(size_t *len, unsigned char *buf, const size_t cap); +// TEXINFO: YACAtomBlobEncode +// @deftypefun bool YACAtomBlobEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, const size_t chunkLen) +// Encode BLOB atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. You must call @ref{YACAtomChunkEncode} +// functions for subsequent chunks, and terminate the blob with +// @ref{YACAtomBinEncode}. +// @end deftypefun bool YACAtomBlobEncode( size_t *len, @@ -40,6 +108,14 @@ YACAtomBlobEncode( const size_t cap, const size_t chunkLen); +// TEXINFO: YACAtomStrEncode +// @deftypefun bool YACAtomStrEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, @ +// const unsigned char *src, const size_t srcLen) +// Encode STR atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool YACAtomStrEncode( size_t *len, @@ -48,6 +124,14 @@ YACAtomStrEncode( const unsigned char *src, const size_t srcLen); +// TEXINFO: YACAtomBinEncode +// @deftypefun bool YACAtomBinEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, @ +// const unsigned char *src, const size_t srcLen) +// Encode BIN atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. +// @end deftypefun bool YACAtomBinEncode( size_t *len, @@ -56,6 +140,15 @@ YACAtomBinEncode( const unsigned char *src, const size_t srcLen); +// TEXINFO: YACAtomChunkEncode +// @deftypefun bool YACAtomChunkEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, @ +// const unsigned char *src, const size_t srcLen) +// Encode the chunk in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. It is just a convenient wrapper instead +// of using @ref{YACAtomNILEncode} followed by @var{srcLen} bytes. +// @end deftypefun bool YACAtomChunkEncode( size_t *len, @@ -64,6 +157,15 @@ YACAtomChunkEncode( const unsigned char *src, const size_t srcLen); +// TEXINFO: YACAtomTAI64Encode +// @deftypefun bool YACAtomTAI64Encode( @ +// size_t *len, unsigned char *buf, const size_t cap, @ +// const unsigned char *src, const size_t srcLen) +// Encode the TAI64* atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. You have to provide either 8, or 12, or +// 16 byte string. +// @end deftypefun bool YACAtomTAI64Encode( size_t *len, @@ -72,6 +174,15 @@ YACAtomTAI64Encode( const unsigned char *src, const size_t srcLen); +// TEXINFO: YACAtomRawEncode +// @deftypefun bool YACAtomRawEncode( @ +// size_t *len, unsigned char *buf, const size_t cap, @ +// const unsigned char typ, const unsigned char *src, const size_t srcLen) +// Encode raw atom in provided @var{buf} with capacity of @var{cap}. +// In case of success, true is returned and @var{len} will hold how many +// bytes were written to buffer. It is just a convenient wrapper instead +// of manual writing of the @code{typ} byte followed by @var{srcLen} bytes. +// @end deftypefun bool YACAtomRawEncode( size_t *len, diff --git a/cyac/lib/enctai.c b/cyac/lib/enctai.c index 32afb56..2538c0b 100644 --- a/cyac/lib/enctai.c +++ b/cyac/lib/enctai.c @@ -21,23 +21,35 @@ #include "tobe.h" bool -YACTimespecToTAI64(unsigned char *buf, const struct timespec *tv) +YACTimespecToTAI64(unsigned char *buf, const struct timespec *ts) { - int64_t v = (int64_t)(tv->tv_sec); - int64_t diff = YACLeapsecs1972; + int64_t v = (int64_t)(ts->tv_sec); + uint64_t val = (uint64_t)v + 0x4000000000000000; + if (val <= (uint64_t)v) { + return false; + } + yacToBE(buf, 8, val); + if (ts->tv_nsec != 0) { + yacToBE(buf + 8, 4, (uint64_t)(ts->tv_nsec)); + } + return true; +} + +bool +YACTimespecToTAI(struct timespec *ts) +{ + int64_t v = (int64_t)(ts->tv_sec); + int64_t diff = 10; for (size_t i = 0; i < YACLeapsecsN; i++) { if (v > YACLeapsecs[i]) { diff += YACLeapsecsN - i; break; } } - uint64_t val = (uint64_t)v + 0x4000000000000000 + (uint64_t)diff; - if (val <= (uint64_t)v) { + v += diff; + if (((uint64_t)1 << ((sizeof(time_t) * 8) - 1)) < (uint64_t)v) { return false; } - yacToBE(buf, 8, val); - if (tv->tv_nsec != 0) { - yacToBE(buf + 8, 4, (uint64_t)(tv->tv_nsec)); - } + ts->tv_sec = (time_t)v; return true; } diff --git a/cyac/lib/enctai.h b/cyac/lib/enctai.h index e965fe9..b7c25bb 100644 --- a/cyac/lib/enctai.h +++ b/cyac/lib/enctai.h @@ -4,7 +4,23 @@ #include #include +// TEXINFO: YACTimespecToTAI64 +// @deftypefun bool YACTimespecToTAI64( @ +// unsigned char *buf, const struct timespec *ts) +// Convert @var{ts} structure to TAI64/TAI64N in the @var{buf}. +// If @var{ts} contains non zero nanoseconds count, then be sure that +// @var{buf} has enough capacity for 12-byte TAI64N value. +// False is returned if represented time is out of allowable bounds. +// @end deftypefun bool -YACTimespecToTAI64(unsigned char *buf, const struct timespec *tv); +YACTimespecToTAI64(unsigned char *buf, const struct timespec *ts); + +// TEXINFO: YACTimespecToTAI +// @deftypefun bool YACTimespecToTAI(struct timespec *ts) +// Convert UTC stored in @var{ts} structure to TAI. +// False is returned if represented time is out of allowable bounds. +// @end deftypefun +bool +YACTimespecToTAI(struct timespec *ts); #endif // YAC_ENCTAI_H diff --git a/cyac/lib/err.h b/cyac/lib/err.h index 64bf0aa..67d98e3 100644 --- a/cyac/lib/err.h +++ b/cyac/lib/err.h @@ -1,7 +1,9 @@ #ifndef YAC_ERR_H #define YAC_ERR_H -// @deftypevar enum YACErr +// TEXINFO: YACErr +// @deftp {Data type} {enum YACErr} +// Errors are just a predefined enumeration value. // @table @code // @item YACErrInvalid // Unset error, must be never met. @@ -38,7 +40,7 @@ // @item YACErrUnsatisfiedSchema // Unsatisfied structure's schema. // @end table -// @end deftypevar +// @end deftp enum YACErr { YACErrInvalid = 0, YACErrNo = 1, @@ -60,6 +62,10 @@ enum YACErr { YACErrUnsatisfiedSchema, }; +// TEXINFO: YACErr2Str +// @deftypefun {const char *} YACErr2Str(const enum YACErr) +// Get human-readable string of the error. +// @end deftypefun const char * YACErr2Str(const enum YACErr); diff --git a/cyac/lib/frombe.h b/cyac/lib/frombe.h index 49ff480..78651f9 100644 --- a/cyac/lib/frombe.h +++ b/cyac/lib/frombe.h @@ -4,6 +4,7 @@ #include #include +// Decode big-endian integer of the length @var{len}. uint64_t yacFromBE(const unsigned char *buf, const size_t len); diff --git a/cyac/lib/items.c b/cyac/lib/items.c index af0eacf..c9182ca 100644 --- a/cyac/lib/items.c +++ b/cyac/lib/items.c @@ -44,7 +44,7 @@ YACItemsInit(struct YACItems *items) } enum YACErr -yacItemsGrow(struct YACItems *items) +YACItemsGrow(struct YACItems *items) { if (items->cap == -1) { return YACErrNoMem; @@ -89,7 +89,7 @@ yacItemsAdd( { enum YACErr err = YACErrInvalid; if (items->len == (size_t)(items->cap)) { - err = yacItemsGrow(items); + err = YACItemsGrow(items); if (err != YACErrNo) { return err; } diff --git a/cyac/lib/items.h b/cyac/lib/items.h index 961d3a9..b0bba8f 100644 --- a/cyac/lib/items.h +++ b/cyac/lib/items.h @@ -7,34 +7,47 @@ #include "dec.h" #include "err.h" -// @deftypevar struct YACItem -// Each item contains the atom structure. But item can be a part of the -// list or map. @code{.next} contains the pool index value to the next -// element of the list or map, following current one. It equals to -1, -// then it is the last one. -// Map is a list of pairs: first value is always a UTF-8 string with the -// key name, next one is its value. -// @code{.off} is the offset of item in the previously provided buffer. +// TEXINFO: YACItem +// @deftp {Data type} {struct YACItem} +// Each item contain the @code{.atom} structure. But item can be a part +// of the list or map, so @code{.next} contains the pool index value to +// the next element of the list or map. It equals to 0, if it is the +// last one. // -// Remember that @code{.next} of the list/map/blob is the (possible) -// element after the whole list/map/blob. @code{.atom.val.first} is the -// (possible) first element inside map/list. +// Map is a list of pairs: first value is always an UTF-8 string with +// the key name, next one is its value. // -// Blob's first element always present and it is the next binary string -// item, until its length is less than chunk len. -// @end deftypevar +// Blob's first element always exists and it is the next binary string +// item, until its length is less than chunk length. +// @end deftp struct YACItem { size_t next; struct YACAtom atom; }; -// @deftypevar struct YACItems -// Items pool contains concatenated @code{YACItem}s. Item's @{.next} can -// be used as an index in that pool: @code{items->list[item.next]}. -// @strong{Remember} that if there is not enough room for the next item, -// then @code{.list} is reallocated, so previous pointers to the items -// may become invalid! Using their indices will be safer. -// @end deftypevar +// TEXINFO: YACItems +// @deftp {Data type} {struct YACItems} +// Items pool @code{.list} contains concatenated @code{YACItem}s. Item's +// @code{.next} can be used as an index in that pool: +// @code{items->list[item.next]}. +// +// Items is just a list of possibly linked lists. You may insert an item +// at arbitrary LIST place by appending it to the @code{.list} and by +// modifying the necessary @code{.next} of the desired preceding +// element. +// +// @code{.cap} and @code{.len} are capacity and length of the +// @code{.list}. If you use @ref{YACItemsParse}, then it parses the +// buffer and continuously appends new items to that list. If its length +// matches with the capacity, then @ref{YACItemsGrow} is called to +// enlarge the underlying buffer, possibly reallocating the whole of it. +// So be careful with the pointers to the list's items, because they +// could be invalidated after the growth. Using their indices instead +// will be safer. +// +// Corresponding @code{.offsets} store the offset of the decoded item +// relative to the previously provided buffer. +// @end deftp struct YACItems { struct YACItem *list; size_t *offsets; @@ -42,12 +55,46 @@ struct YACItems { ptrdiff_t cap; }; +// TEXINFO: YACItemsInit +// @deftypefun {enum YACErr} YACItemsInit(struct YACItems *items) +// Initialise the @ref{YACItems} structure by allocating an initial +// capacity for the underlying storage. +// +// If you do not want to use heap allocation and want to use +// preallocated fixed buffer, then just point @code{.list} to it and set +// the proper @code{.cap}acity. +// +// If you do not want to store items offsets during decoding, or you use +// @ref{YACItems} only for encoding purposes, then set @code{.offsets} +// to NULL (do not forget to free it, if it was initialised before). +// @end deftypefun enum YACErr YACItemsInit(struct YACItems *); +// TEXINFO: YACItemsGrow +// @deftypefun {enum YACErr} YACItemsGrow(struct YACItems *items) +// Enlarge underlying storage of items, increasing its capacity. If +// @code{.cap} equals to -1, then nothing will happen and +// @code{YACErrNoMem} error will be returned. You can use that -1 value +// to omit attempts to call heap allocation functions. +// @end deftypefun enum YACErr -yacItemsGrow(struct YACItems *); +YACItemsGrow(struct YACItems *); +// TEXINFO: YACItemsParse +// @deftypefun {enum YACErr} YACItemsParse( @ +// struct YACItems *items, @ +// size_t *off, @ +// const unsigned char *buf, @ +// const size_t len) +// Parse the provided @var{buf} appending newly created linked +// @ref{YACItem}s in @var{items}. If there is not enough capacity +// available, then @ref{YACItemsGrow} is automatically called. If +// @code{.offsets} equal to NULL, then no offsets will be stored. +// +// @code{off}set is a position in @var{buf} from which the parsing will +// be done. Its final value is the position when we stopped. +// @end deftypefun enum YACErr YACItemsParse( struct YACItems *, @@ -55,6 +102,18 @@ YACItemsParse( const unsigned char *buf, const size_t len); +// TEXINFO: YACItemsEncode +// @deftypefun bool YACItemsEncode( @ +// const struct YACItems *items, @ +// size_t idx, @ +// size_t *off, @ +// unsigned char *buf, @ +// const size_t cap) +// Encode the previously filled @var{items} to the @var{buf}. @code{idx} +// tells what exact item must be encoded. So you can easily encode only +// the subpart of the @var{items}. @code{off} stored the resulting +// length of the encoded data. +// @end deftypefun bool YACItemsEncode( const struct YACItems *, @@ -63,6 +122,15 @@ YACItemsEncode( unsigned char *buf, const size_t cap); +// TEXINFO: YACItemsGetByKeyLen +// @deftypefun size_t YACItemsGetByKeyLen( @ +// const struct YACItems *items, @ +// const size_t itemIdx, @ +// const char *key, @ +// const size_t keyLen) +// Get the index of the key with @var{key} name of @var{keyLen} length +// in @var{items}'es element @var{itemIdx}. Returns zero if none found. +// @end deftypefun size_t YACItemsGetByKeyLen( const struct YACItems *, @@ -70,9 +138,25 @@ YACItemsGetByKeyLen( const char *key, const size_t keyLen); +// TEXINFO: YACItemsGetByKey +// @deftypefun size_t YACItemsGetByKey( @ +// const struct YACItems *items, @ +// const size_t itemIdx, @ +// const char *key) +// Get the index of the null-terminated key with @var{key} name in +// @var{items}'es element @var{itemIdx}. Returns zero if none found. +// @end deftypefun size_t YACItemsGetByKey(const struct YACItems *, const size_t itemIdx, const char *key); +// TEXINFO: YACItemsGetByKeyAndType +// @deftypefun size_t YACItemsGetByKeyAndType( @ +// const struct YACItems *items, @ +// const size_t itemIdx, @ +// const char *key, @ +// const enum YACItemType typ) +// Same as @ref{YACItemsGetByKey}, but also check that value's type is @var{typ}. +// @end deftypefun size_t YACItemsGetByKeyAndType( const struct YACItems *, @@ -80,12 +164,32 @@ YACItemsGetByKeyAndType( const char *key, const enum YACItemType typ); +// TEXINFO: YACStrEqual +// @deftypefun bool YACStrEqual(const struct YACAtom *atom, const char *s) +// Returns true if string atom's value equal to null-terminated @var{s}. +// @end deftypefun bool YACStrEqual(const struct YACAtom *, const char *s); +// TEXINFO: YACListHasOnlyType +// @deftypefun bool YACListHasOnlyType( @ +// const struct YACItems *items, @ +// const size_t idx, @ +// const enum YACItemType typ) +// Returns true if @var{idx} list in @var{items} contains only values +// with the @var{typ} type. +// @end deftypefun bool YACListHasOnlyType(const struct YACItems *, size_t idx, const enum YACItemType typ); +// TEXINFO: YACMapHasOnlyType +// @deftypefun bool YACMapHasOnlyType( @ +// const struct YACItems *items, @ +// const size_t idx, @ +// const enum YACItemType typ) +// Returns true if @var{idx} map in @var{items} contains only values +// with the @var{typ} type. +// @end deftypefun bool YACMapHasOnlyType(const struct YACItems *, size_t idx, const enum YACItemType typ); diff --git a/cyac/lib/leapsecs.c b/cyac/lib/leapsecs.c index c3102c1..b1d86ae 100644 --- a/cyac/lib/leapsecs.c +++ b/cyac/lib/leapsecs.c @@ -4,8 +4,7 @@ #include "leapsecs.h" const size_t YACLeapsecsN = 27; -const int64_t YACLeapsecs1972 = 10; -int64_t YACLeapsecs[] = { +const int64_t YACLeapsecs[] = { 1483228800, // 2017-01 1435708800, // 2015-07 1341100800, // 2012-07 diff --git a/cyac/lib/leapsecs.h b/cyac/lib/leapsecs.h index 5b1b1ca..206c6f3 100644 --- a/cyac/lib/leapsecs.h +++ b/cyac/lib/leapsecs.h @@ -4,8 +4,13 @@ #include #include +// TEXINFO: YACLeapsecs +// @deftypevar {const int64_t *} YACLeapsecs +// Leap seconds database. It contains TAI seconds when an additional +// second was added. @var{YACLeapsecsN} variable holds the length of +// that list. +// @end deftypevar extern const size_t YACLeapsecsN; -extern const int64_t YACLeapsecs1972; -extern int64_t YACLeapsecs[]; +extern const int64_t YACLeapsecs[]; #endif // YAC_LEAPSECS_H diff --git a/cyac/lib/tobe.h b/cyac/lib/tobe.h index 872e1d1..2b0318e 100644 --- a/cyac/lib/tobe.h +++ b/cyac/lib/tobe.h @@ -4,6 +4,7 @@ #include #include +// Store big-endian integer of the length @var{len}. void yacToBE(unsigned char *buf, const size_t len, const uint64_t v); diff --git a/tyac/README b/tyac/README index ae48a30..c75d95b 100644 --- a/tyac/README +++ b/tyac/README @@ -1,5 +1,5 @@ Tcl implementation of the YAC encoder. -* No FLOAT* support. They is stored just as a raw value +* No FLOAT* support. They can be stored just as a raw value. tyac is free software: see the file COPYING.LESSER for copying conditions. -- 2.50.0