diff -r 28294b895e5e -r 68a281aefa76 src/lib/ASN1ContentHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ASN1ContentHandler.h Sat Jul 24 17:35:27 2021 +0200 @@ -0,0 +1,381 @@ +/** + * Relational pipes + * Copyright © 2021 František Kučera (Frantovo.cz, GlobalCode.info) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "ProxyVector.h" + +namespace relpipe { +namespace in { +namespace asn1 { +namespace lib { + +namespace UniversalType { +static const uint64_t EndOfContent = 0x00; +static const uint64_t Boolean = 0x01; +static const uint64_t Integer = 0x02; +static const uint64_t BitString = 0x03; +static const uint64_t OctetString = 0x04; +static const uint64_t Null = 0x05; +static const uint64_t ObjectIdentifier = 0x06; +static const uint64_t ObjectDescriptor = 0x07; +static const uint64_t External = 0x08; +static const uint64_t Real = 0x09; +static const uint64_t Enumerated = 0xA; +static const uint64_t Embedded = 0xB; +static const uint64_t UTF8String = 0xC; +static const uint64_t RelativeObjectIdentifier = 0xD; +static const uint64_t Time = 0xE; +static const uint64_t Reserved = 0xF; +static const uint64_t Sequence = 0x10; +static const uint64_t Set = 0x11; +static const uint64_t NumericString = 0x12; +static const uint64_t PrintableString = 0x13; +static const uint64_t T61String = 0x14; +static const uint64_t VideotexString = 0x15; +static const uint64_t IA5String = 0x16; +static const uint64_t UTCTime = 0x17; +static const uint64_t GeneralizedTime = 0x18; +static const uint64_t GraphicString = 0x19; +static const uint64_t VisibleString = 0x1A; +static const uint64_t GeneralString = 0x1B; +static const uint64_t UniversalString = 0x1C; +static const uint64_t CharacterString = 0x1D; +static const uint64_t BMPString = 0x1E; +static const uint64_t Date = 0x1F; +static const uint64_t TimeOfDay = 0x20; +static const uint64_t DateTime = 0x21; +static const uint64_t Duration = 0x22; +static const uint64_t ObjectIdentifierIRI = 0x23; +static const uint64_t RelativeObjectIdentifierIRI = 0x24; +} + +class ASN1ContentHandler { +public: + + enum class TagClass : uint8_t { + Universal = 0, + Application = 1, + ContextSpecific = 2, + Private = 3 + }; + + enum class PC : uint8_t { + Primitive = 0, + Constructed = 1 + }; + + class Header { + public: + TagClass tagClass; + PC pc; + uint64_t tag; + }; + + // TODO: separate implementation of particular types from the interface, separate SPI from API? + + class Integer { + private: + // TODO: use std::string (of octets, not ASCII) instead of std::vector? + // TODO: use this class as BigInteger across Relational pipes? + std::vector data; + public: + + /** + * @param data integer octets as in BER encoding + */ + Integer(std::vector data) : data(data) { + } + + virtual ~Integer() { + } + + size_t size() const { + return data.size(); + } + + const uint8_t& operator[](std::size_t index) const { + return data[index]; + } + + const std::string toHex() const { + std::stringstream hex; + hex << std::hex << std::setfill('0'); + for (uint8_t b : data) hex << std::setw(2) << (int) b; + return hex.str(); + } + + const std::string toString() const { + try { + return std::to_string(toInt64()); + } catch (...) { + // integer has more than 64 bits → only HEX form value will be available + // TODO: support longer values than 64 bits + // TODO: do not ignore zero-length error? + return ""; + } + } + + const int64_t toInt64() const { + int64_t value = 0; + + if (data.size() > sizeof (value)) throw std::invalid_argument("Integer is too long"); + else if (data.size() == 0) throw std::invalid_argument("Integer has zero length"); + + value = data[0]; + bool negative = data[0] & 0x80; + + for (size_t i = 1, limit = data.size(); i < limit; i++) value = (value << 8) | data[i]; + + if (negative) value -= std::pow(256, data.size()); + + return value; + } + + }; + + class ObjectIdentifier { + private: + // TODO: use std::string (of octets, not ASCII) instead of std::vector? + // TODO: use this class across Relational pipes as one of basic types? + std::vector data; + + public: + + /** + * @param data integer octets as in BER encoding + */ + ObjectIdentifier(std::vector data) : data(data) { + // TODO: cache size and element values? + } + + virtual ~ObjectIdentifier() { + } + + /** + * @return number of elements, not octets + */ + size_t size() const { + return 0; // FIXME: correct OID size + } + + /** + * @param index 0 = root element + * @return value of the element at given position + */ + const uint8_t& operator[](std::size_t index) const { + return data[index]; // FIXME: correct OID value + } + + const std::string toString() const { + if (data.size() == 0) return ""; + + std::stringstream result; + + result << (data[0] / 40) << "." << (data[0] % 40); // first two elements are encoded in the first octet + + for (size_t i = 1, limit = data.size(), octet = 0, element = 0; i < limit; i++) { + octet = data[i]; + element = element << 7 | (octet & 0xFF >> 1); + // TODO: throw exception if the element value overflows? (should not happen) or format even longer values + if ((octet & 1 << 7) == 0) { + result << "." << element; + element = 0; + } + } + + return result.str(); + } + + }; + + class DateTime { + public: + + enum class Precision { + Year, + Month, + Day, + Hour, + Minute, + Second, + Milisecond, + Nanosecond + }; + + // TODO: timezone (in minutes or 1/4 hours) + + Precision precision = Precision::Second; + int32_t year = 1970; + int8_t month = 1; + int8_t day = 1; + int8_t hour = 0; + int8_t minute = 0; + int8_t second = 0; + int32_t nanosecond = 0; + int8_t timezoneHour = 0; + int8_t timezoneMinute = 0; + + virtual ~DateTime() { + } + + const std::string toString() const { + std::stringstream result; + result << std::setfill('0'); + result << std::setw(4) << (int) year; + result << "-" << std::setw(2) << (int) month; + result << "-" << std::setw(2) << (int) day; + result << "T" << std::setw(2) << (int) hour; + result << ":" << std::setw(2) << (int) minute; + result << ":" << std::setw(2) << (int) second; + if (precision == Precision::Nanosecond) result << "," << (int) nanosecond; + result << (timezoneHour < 0 ? "-" : "+"); + result << std::setw(2) << (int) std::abs(timezoneHour); + result << ":" << std::setw(2) << (int) timezoneMinute; + return result.str(); + } + }; + + virtual ~ASN1ContentHandler() = default; + + // TODO: more metadata, support OID decoding and ASN.1 modules (schema), probably through a plug-in + // TODO: support also extension extractor plug-ins? (could decode some opaque structures like octet strings and replace them with nested elements) e.g. subjectAltName in https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 + + /** + * @param uri identifier of the option + * @param value value of the option + * @return whether this option is supported and was applied here + */ + virtual bool setOption(const std::string& uri, const std::string& value) { + return false; + } + + virtual void writeStreamStart() = 0; + virtual void writeStreamEnd() = 0; + + virtual void writeCollectionStart(const Header& header) = 0; + virtual void writeCollectionEnd() = 0; + virtual void writeBoolean(const Header& header, bool value) = 0; + virtual void writeNull(const Header& header) = 0; + virtual void writeInteger(const Header& header, Integer value) = 0; + /** + * @param type original type in ASN.1 + * @param value original text converted to UTF-8 + */ + virtual void writeTextString(const Header& header, std::string value) = 0; + /** + * @param value arbitrary sequence of octets (bytes), usually not a human-readable text + */ + virtual void writeOctetString(const Header& header, std::string value) = 0; + /** + * @param value arbitrary sequence of bits (booleans), usually not a human-readable text + */ + virtual void writeBitString(const Header& header, std::vector value) = 0; + virtual void writeOID(const Header& header, ObjectIdentifier value) = 0; + virtual void writeDateTime(const Header& header, DateTime value) = 0; + // Object descriptor + // virtual void writeReal(float value) = 0; + // Enumerated + // Embedded PVD + // Relative OID + // OID-IRI + // Relative OID-IRI + + /** + * Specific value that was not parsed. + * May be processed in a generic way (as binary data or ASCII or UTF-8 string, when possible) + * or according to given application, context or private specification. + * + * @param value original raw data + */ + virtual void writeSpecific(const Header& header, std::string value) = 0; + +}; + +class ASN1ContentHandlerProxy : public ASN1ContentHandler { +private: + ProxyVector handlers; +public: + + void addHandler(std::shared_ptr handler) { + handlers.push_back(handler); + } + + void writeStreamStart() override { + handlers.forward(&ASN1ContentHandler::writeStreamStart); + } + + void writeStreamEnd() override { + handlers.forward(&ASN1ContentHandler::writeStreamEnd); + } + + void writeCollectionStart(const Header& header) override { + handlers.forward(&ASN1ContentHandler::writeCollectionStart, header); + } + + void writeCollectionEnd() override { + handlers.forward(&ASN1ContentHandler::writeCollectionEnd); + } + + void writeBoolean(const Header& header, bool value) override { + handlers.forward(&ASN1ContentHandler::writeBoolean, header, value); + } + + void writeNull(const Header& header) override { + handlers.forward(&ASN1ContentHandler::writeNull, header); + } + + void writeInteger(const Header& header, Integer value) override { + handlers.forward(&ASN1ContentHandler::writeInteger, header, value); + } + + void writeTextString(const Header& header, std::string value) override { + handlers.forward(&ASN1ContentHandler::writeTextString, header, value); + } + + void writeOctetString(const Header& header, std::string value) override { + handlers.forward(&ASN1ContentHandler::writeOctetString, header, value); + } + + void writeBitString(const Header& header, std::vector value) override { + handlers.forward(&ASN1ContentHandler::writeBitString, header, value); + } + + void writeOID(const Header& header, ObjectIdentifier value) override { + handlers.forward(&ASN1ContentHandler::writeOID, header, value); + } + + void writeDateTime(const Header& header, DateTime value) override { + handlers.forward(&ASN1ContentHandler::writeDateTime, header, value); + } + + void writeSpecific(const Header& header, std::string value) override { + handlers.forward(&ASN1ContentHandler::writeSpecific, header, value); + } + +}; + + +} +} +} +}