Skip to content

Commit 37bc010

Browse files
committed
Enable stream construction, fix safety issues
1 parent 0a217e5 commit 37bc010

File tree

1 file changed

+98
-59
lines changed

1 file changed

+98
-59
lines changed

SabreTools.ASN1/TypeLengthValue.cs

Lines changed: 98 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Data;
4+
using System.IO;
35
using System.Numerics;
46
using System.Text;
57
using SabreTools.IO.Extensions;
@@ -14,17 +16,17 @@ public class TypeLengthValue
1416
/// <summary>
1517
/// The ASN.1 type
1618
/// </summary>
17-
public ASN1Type Type { get; }
19+
public ASN1Type Type { get; private set; }
1820

1921
/// <summary>
2022
/// Length of the value
2123
/// </summary>
22-
public ulong Length { get; }
24+
public ulong Length { get; private set; }
2325

2426
/// <summary>
2527
/// Generic value associated with <see cref="Type"/>
2628
/// </summary>
27-
public object? Value { get; }
29+
public object? Value { get; private set; }
2830

2931
/// <summary>
3032
/// Read from the source data array at an index
@@ -33,38 +35,26 @@ public class TypeLengthValue
3335
/// <param name="index">Index within the array to read at</param>
3436
public TypeLengthValue(byte[] data, ref int index)
3537
{
36-
// Get the type and modifiers
37-
Type = (ASN1Type)data[index++];
38-
39-
// If we have an end indicator, we just return
40-
if (Type == ASN1Type.V_ASN1_EOC)
41-
return;
42-
43-
// Get the length of the value
44-
Length = ReadLength(data, ref index);
45-
46-
// Read the value
47-
#if NET20 || NET35
48-
if ((Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
49-
#else
50-
if (Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
51-
#endif
52-
{
53-
var valueList = new List<TypeLengthValue>();
54-
55-
int currentIndex = index;
56-
while (index < currentIndex + (int)Length)
57-
{
58-
valueList.Add(new TypeLengthValue(data, ref index));
59-
}
38+
// If the data is invalid
39+
if (data.Length == 0)
40+
throw new InvalidDataException(nameof(data));
41+
if (index < 0 || index >= data.Length)
42+
throw new IndexOutOfRangeException(nameof(index));
43+
44+
using var stream = new MemoryStream(data);
45+
stream.Seek(index, SeekOrigin.Begin);
46+
if (!Parse(stream))
47+
throw new InvalidDataException(nameof(data));
48+
}
6049

61-
Value = valueList.ToArray();
62-
}
63-
else
64-
{
65-
// TODO: Get more granular based on type
66-
Value = data.ReadBytes(ref index, (int)Length);
67-
}
50+
/// <summary>
51+
/// Read from the source data stream
52+
/// </summary>
53+
/// <param name="data">Stream representing data to read</param>
54+
public TypeLengthValue(Stream data)
55+
{
56+
if (!Parse(data))
57+
throw new InvalidDataException(nameof(data));
6858
}
6959

7060
/// <summary>
@@ -213,52 +203,101 @@ public string Format(int paddingLevel = 0)
213203
return formatBuilder.ToString();
214204
}
215205

206+
/// <summary>
207+
/// Parse a stream into TLV data
208+
/// </summary>
209+
/// <param name="data">Stream representing data to read</param>
210+
/// <returns>Indication if parsing was successful</returns>
211+
private bool Parse(Stream data)
212+
{
213+
// If the data is invalid
214+
if (data.Length == 0 || !data.CanRead)
215+
return false;
216+
if (data.Position < 0 || data.Position >= data.Length)
217+
throw new IndexOutOfRangeException(nameof(data));
218+
219+
// Get the type and modifiers
220+
Type = (ASN1Type)data.ReadByteValue();
221+
222+
// If we have an end indicator, we just return
223+
if (Type == ASN1Type.V_ASN1_EOC)
224+
return true;
225+
226+
// Get the length of the value
227+
Length = ReadLength(data);
228+
229+
// Read the value
230+
#if NET20 || NET35
231+
if ((Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
232+
#else
233+
if (Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
234+
#endif
235+
{
236+
var valueList = new List<TypeLengthValue>();
237+
238+
long currentIndex = data.Position;
239+
while (data.Position < currentIndex + (long)Length)
240+
{
241+
valueList.Add(new TypeLengthValue(data));
242+
}
243+
244+
Value = valueList.ToArray();
245+
}
246+
else
247+
{
248+
// TODO: Get more granular based on type
249+
Value = data.ReadBytes((int)Length);
250+
}
251+
252+
return true;
253+
}
254+
216255
/// <summary>
217256
/// Reads the length field for a type
218257
/// </summary>
219-
/// <param name="data">Byte array representing data to read</param>
220-
/// <param name="index">Index within the array to read at</param>
258+
/// <param name="data">Stream representing data to read</param>
221259
/// <returns>The length value read from the array</returns>
222-
private static ulong ReadLength(byte[] data, ref int index)
260+
private static ulong ReadLength(Stream data)
223261
{
224-
// If we have invalid data, throw an exception
225-
if (data == null || index < 0 && index >= data.Length)
226-
throw new ArgumentException();
262+
// If the data is invalid
263+
if (data.Length == 0 || !data.CanRead)
264+
throw new DataException(nameof(data));
265+
if (data.Position < 0 || data.Position >= data.Length)
266+
throw new IndexOutOfRangeException(nameof(data));
227267

228268
// Read the first byte, assuming it's the length
229-
byte length = data[index++];
269+
byte length = data.ReadByteValue();
230270

231271
// If the bit 7 is not set, then use the value as it is
232272
if ((length & 0x80) == 0)
233273
return length;
234274

235275
// Otherwise, use the value as the number of remaining bytes to read
236276
int bytesToRead = length & ~0x80;
237-
byte[]? bytesRead = data.ReadBytes(ref index, bytesToRead) ?? throw new InvalidOperationException();
238-
239-
// TODO: Write extensions to read big-endian
240-
241-
// Reverse the bytes to be in big-endian order
242-
Array.Reverse(bytesRead);
243-
244-
switch (bytesRead.Length)
277+
switch (bytesToRead)
245278
{
279+
// Powers of 2
246280
case 1:
247-
return bytesRead[0];
281+
return data.ReadByteValue();
248282
case 2:
249-
return BitConverter.ToUInt16(bytesRead, 0);
250-
case 3:
251-
Array.Resize(ref bytesRead, 4);
252-
goto case 4;
283+
return data.ReadUInt16BigEndian();
253284
case 4:
254-
return BitConverter.ToUInt32(bytesRead, 0);
285+
return data.ReadUInt32BigEndian();
286+
case 8:
287+
return data.ReadUInt64BigEndian();
288+
289+
// Values between are assembled
290+
case 3:
291+
byte[] lessThan4 = data.ReadBytes(bytesToRead);
292+
Array.Resize(ref lessThan4, 4);
293+
return data.ReadUInt32BigEndian();
255294
case 5:
256295
case 6:
257296
case 7:
258-
Array.Resize(ref bytesRead, 8);
259-
goto case 8;
260-
case 8:
261-
return BitConverter.ToUInt64(bytesRead, 0);
297+
byte[] lessThan8 = data.ReadBytes(bytesToRead);
298+
Array.Resize(ref lessThan8, 8);
299+
return data.ReadUInt64BigEndian();
300+
262301
default:
263302
throw new InvalidOperationException();
264303
}

0 commit comments

Comments
 (0)