first working version with ASN.1 BER output v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 30 Mar 2019 01:39:05 +0100
branchv_0
changeset 1 e9b4f3a9e436
parent 0 f4f9cdf7ed59
child 2 d1b39a9bd591
first working version with ASN.1 BER output
src/ASN1Handler.h
src/ASN1Writer.h
--- a/src/ASN1Handler.h	Sat Mar 30 01:12:42 2019 +0100
+++ b/src/ASN1Handler.h	Sat Mar 30 01:39:05 2019 +0100
@@ -129,6 +129,7 @@
 		if (valueCount) asn1Writer->writeEndSequence();
 		if (relationCount) asn1Writer->writeEndSequence();
 		asn1Writer->writeEndSequence();
+		asn1Writer->end();
 	}
 
 };
--- a/src/ASN1Writer.h	Sat Mar 30 01:12:42 2019 +0100
+++ b/src/ASN1Writer.h	Sat Mar 30 01:39:05 2019 +0100
@@ -44,9 +44,75 @@
 	wstring_convert<codecvt_utf8<wchar_t>> convertor; // only UTF8String is supported
 	int sequenceLevel = 0;
 
-	void xxx_indent() {
-		// FIXME: remove
-		for (int i = 0; i < sequenceLevel; i++) output << "  ";
+	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 {
+		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;
+				}
+			}
+		}
+	}
+
+	static bool isLittleEndian() {
+		int test = 1;
+		return (*(char *) &test);
+	}
+
+	static bool isBigEndian() {
+		return !isLittleEndian();
 	}
 
 public:
@@ -55,39 +121,70 @@
 	}
 
 	virtual ~ASN1Writer() {
-		if (sequenceLevel) output << "Unable to close ASN1Writer because there are not ended sequences." << endl; // FIXME: better error handling
 		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() {
-		xxx_indent();
-		output << "sequence start" << endl;
-
 		sequenceLevel++;
+		output.put('\x30');
+		output.put('\x80');
 	}
 
 	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--;
-
-		xxx_indent();
-		output << "sequence end" << endl;
+		output.put('\x00');
+		output.put('\x00');
 	}
 
 	void writeBoolean(const boolean_t& value) {
-		xxx_indent();
-		output << "boolean" << (value ? "true" : "false") << endl;
+		output.put('\x01');
+		output.put('\x01');
+		output.put(value ? '\xFF' : '\x00');
+
 	}
 
 	void writeInteger(const integer_t& value) {
-		xxx_indent();
-		output << "integer: " << value << endl;
+		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) {
-		xxx_indent();
-		output << "string: " << convertor.to_bytes(value) << endl;
+		// TODO: empty string → null?
+		std::string bytes = convertor.to_bytes(value);
+		writeIdentifier(TagClass::Universal, PC::Primitive, UniversalType::UTF8String);
+		writeLength(bytes.length());
+		output << bytes;
 	}
 
 };