{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
- ArrayReaderHelper.VerifyArray(reader, expectedValues);
+ Helpers.VerifyArray(reader, expectedValues);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
- ArrayReaderHelper.VerifyArray(reader, expectedValues);
+ Helpers.VerifyArray(reader, expectedValues);
Assert.Equal(CborReaderState.Finished, reader.Peek());
}
}
[Theory]
- [InlineData("81", 1, 0)]
- [InlineData("8201", 2, 1)]
- [InlineData("860102", 6, 2)]
+ [InlineData("821907e4", 2, 1)]
+ [InlineData("861907e41907e4", 6, 2)]
public static void ReadArray_IncorrectDefiniteLength_ShouldThrowFormatException(string hexEncoding, int expectedLength, int actualLength)
{
byte[] encoding = hexEncoding.HexToByteArray();
}
[Theory]
- [InlineData("81", 1, 0)]
[InlineData("828101", 2, 1)]
- [InlineData("8681018102", 6, 2)]
+ [InlineData("868101811907e4", 6, 2)]
public static void ReadArray_IncorrectDefiniteLength_NestedValues_ShouldThrowFormatException(string hexEncoding, int expectedLength, int actualLength)
{
byte[] encoding = hexEncoding.HexToByteArray();
}
[Fact]
- public static void BeginReadArray_EmptyBuffer_ShouldThrowFormatException()
+ public static void ReadStartArray_EmptyBuffer_ShouldThrowFormatException()
{
byte[] encoding = Array.Empty<byte>();
var reader = new CborReader(encoding);
[InlineData("a0")] // {}
[InlineData("f97e00")] // NaN
[InlineData("fb3ff199999999999a")] // 1.1
- public static void BeginReadArray_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding)
+ public static void ReadStartArray_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
[InlineData("9912")]
[InlineData("9a000000")]
[InlineData("9b00000000000000")]
- public static void BeginReadArray_InvalidData_ShouldThrowFormatException(string hexEncoding)
+ public static void ReadStartArray_InvalidData_ShouldThrowFormatException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
Assert.Throws<FormatException>(() => reader.ReadStartArray());
}
- }
- static class ArrayReaderHelper
- {
- public static void VerifyArray(CborReader reader, params object[] expectedValues)
+ [Theory]
+ [InlineData("81")]
+ [InlineData("830102")]
+ [InlineData("9b7fffffffffffffff")] // long.MaxValue
+ public static void ReadStartArray_BufferTooSmall_ShouldThrowFormatException(string hexEncoding)
{
- Assert.Equal(CborReaderState.StartArray, reader.Peek());
-
- ulong? length = reader.ReadStartArray();
-
- Assert.NotNull(length);
- Assert.Equal(expectedValues.Length, (int)length!.Value);
-
- foreach (object value in expectedValues)
- {
- switch (value)
- {
- case int expected:
- if (expected >= 0)
- {
- Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek());
- }
- else
- {
- Assert.Equal(CborReaderState.NegativeInteger, reader.Peek());
- }
-
- long i = reader.ReadInt64();
- Assert.Equal(expected, (int)i);
- break;
- case string expected:
- Assert.Equal(CborReaderState.TextString, reader.Peek());
- string s = reader.ReadTextString();
- Assert.Equal(expected, s);
- break;
- case byte[] expected:
- Assert.Equal(CborReaderState.ByteString, reader.Peek());
- byte[] b = reader.ReadByteString();
- Assert.Equal(expected, b);
- break;
- case object[] nested:
- VerifyArray(reader, nested);
- break;
- default:
- throw new ArgumentException($"Unrecognized argument type {value.GetType()}");
- }
- }
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
- Assert.Equal(CborReaderState.EndArray, reader.Peek());
- reader.ReadEndArray();
+ Assert.Throws<FormatException>(() => reader.ReadStartArray());
}
}
}
--- /dev/null
+// 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.Linq;
+using Xunit;
+
+namespace System.Security.Cryptography.Encoding.Tests.Cbor
+{
+ public partial class CborReaderTests
+ {
+ internal static class Helpers
+ {
+ public static void VerifyValue(CborReader reader, object expectedValue)
+ {
+ switch (expectedValue)
+ {
+ case int expected:
+ VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0);
+ long i = reader.ReadInt64();
+ Assert.Equal(expected, (int)i);
+ break;
+ case long expected:
+ VerifyPeekInteger(reader, isUnsignedInteger: expected >= 0);
+ long l = reader.ReadInt64();
+ Assert.Equal(expected, l);
+ break;
+ case ulong expected:
+ VerifyPeekInteger(reader, isUnsignedInteger: true);
+ ulong u = reader.ReadUInt64();
+ Assert.Equal(expected, u);
+ break;
+ case string expected:
+ Assert.Equal(CborReaderState.TextString, reader.Peek());
+ string s = reader.ReadTextString();
+ Assert.Equal(expected, s);
+ break;
+ case byte[] expected:
+ Assert.Equal(CborReaderState.ByteString, reader.Peek());
+ byte[] b = reader.ReadByteString();
+ Assert.Equal(expected, b);
+ break;
+ case object[] nested when CborWriterTests.Helpers.IsCborMapRepresentation(nested):
+ VerifyMap(reader, nested);
+ break;
+ case object[] nested:
+ VerifyArray(reader, nested);
+ break;
+ default:
+ throw new ArgumentException($"Unrecognized argument type {expectedValue.GetType()}");
+ }
+
+ static void VerifyPeekInteger(CborReader reader, bool isUnsignedInteger)
+ {
+ CborReaderState expectedState = isUnsignedInteger ? CborReaderState.UnsignedInteger : CborReaderState.NegativeInteger;
+ Assert.Equal(expectedState, reader.Peek());
+ }
+ }
+
+ public static void VerifyArray(CborReader reader, params object[] expectedValues)
+ {
+ Assert.Equal(CborReaderState.StartArray, reader.Peek());
+
+ ulong? length = reader.ReadStartArray();
+
+ Assert.NotNull(length);
+ Assert.Equal(expectedValues.Length, (int)length!.Value);
+
+ foreach (object value in expectedValues)
+ {
+ VerifyValue(reader, value);
+ }
+
+ Assert.Equal(CborReaderState.EndArray, reader.Peek());
+ reader.ReadEndArray();
+ }
+
+ public static void VerifyMap(CborReader reader, params object[] expectedValues)
+ {
+ if (!CborWriterTests.Helpers.IsCborMapRepresentation(expectedValues))
+ {
+ throw new ArgumentException($"cbor map expected values missing '{CborWriterTests.Helpers.MapPrefixIdentifier}' prefix.");
+ }
+
+ Assert.Equal(CborReaderState.StartMap, reader.Peek());
+ ulong? length = reader.ReadStartMap();
+
+ Assert.NotNull(length);
+ Assert.Equal((expectedValues.Length - 1) / 2, (int)length!.Value);
+
+ foreach (object value in expectedValues.Skip(1))
+ {
+ VerifyValue(reader, value);
+ }
+
+ Assert.Equal(CborReaderState.EndMap, reader.Peek());
+ reader.ReadEndMap();
+ }
+ }
+ }
+}
--- /dev/null
+// 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/
+
+ public const string Map = CborWriterTests.Helpers.MapPrefixIdentifier;
+
+ [Theory]
+ [InlineData(new object[] { Map }, "a0")]
+ [InlineData(new object[] { Map, 1, 2, 3, 4 }, "a201020304")]
+ [InlineData(new object[] { Map, "a", "A", "b", "B", "c", "C", "d", "D", "e", "E" }, "a56161614161626142616361436164614461656145")]
+ [InlineData(new object[] { Map, "a", "A", -1, 2, new byte[] { }, new byte[] { 1 } }, "a3616161412002404101")]
+ public static void ReadMap_SimpleValues_HappyPath(object[] expectedValues, string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ Helpers.VerifyMap(reader, expectedValues);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "b", new object[] { Map, 2, 3 } }, "a26161016162a10203")]
+ [InlineData(new object[] { Map, "a", new object[] { Map, 2, 3 }, "b", new object[] { Map, "x", -1, "y", new object[] { Map, "z", 0 } } }, "a26161a102036162a26178206179a1617a00")]
+ [InlineData(new object[] { Map, new object[] { Map, "x", 2 }, 42 }, "a1a1617802182a")] // using maps as keys
+ public static void ReadMap_NestedValues_HappyPath(object[] expectedValues, string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ Helpers.VerifyMap(reader, expectedValues);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "b", new object[] { 2, 3 } }, "a26161016162820203")]
+ [InlineData(new object[] { Map, "a", new object[] { 2, 3, "b", new object[] { Map, "x", -1, "y", new object[] { "z", 0 } } } }, "a161618402036162a2617820617982617a00")]
+ [InlineData(new object[] { "a", new object[] { Map, "b", "c" } }, "826161a161626163")]
+ [InlineData(new object[] { Map, new object[] { 1 }, 42 }, "a18101182a")] // using arrays as keys
+ public static void ReadMap_NestedListValues_HappyPath(object expectedValue, string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ Helpers.VerifyValue(reader, expectedValue);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "a", 2 }, "a2616101616102")]
+ public static void ReadMap_DuplicateKeys_ShouldSucceed(object[] values, string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ Helpers.VerifyMap(reader, values);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData("a0", 0)]
+ [InlineData("a10102", 1)]
+ [InlineData("a3010203040506", 3)]
+ public static void ReadMap_DefiniteLengthExceeded_ShouldThrowInvalidOperationException(string hexEncoding, int expectedLength)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+
+ ulong? length = reader.ReadStartMap();
+ Assert.Equal(expectedLength, (int)length!.Value);
+
+ for (int i = 0; i < expectedLength; i++)
+ {
+ reader.ReadInt64(); // key
+ reader.ReadInt64(); // value
+ }
+
+ Assert.Throws<InvalidOperationException>(() => reader.ReadInt64());
+ }
+
+ [Theory]
+ [InlineData("a101a10101", 1)]
+ [InlineData("a301a1010102a1020203a10303", 3)]
+ public static void ReadMap_DefiniteLengthExceeded_WithNestedData_ShouldThrowInvalidOperationException(string hexEncoding, int expectedLength)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+
+ ulong? length = reader.ReadStartMap();
+ Assert.Equal(expectedLength, (int)length!.Value);
+
+ for (int i = 0; i < expectedLength; i++)
+ {
+ reader.ReadInt64(); // key
+
+ // value
+ ulong? nestedLength = reader.ReadStartMap();
+ Assert.Equal(1, (int)nestedLength!.Value);
+ reader.ReadInt64();
+ reader.ReadInt64();
+ reader.ReadEndMap();
+ }
+
+ Assert.Throws<InvalidOperationException>(() => reader.ReadInt64());
+ }
+
+ [Theory]
+ [InlineData("a10101", 1)]
+ [InlineData("a3010203040506", 3)]
+ public static void ReadEndMap_DefiniteLengthNotMet_ShouldThrowInvalidOperationException(string hexEncoding, int expectedLength)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+
+ ulong? length = reader.ReadStartMap();
+ Assert.Equal(expectedLength, (int)length!.Value);
+
+ for (int i = 1; i < expectedLength; i++)
+ {
+ reader.ReadInt64(); // key
+ reader.ReadInt64(); // value
+ }
+
+ Assert.Throws<InvalidOperationException>(() => reader.ReadEndMap());
+ }
+
+ [Theory]
+ [InlineData("a101a10101", 1)]
+ [InlineData("a301a1010102a10202a3a10303", 3)]
+ public static void ReadEndMap_DefiniteLengthNotMet_WithNestedData_ShouldThrowInvalidOperationException(string hexEncoding, int expectedLength)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+
+ ulong? length = reader.ReadStartMap();
+ Assert.Equal(expectedLength, (int)length!.Value);
+
+ for (int i = 1; i < expectedLength; i++)
+ {
+ reader.ReadInt64(); // key
+
+ ulong? nestedLength = reader.ReadStartMap();
+ Assert.Equal(1, (int)nestedLength!.Value);
+ reader.ReadInt64();
+ reader.ReadInt64();
+ reader.ReadEndMap();
+ }
+
+ Assert.Throws<InvalidOperationException>(() => reader.ReadEndMap());
+ }
+
+ [Theory]
+ [InlineData("80", 0)]
+ [InlineData("80", 1)]
+ [InlineData("8180", 2)]
+ public static void ReadEndMap_ImbalancedCall_ShouldThrowInvalidOperationException(string hexEncoding, int depth)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ for (int i = 0; i < depth; i++)
+ {
+ reader.ReadStartArray();
+ }
+
+ Assert.Throws<InvalidOperationException>(() => reader.ReadEndMap());
+ }
+
+ [Theory]
+ [InlineData("a2011907e4", 2, 1)]
+ [InlineData("a6011a01344224031a01344224", 6, 2)]
+ public static void ReadMap_IncorrectDefiniteLength_ShouldThrowFormatException(string hexEncoding, int expectedLength, int actualLength)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+
+ ulong? length = reader.ReadStartMap();
+ Assert.Equal(expectedLength, (int)length!.Value);
+
+ for (int i = 0; i < actualLength; i++)
+ {
+ reader.ReadInt64(); // key
+ reader.ReadInt64(); // value
+ }
+
+ Assert.Throws<FormatException>(() => reader.ReadInt64());
+ }
+
+ [Theory]
+ [InlineData("a201811907e4", 2, 1)]
+ [InlineData("a61907e4811907e402811907e4", 6, 2)]
+ public static void ReadMap_IncorrectDefiniteLength_NestedValues_ShouldThrowFormatException(string hexEncoding, int expectedLength, int actualLength)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+
+ ulong? length = reader.ReadStartMap();
+ Assert.Equal(expectedLength, (int)length!.Value);
+
+ for (int i = 0; i < actualLength; i++)
+ {
+ reader.ReadInt64(); // key
+
+ ulong? innerLength = reader.ReadStartArray();
+ Assert.Equal(1, (int)innerLength!.Value);
+ reader.ReadInt64();
+ reader.ReadEndArray();
+ }
+
+ Assert.Throws<FormatException>(() => reader.ReadInt64());
+ }
+
+ [Fact]
+ public static void ReadStartMap_EmptyBuffer_ShouldThrowFormatException()
+ {
+ byte[] encoding = Array.Empty<byte>();
+ var reader = new CborReader(encoding);
+
+ Assert.Throws<FormatException>(() => reader.ReadStartMap());
+ }
+
+ [Theory]
+ [InlineData("00")] // 0
+ [InlineData("20")] // -1
+ [InlineData("40")] // empty byte string
+ [InlineData("60")] // empty text string
+ [InlineData("f6")] // null
+ [InlineData("80")] // []
+ [InlineData("f97e00")] // NaN
+ [InlineData("fb3ff199999999999a")] // 1.1
+ public static void ReadStartMap_InvalidType_ShouldThrowInvalidOperationException(string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Throws<InvalidOperationException>(() => reader.ReadStartMap());
+ }
+
+ [Theory]
+ // Invalid initial bytes with map major type
+ [InlineData("bc")]
+ [InlineData("bd")]
+ [InlineData("be")]
+ // valid initial bytes missing required definite length data
+ [InlineData("b8")]
+ [InlineData("b912")]
+ [InlineData("ba000000")]
+ [InlineData("bb00000000000000")]
+ public static void ReadStartMap_InvalidData_ShouldThrowFormatException(string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ Assert.Throws<FormatException>(() => reader.ReadStartMap());
+ }
+
+ [Theory]
+ [InlineData("b1")]
+ [InlineData("b20101")]
+ [InlineData("bb7fffffffffffffff")] // long.MaxValue
+ public static void ReadStartMap_BufferTooSmall_ShouldThrowFormatException(string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ Assert.Throws<FormatException>(() => reader.ReadStartMap());
+ }
+
+ [Theory]
+ [InlineData("bb8000000000000000")] // long.MaxValue + 1
+ [InlineData("bbffffffffffffffff")] // ulong.MaxValue
+ public static void ReadStartMap_LargeFieldCount_ShouldThrowOverflowException(string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ Assert.Throws<OverflowException>(() => reader.ReadStartMap());
+ }
+ }
+}
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
using var writer = new CborWriter();
- ArrayWriterHelper.WriteArray(writer, values);
+ Helpers.WriteArray(writer, values);
byte[] actualEncoding = writer.ToArray();
AssertHelper.HexEqual(expectedEncoding, actualEncoding);
}
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
using var writer = new CborWriter();
- ArrayWriterHelper.WriteArray(writer, values);
+ Helpers.WriteArray(writer, values);
byte[] actualEncoding = writer.ToArray();
AssertHelper.HexEqual(expectedEncoding, actualEncoding);
}
[InlineData(1)]
[InlineData(3)]
[InlineData(10)]
- public static void EndWriteArray_DefiniteLengthNotMet_ShouldThrowInvalidOperationException(int definiteLength)
+ public static void WriteEndArray_DefiniteLengthNotMet_ShouldThrowInvalidOperationException(int definiteLength)
{
using var writer = new CborWriter();
writer.WriteStartArray(definiteLength);
[InlineData(1)]
[InlineData(3)]
[InlineData(10)]
- public static void EndWriteArray_DefiniteLengthNotMet_WithNestedData_ShouldThrowInvalidOperationException(int definiteLength)
+ public static void WriteEndArray_DefiniteLengthNotMet_WithNestedData_ShouldThrowInvalidOperationException(int definiteLength)
{
using var writer = new CborWriter();
writer.WriteStartArray(definiteLength);
Assert.Throws<InvalidOperationException>(() => writer.WriteEndArray());
}
- [Fact]
- public static void EndWriteArray_ImbalancedCall_ShouldThrowInvalidOperationException()
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(3)]
+ public static void WriteEndArray_ImbalancedCall_ShouldThrowInvalidOperationException(int depth)
{
using var writer = new CborWriter();
+ for (int i = 0; i < depth; i++)
+ {
+ writer.WriteStartMap(1);
+ }
+
Assert.Throws<InvalidOperationException>(() => writer.WriteEndArray());
}
- }
- static class ArrayWriterHelper
- {
- public static void WriteArray(CborWriter writer, params object[] values)
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(3)]
+ public static void WriteEndArray_AfterStartMap_ShouldThrowInvalidOperationException(int depth)
{
- writer.WriteStartArray(values.Length);
- foreach (object value in values)
+ using var writer = new CborWriter();
+
+ for (int i = 0; i < depth; i++)
{
- switch (value)
+ if (i % 2 == 0)
+ {
+ writer.WriteStartArray(1);
+ }
+ else
{
- case int i: writer.WriteInt64(i); break;
- case string s: writer.WriteTextString(s); break;
- case byte[] b: writer.WriteByteString(b); break;
- case object[] nested: ArrayWriterHelper.WriteArray(writer, nested); break;
- default: throw new ArgumentException($"Unrecognized argument type {value.GetType()}");
- };
+ writer.WriteStartMap(1);
+ }
}
- writer.WriteEndArray();
+
+ writer.WriteStartMap(definiteLength: 0);
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndArray());
}
}
}
--- /dev/null
+// 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.Linq;
+using Xunit;
+
+namespace System.Security.Cryptography.Encoding.Tests.Cbor
+{
+ public partial class CborWriterTests
+ {
+ internal static class Helpers
+ {
+ public const string MapPrefixIdentifier = "_map";
+
+ // Since we inject test data using attributes, meed to represent both arrays and maps using object arrays.
+ // To distinguish between the two types, we prepend map representations using a string constant.
+ public static bool IsCborMapRepresentation(object[] values)
+ {
+ return values.Length % 2 == 1 && values[0] is string s && s == MapPrefixIdentifier;
+ }
+
+ public static void WriteValue(CborWriter writer, object value)
+ {
+ switch (value)
+ {
+ case int i: writer.WriteInt64(i); break;
+ case long i: writer.WriteInt64(i); break;
+ case ulong i: writer.WriteUInt64(i); break;
+ case string s: writer.WriteTextString(s); break;
+ case byte[] b: writer.WriteByteString(b); break;
+ case object[] nested when IsCborMapRepresentation(nested): WriteMap(writer, nested); break;
+ case object[] nested: WriteArray(writer, nested); break;
+ default: throw new ArgumentException($"Unrecognized argument type {value.GetType()}");
+ };
+ }
+
+ public static void WriteArray(CborWriter writer, params object[] values)
+ {
+ writer.WriteStartArray(values.Length);
+ foreach (object value in values)
+ {
+ WriteValue(writer, value);
+ }
+ writer.WriteEndArray();
+ }
+
+ public static void WriteMap(CborWriter writer, params object[] keyValuePairs)
+ {
+ if (!IsCborMapRepresentation(keyValuePairs))
+ {
+ throw new ArgumentException($"CBOR map representation must contain odd number of elements prepended with a '{MapPrefixIdentifier}' constant.");
+ }
+
+ writer.WriteStartMap(keyValuePairs.Length / 2);
+
+ foreach (object value in keyValuePairs.Skip(1))
+ {
+ WriteValue(writer, value);
+ }
+
+ writer.WriteEndMap();
+ }
+ }
+ }
+}
--- /dev/null
+// 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;
+//using static W = System.Security.Cryptography.Encoding.Tests.Cbor.CborWriterHelpers;
+
+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/
+
+ public const string Map = Helpers.MapPrefixIdentifier;
+
+ [Theory]
+ [InlineData(new object[] { Map }, "a0")]
+ [InlineData(new object[] { Map, 1, 2, 3, 4 }, "a201020304")]
+ [InlineData(new object[] { Map, "a", "A", "b", "B", "c", "C", "d", "D", "e", "E" }, "a56161614161626142616361436164614461656145")]
+ [InlineData(new object[] { Map, "a", "A", -1, 2, new byte[] { }, new byte[] { 1 } }, "a3616161412002404101")]
+ public static void WriteMap_SimpleValues_HappyPath(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteMap(writer, values);
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "b", new object[] { Map, 2, 3 } }, "a26161016162a10203")]
+ [InlineData(new object[] { Map, "a", new object[] { Map, 2, 3 }, "b", new object[] { Map, "x", -1, "y", new object[] { Map, "z", 0 } } }, "a26161a102036162a26178206179a1617a00")]
+ [InlineData(new object[] { Map, new object[] { Map, "x", 2 }, 42 }, "a1a1617802182a")] // using maps as keys
+ public static void WriteMap_NestedValues_HappyPath(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteMap(writer, values);
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "b", new object[] { 2, 3 } }, "a26161016162820203")]
+ [InlineData(new object[] { Map, "a", new object[] { 2, 3, "b", new object[] { Map, "x", -1, "y", new object[] { "z", 0 } } } }, "a161618402036162a2617820617982617a00")]
+ [InlineData(new object[] { "a", new object[] { Map, "b", "c" } }, "826161a161626163")]
+ [InlineData(new object[] { Map, new object[] { 1 }, 42 }, "a18101182a")] // using arrays as keys
+ public static void WriteMap_NestedListValues_HappyPath(object value, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteValue(writer, value);
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "a", 2 }, "a2616101616102")]
+ public static void WriteMap_DuplicateKeys_ShouldSucceed(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteMap(writer, values);
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(3)]
+ [InlineData(10)]
+ public static void WriteMap_DefiniteLengthExceeded_ShouldThrowInvalidOperationException(int definiteLength)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartMap(definiteLength);
+ for (int i = 0; i < definiteLength; i++)
+ {
+ writer.WriteTextString($"key_{i}");
+ writer.WriteInt64(i);
+ }
+
+ Assert.Throws<InvalidOperationException>(() => writer.WriteInt64(0));
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(3)]
+ [InlineData(10)]
+ public static void WriteMap_DefiniteLengthExceeded_WithNestedData_ShouldThrowInvalidOperationException(int definiteLength)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartMap(definiteLength);
+ for (int i = 0; i < definiteLength; i++)
+ {
+ writer.WriteTextString($"key_{i}");
+ writer.WriteStartMap(definiteLength: 1);
+ writer.WriteInt64(i);
+ writer.WriteInt64(i);
+ writer.WriteEndMap();
+ }
+
+ Assert.Throws<InvalidOperationException>(() => writer.WriteInt64(0));
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(3)]
+ [InlineData(10)]
+ public static void EndWriteMap_DefiniteLengthNotMet_ShouldThrowInvalidOperationException(int definiteLength)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartMap(definiteLength);
+ for (int i = 1; i < definiteLength; i++)
+ {
+ writer.WriteTextString($"key_{i}");
+ writer.WriteInt64(i);
+ }
+
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(3)]
+ [InlineData(10)]
+ public static void EndWriteMap_DefiniteLengthNotMet_WithNestedData_ShouldThrowInvalidOperationException(int definiteLength)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartMap(definiteLength);
+ for (int i = 1; i < definiteLength; i++)
+ {
+ writer.WriteTextString($"key_{i}");
+ writer.WriteStartMap(definiteLength: 1);
+ writer.WriteInt64(i);
+ writer.WriteInt64(i);
+ writer.WriteEndMap();
+ }
+
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
+ }
+
+ [Fact]
+ public static void EndWriteMap_ImbalancedCall_ShouldThrowInvalidOperationException()
+ {
+ using var writer = new CborWriter();
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(3)]
+ public static void WriteEndMap_ImbalancedCall_ShouldThrowInvalidOperationException(int depth)
+ {
+ using var writer = new CborWriter();
+ for (int i = 0; i < depth; i++)
+ {
+ writer.WriteStartArray(1);
+ }
+
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(3)]
+ public static void WriteEndMap_AfterStartArray_ShouldThrowInvalidOperationException(int depth)
+ {
+ using var writer = new CborWriter();
+
+ for (int i = 0; i < depth; i++)
+ {
+ if (i % 2 == 0)
+ {
+ writer.WriteStartArray(1);
+ }
+ else
+ {
+ writer.WriteStartMap(1);
+ }
+ }
+
+ writer.WriteStartArray(definiteLength: 0);
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
+ }
+ }
+}
internal enum CborAdditionalInfo : byte
{
- UnsignedInteger8BitEncoding = 24,
- UnsignedInteger16BitEncoding = 25,
- UnsignedInteger32BitEncoding = 26,
- UnsignedInteger64BitEncoding = 27,
+ Unsigned8BitIntegerEncoding = 24,
+ Unsigned16BitIntegerEncoding = 25,
+ Unsigned32BitIntegerEncoding = 26,
+ Unsigned64BitIntegerEncoding = 27,
IndefiniteLength = 31,
}
switch (header.AdditionalInfo)
{
- case CborAdditionalInfo x when (x < CborAdditionalInfo.UnsignedInteger8BitEncoding):
+ case CborAdditionalInfo x when (x < CborAdditionalInfo.Unsigned8BitIntegerEncoding):
additionalBytes = 0;
return (ulong)x;
- case CborAdditionalInfo.UnsignedInteger8BitEncoding:
+ case CborAdditionalInfo.Unsigned8BitIntegerEncoding:
EnsureBuffer(2);
additionalBytes = 1;
return buffer[1];
- case CborAdditionalInfo.UnsignedInteger16BitEncoding:
+ case CborAdditionalInfo.Unsigned16BitIntegerEncoding:
EnsureBuffer(3);
additionalBytes = 2;
return BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(1));
- case CborAdditionalInfo.UnsignedInteger32BitEncoding:
+ case CborAdditionalInfo.Unsigned32BitIntegerEncoding:
EnsureBuffer(5);
additionalBytes = 4;
return BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(1));
- case CborAdditionalInfo.UnsignedInteger64BitEncoding:
+ case CborAdditionalInfo.Unsigned64BitIntegerEncoding:
EnsureBuffer(9);
additionalBytes = 8;
return BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(1));
--- /dev/null
+// 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.Buffers.Binary;
+
+namespace System.Security.Cryptography.Encoding.Tests.Cbor
+{
+ internal partial class CborReader
+ {
+ public ulong? ReadStartMap()
+ {
+ CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Map);
+ ulong arrayLength = checked((ulong)ReadUnsignedInteger(header, out int additionalBytes));
+ AdvanceBuffer(1 + additionalBytes);
+ _remainingDataItems--;
+
+ if (arrayLength > long.MaxValue)
+ {
+ throw new OverflowException("Read CBOR map field count exceeds supported size.");
+ }
+
+ PushDataItem(CborMajorType.Map, 2 * arrayLength);
+ return arrayLength;
+ }
+
+ public void ReadEndMap()
+ {
+ PopDataItem(expectedType: CborMajorType.Map);
+ }
+ }
+}
private void PushDataItem(CborMajorType type, ulong? expectedNestedItems)
{
+ if (expectedNestedItems > (ulong)_buffer.Length)
+ {
+ throw new FormatException("Insufficient buffer size for declared definite length in CBOR data item.");
+ }
+
_nestedDataItemStack ??= new Stack<(CborMajorType, ulong?)>();
_nestedDataItemStack.Push((type, _remainingDataItems));
_remainingDataItems = expectedNestedItems;
else if (value <= byte.MaxValue)
{
EnsureWriteCapacity(2);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.UnsignedInteger8BitEncoding).InitialByte;
+ _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned8BitIntegerEncoding).InitialByte;
_buffer[_offset + 1] = (byte)value;
_offset += 2;
}
else if (value <= ushort.MaxValue)
{
EnsureWriteCapacity(3);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.UnsignedInteger16BitEncoding).InitialByte;
+ _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned16BitIntegerEncoding).InitialByte;
BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_offset + 1), (ushort)value);
_offset += 3;
}
else if (value <= uint.MaxValue)
{
EnsureWriteCapacity(5);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.UnsignedInteger32BitEncoding).InitialByte;
+ _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned32BitIntegerEncoding).InitialByte;
BinaryPrimitives.WriteUInt32BigEndian(_buffer.AsSpan(_offset + 1), (uint)value);
_offset += 5;
}
else
{
EnsureWriteCapacity(9);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.UnsignedInteger64BitEncoding).InitialByte;
+ _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned64BitIntegerEncoding).InitialByte;
BinaryPrimitives.WriteUInt64BigEndian(_buffer.AsSpan(_offset + 1), value);
_offset += 9;
}
--- /dev/null
+// 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.Text;
+
+namespace System.Security.Cryptography.Encoding.Tests.Cbor
+{
+ internal partial class CborWriter
+ {
+ public void WriteStartMap(int definiteLength)
+ {
+ if (definiteLength < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(definiteLength), "must be non-negative integer.");
+ }
+
+ WriteUnsignedInteger(CborMajorType.Map, (ulong)definiteLength);
+ PushDataItem(CborMajorType.Map, 2 * (uint)definiteLength);
+ }
+
+ public void WriteEndMap()
+ {
+ PopDataItem(CborMajorType.Map);
+ }
+ }
+}
<Compile Include="Asn1\Writer\WriteObjectIdentifier.cs" />
<Compile Include="Asn1\Writer\WriteUtcTime.cs" />
<Compile Include="Asn1\Writer\WriteUtf8String.cs" />
+ <Compile Include="Cbor.Tests\CborReaderTests.Helpers.cs" />
+ <Compile Include="Cbor.Tests\CborReaderTests.Map.cs" />
+ <Compile Include="Cbor.Tests\CborWriterTests.Helpers.cs" />
<Compile Include="Cbor.Tests\CborReaderTests.Array.cs" />
<Compile Include="Cbor.Tests\CborWriterTests.Array.cs" />
<Compile Include="Cbor.Tests\CborWriterTests.cs" />
+ <Compile Include="Cbor.Tests\CborWriterTests.Map.cs" />
<Compile Include="Cbor\CborInitialByte.cs" />
<Compile Include="Cbor\CborReader.Array.cs" />
<Compile Include="Cbor\CborReader.cs" />
<Compile Include="Cbor\CborReader.Integer.cs" />
+ <Compile Include="Cbor\CborReader.Map.cs" />
<Compile Include="Cbor\CborReader.String.cs" />
<Compile Include="Cbor\CborWriter.Array.cs" />
+ <Compile Include="Cbor\CborWriter.Map.cs" />
<Compile Include="Cbor\CborWriter.String.cs" />
<Compile Include="Cbor\CborWriter.cs" />
<Compile Include="Cbor\CborWriter.Integer.cs" />