Create and use TypeLengthValue deserializer

This commit is contained in:
Matt Nadareski
2025-09-24 08:54:25 -04:00
parent 11d6560290
commit ffe1d9a82d
5 changed files with 324 additions and 295 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using SabreTools.Serialization.ASN1;
using Xunit;
@@ -7,123 +6,13 @@ namespace SabreTools.Serialization.Test.ASN1
{
public class TypeLengthValueTests
{
#region Construction
[Fact]
public void Constructor_EmptyArray_Throws()
{
int index = 0;
byte[] data = [];
Assert.Throws<InvalidDataException>(() => new TypeLengthValue(data, ref index));
}
[Fact]
public void Constructor_ValidArrayNegativeIndex_Throws()
{
int index = -1;
byte[] data = [0x00];
Assert.Throws<IndexOutOfRangeException>(() => new TypeLengthValue(data, ref index));
}
[Fact]
public void Constructor_ValidArrayOverIndex_Throws()
{
int index = 10;
byte[] data = [0x00];
Assert.Throws<IndexOutOfRangeException>(() => new TypeLengthValue(data, ref index));
}
[Fact]
public void Constructor_ValidMinimalArray()
{
int index = 0;
byte[] data = [0x00];
var tlv = new TypeLengthValue(data, ref index);
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
Assert.Equal(default, tlv.Length);
Assert.Null(tlv.Value);
}
[Fact]
public void Constructor_EmptyStream_Throws()
{
Stream data = new MemoryStream([], 0, 0, false, false);
Assert.Throws<InvalidDataException>(() => new TypeLengthValue(data));
}
[Fact]
public void Constructor_ValidMinimalStream()
{
Stream data = new MemoryStream([0x00]);
var tlv = new TypeLengthValue(data);
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
Assert.Equal(default, tlv.Length);
Assert.Null(tlv.Value);
}
[Fact]
public void Constructor_ValidBoolean()
{
Stream data = new MemoryStream([0x01, 0x01, 0x01]);
var tlv = new TypeLengthValue(data);
Assert.Equal(ASN1Type.V_ASN1_BOOLEAN, tlv.Type);
Assert.Equal(1UL, tlv.Length);
Assert.NotNull(tlv.Value);
byte[]? valueAsArray = tlv.Value as byte[];
Assert.NotNull(valueAsArray);
byte actual = Assert.Single(valueAsArray);
Assert.Equal(0x01, actual);
}
[Theory]
[InlineData(new byte[] { 0x26, 0x81, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x82, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x83, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x84, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x85, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
public void Constructor_ComplexValue(byte[] arr)
{
Stream data = new MemoryStream(arr);
var tlv = new TypeLengthValue(data);
Assert.Equal(ASN1Type.V_ASN1_CONSTRUCTED | ASN1Type.V_ASN1_OBJECT, tlv.Type);
Assert.Equal(3UL, tlv.Length);
Assert.NotNull(tlv.Value);
TypeLengthValue[]? valueAsArray = tlv.Value as TypeLengthValue[];
Assert.NotNull(valueAsArray);
TypeLengthValue actual = Assert.Single(valueAsArray);
Assert.Equal(ASN1Type.V_ASN1_BOOLEAN, actual.Type);
Assert.Equal(1UL, actual.Length);
Assert.NotNull(actual.Value);
}
[Theory]
[InlineData(new byte[] { 0x26, 0x80 })]
[InlineData(new byte[] { 0x26, 0x89 })]
public void Constructor_ComplexValueInvalidLength_Throws(byte[] arr)
{
Stream data = new MemoryStream(arr);
Assert.Throws<InvalidOperationException>(() => new TypeLengthValue(data));
}
#endregion
#region Formatting
[Fact]
public void Format_EOC()
{
string expected = "Type: V_ASN1_EOC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_EOC, 0, null);
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_EOC, Length = 0, Value = null };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -132,7 +21,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ZeroLength()
{
string expected = "Type: V_ASN1_NULL, Length: 0";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_NULL, 0, null);
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_NULL, Length = 0, Value = null };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -141,7 +30,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_InvalidConstructed()
{
string expected = "Type: V_ASN1_OBJECT, V_ASN1_CONSTRUCTED, Length: 1, Value: [INVALID DATA TYPE]";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT | ASN1Type.V_ASN1_CONSTRUCTED, 1, (object?)false);
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_OBJECT | ASN1Type.V_ASN1_CONSTRUCTED, Length = 1, Value = (object?)false };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -150,8 +39,8 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidConstructed()
{
string expected = "Type: V_ASN1_OBJECT, V_ASN1_CONSTRUCTED, Length: 3, Value:\n Type: V_ASN1_BOOLEAN, Length: 1, Value: True";
var boolTlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 1, new byte[] { 0x01 });
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT | ASN1Type.V_ASN1_CONSTRUCTED, 3, new TypeLengthValue[] { boolTlv });
var boolTlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BOOLEAN, Length = 1, Value = new byte[] { 0x01 } };
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_OBJECT | ASN1Type.V_ASN1_CONSTRUCTED, Length = 3, Value = new TypeLengthValue[] { boolTlv } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -160,7 +49,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_InvalidDataType()
{
string expected = "Type: V_ASN1_OBJECT, Length: 1, Value: [INVALID DATA TYPE]";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT, 1, (object?)false);
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_OBJECT, Length = 1, Value = (object?)false };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -169,7 +58,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_InvalidLength()
{
string expected = "Type: V_ASN1_NULL, Length: 1, Value: [NO DATA]";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_NULL, 1, Array.Empty<byte>());
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_NULL, Length = 1, Value = Array.Empty<byte>() };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -178,7 +67,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_InvalidBooleanLength()
{
string expected = "Type: V_ASN1_BOOLEAN, Length: 2 [Expected length of 1], Value: True";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 2, new byte[] { 0x01 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BOOLEAN, Length = 2, Value = new byte[] { 0x01 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -187,7 +76,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_InvalidBooleanArrayLength()
{
string expected = "Type: V_ASN1_BOOLEAN, Length: 1 [Expected value length of 1], Value: True";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 1, new byte[] { 0x01, 0x00 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BOOLEAN, Length = 1, Value = new byte[] { 0x01, 0x00 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -196,7 +85,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidBoolean()
{
string expected = "Type: V_ASN1_BOOLEAN, Length: 1, Value: True";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 1, new byte[] { 0x01 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BOOLEAN, Length = 1, Value = new byte[] { 0x01 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -205,7 +94,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidInteger()
{
string expected = "Type: V_ASN1_INTEGER, Length: 1, Value: 1";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_INTEGER, 1, new byte[] { 0x01 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_INTEGER, Length = 1, Value = new byte[] { 0x01 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -214,7 +103,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidBitString_NoBits()
{
string expected = "Type: V_ASN1_BIT_STRING, Length: 1, Value with 0 unused bits";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BIT_STRING, 1, new byte[] { 0x00 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BIT_STRING, Length = 1, Value = new byte[] { 0x00 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -223,7 +112,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidBitString_Bits()
{
string expected = "Type: V_ASN1_BIT_STRING, Length: 1, Value with 1 unused bits: 01";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BIT_STRING, 1, new byte[] { 0x01, 0x01 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BIT_STRING, Length = 1, Value = new byte[] { 0x01, 0x01 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -232,7 +121,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidOctetString()
{
string expected = "Type: V_ASN1_OCTET_STRING, Length: 1, Value: 01";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OCTET_STRING, 1, new byte[] { 0x01 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_OCTET_STRING, Length = 1, Value = new byte[] { 0x01 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -241,7 +130,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidObject()
{
string expected = "Type: V_ASN1_OBJECT, Length: 3, Value: 0.1.2.3 (/ITU-T/1/2/3)";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT, 3, new byte[] { 0x01, 0x02, 0x03 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_OBJECT, Length = 3, Value = new byte[] { 0x01, 0x02, 0x03 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -250,7 +139,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidUTF8String()
{
string expected = "Type: V_ASN1_UTF8STRING, Length: 3, Value: ABC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_UTF8STRING, 3, new byte[] { 0x41, 0x42, 0x43 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_UTF8STRING, Length = 3, Value = new byte[] { 0x41, 0x42, 0x43 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -259,7 +148,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidPrintableString()
{
string expected = "Type: V_ASN1_PRINTABLESTRING, Length: 3, Value: ABC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_PRINTABLESTRING, 3, new byte[] { 0x41, 0x42, 0x43 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_PRINTABLESTRING, Length = 3, Value = new byte[] { 0x41, 0x42, 0x43 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -268,7 +157,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidTeletexString()
{
string expected = "Type: V_ASN1_TELETEXSTRING, Length: 3, Value: ABC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_TELETEXSTRING, 3, new byte[] { 0x41, 0x42, 0x43 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_TELETEXSTRING, Length = 3, Value = new byte[] { 0x41, 0x42, 0x43 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -277,7 +166,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidIA5String()
{
string expected = "Type: V_ASN1_IA5STRING, Length: 3, Value: ABC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_IA5STRING, 3, new byte[] { 0x41, 0x42, 0x43 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_IA5STRING, Length = 3, Value = new byte[] { 0x41, 0x42, 0x43 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -286,7 +175,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_InvalidUTCTime()
{
string expected = "Type: V_ASN1_UTCTIME, Length: 3, Value: ABC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_UTCTIME, 3, new byte[] { 0x41, 0x42, 0x43 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_UTCTIME, Length = 3, Value = new byte[] { 0x41, 0x42, 0x43 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -295,7 +184,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidUTCTime()
{
string expected = "Type: V_ASN1_UTCTIME, Length: 3, Value: 1980-01-01 00:00:00";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_UTCTIME, 3, new byte[] { 0x31, 0x39, 0x38, 0x30, 0x2D, 0x30, 0x31, 0x2D, 0x30, 0x31, 0x20, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_UTCTIME, Length = 3, Value = new byte[] { 0x31, 0x39, 0x38, 0x30, 0x2D, 0x30, 0x31, 0x2D, 0x30, 0x31, 0x20, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -304,7 +193,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidBmpString()
{
string expected = "Type: V_ASN1_BMPSTRING, Length: 6, Value: ABC";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BMPSTRING, 6, new byte[] { 0x41, 0x00, 0x42, 0x00, 0x43, 0x00 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_BMPSTRING, Length = 6, Value = new byte[] { 0x41, 0x00, 0x42, 0x00, 0x43, 0x00 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
@@ -313,7 +202,7 @@ namespace SabreTools.Serialization.Test.ASN1
public void Format_ValidUnformatted()
{
string expected = "Type: V_ASN1_OBJECT_DESCRIPTOR, Length: 1, Value: 01";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT_DESCRIPTOR, 1, new byte[] { 0x01 });
var tlv = new TypeLengthValue { Type = ASN1Type.V_ASN1_OBJECT_DESCRIPTOR, Length = 1, Value = new byte[] { 0x01 } };
string actual = tlv.Format();
Assert.Equal(expected, actual);
}

View File

@@ -0,0 +1,145 @@
using System.IO;
using System.Linq;
using SabreTools.Serialization.Deserializers;
using Xunit;
namespace SabreTools.Serialization.Test.Deserializers
{
public class TypeLengthValueTests
{
[Fact]
public void NullArray_Null()
{
byte[]? data = null;
int offset = 0;
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
}
[Fact]
public void EmptyArray_Null()
{
byte[]? data = [];
int offset = 0;
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
}
[Fact]
public void InvalidArray_Null()
{
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
int offset = 0;
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
}
[Fact]
public void NullStream_Null()
{
Stream? data = null;
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
[Fact]
public void EmptyStream_Null()
{
Stream? data = new MemoryStream([]);
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
[Fact]
public void InvalidStream_Null()
{
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
[Fact]
public void ValidMinimalStream_NotNull()
{
Stream data = new MemoryStream([0x00]);
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.NotNull(actual);
Assert.Equal(Serialization.ASN1.ASN1Type.V_ASN1_EOC, actual.Type);
Assert.Equal(default, actual.Length);
Assert.Null(actual.Value);
}
[Fact]
public void ValidBoolean_NotNull()
{
Stream data = new MemoryStream([0x01, 0x01, 0x01]);
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.NotNull(actual);
Assert.Equal(Serialization.ASN1.ASN1Type.V_ASN1_BOOLEAN, actual.Type);
Assert.Equal(1UL, actual.Length);
Assert.NotNull(actual.Value);
byte[]? valueAsArray = actual.Value as byte[];
Assert.NotNull(valueAsArray);
byte actualValue = Assert.Single(valueAsArray);
Assert.Equal(0x01, actualValue);
}
[Theory]
[InlineData(new byte[] { 0x26, 0x81, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x82, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x83, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x84, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x85, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
[InlineData(new byte[] { 0x26, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
public void ComplexValue_NotNull(byte[] arr)
{
Stream data = new MemoryStream(arr);
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.NotNull(actual);
Assert.Equal(Serialization.ASN1.ASN1Type.V_ASN1_CONSTRUCTED | Serialization.ASN1.ASN1Type.V_ASN1_OBJECT, actual.Type);
Assert.Equal(3UL, actual.Length);
Assert.NotNull(actual.Value);
Serialization.ASN1.TypeLengthValue[]? valueAsArray = actual.Value as Serialization.ASN1.TypeLengthValue[];
Assert.NotNull(valueAsArray);
Serialization.ASN1.TypeLengthValue actualSub = Assert.Single(valueAsArray);
Assert.Equal(Serialization.ASN1.ASN1Type.V_ASN1_BOOLEAN, actualSub.Type);
Assert.Equal(1UL, actualSub.Length);
Assert.NotNull(actualSub.Value);
}
[Theory]
[InlineData(new byte[] { 0x26, 0x80 })]
[InlineData(new byte[] { 0x26, 0x89 })]
public void ComplexValueInvalidLength_Null(byte[] arr)
{
Stream data = new MemoryStream(arr);
var deserializer = new TypeLengthValue();
var actual = deserializer.Deserialize(data);
Assert.Null(actual);
}
}
}

View File

@@ -39,13 +39,19 @@ namespace SabreTools.Serialization.ASN1
if (data.Position < 0 || data.Position >= data.Length)
throw new IndexOutOfRangeException(nameof(data));
// Create the deserializer
var deserializer = new Deserializers.TypeLengthValue();
// Create the output list to return
var topLevelValues = new List<TypeLengthValue>();
// Loop through the data and return all top-level values
while (data.Position < data.Length)
{
var topLevelValue = new TypeLengthValue(data);
var topLevelValue = deserializer.Deserialize(data);
if (topLevelValue == null)
break;
topLevelValues.Add(topLevelValue);
}

View File

@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.ASN1
{
@@ -15,56 +12,17 @@ namespace SabreTools.Serialization.ASN1
/// <summary>
/// The ASN.1 type
/// </summary>
public ASN1Type Type { get; private set; }
public ASN1Type Type { get; set; }
/// <summary>
/// Length of the value
/// </summary>
public ulong Length { get; private set; }
public ulong Length { get; set; }
/// <summary>
/// Generic value associated with <see cref="Type"/>
/// </summary>
public object? Value { get; private set; }
/// <summary>
/// Manual constructor
/// </summary>
public TypeLengthValue(ASN1Type type, ulong length, object? value)
{
Type = type;
Length = length;
Value = value;
}
/// <summary>
/// Read from the source data array at an index
/// </summary>
/// <param name="data">Byte array representing data to read</param>
/// <param name="index">Index within the array to read at</param>
public TypeLengthValue(byte[] data, ref int index)
{
// If the data is invalid
if (data.Length == 0)
throw new InvalidDataException(nameof(data));
if (index < 0 || index >= data.Length)
throw new IndexOutOfRangeException(nameof(index));
using var stream = new MemoryStream(data);
stream.Seek(index, SeekOrigin.Begin);
if (!Parse(stream))
throw new InvalidDataException(nameof(data));
}
/// <summary>
/// Read from the source data stream
/// </summary>
/// <param name="data">Stream representing data to read</param>
public TypeLengthValue(Stream data)
{
if (!Parse(data))
throw new InvalidDataException(nameof(data));
}
public object? Value { get; set; }
/// <summary>
/// Format the TLV as a string
@@ -214,120 +172,5 @@ namespace SabreTools.Serialization.ASN1
// Return the formatted string
return formatBuilder.ToString();
}
/// <summary>
/// Parse a stream into TLV data
/// </summary>
/// <param name="data">Stream representing data to read</param>
/// <returns>Indication if parsing was successful</returns>
private bool Parse(Stream data)
{
// If the data is invalid
if (data.Length == 0 || !data.CanRead)
return false;
if (data.Position < 0 || data.Position >= data.Length)
throw new IndexOutOfRangeException(nameof(data));
// Get the type and modifiers
Type = (ASN1Type)data.ReadByteValue();
// If we have an end indicator, we just return
if (Type == ASN1Type.V_ASN1_EOC)
return true;
// Get the length of the value
Length = ReadLength(data);
// Read the value
#if NET20 || NET35
if ((Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
#else
if (Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
#endif
{
var valueList = new List<TypeLengthValue>();
long currentIndex = data.Position;
while (data.Position < currentIndex + (long)Length)
{
valueList.Add(new TypeLengthValue(data));
}
Value = valueList.ToArray();
}
else
{
// TODO: Get more granular based on type
Value = data.ReadBytes((int)Length);
}
return true;
}
/// <summary>
/// Reads the length field for a type
/// </summary>
/// <param name="data">Stream representing data to read</param>
/// <returns>The length value read from the array</returns>
private static ulong ReadLength(Stream data)
{
// If the data is invalid
if (data.Length == 0 || !data.CanRead)
throw new InvalidDataException(nameof(data));
if (data.Position < 0 || data.Position >= data.Length)
throw new IndexOutOfRangeException(nameof(data));
// Read the first byte, assuming it's the length
byte length = data.ReadByteValue();
// If the bit 7 is not set, then use the value as it is
if ((length & 0x80) == 0)
return length;
// Otherwise, use the value as the number of remaining bytes to read
int bytesToRead = length & ~0x80;
// Assemble the length based on byte count
ulong fullLength = 0;
switch (bytesToRead)
{
case 8:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 7;
case 7:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 6;
case 6:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 5;
case 5:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 4;
case 4:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 3;
case 3:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 2;
case 2:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 1;
case 1:
fullLength |= data.ReadByteValue();
break;
default:
throw new InvalidOperationException();
}
return fullLength;
}
}
}

View File

@@ -0,0 +1,146 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Extensions;
namespace SabreTools.Serialization.Deserializers
{
public class TypeLengthValue : BaseBinaryDeserializer<ASN1.TypeLengthValue>
{
/// <inheritdoc/>
public override ASN1.TypeLengthValue? Deserialize(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
var tlv = ParseTypeLengthValue(data);
if (tlv == null)
return null;
// Return the Type/Length/Value
return tlv;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
/// Parse a Stream into a TypeLengthValue
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled TypeLengthValue on success, null on error</returns>
public ASN1.TypeLengthValue? ParseTypeLengthValue(Stream data)
{
var obj = new ASN1.TypeLengthValue();
// Get the type and modifiers
obj.Type = (ASN1.ASN1Type)data.ReadByteValue();
// If we have an end indicator, we just return
if (obj.Type == ASN1.ASN1Type.V_ASN1_EOC)
return obj;
// Get the length of the value
ulong? length = ReadLength(data);
if (length == null)
return null;
// Set the length
obj.Length = length.Value;
// Read the value
#if NET20 || NET35
if ((obj.Type & ASN1.ASN1Type.V_ASN1_CONSTRUCTED) != 0)
#else
if (obj.Type.HasFlag(ASN1.ASN1Type.V_ASN1_CONSTRUCTED))
#endif
{
var valueList = new List<ASN1.TypeLengthValue>();
long currentIndex = data.Position;
while (data.Position < currentIndex + (long)obj.Length)
{
var value = ParseTypeLengthValue(data);
valueList.Add(value);
}
obj.Value = valueList.ToArray();
}
else
{
// TODO: Get more granular based on type
obj.Value = data.ReadBytes((int)obj.Length);
}
return obj;
}
/// <summary>
/// Reads the length field for a type
/// </summary>
/// <param name="data">Stream representing data to read</param>
/// <returns>The length value read from the array</returns>
private static ulong? ReadLength(Stream data)
{
// Read the first byte, assuming it's the length
byte length = data.ReadByteValue();
// If the bit 7 is not set, then use the value as it is
if ((length & 0x80) == 0)
return length;
// Otherwise, use the value as the number of remaining bytes to read
int bytesToRead = length & ~0x80;
// Assemble the length based on byte count
ulong fullLength = 0;
switch (bytesToRead)
{
case 8:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 7;
case 7:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 6;
case 6:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 5;
case 5:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 4;
case 4:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 3;
case 3:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 2;
case 2:
fullLength |= data.ReadByteValue();
fullLength <<= 8;
goto case 1;
case 1:
fullLength |= data.ReadByteValue();
break;
default:
return null;
}
return fullLength;
}
}
}