Skip to content

Commit

Permalink
[CBOR] Implement Conformance Levels (#35246)
Browse files Browse the repository at this point in the history
* implement writing sorted keys in maps

* rename NoConformance to NonStrict

* add tests for strict mode map writes

* extract common conformance level logic to helper class

* implement reader map conformance levels

* add missing struct labels

* NonStrict -> Lax

* implement CborWriter indefinite length conformance levels

* implement CborWriter tag conformance levels

* remove preserve float representation check

* implement CborReader integer encoding conformance

* implement CborReader indefinte-length conformance

* implement CborReader tag conformance level validation

* factor frame struct out of checkpoint struct

* use HashSet for reader key uniqueness

* Update src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Integer.cs

Co-Authored-By: Jeremy Barton <jbarton@microsoft.com>

* address tuple label naming consistency

* address PR feedback

* address tuple label style

* implement appropriate rollback semantics for key conformance validation

* rename CborWriter.ToArray() => CborWriter.GetEncoding(); add a span encoding method

* implement patching for indefinite-length items

* minor CborReader string refactoring

* add support for indefinite byte string literals in reader helpers

* Implement CborReader.SkipToParent();

* add a validateConformance parameter to Skip* methods

* implement CborWriter encodeIndefiniteLengths parameter

* make ReadStartMap() and ReadStartArray() return int

* strip IndefiniteLength suffix from write methods

* replace SortedList use with HashSet

* pool key encoding range HashSets

* implement writing and reading multiple root-level values

* I am disappoint

* Update src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborConformanceLevel.cs

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>

* Update src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Array.cs

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>

* Update src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>

* Update src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>

* Update src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Map.cs

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>

* address PR feedback

* clean up code and add comments; remove unneeded synchronization

* rework CborWriter map sorting implementation

* store current major type in a field

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
  • Loading branch information
eiriktsarpalis and bartonjs committed May 2, 2020
2 parents 296c82d + c736b7d commit 412b9c5
Show file tree
Hide file tree
Showing 32 changed files with 2,533 additions and 420 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,63 @@ public static void ReadArray_IndefiniteLength_HappyPath(object[] expectedValues,
Assert.Equal(CborReaderState.Finished, reader.PeekState());
}

[Theory]
[InlineData(CborConformanceLevel.Lax, "9800")]
[InlineData(CborConformanceLevel.Lax, "990000")]
[InlineData(CborConformanceLevel.Lax, "9a00000000")]
[InlineData(CborConformanceLevel.Lax, "9b0000000000000000")]
[InlineData(CborConformanceLevel.Strict, "9800")]
[InlineData(CborConformanceLevel.Strict, "990000")]
[InlineData(CborConformanceLevel.Strict, "9a00000000")]
[InlineData(CborConformanceLevel.Strict, "9b0000000000000000")]
internal static void ReadArray_NonCanonicalLengths_SupportedConformanceLevel_ShouldSucceed(CborConformanceLevel level, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding, level);
int? length = reader.ReadStartArray();
Assert.NotNull(length);
Assert.Equal(0, length!.Value);
}

[Theory]
[InlineData(CborConformanceLevel.Rfc7049Canonical, "9800")]
[InlineData(CborConformanceLevel.Rfc7049Canonical, "990000")]
[InlineData(CborConformanceLevel.Rfc7049Canonical, "9a00000000")]
[InlineData(CborConformanceLevel.Rfc7049Canonical, "9b0000000000000000")]
[InlineData(CborConformanceLevel.Ctap2Canonical, "9800")]
[InlineData(CborConformanceLevel.Ctap2Canonical, "990000")]
[InlineData(CborConformanceLevel.Ctap2Canonical, "9a00000000")]
[InlineData(CborConformanceLevel.Ctap2Canonical, "9b0000000000000000")]
internal static void ReadArray_NonCanonicalLengths_UnSupportedConformanceLevel_ShouldThrowFormatException(CborConformanceLevel level, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding, level);
Assert.Throws<FormatException>(() => reader.ReadStartArray());
Assert.Equal(0, reader.BytesRead);
}

[Theory]
[InlineData(CborConformanceLevel.Lax, "9fff")]
[InlineData(CborConformanceLevel.Strict, "9fff")]
internal static void ReadArray_IndefiniteLength_SupportedConformanceLevel_ShouldSucceed(CborConformanceLevel level, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding, level);
int? length = reader.ReadStartArray();
Assert.Null(length);
}

[Theory]
[InlineData(CborConformanceLevel.Rfc7049Canonical, "9fff")]
[InlineData(CborConformanceLevel.Ctap2Canonical, "9fff")]
internal static void ReadArray_IndefiniteLength_UnSupportedConformanceLevel_ShouldThrowFormatException(CborConformanceLevel level, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding, level);
Assert.Throws<FormatException>(() => reader.ReadStartArray());
Assert.Equal(0, reader.BytesRead);
}

[Theory]
[InlineData("80", 0)]
[InlineData("8101", 1)]
Expand All @@ -67,7 +124,7 @@ public static void ReadArray_DefiniteLengthExceeded_ShouldThrowInvalidOperationE
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();
Assert.Equal(expectedLength, (int)length!.Value);

for (int i = 0; i < expectedLength; i++)
Expand All @@ -88,13 +145,13 @@ public static void ReadArray_DefiniteLengthExceeded_WithNestedData_ShouldThrowIn
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();
Assert.Equal(expectedLength, (int)length!.Value);

for (int i = 0; i < expectedLength; i++)
{
ulong? nestedLength = reader.ReadStartArray();
Assert.Equal(1, (int)nestedLength!.Value);
int? nestedLength = reader.ReadStartArray();
Assert.Equal(1, nestedLength!.Value);
reader.ReadInt64();
reader.ReadEndArray();
}
Expand Down Expand Up @@ -146,7 +203,7 @@ public static void EndReadArray_DefiniteLengthNotMet_ShouldThrowInvalidOperation
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();
Assert.Equal(expectedLength, (int)length!.Value);

for (int i = 1; i < expectedLength; i++)
Expand All @@ -167,13 +224,13 @@ public static void EndReadArray_DefiniteLengthNotMet_WithNestedData_ShouldThrowI
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();
Assert.Equal(expectedLength, (int)length!.Value);

for (int i = 1; i < expectedLength; i++)
{
ulong? nestedLength = reader.ReadStartArray();
Assert.Equal(1, (int)nestedLength!.Value);
int? nestedLength = reader.ReadStartArray();
Assert.Equal(1, nestedLength!.Value);
reader.ReadInt64();
reader.ReadEndArray();
}
Expand All @@ -199,7 +256,7 @@ public static void ReadArray_IncorrectDefiniteLength_ShouldThrowFormatException(
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();
Assert.Equal(expectedLength, (int)length!.Value);

for (int i = 0; i < actualLength; i++)
Expand All @@ -220,13 +277,13 @@ public static void ReadArray_IncorrectDefiniteLength_NestedValues_ShouldThrowFor
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();
Assert.Equal(expectedLength, (int)length!.Value);

for (int i = 0; i < actualLength; i++)
{
ulong? innerLength = reader.ReadStartArray();
Assert.Equal(1, (int)innerLength!.Value);
int? innerLength = reader.ReadStartArray();
Assert.Equal(1, innerLength!.Value);
reader.ReadInt64();
reader.ReadEndArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#nullable enable
using System.Linq;
using System.Numerics;
using Test.Cryptography;
using Xunit;

Expand All @@ -21,46 +22,78 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp
Assert.Equal(CborReaderState.Null, reader.PeekState());
reader.ReadNull();
break;

case bool expected:
Assert.Equal(CborReaderState.Boolean, reader.PeekState());
bool b = reader.ReadBoolean();
Assert.Equal(expected, b);
break;

case int expected:
VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0);
int i = reader.ReadInt32();
Assert.Equal(expected, i);
break;

case long expected:
VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0);
long l = reader.ReadInt64();
Assert.Equal(expected, l);
break;

case ulong expected:
VerifyPeekInteger(reader, isUnsignedInteger: true);
ulong u = reader.ReadUInt64();
Assert.Equal(expected, u);
break;

case float expected:
Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.PeekState());
float f = reader.ReadSingle();
Assert.Equal(expected, f);
break;

case double expected:
Assert.Equal(CborReaderState.DoublePrecisionFloat, reader.PeekState());
double d = reader.ReadDouble();
Assert.Equal(expected, d);
break;

case decimal expected:
Assert.Equal(CborReaderState.Tag, reader.PeekState());
decimal dec = reader.ReadDecimal();
Assert.Equal(expected, dec);
break;

case BigInteger expected:
Assert.Equal(CborReaderState.Tag, reader.PeekState());
BigInteger bigint = reader.ReadBigInteger();
Assert.Equal(expected, bigint);
break;

case DateTimeOffset expected:
Assert.Equal(CborReaderState.Tag, reader.PeekState());
DateTimeOffset dto = reader.ReadDateTimeOffset();
Assert.Equal(expected, dto);
break;

case string expected:
Assert.Equal(CborReaderState.TextString, reader.PeekState());
string s = reader.ReadTextString();
Assert.Equal(expected, s);
break;

case byte[] expected:
Assert.Equal(CborReaderState.ByteString, reader.PeekState());
byte[] bytes = reader.ReadByteString();
Assert.Equal(expected.ByteArrayToHex(), bytes.ByteArrayToHex());
break;

case string[] expectedChunks when CborWriterTests.Helpers.IsIndefiniteLengthByteString(expectedChunks):
byte[][] expectedByteChunks = expectedChunks.Skip(1).Select(ch => ch.HexToByteArray()).ToArray();
VerifyValue(reader, expectedByteChunks, expectDefiniteLengthCollections);
break;

case string[] expectedChunks:
Assert.Equal(CborReaderState.StartTextString, reader.PeekState());
reader.ReadStartTextStringIndefiniteLength();
Expand All @@ -73,6 +106,7 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp
Assert.Equal(CborReaderState.EndTextString, reader.PeekState());
reader.ReadEndTextStringIndefiniteLength();
break;

case byte[][] expectedChunks:
Assert.Equal(CborReaderState.StartByteString, reader.PeekState());
reader.ReadStartByteStringIndefiniteLength();
Expand All @@ -89,9 +123,25 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp
case object[] nested when CborWriterTests.Helpers.IsCborMapRepresentation(nested):
VerifyMap(reader, nested, expectDefiniteLengthCollections);
break;

case object[] nested when CborWriterTests.Helpers.IsEncodedValueRepresentation(nested):
string expectedHexEncoding = (string)nested[1];
string actualHexEncoding = reader.ReadEncodedValue().ByteArrayToHex();
Assert.Equal(expectedHexEncoding, actualHexEncoding);
break;

case object[] nested when CborWriterTests.Helpers.IsTaggedValueRepresentation(nested):
CborTag expectedTag = (CborTag)nested[0];
object expectedNestedValue = nested[1];
Assert.Equal(CborReaderState.Tag, reader.PeekState());
Assert.Equal(expectedTag, reader.ReadTag());
VerifyValue(reader, expectedNestedValue, expectDefiniteLengthCollections);
break;

case object[] nested:
VerifyArray(reader, nested, expectDefiniteLengthCollections);
break;

default:
throw new ArgumentException($"Unrecognized argument type {expectedValue.GetType()}");
}
Expand All @@ -107,7 +157,7 @@ public static void VerifyArray(CborReader reader, object[] expectedValues, bool
{
Assert.Equal(CborReaderState.StartArray, reader.PeekState());

ulong? length = reader.ReadStartArray();
int? length = reader.ReadStartArray();

if (expectDefiniteLengthCollections)
{
Expand Down Expand Up @@ -137,7 +187,7 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex

Assert.Equal(CborReaderState.StartMap, reader.PeekState());

ulong? length = reader.ReadStartMap();
int? length = reader.ReadStartMap();

if (expectDefiniteLengthCollections)
{
Expand All @@ -151,7 +201,7 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex

foreach (object value in expectedValues.Skip(1))
{
VerifyValue(reader, value);
VerifyValue(reader, value, expectDefiniteLengthCollections);
}

Assert.Equal(CborReaderState.EndMap, reader.PeekState());
Expand Down Expand Up @@ -217,9 +267,6 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex
"4201",
"61",
"6261",
// invalid utf8 strings
"61ff",
"62f090",
// indefinite-length strings with missing break byte
"5f41ab40",
"7f62616260",
Expand Down
Loading

0 comments on commit 412b9c5

Please sign in to comment.