Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CBOR] Implement tag and special value support for CborWriter and CborReader #34046

Merged
merged 8 commits into from
Mar 30, 2020
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.Single, reader.Peek());
float f = reader.ReadSingle();
Assert.Equal(expected, f);
break;
case double expected:
Assert.Equal(CborReaderState.Double, 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 @@ -173,6 +216,23 @@ 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);
}

[Theory]
[InlineData("40")] // empty byte string
[InlineData("60")] // empty text string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

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

namespace System.Security.Cryptography.Encoding.Tests.Cbor
{
public partial class CborReaderTests
{
// Data points taken from https://tools.ietf.org/html/rfc7049#appendix-A
// Additional pairs generated using http://cbor.me/

[Theory]
[InlineData(100000.0, "fa47c35000")]
[InlineData(3.4028234663852886e+38, "fa7f7fffff")]
[InlineData(float.PositiveInfinity, "fa7f800000")]
[InlineData(float.NegativeInfinity, "faff800000")]
[InlineData(float.NaN, "fa7fc00000")]
internal static void ReadSingle_SingleValue_HappyPath(float expectedResult, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
Assert.Equal(CborReaderState.Single, reader.Peek());
float actualResult = reader.ReadSingle();
Assert.Equal(expectedResult, actualResult);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData(1.1, "fb3ff199999999999a")]
[InlineData(1.0e+300, "fb7e37e43c8800759c")]
[InlineData(-4.1, "fbc010666666666666")]
[InlineData(3.1415926, "fb400921fb4d12d84a")]
[InlineData(double.PositiveInfinity, "fb7ff0000000000000")]
[InlineData(double.NegativeInfinity, "fbfff0000000000000")]
[InlineData(double.NaN, "fb7ff8000000000000")]
internal static void ReadDouble_SingleValue_HappyPath(double expectedResult, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
Assert.Equal(CborReaderState.Double, reader.Peek());
double actualResult = reader.ReadDouble();
Assert.Equal(expectedResult, actualResult);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData(100000.0, "fa47c35000")]
[InlineData(3.4028234663852886e+38, "fa7f7fffff")]
[InlineData(double.PositiveInfinity, "fa7f800000")]
[InlineData(double.NegativeInfinity, "faff800000")]
[InlineData(double.NaN, "fa7fc00000")]
internal static void ReadDouble_SinglePrecisionValue_ShouldCoerceToDouble(double expectedResult, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
Assert.Equal(CborReaderState.Single, reader.Peek());
double actualResult = reader.ReadDouble();
Assert.Equal(expectedResult, actualResult);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Fact]
internal static void ReadNull_SingleValue_HappyPath()
{
byte[] encoding = "f6".HexToByteArray();
var reader = new CborReader(encoding);
Assert.Equal(CborReaderState.Null, reader.Peek());
reader.ReadNull();
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData(false, "f4")]
[InlineData(true, "f5")]
internal static void ReadBoolean_SingleValue_HappyPath(bool expectedResult, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
Assert.Equal(CborReaderState.Boolean, reader.Peek());
bool actualResult = reader.ReadBoolean();
Assert.Equal(expectedResult, actualResult);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData((CborSpecialValue)0, "e0")]
[InlineData(CborSpecialValue.False, "f4")]
[InlineData(CborSpecialValue.True, "f5")]
[InlineData(CborSpecialValue.Null, "f6")]
[InlineData(CborSpecialValue.Undefined, "f7")]
[InlineData((CborSpecialValue)32, "f820")]
[InlineData((CborSpecialValue)255, "f8ff")]
internal static void ReadSpecialValue_SingleValue_HappyPath(CborSpecialValue expectedResult, string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
CborSpecialValue actualResult = reader.ReadSpecialValue();
Assert.Equal(expectedResult, actualResult);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

[Theory]
[InlineData("01")] // integer
[InlineData("40")] // empty text string
[InlineData("60")] // empty byte string
[InlineData("80")] // []
[InlineData("a0")] // {}
[InlineData("c202")] // tagged value
public static void ReadSpecialValue_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
InvalidOperationException exn = Assert.Throws<InvalidOperationException>(() => reader.ReadSpecialValue());

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

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

[Theory]
[InlineData("01")] // integer
[InlineData("40")] // empty text string
[InlineData("60")] // empty byte string
[InlineData("80")] // []
[InlineData("a0")] // {}
[InlineData("f4")] // false
[InlineData("f97e00")] // NaN
[InlineData("fb3ff199999999999a")] // 1.1
[InlineData("c202")] // tagged value
public static void ReadNull_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
Assert.Throws<InvalidOperationException>(() => reader.ReadNull());
}

[Theory]
[InlineData("01")] // integer
[InlineData("40")] // empty text string
[InlineData("60")] // empty byte string
[InlineData("80")] // []
[InlineData("a0")] // {}
[InlineData("f6")] // null
[InlineData("f4")] // false
[InlineData("c202")] // tagged value
public static void ReadSingle_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
Assert.Throws<InvalidOperationException>(() => reader.ReadSingle());
}

[Theory]
[InlineData("01")] // integer
[InlineData("40")] // empty text string
[InlineData("60")] // empty byte string
[InlineData("80")] // []
[InlineData("a0")] // {}
[InlineData("f6")] // null
[InlineData("f4")] // false
[InlineData("c202")] // tagged value
public static void ReadDouble_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
Assert.Throws<InvalidOperationException>(() => reader.ReadDouble());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static void BytesRemaining_SingleRead_ShouldReturnRemainingBytes()
[InlineData(CborMajorType.Array, CborReaderState.StartArray)]
[InlineData(CborMajorType.Map, CborReaderState.StartMap)]
[InlineData(CborMajorType.Tag, CborReaderState.Tag)]
[InlineData(CborMajorType.Special, CborReaderState.Special)]
[InlineData(CborMajorType.Special, CborReaderState.SpecialValue)]
internal static void Peek_SingleByteBuffer_ShouldReturnExpectedState(CborMajorType majorType, CborReaderState expectedResult)
{
ReadOnlyMemory<byte> buffer = new byte[] { (byte)((byte)majorType << 5) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public partial class CborWriterTests
[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 WriteArray_SimpleValues_HappyPath(object[] values, string expectedHexEncoding)
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
Expand Down Expand Up @@ -50,6 +51,7 @@ public static void WriteArray_NestedValues_HappyPath(object[] values, string exp
[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 WriteArray_IndefiniteLength_HappyPath(object[] values, string expectedHexEncoding)
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ public static void WriteValue(CborWriter writer, object value, bool useDefiniteL
{
switch (value)
{
case null: writer.WriteNull(); break;
case bool b: writer.WriteBoolean(b); break;
case int i: writer.WriteInt64(i); break;
case long i: writer.WriteInt64(i); break;
case ulong i: writer.WriteUInt64(i); break;
case float f: writer.WriteSingle(f); break;
case double d: writer.WriteDouble(d); break;
case string s: writer.WriteTextString(s); break;
case byte[] b: writer.WriteByteString(b); break;
case byte[][] chunks: WriteChunkedByteString(writer, chunks); break;
Expand Down
Loading