Skip to content

Commit

Permalink
[CBOR] Implement tag and special value support for CborWriter and Cbo…
Browse files Browse the repository at this point in the history
…rReader (#34046)

* Implement tag support for CborWriter and CborReader

* Implement CBOR special value support

* add nested special value tests

* implement half-precision float decoding; address feedback

* address style

* remove dead code

* add checks for CBOR tags in indefinite-length collections

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

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

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
  • Loading branch information
eiriktsarpalis and bartonjs authored Mar 30, 2020
1 parent d4d8039 commit c3f7da2
Show file tree
Hide file tree
Showing 24 changed files with 923 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public partial class CborReaderTests
[InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "98190102030405060708090a0b0c0d0e0f101112131415161718181819")]
[InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "840120604107")]
[InlineData(new object[] { "lorem", "ipsum", "dolor" }, "83656c6f72656d65697073756d65646f6c6f72")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "84f4f6faffc00000fb7ff0000000000000")]
public static void ReadArray_SimpleValues_HappyPath(object[] expectedValues, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
Expand Down Expand Up @@ -48,6 +49,7 @@ public static void ReadArray_NestedValues_HappyPath(object[] expectedValues, str
[InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff")]
[InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "9f0120604107ff")]
[InlineData(new object[] { "lorem", "ipsum", "dolor" }, "9f656c6f72656d65697073756d65646f6c6f72ff")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "9ff4f6faffc00000fb7ff0000000000000ff")]
public static void ReadArray_IndefiniteLength_HappyPath(object[] expectedValues, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp
{
switch (expectedValue)
{
case null:
Assert.Equal(CborReaderState.Null, reader.Peek());
reader.ReadNull();
break;
case bool expected:
Assert.Equal(CborReaderState.Boolean, reader.Peek());
bool b = reader.ReadBoolean();
Assert.Equal(expected, b);
break;
case int expected:
VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0);
long i = reader.ReadInt64();
Expand All @@ -32,15 +41,25 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp
ulong u = reader.ReadUInt64();
Assert.Equal(expected, u);
break;
case float expected:
Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.Peek());
float f = reader.ReadSingle();
Assert.Equal(expected, f);
break;
case double expected:
Assert.Equal(CborReaderState.DoublePrecisionFloat, reader.Peek());
double d = reader.ReadDouble();
Assert.Equal(expected, d);
break;
case string expected:
Assert.Equal(CborReaderState.TextString, reader.Peek());
string s = reader.ReadTextString();
Assert.Equal(expected, s);
break;
case byte[] expected:
Assert.Equal(CborReaderState.ByteString, reader.Peek());
byte[] b = reader.ReadByteString();
Assert.Equal(expected.ByteArrayToHex(), b.ByteArrayToHex());
byte[] bytes = reader.ReadByteString();
Assert.Equal(expected.ByteArrayToHex(), bytes.ByteArrayToHex());
break;
case string[] expectedChunks:
Assert.Equal(CborReaderState.StartTextString, reader.Peek());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,49 @@ public static void ReadCborNegativeIntegerEncoding_SingleValue_HappyPath(ulong e
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData(2, 2, "c202")]
[InlineData(0, "2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")]
[InlineData(1, 1363896240, "c11a514b67b0")]
[InlineData(23, new byte[] { 1, 2, 3, 4 }, "d74401020304")]
[InlineData(32, "http://www.example.com", "d82076687474703a2f2f7777772e6578616d706c652e636f6d")]
[InlineData(int.MaxValue, 2, "da7fffffff02")]
[InlineData(ulong.MaxValue, new object[] { 1, 2 }, "dbffffffffffffffff820102")]
public static void ReadTag_SingleValue_HappyPath(ulong expectedTag, object expectedValue, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

Assert.Equal(CborReaderState.Tag, reader.Peek());
CborTag tag = reader.ReadTag();
Assert.Equal(expectedTag, (ulong)tag);

Helpers.VerifyValue(reader, expectedValue);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData(new ulong[] { 1, 2, 3 }, 2, "c1c2c302")]
[InlineData(new ulong[] { 0, 0, 0 }, "2013-03-21T20:04:00Z", "c0c0c074323031332d30332d32315432303a30343a30305a")]
[InlineData(new ulong[] { int.MaxValue, ulong.MaxValue }, 1363896240, "da7fffffffdbffffffffffffffff1a514b67b0")]
[InlineData(new ulong[] { 23, 24, 100 }, new byte[] { 1, 2, 3, 4 }, "d7d818d8644401020304")]
[InlineData(new ulong[] { 32, 1, 1 }, new object[] { 1, "lorem ipsum" }, "d820c1c182016b6c6f72656d20697073756d")]
public static void ReadTag_NestedTags_HappyPath(ulong[] expectedTags, object expectedValue, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

foreach (ulong expectedTag in expectedTags)
{
Assert.Equal(CborReaderState.Tag, reader.Peek());
CborTag tag = reader.ReadTag();
Assert.Equal(expectedTag, (ulong)tag);
}

Helpers.VerifyValue(reader, expectedValue);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
// all possible definite-length encodings for the value 23
[InlineData("17")]
Expand Down Expand Up @@ -156,6 +199,16 @@ public static void ReadUInt64_OutOfRangeValues_ShouldThrowOverflowException(stri
Assert.Throws<OverflowException>(() => reader.ReadUInt64());
}

[Theory]
[InlineData("c2")]
public static void ReadTag_NoSubsequentData_ShouldPeekEndOfData(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
reader.ReadTag();
Assert.Equal(CborReaderState.EndOfData, reader.Peek());
}

[Theory]
[InlineData("40")] // empty text string
[InlineData("60")] // empty byte string
Expand All @@ -173,6 +226,67 @@ public static void ReadInt64_InvalidTypes_ShouldThrowInvalidOperationException(s
Assert.Equal("Data item major type mismatch.", exn.Message);
}

[Theory]
[InlineData("40")] // empty text string
[InlineData("60")] // empty byte string
[InlineData("f6")] // null
[InlineData("80")] // []
[InlineData("a0")] // {}
[InlineData("f97e00")] // NaN
[InlineData("fb3ff199999999999a")] // 1.1
public static void ReadTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
InvalidOperationException exn = Assert.Throws<InvalidOperationException>(() => reader.ReadTag());

Assert.Equal("Data item major type mismatch.", exn.Message);
}

[Fact]
public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatException()
{
byte[] data = "9fc2ff".HexToByteArray();
var reader = new CborReader(data);

reader.ReadStartArray();
reader.ReadTag();
Assert.Equal(CborReaderState.FormatError, reader.Peek());
Assert.Throws<FormatException>(() => reader.ReadEndArray());
}

[Theory]
[InlineData("8201c202")] // definite length array
[InlineData("9f01c202ff")] // equivalent indefinite-length array
public static void ReadTag_CallingEndReadArrayPrematurely_ShouldThrowInvalidOperationException(string hexEncoding)
{
// encoding is valid CBOR, so should not throw FormatException
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);

reader.ReadStartArray();
reader.ReadInt64();
reader.ReadTag();
Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek());
Assert.Throws<InvalidOperationException>(() => reader.ReadEndArray());
}

[Theory]
[InlineData("a102c202")] // definite length map
[InlineData("bf02c202ff")] // equivalent indefinite-length map
public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperationException(string hexEncoding)
{
// encoding is valid CBOR, so should not throw FormatException
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);

reader.ReadStartMap();
reader.ReadInt64();
reader.ReadTag();
Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek());
Assert.Throws<InvalidOperationException>(() => reader.ReadEndArray());
}

[Theory]
[InlineData("40")] // empty byte string
[InlineData("60")] // empty text string
Expand Down
Loading

0 comments on commit c3f7da2

Please sign in to comment.