/**
* 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;
}
};
}
}
}