Skip to content

Commit

Permalink
[CBOR] Implement Write/ReadEncodedValue() methods (#34650)
Browse files Browse the repository at this point in the history
* Implement CBOR Write/ReadEncodedValue() methods

* Wrap utf8 encoding and decoding exceptions in ArgumentException and FormatException respectively

* Address feedback

* address feedback

* add contextual tests for WriteEncodedValue
  • Loading branch information
eiriktsarpalis authored Apr 8, 2020
1 parent 10828e1 commit cb2308b
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,101 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex
reader.ReadEndMap();
}
}

public static string[] SampleCborValues =>
new[]
{
// numeric values
"01",
"37",
"3818",
"1818",
"190100",
"390100",
"1a000f4240",
"3affffffff",
// byte strings
"40",
"4401020304",
"5f41ab40ff",
// text strings
"60",
"6161",
"6449455446",
"7f62616260ff",
// Arrays
"80",
"840120604107",
"8301820203820405",
"9f182aff",
// Maps
"a0",
"a201020304",
"a1a1617802182a",
"bf01020304ff",
// tagged values
"c202",
"d82076687474703a2f2f7777772e6578616d706c652e636f6d",
// special values
"f4",
"f6",
"fa47c35000",
};

public static string[] InvalidCborValues =>
new[]
{
"",
// numeric types with missing bytes
"18",
"19ff",
"1affffff",
"1bffffffffffffff",
"38",
"39ff",
"3affffff",
"3bffffffffffffff",
// definite-length strings with missing bytes
"41",
"4201",
"61",
"6261",
// invalid utf8 strings
"61ff",
"62f090",
// indefinite-length strings with missing break byte
"5f41ab40",
"7f62616260",
// definite-length arrays with missing elements
"81",
"8201",
// definite-length maps with missing fields
"a1",
"a20102",
// maps with odd number of elements
"a101",
"a2010203",
"bf01ff",
// indefinite-length collections with missing break byte
"9f",
"9f01",
"bf",
"bf0102",
// tags missing data
"d8",
"d9ff",
"daffffff",
"daffffffffff",
// valid tag not followed by value
"c2",
// floats missing data
"f9ff",
"faffffff",
"fbffffffffffffff",
// special value missing data
"f8",
// invalid special value
"f81f",
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
public partial class CborReaderTests
{
[Theory]
[MemberData(nameof(SampleValues))]
[MemberData(nameof(SkipTestInputs))]
public static void SkipValue_RootValue_HappyPath(string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
Expand All @@ -26,7 +26,7 @@ public static void SkipValue_RootValue_HappyPath(string hexEncoding)
}

[Theory]
[MemberData(nameof(SampleValues))]
[MemberData(nameof(SkipTestInputs))]
public static void SkipValue_NestedValue_HappyPath(string hexEncoding)
{
byte[] encoding = $"8301{hexEncoding}03".HexToByteArray();
Expand All @@ -41,7 +41,7 @@ public static void SkipValue_NestedValue_HappyPath(string hexEncoding)
}

[Theory]
[MemberData(nameof(SampleValues))]
[MemberData(nameof(SkipTestInputs))]
public static void SkipValue_TaggedValue_HappyPath(string hexEncoding)
{
byte[] encoding = $"c2{hexEncoding}".HexToByteArray();
Expand All @@ -63,11 +63,7 @@ public static void SkipValue_NotAtValue_ShouldThrowInvalidOperationException()
}

[Theory]
[InlineData("")]
[InlineData("ff")]
[InlineData("c2")]
[InlineData("bf01ff")]
[InlineData("7f01ff")]
[MemberData(nameof(SkipTestInvalidCborInputs))]
public static void SkipValue_InvalidFormat_ShouldThrowFormatException(string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
Expand All @@ -79,12 +75,14 @@ public static void SkipValue_InvalidFormat_ShouldThrowFormatException(string hex
[Theory]
[InlineData("61ff")]
[InlineData("62f090")]
public static void SkipValue_InvalidUtf8_ShouldDecoderFallbackException(string hexEncoding)
public static void SkipValue_InvalidUtf8_ShouldThrowFormatException(string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

Assert.Throws<DecoderFallbackException>(() => reader.SkipValue());
FormatException exn = Assert.Throws<FormatException>(() => reader.SkipValue());
Assert.NotNull(exn.InnerException);
Assert.IsType<DecoderFallbackException>(exn.InnerException);
}

[Theory]
Expand All @@ -104,39 +102,7 @@ public static void SkipValue_ExtremelyNestedValues_ShouldNotStackOverflow(int de
Assert.Equal(CborReaderState.Finished, reader.Peek());
}

public static IEnumerable<object[]> SampleValues =>
new string[]
{
// numeric values
"01",
"1a000f4240",
"3affffffff",
// byte strings
"40",
"4401020304",
"5f41ab40ff",
// text strings
"60",
"6161",
"6449455446",
"7f62616260ff",
// Arrays
"80",
"840120604107",
"8301820203820405",
"9f182aff",
// Maps
"a0",
"a201020304",
"a1a1617802182a",
"bf01020304ff",
// tagged values
"c202",
"d82076687474703a2f2f7777772e6578616d706c652e636f6d",
// special values
"f4",
"f6",
"fa47c35000",
}.Select(x => new object[] { x });
public static IEnumerable<object[]> SkipTestInputs => SampleCborValues.Select(x => new [] { x });
public static IEnumerable<object[]> SkipTestInvalidCborInputs => InvalidCborValues.Select(x => new[] { x });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -486,23 +486,27 @@ public static void ReadTextString_StringLengthTooLarge_ShouldThrowOverflowExcept
[Theory]
[InlineData("61ff")]
[InlineData("62f090")]
public static void ReadTextString_InvalidUnicode_ShouldThrowDecoderFallbackException(string hexEncoding)
public static void ReadTextString_InvalidUnicode_ShouldThrowFormatException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
var reader = new CborReader(data);
Assert.Throws<System.Text.DecoderFallbackException>(() => reader.ReadTextString());
FormatException exn = Assert.Throws<FormatException>(() => reader.ReadTextString());
Assert.NotNull(exn.InnerException);
Assert.IsType<System.Text.DecoderFallbackException>(exn.InnerException);
}

[Theory]
[InlineData("61ff")]
[InlineData("62f090")]
public static void TryReadTextString_InvalidUnicode_ShouldThrowDecoderFallbackException(string hexEncoding)
public static void TryReadTextString_InvalidUnicode_ShouldThrowFormatException(string hexEncoding)
{
byte[] data = hexEncoding.HexToByteArray();
char[] buffer = new char[32];
var reader = new CborReader(data);

Assert.Throws<System.Text.DecoderFallbackException>(() => reader.TryReadTextString(buffer, out int _));
FormatException exn = Assert.Throws<FormatException>(() => reader.TryReadTextString(buffer, out int _));
Assert.NotNull(exn.InnerException);
Assert.IsType<System.Text.DecoderFallbackException>(exn.InnerException);
}

[Fact]
Expand Down Expand Up @@ -614,15 +618,15 @@ public static void ReadTextString_IndefiniteLengthConcatenated_ContainingInvalid
}

[Fact]
public static void ReadTextString_IndefiniteLengthConcatenated_InvalidUtf8Chunks_ShouldThrowDecoderFallbackException()
public static void ReadTextString_IndefiniteLengthConcatenated_InvalidUtf8Chunks_ShouldThrowFormatException()
{
// 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());
Assert.Throws<FormatException>(() => reader.ReadTextString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Test.Cryptography;
using Xunit;

Expand Down Expand Up @@ -74,5 +76,44 @@ public static void CborReader_ReadingTwoPrimitiveValues_ShouldThrowInvalidOperat
Assert.Equal(CborReaderState.Finished, reader.Peek());
Assert.Throws<InvalidOperationException>(() => reader.ReadInt64());
}

[Theory]
[MemberData(nameof(EncodedValueInputs))]
public static void ReadEncodedValue_RootValue_HappyPath(string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

byte[] encodedValue = reader.ReadEncodedValue().ToArray();
Assert.Equal(hexEncoding, encodedValue.ByteArrayToHex().ToLower());
}

[Theory]
[MemberData(nameof(EncodedValueInputs))]
public static void ReadEncodedValue_NestedValue_HappyPath(string hexEncoding)
{
byte[] encoding = $"8301{hexEncoding}60".HexToByteArray();

var reader = new CborReader(encoding);

reader.ReadStartArray();
reader.ReadInt64();
byte[] encodedValue = reader.ReadEncodedValue().ToArray();

Assert.Equal(hexEncoding, encodedValue.ByteArrayToHex().ToLower());
}

[Theory]
[MemberData(nameof(EncodedValueInvalidInputs))]
public static void ReadEncodedValue_InvalidCbor_ShouldThrowFormatException(string hexEncoding)
{
byte[] encoding = hexEncoding.HexToByteArray();
var reader = new CborReader(encoding);

Assert.Throws<FormatException>(() => reader.ReadEncodedValue());
}

public static IEnumerable<object[]> EncodedValueInputs => CborReaderTests.SampleCborValues.Select(x => new[] { x });
public static IEnumerable<object[]> EncodedValueInvalidInputs => CborReaderTests.InvalidCborValues.Select(x => new[] { x });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#nullable enable

using System.Linq;
using Test.Cryptography;
using Xunit;

namespace System.Security.Cryptography.Encoding.Tests.Cbor
Expand All @@ -15,13 +16,22 @@ internal static class Helpers
{
public const string MapPrefixIdentifier = "_map";

public const string EncodedPrefixIdentifier = "_encodedValue";

// 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 bool IsEncodedValueRepresentation(object[] values)
{
return values.Length == 2 &&
values[0] is string s && s == EncodedPrefixIdentifier &&
values[1] is string;
}

public static void WriteValue(CborWriter writer, object value, bool useDefiniteLengthCollections = true)
{
switch (value)
Expand All @@ -38,6 +48,11 @@ public static void WriteValue(CborWriter writer, object value, bool useDefiniteL
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 when IsEncodedValueRepresentation(nested):
byte[] encodedValue = ((string)nested[1]).HexToByteArray();
writer.WriteEncodedValue(encodedValue);
break;

case object[] nested: WriteArray(writer, nested, useDefiniteLengthCollections); break;
default: throw new ArgumentException($"Unrecognized argument type {value.GetType()}");
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ public static void WriteTextString_IndefiniteLength_SingleValue_HappyPath(string
}

[Fact]
public static void WriteTextString_InvalidUnicodeString_ShouldThrowEncoderFallbackException()
public static void WriteTextString_InvalidUnicodeString_ShouldThrowArgumentException()
{
// NB Xunit's InlineDataAttribute will corrupt string literals containing invalid unicode
string invalidUnicodeString = "\ud800";
using var writer = new CborWriter();
Assert.Throws<System.Text.EncoderFallbackException>(() => writer.WriteTextString(invalidUnicodeString));
ArgumentException exn = Assert.Throws<ArgumentException>(() => writer.WriteTextString(invalidUnicodeString));
Assert.NotNull(exn.InnerException);
Assert.IsType<System.Text.EncoderFallbackException>(exn.InnerException);
}

[Theory]
Expand Down
Loading

0 comments on commit cb2308b

Please sign in to comment.