--- /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 <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <sstream>
+#include <iomanip>
+#include <cmath>
+
+#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<uint8_t> data;
+ public:
+
+ /**
+ * @param data integer octets as in BER encoding
+ */
+ Integer(std::vector<uint8_t> 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<uint8_t> data;
+
+ public:
+
+ /**
+ * @param data integer octets as in BER encoding
+ */
+ ObjectIdentifier(std::vector<uint8_t> 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<bool> 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<ASN1ContentHandler> handlers;
+public:
+
+ void addHandler(std::shared_ptr<ASN1ContentHandler> 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<bool> 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);
+ }
+
+};
+
+
+}
+}
+}
+}