Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[CBOR] Implement indefinite length writer and reader support #33831

Merged
merged 12 commits into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#nullable enable
using System;
using System.Linq;
using System.Text;
using Test.Cryptography;
using Xunit;

Expand Down Expand Up @@ -89,26 +90,96 @@ public static void TryReadTextString_SingleValue_HappyPath(string expectedValue,
[InlineData(new string[] { "" }, "5f40ff")]
[InlineData(new string[] { "ab", "" }, "5f41ab40ff")]
[InlineData(new string[] { "ab", "bc", "" }, "5f41ab41bc40ff")]
public static void ReadByteString_IndefiteLength_SingleValue_HappyPath(string[] expectedHexValues, string hexEncoding)
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());
}

[Theory]
[InlineData(new string[] { }, "7fff")]
[InlineData(new string[] { "" }, "7f60ff")]
[InlineData(new string[] { "ab", "" }, "7f62616260ff")]
[InlineData(new string[] { "ab", "bc", "" }, "7f62616262626360ff")]
public static void ReadTextString_IndefiteLength_SingleValue_HappyPath(string[] expectedValues, string hexEncoding)
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());
}

[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")]
Expand Down Expand Up @@ -142,6 +213,38 @@ public static void TryReadTextString_BufferTooSmall_ShouldReturnFalse(string act
Assert.All(buffer, (b => Assert.Equal(0, '\0')));
}

[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 - 1];
bool result = reader.TryReadByteString(buffer, out int bytesWritten);

Assert.False(result);
Assert.Equal(0, bytesWritten);
Assert.All(buffer, (b => Assert.Equal(0, b)));
}

[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 - 1];
bool result = reader.TryReadTextString(buffer, out int charsWritten);

Assert.False(result);
Assert.Equal(0, charsWritten);
Assert.All(buffer, (b => Assert.Equal(0, '\0')));
}
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved

[Theory]
[InlineData("00")] // 0
[InlineData("20")] // -1
Expand Down Expand Up @@ -371,27 +474,61 @@ public static void ReadByteString_EmptyBuffer_ShouldThrowFormatException()
}

[Fact]
public static void ReadByteString_IndefiteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException()
public static void ReadByteString_IndefiniteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException()
{
string hexEncoding = "5f4001ff";
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
reader.ReadStartByteString();
reader.ReadByteString();
Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); // peek should not fail
Assert.Throws<FormatException>(() => reader.ReadInt64()); // throws FormatException even if it's the right major type we're trying to read

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_IndefiteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException()
public static void ReadTextString_IndefiniteLength_ContainingInvalidMajorTypes_ShouldThrowFormatException()
{
string hexEncoding = "7f6001ff";
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
reader.ReadStartTextString();
reader.ReadTextString();
Assert.Equal(CborReaderState.UnsignedInteger, reader.Peek()); // peek should not fail
Assert.Throws<FormatException>(() => reader.ReadInt64()); // throws FormatException even if it's the right major type we're trying to read

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_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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal partial class CborReader
}
else
{
ulong arrayLength = ReadUnsignedInteger(header, out int additionalBytes);
ulong arrayLength = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes);
AdvanceBuffer(1 + additionalBytes);
DecrementRemainingItemCount();
PushDataItem(CborMajorType.Array, arrayLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ulong ReadUInt64()
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);
DecrementRemainingItemCount();
return value;
Expand All @@ -40,13 +40,13 @@ public long ReadInt64()
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);
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);
DecrementRemainingItemCount();
return value;
Expand All @@ -61,40 +61,38 @@ public long ReadInt64()
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);
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):
additionalBytes = 0;
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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal partial class CborReader
}
else
{
ulong mapSize = ReadUnsignedInteger(header, out int additionalBytes);
ulong mapSize = ReadUnsignedInteger(_buffer.Span, header, out int additionalBytes);

if (mapSize > long.MaxValue)
{
Expand Down
Loading