diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Array.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Array.cs index 5f479b6a085641..c87827f87a7906 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Array.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Array.cs @@ -27,7 +27,7 @@ public static void ReadArray_SimpleValues_HappyPath(object[] expectedValues, str byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyArray(reader, expectedValues); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -39,7 +39,7 @@ public static void ReadArray_NestedValues_HappyPath(object[] expectedValues, str byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyArray(reader, expectedValues); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -55,7 +55,7 @@ public static void ReadArray_IndefiniteLength_HappyPath(object[] expectedValues, byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyArray(reader, expectedValues, expectDefiniteLengthCollections: false); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -75,7 +75,9 @@ public static void ReadArray_DefiniteLengthExceeded_ShouldThrowInvalidOperationE reader.ReadInt64(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -109,12 +111,12 @@ public static void ReadArray_IndefiniteLength_MissingBreakByte_ShouldReportEndOf byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); reader.ReadStartArray(); - while (reader.Peek() == CborReaderState.UnsignedInteger) + while (reader.PeekState() == CborReaderState.UnsignedInteger) { reader.ReadInt64(); } - Assert.Equal(CborReaderState.EndOfData, reader.Peek()); + Assert.Equal(CborReaderState.EndOfData, reader.PeekState()); } [Theory] @@ -131,7 +133,9 @@ public static void ReadArray_IndefiniteLength_PrematureEndArrayCall_ShouldThrowI reader.ReadInt64(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -150,7 +154,9 @@ public static void EndReadArray_DefiniteLengthNotMet_ShouldThrowInvalidOperation reader.ReadInt64(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -172,7 +178,9 @@ public static void EndReadArray_DefiniteLengthNotMet_WithNestedData_ShouldThrowI reader.ReadEndArray(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] @@ -199,7 +207,9 @@ public static void ReadArray_IncorrectDefiniteLength_ShouldThrowFormatException( reader.ReadInt64(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -221,7 +231,9 @@ public static void ReadArray_IncorrectDefiniteLength_NestedValues_ShouldThrowFor reader.ReadEndArray(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] @@ -231,6 +243,7 @@ public static void ReadStartArray_EmptyBuffer_ShouldThrowFormatException() var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadStartArray()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -244,9 +257,11 @@ public static void ReadStartArray_EmptyBuffer_ShouldThrowFormatException() [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadStartArray_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadStartArray()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -261,22 +276,24 @@ public static void ReadStartArray_InvalidType_ShouldThrowInvalidOperationExcepti [InlineData("9b00000000000000")] public static void ReadStartArray_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadStartArray()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] - [InlineData("81")] - [InlineData("830102")] + [InlineData("82")] + [InlineData("870102")] [InlineData("9b7fffffffffffffff")] // long.MaxValue public static void ReadStartArray_BufferTooSmall_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadStartArray()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Helpers.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Helpers.cs index 6c09b4a6c8ce35..27f22c9ce942ca 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Helpers.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Helpers.cs @@ -18,18 +18,18 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp switch (expectedValue) { case null: - Assert.Equal(CborReaderState.Null, reader.Peek()); + Assert.Equal(CborReaderState.Null, reader.PeekState()); reader.ReadNull(); break; case bool expected: - Assert.Equal(CborReaderState.Boolean, reader.Peek()); + Assert.Equal(CborReaderState.Boolean, reader.PeekState()); bool b = reader.ReadBoolean(); Assert.Equal(expected, b); break; case int expected: VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0); - long i = reader.ReadInt64(); - Assert.Equal(expected, (int)i); + int i = reader.ReadInt32(); + Assert.Equal(expected, i); break; case long expected: VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0); @@ -42,47 +42,47 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp Assert.Equal(expected, u); break; case float expected: - Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.PeekState()); float f = reader.ReadSingle(); Assert.Equal(expected, f); break; case double expected: - Assert.Equal(CborReaderState.DoublePrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.DoublePrecisionFloat, reader.PeekState()); double d = reader.ReadDouble(); Assert.Equal(expected, d); break; case string expected: - Assert.Equal(CborReaderState.TextString, reader.Peek()); + Assert.Equal(CborReaderState.TextString, reader.PeekState()); string s = reader.ReadTextString(); Assert.Equal(expected, s); break; case byte[] expected: - Assert.Equal(CborReaderState.ByteString, reader.Peek()); + Assert.Equal(CborReaderState.ByteString, reader.PeekState()); byte[] bytes = reader.ReadByteString(); Assert.Equal(expected.ByteArrayToHex(), bytes.ByteArrayToHex()); break; case string[] expectedChunks: - Assert.Equal(CborReaderState.StartTextString, reader.Peek()); + Assert.Equal(CborReaderState.StartTextString, reader.PeekState()); reader.ReadStartTextStringIndefiniteLength(); foreach(string expectedChunk in expectedChunks) { - Assert.Equal(CborReaderState.TextString, reader.Peek()); + Assert.Equal(CborReaderState.TextString, reader.PeekState()); string chunk = reader.ReadTextString(); Assert.Equal(expectedChunk, chunk); } - Assert.Equal(CborReaderState.EndTextString, reader.Peek()); + Assert.Equal(CborReaderState.EndTextString, reader.PeekState()); reader.ReadEndTextStringIndefiniteLength(); break; case byte[][] expectedChunks: - Assert.Equal(CborReaderState.StartByteString, reader.Peek()); + Assert.Equal(CborReaderState.StartByteString, reader.PeekState()); reader.ReadStartByteStringIndefiniteLength(); foreach (byte[] expectedChunk in expectedChunks) { - Assert.Equal(CborReaderState.ByteString, reader.Peek()); + Assert.Equal(CborReaderState.ByteString, reader.PeekState()); byte[] chunk = reader.ReadByteString(); Assert.Equal(expectedChunk.ByteArrayToHex(), chunk.ByteArrayToHex()); } - Assert.Equal(CborReaderState.EndByteString, reader.Peek()); + Assert.Equal(CborReaderState.EndByteString, reader.PeekState()); reader.ReadEndByteStringIndefiniteLength(); break; @@ -99,13 +99,13 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp static void VerifyPeekInteger(CborReader reader, bool isUnsignedInteger) { CborReaderState expectedState = isUnsignedInteger ? CborReaderState.UnsignedInteger : CborReaderState.NegativeInteger; - Assert.Equal(expectedState, reader.Peek()); + Assert.Equal(expectedState, reader.PeekState()); } } public static void VerifyArray(CborReader reader, object[] expectedValues, bool expectDefiniteLengthCollections = true) { - Assert.Equal(CborReaderState.StartArray, reader.Peek()); + Assert.Equal(CborReaderState.StartArray, reader.PeekState()); ulong? length = reader.ReadStartArray(); @@ -124,7 +124,7 @@ public static void VerifyArray(CborReader reader, object[] expectedValues, bool VerifyValue(reader, value); } - Assert.Equal(CborReaderState.EndArray, reader.Peek()); + Assert.Equal(CborReaderState.EndArray, reader.PeekState()); reader.ReadEndArray(); } @@ -135,7 +135,7 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex throw new ArgumentException($"cbor map expected values missing '{CborWriterTests.Helpers.MapPrefixIdentifier}' prefix."); } - Assert.Equal(CborReaderState.StartMap, reader.Peek()); + Assert.Equal(CborReaderState.StartMap, reader.PeekState()); ulong? length = reader.ReadStartMap(); @@ -154,7 +154,7 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex VerifyValue(reader, value); } - Assert.Equal(CborReaderState.EndMap, reader.Peek()); + Assert.Equal(CborReaderState.EndMap, reader.PeekState()); reader.ReadEndMap(); } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Integer.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Integer.cs index 3542d3412ec00c..613f016dd1b853 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Integer.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Integer.cs @@ -50,7 +50,40 @@ public static void ReadInt64_SingleValue_HappyPath(long expectedResult, string h var reader = new CborReader(data); long actualResult = reader.ReadInt64(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + + [Theory] + [InlineData(0, "00")] + [InlineData(1, "01")] + [InlineData(10, "0a")] + [InlineData(23, "17")] + [InlineData(24, "1818")] + [InlineData(25, "1819")] + [InlineData(100, "1864")] + [InlineData(1000, "1903e8")] + [InlineData(1000000, "1a000f4240")] + [InlineData(-1, "20")] + [InlineData(-10, "29")] + [InlineData(-100, "3863")] + [InlineData(-1000, "3903e7")] + [InlineData(byte.MaxValue, "18ff")] + [InlineData(byte.MaxValue + 1, "190100")] + [InlineData(-1 - byte.MaxValue, "38ff")] + [InlineData(-2 - byte.MaxValue, "390100")] + [InlineData(ushort.MaxValue, "19ffff")] + [InlineData(ushort.MaxValue + 1, "1a00010000")] + [InlineData(-1 - ushort.MaxValue, "39ffff")] + [InlineData(-2 - ushort.MaxValue, "3a00010000")] + [InlineData(int.MaxValue, "1a7fffffff")] + [InlineData(int.MinValue, "3a7fffffff")] + public static void ReadInt32_SingleValue_HappyPath(int expectedResult, string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + long actualResult = reader.ReadInt32(); + Assert.Equal(expectedResult, actualResult); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -78,7 +111,32 @@ public static void ReadUInt64_SingleValue_HappyPath(ulong expectedResult, string var reader = new CborReader(data); ulong actualResult = reader.ReadUInt64(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + + [Theory] + [InlineData(0, "00")] + [InlineData(1, "01")] + [InlineData(10, "0a")] + [InlineData(23, "17")] + [InlineData(24, "1818")] + [InlineData(25, "1819")] + [InlineData(100, "1864")] + [InlineData(1000, "1903e8")] + [InlineData(1000000, "1a000f4240")] + [InlineData(byte.MaxValue, "18ff")] + [InlineData(byte.MaxValue + 1, "190100")] + [InlineData(ushort.MaxValue, "19ffff")] + [InlineData(ushort.MaxValue + 1, "1a00010000")] + [InlineData(int.MaxValue, "1a7fffffff")] + [InlineData(uint.MaxValue, "1affffffff")] + public static void ReadUInt32_SingleValue_HappyPath(uint expectedResult, string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + uint actualResult = reader.ReadUInt32(); + Assert.Equal(expectedResult, actualResult); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -97,50 +155,7 @@ public static void ReadCborNegativeIntegerEncoding_SingleValue_HappyPath(ulong e var reader = new CborReader(data); ulong actualResult = reader.ReadCborNegativeIntegerEncoding(); Assert.Equal(expectedResult, actualResult); - 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()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -156,7 +171,7 @@ public static void ReadUInt64_SingleValue_ShouldSupportNonCanonicalEncodings(str var reader = new CborReader(data); ulong result = reader.ReadUInt64(); Assert.Equal(23ul, result); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -172,7 +187,7 @@ public static void ReadInt64_SingleValue_ShouldSupportNonCanonicalEncodings(stri var reader = new CborReader(data); long result = reader.ReadInt64(); Assert.Equal(-24, result); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } @@ -182,10 +197,38 @@ public static void ReadInt64_SingleValue_ShouldSupportNonCanonicalEncodings(stri [InlineData("1bffffffffffffffff")] // ulong.MaxValue public static void ReadInt64_OutOfRangeValues_ShouldThrowOverflowException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("1a80000000")] // int.MaxValue + 1 + [InlineData("3a80000000")] // int.MinValue - 1 + [InlineData("1b8000000000000000")] // long.MaxValue + 1 + [InlineData("3a8000000000000000")] // long.MinValue - 1 + [InlineData("1bffffffffffffffff")] // ulong.MaxValue + public static void ReadInt32_OutOfRangeValues_ShouldThrowOverflowException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadInt32()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("20")] + [InlineData("1b0000000100000000")] // uint.MaxValue + 1 + public static void ReadUInt32_OutOfRangeValues_ShouldThrowOverflowException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadUInt32()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -194,19 +237,10 @@ public static void ReadInt64_OutOfRangeValues_ShouldThrowOverflowException(strin [InlineData("3b7fffffffffffffff")] // long.MinValue public static void ReadUInt64_OutOfRangeValues_ShouldThrowOverflowException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => 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()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -219,11 +253,13 @@ public static void ReadTag_NoSubsequentData_ShouldPeekEndOfData(string hexEncodi [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadInt64_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadInt64()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + InvalidOperationException exn = Assert.Throws(() => reader.ReadInt64()); Assert.Equal("Data item major type mismatch.", exn.Message); + + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -234,57 +270,34 @@ public static void ReadInt64_InvalidTypes_ShouldThrowInvalidOperationException(s [InlineData("a0")] // {} [InlineData("f97e00")] // NaN [InlineData("fb3ff199999999999a")] // 1.1 - public static void ReadTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) + public static void ReadInt32_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadTag()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + InvalidOperationException exn = Assert.Throws(() => reader.ReadInt32()); 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(() => reader.ReadEndArray()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] - [InlineData("8201c202")] // definite length array - [InlineData("9f01c202ff")] // equivalent indefinite-length array - public static void ReadTag_CallingEndReadArrayPrematurely_ShouldThrowInvalidOperationException(string hexEncoding) + [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 ReadUInt32_InvalidTypes_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(() => reader.ReadEndArray()); - } + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); - [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); + InvalidOperationException exn = Assert.Throws(() => reader.ReadUInt32()); + Assert.Equal("Data item major type mismatch.", exn.Message); - reader.ReadStartMap(); - reader.ReadInt64(); - reader.ReadTag(); - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); - Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -297,12 +310,13 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadUInt64_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); InvalidOperationException exn = Assert.Throws(() => reader.ReadUInt64()); - Assert.Equal("Data item major type mismatch.", exn.Message); + + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -317,12 +331,13 @@ public static void ReadUInt64_InvalidTypes_ShouldThrowInvalidOperationException( [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadCborNegativeIntegerEncoding_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); InvalidOperationException exn = Assert.Throws(() => reader.ReadCborNegativeIntegerEncoding()); - Assert.Equal("Data item major type mismatch.", exn.Message); + + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -344,9 +359,11 @@ public static void ReadCborNegativeIntegerEncoding_InvalidTypes_ShouldThrowInval [InlineData("3b00000000000000")] public static void ReadInt64_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadInt64()); + + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -361,10 +378,11 @@ public static void ReadInt64_InvalidData_ShouldThrowFormatException(string hexEn [InlineData("3b00000000000000")] public static void ReadCborNegativeIntegerEncoding_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadCborNegativeIntegerEncoding()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -372,10 +390,11 @@ public static void ReadCborNegativeIntegerEncoding_InvalidData_ShouldThrowFormat [InlineData("3f")] public static void ReadInt64_IndefiniteLengthIntegers_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] @@ -385,6 +404,7 @@ public static void ReadUInt64_EmptyBuffer_ShouldThrowFormatException() var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadUInt64()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] @@ -394,6 +414,7 @@ public static void ReadCborNegativeIntegerEncoding_EmptyBuffer_ShouldThrowFormat var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadCborNegativeIntegerEncoding()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Map.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Map.cs index 8c90149c5c91fe..586b8e9b197831 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Map.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Map.cs @@ -26,7 +26,7 @@ public static void ReadMap_SimpleValues_HappyPath(object[] expectedValues, strin byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyMap(reader, expectedValues); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -38,7 +38,7 @@ public static void ReadMap_NestedValues_HappyPath(object[] expectedValues, strin byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyMap(reader, expectedValues); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -51,7 +51,7 @@ public static void ReadMap_NestedListValues_HappyPath(object expectedValue, stri byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyValue(reader, expectedValue); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -64,7 +64,7 @@ public static void ReadMap_IndefiniteLength_SimpleValues_HappyPath(object[] exoe byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyMap(reader, exoectedValues, expectDefiniteLengthCollections: false); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } @@ -75,7 +75,7 @@ public static void ReadMap_DuplicateKeys_ShouldSucceed(object[] values, string h byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); Helpers.VerifyMap(reader, values); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -96,7 +96,9 @@ public static void ReadMap_DefiniteLengthExceeded_ShouldThrowInvalidOperationExc reader.ReadInt64(); // value } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -122,7 +124,9 @@ public static void ReadMap_DefiniteLengthExceeded_WithNestedData_ShouldThrowInva reader.ReadEndMap(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -142,7 +146,9 @@ public static void ReadEndMap_DefiniteLengthNotMet_ShouldThrowInvalidOperationEx reader.ReadInt64(); // value } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadEndMap()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -167,7 +173,9 @@ public static void ReadEndMap_DefiniteLengthNotMet_WithNestedData_ShouldThrowInv reader.ReadEndMap(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadEndMap()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -183,7 +191,9 @@ public static void ReadEndMap_ImbalancedCall_ShouldThrowInvalidOperationExceptio reader.ReadStartArray(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadEndMap()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -203,7 +213,9 @@ public static void ReadMap_IncorrectDefiniteLength_ShouldThrowFormatException(st reader.ReadInt64(); // value } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -215,12 +227,12 @@ public static void ReadMap_IndefiniteLength_MissingBreakByte_ShouldReportEndOfDa byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); reader.ReadStartMap(); - while (reader.Peek() == CborReaderState.UnsignedInteger) + while (reader.PeekState() == CborReaderState.UnsignedInteger) { reader.ReadInt64(); } - Assert.Equal(CborReaderState.EndOfData, reader.Peek()); + Assert.Equal(CborReaderState.EndOfData, reader.PeekState()); } [Theory] @@ -237,8 +249,12 @@ public static void ReadMap_IndefiniteLength_PrematureEndArrayCall_ShouldThrowInv reader.ReadInt64(); } - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + int bytesRemaining = reader.BytesRemaining; + + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); Assert.Throws(() => reader.ReadEndMap()); + + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -255,8 +271,12 @@ public static void ReadMap_IndefiniteLength_OddKeyValuePairs_ShouldThrowFormatEx reader.ReadInt64(); } - Assert.Equal(CborReaderState.FormatError, reader.Peek()); // don't want this to fail + int bytesRemaining = reader.BytesRemaining; + + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); // don't want this to fail Assert.Throws(() => reader.ReadEndMap()); + + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -280,7 +300,9 @@ public static void ReadMap_IncorrectDefiniteLength_NestedValues_ShouldThrowForma reader.ReadEndArray(); } + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] @@ -289,7 +311,9 @@ public static void ReadStartMap_EmptyBuffer_ShouldThrowFormatException() byte[] encoding = Array.Empty(); var reader = new CborReader(encoding); + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadStartMap()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -303,9 +327,11 @@ public static void ReadStartMap_EmptyBuffer_ShouldThrowFormatException() [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadStartMap_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadStartMap()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -320,10 +346,11 @@ public static void ReadStartMap_InvalidType_ShouldThrowInvalidOperationException [InlineData("bb00000000000000")] public static void ReadStartMap_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadStartMap()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -332,10 +359,11 @@ public static void ReadStartMap_InvalidData_ShouldThrowFormatException(string he [InlineData("bb7fffffffffffffff")] // long.MaxValue public static void ReadStartMap_BufferTooSmall_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadStartMap()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -343,10 +371,11 @@ public static void ReadStartMap_BufferTooSmall_ShouldThrowFormatException(string [InlineData("bbffffffffffffffff")] // ulong.MaxValue public static void ReadStartMap_LargeFieldCount_ShouldThrowOverflowException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadStartMap()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Special.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Simple.cs similarity index 76% rename from src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Special.cs rename to src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Simple.cs index f33caae979f9d3..eab951ea870f29 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Special.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Simple.cs @@ -24,10 +24,10 @@ internal static void ReadSingle_SingleValue_HappyPath(float expectedResult, stri { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.PeekState()); float actualResult = reader.ReadSingle(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -42,10 +42,10 @@ internal static void ReadDouble_SingleValue_HappyPath(double expectedResult, str { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.DoublePrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.DoublePrecisionFloat, reader.PeekState()); double actualResult = reader.ReadDouble(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -58,10 +58,10 @@ internal static void ReadDouble_SinglePrecisionValue_ShouldCoerceToDouble(double { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.SinglePrecisionFloat, reader.PeekState()); double actualResult = reader.ReadDouble(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -80,10 +80,10 @@ internal static void ReadDouble_HalfPrecisionValue_ShouldCoerceToDouble(double e { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.HalfPrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.HalfPrecisionFloat, reader.PeekState()); double actualResult = reader.ReadDouble(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -102,10 +102,10 @@ internal static void ReadSingle_HalfPrecisionValue_ShouldCoerceToSingle(float ex { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.HalfPrecisionFloat, reader.Peek()); + Assert.Equal(CborReaderState.HalfPrecisionFloat, reader.PeekState()); float actualResult = reader.ReadSingle(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Fact] @@ -113,9 +113,9 @@ internal static void ReadNull_SingleValue_HappyPath() { byte[] encoding = "f6".HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.Null, reader.Peek()); + Assert.Equal(CborReaderState.Null, reader.PeekState()); reader.ReadNull(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -125,27 +125,27 @@ internal static void ReadBoolean_SingleValue_HappyPath(bool expectedResult, stri { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.Boolean, reader.Peek()); + Assert.Equal(CborReaderState.Boolean, reader.PeekState()); bool actualResult = reader.ReadBoolean(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [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) + [InlineData((CborSimpleValue)0, "e0")] + [InlineData(CborSimpleValue.False, "f4")] + [InlineData(CborSimpleValue.True, "f5")] + [InlineData(CborSimpleValue.Null, "f6")] + [InlineData(CborSimpleValue.Undefined, "f7")] + [InlineData((CborSimpleValue)32, "f820")] + [InlineData((CborSimpleValue)255, "f8ff")] + internal static void ReadSimpleValue_SingleValue_HappyPath(CborSimpleValue expectedResult, string hexEncoding) { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - CborSpecialValue actualResult = reader.ReadSpecialValue(); + CborSimpleValue actualResult = reader.ReadSimpleValue(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -155,13 +155,13 @@ internal static void ReadSpecialValue_SingleValue_HappyPath(CborSpecialValue exp [InlineData("80")] // [] [InlineData("a0")] // {} [InlineData("c202")] // tagged value - public static void ReadSpecialValue_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) + public static void ReadSimpleValue_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadSpecialValue()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); - Assert.Equal("Data item major type mismatch.", exn.Message); + Assert.Throws(() => reader.ReadSimpleValue()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -176,9 +176,10 @@ public static void ReadSpecialValue_InvalidTypes_ShouldThrowInvalidOperationExce [InlineData("c202")] // tagged value public static void ReadBoolean_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadBoolean()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -193,9 +194,10 @@ public static void ReadBoolean_InvalidTypes_ShouldThrowInvalidOperationException [InlineData("c202")] // tagged value public static void ReadNull_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadNull()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -209,9 +211,10 @@ public static void ReadNull_InvalidTypes_ShouldThrowInvalidOperationException(st [InlineData("c202")] // tagged value public static void ReadSingle_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadSingle()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -225,9 +228,10 @@ public static void ReadSingle_InvalidTypes_ShouldThrowInvalidOperationException( [InlineData("c202")] // tagged value public static void ReadDouble_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDouble()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.SkipValue.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.SkipValue.cs index 5bd163de8b995d..a2449d691030c3 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.SkipValue.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.SkipValue.cs @@ -22,7 +22,7 @@ public static void SkipValue_RootValue_HappyPath(string hexEncoding) var reader = new CborReader(encoding); reader.SkipValue(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -37,7 +37,7 @@ public static void SkipValue_NestedValue_HappyPath(string hexEncoding) reader.SkipValue(); reader.ReadInt64(); reader.ReadEndArray(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -49,7 +49,7 @@ public static void SkipValue_TaggedValue_HappyPath(string hexEncoding) reader.ReadTag(); reader.SkipValue(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Fact] @@ -59,7 +59,10 @@ public static void SkipValue_NotAtValue_ShouldThrowInvalidOperationException() var reader = new CborReader(encoding); reader.ReadStartArray(); + + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.SkipValue()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -70,6 +73,7 @@ public static void SkipValue_InvalidFormat_ShouldThrowFormatException(string hex var reader = new CborReader(encoding); Assert.Throws(() => reader.SkipValue()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -83,6 +87,40 @@ public static void SkipValue_InvalidUtf8_ShouldThrowFormatException(string hexEn FormatException exn = Assert.Throws(() => reader.SkipValue()); Assert.NotNull(exn.InnerException); Assert.IsType(exn.InnerException); + + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Fact] + public static void SkipValue_NestedFormatException_ShouldPreserveOriginalReaderState() + { + string hexEncoding = "820181bf01ff"; // [1, [ {_ 1 : } ]] + var reader = new CborReader(hexEncoding.HexToByteArray()); + + reader.ReadStartArray(); + reader.ReadInt64(); + + // capture current state + int currentBytesRead = reader.BytesRead; + int currentBytesRemaining = reader.BytesRemaining; + + // make failing call + int bytesRemaining = reader.BytesRemaining; + Assert.Throws(() => reader.SkipValue()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + + // ensure reader state has reverted to original + Assert.Equal(reader.BytesRead, currentBytesRead); + Assert.Equal(reader.BytesRemaining, currentBytesRemaining); + + // ensure we can read every value up to the format error + Assert.Equal(CborReaderState.StartArray, reader.PeekState()); + reader.ReadStartArray(); + Assert.Equal(CborReaderState.StartMap, reader.PeekState()); + reader.ReadStartMap(); + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); + reader.ReadUInt64(); + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); } [Theory] @@ -99,7 +137,7 @@ public static void SkipValue_ExtremelyNestedValues_ShouldNotStackOverflow(int de var reader = new CborReader(encoding); reader.SkipValue(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } public static IEnumerable SkipTestInputs => SampleCborValues.Select(x => new [] { x }); diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.String.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.String.cs index 50ad86d04c78fb..669400b162cea0 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.String.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.String.cs @@ -26,7 +26,7 @@ public static void ReadByteString_SingleValue_HappyPath(string hexExpectedValue, var reader = new CborReader(encoding); byte[] output = reader.ReadByteString(); Assert.Equal(expectedValue, output); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -43,7 +43,7 @@ public static void TryReadByteString_SingleValue_HappyPath(string hexExpectedVal Assert.True(result); Assert.Equal(expectedValue.Length, bytesWritten); Assert.Equal(expectedValue, buffer[..bytesWritten]); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -61,7 +61,7 @@ public static void ReadTextString_SingleValue_HappyPath(string expectedValue, st var reader = new CborReader(data); string actualResult = reader.ReadTextString(); Assert.Equal(expectedValue, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -82,7 +82,7 @@ public static void TryReadTextString_SingleValue_HappyPath(string expectedValue, Assert.True(result); Assert.Equal(expectedValue.Length, charsWritten); Assert.Equal(expectedValue.ToCharArray(), buffer[..charsWritten]); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -107,10 +107,10 @@ public static void ReadByteString_IndefiniteLengthConcatenated_SingleValue_Happy { byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); - Assert.Equal(CborReaderState.StartByteString, reader.Peek()); + Assert.Equal(CborReaderState.StartByteString, reader.PeekState()); byte[] actualValue = reader.ReadByteString(); Assert.Equal(expectedHexValue.ToUpper(), actualValue.ByteArrayToHex()); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -122,7 +122,7 @@ public static void TryReadByteString_IndefiniteLengthConcatenated_SingleValue_Ha { byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); - Assert.Equal(CborReaderState.StartByteString, reader.Peek()); + Assert.Equal(CborReaderState.StartByteString, reader.PeekState()); Span buffer = new byte[32]; bool result = reader.TryReadByteString(buffer, out int bytesWritten); @@ -130,7 +130,7 @@ public static void TryReadByteString_IndefiniteLengthConcatenated_SingleValue_Ha Assert.True(result); Assert.Equal(expectedHexValue.Length / 2, bytesWritten); Assert.Equal(expectedHexValue.ToUpper(), buffer.Slice(0, bytesWritten).ByteArrayToHex()); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Fact] @@ -143,7 +143,7 @@ public static void ReadByteString_IndefiniteLengthConcatenated_NestedValues_Happ Assert.Equal("AB", reader.ReadByteString().ByteArrayToHex()); Assert.Equal("AB", reader.ReadByteString().ByteArrayToHex()); reader.ReadEndArray(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -167,10 +167,10 @@ public static void ReadTextString_IndefiniteLengthConcatenated_SingleValue_Happy { byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); - Assert.Equal(CborReaderState.StartTextString, reader.Peek()); + Assert.Equal(CborReaderState.StartTextString, reader.PeekState()); string actualValue = reader.ReadTextString(); Assert.Equal(expectedValue, actualValue); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Fact] @@ -183,7 +183,7 @@ public static void ReadTextString_IndefiniteLengthConcatenated_NestedValues_Happ Assert.Equal("ab", reader.ReadTextString()); Assert.Equal("ab", reader.ReadTextString()); reader.ReadEndArray(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -195,7 +195,7 @@ public static void TryReadTextString_IndefiniteLengthConcatenated_SingleValue__H { byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); - Assert.Equal(CborReaderState.StartTextString, reader.Peek()); + Assert.Equal(CborReaderState.StartTextString, reader.PeekState()); Span buffer = new char[32]; bool result = reader.TryReadTextString(buffer, out int charsWritten); @@ -203,7 +203,7 @@ public static void TryReadTextString_IndefiniteLengthConcatenated_SingleValue__H Assert.True(result); Assert.Equal(expectedValue.Length, charsWritten); Assert.Equal(expectedValue, new string(buffer.Slice(0, charsWritten))); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -306,10 +306,11 @@ public static void TryReadTextString_IndefiniteLengthConcatenated_BufferTooSmall [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadByteString_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadByteString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -323,10 +324,11 @@ public static void ReadByteString_InvalidType_ShouldThrowInvalidOperationExcepti [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadTextString_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -340,11 +342,12 @@ public static void ReadTextString_InvalidType_ShouldThrowInvalidOperationExcepti [InlineData("fb3ff199999999999a")] // 1.1 public static void TryReadByteString_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); + byte[] encoding = hexEncoding.HexToByteArray(); byte[] buffer = new byte[32]; - var reader = new CborReader(data); + var reader = new CborReader(encoding); Assert.Throws(() => reader.TryReadByteString(buffer, out int _)); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -358,11 +361,12 @@ public static void TryReadByteString_InvalidType_ShouldThrowInvalidOperationExce [InlineData("fb3ff199999999999a")] // 1.1 public static void TryReadTextString_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); + byte[] encoding = hexEncoding.HexToByteArray(); char[] buffer = new char[32]; - var reader = new CborReader(data); + var reader = new CborReader(encoding); Assert.Throws(() => reader.TryReadTextString(buffer, out int _)); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -383,9 +387,10 @@ public static void TryReadTextString_InvalidType_ShouldThrowInvalidOperationExce [InlineData("5a00010000ff")] public static void ReadByteString_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadByteString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -406,9 +411,10 @@ public static void ReadByteString_InvalidData_ShouldThrowFormatException(string [InlineData("7a00010000ff")] public static void ReadTextString_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -429,11 +435,12 @@ public static void ReadTextString_InvalidData_ShouldThrowFormatException(string [InlineData("5a00010000ff")] public static void TryReadByteString_InvalidData_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); + byte[] encoding = hexEncoding.HexToByteArray(); byte[] buffer = new byte[32]; - var reader = new CborReader(data); + var reader = new CborReader(encoding); Assert.Throws(() => reader.TryReadByteString(buffer, out int _)); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -467,9 +474,10 @@ public static void TryReadTextString_InvalidData_ShouldThrowFormatException(stri [InlineData("5bffffffffffffffff")] public static void ReadByteString_StringLengthTooLarge_ShouldThrowOverflowException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadByteString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -478,9 +486,10 @@ public static void ReadByteString_StringLengthTooLarge_ShouldThrowOverflowExcept [InlineData("7bffffffffffffffff")] public static void ReadTextString_StringLengthTooLarge_ShouldThrowOverflowException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -488,11 +497,12 @@ public static void ReadTextString_StringLengthTooLarge_ShouldThrowOverflowExcept [InlineData("62f090")] public static void ReadTextString_InvalidUnicode_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); FormatException exn = Assert.Throws(() => reader.ReadTextString()); Assert.NotNull(exn.InnerException); Assert.IsType(exn.InnerException); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -500,13 +510,14 @@ public static void ReadTextString_InvalidUnicode_ShouldThrowFormatException(stri [InlineData("62f090")] public static void TryReadTextString_InvalidUnicode_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); + byte[] encoding = hexEncoding.HexToByteArray(); char[] buffer = new char[32]; - var reader = new CborReader(data); + var reader = new CborReader(encoding); FormatException exn = Assert.Throws(() => reader.TryReadTextString(buffer, out int _)); Assert.NotNull(exn.InnerException); Assert.IsType(exn.InnerException); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] @@ -516,6 +527,7 @@ public static void ReadTextString_EmptyBuffer_ShouldThrowFormatException() var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] @@ -525,96 +537,109 @@ public static void ReadByteString_EmptyBuffer_ShouldThrowFormatException() var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadByteString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] public static void ReadByteString_IndefiniteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException() { string hexEncoding = "5f4001ff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); reader.ReadStartByteStringIndefiniteLength(); reader.ReadByteString(); - Assert.Equal(CborReaderState.FormatError, reader.Peek()); + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); // throws FormatException even if it's the right major type we're trying to read Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] public static void ReadTextString_IndefiniteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException() { string hexEncoding = "7f6001ff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); reader.ReadStartTextStringIndefiniteLength(); reader.ReadTextString(); - Assert.Equal(CborReaderState.FormatError, reader.Peek()); + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); // throws FormatException even if it's the right major type we're trying to read Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] public static void ReadByteString_IndefiniteLength_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException() { string hexEncoding = "5f5fffff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); reader.ReadStartByteStringIndefiniteLength(); + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadStartByteStringIndefiniteLength()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] public static void ReadByteString_IndefiniteLengthConcatenated_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException() { string hexEncoding = "5f5fffff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadByteString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] public static void ReadTextString_IndefiniteLength_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException() { string hexEncoding = "7f7fffff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); reader.ReadStartTextStringIndefiniteLength(); + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadStartTextStringIndefiniteLength()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Fact] public static void ReadTextString_IndefiniteLengthConcatenated_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException() { string hexEncoding = "7f7fffff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] public static void ReadByteString_IndefiniteLengthConcatenated_ContainingInvalidMajorTypes_ShouldThrowFormatException() { string hexEncoding = "5f4001ff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadByteString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] public static void ReadTextString_IndefiniteLengthConcatenated_ContainingInvalidMajorTypes_ShouldThrowFormatException() { string hexEncoding = "7f6001ff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] @@ -624,9 +649,10 @@ public static void ReadTextString_IndefiniteLengthConcatenated_InvalidUtf8Chunks // which is in violation of the CBOR format. string hexEncoding = "7f62f090628591ff"; - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadTextString()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Tag.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Tag.cs new file mode 100644 index 00000000000000..9d2b0599dc4912 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Tag.cs @@ -0,0 +1,476 @@ +// 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 System.Numerics; +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(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.PeekState()); + CborTag tag = reader.ReadTag(); + Assert.Equal(expectedTag, (ulong)tag); + + Helpers.VerifyValue(reader, expectedValue); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + + [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.PeekState()); + CborTag tag = reader.ReadTag(); + Assert.Equal(expectedTag, (ulong)tag); + } + + Helpers.VerifyValue(reader, expectedValue); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + + [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.PeekState()); + } + + [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[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadTag()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData(2, "c202")] + [InlineData(0, "c074323031332d30332d32315432303a30343a30305a")] + [InlineData(1, "c11a514b67b0")] + [InlineData(23, "d74401020304")] + [InlineData(32, "d82076687474703a2f2f7777772e6578616d706c652e636f6d")] + [InlineData(int.MaxValue, "da7fffffff02")] + [InlineData(ulong.MaxValue, "dbffffffffffffffff820102")] + public static void PeekTag_SingleValue_HappyPath(ulong expectedTag, string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + CborTag tag = reader.PeekTag(); + Assert.Equal(expectedTag, (ulong)tag); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + Assert.Equal(0, reader.BytesRead); + } + + [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 PeekTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.PeekTag()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Fact] + public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatException() + { + byte[] encoding = "9fc2ff".HexToByteArray(); + var reader = new CborReader(encoding); + + reader.ReadStartArray(); + reader.ReadTag(); + + int bytesRemaining = reader.BytesRemaining; + + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); + Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + } + + [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(); + + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); + Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + } + + [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(); + + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); + Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + } + + [Theory] + [InlineData("2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] + [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] + [InlineData("2020-04-09T11:41:19.12-08:00", "c0781c323032302d30342d30395431313a34313a31392e31322d30383a3030")] + [InlineData("2020-04-09T11:41:19.12-08:00", "c07f781c323032302d30342d30395431313a34313a31392e31322d30383a3030ff")] // indefinite-length date string + public static void ReadDateTimeOffset_SingleValue_HappyPath(string expectedValueString, string hexEncoding) + { + DateTimeOffset expectedValue = DateTimeOffset.Parse(expectedValueString); + byte[] data = hexEncoding.HexToByteArray(); + + var reader = new CborReader(data); + + DateTimeOffset result = reader.ReadDateTimeOffset(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedValue.Offset, result.Offset); + } + + [Theory] + [InlineData("c01a514b67b0")] // string datetime tag with unix time payload + public static void ReadDateTimeOffset_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadDateTimeOffset()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("c07330392f30342f323032302031393a35313a3530")] // 0("09/04/2020 19:51:50") + [InlineData("c06e4c617374204368726973746d6173")] // 0("Last Christmas") + public static void ReadDateTimeOffset_InvalidDateString_ShouldThrowFormatException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadDateTimeOffset()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("01")] // numeric value without tag + [InlineData("c301")] // non-datetime tag + public static void ReadDateTimeOffset_InvalidTag_ShouldThrowInvalidOperationxception(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadDateTimeOffset()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("81c07330392f30342f323032302031393a35313a3530")] // [0("09/04/2020 19:51:50")] + [InlineData("81c06e4c617374204368726973746d6173")] // [0("Last Christmas")] + public static void ReadDateTimeOffset_InvalidFormat_ShouldRollbackToInitialState(string hexEncoding) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + + reader.ReadStartArray(); + int bytesRead = reader.BytesRead; + int bytesRemaining = reader.BytesRemaining; + Assert.Throws(() => reader.ReadDateTimeOffset()); + + Assert.Equal(bytesRead, reader.BytesRead); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + Assert.Equal(CborTag.DateTimeString, reader.ReadTag()); + } + + [Theory] + [InlineData("2013-03-21T20:04:00Z", "c11a514b67b0")] + [InlineData("2013-03-21T20:04:00.5Z", "c1fb41d452d9ec200000")] + [InlineData("2020-04-09T13:31:21Z", "c11a5e8f23a9")] + [InlineData("1970-01-01T00:00:00Z", "c100")] + [InlineData("1969-12-31T23:59:59Z", "c120")] + [InlineData("1960-01-01T00:00:00Z", "c13a12cff77f")] + public static void ReadUnixTimeSeconds_SingleValue_HappyPath(string expectedValueString, string hexEncoding) + { + DateTimeOffset expectedValue = DateTimeOffset.Parse(expectedValueString); + byte[] data = hexEncoding.HexToByteArray(); + + var reader = new CborReader(data); + + DateTimeOffset result = reader.ReadUnixTimeSeconds(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + Assert.Equal(expectedValue, result); + Assert.Equal(TimeSpan.Zero, result.Offset); + } + + [Theory] + [InlineData("c174323031332d30332d32315432303a30343a30305a")] // epoch datetime tag with string payload + public static void ReadUnixTimeSeconds_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("c1f97e00")] // 0(NaN) + [InlineData("c1f9fc00")] // 0(-Infinity) + public static void ReadUnixTimeSeconds_InvalidFloatPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("01")] // numeric value without tag + [InlineData("c301")] // non-datetime tag + public static void ReadUnixTimeSeconds_InvalidTag_ShouldThrowInvalidOperationxception(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("81c17330392f30342f323032302031393a35313a3530")] // [1("09/04/2020 19:51:50")] + [InlineData("81c16e4c617374204368726973746d6173")] // [1("Last Christmas")] + public static void ReadUnixTimeSeconds_InvalidFormat_ShouldRollbackToInitialState(string hexEncoding) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + + reader.ReadStartArray(); + int bytesRead = reader.BytesRead; + int bytesRemaining = reader.BytesRemaining; + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + + Assert.Equal(bytesRead, reader.BytesRead); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + Assert.Equal(CborTag.UnixTimeSeconds, reader.ReadTag()); + } + + [Theory] + [InlineData("0", "c240")] + [InlineData("0", "c24100")] + [InlineData("1", "c24101")] + [InlineData("1", "c2420001")] // should recognize leading zeroes in buffer + [InlineData("-1", "c34100")] + [InlineData("255", "c241ff")] + [InlineData("-256", "c341ff")] + [InlineData("256", "c2420100")] + [InlineData("-257", "c3420100")] + [InlineData("9223372036854775807", "c2487fffffffffffffff")] + [InlineData("-9223372036854775808", "c3487fffffffffffffff")] + [InlineData("18446744073709551616", "c249010000000000000000")] + [InlineData("-18446744073709551617", "c349010000000000000000")] + [InlineData("1", "c25f4101ff")] // indefinite-length buffer + public static void ReadBigInteger_SingleValue_HappyPath(string expectedValueString, string hexEncoding) + { + BigInteger expectedValue = BigInteger.Parse(expectedValueString); + byte[] data = hexEncoding.HexToByteArray(); + + var reader = new CborReader(data); + + BigInteger result = reader.ReadBigInteger(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + Assert.Equal(expectedValue, result); + } + + + [Theory] + [InlineData("01")] + [InlineData("c001")] + public static void ReadBigInteger_InvalidCborTag_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadBigInteger()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("c280")] + [InlineData("c301")] + public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Throws(() => reader.ReadBigInteger()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("81c280")] + [InlineData("81c301")] + public static void ReadBigInteger_InvalidTagPayload_ShouldRollbackToInitialState(string hexEncoding) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + + reader.ReadStartArray(); + int bytesRead = reader.BytesRead; + int bytesRemaining = reader.BytesRemaining; + Assert.Throws(() => reader.ReadBigInteger()); + + Assert.Equal(bytesRead, reader.BytesRead); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + } + + [Theory] + [InlineData("0", "c4820000")] + [InlineData("1", "c4820001")] + [InlineData("-1", "c4820020")] + [InlineData("1.1", "c482200b")] + [InlineData("1.000", "c482221903e8")] + [InlineData("273.15", "c48221196ab3")] + [InlineData("79228162514264337593543950335", "c48200c24cffffffffffffffffffffffff")] // decimal.MaxValue + [InlineData("7922816251426433759354395033.5", "c48220c24cffffffffffffffffffffffff")] + [InlineData("-79228162514264337593543950335", "c48200c34cfffffffffffffffffffffffe")] // decimal.MinValue + [InlineData("3.9614081247908796757769715711", "c482381bc24c7fffffff7fffffff7fffffff")] // maximal number of fractional digits + [InlineData("2000000000", "c4820902")] // encoding with positive exponent representation in payload (2 * 10^9) + public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, string hexEncoding) + { + decimal expectedValue = decimal.Parse(expectedStringValue, Globalization.CultureInfo.InvariantCulture); + byte[] data = hexEncoding.HexToByteArray(); + + var reader = new CborReader(data); + + decimal result = reader.ReadDecimal(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + Assert.Equal(expectedValue, result); + } + + [Theory] + [InlineData("c482181d02")] // 2 * 10^29 + [InlineData("c482381c02")] // 2 * 10^-29 + [InlineData("c48201c24cffffffffffffffffffffffff")] // decimal.MaxValue * 10^1 + [InlineData("c48200c24d01000000000000000000000000")] // (decimal.MaxValue + 1) * 10^0 + [InlineData("c48200c34cffffffffffffffffffffffff")] // (decimal.MinValue - 1) * 10^0 + public static void ReadDecimal_LargeValues_ShouldThrowOverflowException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadDecimal()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("c201")] + public static void ReadDecimal_InvalidTag_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadDecimal()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("c401")] // 4(1) + [InlineData("c480")] // 4([]) + [InlineData("c48101")] // 4([1]) + [InlineData("c4820160")] // 4([1, ""]) + [InlineData("c4826001")] // 4(["", 1]) + public static void ReadDecimal_InvalidFormat_ShouldThrowFormatException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadDecimal()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + + [Theory] + [InlineData("81c401")] // 4(1) + [InlineData("81c480")] // [4([])] + [InlineData("81c4826001")] // [4(["", 1])] + // decimal using an invalid biginteger encoding, + // in this case two nested state rollbacks will take place + [InlineData("81c48201c260")] // [4([1, 2("")])] + public static void ReadDecimal_InvalidTagPayload_ShouldRollbackToInitialState(string hexEncoding) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + + reader.ReadStartArray(); + + int bytesRead = reader.BytesRead; + int bytesRemaining = reader.BytesRemaining; + Assert.Throws(() => reader.ReadDecimal()); + + Assert.Equal(bytesRead, reader.BytesRead); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + Assert.Equal(CborTag.DecimalFraction, reader.ReadTag()); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.cs index c7d36376f535ca..2c3fe35fa7dbfa 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.cs @@ -17,7 +17,7 @@ public partial class CborReaderTests public static void Peek_EmptyBuffer_ShouldReturnEof() { var reader = new CborReader(ReadOnlyMemory.Empty); - Assert.Equal(CborReaderState.EndOfData, reader.Peek()); + Assert.Equal(CborReaderState.EndOfData, reader.PeekState()); } [Fact] @@ -59,12 +59,12 @@ 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.SpecialValue)] + [InlineData(CborMajorType.Simple, CborReaderState.SpecialValue)] internal static void Peek_SingleByteBuffer_ShouldReturnExpectedState(CborMajorType majorType, CborReaderState expectedResult) { ReadOnlyMemory buffer = new byte[] { (byte)((byte)majorType << 5) }; var reader = new CborReader(buffer); - Assert.Equal(expectedResult, reader.Peek()); + Assert.Equal(expectedResult, reader.PeekState()); } [Fact] @@ -73,8 +73,11 @@ public static void CborReader_ReadingTwoPrimitiveValues_ShouldThrowInvalidOperat ReadOnlyMemory buffer = new byte[] { 0, 0 }; var reader = new CborReader(buffer); reader.ReadInt64(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + + int bytesRemaining = reader.BytesRemaining; Assert.Throws(() => reader.ReadInt64()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -109,8 +112,8 @@ public static void ReadEncodedValue_InvalidCbor_ShouldThrowFormatException(strin { byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Throws(() => reader.ReadEncodedValue()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } public static IEnumerable EncodedValueInputs => CborReaderTests.SampleCborValues.Select(x => new[] { x }); diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Helpers.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Helpers.cs index c88944e4b27dd8..92ad8907d38271 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Helpers.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Helpers.cs @@ -38,7 +38,7 @@ public static void WriteValue(CborWriter writer, object value, bool useDefiniteL { case null: writer.WriteNull(); break; case bool b: writer.WriteBoolean(b); break; - case int i: writer.WriteInt64(i); break; + case int i: writer.WriteInt32(i); break; case long i: writer.WriteInt64(i); break; case ulong i: writer.WriteUInt64(i); break; case float f: writer.WriteSingle(f); break; diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Integer.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Integer.cs index 1996e9e0adc25b..d7809a794edbd7 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Integer.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Integer.cs @@ -51,6 +51,38 @@ public static void WriteInt64_SingleValue_HappyPath(long input, string hexExpect AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); } + [Theory] + [InlineData(0, "00")] + [InlineData(1, "01")] + [InlineData(10, "0a")] + [InlineData(23, "17")] + [InlineData(24, "1818")] + [InlineData(25, "1819")] + [InlineData(100, "1864")] + [InlineData(1000, "1903e8")] + [InlineData(1000000, "1a000f4240")] + [InlineData(-1, "20")] + [InlineData(-10, "29")] + [InlineData(-100, "3863")] + [InlineData(-1000, "3903e7")] + [InlineData(byte.MaxValue, "18ff")] + [InlineData(byte.MaxValue + 1, "190100")] + [InlineData(-1 - byte.MaxValue, "38ff")] + [InlineData(-2 - byte.MaxValue, "390100")] + [InlineData(ushort.MaxValue, "19ffff")] + [InlineData(ushort.MaxValue + 1, "1a00010000")] + [InlineData(-1 - ushort.MaxValue, "39ffff")] + [InlineData(-2 - ushort.MaxValue, "3a00010000")] + [InlineData(int.MaxValue, "1a7fffffff")] + [InlineData(int.MinValue, "3a7fffffff")] + public static void WriteInt32_SingleValue_HappyPath(int input, string hexExpectedEncoding) + { + byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); + using var writer = new CborWriter(); + writer.WriteInt32(input); + AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); + } + [Theory] [InlineData(0, "00")] [InlineData(1, "01")] @@ -79,66 +111,46 @@ public static void WriteUInt64_SingleValue_HappyPath(ulong input, string hexExpe } [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 WriteTag_SingleValue_HappyPath(ulong tag, object value, string hexExpectedEncoding) + [InlineData(0, "00")] + [InlineData(1, "01")] + [InlineData(10, "0a")] + [InlineData(23, "17")] + [InlineData(24, "1818")] + [InlineData(25, "1819")] + [InlineData(100, "1864")] + [InlineData(1000, "1903e8")] + [InlineData(1000000, "1a000f4240")] + [InlineData(byte.MaxValue, "18ff")] + [InlineData(byte.MaxValue + 1, "190100")] + [InlineData(ushort.MaxValue, "19ffff")] + [InlineData(ushort.MaxValue + 1, "1a00010000")] + [InlineData(int.MaxValue, "1a7fffffff")] + [InlineData(uint.MaxValue, "1affffffff")] + public static void WriteUInt32_SingleValue_HappyPath(uint input, string hexExpectedEncoding) { byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); using var writer = new CborWriter(); - writer.WriteTag((CborTag)tag); - Helpers.WriteValue(writer, value); + writer.WriteUInt32(input); AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); } [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 WriteTag_NestedTags_HappyPath(ulong[] tags, object value, string hexExpectedEncoding) + [InlineData(0, "20")] + [InlineData(9, "29")] + [InlineData(23, "37")] + [InlineData(99, "3863")] + [InlineData(999, "3903e7")] + [InlineData(byte.MaxValue, "38ff")] + [InlineData(ushort.MaxValue, "39ffff")] + [InlineData(uint.MaxValue, "3affffffff")] + [InlineData(ulong.MaxValue, "3bffffffffffffffff")] + public static void WriteCborNegativeIntegerEncoding_SingleValue_HappyPath(ulong input, string hexExpectedEncoding) { byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); using var writer = new CborWriter(); - foreach (var tag in tags) - { - writer.WriteTag((CborTag)tag); - } - Helpers.WriteValue(writer, value); + writer.WriteCborNegativeIntegerEncoding(input); AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); } - - [Theory] - [InlineData(new ulong[] { 2 })] - [InlineData(new ulong[] { 1, 2, 3 })] - public static void WriteTag_NoValue_ShouldThrowInvalidOperationException(ulong[] tags) - { - using var writer = new CborWriter(); - - foreach (ulong tag in tags) - { - writer.WriteTag((CborTag)tag); - } - - InvalidOperationException exn = Assert.Throws(() => writer.ToArray()); - - Assert.Equal("Buffer contains incomplete CBOR document.", exn.Message); - } - - [Fact] - public static void WriteTag_NoValueInNestedContext_ShouldThrowInvalidOperationException() - { - using var writer = new CborWriter(); - - writer.WriteStartArrayIndefiniteLength(); - writer.WriteTag(CborTag.Uri); - Assert.Throws(() => writer.WriteEndArray()); - } } internal static class AssertHelper diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Special.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Simple.cs similarity index 84% rename from src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Special.cs rename to src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Simple.cs index 8b228267cb6c25..015ec42b4c9352 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Special.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Simple.cs @@ -65,18 +65,18 @@ internal static void WriteBoolean_SingleValue_HappyPath(bool input, string hexEx } [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 WriteSpecialValue_SingleValue_HappyPath(CborSpecialValue input, string hexExpectedEncoding) + [InlineData((CborSimpleValue)0, "e0")] + [InlineData(CborSimpleValue.False, "f4")] + [InlineData(CborSimpleValue.True, "f5")] + [InlineData(CborSimpleValue.Null, "f6")] + [InlineData(CborSimpleValue.Undefined, "f7")] + [InlineData((CborSimpleValue)32, "f820")] + [InlineData((CborSimpleValue)255, "f8ff")] + internal static void WriteSimpleValue_SingleValue_HappyPath(CborSimpleValue input, string hexExpectedEncoding) { byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); using var writer = new CborWriter(); - writer.WriteSpecialValue(input); + writer.WriteSimpleValue(input); AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Tag.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Tag.cs new file mode 100644 index 00000000000000..5a48c1b020bbd9 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Tag.cs @@ -0,0 +1,179 @@ +// 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 System.Numerics; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Encoding.Tests.Cbor +{ + public partial class CborWriterTests + { + // Data points taken from https://tools.ietf.org/html/rfc7049#appendix-A + // Additional pairs generated using http://cbor.me/ + + [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 WriteTag_SingleValue_HappyPath(ulong tag, object value, string hexExpectedEncoding) + { + byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); + using var writer = new CborWriter(); + writer.WriteTag((CborTag)tag); + Helpers.WriteValue(writer, value); + AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); + } + + [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 WriteTag_NestedTags_HappyPath(ulong[] tags, object value, string hexExpectedEncoding) + { + byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); + using var writer = new CborWriter(); + foreach (ulong tag in tags) + { + writer.WriteTag((CborTag)tag); + } + Helpers.WriteValue(writer, value); + AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); + } + + [Theory] + [InlineData(new ulong[] { 2 })] + [InlineData(new ulong[] { 1, 2, 3 })] + public static void WriteTag_NoValue_ShouldThrowInvalidOperationException(ulong[] tags) + { + using var writer = new CborWriter(); + + foreach (ulong tag in tags) + { + writer.WriteTag((CborTag)tag); + } + + InvalidOperationException exn = Assert.Throws(() => writer.ToArray()); + + Assert.Equal("Buffer contains incomplete CBOR document.", exn.Message); + } + + [Fact] + public static void WriteTag_NoValueInNestedContext_ShouldThrowInvalidOperationException() + { + using var writer = new CborWriter(); + + writer.WriteStartArrayIndefiniteLength(); + writer.WriteTag(CborTag.Uri); + Assert.Throws(() => writer.WriteEndArray()); + } + + [Theory] + [InlineData("2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] + [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] + [InlineData("2020-04-09T11:41:19.12-08:00", "c0781c323032302d30342d30395431313a34313a31392e31322d30383a3030")] + public static void WriteDateTimeOffset_SingleValue_HappyPath(string valueString, string expectedHexEncoding) + { + DateTimeOffset value = DateTimeOffset.Parse(valueString); + using var writer = new CborWriter(); + writer.WriteDateTimeOffset(value); + + byte[] encoding = writer.ToArray(); + AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); + } + + [Theory] + [InlineData(1363896240, "c11a514b67b0")] + [InlineData(1586439081, "c11a5e8f23a9")] + [InlineData(0, "c100")] + [InlineData(-1, "c120")] + [InlineData(-315619200, "c13a12cff77f")] + public static void WriteUnixTimeSeconds_Long_SingleValue_HappyPath(long value, string expectedHexEncoding) + { + using var writer = new CborWriter(); + writer.WriteUnixTimeSeconds(value); + + byte[] encoding = writer.ToArray(); + AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); + } + + [Theory] + [InlineData(1363896240, "c1fb41d452d9ec000000")] + [InlineData(1586439081, "c1fb41d7a3c8ea400000")] + [InlineData(0, "c1fb0000000000000000")] + [InlineData(-1, "c1fbbff0000000000000")] + [InlineData(-315619200, "c1fbc1b2cff780000000")] + [InlineData(1363896240.5, "c1fb41d452d9ec200000")] + [InlineData(15870467036.15, "c1fb420d8fa0dee13333")] + public static void WriteUnixTimeSeconds_Double_SingleValue_HappyPath(double value, string expectedHexEncoding) + { + using var writer = new CborWriter(); + writer.WriteUnixTimeSeconds(value); + + byte[] encoding = writer.ToArray(); + AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); + } + + [Theory] + [InlineData(double.NaN)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NegativeInfinity)] + public static void WriteUnixTimeSeconds_Double_InvalidInput_ShouldThrowArgumentException(double value) + { + using var writer = new CborWriter(); + Assert.Throws(() => writer.WriteUnixTimeSeconds(value)); + } + + [Theory] + [InlineData("0", "c24100")] + [InlineData("1", "c24101")] + [InlineData("-1", "c34100")] + [InlineData("255", "c241ff")] + [InlineData("-256", "c341ff")] + [InlineData("256", "c2420100")] + [InlineData("-257", "c3420100")] + [InlineData("9223372036854775807", "c2487fffffffffffffff")] + [InlineData("-9223372036854775808", "c3487fffffffffffffff")] + [InlineData("18446744073709551616", "c249010000000000000000")] + [InlineData("-18446744073709551617", "c349010000000000000000")] + public static void WriteInteger_SingleValue_HappyPath(string valueString, string expectedHexEncoding) + { + BigInteger value = BigInteger.Parse(valueString); + + using var writer = new CborWriter(); + writer.WriteBigInteger(value); + + byte[] encoding = writer.ToArray(); + AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); + } + + [Theory] + [InlineData("0", "c4820000")] + [InlineData("1", "c4820001")] + [InlineData("-1", "c4820020")] + [InlineData("1.1", "c482200b")] + [InlineData("1.000", "c482221903e8")] + [InlineData("273.15", "c48221196ab3")] + [InlineData("79228162514264337593543950335", "c48200c24cffffffffffffffffffffffff")] // decimal.MaxValue + [InlineData("7922816251426433759354395033.5", "c48220c24cffffffffffffffffffffffff")] + [InlineData("-79228162514264337593543950335", "c48200c34cfffffffffffffffffffffffe")] // decimal.MinValue + [InlineData("3.9614081247908796757769715711", "c482381bc24c7fffffff7fffffff7fffffff")] // maximal number of fractional digits + public static void WriteDecimal_SingleValue_HappyPath(string stringValue, string expectedHexEncoding) + { + decimal value = decimal.Parse(stringValue, Globalization.CultureInfo.InvariantCulture); + using var writer = new CborWriter(); + writer.WriteDecimal(value); + byte[] encoding = writer.ToArray(); + AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborInitialByte.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborInitialByte.cs index 772f2ee384e223..185097e1a5e643 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborInitialByte.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborInitialByte.cs @@ -15,14 +15,14 @@ internal enum CborMajorType : byte Array = 4, Map = 5, Tag = 6, - Special = 7, + Simple = 7, } internal enum CborAdditionalInfo : byte { - SpecialValueFalse = 20, - SpecialValueTrue = 21, - SpecialValueNull = 22, + SimpleValueFalse = 20, + SimpleValueTrue = 21, + SimpleValueNull = 22, Additional8BitData = 24, Additional16BitData = 25, diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Array.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Array.cs index bfa415a7f38204..adc256f88e4304 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Array.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Array.cs @@ -15,17 +15,15 @@ internal partial class CborReader if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength) { - AdvanceBuffer(1); - AdvanceDataItemCounters(); PushDataItem(CborMajorType.Array, null); + AdvanceBuffer(1); return null; } else { ulong arrayLength = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes); - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); PushDataItem(CborMajorType.Array, arrayLength); + AdvanceBuffer(1 + additionalBytes); return arrayLength; } } @@ -42,11 +40,13 @@ public void ReadEndArray() } PopDataItem(expectedType: CborMajorType.Array); + AdvanceDataItemCounters(); AdvanceBuffer(1); } else { PopDataItem(expectedType: CborMajorType.Array); + AdvanceDataItemCounters(); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Integer.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Integer.cs index d3f3cc91eba501..bc3b21298698fa 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Integer.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Integer.cs @@ -8,17 +8,59 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor { internal partial class CborReader { - // Implements major type 0 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 + // Implements major type 0,1 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 + + public long ReadInt64() + { + long value = PeekSignedInteger(out int additionalBytes); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; + } + public ulong ReadUInt64() + { + ulong value = PeekUnsignedInteger(out int additionalBytes); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; + } + + public int ReadInt32() + { + int value = checked((int)PeekSignedInteger(out int additionalBytes)); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; + } + + public uint ReadUInt32() + { + uint value = checked((uint)PeekUnsignedInteger(out int additionalBytes)); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; + } + + // Returns the next CBOR negative integer encoding according to + // https://tools.ietf.org/html/rfc7049#section-2.1 + public ulong ReadCborNegativeIntegerEncoding() + { + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.NegativeInteger); + ulong value = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; + } + + private ulong PeekUnsignedInteger(out int additionalBytes) { CborInitialByte header = PeekInitialByte(); switch (header.MajorType) { case CborMajorType.UnsignedInteger: - ulong value = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes); - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); + ulong value = ReadUnsignedInteger(_buffer.Span, header, out additionalBytes); return value; case CborMajorType.NegativeInteger: @@ -29,26 +71,19 @@ public ulong ReadUInt64() } } - // Implements major type 0,1 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 - public long ReadInt64() + private long PeekSignedInteger(out int additionalBytes) { - long value; - int additionalBytes; - CborInitialByte header = PeekInitialByte(); + long value; switch (header.MajorType) { case CborMajorType.UnsignedInteger: value = checked((long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); return value; case CborMajorType.NegativeInteger: value = checked(-1 - (long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); return value; default: @@ -56,27 +91,6 @@ public long ReadInt64() } } - public CborTag ReadTag() - { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Tag); - ulong tag = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes); - AdvanceBuffer(1 + additionalBytes); - // NB tag reads do not advance data item counters - _isTagContext = true; - return (CborTag)tag; - } - - // Returns the next CBOR negative integer encoding according to - // https://tools.ietf.org/html/rfc7049#section-2.1 - public ulong ReadCborNegativeIntegerEncoding() - { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.NegativeInteger); - ulong value = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes); - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); - return value; - } - // Unsigned integer decoding https://tools.ietf.org/html/rfc7049#section-2.1 private static ulong ReadUnsignedInteger(ReadOnlySpan buffer, CborInitialByte header, out int additionalBytes) { @@ -107,7 +121,7 @@ private static ulong ReadUnsignedInteger(ReadOnlySpan buffer, CborInitialB return BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(1)); default: - throw new FormatException("initial byte contains invalid integer encoding data"); + throw new FormatException("initial byte contains invalid integer encoding data."); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Map.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Map.cs index 7d18195bb5821c..7f59f0cb9f1a9a 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Map.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Map.cs @@ -15,9 +15,8 @@ internal partial class CborReader if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength) { - AdvanceBuffer(1); - AdvanceDataItemCounters(); PushDataItem(CborMajorType.Map, null); + AdvanceBuffer(1); return null; } else @@ -29,9 +28,8 @@ internal partial class CborReader throw new OverflowException("Read CBOR map field count exceeds supported size."); } - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); PushDataItem(CborMajorType.Map, 2 * mapSize); + AdvanceBuffer(1 + additionalBytes); return mapSize; } } @@ -53,11 +51,13 @@ public void ReadEndMap() } PopDataItem(expectedType: CborMajorType.Map); + AdvanceDataItemCounters(); AdvanceBuffer(1); } else { PopDataItem(expectedType: CborMajorType.Map); + AdvanceDataItemCounters(); } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Special.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Simple.cs similarity index 83% rename from src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Special.cs rename to src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Simple.cs index adbc97752d1a1f..67ebf93aeef5a7 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Special.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Simple.cs @@ -10,7 +10,7 @@ internal partial class CborReader { public float ReadSingle() { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Special); + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Simple); ReadOnlySpan buffer = _buffer.Span; float result; @@ -41,7 +41,7 @@ public float ReadSingle() public double ReadDouble() { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Special); + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Simple); ReadOnlySpan buffer = _buffer.Span; double result; @@ -75,30 +75,27 @@ public double ReadDouble() public bool ReadBoolean() { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Special); + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Simple); - switch (header.AdditionalInfo) + bool result = header.AdditionalInfo switch { - case CborAdditionalInfo.SpecialValueFalse: - AdvanceBuffer(1); - AdvanceDataItemCounters(); - return false; - case CborAdditionalInfo.SpecialValueTrue: - AdvanceBuffer(1); - AdvanceDataItemCounters(); - return true; - default: - throw new InvalidOperationException("CBOR data item does not encode a boolean value."); - } + CborAdditionalInfo.SimpleValueFalse => false, + CborAdditionalInfo.SimpleValueTrue => true, + _ => throw new InvalidOperationException("CBOR data item does not encode a boolean value."), + }; + + AdvanceBuffer(1); + AdvanceDataItemCounters(); + return result; } public void ReadNull() { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Special); + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Simple); switch (header.AdditionalInfo) { - case CborAdditionalInfo.SpecialValueNull: + case CborAdditionalInfo.SimpleValueNull: AdvanceBuffer(1); AdvanceDataItemCounters(); return; @@ -107,30 +104,30 @@ public void ReadNull() } } - public CborSpecialValue ReadSpecialValue() + public CborSimpleValue ReadSimpleValue() { - CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Special); + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Simple); switch (header.AdditionalInfo) { case CborAdditionalInfo info when (byte)info < 24: AdvanceBuffer(1); AdvanceDataItemCounters(); - return (CborSpecialValue)header.AdditionalInfo; + return (CborSimpleValue)header.AdditionalInfo; case CborAdditionalInfo.Additional8BitData: EnsureBuffer(2); byte value = _buffer.Span[1]; if (value < 32) { - throw new FormatException("Two-byte CBOR special value must be between 32 and 255."); + throw new FormatException("Two-byte CBOR simple value must be between 32 and 255."); } AdvanceBuffer(2); AdvanceDataItemCounters(); - return (CborSpecialValue)value; + return (CborSimpleValue)value; default: - throw new InvalidOperationException("CBOR data item does not encode a special value."); + throw new InvalidOperationException("CBOR data item does not encode a simple value."); } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.SkipValue.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.SkipValue.cs index 6cdb8bfc2d47dc..be33ae3eb24169 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.SkipValue.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.SkipValue.cs @@ -8,12 +8,22 @@ internal partial class CborReader { public void SkipValue() { - int depth = 0; + CborReaderCheckpoint checkpoint = CreateCheckpoint(); - do + try { - SkipNextNode(ref depth); - } while (depth > 0); + int depth = 0; + + do + { + SkipNextNode(ref depth); + } while (depth > 0); + } + catch + { + RestoreCheckpoint(checkpoint); + throw; + } } private void SkipNextNode(ref int depth) @@ -21,7 +31,7 @@ private void SkipNextNode(ref int depth) CborReaderState state; // peek, skipping any tags we might encounter - while ((state = Peek()) == CborReaderState.Tag) + while ((state = PeekState()) == CborReaderState.Tag) { ReadTag(); } @@ -97,7 +107,7 @@ private void SkipNextNode(ref int depth) case CborReaderState.Null: case CborReaderState.Boolean: case CborReaderState.SpecialValue: - ReadSpecialValue(); + ReadSimpleValue(); break; case CborReaderState.EndOfData: diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.String.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.String.cs index 6d39c7b0f2c9fe..aa27af83eb16ee 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.String.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.String.cs @@ -125,16 +125,15 @@ public void ReadStartTextStringIndefiniteLength() throw new InvalidOperationException("CBOR text string is not of indefinite length."); } - AdvanceDataItemCounters(); - AdvanceBuffer(1); - PushDataItem(CborMajorType.TextString, expectedNestedItems: null); + AdvanceBuffer(1); } public void ReadEndTextStringIndefiniteLength() { ReadNextIndefiniteLengthBreakByte(); PopDataItem(CborMajorType.TextString); + AdvanceDataItemCounters(); AdvanceBuffer(1); } @@ -147,16 +146,15 @@ public void ReadStartByteStringIndefiniteLength() throw new InvalidOperationException("CBOR text string is not of indefinite length."); } - AdvanceDataItemCounters(); - AdvanceBuffer(1); - PushDataItem(CborMajorType.ByteString, expectedNestedItems: null); + AdvanceBuffer(1); } public void ReadEndByteStringIndefiniteLength() { ReadNextIndefiniteLengthBreakByte(); PopDataItem(CborMajorType.ByteString); + AdvanceDataItemCounters(); AdvanceBuffer(1); } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs new file mode 100644 index 00000000000000..ac8bbe9e77ff13 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -0,0 +1,223 @@ +// 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. + +using System.Globalization; +using System.Numerics; + +namespace System.Security.Cryptography.Encoding.Tests.Cbor +{ + internal partial class CborReader + { + public CborTag ReadTag() + { + CborTag tag = PeekTagCore(out int additionalBytes); + + AdvanceBuffer(1 + additionalBytes); + _isTagContext = true; + return tag; + } + + public void ReadTag(CborTag expectedTag) + { + CborTag tag = PeekTagCore(out int additionalBytes); + + if (expectedTag != tag) + { + throw new InvalidOperationException("CBOR tag mismatch."); + } + + AdvanceBuffer(1 + additionalBytes); + _isTagContext = true; + } + + public CborTag PeekTag() => PeekTagCore(out int _); + + private CborTag PeekTagCore(out int additionalBytes) + { + CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Tag); + return (CborTag)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes); + } + + // Additional tagged type support + + public DateTimeOffset ReadDateTimeOffset() + { + // implements https://tools.ietf.org/html/rfc7049#section-2.4.1 + + CborReaderCheckpoint checkpoint = CreateCheckpoint(); + + try + { + ReadTag(expectedTag: CborTag.DateTimeString); + + switch (PeekState()) + { + case CborReaderState.TextString: + case CborReaderState.StartTextString: + break; + default: + throw new FormatException("String DateTime semantic tag should annotate string value."); + } + + string dateString = ReadTextString(); + + // TODO determine if conformance levels should allow inexact date sting parsing + if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out DateTimeOffset result)) + { + throw new FormatException("DateTime string is not valid RFC3339 format."); + } + + return result; + } + catch + { + RestoreCheckpoint(checkpoint); + throw; + } + } + + public DateTimeOffset ReadUnixTimeSeconds() + { + // implements https://tools.ietf.org/html/rfc7049#section-2.4.1 + + CborReaderCheckpoint checkpoint = CreateCheckpoint(); + + try + { + ReadTag(expectedTag: CborTag.UnixTimeSeconds); + + switch (PeekState()) + { + case CborReaderState.UnsignedInteger: + case CborReaderState.NegativeInteger: + return DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); + + case CborReaderState.HalfPrecisionFloat: + case CborReaderState.SinglePrecisionFloat: + case CborReaderState.DoublePrecisionFloat: + double seconds = ReadDouble(); + + if (double.IsNaN(seconds) || double.IsInfinity(seconds)) + { + throw new FormatException("Unix time representation cannot be infinity or NaN."); + } + + TimeSpan timespan = TimeSpan.FromSeconds(seconds); + return DateTimeOffset.UnixEpoch + timespan; + + default: + throw new FormatException("UnixDateTime tag should annotate a numeric value."); + } + } + catch + { + RestoreCheckpoint(checkpoint); + throw; + } + } + + public BigInteger ReadBigInteger() + { + // implements https://tools.ietf.org/html/rfc7049#section-2.4.2 + + CborReaderCheckpoint checkpoint = CreateCheckpoint(); + + try + { + bool isNegative = ReadTag() switch + { + CborTag.UnsignedBigNum => false, + CborTag.NegativeBigNum => true, + _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), + }; + + switch(PeekState()) + { + case CborReaderState.ByteString: + case CborReaderState.StartByteString: + break; + default: + throw new FormatException("BigNum semantic tag should annotate byte string value."); + } + + byte[] unsignedBigEndianEncoding = ReadByteString(); + BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); + return isNegative ? -1 - unsignedValue : unsignedValue; + } + catch + { + RestoreCheckpoint(checkpoint); + throw; + } + } + + public decimal ReadDecimal() + { + // implements https://tools.ietf.org/html/rfc7049#section-2.4.3 + + CborReaderCheckpoint checkpoint = CreateCheckpoint(); + + try + { + ReadTag(expectedTag: CborTag.DecimalFraction); + + if (PeekState() != CborReaderState.StartArray || ReadStartArray() != 2) + { + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + decimal mantissa; // signed integral component of the decimal value + long exponent; // base-10 exponent + + switch (PeekState()) + { + case CborReaderState.UnsignedInteger: + case CborReaderState.NegativeInteger: + exponent = ReadInt64(); + break; + + default: + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + switch (PeekState()) + { + case CborReaderState.UnsignedInteger: + mantissa = ReadUInt64(); + break; + + case CborReaderState.NegativeInteger: + mantissa = -1m - ReadCborNegativeIntegerEncoding(); + break; + + case CborReaderState.Tag: + switch(PeekTag()) + { + case CborTag.UnsignedBigNum: + case CborTag.NegativeBigNum: + mantissa = (decimal)ReadBigInteger(); + break; + + default: + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + break; + + default: + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + ReadEndArray(); + + return DecimalHelpers.Reconstruct(mantissa, exponent); + } + catch + { + RestoreCheckpoint(checkpoint); + throw; + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs index f55a1d050f673a..e366c891eb615a 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs @@ -5,6 +5,7 @@ #nullable enable using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; namespace System.Security.Cryptography.Encoding.Tests.Cbor @@ -46,8 +47,9 @@ internal partial class CborReader // The root context ony permits one data item to be read. private ulong? _remainingDataItems = 1; private bool _isEvenNumberOfDataItemsRead = true; // required for indefinite-length map writes - private Stack<(CborMajorType type, bool isEvenNumberOfDataItemsWritten, ulong? remainingDataItems)>? _nestedDataItemStack; + private Stack<(CborMajorType type, int bytesRead, bool isEvenNumberOfDataItemsWritten, ulong? remainingDataItems)>? _nestedDataItems; private bool _isTagContext = false; // true if reader is expecting a tagged value + private CborReaderState _cachedState = CborReaderState.Unknown; // caches the current reader state // stores a reusable List allocation for keeping ranges in the buffer private List<(int offset, int length)>? _rangeListAllocation = null; @@ -60,13 +62,23 @@ internal CborReader(ReadOnlyMemory buffer) public int BytesRead => _bytesRead; public int BytesRemaining => _buffer.Length; - public CborReaderState Peek() + public CborReaderState PeekState() + { + if (_cachedState == CborReaderState.Unknown) + { + _cachedState = PeekStateCore(); + } + + return _cachedState; + } + + private CborReaderState PeekStateCore() { if (_remainingDataItems == 0) { - if (_nestedDataItemStack?.Count > 0) + if (_nestedDataItems?.Count > 0) { - return _nestedDataItemStack.Peek().type switch + return _nestedDataItems.Peek().type switch { CborMajorType.Array => CborReaderState.EndArray, CborMajorType.Map => CborReaderState.EndMap, @@ -97,9 +109,9 @@ public CborReaderState Peek() if (_remainingDataItems == null) { // stack guaranteed to be populated since root context cannot be indefinite-length - Debug.Assert(_nestedDataItemStack != null && _nestedDataItemStack.Count > 0); + Debug.Assert(_nestedDataItems != null && _nestedDataItems.Count > 0); - return _nestedDataItemStack.Peek().type switch + return _nestedDataItems.Peek().type switch { CborMajorType.ByteString => CborReaderState.EndByteString, CborMajorType.TextString => CborReaderState.EndTextString, @@ -118,9 +130,9 @@ public CborReaderState Peek() if (_remainingDataItems == null) { // stack guaranteed to be populated since root context cannot be indefinite-length - Debug.Assert(_nestedDataItemStack != null && _nestedDataItemStack.Count > 0); + Debug.Assert(_nestedDataItems != null && _nestedDataItems.Count > 0); - CborMajorType parentType = _nestedDataItemStack.Peek().type; + CborMajorType parentType = _nestedDataItems.Peek().type; switch (parentType) { @@ -147,7 +159,7 @@ public CborReaderState Peek() CborMajorType.Array => CborReaderState.StartArray, CborMajorType.Map => CborReaderState.StartMap, CborMajorType.Tag => CborReaderState.Tag, - CborMajorType.Special => MapSpecialValueTagToReaderState(initialByte.AdditionalInfo), + CborMajorType.Simple => MapSpecialValueTagToReaderState(initialByte.AdditionalInfo), _ => throw new Exception("CborReader internal error. Invalid major type."), }; @@ -157,10 +169,10 @@ static CborReaderState MapSpecialValueTagToReaderState (CborAdditionalInfo value switch (value) { - case CborAdditionalInfo.SpecialValueNull: + case CborAdditionalInfo.SimpleValueNull: return CborReaderState.Null; - case CborAdditionalInfo.SpecialValueFalse: - case CborAdditionalInfo.SpecialValueTrue: + case CborAdditionalInfo.SimpleValueFalse: + case CborAdditionalInfo.SimpleValueTrue: return CborReaderState.Boolean; case CborAdditionalInfo.Additional16BitData: return CborReaderState.HalfPrecisionFloat; @@ -201,11 +213,9 @@ private CborInitialByte PeekInitialByte() var result = new CborInitialByte(_buffer.Span[0]); - // TODO check for tag state - - if (_nestedDataItemStack != null && _nestedDataItemStack.Count > 0) + if (_nestedDataItems != null && _nestedDataItems.Count > 0) { - CborMajorType parentType = _nestedDataItemStack.Peek().type; + CborMajorType parentType = _nestedDataItems.Peek().type; switch (parentType) { @@ -250,25 +260,27 @@ private void ReadNextIndefiniteLengthBreakByte() private void PushDataItem(CborMajorType type, ulong? expectedNestedItems) { + // a conservative check intended to filter out malicious payloads if (expectedNestedItems > (ulong)_buffer.Length) { throw new FormatException("Insufficient buffer size for declared definite length in CBOR data item."); } - _nestedDataItemStack ??= new Stack<(CborMajorType, bool, ulong?)>(); - _nestedDataItemStack.Push((type, _isEvenNumberOfDataItemsRead, _remainingDataItems)); + _nestedDataItems ??= new Stack<(CborMajorType, int, bool, ulong?)>(); + _nestedDataItems.Push((type, _bytesRead, _isEvenNumberOfDataItemsRead, _remainingDataItems)); _remainingDataItems = expectedNestedItems; _isEvenNumberOfDataItemsRead = true; + _isTagContext = false; } private void PopDataItem(CborMajorType expectedType) { - if (_nestedDataItemStack is null || _nestedDataItemStack.Count == 0) + if (_nestedDataItems is null || _nestedDataItems.Count == 0) { throw new InvalidOperationException("No active CBOR nested data item to pop"); } - (CborMajorType actualType, bool isEvenNumberOfDataItemsWritten, ulong? remainingItems) = _nestedDataItemStack.Peek(); + (CborMajorType actualType, int _, bool isEvenNumberOfDataItemsWritten, ulong? remainingItems) = _nestedDataItems.Peek(); if (expectedType != actualType) { @@ -285,9 +297,14 @@ private void PopDataItem(CborMajorType expectedType) throw new FormatException("CBOR tag should be followed by a data item."); } - _nestedDataItemStack.Pop(); + _nestedDataItems.Pop(); _remainingDataItems = remainingItems; _isEvenNumberOfDataItemsRead = isEvenNumberOfDataItemsWritten; + // Popping items from the stack can change the reader state + // without necessarily needing to advance the buffer + // (e.g. we're at the end of a definite-length collection). + // We therefore need to invalidate the cache here. + _cachedState = CborReaderState.Unknown; } private void AdvanceDataItemCounters() @@ -301,6 +318,8 @@ private void AdvanceBuffer(int length) { _buffer = _buffer.Slice(length); _bytesRead += length; + // invalidate the state cache + _cachedState = CborReaderState.Unknown; } private void EnsureBuffer(int length) @@ -336,5 +355,73 @@ private void ReturnRangeList(List<(int offset, int length)> ranges) { _rangeListAllocation = ranges; } + + private CborReaderCheckpoint CreateCheckpoint() + { + return new CborReaderCheckpoint( + buffer: _buffer, + bytesRead: _bytesRead, + depth: _nestedDataItems?.Count ?? 0, + contextOffset: (_nestedDataItems == null || _nestedDataItems.Count == 0) ? 0 : _nestedDataItems.Peek().bytesRead, + remainingDataItems: _remainingDataItems, + isEvenNumberOfDataItemsRead: _isEvenNumberOfDataItemsRead, + isTagContext: _isTagContext); + } + + private void RestoreCheckpoint(CborReaderCheckpoint checkpoint) + { + if (_nestedDataItems != null) + { + int stackOffset = _nestedDataItems.Count - checkpoint.Depth; + + Debug.Assert( + stackOffset >= 0 && + _nestedDataItems.Skip(stackOffset).FirstOrDefault().bytesRead == checkpoint.ContextOffset, + "Attempting to restore checkpoint outside of its original context."); + + // pop any nested data items introduced after the checkpoint + for (int i = 0; i < stackOffset; i++) + { + _nestedDataItems.Pop(); + } + } + + _buffer = checkpoint.Buffer; + _bytesRead = checkpoint.BytesRead; + _remainingDataItems = checkpoint.RemainingDataItems; + _isEvenNumberOfDataItemsRead = checkpoint.IsEvenNumberOfDataItemsRead; + _isTagContext = checkpoint.IsTagContext; + // invalidate the state cache + _cachedState = CborReaderState.Unknown; + } + } + + // Struct containing checkpoint data for rolling back reader state in the event of a failure + // NB checkpoints do not contain stack information, so we can only roll back provided that the + // reader is within the original context in which the checkpoint was created + internal readonly struct CborReaderCheckpoint + { + public CborReaderCheckpoint( + ReadOnlyMemory buffer, int bytesRead, + int depth, int contextOffset, ulong? remainingDataItems, + bool isEvenNumberOfDataItemsRead, bool isTagContext) + { + Buffer = buffer; + BytesRead = bytesRead; + Depth = depth; + ContextOffset = contextOffset; + RemainingDataItems = remainingDataItems; + IsEvenNumberOfDataItemsRead = isEvenNumberOfDataItemsRead; + IsTagContext = isTagContext; + } + + public ReadOnlyMemory Buffer { get; } + public int BytesRead { get; } + public int Depth { get; } + public int ContextOffset { get; } + + public ulong? RemainingDataItems { get; } + public bool IsEvenNumberOfDataItemsRead { get; } + public bool IsTagContext { get; } } } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs index f7d4ca1f270e76..765953056e138d 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs @@ -8,8 +8,8 @@ namespace System.Security.Cryptography.Encoding.Tests internal enum CborTag : ulong { DateTimeString = 0, - EpochDateTime = 1, - PositiveBigNum = 2, + UnixTimeSeconds = 1, + UnsignedBigNum = 2, NegativeBigNum = 3, DecimalFraction = 4, BigFloat = 5, @@ -29,7 +29,7 @@ internal enum CborTag : ulong } // https://tools.ietf.org/html/rfc7049#section-2.3 - internal enum CborSpecialValue : byte + internal enum CborSimpleValue : byte { False = 20, True = 21, diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Integer.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Integer.cs index 6351306183840c..4db3193fe097be 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Integer.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Integer.cs @@ -31,11 +31,15 @@ public void WriteInt64(long value) AdvanceDataItemCounters(); } - public void WriteTag(CborTag tag) + public void WriteInt32(int value) => WriteInt64(value); + public void WriteUInt32(uint value) => WriteUInt64(value); + + // Writes a CBOR negative integer encoding according to + // https://tools.ietf.org/html/rfc7049#section-2.1 + public void WriteCborNegativeIntegerEncoding(ulong value) { - WriteUnsignedInteger(CborMajorType.Tag, (ulong)tag); - // NB tag writes do not advance data item counters - _isTagContext = true; + WriteUnsignedInteger(CborMajorType.NegativeInteger, value); + AdvanceDataItemCounters(); } // Unsigned integer encoding https://tools.ietf.org/html/rfc7049#section-2.1 diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Special.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Simple.cs similarity index 80% rename from src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Special.cs rename to src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Simple.cs index 20184b5b1f6f0f..e3e9467212c92b 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Special.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Simple.cs @@ -13,7 +13,7 @@ internal partial class CborWriter public void WriteSingle(float value) { EnsureWriteCapacity(5); - WriteInitialByte(new CborInitialByte(CborMajorType.Special, CborAdditionalInfo.Additional32BitData)); + WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional32BitData)); BinaryPrimitives.WriteSingleBigEndian(_buffer.AsSpan(_offset), value); _offset += 4; AdvanceDataItemCounters(); @@ -22,7 +22,7 @@ public void WriteSingle(float value) public void WriteDouble(double value) { EnsureWriteCapacity(9); - WriteInitialByte(new CborInitialByte(CborMajorType.Special, CborAdditionalInfo.Additional64BitData)); + WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData)); BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); _offset += 8; AdvanceDataItemCounters(); @@ -30,25 +30,25 @@ public void WriteDouble(double value) public void WriteBoolean(bool value) { - WriteSpecialValue(value ? CborSpecialValue.True : CborSpecialValue.False); + WriteSimpleValue(value ? CborSimpleValue.True : CborSimpleValue.False); } public void WriteNull() { - WriteSpecialValue(CborSpecialValue.Null); + WriteSimpleValue(CborSimpleValue.Null); } - public void WriteSpecialValue(CborSpecialValue value) + public void WriteSimpleValue(CborSimpleValue value) { if ((byte)value < 24) { EnsureWriteCapacity(1); - WriteInitialByte(new CborInitialByte(CborMajorType.Special, (CborAdditionalInfo)value)); + WriteInitialByte(new CborInitialByte(CborMajorType.Simple, (CborAdditionalInfo)value)); } else { EnsureWriteCapacity(2); - WriteInitialByte(new CborInitialByte(CborMajorType.Special, CborAdditionalInfo.Additional8BitData)); + WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional8BitData)); _buffer[_offset++] = (byte)value; } diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs new file mode 100644 index 00000000000000..6ee28a0ff01f49 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -0,0 +1,163 @@ +// 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. + +using System.Diagnostics; +using System.Numerics; + +namespace System.Security.Cryptography.Encoding.Tests.Cbor +{ + internal partial class CborWriter + { + public void WriteTag(CborTag tag) + { + WriteUnsignedInteger(CborMajorType.Tag, (ulong)tag); + _isTagContext = true; + } + + // Additional tagged type support + + internal const string Rfc3339FormatString = "yyyy-MM-ddTHH:mm:ss.FFFFFFFK"; + + public void WriteDateTimeOffset(DateTimeOffset value) + { + string dateString = + value.Offset == TimeSpan.Zero ? + value.UtcDateTime.ToString(Rfc3339FormatString) : // prefer 'Z' over '+00:00' + value.ToString(Rfc3339FormatString); + + WriteTag(CborTag.DateTimeString); + WriteTextString(dateString); + } + + public void WriteUnixTimeSeconds(long seconds) + { + WriteTag(CborTag.UnixTimeSeconds); + WriteInt64(seconds); + } + + public void WriteUnixTimeSeconds(double seconds) + { + if (double.IsInfinity(seconds) || double.IsNaN(seconds)) + { + throw new ArgumentException("Value cannot be infinite or NaN.", nameof(seconds)); + } + + WriteTag(CborTag.UnixTimeSeconds); + WriteDouble(seconds); + } + + public void WriteBigInteger(BigInteger value) + { + bool isUnsigned = value.Sign >= 0; + BigInteger unsignedValue = isUnsigned ? value : -1 - value; + byte[] unsignedBigEndianEncoding = unsignedValue.ToByteArray(isUnsigned: true, isBigEndian: true); + + WriteTag(isUnsigned ? CborTag.UnsignedBigNum : CborTag.NegativeBigNum); + WriteByteString(unsignedBigEndianEncoding); + } + + public void WriteDecimal(decimal value) + { + // implements https://tools.ietf.org/html/rfc7049#section-2.4.3 + DecimalHelpers.Deconstruct(value, out decimal mantissa, out byte scale); + + WriteTag(CborTag.DecimalFraction); + WriteStartArray(2); + WriteInt64(-(long)scale); + + if (-1m - ulong.MinValue <= mantissa && mantissa <= ulong.MaxValue) + { + if (mantissa >= 0m) + { + WriteUInt64((ulong)mantissa); + } + else + { + WriteCborNegativeIntegerEncoding((ulong)(-1m - mantissa)); + } + } + else + { + // the mantissa can also be a BigNum + WriteBigInteger((BigInteger)mantissa); + } + + WriteEndArray(); + } + } + + internal static class DecimalHelpers + { + private const int SignMask = unchecked((int)0x80000000); + private const int ScaleMask = 0x00ff0000; + private const int ScaleShift = 16; + private const int ExponentUpperBound = 28; + + /// deconstructs a decimal value into its signed integral component and negative base-10 exponent + public static void Deconstruct(decimal value, out decimal mantissa, out byte scale) + { + Span buf = stackalloc int[4]; + decimal.GetBits(value, buf); + + int flags = buf[3]; + bool isNegative = (flags & SignMask) == SignMask; + mantissa = new decimal(lo: buf[0], mid: buf[1], hi: buf[2], isNegative: isNegative, scale: 0); + scale = (byte)((flags & ScaleMask) >> ScaleShift); + } + + /// reconstructs a decimal value out of a signed integral component and a negative base-10 exponent + public static decimal Reconstruct(decimal mantissa, byte scale) + { + Span buf = stackalloc int[4]; + decimal.GetBits(mantissa, buf); + + int flags = buf[3]; + bool isNegative = (flags & SignMask) == SignMask; + Debug.Assert((flags & ScaleMask) == 0, "mantissa argument should be integral."); + return new decimal(lo: buf[0], mid: buf[1], hi: buf[2], isNegative: isNegative, scale: scale); + } + + public static decimal Reconstruct(decimal mantissa, long exponent) + { + if (mantissa == 0) + { + return mantissa; + } + else if (exponent > ExponentUpperBound) + { + throw new OverflowException("Value was either too large or too small for a Decimal."); + } + else if (exponent >= 0) + { + // for positive exponents attempt to compute its decimal representation, + // with risk of throwing OverflowException + for (; exponent >= 5; exponent -= 5) + { + mantissa *= 100_000m; + } + + switch (exponent) + { + case 0: return mantissa; + case 1: return mantissa * 10m; + case 2: return mantissa * 100m; + case 3: return mantissa * 1000m; + case 4: return mantissa * 10000m; + default: + Debug.Fail("Unreachable code in decimal exponentiation logic"); + throw new Exception("Unreachable code in decimal exponentiation logic"); + } + } + else if (exponent >= -ExponentUpperBound) + { + // exponent falls within range of decimal normal-form representation + return Reconstruct(mantissa, (byte)(-exponent)); + } + else + { + throw new OverflowException("Value was either too large or too small for a Decimal."); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj b/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj index 5e0e4749d0896f..1a63d7889f1b9b 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj @@ -51,15 +51,18 @@ - + + - + + - + + @@ -69,7 +72,7 @@ - + @@ -82,6 +85,7 @@ +