62 Commits
1.3.2 ... main

Author SHA1 Message Date
Matt Nadareski
0bdfc5426d Update notice 2025-09-24 11:12:22 -04:00
Matt Nadareski
827913d09c Add notice 2025-09-24 08:24:43 -04:00
Matt Nadareski
2d955c8ef7 Update IO to 1.7.5 2025-09-24 08:16:16 -04:00
Matt Nadareski
ebdab151be Bump version 2025-09-22 17:55:59 -04:00
Matt Nadareski
a664cff34c Update IO to 1.7.4 2025-09-22 17:54:53 -04:00
Matt Nadareski
8110cdd0cb Bump version 2025-09-20 22:22:45 -04:00
Matt Nadareski
7a10aab5d1 Update IO to 1.7.3 2025-09-20 22:21:34 -04:00
Matt Nadareski
1516a8647a There 2025-09-10 21:53:37 -04:00
Matt Nadareski
4ee64a2c49 Bump version 2025-09-05 10:29:40 -04:00
Matt Nadareski
9cdf10d481 Suppress TFM issues 2025-09-05 10:27:16 -04:00
Matt Nadareski
04996584b5 Update IO to 1.7.2 2025-09-05 10:23:47 -04:00
Matt Nadareski
98b930e231 Bump version 2025-09-01 15:31:18 -04:00
Matt Nadareski
2da26d2dca Update packages 2025-09-01 15:30:58 -04:00
Matt Nadareski
32a226ede6 Bump version 2025-07-24 08:16:02 -04:00
Matt Nadareski
e9abf681c3 Be consistent about end-of-file newlines 2025-07-24 08:12:50 -04:00
Matt Nadareski
1c10ebf648 Add .NET Standard 2.0 and 2.1 2025-07-24 08:12:11 -04:00
Matt Nadareski
ea6d89d2da Update nuget packages 2025-07-24 08:11:12 -04:00
Matt Nadareski
f457e12d85 Update IO to 1.6.3 2025-05-12 08:25:05 -04:00
Matt Nadareski
3c561a60e7 Fix how conditions are used for references 2025-02-25 21:13:56 -05:00
Matt Nadareski
5830e2a2fd Ensure .NET versions are installed for testing 2024-12-19 10:50:45 -05:00
Matt Nadareski
10163eb9eb Ensure .NET versions are installed for testing 2024-12-19 10:48:13 -05:00
Matt Nadareski
4e45de3fab Bump version 2024-12-16 14:09:59 -05:00
Matt Nadareski
ec92bb22aa Update IO to 1.6.2 2024-12-16 14:08:54 -05:00
Matt Nadareski
6a0826e95c Allow symbols to be packed 2024-12-16 14:07:12 -05:00
Matt Nadareski
b7010349a0 Use publish script and update README 2024-12-06 11:03:01 -05:00
Matt Nadareski
005a447e35 Add automatic testing 2024-11-26 14:44:55 -05:00
Matt Nadareski
ca1cb5a66e Bump version 2024-11-26 14:42:59 -05:00
Matt Nadareski
4c6ef33af2 Update OID and add tests 2024-11-26 13:16:02 -05:00
Matt Nadareski
eb519af488 Use collection expression in OID 2024-11-26 12:58:40 -05:00
Matt Nadareski
402fe65e20 Sync modified OIDIRI to regular 2024-11-26 12:57:45 -05:00
Matt Nadareski
c22f0f2c6e Update static ASN1 and add tests 2024-11-26 12:55:58 -05:00
Matt Nadareski
92d5a097a8 Write TLV tests, update code accordingly 2024-11-26 12:47:15 -05:00
Matt Nadareski
d722f1fad2 Comment out V_ASN1_UNIVERSAL 2024-11-26 11:58:20 -05:00
Matt Nadareski
a570f9a96a Modifiers should come after values 2024-11-26 11:56:41 -05:00
Matt Nadareski
d4a118ba04 Add non-formatting tests for TLV 2024-11-26 11:49:31 -05:00
Matt Nadareski
9ceea4f85a Normalize exceptions 2024-11-26 11:49:07 -05:00
Matt Nadareski
a0a7f23b6c Simplify unknown format printing 2024-11-26 11:48:14 -05:00
Matt Nadareski
bca1754d42 0 is not an unknown type 2024-11-26 11:45:48 -05:00
Matt Nadareski
a6a281deb8 Assemble length byte-wise instead of trickery 2024-11-26 11:39:57 -05:00
Matt Nadareski
37bc010762 Enable stream construction, fix safety issues 2024-11-26 10:37:26 -05:00
Matt Nadareski
0a217e5e6b Fix field access in TLV 2024-11-26 09:57:01 -05:00
Matt Nadareski
73e7b035b8 Update IO to 1.6.0 2024-11-26 09:54:31 -05:00
Matt Nadareski
7276ab2199 Update test package versions 2024-11-26 09:53:44 -05:00
Matt Nadareski
fef4d30add Add skeleton test project 2024-11-26 09:51:28 -05:00
Matt Nadareski
e632f8537b Bump version 2024-11-20 15:55:54 -05:00
Matt Nadareski
ce8c4b1831 Fix issue with 0 unused bits 2024-11-20 15:03:26 -05:00
Matt Nadareski
2bdb56aca2 Bump version 2024-11-15 20:51:12 -05:00
Matt Nadareski
ce6ffd3f7f Framework only matters for executable 2024-11-15 20:48:13 -05:00
Matt Nadareski
c06618c66c Update IO to 1.5.1 2024-11-15 20:46:27 -05:00
Matt Nadareski
eb356483b4 Fix path in GHA 2024-11-13 11:58:19 -05:00
Matt Nadareski
0e76552df8 Fix path in GHA 2024-11-13 11:55:55 -05:00
Matt Nadareski
070274e33f Bump version 2024-11-13 00:50:36 -05:00
Matt Nadareski
dd64d6f843 Add .NET 9 to target frameworks 2024-11-13 00:50:16 -05:00
Matt Nadareski
69992f64be Add proper multi-framework support 2024-11-05 19:40:49 -05:00
Matt Nadareski
e63921fbc2 Linq just isn't needed 2024-11-05 19:38:24 -05:00
Matt Nadareski
69dc21c814 Bump version 2024-10-01 13:32:24 -04:00
Matt Nadareski
4c07f24e3c Update IO to 1.4.12 2024-10-01 13:31:41 -04:00
Matt Nadareski
b6b43d9e1b Fix README location for package 2024-10-01 13:09:54 -04:00
Matt Nadareski
7c972c1ea2 Move project to subfolder 2024-10-01 13:07:45 -04:00
Matt Nadareski
7c40583898 Some old .NET simplification 2024-10-01 12:41:53 -04:00
Matt Nadareski
ef479c783a Remove Linq requirement from old .NET 2024-10-01 01:53:26 -04:00
Matt Nadareski
1ebd9f82ee Update IO to 1.4.11 2024-09-25 10:54:48 -04:00
22 changed files with 860 additions and 191 deletions

View File

@@ -1,4 +1,4 @@
name: Nuget Pack
name: Build and Test
on:
push:
@@ -16,25 +16,28 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
dotnet-version: |
6.0.x
8.0.x
9.0.x
- name: Run tests
run: dotnet test
- name: Pack
run: dotnet pack
- name: Run publish script
run: ./publish-nix.sh
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: 'Nuget Package'
path: 'bin/Release/*.nupkg'
path: "*.nupkg,*.snupkg"
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: 'bin/Release/*.nupkg'
artifacts: "*.nupkg,*.snupkg"
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True

View File

@@ -11,7 +11,13 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: |
6.0.x
8.0.x
9.0.x
- name: Build
run: dotnet build
run: dotnet build
- name: Run tests
run: dotnet test

View File

@@ -1,31 +0,0 @@
using System.Collections.Generic;
namespace SabreTools.ASN1
{
/// <summary>
/// ASN.1 Parser
/// </summary>
public static class AbstractSyntaxNotationOne
{
/// <summary>
/// Parse a byte array into a DER-encoded ASN.1 structure
/// </summary>
/// <param name="data">Byte array representing the data</param>
/// <param name="pointer">Current pointer into the data</param>
/// <returns></returns>
public static List<TypeLengthValue> Parse(byte[] data, int pointer)
{
// Create the output list to return
var topLevelValues = new List<TypeLengthValue>();
// Loop through the data and return all top-level values
while (pointer < data.Length)
{
var topLevelValue = new TypeLengthValue(data, ref pointer);
topLevelValues.Add(topLevelValue);
}
return topLevelValues;
}
}
}

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2018-2025 Matt Nadareski
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,5 +1,18 @@
# SabreTools.ASN1
[![Build and Test](https://github.com/SabreTools/SabreTools.ASN1/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/SabreTools/SabreTools.ASN1/actions/workflows/build_and_test.yml)
**NOTICE:** This library has been deprecated. All functionality formerly in this library is in [SabreTools.Serialization](https://github.com/SabreTools/SabreTools.Serialization) as of version 1.9.6.
This library comprises of code to parse Abstract Syntax Notation One (ASN.1) codes and output them into various formats. This also performs some rudimentary validation based on the chains that are input.
**Note:** This code is known to be incomplete and will be added to over time.
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.ASN1).
## Releases
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/SabreTools.ASN1/releases)
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/SabreTools.ASN1/releases/rolling)

View File

@@ -0,0 +1,61 @@
using System;
using System.IO;
using Xunit;
namespace SabreTools.ASN1.Test
{
public class AbstractSyntaxNotationOneTests
{
[Fact]
public void Parse_EmptyArray_Throws()
{
byte[] data = [];
Assert.Throws<InvalidDataException>(() => AbstractSyntaxNotationOne.Parse(data, 0));
}
[Fact]
public void Parse_ValidArrayNegativeIndex_Throws()
{
byte[] data = [0x00];
Assert.Throws<IndexOutOfRangeException>(() => AbstractSyntaxNotationOne.Parse(data, -1));
}
[Fact]
public void Parse_ValidArrayOverIndex_Throws()
{
byte[] data = [0x00];
Assert.Throws<IndexOutOfRangeException>(() => AbstractSyntaxNotationOne.Parse(data, 10));
}
[Fact]
public void Parse_ValidMinimalArray()
{
byte[] data = [0x00];
var tlvs = AbstractSyntaxNotationOne.Parse(data, 0);
var tlv = Assert.Single(tlvs);
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
Assert.Equal(default, tlv.Length);
Assert.Null(tlv.Value);
}
[Fact]
public void Parse_EmptyStream_Throws()
{
Stream data = new MemoryStream([], 0, 0, false, false);
Assert.Throws<InvalidDataException>(() => AbstractSyntaxNotationOne.Parse(data));
}
[Fact]
public void Parse_ValidMinimalStream()
{
Stream data = new MemoryStream([0x00]);
var tlvs = AbstractSyntaxNotationOne.Parse(data);
var tlv = Assert.Single(tlvs);
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
Assert.Equal(default, tlv.Length);
Assert.Null(tlv.Value);
}
}
}

View File

@@ -0,0 +1,110 @@
using Xunit;
namespace SabreTools.ASN1.Test
{
// These tests are known to be incomplete due to the sheer number
// of possible OIDs that exist. The tests below are a minimal
// representation of functionality to guarantee proper behavior
// not necessarily absolute outputs
public class ObjectIdentifierTests
{
#region ASN.1
[Fact]
public void ASN1Notation_AlwaysNull()
{
ulong[]? values = null;
string? actual = ObjectIdentifier.ParseOIDToASN1Notation(values);
Assert.Null(actual);
}
#endregion
#region Dot Notation
[Fact]
public void DotNotation_NullValues_Null()
{
ulong[]? values = null;
string? actual = ObjectIdentifier.ParseOIDToDotNotation(values);
Assert.Null(actual);
}
[Fact]
public void DotNotation_EmptyValues_Null()
{
ulong[]? values = [];
string? actual = ObjectIdentifier.ParseOIDToDotNotation(values);
Assert.Null(actual);
}
[Fact]
public void DotNotation_Values_Formatted()
{
string expected = "0.1.2.3";
ulong[]? values = [0, 1, 2, 3];
string? actual = ObjectIdentifier.ParseOIDToDotNotation(values);
Assert.Equal(expected, actual);
}
#endregion
#region Modified OID-IRI
[Fact]
public void ModifiedOIDIRI_NullValues_Null()
{
ulong[]? values = null;
string? actual = ObjectIdentifier.ParseOIDToModifiedOIDIRI(values);
Assert.Null(actual);
}
[Fact]
public void ModifiedOIDIRI_EmptyValues_Null()
{
ulong[]? values = [];
string? actual = ObjectIdentifier.ParseOIDToModifiedOIDIRI(values);
Assert.Null(actual);
}
[Fact]
public void ModifiedOIDIRI_Values_Formatted()
{
string expected = "/ITU-T/[question]/2/3";
ulong[]? values = [0, 1, 2, 3];
string? actual = ObjectIdentifier.ParseOIDToModifiedOIDIRI(values);
Assert.Equal(expected, actual);
}
#endregion
#region OID-IRI
[Fact]
public void OIDIRI_NullValues_Null()
{
ulong[]? values = null;
string? actual = ObjectIdentifier.ParseOIDToOIDIRINotation(values);
Assert.Null(actual);
}
[Fact]
public void OIDIRI_EmptyValues_Null()
{
ulong[]? values = [];
string? actual = ObjectIdentifier.ParseOIDToOIDIRINotation(values);
Assert.Null(actual);
}
[Fact]
public void OIDIRI_Values_Formatted()
{
string expected = "/ITU-T/1/2/3";
ulong[]? values = [0, 1, 2, 3];
string? actual = ObjectIdentifier.ParseOIDToOIDIRINotation(values);
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<NoWarn>NU1903</NoWarn>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.ASN1\SabreTools.ASN1.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,322 @@
using System;
using System.IO;
using Xunit;
namespace SabreTools.ASN1.Test
{
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);
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
public void Format_ZeroLength()
{
string expected = "Type: V_ASN1_NULL, Length: 0";
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_NULL, 0, null);
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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);
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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);
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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>());
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
[Fact]
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 });
string actual = tlv.Format();
Assert.Equal(expected, actual);
}
#endregion
}
}

View File

@@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.3.2</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Serialization and deserialization helpers for various types</Description>
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.ASN1</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>asn asn1 dot oid</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
<PackageReference Include="Net30.LinqBridge" Version="1.3.0" />
<PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.4.5" />
</ItemGroup>
</Project>

View File

@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.ASN1", "SabreTools.ASN1.csproj", "{88EA40DD-E313-479C-94EA-0AB948DB4720}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.ASN1", "SabreTools.ASN1\SabreTools.ASN1.csproj", "{88EA40DD-E313-479C-94EA-0AB948DB4720}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.ASN1.Test", "SabreTools.ASN1.Test\SabreTools.ASN1.Test.csproj", "{5C740920-9505-4B58-AC17-2967559F6AE7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -18,5 +20,9 @@ Global
{88EA40DD-E313-479C-94EA-0AB948DB4720}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88EA40DD-E313-479C-94EA-0AB948DB4720}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88EA40DD-E313-479C-94EA-0AB948DB4720}.Release|Any CPU.Build.0 = Release|Any CPU
{5C740920-9505-4B58-AC17-2967559F6AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C740920-9505-4B58-AC17-2967559F6AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C740920-9505-4B58-AC17-2967559F6AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C740920-9505-4B58-AC17-2967559F6AE7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SabreTools.ASN1
{
/// <summary>
/// ASN.1 Parser
/// </summary>
public static class AbstractSyntaxNotationOne
{
/// <summary>
/// Parse a byte array into a DER-encoded ASN.1 structure
/// </summary>
/// <param name="data">Byte array representing the data</param>
/// <param name="pointer">Current pointer into the data</param>
public static List<TypeLengthValue> Parse(byte[] data, int pointer)
{
// If the data is invalid
if (data.Length == 0)
throw new InvalidDataException(nameof(data));
if (pointer < 0 || pointer >= data.Length)
throw new IndexOutOfRangeException(nameof(pointer));
using var stream = new MemoryStream(data);
stream.Seek(pointer, SeekOrigin.Begin);
return Parse(stream);
}
/// <summary>
/// Parse a stream into a DER-encoded ASN.1 structure
/// </summary>
/// <param name="data">Stream representing the data</param>
public static List<TypeLengthValue> Parse(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));
// 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);
topLevelValues.Add(topLevelValue);
}
return topLevelValues;
}
}
}

View File

@@ -8,17 +8,6 @@ namespace SabreTools.ASN1
[Flags]
public enum ASN1Type : byte
{
#region Modifiers
V_ASN1_UNIVERSAL = 0x00,
V_ASN1_PRIMITIVE_TAG = 0x1F,
V_ASN1_CONSTRUCTED = 0x20,
V_ASN1_APPLICATION = 0x40,
V_ASN1_CONTEXT_SPECIFIC = 0x80,
V_ASN1_PRIVATE = 0xC0,
#endregion
#region Types
V_ASN1_EOC = 0x00,
@@ -51,5 +40,19 @@ namespace SabreTools.ASN1
V_ASN1_BMPSTRING = 0x1E,
#endregion
#region Modifiers
// Commented out because it is the default
// and can interfere with V_ASN1_EOC
// V_ASN1_UNIVERSAL = 0x00,
V_ASN1_PRIMITIVE_TAG = 0x1F,
V_ASN1_CONSTRUCTED = 0x20,
V_ASN1_APPLICATION = 0x40,
V_ASN1_CONTEXT_SPECIFIC = 0x80,
V_ASN1_PRIVATE = 0xC0,
#endregion
}
}

View File

@@ -9,13 +9,12 @@ namespace SabreTools.ASN1
/// Parse an OID in separated-value notation into ASN.1 notation
/// </summary>
/// <param name="values">List of values to check against</param>
/// <param name="index">Current index into the list</param>
/// <returns>ASN.1 formatted string, if possible</returns>
/// <remarks>
public static string? ParseOIDToASN1Notation(ulong[]? values, ref int index)
public static string? ParseOIDToASN1Notation(ulong[]? values)
{
// TODO: Once the modified OID-IRI formatting is done, make an ASN.1 notation version
return null;
}
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System;
using System.Text;
#pragma warning disable CS0162 // Unreachable code detected
@@ -48,7 +48,14 @@ namespace SabreTools.ASN1
// Add trailing items as just values
nameBuilder.Append("/");
nameBuilder.Append(string.Join("/", values.Skip(index).Select(v => v.ToString()).ToArray()));
// Get the remaining values in a new array
var remainingValues = new ulong[values.Length - index];
Array.Copy(values, index, remainingValues, 0, remainingValues.Length);
// Convert the values and append to the builder
var stringValues = Array.ConvertAll(remainingValues, v => v.ToString());
nameBuilder.Append(string.Join("/", stringValues));
// Create and return the string
return nameBuilder.ToString();
@@ -77,7 +84,7 @@ namespace SabreTools.ASN1
#region Start
var oidPath = $"/{values[index]}";
var oidPath = string.Empty;
switch (values[index++])
{
case 0: goto oid_0;
@@ -18029,4 +18036,4 @@ namespace SabreTools.ASN1
#endregion
}
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System;
using System.Text;
namespace SabreTools.ASN1
@@ -41,7 +41,14 @@ namespace SabreTools.ASN1
// Add trailing items as just values
nameBuilder.Append("/");
nameBuilder.Append(string.Join("/", values.Skip(index).Select(index => index.ToString()).ToArray()));
// Get the remaining values in a new array
var remainingValues = new ulong[values.Length - index];
Array.Copy(values, index, remainingValues, 0, remainingValues.Length);
// Convert the values and append to the builder
var stringValues = Array.ConvertAll(remainingValues, v => v.ToString());
nameBuilder.Append(string.Join("/", stringValues));
// Create and return the string
return nameBuilder.ToString();
@@ -66,7 +73,7 @@ namespace SabreTools.ASN1
#region Start
var oidPath = $"/{values[index]}";
var oidPath = string.Empty;
switch (values[index++])
{
case 0: goto oid_0;
@@ -93,7 +100,8 @@ namespace SabreTools.ASN1
case 5: return "/ITU-R/R-Recommendation";
case 9: return $"{oidPath}/Data";
default: return $"{oidPath}/{values[index - 1]}";
};
}
;
// recommendation
#region 0.0.*
@@ -896,4 +904,4 @@ namespace SabreTools.ASN1
#endregion
}
}
}
}

View File

@@ -24,7 +24,7 @@ namespace SabreTools.ASN1
int firstNode = Math.DivRem(data[0], 40, out int secondNode);
// Create a list for all nodes
List<ulong> nodes = new List<ulong> { (ulong)firstNode, (ulong)secondNode };
List<ulong> nodes = [(ulong)firstNode, (ulong)secondNode];
// All other nodes are encoded uniquely
int offset = 1;
@@ -65,7 +65,7 @@ namespace SabreTools.ASN1
nodes.Add(dotValue);
}
return nodes.ToArray();
return [.. nodes];
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System;
namespace SabreTools.ASN1
{
@@ -18,7 +18,8 @@ namespace SabreTools.ASN1
if (values == null || values.Length == 0)
return null;
return string.Join(".", values.Select(v => v.ToString()).ToArray());
var stringValues = Array.ConvertAll(values, v => v.ToString());
return string.Join(".", [.. stringValues]);
}
}
}
}

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<IncludeSymbols>true</IncludeSymbols>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.6.4</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Serialization and deserialization helpers for various types</Description>
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.ASN1</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>asn asn1 dot oid</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="SabreTools.ASN1.Test" />
</ItemGroup>
<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))" />
<PackageReference Include="SabreTools.IO" Version="1.7.5" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Numerics;
using System.Text;
using SabreTools.IO.Extensions;
@@ -27,6 +27,16 @@ namespace SabreTools.ASN1
/// </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>
@@ -34,38 +44,26 @@ namespace SabreTools.ASN1
/// <param name="index">Index within the array to read at</param>
public TypeLengthValue(byte[] data, ref int index)
{
// Get the type and modifiers
this.Type = (ASN1Type)data[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));
// If we have an end indicator, we just return
if (this.Type == ASN1Type.V_ASN1_EOC)
return;
using var stream = new MemoryStream(data);
stream.Seek(index, SeekOrigin.Begin);
if (!Parse(stream))
throw new InvalidDataException(nameof(data));
}
// Get the length of the value
this.Length = ReadLength(data, ref index);
// Read the value
#if NET20 || NET35
if ((this.Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
#else
if (this.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
#endif
{
var valueList = new List<TypeLengthValue>();
int currentIndex = index;
while (index < currentIndex + (int)this.Length)
{
valueList.Add(new TypeLengthValue(data, ref index));
}
this.Value = valueList.ToArray();
}
else
{
// TODO: Get more granular based on type
this.Value = data.ReadBytes(ref index, (int)this.Length);
}
/// <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));
}
/// <summary>
@@ -78,31 +76,27 @@ namespace SabreTools.ASN1
// Create the left-padding string
string padding = new(' ', paddingLevel);
// If we have an invalid item
if (this.Type == 0)
return $"{padding}UNKNOWN TYPE";
// Create the string builder
var formatBuilder = new StringBuilder();
// Append the type
formatBuilder.Append($"{padding}Type: {this.Type}");
if (this.Type == ASN1Type.V_ASN1_EOC)
formatBuilder.Append($"{padding}Type: {Type}");
if (Type == ASN1Type.V_ASN1_EOC)
return formatBuilder.ToString();
// Append the length
formatBuilder.Append($", Length: {this.Length}");
if (this.Length == 0)
formatBuilder.Append($", Length: {Length}");
if (Length == 0)
return formatBuilder.ToString();
// If we have a constructed type
#if NET20 || NET35
if ((this.Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
if ((Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
#else
if (this.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
if (Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
#endif
{
if (this.Value is not TypeLengthValue[] valueAsObjectArray)
if (Value is not TypeLengthValue[] valueAsObjectArray)
{
formatBuilder.Append(", Value: [INVALID DATA TYPE]");
return formatBuilder.ToString();
@@ -120,19 +114,26 @@ namespace SabreTools.ASN1
}
// Get the value as a byte array
if (this.Value is not byte[] valueAsByteArray)
if (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 (this.Type)
switch (Type)
{
/// <see href="https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-boolean"/>
case ASN1Type.V_ASN1_BOOLEAN:
if (this.Length > 1 || valueAsByteArray.Length > 1)
if (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}");
@@ -149,7 +150,10 @@ namespace SabreTools.ASN1
case ASN1Type.V_ASN1_BIT_STRING:
// TODO: Read into a BitArray and print that out instead?
int unusedBits = valueAsByteArray[0];
formatBuilder.Append($", Value with {unusedBits} unused bits: {BitConverter.ToString(valueAsByteArray.Skip(1).ToArray()).Replace('-', ' ')}");
if (unusedBits == 0)
formatBuilder.Append($", Value with {unusedBits} unused bits");
else
formatBuilder.Append($", Value with {unusedBits} unused bits: {BitConverter.ToString(valueAsByteArray, 1).Replace('-', ' ')}");
break;
/// <see href="https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-octet-string"/>
@@ -161,7 +165,7 @@ namespace SabreTools.ASN1
/// <see cref="http://snmpsharpnet.com/index.php/2009/03/02/ber-encoding-and-decoding-oid-values/"/>
case ASN1Type.V_ASN1_OBJECT:
// Derive array of values
ulong[] objectNodes = ObjectIdentifier.ParseDERIntoArray(valueAsByteArray, this.Length);
ulong[] objectNodes = ObjectIdentifier.ParseDERIntoArray(valueAsByteArray, Length);
// Append the dot and modified OID-IRI notations
string? dotNotationString = ObjectIdentifier.ParseOIDToDotNotation(objectNodes);
@@ -192,7 +196,7 @@ namespace SabreTools.ASN1
case ASN1Type.V_ASN1_UTCTIME:
string utctimeString = Encoding.ASCII.GetString(valueAsByteArray);
if (DateTime.TryParse(utctimeString, out DateTime utctimeDateTime))
formatBuilder.Append($", Value: {utctimeDateTime}");
formatBuilder.Append($", Value: {utctimeDateTime:yyyy-MM-dd HH:mm:ss}");
else
formatBuilder.Append($", Value: {utctimeString}");
break;
@@ -203,7 +207,7 @@ namespace SabreTools.ASN1
break;
default:
formatBuilder.Append($", Value (Unknown Format): {BitConverter.ToString(this.Value as byte[] ?? []).Replace('-', ' ')}");
formatBuilder.Append($", Value: {BitConverter.ToString(valueAsByteArray).Replace('-', ' ')}");
break;
}
@@ -211,20 +215,70 @@ namespace SabreTools.ASN1
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">Byte array representing data to read</param>
/// <param name="index">Index within the array to read at</param>
/// <param name="data">Stream representing data to read</param>
/// <returns>The length value read from the array</returns>
private static ulong ReadLength(byte[] data, ref int index)
private static ulong ReadLength(Stream data)
{
// If we have invalid data, throw an exception
if (data == null || index < 0 && index >= data.Length)
throw new ArgumentException();
// 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[index++];
byte length = data.ReadByteValue();
// If the bit 7 is not set, then use the value as it is
if ((length & 0x80) == 0)
@@ -232,34 +286,48 @@ namespace SabreTools.ASN1
// Otherwise, use the value as the number of remaining bytes to read
int bytesToRead = length & ~0x80;
byte[]? bytesRead = data.ReadBytes(ref index, bytesToRead) ?? throw new InvalidOperationException();
// TODO: Write extensions to read big-endian
// Reverse the bytes to be in big-endian order
Array.Reverse(bytesRead);
switch (bytesRead.Length)
// Assemble the length based on byte count
ulong fullLength = 0;
switch (bytesToRead)
{
case 1:
return bytesRead[0];
case 2:
return BitConverter.ToUInt16(bytesRead, 0);
case 3:
Array.Resize(ref bytesRead, 4);
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:
return BitConverter.ToUInt32(bytesRead, 0);
case 5:
case 6:
case 7:
Array.Resize(ref bytesRead, 8);
goto case 8;
case 8:
return BitConverter.ToUInt64(bytesRead, 0);
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

@@ -1,14 +1,14 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - .NET 9.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
NO_BUILD=false
while getopts "uba" OPTION
while getopts "b" OPTION
do
case $OPTION in
b)
@@ -32,5 +32,5 @@ then
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.ASN1.csproj --output $BUILD_FOLDER
dotnet pack SabreTools.ASN1/SabreTools.ASN1.csproj --output $BUILD_FOLDER
fi

View File

@@ -1,5 +1,5 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - .NET 9.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
@@ -22,5 +22,5 @@ if (!$NO_BUILD.IsPresent)
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.ASN1.csproj --output $BUILD_FOLDER
dotnet pack SabreTools.ASN1\SabreTools.ASN1.csproj --output $BUILD_FOLDER
}