Assert.Equal(CborReaderState.Finished, reader.Peek());
}
+ [Theory]
+ [InlineData(new object[] { }, "9fff")]
+ [InlineData(new object[] { 42 }, "9f182aff")]
+ [InlineData(new object[] { 1, 2, 3 }, "9f010203ff")]
+ [InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff")]
+ [InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "9f0120604107ff")]
+ [InlineData(new object[] { "lorem", "ipsum", "dolor" }, "9f656c6f72656d65697073756d65646f6c6f72ff")]
+ public static void ReadArray_IndefiniteLength_HappyPath(object[] expectedValues, string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ Helpers.VerifyArray(reader, expectedValues, expectDefiniteLengthCollections: false);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
[Theory]
[InlineData("80", 0)]
[InlineData("8101", 1)]
Assert.Throws<InvalidOperationException>(() => reader.ReadInt64());
}
+ [Theory]
+ [InlineData("9f")]
+ [InlineData("9f01")]
+ [InlineData("9f0102")]
+ public static void ReadArray_IndefiniteLength_MissingBreakByte_ShouldReportEndOfData(string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ reader.ReadStartArray();
+ while (reader.Peek() == CborReaderState.UnsignedInteger)
+ {
+ reader.ReadInt64();
+ }
+
+ Assert.Equal(CborReaderState.EndOfData, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData("9f01ff", 1)]
+ [InlineData("9f0102ff", 2)]
+ [InlineData("9f010203ff", 3)]
+ public static void ReadArray_IndefiniteLength_PrematureEndArrayCall_ShouldThrowInvalidOperationException(string hexEncoding, int length)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ reader.ReadStartArray();
+ for (int i = 1; i < length; i++)
+ {
+ reader.ReadInt64();
+ }
+
+ Assert.Throws<InvalidOperationException>(() => reader.ReadEndArray());
+ }
+
[Theory]
[InlineData("8101", 1)]
[InlineData("83010203", 3)]
#nullable enable
using System.Linq;
+using Test.Cryptography;
using Xunit;
namespace System.Security.Cryptography.Encoding.Tests.Cbor
{
internal static class Helpers
{
- public static void VerifyValue(CborReader reader, object expectedValue)
+ public static void VerifyValue(CborReader reader, object expectedValue, bool expectDefiniteLengthCollections = true)
{
switch (expectedValue)
{
case byte[] expected:
Assert.Equal(CborReaderState.ByteString, reader.Peek());
byte[] b = reader.ReadByteString();
- Assert.Equal(expected, b);
+ Assert.Equal(expected.ByteArrayToHex(), b.ByteArrayToHex());
break;
+ case string[] expectedChunks:
+ Assert.Equal(CborReaderState.StartTextString, reader.Peek());
+ reader.ReadStartTextStringIndefiniteLength();
+ foreach(string expectedChunk in expectedChunks)
+ {
+ Assert.Equal(CborReaderState.TextString, reader.Peek());
+ string chunk = reader.ReadTextString();
+ Assert.Equal(expectedChunk, chunk);
+ }
+ Assert.Equal(CborReaderState.EndTextString, reader.Peek());
+ reader.ReadEndTextStringIndefiniteLength();
+ break;
+ case byte[][] expectedChunks:
+ Assert.Equal(CborReaderState.StartByteString, reader.Peek());
+ reader.ReadStartByteStringIndefiniteLength();
+ foreach (byte[] expectedChunk in expectedChunks)
+ {
+ Assert.Equal(CborReaderState.ByteString, reader.Peek());
+ byte[] chunk = reader.ReadByteString();
+ Assert.Equal(expectedChunk.ByteArrayToHex(), chunk.ByteArrayToHex());
+ }
+ Assert.Equal(CborReaderState.EndByteString, reader.Peek());
+ reader.ReadEndByteStringIndefiniteLength();
+ break;
+
case object[] nested when CborWriterTests.Helpers.IsCborMapRepresentation(nested):
- VerifyMap(reader, nested);
+ VerifyMap(reader, nested, expectDefiniteLengthCollections);
break;
case object[] nested:
- VerifyArray(reader, nested);
+ VerifyArray(reader, nested, expectDefiniteLengthCollections);
break;
default:
throw new ArgumentException($"Unrecognized argument type {expectedValue.GetType()}");
}
}
- public static void VerifyArray(CborReader reader, params object[] expectedValues)
+ public static void VerifyArray(CborReader reader, object[] expectedValues, bool expectDefiniteLengthCollections = true)
{
Assert.Equal(CborReaderState.StartArray, reader.Peek());
ulong? length = reader.ReadStartArray();
- Assert.NotNull(length);
- Assert.Equal(expectedValues.Length, (int)length!.Value);
+ if (expectDefiniteLengthCollections)
+ {
+ Assert.NotNull(length);
+ Assert.Equal(expectedValues.Length, (int)length!.Value);
+ }
+ else
+ {
+ Assert.Null(length);
+ }
foreach (object value in expectedValues)
{
reader.ReadEndArray();
}
- public static void VerifyMap(CborReader reader, params object[] expectedValues)
+ public static void VerifyMap(CborReader reader, object[] expectedValues, bool expectDefiniteLengthCollections = true)
{
if (!CborWriterTests.Helpers.IsCborMapRepresentation(expectedValues))
{
}
Assert.Equal(CborReaderState.StartMap, reader.Peek());
+
ulong? length = reader.ReadStartMap();
- Assert.NotNull(length);
- Assert.Equal((expectedValues.Length - 1) / 2, (int)length!.Value);
+ if (expectDefiniteLengthCollections)
+ {
+ Assert.NotNull(length);
+ Assert.Equal((expectedValues.Length - 1) / 2, (int)length!.Value);
+ }
+ else
+ {
+ Assert.Null(length);
+ }
foreach (object value in expectedValues.Skip(1))
{
[Theory]
[InlineData("1f")]
[InlineData("3f")]
- public static void ReadInt64_IndefiniteLengthIntegers_ShouldThrowNotImplementedException(string hexEncoding)
+ public static void ReadInt64_IndefiniteLengthIntegers_ShouldThrowFormatException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
- Assert.Throws<NotImplementedException>(() => reader.ReadInt64());
+ Assert.Throws<FormatException>(() => reader.ReadInt64());
}
[Fact]
Assert.Equal(CborReaderState.Finished, reader.Peek());
}
+ [Theory]
+ [InlineData(new object[] { Map }, "bfff")]
+ [InlineData(new object[] { Map, 1, 2, 3, 4 }, "bf01020304ff")]
+ [InlineData(new object[] { Map, "a", "A", "b", "B", "c", "C", "d", "D", "e", "E" }, "bf6161614161626142616361436164614461656145ff")]
+ [InlineData(new object[] { Map, "a", "A", -1, 2, new byte[] { }, new byte[] { 1 } }, "bf616161412002404101ff")]
+ public static void ReadMap_IndefiniteLength_SimpleValues_HappyPath(object[] exoectedValues, string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ Helpers.VerifyMap(reader, exoectedValues, expectDefiniteLengthCollections: false);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
[Theory]
[InlineData(new object[] { Map, "a", 1, "a", 2 }, "a2616101616102")]
Assert.Throws<FormatException>(() => reader.ReadInt64());
}
+ [Theory]
+ [InlineData("bf")]
+ [InlineData("bf0102")]
+ [InlineData("bf01020304")]
+ public static void ReadMap_IndefiniteLength_MissingBreakByte_ShouldReportEndOfData(string hexEncoding)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ reader.ReadStartMap();
+ while (reader.Peek() == CborReaderState.UnsignedInteger)
+ {
+ reader.ReadInt64();
+ }
+
+ Assert.Equal(CborReaderState.EndOfData, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData("bf0102ff", 1)]
+ [InlineData("bf01020304ff", 2)]
+ [InlineData("bf010203040506ff", 3)]
+ public static void ReadMap_IndefiniteLength_PrematureEndArrayCall_ShouldThrowInvalidOperationException(string hexEncoding, int length)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ reader.ReadStartMap();
+ for (int i = 1; i < length; i++)
+ {
+ reader.ReadInt64();
+ }
+
+ Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek());
+ Assert.Throws<InvalidOperationException>(() => reader.ReadEndMap());
+ }
+
+ [Theory]
+ [InlineData("bf01ff", 1)]
+ [InlineData("bf010203ff", 3)]
+ [InlineData("bf0102030405ff", 5)]
+ public static void ReadMap_IndefiniteLength_OddKeyValuePairs_ShouldThrowFormatException(string hexEncoding, int length)
+ {
+ byte[] encoding = hexEncoding.HexToByteArray();
+ var reader = new CborReader(encoding);
+ reader.ReadStartMap();
+ for (int i = 0; i < length; i++)
+ {
+ reader.ReadInt64();
+ }
+
+ Assert.Equal(CborReaderState.FormatError, reader.Peek()); // don't want this to fail
+ Assert.Throws<FormatException>(() => reader.ReadEndMap());
+ }
+
[Theory]
[InlineData("a201811907e4", 2, 1)]
[InlineData("a61907e4811907e402811907e4", 6, 2)]
#nullable enable
using System;
+using System.Linq;
+using System.Text;
using Test.Cryptography;
using Xunit;
Assert.Equal(CborReaderState.Finished, reader.Peek());
}
+ [Theory]
+ [InlineData(new string[] { }, "5fff")]
+ [InlineData(new string[] { "" }, "5f40ff")]
+ [InlineData(new string[] { "ab", "" }, "5f41ab40ff")]
+ [InlineData(new string[] { "ab", "bc", "" }, "5f41ab41bc40ff")]
+ public static void ReadByteString_IndefiniteLength_SingleValue_HappyPath(string[] expectedHexValues, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ byte[][] expectedValues = expectedHexValues.Select(x => x.HexToByteArray()).ToArray();
+ var reader = new CborReader(data);
+ Helpers.VerifyValue(reader, expectedValues);
+ }
+
+ [Theory]
+ [InlineData("", "5fff")]
+ [InlineData("", "5f40ff")]
+ [InlineData("ab", "5f41ab40ff")]
+ [InlineData("abbc", "5f41ab41bc40ff")]
+ public static void ReadByteString_IndefiniteLengthConcatenated_SingleValue_HappyPath(string expectedHexValue, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Equal(CborReaderState.StartByteString, reader.Peek());
+ byte[] actualValue = reader.ReadByteString();
+ Assert.Equal(expectedHexValue.ToUpper(), actualValue.ByteArrayToHex());
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData("", "5fff")]
+ [InlineData("", "5f40ff")]
+ [InlineData("ab", "5f41ab40ff")]
+ [InlineData("abbc", "5f41ab41bc40ff")]
+ public static void TryReadByteString_IndefiniteLengthConcatenated_SingleValue_HappyPath(string expectedHexValue, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Equal(CborReaderState.StartByteString, reader.Peek());
+
+ Span<byte> buffer = new byte[32];
+ bool result = reader.TryReadByteString(buffer, out int bytesWritten);
+
+ Assert.True(result);
+ Assert.Equal(expectedHexValue.Length / 2, bytesWritten);
+ Assert.Equal(expectedHexValue.ToUpper(), buffer.Slice(0, bytesWritten).ByteArrayToHex());
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Fact]
+ public static void ReadByteString_IndefiniteLengthConcatenated_NestedValues_HappyPath()
+ {
+ string hexEncoding = "825f41ab40ff5f41ab40ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ reader.ReadStartArray();
+ Assert.Equal("AB", reader.ReadByteString().ByteArrayToHex());
+ Assert.Equal("AB", reader.ReadByteString().ByteArrayToHex());
+ reader.ReadEndArray();
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData(new string[] { }, "7fff")]
+ [InlineData(new string[] { "" }, "7f60ff")]
+ [InlineData(new string[] { "ab", "" }, "7f62616260ff")]
+ [InlineData(new string[] { "ab", "bc", "" }, "7f62616262626360ff")]
+ public static void ReadTextString_IndefiniteLength_SingleValue_HappyPath(string[] expectedValues, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Helpers.VerifyValue(reader, expectedValues);
+ }
+
+ [Theory]
+ [InlineData("", "7fff")]
+ [InlineData("", "7f60ff")]
+ [InlineData("ab", "7f62616260ff")]
+ [InlineData("abbc", "7f62616262626360ff")]
+ public static void ReadTextString_IndefiniteLengthConcatenated_SingleValue_HappyPath(string expectedValue, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Equal(CborReaderState.StartTextString, reader.Peek());
+ string actualValue = reader.ReadTextString();
+ Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Fact]
+ public static void ReadTextString_IndefiniteLengthConcatenated_NestedValues_HappyPath()
+ {
+ string hexEncoding = "827f62616260ff7f62616260ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ reader.ReadStartArray();
+ Assert.Equal("ab", reader.ReadTextString());
+ Assert.Equal("ab", reader.ReadTextString());
+ reader.ReadEndArray();
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
+ [Theory]
+ [InlineData("", "7fff")]
+ [InlineData("", "7f60ff")]
+ [InlineData("ab", "7f62616260ff")]
+ [InlineData("abbc", "7f62616262626360ff")]
+ public static void TryReadTextString_IndefiniteLengthConcatenated_SingleValue__HappyPath(string expectedValue, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Equal(CborReaderState.StartTextString, reader.Peek());
+
+ Span<char> buffer = new char[32];
+ bool result = reader.TryReadTextString(buffer, out int charsWritten);
+
+ Assert.True(result);
+ Assert.Equal(expectedValue.Length, charsWritten);
+ Assert.Equal(expectedValue, new string(buffer.Slice(0, charsWritten)));
+ Assert.Equal(CborReaderState.Finished, reader.Peek());
+ }
+
[Theory]
[InlineData("01020304", "4401020304")]
[InlineData("ffffffffffffffffffffffffffff", "4effffffffffffffffffffffffffff")]
public static void TryReadByteString_BufferTooSmall_ShouldReturnFalse(string actualValue, string hexEncoding)
{
- byte[] buffer = new byte[actualValue.Length / 2 - 1];
+ byte[] buffer = new byte[actualValue.Length / 2];
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
- bool result = reader.TryReadByteString(buffer, out int bytesWritten);
+ bool result = reader.TryReadByteString(buffer.AsSpan(1), out int bytesWritten);
Assert.False(result);
Assert.Equal(0, bytesWritten);
Assert.All(buffer, (b => Assert.Equal(0, b)));
+
+ // ensure that reader is still able to complete the read operation if a large enough buffer is supplied subsequently
+ result = reader.TryReadByteString(buffer, out bytesWritten);
+ Assert.True(result);
+ Assert.Equal(buffer.Length, bytesWritten);
+ Assert.Equal(actualValue.ToUpper(), buffer.ByteArrayToHex());
}
[Theory]
[InlineData("\ud800\udd51", "64f0908591")]
public static void TryReadTextString_BufferTooSmall_ShouldReturnFalse(string actualValue, string hexEncoding)
{
- char[] buffer = new char[actualValue.Length - 1];
+ char[] buffer = new char[actualValue.Length];
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);
- bool result = reader.TryReadTextString(buffer, out int charsWritten);
+ bool result = reader.TryReadTextString(buffer.AsSpan(1), out int charsWritten);
Assert.False(result);
Assert.Equal(0, charsWritten);
Assert.All(buffer, (b => Assert.Equal(0, '\0')));
+
+ // ensure that reader is still able to complete the read operation if a large enough buffer is supplied subsequently
+ result = reader.TryReadTextString(buffer, out charsWritten);
+ Assert.True(result);
+ Assert.Equal(actualValue.Length, charsWritten);
+ Assert.Equal(actualValue, new string(buffer.AsSpan(0, charsWritten)));
+ }
+
+ [Theory]
+ [InlineData("ab", "5f41ab40ff")]
+ [InlineData("abbc", "5f41ab41bc40ff")]
+ public static void TryReadByteString_IndefiniteLengthConcatenated_BufferTooSmall_ShouldReturnFalse(string expectedHexValue, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ byte[] buffer = new byte[expectedHexValue.Length / 2];
+ bool result = reader.TryReadByteString(buffer.AsSpan(1), out int bytesWritten);
+
+ Assert.False(result);
+ Assert.Equal(0, bytesWritten);
+ Assert.All(buffer, (b => Assert.Equal(0, b)));
+
+ // ensure that reader is still able to complete the read operation if a large enough buffer is supplied subsequently
+ result = reader.TryReadByteString(buffer, out bytesWritten);
+ Assert.True(result);
+ Assert.Equal(buffer.Length, bytesWritten);
+ Assert.Equal(expectedHexValue.ToUpper(), buffer.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData("ab", "7f62616260ff")]
+ [InlineData("abbc", "7f62616262626360ff")]
+ public static void TryReadTextString_IndefiniteLengthConcatenated_BufferTooSmall_ShouldReturnFalse(string expectedValue, string hexEncoding)
+ {
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ char[] buffer = new char[expectedValue.Length];
+ bool result = reader.TryReadTextString(buffer.AsSpan(1), out int charsWritten);
+
+ Assert.False(result);
+ Assert.Equal(0, charsWritten);
+ Assert.All(buffer, (b => Assert.Equal(0, '\0')));
+
+ // ensure that reader is still able to perform the read operation if a large enough buffer is supplied subsequently
+ result = reader.TryReadTextString(buffer, out charsWritten);
+ Assert.True(result);
+ Assert.Equal(expectedValue.Length, charsWritten);
+ Assert.Equal(expectedValue, new string(buffer.AsSpan(0, charsWritten)));
}
[Theory]
Assert.Throws<FormatException>(() => reader.ReadByteString());
}
+
+ [Fact]
+ public static void ReadByteString_IndefiniteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException()
+ {
+ string hexEncoding = "5f4001ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ reader.ReadStartByteStringIndefiniteLength();
+ reader.ReadByteString();
+
+ Assert.Equal(CborReaderState.FormatError, reader.Peek());
+ // throws FormatException even if it's the right major type we're trying to read
+ Assert.Throws<FormatException>(() => reader.ReadInt64());
+ }
+
+ [Fact]
+ public static void ReadTextString_IndefiniteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException()
+ {
+ string hexEncoding = "7f6001ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ reader.ReadStartTextStringIndefiniteLength();
+ reader.ReadTextString();
+
+ Assert.Equal(CborReaderState.FormatError, reader.Peek());
+ // throws FormatException even if it's the right major type we're trying to read
+ Assert.Throws<FormatException>(() => reader.ReadInt64());
+ }
+
+ [Fact]
+ public static void ReadByteString_IndefiniteLength_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException()
+ {
+ string hexEncoding = "5f5fffff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ reader.ReadStartByteStringIndefiniteLength();
+
+ Assert.Throws<FormatException>(() => reader.ReadStartByteStringIndefiniteLength());
+ }
+
+ [Fact]
+ public static void ReadByteString_IndefiniteLengthConcatenated_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException()
+ {
+ string hexEncoding = "5f5fffff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ Assert.Throws<FormatException>(() => reader.ReadByteString());
+ }
+
+ [Fact]
+ public static void ReadTextString_IndefiniteLength_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException()
+ {
+ string hexEncoding = "7f7fffff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ reader.ReadStartTextStringIndefiniteLength();
+
+ Assert.Throws<FormatException>(() => reader.ReadStartTextStringIndefiniteLength());
+ }
+
+ [Fact]
+ public static void ReadTextString_IndefiniteLengthConcatenated_ContainingNestedIndefiniteLengthStrings_ShouldThrowFormatException()
+ {
+ string hexEncoding = "7f7fffff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+
+ Assert.Throws<FormatException>(() => reader.ReadTextString());
+ }
+
+ [Fact]
+ public static void ReadByteString_IndefiniteLengthConcatenated_ContainingInvalidMajorTypes_ShouldThrowFormatException()
+ {
+ string hexEncoding = "5f4001ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Throws<FormatException>(() => reader.ReadByteString());
+ }
+
+ [Fact]
+ public static void ReadTextString_IndefiniteLengthConcatenated_ContainingInvalidMajorTypes_ShouldThrowFormatException()
+ {
+ string hexEncoding = "7f6001ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Throws<FormatException>(() => reader.ReadTextString());
+ }
+
+ [Fact]
+ public static void ReadTextString_IndefiniteLengthConcatenated_InvalidUtf8Chunks_ShouldThrowDecoderFallbackException()
+ {
+ // while the concatenated string is valid utf8, the individual chunks are not,
+ // which is in violation of the CBOR format.
+
+ string hexEncoding = "7f62f090628591ff";
+ byte[] data = hexEncoding.HexToByteArray();
+ var reader = new CborReader(data);
+ Assert.Throws<DecoderFallbackException>(() => reader.ReadTextString());
+ }
}
}
AssertHelper.HexEqual(expectedEncoding, actualEncoding);
}
+ [Theory]
+ [InlineData(new object[] { }, "9fff")]
+ [InlineData(new object[] { 42 }, "9f182aff")]
+ [InlineData(new object[] { 1, 2, 3 }, "9f010203ff")]
+ [InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff")]
+ [InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "9f0120604107ff")]
+ [InlineData(new object[] { "lorem", "ipsum", "dolor" }, "9f656c6f72656d65697073756d65646f6c6f72ff")]
+ public static void WriteArray_IndefiniteLength_HappyPath(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+
+ using var writer = new CborWriter();
+ Helpers.WriteArray(writer, values, useDefiniteLengthCollections: false);
+
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
+ [Theory]
+ [InlineData(new object[] { new object[] { } }, "9f9fffff")]
+ [InlineData(new object[] { 1, new object[] { 2, 3 }, new object[] { 4, 5 } }, "9f019f0203ff9f0405ffff")]
+ [InlineData(new object[] { "", new object[] { new object[] { }, new object[] { 1, new byte[] { 10 } } } }, "9f609f9fff9f01410affffff")]
+ public static void WriteArray_IndefiniteLength_NestedValues_HappyPath(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+
+ using var writer = new CborWriter();
+ Helpers.WriteArray(writer, values, useDefiniteLengthCollections: false);
+
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
[Theory]
[InlineData(0)]
[InlineData(1)]
return values.Length % 2 == 1 && values[0] is string s && s == MapPrefixIdentifier;
}
- public static void WriteValue(CborWriter writer, object value)
+ public static void WriteValue(CborWriter writer, object value, bool useDefiniteLengthCollections = true)
{
switch (value)
{
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;
+ case byte[][] chunks: WriteChunkedByteString(writer, chunks); break;
+ case string[] chunks: WriteChunkedTextString(writer, chunks); break;
+ case object[] nested when IsCborMapRepresentation(nested): WriteMap(writer, nested, useDefiniteLengthCollections); break;
+ case object[] nested: WriteArray(writer, nested, useDefiniteLengthCollections); break;
default: throw new ArgumentException($"Unrecognized argument type {value.GetType()}");
};
}
- public static void WriteArray(CborWriter writer, params object[] values)
+ public static void WriteArray(CborWriter writer, object[] values, bool useDefiniteLengthCollections = true)
{
- writer.WriteStartArray(values.Length);
+ if (useDefiniteLengthCollections)
+ {
+ writer.WriteStartArray(values.Length);
+ }
+ else
+ {
+ writer.WriteStartArrayIndefiniteLength();
+ }
+
foreach (object value in values)
{
- WriteValue(writer, value);
+ WriteValue(writer, value, useDefiniteLengthCollections);
}
+
writer.WriteEndArray();
}
- public static void WriteMap(CborWriter writer, params object[] keyValuePairs)
+ public static void WriteMap(CborWriter writer, object[] keyValuePairs, bool useDefiniteLengthCollections = true)
{
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);
+ if (useDefiniteLengthCollections)
+ {
+ writer.WriteStartMap(keyValuePairs.Length / 2);
+ }
+ else
+ {
+ writer.WriteStartMapIndefiniteLength();
+ }
foreach (object value in keyValuePairs.Skip(1))
{
- WriteValue(writer, value);
+ WriteValue(writer, value, useDefiniteLengthCollections);
}
writer.WriteEndMap();
}
+
+ public static void WriteChunkedByteString(CborWriter writer, byte[][] chunks)
+ {
+ writer.WriteStartByteStringIndefiniteLength();
+ foreach (byte[] chunk in chunks)
+ {
+ writer.WriteByteString(chunk);
+ }
+ writer.WriteEndByteStringIndefiniteLength();
+ }
+
+ public static void WriteChunkedTextString(CborWriter writer, string[] chunks)
+ {
+ writer.WriteStartTextStringIndefiniteLength();
+ foreach (string chunk in chunks)
+ {
+ writer.WriteTextString(chunk);
+ }
+ writer.WriteEndTextStringIndefiniteLength();
+ }
+
+ public static void ExecOperation(CborWriter writer, string op)
+ {
+ switch (op)
+ {
+ case nameof(writer.WriteInt64): writer.WriteInt64(42); break;
+ case nameof(writer.WriteByteString): writer.WriteByteString(Array.Empty<byte>()); break;
+ case nameof(writer.WriteTextString): writer.WriteTextString(""); break;
+ case nameof(writer.WriteStartTextStringIndefiniteLength): writer.WriteStartTextStringIndefiniteLength(); break;
+ case nameof(writer.WriteStartByteStringIndefiniteLength): writer.WriteStartByteStringIndefiniteLength(); break;
+ case nameof(writer.WriteStartArray): writer.WriteStartArrayIndefiniteLength(); break;
+ case nameof(writer.WriteStartMap): writer.WriteStartMapIndefiniteLength(); break;
+ case nameof(writer.WriteEndByteStringIndefiniteLength): writer.WriteEndByteStringIndefiniteLength(); break;
+ case nameof(writer.WriteEndTextStringIndefiniteLength): writer.WriteEndTextStringIndefiniteLength(); break;
+ case nameof(writer.WriteEndArray): writer.WriteEndArray(); break;
+ case nameof(writer.WriteEndMap): writer.WriteEndMap(); break;
+ default: throw new Exception($"Unrecognized CborWriter operation name {op}");
+ }
+ }
}
}
}
AssertHelper.HexEqual(expectedEncoding, actualEncoding);
}
+ [Theory]
+ [InlineData(new object[] { Map }, "bfff")]
+ [InlineData(new object[] { Map, 1, 2, 3, 4 }, "bf01020304ff")]
+ [InlineData(new object[] { Map, "a", "A", "b", "B", "c", "C", "d", "D", "e", "E" }, "bf6161614161626142616361436164614461656145ff")]
+ [InlineData(new object[] { Map, "a", "A", -1, 2, new byte[] { }, new byte[] { 1 } }, "bf616161412002404101ff")]
+ public static void WriteMap_IndefiniteLength_SimpleValues_HappyPath(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteMap(writer, values, useDefiniteLengthCollections: false);
+ byte[] actualEncoding = writer.ToArray();
+ AssertHelper.HexEqual(expectedEncoding, actualEncoding);
+ }
+
+ [Theory]
+ [InlineData(new object[] { Map, "a", 1, "b", new object[] { Map, 2, 3 } }, "bf6161016162bf0203ffff")]
+ [InlineData(new object[] { Map, "a", new object[] { Map, 2, 3 }, "b", new object[] { Map, "x", -1, "y", new object[] { Map, "z", 0 } } }, "bf6161bf0203ff6162bf6178206179bf617a00ffffff")]
+ [InlineData(new object[] { Map, new object[] { Map, "x", 2 }, 42 }, "bfbf617802ff182aff")] // using maps as keys
+ public static void WriteMap_IndefiniteLength_NestedValues_HappyPath(object[] values, string expectedHexEncoding)
+ {
+ byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteMap(writer, values, useDefiniteLengthCollections: false);
+ 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")]
Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
}
+ [Theory]
+ [InlineData(0)]
+ [InlineData(3)]
+ [InlineData(10)]
+ public static void EndWriteMap_IndefiniteLength_EvenItems_ShouldThrowInvalidOperationException(int length)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartMapIndefiniteLength();
+
+ for (int i = 1; i < length; i++)
+ {
+ writer.WriteTextString($"key_{i}");
+ writer.WriteInt64(i);
+ }
+
+ writer.WriteInt64(0);
+
+ Assert.Throws<InvalidOperationException>(() => writer.WriteEndMap());
+ }
+
[Fact]
public static void EndWriteMap_ImbalancedCall_ShouldThrowInvalidOperationException()
{
#nullable enable
using System;
+using System.Linq;
using Test.Cryptography;
using Xunit;
AssertHelper.HexEqual(expectedEncoding, writer.ToArray());
}
+ [Theory]
+ [InlineData(new string[] { }, "5fff")]
+ [InlineData(new string[] { "" }, "5f40ff")]
+ [InlineData(new string[] { "ab", "" }, "5f41ab40ff")]
+ [InlineData(new string[] { "ab", "bc", "" }, "5f41ab41bc40ff")]
+ public static void WriteByteString_IndefiteLength_SingleValue_HappyPath(string[] hexChunkInputs, string hexExpectedEncoding)
+ {
+ byte[][] chunkInputs = hexChunkInputs.Select(ch => ch.HexToByteArray()).ToArray();
+ byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
+
+ using var writer = new CborWriter();
+ Helpers.WriteChunkedByteString(writer, chunkInputs);
+ AssertHelper.HexEqual(expectedEncoding, writer.ToArray());
+ }
+
[Theory]
[InlineData("", "60")]
[InlineData("a", "6161")]
AssertHelper.HexEqual(expectedEncoding, writer.ToArray());
}
+ [Theory]
+ [InlineData(new string[] { }, "7fff")]
+ [InlineData(new string[] { "" }, "7f60ff")]
+ [InlineData(new string[] { "ab", "" }, "7f62616260ff")]
+ [InlineData(new string[] { "ab", "bc", "" }, "7f62616262626360ff")]
+ public static void WriteTextString_IndefiniteLength_SingleValue_HappyPath(string[] chunkInputs, string hexExpectedEncoding)
+ {
+ byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
+ using var writer = new CborWriter();
+ Helpers.WriteChunkedTextString(writer, chunkInputs);
+ AssertHelper.HexEqual(expectedEncoding, writer.ToArray());
+ }
+
[Fact]
public static void WriteTextString_InvalidUnicodeString_ShouldThrowEncoderFallbackException()
{
using var writer = new CborWriter();
Assert.Throws<System.Text.EncoderFallbackException>(() => writer.WriteTextString(invalidUnicodeString));
}
+
+ [Theory]
+ [InlineData(nameof(CborWriter.WriteInt64))]
+ [InlineData(nameof(CborWriter.WriteByteString))]
+ [InlineData(nameof(CborWriter.WriteStartTextStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteStartByteStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteStartArray))]
+ [InlineData(nameof(CborWriter.WriteStartMap))]
+ public static void WriteTextString_IndefiniteLength_NestedWrites_ShouldThrowInvalidOperationException(string opName)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartTextStringIndefiniteLength();
+ Assert.Throws<InvalidOperationException>(() => Helpers.ExecOperation(writer, opName));
+ }
+
+ [Theory]
+ [InlineData(nameof(CborWriter.WriteEndByteStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteEndArray))]
+ [InlineData(nameof(CborWriter.WriteEndMap))]
+ public static void WriteTextString_IndefiniteLength_ImbalancedWrites_ShouldThrowInvalidOperationException(string opName)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartTextStringIndefiniteLength();
+ Assert.Throws<InvalidOperationException>(() => Helpers.ExecOperation(writer, opName));
+ }
+
+ [Theory]
+ [InlineData(nameof(CborWriter.WriteInt64))]
+ [InlineData(nameof(CborWriter.WriteTextString))]
+ [InlineData(nameof(CborWriter.WriteStartTextStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteStartByteStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteStartArray))]
+ [InlineData(nameof(CborWriter.WriteStartMap))]
+ [InlineData(nameof(CborWriter.WriteEndTextStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteEndArray))]
+ [InlineData(nameof(CborWriter.WriteEndMap))]
+ public static void WriteByteString_IndefiteLength_NestedWrites_ShouldThrowInvalidOperationException(string opName)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartByteStringIndefiniteLength();
+ Assert.Throws<InvalidOperationException>(() => Helpers.ExecOperation(writer, opName));
+ }
+
+ [Theory]
+ [InlineData(nameof(CborWriter.WriteEndTextStringIndefiniteLength))]
+ [InlineData(nameof(CborWriter.WriteEndArray))]
+ [InlineData(nameof(CborWriter.WriteEndMap))]
+ public static void WriteByteString_IndefiteLength_ImbalancedWrites_ShouldThrowInvalidOperationException(string opName)
+ {
+ using var writer = new CborWriter();
+ writer.WriteStartByteStringIndefiniteLength();
+ Assert.Throws<InvalidOperationException>(() => Helpers.ExecOperation(writer, opName));
+ }
}
}
/// Represents the Cbor Data item initial byte structure
internal readonly struct CborInitialByte
{
+ public const byte IndefiniteLengthBreakByte = 0xff;
private const byte AdditionalInformationMask = 0b000_11111;
+
public byte InitialByte { get; }
internal CborInitialByte(CborMajorType majorType, CborAdditionalInfo additionalInfo)
public ulong? ReadStartArray()
{
CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Array);
- ulong arrayLength = checked((ulong)ReadUnsignedInteger(header, out int additionalBytes));
- AdvanceBuffer(1 + additionalBytes);
- _remainingDataItems--;
- PushDataItem(CborMajorType.Array, arrayLength);
- return arrayLength;
+ if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength)
+ {
+ AdvanceBuffer(1);
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.Array, null);
+ return null;
+ }
+ else
+ {
+ ulong arrayLength = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes);
+ AdvanceBuffer(1 + additionalBytes);
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.Array, arrayLength);
+ return arrayLength;
+ }
}
public void ReadEndArray()
{
- PopDataItem(expectedType: CborMajorType.Array);
+ if (_remainingDataItems == null)
+ {
+ CborInitialByte value = PeekInitialByte();
+
+ if (value.InitialByte != CborInitialByte.IndefiniteLengthBreakByte)
+ {
+ throw new InvalidOperationException("Not at end of indefinite-length array.");
+ }
+
+ PopDataItem(expectedType: CborMajorType.Array);
+ AdvanceBuffer(1);
+ }
+ else
+ {
+ PopDataItem(expectedType: CborMajorType.Array);
+ }
}
}
}
switch (header.MajorType)
{
case CborMajorType.UnsignedInteger:
- ulong value = ReadUnsignedInteger(header, out int additionalBytes);
+ ulong value = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes);
AdvanceBuffer(1 + additionalBytes);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
return value;
case CborMajorType.NegativeInteger:
switch (header.MajorType)
{
case CborMajorType.UnsignedInteger:
- value = checked((long)ReadUnsignedInteger(header, out additionalBytes));
+ value = checked((long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes));
AdvanceBuffer(1 + additionalBytes);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
return value;
case CborMajorType.NegativeInteger:
- value = checked(-1 - (long)ReadUnsignedInteger(header, out additionalBytes));
+ value = checked(-1 - (long)ReadUnsignedInteger(_buffer.Span, header, out additionalBytes));
AdvanceBuffer(1 + additionalBytes);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
return value;
default:
public ulong ReadCborNegativeIntegerEncoding()
{
CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.NegativeInteger);
- ulong value = ReadUnsignedInteger(header, out int additionalBytes);
+ ulong value = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes);
AdvanceBuffer(1 + additionalBytes);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
return value;
}
// Unsigned integer decoding https://tools.ietf.org/html/rfc7049#section-2.1
- private ulong ReadUnsignedInteger(CborInitialByte header, out int additionalBytes)
+ private static ulong ReadUnsignedInteger(ReadOnlySpan<byte> buffer, CborInitialByte header, out int additionalBytes)
{
- ReadOnlySpan<byte> buffer = _buffer.Span;
-
switch (header.AdditionalInfo)
{
case CborAdditionalInfo x when (x < CborAdditionalInfo.Unsigned8BitIntegerEncoding):
return (ulong)x;
case CborAdditionalInfo.Unsigned8BitIntegerEncoding:
- EnsureBuffer(2);
+ EnsureBuffer(buffer, 2);
additionalBytes = 1;
return buffer[1];
case CborAdditionalInfo.Unsigned16BitIntegerEncoding:
- EnsureBuffer(3);
+ EnsureBuffer(buffer, 3);
additionalBytes = 2;
return BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(1));
case CborAdditionalInfo.Unsigned32BitIntegerEncoding:
- EnsureBuffer(5);
+ EnsureBuffer(buffer, 5);
additionalBytes = 4;
return BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(1));
case CborAdditionalInfo.Unsigned64BitIntegerEncoding:
- EnsureBuffer(9);
+ EnsureBuffer(buffer, 9);
additionalBytes = 8;
return BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(1));
- case CborAdditionalInfo.IndefiniteLength:
- throw new NotImplementedException("indefinite length support");
-
default:
throw new FormatException("initial byte contains invalid integer encoding data");
}
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)
+ if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength)
{
- throw new OverflowException("Read CBOR map field count exceeds supported size.");
+ AdvanceBuffer(1);
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.Map, null);
+ return null;
}
+ else
+ {
+ ulong mapSize = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes);
+
+ if (mapSize > long.MaxValue)
+ {
+ throw new OverflowException("Read CBOR map field count exceeds supported size.");
+ }
- PushDataItem(CborMajorType.Map, 2 * arrayLength);
- return arrayLength;
+ AdvanceBuffer(1 + additionalBytes);
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.Map, 2 * mapSize);
+ return mapSize;
+ }
}
public void ReadEndMap()
{
- PopDataItem(expectedType: CborMajorType.Map);
+ if (_remainingDataItems == null)
+ {
+ CborInitialByte value = PeekInitialByte();
+
+ if (value.InitialByte != CborInitialByte.IndefiniteLengthBreakByte)
+ {
+ throw new InvalidOperationException("Not at end of indefinite-length map.");
+ }
+
+ if (!_isEvenNumberOfDataItemsRead)
+ {
+ throw new FormatException("CBOR Map types require an even number of key/value combinations");
+ }
+
+ PopDataItem(expectedType: CborMajorType.Map);
+ AdvanceBuffer(1);
+ }
+ else
+ {
+ PopDataItem(expectedType: CborMajorType.Map);
+ }
}
}
}
// 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;
+#nullable enable
+using System.Collections.Generic;
+using System.Diagnostics;
namespace System.Security.Cryptography.Encoding.Tests.Cbor
{
public byte[] ReadByteString()
{
CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.ByteString);
- int length = checked((int)ReadUnsignedInteger(header, out int additionalBytes));
+
+ if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength)
+ {
+ return ReadChunkedByteStringConcatenated();
+ }
+
+ int length = checked((int)ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes));
EnsureBuffer(1 + additionalBytes + length);
byte[] result = new byte[length];
_buffer.Slice(1 + additionalBytes, length).CopyTo(result);
AdvanceBuffer(1 + additionalBytes + length);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
return result;
}
public bool TryReadByteString(Span<byte> destination, out int bytesWritten)
{
CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.ByteString);
- int length = checked((int)ReadUnsignedInteger(header, out int additionalBytes));
+
+ if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength)
+ {
+ return TryReadChunkedByteStringConcatenated(destination, out bytesWritten);
+ }
+
+ int length = checked((int)ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes));
EnsureBuffer(1 + additionalBytes + length);
if (length > destination.Length)
_buffer.Span.Slice(1 + additionalBytes, length).CopyTo(destination);
AdvanceBuffer(1 + additionalBytes + length);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
bytesWritten = length;
return true;
public string ReadTextString()
{
CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.TextString);
- int length = checked((int)ReadUnsignedInteger(header, out int additionalBytes));
+
+ if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength)
+ {
+ return ReadChunkedTextStringConcatenated();
+ }
+
+ int length = checked((int)ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes));
EnsureBuffer(1 + additionalBytes + length);
ReadOnlySpan<byte> encodedString = _buffer.Span.Slice(1 + additionalBytes, length);
string result = s_utf8Encoding.GetString(encodedString);
AdvanceBuffer(1 + additionalBytes + length);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
return result;
}
public bool TryReadTextString(Span<char> destination, out int charsWritten)
{
CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.TextString);
- int byteLength = checked((int)ReadUnsignedInteger(header, out int additionalBytes));
+
+ if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength)
+ {
+ return TryReadChunkedTextStringConcatenated(destination, out charsWritten);
+ }
+
+ int byteLength = checked((int)ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes));
EnsureBuffer(1 + additionalBytes + byteLength);
ReadOnlySpan<byte> encodedSlice = _buffer.Span.Slice(1 + additionalBytes, byteLength);
s_utf8Encoding.GetChars(encodedSlice, destination);
AdvanceBuffer(1 + additionalBytes + byteLength);
- _remainingDataItems--;
+ DecrementRemainingItemCount();
charsWritten = charLength;
return true;
}
+
+ public void ReadStartTextStringIndefiniteLength()
+ {
+ CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.TextString);
+
+ if (header.AdditionalInfo != CborAdditionalInfo.IndefiniteLength)
+ {
+ throw new InvalidOperationException("CBOR text string is not of indefinite length.");
+ }
+
+ DecrementRemainingItemCount();
+ AdvanceBuffer(1);
+
+ PushDataItem(CborMajorType.TextString, expectedNestedItems: null);
+ }
+
+ public void ReadEndTextStringIndefiniteLength()
+ {
+ ReadNextIndefiniteLengthBreakByte();
+ PopDataItem(CborMajorType.TextString);
+ AdvanceBuffer(1);
+ }
+
+ public void ReadStartByteStringIndefiniteLength()
+ {
+ CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.ByteString);
+
+ if (header.AdditionalInfo != CborAdditionalInfo.IndefiniteLength)
+ {
+ throw new InvalidOperationException("CBOR text string is not of indefinite length.");
+ }
+
+ DecrementRemainingItemCount();
+ AdvanceBuffer(1);
+
+ PushDataItem(CborMajorType.ByteString, expectedNestedItems: null);
+ }
+
+ public void ReadEndByteStringIndefiniteLength()
+ {
+ ReadNextIndefiniteLengthBreakByte();
+ PopDataItem(CborMajorType.ByteString);
+ AdvanceBuffer(1);
+ }
+
+ private bool TryReadChunkedByteStringConcatenated(Span<byte> destination, out int bytesWritten)
+ {
+ List<(int offset, int length)> ranges = ReadChunkedStringRanges(CborMajorType.ByteString, out int encodingLength, out int concatenatedBufferSize);
+
+ if (concatenatedBufferSize > destination.Length)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ ReadOnlySpan<byte> source = _buffer.Span;
+
+ foreach ((int o, int l) in ranges)
+ {
+ source.Slice(o, l).CopyTo(destination);
+ destination = destination.Slice(l);
+ }
+
+ bytesWritten = concatenatedBufferSize;
+ AdvanceBuffer(encodingLength);
+ DecrementRemainingItemCount();
+ ReturnRangeList(ranges);
+ return true;
+ }
+
+ private bool TryReadChunkedTextStringConcatenated(Span<char> destination, out int charsWritten)
+ {
+ List<(int offset, int length)> ranges = ReadChunkedStringRanges(CborMajorType.TextString, out int encodingLength, out int _);
+ ReadOnlySpan<byte> buffer = _buffer.Span;
+
+ int concatenatedStringSize = 0;
+ foreach ((int o, int l) in ranges)
+ {
+ concatenatedStringSize += s_utf8Encoding.GetCharCount(buffer.Slice(o, l));
+ }
+
+ if (concatenatedStringSize > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ foreach ((int o, int l) in ranges)
+ {
+ s_utf8Encoding.GetChars(buffer.Slice(o, l), destination);
+ destination = destination.Slice(l);
+ }
+
+ charsWritten = concatenatedStringSize;
+ AdvanceBuffer(encodingLength);
+ DecrementRemainingItemCount();
+ ReturnRangeList(ranges);
+ return true;
+ }
+
+ private byte[] ReadChunkedByteStringConcatenated()
+ {
+ List<(int offset, int length)> ranges = ReadChunkedStringRanges(CborMajorType.ByteString, out int encodingLength, out int concatenatedBufferSize);
+ var output = new byte[concatenatedBufferSize];
+
+ ReadOnlySpan<byte> source = _buffer.Span;
+ Span<byte> target = output;
+
+ foreach ((int o, int l) in ranges)
+ {
+ source.Slice(o, l).CopyTo(target);
+ target = target.Slice(l);
+ }
+
+ Debug.Assert(target.IsEmpty);
+ AdvanceBuffer(encodingLength);
+ DecrementRemainingItemCount();
+ ReturnRangeList(ranges);
+ return output;
+ }
+
+ private string ReadChunkedTextStringConcatenated()
+ {
+ List<(int offset, int length)> ranges = ReadChunkedStringRanges(CborMajorType.TextString, out int encodingLength, out int concatenatedBufferSize);
+ ReadOnlySpan<byte> buffer = _buffer.Span;
+ int concatenatedStringSize = 0;
+
+ foreach ((int o, int l) in ranges)
+ {
+ concatenatedStringSize += s_utf8Encoding.GetCharCount(buffer.Slice(o, l));
+ }
+
+ string output = string.Create(concatenatedStringSize, (ranges, _buffer), BuildString);
+
+ AdvanceBuffer(encodingLength);
+ DecrementRemainingItemCount();
+ ReturnRangeList(ranges);
+ return output;
+
+ static void BuildString(Span<char> target, (List<(int offset, int length)> ranges, ReadOnlyMemory<byte> source) input)
+ {
+ ReadOnlySpan<byte> source = input.source.Span;
+
+ foreach ((int o, int l) in input.ranges)
+ {
+ s_utf8Encoding.GetChars(source.Slice(o, l), target);
+ target = target.Slice(l);
+ }
+
+ Debug.Assert(target.IsEmpty);
+ }
+ }
+
+ // reads a buffer starting with an indefinite-length string,
+ // performing validation and returning a list of ranges containing the individual chunk payloads
+ private List<(int offset, int length)> ReadChunkedStringRanges(CborMajorType type, out int encodingLength, out int concatenatedBufferSize)
+ {
+ var ranges = AcquireRangeList();
+ ReadOnlySpan<byte> buffer = _buffer.Span;
+ concatenatedBufferSize = 0;
+
+ int i = 1; // skip the indefinite-length initial byte
+ CborInitialByte nextInitialByte = ReadNextInitialByte(buffer.Slice(i), type);
+
+ while (nextInitialByte.InitialByte != CborInitialByte.IndefiniteLengthBreakByte)
+ {
+ checked
+ {
+ int chunkLength = (int)ReadUnsignedInteger(buffer.Slice(i), nextInitialByte, out int additionalBytes);
+ ranges.Add((i + 1 + additionalBytes, chunkLength));
+ i += 1 + additionalBytes + chunkLength;
+ concatenatedBufferSize += chunkLength;
+ }
+
+ nextInitialByte = ReadNextInitialByte(buffer.Slice(i), type);
+ }
+
+ encodingLength = i + 1; // include the break byte
+ return ranges;
+
+ static CborInitialByte ReadNextInitialByte(ReadOnlySpan<byte> buffer, CborMajorType expectedType)
+ {
+ EnsureBuffer(buffer, 1);
+ var cib = new CborInitialByte(buffer[0]);
+
+ if (cib.InitialByte != CborInitialByte.IndefiniteLengthBreakByte && cib.MajorType != expectedType)
+ {
+ throw new FormatException("Indefinite-length CBOR string containing invalid data item.");
+ }
+
+ return cib;
+ }
+ }
}
}
#nullable enable
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
namespace System.Security.Cryptography.Encoding.Tests.Cbor
{
NegativeInteger,
ByteString,
TextString,
+ StartTextString,
+ StartByteString,
StartArray,
StartMap,
+ EndTextString,
+ EndByteString,
EndArray,
EndMap,
Tag,
Special,
Finished,
+ FormatError,
EndOfData,
}
// with null representing indefinite length data items.
// The root context ony permits one data item to be read.
private ulong? _remainingDataItems = 1;
- private Stack<(CborMajorType type, ulong? remainingDataItems)>? _nestedDataItemStack;
+ private bool _isEvenNumberOfDataItemsRead = true; // required for indefinite-length map writes
+ private Stack<(CborMajorType type, bool isEvenNumberOfDataItemsWritten, ulong? remainingDataItems)>? _nestedDataItemStack;
+
+ // stores a reusable List allocation for keeping ranges in the buffer
+ private List<(int offset, int length)>? _rangeListAllocation = null;
internal CborReader(ReadOnlyMemory<byte> buffer)
{
public CborReaderState Peek()
{
- if (_remainingDataItems is null)
- {
- throw new NotImplementedException("indefinite length collections");
- }
-
if (_remainingDataItems == 0)
{
if (_nestedDataItemStack?.Count > 0)
return CborReaderState.EndOfData;
}
- CborInitialByte initialByte = new CborInitialByte(_buffer.Span[0]);
+ var initialByte = new CborInitialByte(_buffer.Span[0]);
+
+ if (initialByte.InitialByte == CborInitialByte.IndefiniteLengthBreakByte)
+ {
+ if (_remainingDataItems == null)
+ {
+ // stack guaranteed to be populated since root context cannot be indefinite-length
+ Debug.Assert(_nestedDataItemStack != null && _nestedDataItemStack.Count > 0);
+
+ return _nestedDataItemStack.Peek().type switch
+ {
+ CborMajorType.ByteString => CborReaderState.EndByteString,
+ CborMajorType.TextString => CborReaderState.EndTextString,
+ CborMajorType.Array => CborReaderState.EndArray,
+ CborMajorType.Map when !_isEvenNumberOfDataItemsRead => CborReaderState.FormatError,
+ CborMajorType.Map => CborReaderState.EndMap,
+ _ => throw new Exception("CborReader internal error. Invalid CBOR major type pushed to stack."),
+ };
+ }
+ else
+ {
+ return CborReaderState.FormatError;
+ }
+ }
+
+ if (_remainingDataItems == null)
+ {
+ // stack guaranteed to be populated since root context cannot be indefinite-length
+ Debug.Assert(_nestedDataItemStack != null && _nestedDataItemStack.Count > 0);
+
+ CborMajorType parentType = _nestedDataItemStack.Peek().type;
+
+ switch (parentType)
+ {
+ case CborMajorType.ByteString:
+ case CborMajorType.TextString:
+ // indefinite length string contexts can only contain data items of same major type
+ if (initialByte.MajorType != parentType)
+ {
+ return CborReaderState.FormatError;
+ }
+
+ break;
+ }
+ }
return initialByte.MajorType switch
{
CborMajorType.UnsignedInteger => CborReaderState.UnsignedInteger,
CborMajorType.NegativeInteger => CborReaderState.NegativeInteger,
+ CborMajorType.ByteString when initialByte.AdditionalInfo == CborAdditionalInfo.IndefiniteLength => CborReaderState.StartByteString,
CborMajorType.ByteString => CborReaderState.ByteString,
+ CborMajorType.TextString when initialByte.AdditionalInfo == CborAdditionalInfo.IndefiniteLength => CborReaderState.StartTextString,
CborMajorType.TextString => CborReaderState.TextString,
CborMajorType.Array => CborReaderState.StartArray,
CborMajorType.Map => CborReaderState.StartMap,
CborMajorType.Tag => CborReaderState.Tag,
CborMajorType.Special => CborReaderState.Special,
- _ => throw new FormatException("Invalid CBOR major type"),
+ _ => CborReaderState.FormatError,
};
}
throw new FormatException("unexpected end of buffer.");
}
- return new CborInitialByte(_buffer.Span[0]);
+ var result = new CborInitialByte(_buffer.Span[0]);
+
+ // TODO check for tag state
+
+ if (_nestedDataItemStack != null && _nestedDataItemStack.Count > 0)
+ {
+ CborMajorType parentType = _nestedDataItemStack.Peek().type;
+
+ switch (parentType)
+ {
+ // indefinite-length string contexts do not permit nesting
+ case CborMajorType.ByteString:
+ case CborMajorType.TextString:
+ if (result.InitialByte == CborInitialByte.IndefiniteLengthBreakByte ||
+ result.MajorType == parentType &&
+ result.AdditionalInfo != CborAdditionalInfo.IndefiniteLength)
+ {
+ break;
+ }
+
+ throw new FormatException("Indefinite-length CBOR string containing invalid data item.");
+ }
+ }
+
+ return result;
}
private CborInitialByte PeekInitialByte(CborMajorType expectedType)
return result;
}
+ private void ReadNextIndefiniteLengthBreakByte()
+ {
+ CborInitialByte result = PeekInitialByte();
+
+ if (result.InitialByte != CborInitialByte.IndefiniteLengthBreakByte)
+ {
+ throw new InvalidOperationException("Next data item is not indefinite-length break byte.");
+ }
+ }
+
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));
+ _nestedDataItemStack ??= new Stack<(CborMajorType, bool, ulong?)>();
+ _nestedDataItemStack.Push((type, _isEvenNumberOfDataItemsRead, _remainingDataItems));
_remainingDataItems = expectedNestedItems;
+ _isEvenNumberOfDataItemsRead = true;
}
private void PopDataItem(CborMajorType expectedType)
{
- if (_remainingDataItems == null)
- {
- throw new NotImplementedException("Indefinite-length data items");
- }
-
- if (_remainingDataItems > 0)
- {
- throw new InvalidOperationException("Definite-length nested CBOR data item is incomplete.");
- }
-
if (_nestedDataItemStack is null || _nestedDataItemStack.Count == 0)
{
throw new InvalidOperationException("No active CBOR nested data item to pop");
}
- (CborMajorType actualType, ulong? remainingItems) = _nestedDataItemStack.Peek();
+ (CborMajorType actualType, bool isEvenNumberOfDataItemsWritten, ulong? remainingItems) = _nestedDataItemStack.Peek();
if (expectedType != actualType)
{
throw new InvalidOperationException("Unexpected major type in nested CBOR data item.");
}
+ if (_remainingDataItems > 0)
+ {
+ throw new InvalidOperationException("Definite-length nested CBOR data item is incomplete.");
+ }
+
_nestedDataItemStack.Pop();
_remainingDataItems = remainingItems;
+ _isEvenNumberOfDataItemsRead = isEvenNumberOfDataItemsWritten;
+ }
+
+ private void DecrementRemainingItemCount()
+ {
+ _remainingDataItems--;
+ _isEvenNumberOfDataItemsRead = !_isEvenNumberOfDataItemsRead;
}
private void AdvanceBuffer(int length)
throw new FormatException("Unexpected end of buffer.");
}
}
+
+ private static void EnsureBuffer(ReadOnlySpan<byte> buffer, int requiredLength)
+ {
+ if (buffer.Length < requiredLength)
+ {
+ throw new FormatException("Unexpected end of buffer.");
+ }
+ }
+
+ private List<(int offset, int length)> AcquireRangeList()
+ {
+ List<(int offset, int length)>? ranges = Interlocked.Exchange(ref _rangeListAllocation, null);
+
+ if (ranges != null)
+ {
+ ranges.Clear();
+ return ranges;
+ }
+
+ return new List<(int, int)>();
+ }
+
+ private void ReturnRangeList(List<(int offset, int length)> ranges)
+ {
+ _rangeListAllocation = ranges;
+ }
}
}
public void WriteEndArray()
{
+ if (!_remainingDataItems.HasValue)
+ {
+ // indefinite-length map, add break byte
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborInitialByte.IndefiniteLengthBreakByte));
+ }
+
PopDataItem(CborMajorType.Array);
}
+
+ public void WriteStartArrayIndefiniteLength()
+ {
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborMajorType.Array, CborAdditionalInfo.IndefiniteLength));
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.Array, expectedNestedItems: null);
+ }
}
}
// Unsigned integer encoding https://tools.ietf.org/html/rfc7049#section-2.1
private void WriteUnsignedInteger(CborMajorType type, ulong value)
{
- EnsureCanWriteNewDataItem();
-
if (value < 24)
{
EnsureWriteCapacity(1);
- _buffer[_offset++] = new CborInitialByte(type, (CborAdditionalInfo)value).InitialByte;
+ WriteInitialByte(new CborInitialByte(type, (CborAdditionalInfo)value));
}
else if (value <= byte.MaxValue)
{
EnsureWriteCapacity(2);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned8BitIntegerEncoding).InitialByte;
- _buffer[_offset + 1] = (byte)value;
- _offset += 2;
+ WriteInitialByte(new CborInitialByte(type, CborAdditionalInfo.Unsigned8BitIntegerEncoding));
+ _buffer[_offset++] = (byte)value;
}
else if (value <= ushort.MaxValue)
{
EnsureWriteCapacity(3);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned16BitIntegerEncoding).InitialByte;
- BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_offset + 1), (ushort)value);
- _offset += 3;
+ WriteInitialByte(new CborInitialByte(type, CborAdditionalInfo.Unsigned16BitIntegerEncoding));
+ BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_offset), (ushort)value);
+ _offset += 2;
}
else if (value <= uint.MaxValue)
{
EnsureWriteCapacity(5);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned32BitIntegerEncoding).InitialByte;
- BinaryPrimitives.WriteUInt32BigEndian(_buffer.AsSpan(_offset + 1), (uint)value);
- _offset += 5;
+ WriteInitialByte(new CborInitialByte(type, CborAdditionalInfo.Unsigned32BitIntegerEncoding));
+ BinaryPrimitives.WriteUInt32BigEndian(_buffer.AsSpan(_offset), (uint)value);
+ _offset += 4;
}
else
{
EnsureWriteCapacity(9);
- _buffer[_offset] = new CborInitialByte(type, CborAdditionalInfo.Unsigned64BitIntegerEncoding).InitialByte;
- BinaryPrimitives.WriteUInt64BigEndian(_buffer.AsSpan(_offset + 1), value);
- _offset += 9;
+ WriteInitialByte(new CborInitialByte(type, CborAdditionalInfo.Unsigned64BitIntegerEncoding));
+ BinaryPrimitives.WriteUInt64BigEndian(_buffer.AsSpan(_offset), value);
+ _offset += 8;
}
- _remainingDataItems--;
+ DecrementRemainingItemCount();
}
}
}
public void WriteEndMap()
{
+ if (!_isEvenNumberOfDataItemsWritten)
+ {
+ throw new InvalidOperationException("CBOR Map types require an even number of key/value combinations");
+ }
+
+ if (!_remainingDataItems.HasValue)
+ {
+ // indefinite-length map, add break byte
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborInitialByte.IndefiniteLengthBreakByte));
+ }
+
PopDataItem(CborMajorType.Map);
}
+
+ public void WriteStartMapIndefiniteLength()
+ {
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborMajorType.Map, CborAdditionalInfo.IndefiniteLength));
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.Map, expectedNestedItems: null);
+ }
}
}
s_utf8Encoding.GetBytes(value, _buffer.AsSpan(_offset));
_offset += length;
}
+
+ public void WriteStartByteStringIndefiniteLength()
+ {
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborMajorType.ByteString, CborAdditionalInfo.IndefiniteLength));
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.ByteString, expectedNestedItems: null);
+ }
+
+ public void WriteEndByteStringIndefiniteLength()
+ {
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborInitialByte.IndefiniteLengthBreakByte));
+ PopDataItem(CborMajorType.ByteString);
+ }
+
+ public void WriteStartTextStringIndefiniteLength()
+ {
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborMajorType.TextString, CborAdditionalInfo.IndefiniteLength));
+ DecrementRemainingItemCount();
+ PushDataItem(CborMajorType.TextString, expectedNestedItems: null);
+ }
+
+ public void WriteEndTextStringIndefiniteLength()
+ {
+ EnsureWriteCapacity(1);
+ WriteInitialByte(new CborInitialByte(CborInitialByte.IndefiniteLengthBreakByte));
+ PopDataItem(CborMajorType.TextString);
+ }
}
}
// with null representing indefinite length data items.
// The root context ony permits one data item to be written.
private uint? _remainingDataItems = 1;
- private Stack<(CborMajorType type, uint? remainingDataItems)>? _nestedDataItemStack;
+ private bool _isEvenNumberOfDataItemsWritten = true; // required for indefinite-length map writes
+ private Stack<(CborMajorType type, bool isEvenNumberOfDataItemsWritten, uint? remainingDataItems)>? _nestedDataItemStack;
public CborWriter()
{
}
}
- private void EnsureCanWriteNewDataItem()
- {
- if (_remainingDataItems == 0)
- {
- throw new InvalidOperationException("Adding a CBOR data item to the current context exceeds its definite length.");
- }
- }
-
private void PushDataItem(CborMajorType type, uint? expectedNestedItems)
{
- _nestedDataItemStack ??= new Stack<(CborMajorType, uint?)>();
- _nestedDataItemStack.Push((type, _remainingDataItems));
+ _nestedDataItemStack ??= new Stack<(CborMajorType, bool, uint?)>();
+ _nestedDataItemStack.Push((type, _isEvenNumberOfDataItemsWritten, _remainingDataItems));
_remainingDataItems = expectedNestedItems;
+ _isEvenNumberOfDataItemsWritten = true;
}
private void PopDataItem(CborMajorType expectedType)
{
- if (_remainingDataItems == null)
+ if (_nestedDataItemStack is null || _nestedDataItemStack.Count == 0)
{
- throw new NotImplementedException("Indefinite-length data items");
+ throw new InvalidOperationException("No active CBOR nested data item to pop");
+ }
+
+ (CborMajorType actualType, bool isEvenNumberOfDataItemsWritten, uint? remainingItems) = _nestedDataItemStack.Peek();
+
+ if (expectedType != actualType)
+ {
+ throw new InvalidOperationException("Unexpected major type in nested CBOR data item.");
}
if (_remainingDataItems > 0)
throw new InvalidOperationException("Definite-length nested CBOR data item is incomplete.");
}
- if (_nestedDataItemStack is null || _nestedDataItemStack.Count == 0)
+ _nestedDataItemStack.Pop();
+ _remainingDataItems = remainingItems;
+ _isEvenNumberOfDataItemsWritten = isEvenNumberOfDataItemsWritten;
+ }
+
+ private void DecrementRemainingItemCount()
+ {
+ _remainingDataItems--;
+ _isEvenNumberOfDataItemsWritten = !_isEvenNumberOfDataItemsWritten;
+ }
+
+ private void WriteInitialByte(CborInitialByte initialByte)
+ {
+ if (_remainingDataItems == 0)
{
- throw new InvalidOperationException("No active CBOR nested data item to pop");
+ throw new InvalidOperationException("Adding a CBOR data item to the current context exceeds its definite length.");
+ }
+
+ if (_remainingDataItems.HasValue && initialByte.InitialByte == CborInitialByte.IndefiniteLengthBreakByte)
+ {
+ throw new InvalidOperationException("Cannot write CBOR break byte in definite-length contexts");
}
- (CborMajorType actualType, uint? remainingItems) = _nestedDataItemStack.Peek();
+ // TODO check for tag state
- if (expectedType != actualType)
+ if (_nestedDataItemStack != null && _nestedDataItemStack.Count > 0)
{
- throw new InvalidOperationException("Unexpected major type in nested CBOR data item.");
+ CborMajorType parentType = _nestedDataItemStack.Peek().type;
+
+ switch (parentType)
+ {
+ // indefinite-length string contexts do not permit nesting
+ case CborMajorType.ByteString:
+ case CborMajorType.TextString:
+ if (initialByte.InitialByte == CborInitialByte.IndefiniteLengthBreakByte ||
+ initialByte.MajorType == parentType &&
+ initialByte.AdditionalInfo != CborAdditionalInfo.IndefiniteLength)
+ {
+ break;
+ }
+
+ throw new InvalidOperationException("Cannot nest data items in indefinite-length CBOR string contexts.");
+ }
}
- _nestedDataItemStack.Pop();
- _remainingDataItems = remainingItems;
+ _buffer[_offset++] = initialByte.InitialByte;
}
private void CheckDisposed()