using System; using System.Numerics; using System.Text; using SabreTools.Data.Models.ASN1; using SabreTools.ObjectIdentifier; namespace SabreTools.Data.Extensions { public static class TypeLengthValueExtensions { /// /// Format a TypeLengthValue as a string /// /// Padding level of the item when formatting /// String representing the TypeLengthValue, if possible public static string Format(this TypeLengthValue tlv, int paddingLevel = 0) { // Create the left-padding string string padding = new(' ', paddingLevel); // Create the string builder var formatBuilder = new StringBuilder(); // Append the type formatBuilder.Append($"{padding}Type: {tlv.Type}"); if (tlv.Type == ASN1Type.V_ASN1_EOC) return formatBuilder.ToString(); // Append the length formatBuilder.Append($", Length: {tlv.Length}"); if (tlv.Length == 0) return formatBuilder.ToString(); // If we have a constructed type #if NET20 || NET35 if ((tlv.Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0) #else if (tlv.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED)) #endif { if (tlv.Value is not TypeLengthValue[] valueAsObjectArray) { formatBuilder.Append(", Value: [INVALID DATA TYPE]"); return formatBuilder.ToString(); } formatBuilder.Append(", Value:\n"); for (int i = 0; i < valueAsObjectArray.Length; i++) { var child = valueAsObjectArray[i]; string childString = child.Format(paddingLevel + 1); formatBuilder.Append($"{childString}\n"); } return formatBuilder.ToString().TrimEnd('\n'); } // Get the value as a byte array if (tlv.Value is not byte[] valueAsByteArray) { formatBuilder.Append(", Value: [INVALID DATA TYPE]"); return formatBuilder.ToString(); } else if (valueAsByteArray.Length == 0) { formatBuilder.Append(", Value: [NO DATA]"); return formatBuilder.ToString(); } // If we have a primitive type switch (tlv.Type) { /// case ASN1Type.V_ASN1_BOOLEAN: if (tlv.Length > 1) formatBuilder.Append($" [Expected length of 1]"); else if (valueAsByteArray.Length > 1) formatBuilder.Append($" [Expected value length of 1]"); bool booleanValue = valueAsByteArray[0] != 0x00; formatBuilder.Append($", Value: {booleanValue}"); break; /// case ASN1Type.V_ASN1_INTEGER: Array.Reverse(valueAsByteArray); var integerValue = new BigInteger(valueAsByteArray); formatBuilder.Append($", Value: {integerValue}"); break; /// case ASN1Type.V_ASN1_BIT_STRING: // TODO: Read into a BitArray and print that out instead? int unusedBits = valueAsByteArray[0]; if (unusedBits == 0) formatBuilder.Append($", Value with {unusedBits} unused bits"); else formatBuilder.Append($", Value with {unusedBits} unused bits: {BitConverter.ToString(valueAsByteArray, 1).Replace('-', ' ')}"); break; /// case ASN1Type.V_ASN1_OCTET_STRING: formatBuilder.Append($", Value: {BitConverter.ToString(valueAsByteArray).Replace('-', ' ')}"); break; /// /// case ASN1Type.V_ASN1_OBJECT: // Derive array of values ulong[] objectNodes = Parser.ParseDERIntoArray(valueAsByteArray, tlv.Length); // Append the dot and modified OID-IRI notations string? dotNotationString = Parser.ParseOIDToDotNotation(objectNodes); string? oidIriString = Parser.ParseOIDToOIDIRINotation(objectNodes); formatBuilder.Append($", Value: {dotNotationString} ({oidIriString})"); break; /// case ASN1Type.V_ASN1_UTF8STRING: formatBuilder.Append($", Value: {Encoding.UTF8.GetString(valueAsByteArray)}"); break; /// case ASN1Type.V_ASN1_PRINTABLESTRING: formatBuilder.Append($", Value: {Encoding.ASCII.GetString(valueAsByteArray)}"); break; //case ASN1Type.V_ASN1_T61STRING: case ASN1Type.V_ASN1_TELETEXSTRING: formatBuilder.Append($", Value: {Encoding.ASCII.GetString(valueAsByteArray)}"); break; /// case ASN1Type.V_ASN1_IA5STRING: formatBuilder.Append($", Value: {Encoding.ASCII.GetString(valueAsByteArray)}"); break; case ASN1Type.V_ASN1_UTCTIME: string utctimeString = Encoding.ASCII.GetString(valueAsByteArray); if (DateTime.TryParse(utctimeString, out DateTime utctimeDateTime)) formatBuilder.Append($", Value: {utctimeDateTime:yyyy-MM-dd HH:mm:ss}"); else formatBuilder.Append($", Value: {utctimeString}"); break; /// case ASN1Type.V_ASN1_BMPSTRING: formatBuilder.Append($", Value: {Encoding.Unicode.GetString(valueAsByteArray)}"); break; // Unimplemented case ASN1Type.V_ASN1_EOC: case ASN1Type.V_ASN1_NULL: case ASN1Type.V_ASN1_OBJECT_DESCRIPTOR: case ASN1Type.V_ASN1_EXTERNAL: case ASN1Type.V_ASN1_REAL: case ASN1Type.V_ASN1_ENUMERATED: case ASN1Type.V_ASN1_SEQUENCE: case ASN1Type.V_ASN1_SET: case ASN1Type.V_ASN1_NUMERICSTRING: case ASN1Type.V_ASN1_VIDEOTEXSTRING: case ASN1Type.V_ASN1_GENERALIZEDTIME: case ASN1Type.V_ASN1_GRAPHICSTRING: case ASN1Type.V_ASN1_ISO64STRING: case ASN1Type.V_ASN1_GENERALSTRING: case ASN1Type.V_ASN1_UNIVERSALSTRING: case ASN1Type.V_ASN1_PRIMITIVE_TAG: case ASN1Type.V_ASN1_CONSTRUCTED: case ASN1Type.V_ASN1_APPLICATION: case ASN1Type.V_ASN1_CONTEXT_SPECIFIC: case ASN1Type.V_ASN1_PRIVATE: default: formatBuilder.Append($", Value: {BitConverter.ToString(valueAsByteArray).Replace('-', ' ')}"); break; } // Return the formatted string return formatBuilder.ToString(); } } }