src/ASN1Writer.h
author František Kučera <franta-hg@frantovo.cz>
Tue, 22 Oct 2019 21:53:25 +0200
branchv_0
changeset 7 c155b441306f
parent 2 d1b39a9bd591
permissions -rw-r--r--
fix license version: GNU GPLv3

/**
 * Relational pipes
 * Copyright © 2018 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 <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <codecvt>
#include <locale>

#include <relpipe/reader/typedefs.h>
#include <relpipe/cli/RelpipeCLIException.h>

using namespace std;

namespace relpipe {
namespace out {
namespace asn1 {

using namespace relpipe::reader;

/**
 * A simple library for writing ASN.1 BER encoded files.
 */
class ASN1Writer {
private:
	ostream& output;
	wstring_convert<codecvt_utf8<wchar_t>> convertor; // only UTF8String is supported
	int sequenceLevel = 0;

	enum TagClass : uint8_t {
		/** The type is native to ASN.1 */
		Universal = 0,
		/** The type is only valid for one specific application */
		Application = 1,
		/** Meaning of this type depends on the context (such as within a sequence, set or choice) */
		ContextSpecific = 2,
		/** Defined in private specifications */
		Private = 3,
	};

	enum PC : uint8_t {
		/** The contents octets directly encode the element value. */
		Primitive = 0,
		/** The contents octets contain 0, 1, or more element encodings.  */
		Constructed = 1,
	};

	enum UniversalType : uint16_t {
		EndOfContent = 0,
		Boolean = 1,
		Integer = 2,
		OctetString = 4,
		Null = 5,
		ObjectIdentifier = 6,
		Real = 9,
		UTF8String = 12,
		RelativeObjectIdentifier = 13,
		Sequence = 16,
	};

	void writeIdentifier(TagClass tagClass, PC pc, uint16_t tagNumber) {
		if (tagNumber > 30) throw cli::RelpipeCLIException(L"Tag numbers higher than 30 are currently unsupported.", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // should not happen, error in the program // TODO: support tag numbers > 30
		uint8_t tag = 0;
		tag |= tagClass << 6;
		tag |= pc << 5;
		tag |= tagNumber;
		output.put(tag);
	}

	/**
	 * @param der
	 * @param length
	 */
	template<typename N, typename = typename std::enable_if<std::is_unsigned<N>::value, N>::type>
	void writeLength(N length) {
		if (length < 128) {
			output.put(length);
		} else {
			for (int i = sizeof (N) - 1; i >= 0; i--) {
				uint8_t b = length >> (i * 8);
				if (b || i == 0) {
					output.put((i + 1) | 0x80);
					for (; i >= 0; i--) {
						b = length >> (i * 8);
						output.put(b);
					}
					break;
				}
			}
		}
	}

	void writeLengthIndefinite() {
		output.put('\x80');
	}

	static bool isLittleEndian() {
		int test = 1;
		return (*(char *) &test);
	}

	static bool isBigEndian() {
		return !isLittleEndian();
	}

public:

	ASN1Writer(std::ostream& output) : output(output) {
	}

	virtual ~ASN1Writer() {
		output.flush();
	}

	/**
	 * Just check whether all sequences are closed. If not, throws RelpipeCLIException.
	 */
	void end() {
		if (sequenceLevel) throw cli::RelpipeCLIException(L"Unable to close ASN1Writer because there are not ended sequences.", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // should not happen, error in the program
	}

	void writeStartSequence() {
		sequenceLevel++;
		writeIdentifier(TagClass::Universal, PC::Constructed, UniversalType::Sequence);
		writeLengthIndefinite();
	}

	void writeEndSequence() {
		if (sequenceLevel == 0) throw cli::RelpipeCLIException(L"Unable to writeEndSequence() if not sequence is currently started.", cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // should not happen, error in the program
		sequenceLevel--;
		writeIdentifier(TagClass::Universal, PC::Primitive, UniversalType::EndOfContent);
		writeLength(0u);
	}

	void writeBoolean(const boolean_t& value) {
		writeIdentifier(TagClass::Universal, PC::Primitive, UniversalType::Boolean);
		writeLength(1u);
		output.put(value ? '\xFF' : '\x00');

	}

	void writeInteger(const integer_t& value) {
		uint8_t* end = (uint8_t*) & value;
		uint8_t* start = end + sizeof (value) - 1;
		uint8_t* current;
		int direction = -1;

		if (isBigEndian()) {
			std::swap(end, start);
			direction = -direction;
		}

		// move current pointer to the first valuable octet
		for (current = start; current != end; current += direction) {
			switch (*current) {
				case 0x00: if ((*(current + direction) & 0x80) == 0) continue;
					break;
				case 0xFF: if ((*(current + direction) & 0x80) != 0) continue;
					break;
			}
			break;
		}

		std::vector<char> v;
		writeIdentifier(TagClass::Universal, PC::Primitive, UniversalType::Integer);
		writeLength((size_t) (end > current ? end - current : current - end) + 1);
		for (end += direction; current != end; current += direction) output.put(*current);
	}

	void writeString(const string_t& value) {
		// TODO: empty string → null?
		std::string bytes = convertor.to_bytes(value);
		writeIdentifier(TagClass::Universal, PC::Primitive, UniversalType::UTF8String);
		writeLength(bytes.length());
		output << bytes;
	}

};

}
}
}