From 37cde44d586a263286538c8317fd7a459e04f343 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Thu, 26 Dec 2019 11:43:01 -0800 Subject: [PATCH] ircd::cbor: Reconstitute head reader and utils. --- include/ircd/cbor/cbor.h | 96 +++++++++++++++++ include/ircd/ircd.h | 1 + ircd/Makefile.am | 1 + ircd/cbor.cc | 217 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 315 insertions(+) create mode 100644 include/ircd/cbor/cbor.h create mode 100644 ircd/cbor.cc diff --git a/include/ircd/cbor/cbor.h b/include/ircd/cbor/cbor.h new file mode 100644 index 000000000..b3edbd3d5 --- /dev/null +++ b/include/ircd/cbor/cbor.h @@ -0,0 +1,96 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2019 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_CBOR_H + +/// Concise Binary Object Representation (RFC7049) +/// +namespace ircd::cbor +{ + IRCD_EXCEPTION(ircd::error, error); + IRCD_EXCEPTION(error, type_error); + IRCD_EXCEPTION(error, parse_error); + IRCD_EXCEPTION(parse_error, buffer_underrun); + + enum major :uint8_t; + enum minor :uint8_t; + struct head; + + string_view reflect(const enum major &); + enum major major(const const_buffer &); +} + +/// Item head. +/// +/// This object represents the head byte and any following-integer bytes under +/// its const_buffer. If the major type has a payload, it starts immediately +/// following the end of this object's buffer. The first byte of this object's +/// buffer is the leading head byte. This object's buffer will never be empty() +/// unless it's default-initialized (i.e not pointing at anything). +/// +/// This is used to query information about the item from the head data. +/// +struct ircd::cbor::head +:const_buffer +{ + static uint8_t major(const uint8_t &); // Major type + static uint8_t minor(const uint8_t &); // Minor type + static size_t length(const uint8_t &); // (1 + size(following())) + + // Member ops when buffer has >= 1 byte + const uint8_t &leading() const; // Reference the leading byte + enum major major() const; // major(leading()) + enum minor minor() const; // minor(leading()) + size_t length() const; // length(leading()) + + // Get bytes following leading byte based on major/minor + const_buffer following() const; + template const T &following() const; + + // Construct from at least first byte of item or more + head(const const_buffer &); + head() = default; + head(head &&) = default; + head(const head &) = default; +}; + +/// RFC7049 Major type codes +enum ircd::cbor::major +:uint8_t +{ + POSITIVE = 0, ///< Z* + NEGATIVE = 1, ///< Z- + BINARY = 2, ///< Raw byte sequence + STRING = 3, ///< UTF-8 character sequence + ARRAY = 4, ///< Array of items + OBJECT = 5, ///< Dictionary of items + TAG = 6, ///< CBOR extensions (IANA registered) + PRIMITIVE = 7, ///< Literals / floats +}; + +/// RFC7049 Minor type codes +enum ircd::cbor::minor +:uint8_t +{ + FALSE = 20, ///< False + TRUE = 21, ///< True + NUL = 22, ///< Null + UD = 23, ///< Undefined value + U8 = 24, ///< 8 bits follow + U16 = 25, ///< 16 bits follow + F16 = 25, ///< IEEE754 half-precision (16 bits follow) + U32 = 26, ///< 32 bits follow + F32 = 26, ///< IEEE754 single-precision (32 bits follow) + U64 = 27, ///< 64 bits follow + F64 = 27, ///< IEEE754 double-precision (64 bits follow) + STREAM = 31, ///< Variable length (terminated by BREAK) + BREAK = 31, ///< Terminator code +}; diff --git a/include/ircd/ircd.h b/include/ircd/ircd.h index df9687230..5bc4eb3a0 100644 --- a/include/ircd/ircd.h +++ b/include/ircd/ircd.h @@ -48,6 +48,7 @@ #include "parse.h" #include "rfc1459.h" #include "json/json.h" +#include "cbor/cbor.h" #include "openssl.h" #include "fmt.h" #include "http.h" diff --git a/ircd/Makefile.am b/ircd/Makefile.am index 95211fb66..d2057eba1 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -108,6 +108,7 @@ libircd_la_SOURCES += base.cc libircd_la_SOURCES += crh.cc libircd_la_SOURCES += fmt.cc libircd_la_SOURCES += json.cc +libircd_la_SOURCES += cbor.cc libircd_la_SOURCES += conf.cc libircd_la_SOURCES += stats.cc libircd_la_SOURCES += logger.cc diff --git a/ircd/cbor.cc b/ircd/cbor.cc new file mode 100644 index 000000000..a78233358 --- /dev/null +++ b/ircd/cbor.cc @@ -0,0 +1,217 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2019 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +/// Given a buffer of CBOR this function parses the head data and maintains +/// a const_buffer span of the head. The span includes the leading head byte +/// and one or more integer bytes following the leading byte. If the major +/// type found in this head has a data payload which is not a following-integer +/// then that data starts directly after this head buffer ends. +/// +/// The argument buffer must be at least one byte and must at least cover the +/// following-integer bytes (and can also be as large as possible). +/// +ircd::cbor::head::head(const const_buffer &buf) +:const_buffer{[&buf] +{ + if(unlikely(size(buf) < sizeof(uint8_t))) + throw buffer_underrun + { + "Item buffer is too small to contain a header" + }; + + const uint8_t &leading + { + *reinterpret_cast(data(buf)) + }; + + return const_buffer + { + data(buf), length(leading) + }; +}()} +{ + if(unlikely(size(*this) > size(buf))) + throw buffer_underrun + { + "Item buffer is too small to contain full header" + }; +} + +/// Pun a reference to the integer contained by the bytes following the head. +/// If there are no bytes following the head because the integer is contained +/// as bits packed into the leading head byte, this function will throw. +/// +template +const T & +ircd::cbor::head::following() +const +{ + if(unlikely(size(following()) < sizeof(T))) + throw buffer_underrun + { + "Buffer following header is too small (%zu) for type requiring %zu", + size(following()), + sizeof(T) + }; + + return *reinterpret_cast(data(following())); +} + +/// Return buffer spanning the integer bytes following this head. This may be +/// an empty buffer if the integer byte is packed into bits of the leading +/// byte (denoted by minor()). +ircd::const_buffer +ircd::cbor::head::following() +const +{ + return { data(*this) + 1, length() - 1 }; +} + +/// Extract the length of the head from the buffer (requires 1 byte of buffer) +size_t +ircd::cbor::head::length() +const +{ + return length(leading()); +} + +/// Extract the minor type from the reference to the leading byte in the head. +enum ircd::cbor::minor +ircd::cbor::head::minor() +const +{ + return static_cast(minor(leading())); +} + +/// Extract the major type from the reference to the leading byte in the head. +enum ircd::cbor::major +ircd::cbor::head::major() +const +{ + return static_cast(major(leading())); +} + +/// Reference the leading byte of the head. +const uint8_t & +ircd::cbor::head::leading() +const +{ + assert(size(*this) >= sizeof(uint8_t)); + return *reinterpret_cast(data(*this)); +} + +/// Extract length of head from leading head byte (arg); this is the length of +/// the integer following the leading head byte plus the one leading head +/// byte. This length covers all bytes which come before item payload bytes +/// (when such a payload exists). If this length is 1 then no integer bytes +/// are following the leading head byte. The length/returned value is never 0. +size_t +ircd::cbor::head::length(const uint8_t &a) +{ + switch(major(a)) + { + case POSITIVE: + case NEGATIVE: + case BINARY: + case STRING: + case ARRAY: + case OBJECT: + case TAG: + { + if(minor(a) > 23) switch(minor(a)) + { + case minor::U8: return 2; + case minor::U16: return 3; + case minor::U32: return 5; + case minor::U64: return 9; + default: throw type_error + { + "Unknown minor type (%u); length of header unknown", minor(a) + }; + } + else return 1; + } + + case PRIMITIVE: + { + if(minor(a) > 23) switch(minor(a)) + { + case FALSE: + case TRUE: + case NUL: + case UD: return 1; + case minor::F16: return 3; + case minor::F32: return 5; + case minor::F64: return 9; + default: throw type_error + { + "Unknown primitive minor type (%u); length of header unknown", minor(a) + }; + } + else return 1; + } + + default: throw type_error + { + "Unknown major type; length of header unknown" + }; + } +} + +/// Extract major type from leading head byte (arg) +uint8_t +ircd::cbor::head::major(const uint8_t &a) +{ + // shift for higher 3 bits only + static const int &shift(5); + return a >> shift; +} + +/// Extract minor type from leading head byte (arg) +uint8_t +ircd::cbor::head::minor(const uint8_t &a) +{ + // mask of lower 5 bits only + static const uint8_t &mask + { + uint8_t(0xFF) >> 3 + }; + + return a & mask; +} + +// +// cbor.h +// + +enum ircd::cbor::major +ircd::cbor::major(const const_buffer &buf) +{ + const head head(buf); + return head.major(); +} + +ircd::string_view +ircd::cbor::reflect(const enum major &major) +{ + switch(major) + { + case major::POSITIVE: return "POSITIVE"; + case major::NEGATIVE: return "NEGATIVE"; + case major::BINARY: return "BINARY"; + case major::STRING: return "STRING"; + case major::ARRAY: return "ARRAY"; + case major::OBJECT: return "OBJECT"; + case major::TAG: return "TAG"; + case major::PRIMITIVE: return "PRIMITIVE"; + } + + return "??????"; +}