src/lib/ASN1ContentHandler.h
branchv_0
changeset 1 68a281aefa76
--- /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);
+	}
+
+};
+
+
+}
+}
+}
+}