/**
* 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;
};
enum class StringType : uint64_t {
UTF8String = 0xC,
NumericString = 0x12,
PrintableString = 0x13,
T61String = 0x14,
VideotexString = 0x15,
IA5String = 0x16,
GraphicString = 0x19,
VisibleString = 0x1A,
GeneralString = 0x1B,
UniversalString = 0x1C,
CharacterString = 0x1D,
BMPString = 0x1E,
};
enum class DateTimeType : uint64_t {
UTCTime,
GeneralizedTime,
Time,
Date,
TimeOfDay,
DateTime,
Duration,
// TODO: review date/time types
};
// 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
virtual void writeStreamStart() = 0;
virtual void writeStreamEnd() = 0;
virtual void writeCollectionStart(const Header& header) = 0;
virtual void writeCollectionEnd() = 0;
virtual void writeBoolean(bool value) = 0;
virtual void writeNull() = 0;
virtual void writeInteger(Integer value) = 0;
/**
* @param type original type in ASN.1
* @param value original text converted to UTF-8
*/
virtual void writeTextString(StringType type, std::string value) = 0;
/**
* @param value arbitrary sequence of octets (bytes), usually not a human-readable text
*/
virtual void writeOctetString(std::string value) = 0;
/**
* @param value arbitrary sequence of bits (booleans), usually not a human-readable text
*/
virtual void writeBitString(std::vector<bool> value) = 0;
virtual void writeOID(ObjectIdentifier value) = 0;
virtual void writeDateTime(DateTimeType type, DateTime value) = 0;
// Object descriptor
// virtual void writeReal(float value) = 0;
// Enumerated
// Embedded PVD
// Relative OID
// OID-IRI
// Relative OID-IRI
};
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(bool value) override {
handlers.forward(&ASN1ContentHandler::writeBoolean, value);
}
void writeNull() override {
handlers.forward(&ASN1ContentHandler::writeNull);
}
void writeInteger(Integer value) override {
handlers.forward(&ASN1ContentHandler::writeInteger, value);
}
void writeTextString(StringType type, std::string value) override {
handlers.forward(&ASN1ContentHandler::writeTextString, type, value);
}
void writeOctetString(std::string value) override {
handlers.forward(&ASN1ContentHandler::writeOctetString, value);
}
void writeBitString(std::vector<bool> value) override {
handlers.forward(&ASN1ContentHandler::writeBitString, value);
}
void writeOID(ObjectIdentifier value) override {
handlers.forward(&ASN1ContentHandler::writeOID, value);
}
void writeDateTime(DateTimeType type, DateTime value) override {
handlers.forward(&ASN1ContentHandler::writeDateTime, type, value);
}
};
}
}
}
}