From 72dd7bfb1ac5006f9bdb195285534409674d11f0 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 9 Apr 2020 16:10:50 +0100 Subject: [PATCH 01/29] [CBOR] Implement support for dateTime types --- .../Cbor.Tests/CborReaderTests.Integer.cs | 114 ------------ .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 172 ++++++++++++++++++ .../Cbor.Tests/CborWriterTests.Integer.cs | 62 ------- .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 104 +++++++++++ .../tests/Cbor/CborReader.Integer.cs | 10 - .../tests/Cbor/CborReader.Tag.cs | 84 +++++++++ .../tests/Cbor/CborTag.cs | 2 +- .../tests/Cbor/CborWriter.Integer.cs | 7 - .../tests/Cbor/CborWriter.Tag.cs | 32 ++++ ...ecurity.Cryptography.Encoding.Tests.csproj | 4 + 10 files changed, 397 insertions(+), 194 deletions(-) create mode 100644 src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Tag.cs create mode 100644 src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Tag.cs create mode 100644 src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs create mode 100644 src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs 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..62a04378d245d6 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 @@ -100,49 +100,6 @@ public static void ReadCborNegativeIntegerEncoding_SingleValue_HappyPath(ulong e Assert.Equal(CborReaderState.Finished, reader.Peek()); } - [Theory] - [InlineData(2, 2, "c202")] - [InlineData(0, "2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] - [InlineData(1, 1363896240, "c11a514b67b0")] - [InlineData(23, new byte[] { 1, 2, 3, 4 }, "d74401020304")] - [InlineData(32, "http://www.example.com", "d82076687474703a2f2f7777772e6578616d706c652e636f6d")] - [InlineData(int.MaxValue, 2, "da7fffffff02")] - [InlineData(ulong.MaxValue, new object[] { 1, 2 }, "dbffffffffffffffff820102")] - public static void ReadTag_SingleValue_HappyPath(ulong expectedTag, object expectedValue, string hexEncoding) - { - byte[] encoding = hexEncoding.HexToByteArray(); - var reader = new CborReader(encoding); - - Assert.Equal(CborReaderState.Tag, reader.Peek()); - CborTag tag = reader.ReadTag(); - Assert.Equal(expectedTag, (ulong)tag); - - Helpers.VerifyValue(reader, expectedValue); - Assert.Equal(CborReaderState.Finished, reader.Peek()); - } - - [Theory] - [InlineData(new ulong[] { 1, 2, 3 }, 2, "c1c2c302")] - [InlineData(new ulong[] { 0, 0, 0 }, "2013-03-21T20:04:00Z", "c0c0c074323031332d30332d32315432303a30343a30305a")] - [InlineData(new ulong[] { int.MaxValue, ulong.MaxValue }, 1363896240, "da7fffffffdbffffffffffffffff1a514b67b0")] - [InlineData(new ulong[] { 23, 24, 100 }, new byte[] { 1, 2, 3, 4 }, "d7d818d8644401020304")] - [InlineData(new ulong[] { 32, 1, 1 }, new object[] { 1, "lorem ipsum" }, "d820c1c182016b6c6f72656d20697073756d")] - public static void ReadTag_NestedTags_HappyPath(ulong[] expectedTags, object expectedValue, string hexEncoding) - { - byte[] encoding = hexEncoding.HexToByteArray(); - var reader = new CborReader(encoding); - - foreach (ulong expectedTag in expectedTags) - { - Assert.Equal(CborReaderState.Tag, reader.Peek()); - CborTag tag = reader.ReadTag(); - Assert.Equal(expectedTag, (ulong)tag); - } - - Helpers.VerifyValue(reader, expectedValue); - Assert.Equal(CborReaderState.Finished, reader.Peek()); - } - [Theory] // all possible definite-length encodings for the value 23 [InlineData("17")] @@ -199,16 +156,6 @@ public static void ReadUInt64_OutOfRangeValues_ShouldThrowOverflowException(stri 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()); - } - [Theory] [InlineData("40")] // empty text string [InlineData("60")] // empty byte string @@ -226,67 +173,6 @@ public static void ReadInt64_InvalidTypes_ShouldThrowInvalidOperationException(s Assert.Equal("Data item major type mismatch.", exn.Message); } - [Theory] - [InlineData("40")] // empty text string - [InlineData("60")] // empty byte string - [InlineData("f6")] // null - [InlineData("80")] // [] - [InlineData("a0")] // {} - [InlineData("f97e00")] // NaN - [InlineData("fb3ff199999999999a")] // 1.1 - public static void ReadTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) - { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadTag()); - - Assert.Equal("Data item major type mismatch.", exn.Message); - } - - [Fact] - public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatException() - { - byte[] data = "9fc2ff".HexToByteArray(); - var reader = new CborReader(data); - - reader.ReadStartArray(); - reader.ReadTag(); - Assert.Equal(CborReaderState.FormatError, reader.Peek()); - Assert.Throws(() => reader.ReadEndArray()); - } - - [Theory] - [InlineData("8201c202")] // definite length array - [InlineData("9f01c202ff")] // equivalent indefinite-length array - public static void ReadTag_CallingEndReadArrayPrematurely_ShouldThrowInvalidOperationException(string hexEncoding) - { - // encoding is valid CBOR, so should not throw FormatException - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - - reader.ReadStartArray(); - reader.ReadInt64(); - reader.ReadTag(); - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); - Assert.Throws(() => reader.ReadEndArray()); - } - - [Theory] - [InlineData("a102c202")] // definite length map - [InlineData("bf02c202ff")] // equivalent indefinite-length map - public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperationException(string hexEncoding) - { - // encoding is valid CBOR, so should not throw FormatException - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - - reader.ReadStartMap(); - reader.ReadInt64(); - reader.ReadTag(); - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); - Assert.Throws(() => reader.ReadEndArray()); - } - [Theory] [InlineData("40")] // empty byte string [InlineData("60")] // empty text string 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..844b36de4078a5 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Tag.cs @@ -0,0 +1,172 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Encoding.Tests.Cbor +{ + public partial class CborReaderTests + { + // Data points taken from https://tools.ietf.org/html/rfc7049#appendix-A + // Additional pairs generated using http://cbor.me/ + + [Theory] + [InlineData(2, 2, "c202")] + [InlineData(0, "2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] + [InlineData(1, 1363896240, "c11a514b67b0")] + [InlineData(23, new byte[] { 1, 2, 3, 4 }, "d74401020304")] + [InlineData(32, "http://www.example.com", "d82076687474703a2f2f7777772e6578616d706c652e636f6d")] + [InlineData(int.MaxValue, 2, "da7fffffff02")] + [InlineData(ulong.MaxValue, new object[] { 1, 2 }, "dbffffffffffffffff820102")] + public static void ReadTag_SingleValue_HappyPath(ulong expectedTag, object expectedValue, string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + Assert.Equal(CborReaderState.Tag, reader.Peek()); + CborTag tag = reader.ReadTag(); + Assert.Equal(expectedTag, (ulong)tag); + + Helpers.VerifyValue(reader, expectedValue); + Assert.Equal(CborReaderState.Finished, reader.Peek()); + } + + [Theory] + [InlineData(new ulong[] { 1, 2, 3 }, 2, "c1c2c302")] + [InlineData(new ulong[] { 0, 0, 0 }, "2013-03-21T20:04:00Z", "c0c0c074323031332d30332d32315432303a30343a30305a")] + [InlineData(new ulong[] { int.MaxValue, ulong.MaxValue }, 1363896240, "da7fffffffdbffffffffffffffff1a514b67b0")] + [InlineData(new ulong[] { 23, 24, 100 }, new byte[] { 1, 2, 3, 4 }, "d7d818d8644401020304")] + [InlineData(new ulong[] { 32, 1, 1 }, new object[] { 1, "lorem ipsum" }, "d820c1c182016b6c6f72656d20697073756d")] + public static void ReadTag_NestedTags_HappyPath(ulong[] expectedTags, object expectedValue, string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + foreach (ulong expectedTag in expectedTags) + { + Assert.Equal(CborReaderState.Tag, reader.Peek()); + CborTag tag = reader.ReadTag(); + Assert.Equal(expectedTag, (ulong)tag); + } + + Helpers.VerifyValue(reader, expectedValue); + Assert.Equal(CborReaderState.Finished, reader.Peek()); + } + + [Theory] + [InlineData("c2")] + public static void ReadTag_NoSubsequentData_ShouldPeekEndOfData(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + reader.ReadTag(); + Assert.Equal(CborReaderState.EndOfData, reader.Peek()); + } + + [Theory] + [InlineData("40")] // empty text string + [InlineData("60")] // empty byte string + [InlineData("f6")] // null + [InlineData("80")] // [] + [InlineData("a0")] // {} + [InlineData("f97e00")] // NaN + [InlineData("fb3ff199999999999a")] // 1.1 + public static void ReadTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + InvalidOperationException exn = Assert.Throws(() => reader.ReadTag()); + + Assert.Equal("Data item major type mismatch.", exn.Message); + } + + [Fact] + public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatException() + { + byte[] data = "9fc2ff".HexToByteArray(); + var reader = new CborReader(data); + + reader.ReadStartArray(); + reader.ReadTag(); + Assert.Equal(CborReaderState.FormatError, reader.Peek()); + Assert.Throws(() => reader.ReadEndArray()); + } + + [Theory] + [InlineData("8201c202")] // definite length array + [InlineData("9f01c202ff")] // equivalent indefinite-length array + public static void ReadTag_CallingEndReadArrayPrematurely_ShouldThrowInvalidOperationException(string hexEncoding) + { + // encoding is valid CBOR, so should not throw FormatException + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + reader.ReadStartArray(); + reader.ReadInt64(); + reader.ReadTag(); + Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + Assert.Throws(() => reader.ReadEndArray()); + } + + [Theory] + [InlineData("a102c202")] // definite length map + [InlineData("bf02c202ff")] // equivalent indefinite-length map + public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperationException(string hexEncoding) + { + // encoding is valid CBOR, so should not throw FormatException + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + reader.ReadStartMap(); + reader.ReadInt64(); + reader.ReadTag(); + Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + Assert.Throws(() => reader.ReadEndArray()); + } + + [Theory] + [InlineData("2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] + [InlineData("2013-03-21T20:04:00Z", "c11a514b67b0")] + [InlineData("2013-03-21T20:04:00.5Z", "c1fb41d452d9ec200000")] + [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] + [InlineData("2020-04-09T13:31:21Z", "c11a5e8f23a9")] + 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.Peek()); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedValue.Offset, result.Offset); + } + + [Theory] + [InlineData("c01a514b67b0")] // string datetime tag with unix time payload + [InlineData("c174323031332d30332d32315432303a30343a30305a")] // epoch datetime tag with string payload + public static void ReadDateTimeOffset_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadDateTimeOffset()); + } + + [Theory] + [InlineData("01")] // numeric value without tag + [InlineData("c301")] // non-datetime tag + public static void ReadDateTimeOffset_InvalidTag_ShouldThrowInvalidOperationxception(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadDateTimeOffset()); + } + } +} 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..c0abd66873e052 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 @@ -77,68 +77,6 @@ public static void WriteUInt64_SingleValue_HappyPath(ulong input, string hexExpe writer.WriteUInt64(input); AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); } - - [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 (var 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()); - } } internal static class AssertHelper 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..0c1b506f3ab212 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Tag.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Encoding.Tests.Cbor +{ + public partial class 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 (var 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")] + 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")] + public static void WriteUnixTimeSeconds_SingleValue_HappyPath(long value, string expectedHexEncoding) + { + using var writer = new CborWriter(); + writer.WriteUnixTimeSeconds(value); + + byte[] encoding = writer.ToArray(); + AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); + } + } +} 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..563be487683945 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 @@ -56,16 +56,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() 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..fbd021ab961d84 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -0,0 +1,84 @@ +// 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.Buffers.Binary; +using System.Globalization; + +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 + + switch (ReadTag()) + { + case CborTag.DateTimeString: + if (Peek() != CborReaderState.TextString) + { + throw new FormatException("String DateTime semantic tag should annotate string value."); + } + + string dateString = ReadTextString(); + return DateTimeOffset.Parse(dateString, formatProvider: null, DateTimeStyles.RoundtripKind); + + case CborTag.DateTimeUnixSeconds: + switch (Peek()) + { + case CborReaderState.UnsignedInteger: + case CborReaderState.NegativeInteger: + return DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); + + case CborReaderState.HalfPrecisionFloat: + case CborReaderState.SinglePrecisionFloat: + case CborReaderState.DoublePrecisionFloat: + // we don't (but probably should) have a float overload for DateTimeOffset.FromUnixTimeSeconds + double seconds = ReadDouble(); + long epochTicks = DateTimeOffset.UnixEpoch.Ticks; + long ticks = checked(epochTicks + (long)(seconds * TimeSpan.TicksPerSecond)); + return new DateTimeOffset(ticks, TimeSpan.Zero); + + default: + throw new FormatException("Epoch DateTime semantic tag should annotate numeric value."); + } + + default: + throw new InvalidOperationException("CBOR tag is not a recognized DateTime value."); + } + } + } +} 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..dc39f7c9d45c93 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Encoding.Tests internal enum CborTag : ulong { DateTimeString = 0, - EpochDateTime = 1, + DateTimeUnixSeconds = 1, PositiveBigNum = 2, NegativeBigNum = 3, DecimalFraction = 4, 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..319ef5a1ef557f 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,13 +31,6 @@ public void WriteInt64(long value) AdvanceDataItemCounters(); } - public void WriteTag(CborTag tag) - { - WriteUnsignedInteger(CborMajorType.Tag, (ulong)tag); - // NB tag writes do not advance data item counters - _isTagContext = true; - } - // Unsigned integer encoding https://tools.ietf.org/html/rfc7049#section-2.1 private void WriteUnsignedInteger(CborMajorType type, ulong 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..1d239d3bd3aee0 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -0,0 +1,32 @@ +// 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.Buffers.Binary; +using Microsoft.Extensions.Logging; + +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 + + public void WriteDateTimeOffset(DateTimeOffset value) + { + WriteTag(CborTag.DateTimeString); + WriteTextString(value.ToString("O")); + } + + public void WriteUnixTimeSeconds(long unixTimeSeconds) + { + WriteTag(CborTag.DateTimeUnixSeconds); + WriteInt64(unixTimeSeconds); + } + } +} 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..026b64d8638275 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 @@ -52,14 +52,17 @@ + + + @@ -82,6 +85,7 @@ + From d02f2a6535492670065e33a3406b29d6c0382b0b Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 9 Apr 2020 17:32:01 +0100 Subject: [PATCH 02/29] fix test errors --- .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 2 +- .../tests/Cbor/CborReader.Tag.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) 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 index 0c1b506f3ab212..a1eb5c446212f3 100644 --- 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 @@ -77,7 +77,7 @@ public static void WriteTag_NoValueInNestedContext_ShouldThrowInvalidOperationEx } [Theory] - [InlineData("2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] + [InlineData("2013-03-21T20:04:00.0000000+00:00", "c07821323031332d30332d32315432303a30343a30302e303030303030302b30303a3030")] [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] public static void WriteDateTimeOffset_SingleValue_HappyPath(string valueString, string expectedHexEncoding) { 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 index fbd021ab961d84..4413aaaf6f909f 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -4,6 +4,7 @@ using System.Buffers.Binary; using System.Globalization; +using System.Numerics; namespace System.Security.Cryptography.Encoding.Tests.Cbor { @@ -45,9 +46,11 @@ public DateTimeOffset ReadDateTimeOffset() { // implements https://tools.ietf.org/html/rfc7049#section-2.4.1 - switch (ReadTag()) + switch (PeekTag()) { case CborTag.DateTimeString: + ReadTag(); + if (Peek() != CborReaderState.TextString) { throw new FormatException("String DateTime semantic tag should annotate string value."); @@ -57,6 +60,8 @@ public DateTimeOffset ReadDateTimeOffset() return DateTimeOffset.Parse(dateString, formatProvider: null, DateTimeStyles.RoundtripKind); case CborTag.DateTimeUnixSeconds: + ReadTag(); + switch (Peek()) { case CborReaderState.UnsignedInteger: From 484feef827485941340dc95018699a8fb0c38b19 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 9 Apr 2020 19:45:34 +0100 Subject: [PATCH 03/29] refine RFC3339 dateTime formatting logic --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 1 + .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 3 ++- .../tests/Cbor/CborReader.Tag.cs | 2 +- .../tests/Cbor/CborWriter.Tag.cs | 9 ++++++++- 4 files changed, 12 insertions(+), 3 deletions(-) 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 index 844b36de4078a5..30eb04f9f2b52b 100644 --- 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 @@ -133,6 +133,7 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat [InlineData("2013-03-21T20:04:00Z", "c11a514b67b0")] [InlineData("2013-03-21T20:04:00.5Z", "c1fb41d452d9ec200000")] [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] + [InlineData("2020-04-09T11:41:19.12-08:00", "c0781c323032302d30342d30395431313a34313a31392e31322d30383a3030")] [InlineData("2020-04-09T13:31:21Z", "c11a5e8f23a9")] public static void ReadDateTimeOffset_SingleValue_HappyPath(string expectedValueString, string hexEncoding) { 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 index a1eb5c446212f3..b41a4549946060 100644 --- 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 @@ -77,8 +77,9 @@ public static void WriteTag_NoValueInNestedContext_ShouldThrowInvalidOperationEx } [Theory] - [InlineData("2013-03-21T20:04:00.0000000+00:00", "c07821323031332d30332d32315432303a30343a30302e303030303030302b30303a3030")] + [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); 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 index 4413aaaf6f909f..9228cb8087c67e 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -57,7 +57,7 @@ public DateTimeOffset ReadDateTimeOffset() } string dateString = ReadTextString(); - return DateTimeOffset.Parse(dateString, formatProvider: null, DateTimeStyles.RoundtripKind); + return DateTimeOffset.ParseExact(dateString, CborWriter.Rfc3339FormatString, null); case CborTag.DateTimeUnixSeconds: ReadTag(); 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 index 1d239d3bd3aee0..3bb5c008ad3829 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -17,10 +17,17 @@ public void WriteTag(CborTag tag) // 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(value.ToString("O")); + WriteTextString(dateString); } public void WriteUnixTimeSeconds(long unixTimeSeconds) From d686a8c9442c8506d362830a7d6a62f402722a78 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 9 Apr 2020 19:58:25 +0100 Subject: [PATCH 04/29] add invalid date string tests --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 11 +++++++++++ .../tests/Cbor/CborReader.Tag.cs | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) 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 index 30eb04f9f2b52b..f9b7461a2b0aef 100644 --- 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 @@ -159,6 +159,17 @@ public static void ReadDateTimeOffset_InvalidTagPayload_ShouldThrowFormatExcepti Assert.Throws(() => reader.ReadDateTimeOffset()); } + [Theory] + [InlineData("c07330392f30342f323032302031393a35313a3530")] // 0("09/04/2020 19:51:50") + [InlineData("c06e4c617374204368726973746d6173")] // 0("Last Christmas") + public static void ReadDateTimeOffset_InvalidDateString_ShouldThrowFormatException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadDateTimeOffset()); + } + [Theory] [InlineData("01")] // numeric value without tag [InlineData("c301")] // non-datetime tag 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 index 9228cb8087c67e..0f26043c2d74f5 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -57,7 +57,13 @@ public DateTimeOffset ReadDateTimeOffset() } string dateString = ReadTextString(); - return DateTimeOffset.ParseExact(dateString, CborWriter.Rfc3339FormatString, null); + + if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out DateTimeOffset result)) + { + throw new FormatException("DateTime string is not valid RFC3339."); + } + + return result; case CborTag.DateTimeUnixSeconds: ReadTag(); From 5251fc6a0ef2325130bdb42b4e6df5f2f4792721 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 9 Apr 2020 22:21:00 +0100 Subject: [PATCH 05/29] Implement BigInteger support --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 48 +++++++++++++++++++ .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 26 +++++++++- .../tests/Cbor/CborReader.Tag.cs | 21 ++++++++ .../tests/Cbor/CborTag.cs | 2 +- .../tests/Cbor/CborWriter.Tag.cs | 17 +++++-- 5 files changed, 108 insertions(+), 6 deletions(-) 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 index f9b7461a2b0aef..f1cddbe32b892d 100644 --- 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 @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Numerics; using Test.Cryptography; using Xunit; @@ -180,5 +181,52 @@ public static void ReadDateTimeOffset_InvalidTag_ShouldThrowInvalidOperationxcep Assert.Throws(() => reader.ReadDateTimeOffset()); } + + [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 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.Peek()); + Assert.Equal(expectedValue, result); + } + + + [Theory] + [InlineData("01")] + [InlineData("c001")] + public static void ReadBigInteger_InvalidCborTag_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadBigInteger()); + } + + [Theory] + [InlineData("c280")] + [InlineData("c301")] + public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadBigInteger()); + } } } 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 index b41a4549946060..faf343d5a218d0 100644 --- 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 @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Numerics; using Test.Cryptography; using Xunit; @@ -41,7 +42,7 @@ public static void WriteTag_NestedTags_HappyPath(ulong[] tags, object value, str { byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray(); using var writer = new CborWriter(); - foreach (var tag in tags) + foreach (ulong tag in tags) { writer.WriteTag((CborTag)tag); } @@ -101,5 +102,28 @@ public static void WriteUnixTimeSeconds_SingleValue_HappyPath(long value, string byte[] encoding = writer.ToArray(); AssertHelper.HexEqual(expectedHexEncoding.HexToByteArray(), encoding); } + + [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); + } } } 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 index 0f26043c2d74f5..4467696a0fb5b2 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -91,5 +91,26 @@ public DateTimeOffset ReadDateTimeOffset() throw new InvalidOperationException("CBOR tag is not a recognized DateTime value."); } } + + public BigInteger ReadBigInteger() + { + bool isUnsigned = PeekTag() switch + { + CborTag.UnsignedBigNum => true, + CborTag.NegativeBigNum => false, + _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), + }; + + ReadTag(); + + if (Peek() != CborReaderState.ByteString) + { + throw new FormatException("BigNum semantic tag should annotate byte string value."); + } + + byte[] unsignedBigEndianEncoding = ReadByteString(); + BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); + return isUnsigned ? unsignedValue : -1 - unsignedValue; + } } } 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 dc39f7c9d45c93..b3314abf7cd0b9 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs @@ -9,7 +9,7 @@ internal enum CborTag : ulong { DateTimeString = 0, DateTimeUnixSeconds = 1, - PositiveBigNum = 2, + UnsignedBigNum = 2, NegativeBigNum = 3, DecimalFraction = 4, BigFloat = 5, 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 index 3bb5c008ad3829..7663901a9ccec1 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -2,8 +2,7 @@ // 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.Buffers.Binary; -using Microsoft.Extensions.Logging; +using System.Numerics; namespace System.Security.Cryptography.Encoding.Tests.Cbor { @@ -30,10 +29,20 @@ public void WriteDateTimeOffset(DateTimeOffset value) WriteTextString(dateString); } - public void WriteUnixTimeSeconds(long unixTimeSeconds) + public void WriteUnixTimeSeconds(long value) { WriteTag(CborTag.DateTimeUnixSeconds); - WriteInt64(unixTimeSeconds); + WriteInt64(value); + } + + 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); } } } From 9943b72a92228e03d48bd8a924f8f18364dfdfda Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 10 Apr 2020 01:35:20 +0100 Subject: [PATCH 06/29] initial decimal support implementation --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 22 +++++++ .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 19 ++++++ .../tests/Cbor/CborReader.Tag.cs | 41 +++++++++++++ .../tests/Cbor/CborWriter.Integer.cs | 8 +++ .../tests/Cbor/CborWriter.Tag.cs | 60 +++++++++++++++++++ 5 files changed, 150 insertions(+) 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 index f1cddbe32b892d..263f31c81eea8b 100644 --- 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 @@ -228,5 +228,27 @@ public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(s Assert.Throws(() => reader.ReadBigInteger()); } + + [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 + public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, string hexEncoding) + { + decimal expectedValue = Decimal.Parse(expectedStringValue); + byte[] data = hexEncoding.HexToByteArray(); + + var reader = new CborReader(data); + + decimal result = reader.ReadDecimal(); + Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(expectedValue, result); + } } } 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 index faf343d5a218d0..ef4df9c0a70e82 100644 --- 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 @@ -125,5 +125,24 @@ public static void WriteInteger_SingleValue_HappyPath(string valueString, string 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 + public static void WriteDecimal_SingleValue_HappyPath(string stringValue, string expectedHexEncoding) + { + decimal value = Decimal.Parse(stringValue); + 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/CborReader.Tag.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs index 4467696a0fb5b2..47baa85d46b0c5 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -112,5 +112,46 @@ public BigInteger ReadBigInteger() BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); return isUnsigned ? unsignedValue : -1 - unsignedValue; } + + public decimal ReadDecimal() + { + ReadTag(expectedTag: CborTag.DecimalFraction); + + if (Peek() != CborReaderState.StartArray || ReadStartArray() != 2) + { + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + long scale = -ReadInt64(); + decimal mantissa; + + if (scale < 0 || scale > 28) + { + // TODO support positive exponents + throw new OverflowException(); + } + + switch (Peek()) + { + case CborReaderState.UnsignedInteger: + mantissa = ReadUInt64(); + break; + + case CborReaderState.NegativeInteger: + mantissa = -1m - ReadCborNegativeIntegerEncoding(); + break; + + case CborReaderState.Tag: + mantissa = (decimal)ReadBigInteger(); + break; + + default: + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + ReadEndArray(); + + return DecimalHelpers.Reconstruct(mantissa, (byte)scale); + } } } 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 319ef5a1ef557f..c9a6ecf60b2c00 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,6 +31,14 @@ public void WriteInt64(long value) AdvanceDataItemCounters(); } + // Writes a CBOR negative integer encoding according to + // https://tools.ietf.org/html/rfc7049#section-2.1 + public void WriteCborNegativeIntegerEncoding(ulong value) + { + WriteUnsignedInteger(CborMajorType.NegativeInteger, value); + AdvanceDataItemCounters(); + } + // Unsigned integer encoding https://tools.ietf.org/html/rfc7049#section-2.1 private void WriteUnsignedInteger(CborMajorType type, ulong 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 index 7663901a9ccec1..695979d6354848 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Numerics; +using System.Threading; namespace System.Security.Cryptography.Encoding.Tests.Cbor { @@ -44,5 +45,64 @@ public void WriteBigInteger(BigInteger value) 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 static readonly ThreadLocal s_decimalBuf = new ThreadLocal(() => new int[4]); + + public static decimal Reconstruct(decimal mantissa, byte scale) + { + int[] buf = s_decimalBuf.Value; + Decimal.GetBits(mantissa, buf); + + int flags = buf[3]; + bool isNegative = (flags & SignMask) == SignMask; + return new decimal(lo: buf[0], mid: buf[1], hi: buf[2], isNegative: isNegative, scale: scale); + } + + public static void Deconstruct(decimal value, out decimal mantissa, out byte scale) + { + int[] buf = s_decimalBuf.Value; + 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); + } } } From 8ad65f210c27cfa0ad7144b71a59e24ca41f4856 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 14 Apr 2020 18:46:21 +0100 Subject: [PATCH 07/29] use stack allocated buffers instead of thread local buffers --- .../tests/Cbor/CborWriter.Tag.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 index 695979d6354848..821a8ace7df71e 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Numerics; -using System.Threading; namespace System.Security.Cryptography.Encoding.Tests.Cbor { @@ -82,12 +81,10 @@ internal static class DecimalHelpers private const int ScaleMask = 0x00ff0000; private const int ScaleShift = 16; - private static readonly ThreadLocal s_decimalBuf = new ThreadLocal(() => new int[4]); - public static decimal Reconstruct(decimal mantissa, byte scale) { - int[] buf = s_decimalBuf.Value; - Decimal.GetBits(mantissa, buf); + Span buf = stackalloc int[4]; + decimal.GetBits(mantissa, buf); int flags = buf[3]; bool isNegative = (flags & SignMask) == SignMask; @@ -96,8 +93,8 @@ public static decimal Reconstruct(decimal mantissa, byte scale) public static void Deconstruct(decimal value, out decimal mantissa, out byte scale) { - int[] buf = s_decimalBuf.Value; - Decimal.GetBits(value, buf); + Span buf = stackalloc int[4]; + decimal.GetBits(value, buf); int flags = buf[3]; bool isNegative = (flags & SignMask) == SignMask; From 2ed35e8f789de42e118d54aa56338ebf975e1992 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 14 Apr 2020 19:18:01 +0100 Subject: [PATCH 08/29] add test cases for negative unix time dates --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 3 +++ .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 3 +++ 2 files changed, 6 insertions(+) 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 index 263f31c81eea8b..f208123f904493 100644 --- 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 @@ -136,6 +136,9 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] [InlineData("2020-04-09T11:41:19.12-08:00", "c0781c323032302d30342d30395431313a34313a31392e31322d30383a3030")] [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 ReadDateTimeOffset_SingleValue_HappyPath(string expectedValueString, string hexEncoding) { DateTimeOffset expectedValue = DateTimeOffset.Parse(expectedValueString); 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 index ef4df9c0a70e82..795d2484c5c5a5 100644 --- 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 @@ -94,6 +94,9 @@ public static void WriteDateTimeOffset_SingleValue_HappyPath(string valueString, [Theory] [InlineData(1363896240, "c11a514b67b0")] [InlineData(1586439081, "c11a5e8f23a9")] + [InlineData(0, "c100")] + [InlineData(-1, "c120")] + [InlineData(-315619200, "c13a12cff77f")] public static void WriteUnixTimeSeconds_SingleValue_HappyPath(long value, string expectedHexEncoding) { using var writer = new CborWriter(); From c9158283b160b02ea1a3d452e3c25969279ce0bd Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 14 Apr 2020 19:50:52 +0100 Subject: [PATCH 09/29] add WriteNegativeIntegerEncoding tests --- .../Cbor.Tests/CborWriterTests.Integer.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 c0abd66873e052..aec4bf1b16730c 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 @@ -77,6 +77,24 @@ public static void WriteUInt64_SingleValue_HappyPath(ulong input, string hexExpe writer.WriteUInt64(input); AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); } + + [Theory] + [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(); + writer.WriteCborNegativeIntegerEncoding(input); + AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); + } } internal static class AssertHelper From 5672f1d3388dcb31b351c8d7b11353bb5336736f Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 14 Apr 2020 21:37:48 +0100 Subject: [PATCH 10/29] implement checkpointing logic and apply to SkipValue() method --- .../Cbor.Tests/CborReaderTests.SkipValue.cs | 30 ++++++++ .../tests/Cbor/CborReader.SkipValue.cs | 17 ++++- .../tests/Cbor/CborReader.cs | 72 +++++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) 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..6a5334e01ac09f 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 @@ -85,6 +85,36 @@ public static void SkipValue_InvalidUtf8_ShouldThrowFormatException(string hexEn Assert.IsType(exn.InnerException); } + [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 + Assert.Throws(() => reader.SkipValue()); + + // 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.Peek()); + reader.ReadStartArray(); + Assert.Equal(CborReaderState.StartMap, reader.Peek()); + reader.ReadStartMap(); + Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + reader.ReadUInt64(); + Assert.Equal(CborReaderState.FormatError, reader.Peek()); + } + [Theory] [InlineData(50_000)] public static void SkipValue_ExtremelyNestedValues_ShouldNotStackOverflow(int depth) 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..ef4995a177d5d9 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,23 @@ internal partial class CborReader { public void SkipValue() { + CreateCheckpoint(); int depth = 0; - do + try { - SkipNextNode(ref depth); - } while (depth > 0); + do + { + SkipNextNode(ref depth); + } while (depth > 0); + } + catch + { + RestoreCheckpoint(); + throw; + } + + ClearCheckpoint(); } private void SkipNextNode(ref int depth) 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..8e080a127ce2e0 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs @@ -52,6 +52,8 @@ internal partial class CborReader // stores a reusable List allocation for keeping ranges in the buffer private List<(int offset, int length)>? _rangeListAllocation = null; + private Checkpoint? _checkpoint = null; + internal CborReader(ReadOnlyMemory buffer) { _buffer = buffer; @@ -268,6 +270,9 @@ private void PopDataItem(CborMajorType expectedType) throw new InvalidOperationException("No active CBOR nested data item to pop"); } + // Checkpoint should never escape its creation scope + Debug.Assert(_checkpoint == null || _checkpoint.Value.Depth <= _nestedDataItemStack.Count); + (CborMajorType actualType, bool isEvenNumberOfDataItemsWritten, ulong? remainingItems) = _nestedDataItemStack.Peek(); if (expectedType != actualType) @@ -336,5 +341,72 @@ private void ReturnRangeList(List<(int offset, int length)> ranges) { _rangeListAllocation = ranges; } + + // 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 + private readonly struct Checkpoint + { + public Checkpoint( + ReadOnlyMemory buffer, + int bytesRead, int depth, ulong? remainingDataItems, + bool isEvenNumberOfDataItemsRead, bool isTagContext) + { + Buffer = buffer; + BytesRead = bytesRead; + Depth = depth; + RemainingDataItems = remainingDataItems; + IsEvenNumberOfDataItemsRead = isEvenNumberOfDataItemsRead; + IsTagContext = isTagContext; + } + + public ReadOnlyMemory Buffer { get; } + public int BytesRead { get; } + public int Depth { get; } + + public ulong? RemainingDataItems { get; } + public bool IsEvenNumberOfDataItemsRead { get; } + public bool IsTagContext { get; } + } + + private void CreateCheckpoint() + { + Debug.Assert(_checkpoint == null); + _checkpoint = new Checkpoint( + buffer: _buffer, + bytesRead: _bytesRead, + depth: _nestedDataItemStack?.Count ?? 0, + remainingDataItems: _remainingDataItems, + isEvenNumberOfDataItemsRead: _isEvenNumberOfDataItemsRead, + isTagContext: _isTagContext); + } + + private void ClearCheckpoint() + { + Debug.Assert(_checkpoint != null); + _checkpoint = null; + } + + private void RestoreCheckpoint() + { + Debug.Assert(_checkpoint != null); + Checkpoint checkpoint = _checkpoint.Value; + + _buffer = checkpoint.Buffer; + _bytesRead = checkpoint.BytesRead; + _remainingDataItems = checkpoint.RemainingDataItems; + _isEvenNumberOfDataItemsRead = checkpoint.IsEvenNumberOfDataItemsRead; + _isTagContext = checkpoint.IsTagContext; + _checkpoint = null; + + if (_nestedDataItemStack != null) + { + // pop any nested data items introduced after the checkpoint + for (int i = 0; i < _nestedDataItemStack.Count - checkpoint.Depth; i++) + { + _nestedDataItemStack.Pop(); + } + } + } } } From e51fdddd848bf06f6e55c6c37b57c097a4a762ce Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 15 Apr 2020 15:19:06 +0100 Subject: [PATCH 11/29] implement checkpointing for tagged type readers --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 2 +- .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 2 +- .../tests/Cbor/CborReader.Tag.cs | 223 +++++++++++------- .../tests/Cbor/CborWriter.Tag.cs | 52 +++- 4 files changed, 183 insertions(+), 96 deletions(-) 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 index f208123f904493..f8baaa831eceab 100644 --- 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 @@ -244,7 +244,7 @@ public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(s [InlineData("-79228162514264337593543950335", "c48200c34cfffffffffffffffffffffffe")] // decimal.MinValue public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, string hexEncoding) { - decimal expectedValue = Decimal.Parse(expectedStringValue); + decimal expectedValue = decimal.Parse(expectedStringValue); byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); 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 index 795d2484c5c5a5..f57696919f4036 100644 --- 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 @@ -141,7 +141,7 @@ public static void WriteInteger_SingleValue_HappyPath(string valueString, string [InlineData("-79228162514264337593543950335", "c48200c34cfffffffffffffffffffffffe")] // decimal.MinValue public static void WriteDecimal_SingleValue_HappyPath(string stringValue, string expectedHexEncoding) { - decimal value = Decimal.Parse(stringValue); + decimal value = decimal.Parse(stringValue); using var writer = new CborWriter(); writer.WriteDecimal(value); byte[] encoding = writer.ToArray(); 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 index 47baa85d46b0c5..215f46e0d0a77f 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers.Binary; +using System.Diagnostics; using System.Globalization; using System.Numerics; @@ -46,112 +47,160 @@ public DateTimeOffset ReadDateTimeOffset() { // implements https://tools.ietf.org/html/rfc7049#section-2.4.1 - switch (PeekTag()) + CreateCheckpoint(); + DateTimeOffset result; + + try + { + switch (ReadTag()) + { + case CborTag.DateTimeString: + if (Peek() != CborReaderState.TextString) + { + throw new FormatException("String DateTime semantic tag should annotate string value."); + } + + string dateString = ReadTextString(); + + if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out result)) + { + throw new FormatException("DateTime string is not valid RFC3339."); + } + + ClearCheckpoint(); + return result; + + case CborTag.DateTimeUnixSeconds: + switch (Peek()) + { + case CborReaderState.UnsignedInteger: + case CborReaderState.NegativeInteger: + result = DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); + ClearCheckpoint(); + return result; + + case CborReaderState.HalfPrecisionFloat: + case CborReaderState.SinglePrecisionFloat: + case CborReaderState.DoublePrecisionFloat: + // we don't (but probably should) have a float overload for DateTimeOffset.FromUnixTimeSeconds + double seconds = ReadDouble(); + long epochTicks = DateTimeOffset.UnixEpoch.Ticks; + long ticks = checked(epochTicks + (long)(seconds * TimeSpan.TicksPerSecond)); + result = new DateTimeOffset(ticks, TimeSpan.Zero); + ClearCheckpoint(); + return result; + + default: + throw new FormatException("Epoch DateTime semantic tag should annotate numeric value."); + } + + default: + throw new InvalidOperationException("CBOR tag is not a recognized DateTime value."); + } + } + catch { - case CborTag.DateTimeString: - ReadTag(); - - if (Peek() != CborReaderState.TextString) - { - throw new FormatException("String DateTime semantic tag should annotate string value."); - } - - string dateString = ReadTextString(); - - if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out DateTimeOffset result)) - { - throw new FormatException("DateTime string is not valid RFC3339."); - } - - return result; - - case CborTag.DateTimeUnixSeconds: - ReadTag(); - - switch (Peek()) - { - case CborReaderState.UnsignedInteger: - case CborReaderState.NegativeInteger: - return DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); - - case CborReaderState.HalfPrecisionFloat: - case CborReaderState.SinglePrecisionFloat: - case CborReaderState.DoublePrecisionFloat: - // we don't (but probably should) have a float overload for DateTimeOffset.FromUnixTimeSeconds - double seconds = ReadDouble(); - long epochTicks = DateTimeOffset.UnixEpoch.Ticks; - long ticks = checked(epochTicks + (long)(seconds * TimeSpan.TicksPerSecond)); - return new DateTimeOffset(ticks, TimeSpan.Zero); - - default: - throw new FormatException("Epoch DateTime semantic tag should annotate numeric value."); - } - - default: - throw new InvalidOperationException("CBOR tag is not a recognized DateTime value."); + RestoreCheckpoint(); + throw; } } public BigInteger ReadBigInteger() { - bool isUnsigned = PeekTag() switch - { - CborTag.UnsignedBigNum => true, - CborTag.NegativeBigNum => false, - _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), - }; - - ReadTag(); + CreateCheckpoint(); - if (Peek() != CborReaderState.ByteString) + try { - throw new FormatException("BigNum semantic tag should annotate byte string value."); + bool isUnsigned = ReadTag() switch + { + CborTag.UnsignedBigNum => true, + CborTag.NegativeBigNum => false, + _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), + }; + + if (Peek() != CborReaderState.ByteString) + { + throw new FormatException("BigNum semantic tag should annotate byte string value."); + } + + byte[] unsignedBigEndianEncoding = ReadByteString(); + BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); + ClearCheckpoint(); + return isUnsigned ? unsignedValue : -1 - unsignedValue; + } + catch + { + RestoreCheckpoint(); + throw; } - - byte[] unsignedBigEndianEncoding = ReadByteString(); - BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); - return isUnsigned ? unsignedValue : -1 - unsignedValue; } public decimal ReadDecimal() { - ReadTag(expectedTag: CborTag.DecimalFraction); + CreateCheckpoint(); - if (Peek() != CborReaderState.StartArray || ReadStartArray() != 2) + try { - throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); - } - - long scale = -ReadInt64(); - decimal mantissa; + ReadTag(expectedTag: CborTag.DecimalFraction); - if (scale < 0 || scale > 28) - { - // TODO support positive exponents - throw new OverflowException(); + if (Peek() != 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 (Peek()) + { + case CborReaderState.UnsignedInteger: + case CborReaderState.NegativeInteger: + exponent = ReadInt64(); + break; + + default: + throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + } + + switch (Peek()) + { + 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(); + + decimal result = DecimalHelpers.Reconstruct(mantissa, exponent); + ClearCheckpoint(); + return result; } - - switch (Peek()) + catch { - case CborReaderState.UnsignedInteger: - mantissa = ReadUInt64(); - break; - - case CborReaderState.NegativeInteger: - mantissa = -1m - ReadCborNegativeIntegerEncoding(); - break; - - case CborReaderState.Tag: - mantissa = (decimal)ReadBigInteger(); - break; - - default: - throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); + RestoreCheckpoint(); + throw; } - - ReadEndArray(); - - return DecimalHelpers.Reconstruct(mantissa, (byte)scale); } } } 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 index 821a8ace7df71e..b6c047e8431e61 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -2,6 +2,7 @@ // 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 @@ -81,25 +82,62 @@ internal static class DecimalHelpers private const int ScaleMask = 0x00ff0000; private const int ScaleShift = 16; - public static decimal Reconstruct(decimal mantissa, byte scale) + /// 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(mantissa, buf); + decimal.GetBits(value, buf); int flags = buf[3]; bool isNegative = (flags & SignMask) == SignMask; - return new decimal(lo: buf[0], mid: buf[1], hi: buf[2], isNegative: isNegative, scale: scale); + mantissa = new decimal(lo: buf[0], mid: buf[1], hi: buf[2], isNegative: isNegative, scale: 0); + scale = (byte)((flags & ScaleMask) >> ScaleShift); } - public static void Deconstruct(decimal value, out decimal mantissa, out byte scale) + /// 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(value, buf); + decimal.GetBits(mantissa, 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); + 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 (exponent >= 0) + { + // for positive exponents attempt to compute its decimal value, + // 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 >= -28) + { + // 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."); + } } } } From 0d7105b259c6a06a7a467c8d654524a38af633f5 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 15 Apr 2020 16:39:11 +0100 Subject: [PATCH 12/29] store reader checkpoints in the stack --- .../tests/Cbor/CborReader.SkipValue.cs | 9 +- .../tests/Cbor/CborReader.Tag.cs | 35 ++--- .../tests/Cbor/CborReader.cs | 131 +++++++++--------- 3 files changed, 80 insertions(+), 95 deletions(-) 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 ef4995a177d5d9..c70da952f76f44 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,11 +8,12 @@ internal partial class CborReader { public void SkipValue() { - CreateCheckpoint(); - int depth = 0; + CborReaderCheckpoint checkpoint = CreateCheckpoint(); try { + int depth = 0; + do { SkipNextNode(ref depth); @@ -20,11 +21,9 @@ public void SkipValue() } catch { - RestoreCheckpoint(); + RestoreCheckpoint(checkpoint); throw; } - - ClearCheckpoint(); } private void SkipNextNode(ref int depth) 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 index 215f46e0d0a77f..9f2816174f4389 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -2,8 +2,6 @@ // 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.Buffers.Binary; -using System.Diagnostics; using System.Globalization; using System.Numerics; @@ -47,8 +45,7 @@ public DateTimeOffset ReadDateTimeOffset() { // implements https://tools.ietf.org/html/rfc7049#section-2.4.1 - CreateCheckpoint(); - DateTimeOffset result; + CborReaderCheckpoint checkpoint = CreateCheckpoint(); try { @@ -62,12 +59,11 @@ public DateTimeOffset ReadDateTimeOffset() string dateString = ReadTextString(); - if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out result)) + if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out DateTimeOffset result)) { throw new FormatException("DateTime string is not valid RFC3339."); } - ClearCheckpoint(); return result; case CborTag.DateTimeUnixSeconds: @@ -75,9 +71,7 @@ public DateTimeOffset ReadDateTimeOffset() { case CborReaderState.UnsignedInteger: case CborReaderState.NegativeInteger: - result = DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); - ClearCheckpoint(); - return result; + return DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); case CborReaderState.HalfPrecisionFloat: case CborReaderState.SinglePrecisionFloat: @@ -86,9 +80,7 @@ public DateTimeOffset ReadDateTimeOffset() double seconds = ReadDouble(); long epochTicks = DateTimeOffset.UnixEpoch.Ticks; long ticks = checked(epochTicks + (long)(seconds * TimeSpan.TicksPerSecond)); - result = new DateTimeOffset(ticks, TimeSpan.Zero); - ClearCheckpoint(); - return result; + return new DateTimeOffset(ticks, TimeSpan.Zero); default: throw new FormatException("Epoch DateTime semantic tag should annotate numeric value."); @@ -100,14 +92,16 @@ public DateTimeOffset ReadDateTimeOffset() } catch { - RestoreCheckpoint(); + RestoreCheckpoint(checkpoint); throw; } } public BigInteger ReadBigInteger() { - CreateCheckpoint(); + // implements https://tools.ietf.org/html/rfc7049#section-2.4.2 + + CborReaderCheckpoint checkpoint = CreateCheckpoint(); try { @@ -125,19 +119,20 @@ public BigInteger ReadBigInteger() byte[] unsignedBigEndianEncoding = ReadByteString(); BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); - ClearCheckpoint(); return isUnsigned ? unsignedValue : -1 - unsignedValue; } catch { - RestoreCheckpoint(); + RestoreCheckpoint(checkpoint); throw; } } public decimal ReadDecimal() { - CreateCheckpoint(); + // implements https://tools.ietf.org/html/rfc7049#section-2.4.3 + + CborReaderCheckpoint checkpoint = CreateCheckpoint(); try { @@ -192,13 +187,11 @@ public decimal ReadDecimal() ReadEndArray(); - decimal result = DecimalHelpers.Reconstruct(mantissa, exponent); - ClearCheckpoint(); - return result; + return DecimalHelpers.Reconstruct(mantissa, exponent); } catch { - RestoreCheckpoint(); + 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 8e080a127ce2e0..631f70ed0a0078 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,14 +47,12 @@ 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 // stores a reusable List allocation for keeping ranges in the buffer private List<(int offset, int length)>? _rangeListAllocation = null; - private Checkpoint? _checkpoint = null; - internal CborReader(ReadOnlyMemory buffer) { _buffer = buffer; @@ -66,9 +65,9 @@ public CborReaderState Peek() { 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, @@ -99,9 +98,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, @@ -120,9 +119,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) { @@ -203,11 +202,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) { @@ -257,23 +254,20 @@ private void PushDataItem(CborMajorType type, ulong? expectedNestedItems) 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; } 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"); } - // Checkpoint should never escape its creation scope - Debug.Assert(_checkpoint == null || _checkpoint.Value.Depth <= _nestedDataItemStack.Count); - - (CborMajorType actualType, bool isEvenNumberOfDataItemsWritten, ulong? remainingItems) = _nestedDataItemStack.Peek(); + (CborMajorType actualType, int _, bool isEvenNumberOfDataItemsWritten, ulong? remainingItems) = _nestedDataItems.Peek(); if (expectedType != actualType) { @@ -290,7 +284,7 @@ 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; } @@ -342,71 +336,70 @@ private void ReturnRangeList(List<(int offset, int length)> ranges) _rangeListAllocation = ranges; } - // 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 - private readonly struct Checkpoint + private CborReaderCheckpoint CreateCheckpoint() { - public Checkpoint( - ReadOnlyMemory buffer, - int bytesRead, int depth, ulong? remainingDataItems, - bool isEvenNumberOfDataItemsRead, bool isTagContext) - { - Buffer = buffer; - BytesRead = bytesRead; - Depth = depth; - RemainingDataItems = remainingDataItems; - IsEvenNumberOfDataItemsRead = isEvenNumberOfDataItemsRead; - IsTagContext = isTagContext; - } - - public ReadOnlyMemory Buffer { get; } - public int BytesRead { get; } - public int Depth { get; } - - public ulong? RemainingDataItems { get; } - public bool IsEvenNumberOfDataItemsRead { get; } - public bool IsTagContext { get; } - } - - private void CreateCheckpoint() - { - Debug.Assert(_checkpoint == null); - _checkpoint = new Checkpoint( + return new CborReaderCheckpoint( buffer: _buffer, bytesRead: _bytesRead, - depth: _nestedDataItemStack?.Count ?? 0, + depth: _nestedDataItems?.Count ?? 0, + contextOffset: (_nestedDataItems == null || _nestedDataItems.Count == 0) ? 0 : _nestedDataItems.Peek().bytesRead, remainingDataItems: _remainingDataItems, isEvenNumberOfDataItemsRead: _isEvenNumberOfDataItemsRead, isTagContext: _isTagContext); } - private void ClearCheckpoint() + private void RestoreCheckpoint(CborReaderCheckpoint checkpoint) { - Debug.Assert(_checkpoint != null); - _checkpoint = null; - } + if (_nestedDataItems != null) + { + int stackOffset = _nestedDataItems.Count - checkpoint.Depth; - private void RestoreCheckpoint() - { - Debug.Assert(_checkpoint != null); - Checkpoint checkpoint = _checkpoint.Value; + 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; - _checkpoint = null; + } + } - if (_nestedDataItemStack != null) - { - // pop any nested data items introduced after the checkpoint - for (int i = 0; i < _nestedDataItemStack.Count - checkpoint.Depth; i++) - { - _nestedDataItemStack.Pop(); - } - } + // 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; } } } From d1a582c5beb863534298a8cbebff7f49d6568baa Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 15 Apr 2020 17:44:09 +0100 Subject: [PATCH 13/29] add tests for rollback logic in tagged type readers --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 89 +++++++++++++++++++ .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 1 + .../tests/Cbor/CborReader.Tag.cs | 8 +- .../tests/Cbor/CborWriter.Tag.cs | 15 +++- 4 files changed, 106 insertions(+), 7 deletions(-) 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 index f8baaa831eceab..dd4a902c0291df 100644 --- 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 @@ -185,6 +185,24 @@ public static void ReadDateTimeOffset_InvalidTag_ShouldThrowInvalidOperationxcep Assert.Throws(() => reader.ReadDateTimeOffset()); } + [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.Peek()); + Assert.Equal(CborTag.DateTimeString, reader.ReadTag()); + } + [Theory] [InlineData("0", "c24100")] [InlineData("1", "c24101")] @@ -232,6 +250,23 @@ public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(s Assert.Throws(() => reader.ReadBigInteger()); } + [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.Peek()); + } + [Theory] [InlineData("0", "c4820000")] [InlineData("1", "c4820001")] @@ -242,6 +277,8 @@ public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(s [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); @@ -253,5 +290,57 @@ public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, Assert.Equal(CborReaderState.Finished, reader.Peek()); 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) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + Assert.Throws(() => reader.ReadDecimal()); + } + + [Theory] + [InlineData("c201")] + public static void ReadDecimal_InvalidTag_ShouldThrowInvalidOperationException(string hexEncoding) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + Assert.Throws(() => reader.ReadDecimal()); + } + + [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) + { + var reader = new CborReader(hexEncoding.HexToByteArray()); + Assert.Throws(() => reader.ReadDecimal()); + } + + [Theory] + [InlineData("81c401")] // 4(1) + [InlineData("81c480")] // [4([])] + [InlineData("81c4826001")] // [4(["", 1])] + 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.Peek()); + Assert.Equal(CborTag.DecimalFraction, reader.ReadTag()); + } } } 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 index f57696919f4036..45a9ae449b8dde 100644 --- 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 @@ -139,6 +139,7 @@ public static void WriteInteger_SingleValue_HappyPath(string valueString, string [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); 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 index 9f2816174f4389..494868ec0968fa 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -105,10 +105,10 @@ public BigInteger ReadBigInteger() try { - bool isUnsigned = ReadTag() switch + bool isNegative = ReadTag() switch { - CborTag.UnsignedBigNum => true, - CborTag.NegativeBigNum => false, + CborTag.UnsignedBigNum => false, + CborTag.NegativeBigNum => true, _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), }; @@ -119,7 +119,7 @@ public BigInteger ReadBigInteger() byte[] unsignedBigEndianEncoding = ReadByteString(); BigInteger unsignedValue = new BigInteger(unsignedBigEndianEncoding, isUnsigned: true, isBigEndian: true); - return isUnsigned ? unsignedValue : -1 - unsignedValue; + return isNegative ? -1 - unsignedValue : unsignedValue; } catch { 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 index b6c047e8431e61..ca4b287c35026f 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -81,6 +81,7 @@ 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) @@ -108,9 +109,17 @@ public static decimal Reconstruct(decimal mantissa, byte scale) public static decimal Reconstruct(decimal mantissa, long exponent) { - if (exponent >= 0) + if (mantissa == 0) { - // for positive exponents attempt to compute its decimal value, + 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) { @@ -129,7 +138,7 @@ public static decimal Reconstruct(decimal mantissa, long exponent) throw new Exception("Unreachable code in decimal exponentiation logic"); } } - else if (exponent >= -28) + else if (exponent >= -ExponentUpperBound) { // exponent falls within range of decimal normal-form representation return Reconstruct(mantissa, (byte)(-exponent)); From 0c13c32070dab5d37a7146f248710badc9a1f96e Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 15 Apr 2020 17:55:02 +0100 Subject: [PATCH 14/29] add tested that utilizes nested reader state rollbacks --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 3 +++ 1 file changed, 3 insertions(+) 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 index dd4a902c0291df..fabd5d5c8de31d 100644 --- 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 @@ -327,6 +327,9 @@ public static void ReadDecimal_InvalidFormat_ShouldThrowFormatException(string h [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()); From 261ff0be3683b67977df8d7372a5bcd4e37f1d19 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 16 Apr 2020 14:42:49 +0100 Subject: [PATCH 15/29] use culture invariant decimal parsing in tests --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 2 +- .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index fabd5d5c8de31d..8cd2d9e4299939 100644 --- 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 @@ -281,7 +281,7 @@ public static void ReadBigInteger_InvalidTagPayload_ShouldRollbackToInitialState [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); + decimal expectedValue = decimal.Parse(expectedStringValue, Globalization.CultureInfo.InvariantCulture); byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); 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 index 45a9ae449b8dde..93cdb5c04de3d7 100644 --- 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 @@ -142,7 +142,7 @@ public static void WriteInteger_SingleValue_HappyPath(string valueString, string [InlineData("3.9614081247908796757769715711", "c482381bc24c7fffffff7fffffff7fffffff")] // maximal number of fractional digits public static void WriteDecimal_SingleValue_HappyPath(string stringValue, string expectedHexEncoding) { - decimal value = decimal.Parse(stringValue); + decimal value = decimal.Parse(stringValue, Globalization.CultureInfo.InvariantCulture); using var writer = new CborWriter(); writer.WriteDecimal(value); byte[] encoding = writer.ToArray(); From 134224b85be554a9f62c3e8f155c419698b7fae0 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 16 Apr 2020 15:41:06 +0100 Subject: [PATCH 16/29] add WriteUnixTimeSeconds(double) method --- .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 19 ++++++++++++++++++- .../tests/Cbor/CborWriter.Tag.cs | 15 +++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) 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 index 93cdb5c04de3d7..577c4c114cf220 100644 --- 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 @@ -97,7 +97,24 @@ public static void WriteDateTimeOffset_SingleValue_HappyPath(string valueString, [InlineData(0, "c100")] [InlineData(-1, "c120")] [InlineData(-315619200, "c13a12cff77f")] - public static void WriteUnixTimeSeconds_SingleValue_HappyPath(long value, string expectedHexEncoding) + 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); 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 index ca4b287c35026f..1edf0632dc5633 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -30,10 +30,21 @@ public void WriteDateTimeOffset(DateTimeOffset value) WriteTextString(dateString); } - public void WriteUnixTimeSeconds(long value) + public void WriteUnixTimeSeconds(long seconds) { WriteTag(CborTag.DateTimeUnixSeconds); - WriteInt64(value); + 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.DateTimeUnixSeconds); + WriteDouble(seconds); } public void WriteBigInteger(BigInteger value) From 9033e1cd225570a7f62a2457a998893e4f2ac239 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 16 Apr 2020 16:10:12 +0100 Subject: [PATCH 17/29] split unix time and string time readers into separate methods --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 77 +++++++++++++++++-- .../tests/Cbor/CborReader.Tag.cs | 75 ++++++++++-------- .../tests/Cbor/CborTag.cs | 2 +- .../tests/Cbor/CborWriter.Tag.cs | 4 +- 4 files changed, 118 insertions(+), 40 deletions(-) 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 index 8cd2d9e4299939..3344c1cb4c2019 100644 --- 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 @@ -131,14 +131,8 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat [Theory] [InlineData("2013-03-21T20:04:00Z", "c074323031332d30332d32315432303a30343a30305a")] - [InlineData("2013-03-21T20:04:00Z", "c11a514b67b0")] - [InlineData("2013-03-21T20:04:00.5Z", "c1fb41d452d9ec200000")] [InlineData("2020-04-09T14:31:21.3535941+01:00", "c07821323032302d30342d30395431343a33313a32312e333533353934312b30313a3030")] [InlineData("2020-04-09T11:41:19.12-08:00", "c0781c323032302d30342d30395431313a34313a31392e31322d30383a3030")] - [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 ReadDateTimeOffset_SingleValue_HappyPath(string expectedValueString, string hexEncoding) { DateTimeOffset expectedValue = DateTimeOffset.Parse(expectedValueString); @@ -154,7 +148,6 @@ public static void ReadDateTimeOffset_SingleValue_HappyPath(string expectedValue [Theory] [InlineData("c01a514b67b0")] // string datetime tag with unix time payload - [InlineData("c174323031332d30332d32315432303a30343a30305a")] // epoch datetime tag with string payload public static void ReadDateTimeOffset_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) { byte[] data = hexEncoding.HexToByteArray(); @@ -203,6 +196,76 @@ public static void ReadDateTimeOffset_InvalidFormat_ShouldRollbackToInitialState 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.Peek()); + 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[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + } + + [Theory] + [InlineData("c1f97e00")] // 0(NaN) + [InlineData("c1f9fc00")] // 0(-Infinity) + public static void ReadUnixTimeSeconds_InvalidFloatPayload_ShouldThrowFormatException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + } + + [Theory] + [InlineData("01")] // numeric value without tag + [InlineData("c301")] // non-datetime tag + public static void ReadUnixTimeSeconds_InvalidTag_ShouldThrowInvalidOperationxception(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + } + + [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.Peek()); + Assert.Equal(CborTag.UnixTimeSeconds, reader.ReadTag()); + } + [Theory] [InlineData("0", "c24100")] [InlineData("1", "c24101")] 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 index 494868ec0968fa..25f378bb4ed00b 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -49,45 +49,60 @@ public DateTimeOffset ReadDateTimeOffset() try { - switch (ReadTag()) + ReadTag(expectedTag: CborTag.DateTimeString); + + if (Peek() != CborReaderState.TextString) { - case CborTag.DateTimeString: - if (Peek() != CborReaderState.TextString) - { - throw new FormatException("String DateTime semantic tag should annotate string value."); - } + throw new FormatException("String DateTime semantic tag should annotate string value."); + } - string dateString = ReadTextString(); + string dateString = ReadTextString(); - if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out DateTimeOffset result)) - { - throw new FormatException("DateTime string is not valid RFC3339."); - } + if (!DateTimeOffset.TryParseExact(dateString, CborWriter.Rfc3339FormatString, null, DateTimeStyles.RoundtripKind, out DateTimeOffset result)) + { + throw new FormatException("DateTime string is not valid RFC3339 format."); + } - return result; + return result; + } + catch + { + RestoreCheckpoint(checkpoint); + throw; + } + } - case CborTag.DateTimeUnixSeconds: - switch (Peek()) - { - case CborReaderState.UnsignedInteger: - case CborReaderState.NegativeInteger: - return DateTimeOffset.FromUnixTimeSeconds(ReadInt64()); - - case CborReaderState.HalfPrecisionFloat: - case CborReaderState.SinglePrecisionFloat: - case CborReaderState.DoublePrecisionFloat: - // we don't (but probably should) have a float overload for DateTimeOffset.FromUnixTimeSeconds - double seconds = ReadDouble(); - long epochTicks = DateTimeOffset.UnixEpoch.Ticks; - long ticks = checked(epochTicks + (long)(seconds * TimeSpan.TicksPerSecond)); - return new DateTimeOffset(ticks, TimeSpan.Zero); + public DateTimeOffset ReadUnixTimeSeconds() + { + // implements https://tools.ietf.org/html/rfc7049#section-2.4.1 - default: - throw new FormatException("Epoch DateTime semantic tag should annotate numeric value."); + CborReaderCheckpoint checkpoint = CreateCheckpoint(); + + try + { + ReadTag(expectedTag: CborTag.UnixTimeSeconds); + + switch (Peek()) + { + 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 InvalidOperationException("CBOR tag is not a recognized DateTime value."); + throw new FormatException("UnixDateTime tag should annotate a numeric value."); } } catch 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 b3314abf7cd0b9..7b6d8aa71983c7 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs @@ -8,7 +8,7 @@ namespace System.Security.Cryptography.Encoding.Tests internal enum CborTag : ulong { DateTimeString = 0, - DateTimeUnixSeconds = 1, + UnixTimeSeconds = 1, UnsignedBigNum = 2, NegativeBigNum = 3, DecimalFraction = 4, 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 index 1edf0632dc5633..6ee28a0ff01f49 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Tag.cs @@ -32,7 +32,7 @@ public void WriteDateTimeOffset(DateTimeOffset value) public void WriteUnixTimeSeconds(long seconds) { - WriteTag(CborTag.DateTimeUnixSeconds); + WriteTag(CborTag.UnixTimeSeconds); WriteInt64(seconds); } @@ -43,7 +43,7 @@ public void WriteUnixTimeSeconds(double seconds) throw new ArgumentException("Value cannot be infinite or NaN.", nameof(seconds)); } - WriteTag(CborTag.DateTimeUnixSeconds); + WriteTag(CborTag.UnixTimeSeconds); WriteDouble(seconds); } From 3275cfa3cb431ca1945236113686eb52310c77e3 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 16 Apr 2020 17:56:02 +0100 Subject: [PATCH 18/29] add WriteUnixTestSeconds invalid input test --- .../tests/Cbor.Tests/CborWriterTests.Tag.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 index 577c4c114cf220..5a48c1b020bbd9 100644 --- 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 @@ -123,6 +123,16 @@ public static void WriteUnixTimeSeconds_Double_SingleValue_HappyPath(double valu 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")] From 2558310533c68059bc091b6601973bd46ff382fd Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 16 Apr 2020 18:44:54 +0100 Subject: [PATCH 19/29] add support and tests for indefinite-length buffers in tagged types --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 4 ++++ .../tests/Cbor/CborReader.Tag.cs | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) 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 index 3344c1cb4c2019..b88f7c6b958d70 100644 --- 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 @@ -133,6 +133,7 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat [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); @@ -267,8 +268,10 @@ public static void ReadUnixTimeSeconds_InvalidFormat_ShouldRollbackToInitialStat } [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")] @@ -278,6 +281,7 @@ public static void ReadUnixTimeSeconds_InvalidFormat_ShouldRollbackToInitialStat [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); 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 index 25f378bb4ed00b..5b0cd5a02c3121 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -51,9 +51,13 @@ public DateTimeOffset ReadDateTimeOffset() { ReadTag(expectedTag: CborTag.DateTimeString); - if (Peek() != CborReaderState.TextString) + switch (Peek()) { - throw new FormatException("String DateTime semantic tag should annotate string value."); + case CborReaderState.TextString: + case CborReaderState.StartTextString: + break; + default: + throw new FormatException("String DateTime semantic tag should annotate string value."); } string dateString = ReadTextString(); @@ -127,9 +131,13 @@ public BigInteger ReadBigInteger() _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), }; - if (Peek() != CborReaderState.ByteString) + switch(Peek()) { - throw new FormatException("BigNum semantic tag should annotate byte string value."); + case CborReaderState.ByteString: + case CborReaderState.StartByteString: + break; + default: + throw new FormatException("BigNum semantic tag should annotate byte string value."); } byte[] unsignedBigEndianEncoding = ReadByteString(); From 1f6d5f40ede07a5de470d9e6ab02036cbda30212 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 15:41:46 +0100 Subject: [PATCH 20/29] add PeekTag() tests --- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 index b88f7c6b958d70..681d10ec7b3319 100644 --- 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 @@ -85,6 +85,43 @@ public static void ReadTag_InvalidTypes_ShouldThrowInvalidOperationException(str Assert.Equal("Data item major type mismatch.", exn.Message); } + [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.Peek()); + CborTag tag = reader.PeekTag(); + Assert.Equal(expectedTag, (ulong)tag); + Assert.Equal(CborReaderState.Tag, reader.Peek()); + 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[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + InvalidOperationException exn = Assert.Throws(() => reader.PeekTag()); + + Assert.Equal("Data item major type mismatch.", exn.Message); + } + [Fact] public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatException() { From db1845915dbcadf019bedd706fb939765ebeb577 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 16:19:16 +0100 Subject: [PATCH 21/29] add Read/WriteInt32() convenience methods --- .../Cbor.Tests/CborReaderTests.Integer.cs | 64 +++++++++++++++++++ .../Cbor.Tests/CborWriterTests.Integer.cs | 32 ++++++++++ .../tests/Cbor/CborReader.Integer.cs | 56 +++++++++------- .../tests/Cbor/CborWriter.Integer.cs | 2 + 4 files changed, 131 insertions(+), 23 deletions(-) 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 62a04378d245d6..ea29f8f9c6580e 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 @@ -53,6 +53,39 @@ public static void ReadInt64_SingleValue_HappyPath(long expectedResult, string h Assert.Equal(CborReaderState.Finished, reader.Peek()); } + [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.Peek()); + } + [Theory] [InlineData(0, "00")] [InlineData(1, "01")] @@ -145,6 +178,20 @@ public static void ReadInt64_OutOfRangeValues_ShouldThrowOverflowException(strin Assert.Throws(() => reader.ReadInt64()); } + [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[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + + Assert.Throws(() => reader.ReadInt32()); + } + [Theory] [InlineData("20")] // -1 [InlineData("3863")] // -100 @@ -173,6 +220,23 @@ public static void ReadInt64_InvalidTypes_ShouldThrowInvalidOperationException(s Assert.Equal("Data item major type mismatch.", exn.Message); } + [Theory] + [InlineData("40")] // empty text string + [InlineData("60")] // empty byte string + [InlineData("f6")] // null + [InlineData("80")] // [] + [InlineData("a0")] // {} + [InlineData("f97e00")] // NaN + [InlineData("fb3ff199999999999a")] // 1.1 + public static void ReadInt32_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] data = hexEncoding.HexToByteArray(); + var reader = new CborReader(data); + InvalidOperationException exn = Assert.Throws(() => reader.ReadInt32()); + + Assert.Equal("Data item major type mismatch.", exn.Message); + } + [Theory] [InlineData("40")] // empty byte string [InlineData("60")] // empty text string 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 aec4bf1b16730c..53433014636093 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")] 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 563be487683945..0a9a8d7246faaa 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 @@ -29,31 +29,20 @@ public ulong ReadUInt64() } } - // Implements major type 0,1 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 public long ReadInt64() { - long value; - int additionalBytes; - - CborInitialByte header = PeekInitialByte(); - - 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; + long value = PeekSignedInteger(out int additionalBytes); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; + } - default: - throw new InvalidOperationException("Data item major type mismatch."); - } + public int ReadInt32() + { + int value = checked((int)PeekSignedInteger(out int additionalBytes)); + AdvanceBuffer(1 + additionalBytes); + AdvanceDataItemCounters(); + return value; } // Returns the next CBOR negative integer encoding according to @@ -97,7 +86,28 @@ 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."); + } + } + + // Implements major type 0,1 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 + private long PeekSignedInteger(out int additionalBytes) + { + CborInitialByte header = PeekInitialByte(); + long value; + + switch (header.MajorType) + { + case CborMajorType.UnsignedInteger: + value = checked((long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); + return value; + + case CborMajorType.NegativeInteger: + value = checked(-1 - (long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); + return value; + + default: + throw new InvalidOperationException("Data item major type mismatch."); } } } 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 c9a6ecf60b2c00..e85b15bd1886be 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,6 +31,8 @@ public void WriteInt64(long value) AdvanceDataItemCounters(); } + public void WriteInt32(int value) => WriteInt64(value); + // Writes a CBOR negative integer encoding according to // https://tools.ietf.org/html/rfc7049#section-2.1 public void WriteCborNegativeIntegerEncoding(ulong value) From 485a859de0092fb096242bc5418fe3e758f991c9 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 18:04:25 +0100 Subject: [PATCH 22/29] add pervasive buffer postcondition checks on negative reader tests; fix related bug. --- .../tests/Cbor.Tests/CborReaderTests.Array.cs | 33 ++++-- .../Cbor.Tests/CborReaderTests.Integer.cs | 63 +++++++---- .../tests/Cbor.Tests/CborReaderTests.Map.cs | 45 ++++++-- .../Cbor.Tests/CborReaderTests.SkipValue.cs | 8 ++ .../Cbor.Tests/CborReaderTests.Special.cs | 28 +++-- .../Cbor.Tests/CborReaderTests.String.cs | 106 +++++++++++------- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 83 +++++++++----- .../tests/Cbor.Tests/CborReaderTests.cs | 5 +- .../tests/Cbor/CborReader.Array.cs | 8 +- .../tests/Cbor/CborReader.Map.cs | 8 +- .../tests/Cbor/CborReader.String.cs | 10 +- .../tests/Cbor/CborReader.cs | 2 + 12 files changed, 262 insertions(+), 137 deletions(-) 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..f192187c6f8104 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 @@ -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] @@ -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.Integer.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Integer.cs index ea29f8f9c6580e..4589e266106e33 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 @@ -172,10 +172,11 @@ 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] @@ -186,10 +187,11 @@ public static void ReadInt64_OutOfRangeValues_ShouldThrowOverflowException(strin [InlineData("1bffffffffffffffff")] // ulong.MaxValue public static void ReadInt32_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.ReadInt32()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -198,9 +200,10 @@ public static void ReadInt32_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()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -213,11 +216,13 @@ public static void ReadUInt64_OutOfRangeValues_ShouldThrowOverflowException(stri [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] @@ -230,11 +235,13 @@ public static void ReadInt64_InvalidTypes_ShouldThrowInvalidOperationException(s [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadInt32_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadInt32()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + InvalidOperationException exn = Assert.Throws(() => reader.ReadInt32()); Assert.Equal("Data item major type mismatch.", exn.Message); + + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -247,12 +254,13 @@ public static void ReadInt32_InvalidTypes_ShouldThrowInvalidOperationException(s [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] @@ -267,12 +275,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] @@ -294,9 +303,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] @@ -311,10 +322,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] @@ -322,10 +334,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] @@ -335,6 +348,7 @@ public static void ReadUInt64_EmptyBuffer_ShouldThrowFormatException() var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadUInt64()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] @@ -344,6 +358,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..d93fbeba498c04 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 @@ -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] @@ -237,8 +249,12 @@ public static void ReadMap_IndefiniteLength_PrematureEndArrayCall_ShouldThrowInv reader.ReadInt64(); } + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); Assert.Throws(() => reader.ReadEndMap()); + + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -255,8 +271,12 @@ public static void ReadMap_IndefiniteLength_OddKeyValuePairs_ShouldThrowFormatEx reader.ReadInt64(); } + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.FormatError, reader.Peek()); // 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.SkipValue.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.SkipValue.cs index 6a5334e01ac09f..2dedc35e754ef6 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 @@ -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,8 @@ 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] @@ -99,7 +105,9 @@ public static void SkipValue_NestedFormatException_ShouldPreserveOriginalReaderS 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); 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.Special.cs index f33caae979f9d3..d77c7dc731fac0 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.Special.cs @@ -157,11 +157,11 @@ internal static void ReadSpecialValue_SingleValue_HappyPath(CborSpecialValue exp [InlineData("c202")] // tagged value public static void ReadSpecialValue_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadSpecialValue()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); - Assert.Equal("Data item major type mismatch.", exn.Message); + Assert.Throws(() => reader.ReadSpecialValue()); + 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.String.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.String.cs index 50ad86d04c78fb..803fe25923f606 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 @@ -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(); + int bytesRemaining = reader.BytesRemaining; Assert.Equal(CborReaderState.FormatError, reader.Peek()); // 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(); + int bytesRemaining = reader.BytesRemaining; Assert.Equal(CborReaderState.FormatError, reader.Peek()); // 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 index 681d10ec7b3319..40e090435aabd0 100644 --- 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 @@ -78,11 +78,10 @@ public static void ReadTag_NoSubsequentData_ShouldPeekEndOfData(string hexEncodi [InlineData("fb3ff199999999999a")] // 1.1 public static void ReadTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.ReadTag()); - - Assert.Equal("Data item major type mismatch.", exn.Message); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + Assert.Throws(() => reader.ReadTag()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -115,23 +114,27 @@ public static void PeekTag_SingleValue_HappyPath(ulong expectedTag, string hexEn [InlineData("fb3ff199999999999a")] // 1.1 public static void PeekTag_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); - InvalidOperationException exn = Assert.Throws(() => reader.PeekTag()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); - Assert.Equal("Data item major type mismatch.", exn.Message); + Assert.Throws(() => reader.PeekTag()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Fact] public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatException() { - byte[] data = "9fc2ff".HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = "9fc2ff".HexToByteArray(); + var reader = new CborReader(encoding); reader.ReadStartArray(); reader.ReadTag(); + + int bytesRemaining = reader.BytesRemaining; + Assert.Equal(CborReaderState.FormatError, reader.Peek()); Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -146,8 +149,11 @@ public static void ReadTag_CallingEndReadArrayPrematurely_ShouldThrowInvalidOper reader.ReadStartArray(); reader.ReadInt64(); reader.ReadTag(); + + int bytesRemaining = reader.BytesRemaining; Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -162,8 +168,11 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat reader.ReadStartMap(); reader.ReadInt64(); reader.ReadTag(); + + int bytesRemaining = reader.BytesRemaining; Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); Assert.Throws(() => reader.ReadEndArray()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); } [Theory] @@ -188,10 +197,11 @@ public static void ReadDateTimeOffset_SingleValue_HappyPath(string expectedValue [InlineData("c01a514b67b0")] // string datetime tag with unix time payload public static void ReadDateTimeOffset_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDateTimeOffset()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -199,10 +209,11 @@ public static void ReadDateTimeOffset_InvalidTagPayload_ShouldThrowFormatExcepti [InlineData("c06e4c617374204368726973746d6173")] // 0("Last Christmas") public static void ReadDateTimeOffset_InvalidDateString_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDateTimeOffset()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -210,10 +221,11 @@ public static void ReadDateTimeOffset_InvalidDateString_ShouldThrowFormatExcepti [InlineData("c301")] // non-datetime tag public static void ReadDateTimeOffset_InvalidTag_ShouldThrowInvalidOperationxception(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDateTimeOffset()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -258,10 +270,11 @@ public static void ReadUnixTimeSeconds_SingleValue_HappyPath(string expectedValu [InlineData("c174323031332d30332d32315432303a30343a30305a")] // epoch datetime tag with string payload public static void ReadUnixTimeSeconds_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -269,10 +282,11 @@ public static void ReadUnixTimeSeconds_InvalidTagPayload_ShouldThrowFormatExcept [InlineData("c1f9fc00")] // 0(-Infinity) public static void ReadUnixTimeSeconds_InvalidFloatPayload_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -280,10 +294,11 @@ public static void ReadUnixTimeSeconds_InvalidFloatPayload_ShouldThrowFormatExce [InlineData("c301")] // non-datetime tag public static void ReadUnixTimeSeconds_InvalidTag_ShouldThrowInvalidOperationxception(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -337,10 +352,11 @@ public static void ReadBigInteger_SingleValue_HappyPath(string expectedValueStri [InlineData("c001")] public static void ReadBigInteger_InvalidCborTag_ShouldThrowInvalidOperationException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadBigInteger()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -348,10 +364,11 @@ public static void ReadBigInteger_InvalidCborTag_ShouldThrowInvalidOperationExce [InlineData("c301")] public static void ReadBigInteger_InvalidTagPayload_ShouldThrowFormatException(string hexEncoding) { - byte[] data = hexEncoding.HexToByteArray(); - var reader = new CborReader(data); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadBigInteger()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -403,16 +420,20 @@ public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, [InlineData("c48200c34cffffffffffffffffffffffff")] // (decimal.MinValue - 1) * 10^0 public static void ReadDecimal_LargeValues_ShouldThrowOverflowException(string hexEncoding) { - var reader = new CborReader(hexEncoding.HexToByteArray()); + 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) { - var reader = new CborReader(hexEncoding.HexToByteArray()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDecimal()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] @@ -423,8 +444,10 @@ public static void ReadDecimal_InvalidTag_ShouldThrowInvalidOperationException(s [InlineData("c4826001")] // 4(["", 1]) public static void ReadDecimal_InvalidFormat_ShouldThrowFormatException(string hexEncoding) { - var reader = new CborReader(hexEncoding.HexToByteArray()); + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDecimal()); + Assert.Equal(encoding.Length, reader.BytesRemaining); } [Theory] 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..14860ad75f7f25 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 @@ -74,7 +74,10 @@ public static void CborReader_ReadingTwoPrimitiveValues_ShouldThrowInvalidOperat var reader = new CborReader(buffer); reader.ReadInt64(); Assert.Equal(CborReaderState.Finished, reader.Peek()); + + 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/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.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.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.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs index 631f70ed0a0078..e24dfe3592295b 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs @@ -249,6 +249,7 @@ 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."); @@ -258,6 +259,7 @@ private void PushDataItem(CborMajorType type, ulong? expectedNestedItems) _nestedDataItems.Push((type, _bytesRead, _isEvenNumberOfDataItemsRead, _remainingDataItems)); _remainingDataItems = expectedNestedItems; _isEvenNumberOfDataItemsRead = true; + _isTagContext = false; } private void PopDataItem(CborMajorType expectedType) From d550b2aef179779145c9ab5f84fb11aa79a19cfa Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 21:06:06 +0100 Subject: [PATCH 23/29] add comment on DateTime parsing --- .../tests/Cbor/CborReader.Tag.cs | 1 + 1 file changed, 1 insertion(+) 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 index 5b0cd5a02c3121..e1dd0aff66c425 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -62,6 +62,7 @@ public DateTimeOffset ReadDateTimeOffset() 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."); From 8b9f7a744f979e56e26a861054906701198bc7a7 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 21:39:41 +0100 Subject: [PATCH 24/29] Rename CborReader.Peek() to CborReader.PeekState() --- .../tests/Cbor.Tests/CborReaderTests.Array.cs | 10 +++--- .../Cbor.Tests/CborReaderTests.Helpers.cs | 34 +++++++++--------- .../Cbor.Tests/CborReaderTests.Integer.cs | 12 +++---- .../tests/Cbor.Tests/CborReaderTests.Map.cs | 18 +++++----- .../Cbor.Tests/CborReaderTests.SkipValue.cs | 16 ++++----- .../Cbor.Tests/CborReaderTests.Special.cs | 30 ++++++++-------- .../Cbor.Tests/CborReaderTests.String.cs | 32 ++++++++--------- .../tests/Cbor.Tests/CborReaderTests.Tag.cs | 36 +++++++++---------- .../tests/Cbor.Tests/CborReaderTests.cs | 6 ++-- .../tests/Cbor/CborReader.SkipValue.cs | 2 +- .../tests/Cbor/CborReader.Tag.cs | 12 +++---- .../tests/Cbor/CborReader.cs | 2 +- 12 files changed, 105 insertions(+), 105 deletions(-) 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 f192187c6f8104..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] @@ -111,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] 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..7bc41627e171d5 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,11 +18,11 @@ 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; @@ -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 4589e266106e33..20f55afcbb139e 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,7 @@ 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] @@ -83,7 +83,7 @@ public static void ReadInt32_SingleValue_HappyPath(int expectedResult, string he var reader = new CborReader(data); long actualResult = reader.ReadInt32(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -111,7 +111,7 @@ 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] @@ -130,7 +130,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()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] @@ -146,7 +146,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] @@ -162,7 +162,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()); } 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 d93fbeba498c04..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] @@ -227,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] @@ -251,7 +251,7 @@ public static void ReadMap_IndefiniteLength_PrematureEndArrayCall_ShouldThrowInv int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); Assert.Throws(() => reader.ReadEndMap()); Assert.Equal(bytesRemaining, reader.BytesRemaining); @@ -273,7 +273,7 @@ public static void ReadMap_IndefiniteLength_OddKeyValuePairs_ShouldThrowFormatEx int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.FormatError, reader.Peek()); // don't want this to fail + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); // don't want this to fail Assert.Throws(() => reader.ReadEndMap()); Assert.Equal(bytesRemaining, 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 2dedc35e754ef6..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] @@ -114,13 +114,13 @@ public static void SkipValue_NestedFormatException_ShouldPreserveOriginalReaderS Assert.Equal(reader.BytesRemaining, currentBytesRemaining); // ensure we can read every value up to the format error - Assert.Equal(CborReaderState.StartArray, reader.Peek()); + Assert.Equal(CborReaderState.StartArray, reader.PeekState()); reader.ReadStartArray(); - Assert.Equal(CborReaderState.StartMap, reader.Peek()); + Assert.Equal(CborReaderState.StartMap, reader.PeekState()); reader.ReadStartMap(); - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); reader.ReadUInt64(); - Assert.Equal(CborReaderState.FormatError, reader.Peek()); + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); } [Theory] @@ -137,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.Special.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Special.cs index d77c7dc731fac0..28499c9e3547ab 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.Special.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,10 +125,10 @@ 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] @@ -145,7 +145,7 @@ internal static void ReadSpecialValue_SingleValue_HappyPath(CborSpecialValue exp var reader = new CborReader(encoding); CborSpecialValue actualResult = reader.ReadSpecialValue(); Assert.Equal(expectedResult, actualResult); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory] 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 803fe25923f606..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] @@ -550,7 +550,7 @@ public static void ReadByteString_IndefiniteLength_ContainingInvalidMajorTypes_S reader.ReadByteString(); int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.FormatError, reader.Peek()); + 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); @@ -566,7 +566,7 @@ public static void ReadTextString_IndefiniteLength_ContainingInvalidMajorTypes_S reader.ReadTextString(); int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.FormatError, reader.Peek()); + 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); 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 index 40e090435aabd0..9d2b0599dc4912 100644 --- 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 @@ -28,12 +28,12 @@ public static void ReadTag_SingleValue_HappyPath(ulong expectedTag, object expec byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); 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] @@ -49,13 +49,13 @@ public static void ReadTag_NestedTags_HappyPath(ulong[] expectedTags, object exp foreach (ulong expectedTag in expectedTags) { - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); 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] @@ -65,7 +65,7 @@ public static void ReadTag_NoSubsequentData_ShouldPeekEndOfData(string hexEncodi byte[] data = hexEncoding.HexToByteArray(); var reader = new CborReader(data); reader.ReadTag(); - Assert.Equal(CborReaderState.EndOfData, reader.Peek()); + Assert.Equal(CborReaderState.EndOfData, reader.PeekState()); } [Theory] @@ -97,10 +97,10 @@ public static void PeekTag_SingleValue_HappyPath(ulong expectedTag, string hexEn byte[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); CborTag tag = reader.PeekTag(); Assert.Equal(expectedTag, (ulong)tag); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); Assert.Equal(0, reader.BytesRead); } @@ -132,7 +132,7 @@ public static void ReadTag_NestedTagWithMissingPayload_ShouldThrowFormatExceptio int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.FormatError, reader.Peek()); + Assert.Equal(CborReaderState.FormatError, reader.PeekState()); Assert.Throws(() => reader.ReadEndArray()); Assert.Equal(bytesRemaining, reader.BytesRemaining); } @@ -151,7 +151,7 @@ public static void ReadTag_CallingEndReadArrayPrematurely_ShouldThrowInvalidOper reader.ReadTag(); int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); Assert.Throws(() => reader.ReadEndArray()); Assert.Equal(bytesRemaining, reader.BytesRemaining); } @@ -170,7 +170,7 @@ public static void ReadTag_CallingEndReadMapPrematurely_ShouldThrowInvalidOperat reader.ReadTag(); int bytesRemaining = reader.BytesRemaining; - Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); + Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState()); Assert.Throws(() => reader.ReadEndArray()); Assert.Equal(bytesRemaining, reader.BytesRemaining); } @@ -188,7 +188,7 @@ public static void ReadDateTimeOffset_SingleValue_HappyPath(string expectedValue var reader = new CborReader(data); DateTimeOffset result = reader.ReadDateTimeOffset(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); Assert.Equal(expectedValue, result); Assert.Equal(expectedValue.Offset, result.Offset); } @@ -242,7 +242,7 @@ public static void ReadDateTimeOffset_InvalidFormat_ShouldRollbackToInitialState Assert.Equal(bytesRead, reader.BytesRead); Assert.Equal(bytesRemaining, reader.BytesRemaining); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); Assert.Equal(CborTag.DateTimeString, reader.ReadTag()); } @@ -261,7 +261,7 @@ public static void ReadUnixTimeSeconds_SingleValue_HappyPath(string expectedValu var reader = new CborReader(data); DateTimeOffset result = reader.ReadUnixTimeSeconds(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); Assert.Equal(expectedValue, result); Assert.Equal(TimeSpan.Zero, result.Offset); } @@ -315,7 +315,7 @@ public static void ReadUnixTimeSeconds_InvalidFormat_ShouldRollbackToInitialStat Assert.Equal(bytesRead, reader.BytesRead); Assert.Equal(bytesRemaining, reader.BytesRemaining); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); Assert.Equal(CborTag.UnixTimeSeconds, reader.ReadTag()); } @@ -342,7 +342,7 @@ public static void ReadBigInteger_SingleValue_HappyPath(string expectedValueStri var reader = new CborReader(data); BigInteger result = reader.ReadBigInteger(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); Assert.Equal(expectedValue, result); } @@ -385,7 +385,7 @@ public static void ReadBigInteger_InvalidTagPayload_ShouldRollbackToInitialState Assert.Equal(bytesRead, reader.BytesRead); Assert.Equal(bytesRemaining, reader.BytesRemaining); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); } [Theory] @@ -408,7 +408,7 @@ public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, var reader = new CborReader(data); decimal result = reader.ReadDecimal(); - Assert.Equal(CborReaderState.Finished, reader.Peek()); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); Assert.Equal(expectedValue, result); } @@ -469,7 +469,7 @@ public static void ReadDecimal_InvalidTagPayload_ShouldRollbackToInitialState(st Assert.Equal(bytesRead, reader.BytesRead); Assert.Equal(bytesRemaining, reader.BytesRemaining); - Assert.Equal(CborReaderState.Tag, reader.Peek()); + 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 14860ad75f7f25..62d5a71a5228b0 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] @@ -64,7 +64,7 @@ internal static void Peek_SingleByteBuffer_ShouldReturnExpectedState(CborMajorTy { 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,7 +73,7 @@ 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()); 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 c70da952f76f44..5746844a85006d 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 @@ -31,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(); } 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 index e1dd0aff66c425..ac8bbe9e77ff13 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Tag.cs @@ -51,7 +51,7 @@ public DateTimeOffset ReadDateTimeOffset() { ReadTag(expectedTag: CborTag.DateTimeString); - switch (Peek()) + switch (PeekState()) { case CborReaderState.TextString: case CborReaderState.StartTextString: @@ -87,7 +87,7 @@ public DateTimeOffset ReadUnixTimeSeconds() { ReadTag(expectedTag: CborTag.UnixTimeSeconds); - switch (Peek()) + switch (PeekState()) { case CborReaderState.UnsignedInteger: case CborReaderState.NegativeInteger: @@ -132,7 +132,7 @@ public BigInteger ReadBigInteger() _ => throw new InvalidOperationException("CBOR tag is not a recognized Bignum value."), }; - switch(Peek()) + switch(PeekState()) { case CborReaderState.ByteString: case CborReaderState.StartByteString: @@ -162,7 +162,7 @@ public decimal ReadDecimal() { ReadTag(expectedTag: CborTag.DecimalFraction); - if (Peek() != CborReaderState.StartArray || ReadStartArray() != 2) + if (PeekState() != CborReaderState.StartArray || ReadStartArray() != 2) { throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); } @@ -170,7 +170,7 @@ public decimal ReadDecimal() decimal mantissa; // signed integral component of the decimal value long exponent; // base-10 exponent - switch (Peek()) + switch (PeekState()) { case CborReaderState.UnsignedInteger: case CborReaderState.NegativeInteger: @@ -181,7 +181,7 @@ public decimal ReadDecimal() throw new FormatException("DecimalFraction tag should annotate a list of two numeric elements."); } - switch (Peek()) + switch (PeekState()) { case CborReaderState.UnsignedInteger: mantissa = ReadUInt64(); 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 e24dfe3592295b..6e5a008459188f 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs @@ -61,7 +61,7 @@ internal CborReader(ReadOnlyMemory buffer) public int BytesRead => _bytesRead; public int BytesRemaining => _buffer.Length; - public CborReaderState Peek() + public CborReaderState PeekState() { if (_remainingDataItems == 0) { From 37cf2bd0e05ef4e98f663d2c0148d34ea288b134 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 21:49:55 +0100 Subject: [PATCH 25/29] Use correct overload for int32 handling --- .../tests/Cbor.Tests/CborReaderTests.Helpers.cs | 4 ++-- .../tests/Cbor.Tests/CborWriterTests.Helpers.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 7bc41627e171d5..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 @@ -28,8 +28,8 @@ public static void VerifyValue(CborReader reader, object expectedValue, bool exp 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); 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; From 69741a8594a8ebb129bc6e02832f2ed2ddcd8018 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 17 Apr 2020 21:57:57 +0100 Subject: [PATCH 26/29] add caching to CborReader.PeekState() --- .../tests/Cbor/CborReader.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 6e5a008459188f..ed5c7bacff5423 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs @@ -49,6 +49,7 @@ internal partial class CborReader private bool _isEvenNumberOfDataItemsRead = true; // required for indefinite-length map writes 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; @@ -62,6 +63,16 @@ internal CborReader(ReadOnlyMemory buffer) public int BytesRemaining => _buffer.Length; public CborReaderState PeekState() + { + if (_cachedState == CborReaderState.Unknown) + { + _cachedState = PeekStateCore(); + } + + return _cachedState; + } + + private CborReaderState PeekStateCore() { if (_remainingDataItems == 0) { @@ -289,6 +300,11 @@ private void PopDataItem(CborMajorType expectedType) _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() @@ -302,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) @@ -373,6 +391,8 @@ private void RestoreCheckpoint(CborReaderCheckpoint checkpoint) _remainingDataItems = checkpoint.RemainingDataItems; _isEvenNumberOfDataItemsRead = checkpoint.IsEvenNumberOfDataItemsRead; _isTagContext = checkpoint.IsTagContext; + // invalidate the state cache + _cachedState = CborReaderState.Unknown; } } From 26d81989ed5eec234c0638185b262188a9b0f8c1 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Mon, 20 Apr 2020 14:40:53 +0100 Subject: [PATCH 27/29] Use "simple" instead of "special" for cbor major type --- ...s.Special.cs => CborReaderTests.Simple.cs} | 22 +++++----- .../tests/Cbor.Tests/CborReaderTests.cs | 2 +- ...s.Special.cs => CborWriterTests.Simple.cs} | 18 ++++---- .../tests/Cbor/CborInitialByte.cs | 8 ++-- ...Reader.Special.cs => CborReader.Simple.cs} | 43 +++++++++---------- .../tests/Cbor/CborReader.SkipValue.cs | 2 +- .../tests/Cbor/CborReader.cs | 8 ++-- .../tests/Cbor/CborTag.cs | 2 +- ...Writer.Special.cs => CborWriter.Simple.cs} | 14 +++--- ...ecurity.Cryptography.Encoding.Tests.csproj | 8 ++-- 10 files changed, 62 insertions(+), 65 deletions(-) rename src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/{CborReaderTests.Special.cs => CborReaderTests.Simple.cs} (93%) rename src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/{CborWriterTests.Special.cs => CborWriterTests.Simple.cs} (84%) rename src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/{CborReader.Special.cs => CborReader.Simple.cs} (83%) rename src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/{CborWriter.Special.cs => CborWriter.Simple.cs} (80%) 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 93% 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 28499c9e3547ab..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 @@ -132,18 +132,18 @@ internal static void ReadBoolean_SingleValue_HappyPath(bool expectedResult, stri } [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.PeekState()); } @@ -155,12 +155,12 @@ 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[] encoding = hexEncoding.HexToByteArray(); var reader = new CborReader(encoding); - Assert.Throws(() => reader.ReadSpecialValue()); + Assert.Throws(() => reader.ReadSimpleValue()); Assert.Equal(encoding.Length, reader.BytesRemaining); } 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 62d5a71a5228b0..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 @@ -59,7 +59,7 @@ public static void BytesRemaining_SingleRead_ShouldReturnRemainingBytes() [InlineData(CborMajorType.Array, CborReaderState.StartArray)] [InlineData(CborMajorType.Map, CborReaderState.StartMap)] [InlineData(CborMajorType.Tag, CborReaderState.Tag)] - [InlineData(CborMajorType.Special, CborReaderState.SpecialValue)] + [InlineData(CborMajorType.Simple, CborReaderState.SpecialValue)] internal static void Peek_SingleByteBuffer_ShouldReturnExpectedState(CborMajorType majorType, CborReaderState expectedResult) { ReadOnlyMemory buffer = new byte[] { (byte)((byte)majorType << 5) }; 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/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.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 5746844a85006d..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 @@ -107,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.cs b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs index ed5c7bacff5423..e366c891eb615a 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs @@ -159,7 +159,7 @@ private CborReaderState PeekStateCore() 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."), }; @@ -169,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; 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 7b6d8aa71983c7..765953056e138d 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborTag.cs @@ -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.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/System.Security.Cryptography.Encoding.Tests.csproj b/src/libraries/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj index 026b64d8638275..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,17 +51,17 @@ - + - + - + @@ -72,7 +72,7 @@ - + From 1533f80b8410656c23aabc3f51f17c78497d87b4 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Mon, 20 Apr 2020 17:36:28 +0100 Subject: [PATCH 28/29] add Write/ReadUInt32() convenience methods --- .../Cbor.Tests/CborReaderTests.Integer.cs | 25 +++++ .../Cbor.Tests/CborWriterTests.Integer.cs | 24 +++++ .../tests/Cbor/CborReader.Integer.cs | 96 +++++++++++-------- .../tests/Cbor/CborWriter.Integer.cs | 1 + 4 files changed, 105 insertions(+), 41 deletions(-) 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 20f55afcbb139e..98e5b5dd0ebac7 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 @@ -114,6 +114,31 @@ public static void ReadUInt64_SingleValue_HappyPath(ulong expectedResult, string 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] [InlineData(0, "20")] [InlineData(9, "29")] 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 53433014636093..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 @@ -110,6 +110,30 @@ public static void WriteUInt64_SingleValue_HappyPath(ulong input, string hexExpe 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(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.WriteUInt32(input); + AssertHelper.HexEqual(expectedEncoding, writer.ToArray()); + } + [Theory] [InlineData(0, "20")] [InlineData(9, "29")] 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 0a9a8d7246faaa..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,26 +8,7 @@ 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 - public ulong ReadUInt64() - { - CborInitialByte header = PeekInitialByte(); - - switch (header.MajorType) - { - case CborMajorType.UnsignedInteger: - ulong value = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes); - AdvanceBuffer(1 + additionalBytes); - AdvanceDataItemCounters(); - return value; - - case CborMajorType.NegativeInteger: - throw new OverflowException(); - - default: - throw new InvalidOperationException("Data item major type mismatch."); - } - } + // Implements major type 0,1 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 public long ReadInt64() { @@ -37,6 +18,14 @@ public long ReadInt64() 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)); @@ -45,6 +34,14 @@ public int ReadInt32() 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() @@ -56,6 +53,44 @@ public ulong ReadCborNegativeIntegerEncoding() return value; } + private ulong PeekUnsignedInteger(out int additionalBytes) + { + CborInitialByte header = PeekInitialByte(); + + switch (header.MajorType) + { + case CborMajorType.UnsignedInteger: + ulong value = ReadUnsignedInteger(_buffer.Span, header, out additionalBytes); + return value; + + case CborMajorType.NegativeInteger: + throw new OverflowException(); + + default: + throw new InvalidOperationException("Data item major type mismatch."); + } + } + + private long PeekSignedInteger(out int additionalBytes) + { + CborInitialByte header = PeekInitialByte(); + long value; + + switch (header.MajorType) + { + case CborMajorType.UnsignedInteger: + value = checked((long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); + return value; + + case CborMajorType.NegativeInteger: + value = checked(-1 - (long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); + return value; + + default: + throw new InvalidOperationException("Data item major type mismatch."); + } + } + // Unsigned integer decoding https://tools.ietf.org/html/rfc7049#section-2.1 private static ulong ReadUnsignedInteger(ReadOnlySpan buffer, CborInitialByte header, out int additionalBytes) { @@ -89,26 +124,5 @@ private static ulong ReadUnsignedInteger(ReadOnlySpan buffer, CborInitialB throw new FormatException("initial byte contains invalid integer encoding data."); } } - - // Implements major type 0,1 decoding per https://tools.ietf.org/html/rfc7049#section-2.1 - private long PeekSignedInteger(out int additionalBytes) - { - CborInitialByte header = PeekInitialByte(); - long value; - - switch (header.MajorType) - { - case CborMajorType.UnsignedInteger: - value = checked((long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); - return value; - - case CborMajorType.NegativeInteger: - value = checked(-1 - (long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes)); - return value; - - default: - throw new InvalidOperationException("Data item major type mismatch."); - } - } } } 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 e85b15bd1886be..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 @@ -32,6 +32,7 @@ public void WriteInt64(long value) } 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 From d33f054e2ad805f4c6d888b18dea9b535ca0fb35 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Mon, 20 Apr 2020 19:58:45 +0100 Subject: [PATCH 29/29] add uint32 negative tests --- .../Cbor.Tests/CborReaderTests.Integer.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) 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 98e5b5dd0ebac7..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 @@ -219,6 +219,18 @@ public static void ReadInt32_OutOfRangeValues_ShouldThrowOverflowException(strin 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] [InlineData("20")] // -1 [InlineData("3863")] // -100 @@ -269,6 +281,25 @@ public static void ReadInt32_InvalidTypes_ShouldThrowInvalidOperationException(s Assert.Equal(encoding.Length, reader.BytesRemaining); } + [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 ReadUInt32_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + InvalidOperationException exn = Assert.Throws(() => reader.ReadUInt32()); + Assert.Equal("Data item major type mismatch.", exn.Message); + + Assert.Equal(encoding.Length, reader.BytesRemaining); + } + [Theory] [InlineData("40")] // empty byte string [InlineData("60")] // empty text string