slightly improved UTCTime and GeneralizedTime support v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed, 30 Jun 2021 20:11:39 +0200
branchv_0
changeset 22 9b6f86760384
parent 21 705036445672
child 23 8941a679299f
slightly improved UTCTime and GeneralizedTime support
src/lib/ASN1ContentHandler.h
src/lib/BasicASN1Reader.h
--- a/src/lib/ASN1ContentHandler.h	Mon Jun 28 22:48:04 2021 +0200
+++ b/src/lib/ASN1ContentHandler.h	Wed Jun 30 20:11:39 2021 +0200
@@ -201,7 +201,9 @@
 		int8_t hour = 0;
 		int8_t minute = 0;
 		int8_t second = 0;
-		// TODO: ms/ns
+		int32_t nanosecond = 0;
+		int8_t timezoneHour = 0;
+		int8_t timezoneMinute = 0;
 
 		virtual ~DateTime() {
 		}
@@ -215,7 +217,10 @@
 			result << "T" << std::setw(2) << (int) hour;
 			result << ":" << std::setw(2) << (int) minute;
 			result << ":" << std::setw(2) << (int) second;
-			result << "+00:00"; // TODO: timezone
+			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();
 		}
 	};
--- a/src/lib/BasicASN1Reader.h	Mon Jun 28 22:48:04 2021 +0200
+++ b/src/lib/BasicASN1Reader.h	Wed Jun 30 20:11:39 2021 +0200
@@ -49,6 +49,46 @@
 		Constructed = 1
 	};
 
+	enum UniversalType : uint64_t {
+		EndOfContent = 0x00,
+		Boolean = 0x01,
+		Integer = 0x02,
+		BitString = 0x03,
+		OctetString = 0x04,
+		Null = 0x05,
+		ObjectIdentifier = 0x06,
+		ObjectDescriptor = 0x07,
+		External = 0x08,
+		Real = 0x09,
+		Enumerated = 0xA,
+		Embedded = 0xB,
+		UTF8String = 0xC,
+		RelativeObjectIdentifier = 0xD,
+		Time = 0xE,
+		Reserved = 0xF,
+		Sequence = 0x10,
+		Set = 0x11,
+		NumericString = 0x12,
+		PrintableString = 0x13,
+		T61String = 0x14,
+		VideotexString = 0x15,
+		IA5String = 0x16,
+		UTCTime = 0x17,
+		GeneralizedTime = 0x18,
+		GraphicString = 0x19,
+		VisibleString = 0x1A,
+		GeneralString = 0x1B,
+		UniversalString = 0x1C,
+		CharacterString = 0x1D,
+		BMPString = 0x1E,
+		Date = 0x1F,
+		TimeOfDay = 0x20,
+		DateTime = 0x21,
+		Duration = 0x22,
+		ObjectIdentifierIRI = 0x23,
+		RelativeObjectIdentifierIRI = 0x24,
+	};
+
 	class Header {
 	public:
 		TagClass tagClass;
@@ -136,46 +176,46 @@
 		// TODO: check tagClass and pc
 
 		// TODO: constants, more types
-		if (typeHeader.tag == 0 && typeHeader.tagClass == TagClass::Universal && typeHeader.pc == PC::Primitive) {
+		if (typeHeader.tag == UniversalType::EndOfContent && typeHeader.tagClass == TagClass::Universal && typeHeader.pc == PC::Primitive) {
 			handlers.writeCollectionEnd();
-		} else if (typeHeader.tag == 16) {
+		} else if (typeHeader.tag == UniversalType::Sequence) {
 			level.push_back({typeHeader.definiteLength, typeHeader.length, getBytesRead()}); // TODO: transaction
 			handlers.writeCollectionStart(ASN1ContentHandler::CollectionType::Sequence);
-		} else if (typeHeader.tag == 17) {
+		} else if (typeHeader.tag == UniversalType::Set) {
 			level.push_back({typeHeader.definiteLength, typeHeader.length, getBytesRead()}); // TODO: transaction
 			handlers.writeCollectionStart(ASN1ContentHandler::CollectionType::Set);
 		} else if (typeHeader.pc == PC::Constructed) {
 			level.push_back({typeHeader.definiteLength, typeHeader.length, getBytesRead()}); // TODO: transaction
 			handlers.writeCollectionStart(ASN1ContentHandler::CollectionType::Constructed);
-		} else if (typeHeader.tag == 5 && typeHeader.length == 0) {
+		} else if (typeHeader.tag == UniversalType::Null && typeHeader.length == 0) {
 			handlers.writeNull();
-		} else if (typeHeader.tag == 1) {
+		} else if (typeHeader.tag == UniversalType::Boolean && typeHeader.definiteLength && typeHeader.length == 1) {
 			bool value;
 			read((uint8_t*) & value, 1);
 			handlers.writeBoolean(value);
-		} else if (typeHeader.tag == 2 && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
+		} else if (typeHeader.tag == UniversalType::Integer && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			// TODO: check available bytes before allocating buffer
 			std::vector<uint8_t> value(typeHeader.length, 0x00);
 			read(value.data(), typeHeader.length);
 			handlers.writeInteger(ASN1ContentHandler::Integer(value));
-		} else if (typeHeader.tag == 6 && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
+		} else if (typeHeader.tag == UniversalType::ObjectIdentifier && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			std::vector<uint8_t> value(typeHeader.length, 0x00);
 			read(value.data(), typeHeader.length);
 			handlers.writeOID({value});
-		} else if (typeHeader.tag == 12 && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
+		} else if (typeHeader.tag == UniversalType::UTF8String && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			// TODO: check available bytes before allocating buffer
 			std::string s;
 			s.resize(typeHeader.length);
 			read((uint8_t*) s.data(), typeHeader.length);
 			handlers.writeString(ASN1ContentHandler::StringType::UTF8String, s);
-		} else if (typeHeader.tag == 19 && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
+		} else if (typeHeader.tag == UniversalType::PrintableString && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			// TODO: check encoding
 			// TODO: check available bytes before allocating buffer
 			std::string s;
 			s.resize(typeHeader.length);
 			read((uint8_t*) s.data(), typeHeader.length);
 			handlers.writeString(ASN1ContentHandler::StringType::PrintableString, s);
-		} else if (typeHeader.tag == 0x17 && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
+		} else if (typeHeader.tag == UniversalType::UTCTime && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
 			// TODO: check available bytes before allocating buffer
 			std::string s;
 			s.resize(typeHeader.length);
@@ -184,7 +224,14 @@
 			ASN1ContentHandler::DateTime dateTime;
 
 			std::smatch match;
-			if (std::regex_match(s, match, std::regex("([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})Z"))) {
+			if (std::regex_match(s, match, std::regex("([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})?(Z|([+-][0-9]{2})'?([0-9]{2})'?)"))) {
+				// Supported UTCTime formats:
+				// YYMMDDhhmmZ
+				// YYMMDDhhmmssZ
+				// YYMMDDhhmm+hhmm
+				// YYMMDDhhmm-hhmm
+				// YYMMDDhhmmss+hhmm
+				// YYMMDDhhmmss-hhmm
 				int i = 1;
 				uint32_t year = std::stoi(match[i++]);
 				dateTime.year = year < 50 ? 2000 + year : 1900 + year;
@@ -192,17 +239,46 @@
 				dateTime.day = std::stoi(match[i++]);
 				dateTime.hour = std::stoi(match[i++]);
 				dateTime.minute = std::stoi(match[i++]);
-				dateTime.precision = ASN1ContentHandler::DateTime::Precision::Second;
+				dateTime.precision = match[i].length() ? ASN1ContentHandler::DateTime::Precision::Second : ASN1ContentHandler::DateTime::Precision::Minute;
+				dateTime.second = match[i].length() ? std::stoi(match[i++]) : 0;
+				if (match[i++] != "Z") {
+					dateTime.timezoneHour = std::stoi(match[i++]);
+					dateTime.timezoneMinute = std::stoi(match[i++]);
+				}
 				handlers.writeDateTime(ASN1ContentHandler::DateTimeType::UTCTime, dateTime);
 			} else {
-				// FIXME: decode more UTCTime formats:
-				// YYMMDDhhmmZ
-				// YYMMDDhhmm+hh'mm'
-				// YYMMDDhhmm-hh'mm'
-				// YYMMDDhhmmssZ
-				// YYMMDDhhmmss+hh'mm'
-				// YYMMDDhhmmss-hh'mm'
-				handlers.writeString(ASN1ContentHandler::StringType::UTF8String, "FIXME: UTCTime format not yet supported: " + s);
+				throw std::invalid_argument("Unsupported UTCTime format: " + s); // TODO: better exception
+			}
+
+		} else if (typeHeader.tag == UniversalType::GeneralizedTime && typeHeader.tagClass == TagClass::Universal && typeHeader.definiteLength) {
+			// TODO: check available bytes before allocating buffer
+			std::string s;
+			s.resize(typeHeader.length);
+			read((uint8_t*) s.data(), typeHeader.length);
+
+			ASN1ContentHandler::DateTime dateTime;
+
+			std::smatch match;
+			if (std::regex_match(s, match, std::regex("([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})(\\.([0-9]{1,3}))?(Z|([+-][0-9]{2})'?([0-9]{2})'?)"))) {
+				int i = 1;
+				dateTime.year = std::stoi(match[i++]);
+				dateTime.month = std::stoi(match[i++]);
+				dateTime.day = std::stoi(match[i++]);
+				dateTime.hour = std::stoi(match[i++]);
+				dateTime.minute = std::stoi(match[i++]);
+				dateTime.second = match[i].length() ? std::stoi(match[i++]) : 0;
+				dateTime.precision = match[i++].length() ? ASN1ContentHandler::DateTime::Precision::Nanosecond : ASN1ContentHandler::DateTime::Precision::Second;
+				if (match[i].length() == 1) dateTime.nanosecond = std::stoi(match[i++]) * 100 * 1000000;
+				else if (match[i].length() == 2) dateTime.nanosecond = std::stoi(match[i++]) * 10 * 1000000;
+				else if (match[i].length() == 3) dateTime.nanosecond = std::stoi(match[i++]) * 1000000;
+				else i++;
+				if (match[i++] != "Z") {
+					dateTime.timezoneHour = std::stoi(match[i++]);
+					dateTime.timezoneMinute = std::stoi(match[i++]);
+				}
+				handlers.writeDateTime(ASN1ContentHandler::DateTimeType::GeneralizedTime, dateTime);
+			} else {
+				throw std::invalid_argument("Unsupported GeneralizedTime format: " + s); // TODO: better exception
 			}
 
 		} else {