-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CBOR] Add a CborReader.SkipValue() method (#34477)
* Add a CborReader.SkipValue() method * revert reader state format error changes * refactor skip from recursive to imperative * use non-allocating skips for string data items * add a test for extremely nested values * remove blank line * consolidate SkipString() methods
- Loading branch information
1 parent
c64c31a
commit 223b843
Showing
5 changed files
with
289 additions
and
2 deletions.
There are no files selected for viewing
142 changes: 142 additions & 0 deletions
142
...aries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.SkipValue.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// 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 System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using Test.Cryptography; | ||
using Xunit; | ||
|
||
namespace System.Security.Cryptography.Encoding.Tests.Cbor | ||
{ | ||
public partial class CborReaderTests | ||
{ | ||
[Theory] | ||
[MemberData(nameof(SampleValues))] | ||
public static void SkipValue_RootValue_HappyPath(string hexEncoding) | ||
{ | ||
byte[] encoding = hexEncoding.HexToByteArray(); | ||
var reader = new CborReader(encoding); | ||
|
||
reader.SkipValue(); | ||
Assert.Equal(CborReaderState.Finished, reader.Peek()); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(SampleValues))] | ||
public static void SkipValue_NestedValue_HappyPath(string hexEncoding) | ||
{ | ||
byte[] encoding = $"8301{hexEncoding}03".HexToByteArray(); | ||
var reader = new CborReader(encoding); | ||
|
||
reader.ReadStartArray(); | ||
reader.ReadInt64(); | ||
reader.SkipValue(); | ||
reader.ReadInt64(); | ||
reader.ReadEndArray(); | ||
Assert.Equal(CborReaderState.Finished, reader.Peek()); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(SampleValues))] | ||
public static void SkipValue_TaggedValue_HappyPath(string hexEncoding) | ||
{ | ||
byte[] encoding = $"c2{hexEncoding}".HexToByteArray(); | ||
var reader = new CborReader(encoding); | ||
|
||
reader.ReadTag(); | ||
reader.SkipValue(); | ||
Assert.Equal(CborReaderState.Finished, reader.Peek()); | ||
} | ||
|
||
[Fact] | ||
public static void SkipValue_NotAtValue_ShouldThrowInvalidOperationException() | ||
{ | ||
byte[] encoding = "80".HexToByteArray(); | ||
var reader = new CborReader(encoding); | ||
|
||
reader.ReadStartArray(); | ||
Assert.Throws<InvalidOperationException>(() => reader.SkipValue()); | ||
} | ||
|
||
[Theory] | ||
[InlineData("")] | ||
[InlineData("ff")] | ||
[InlineData("c2")] | ||
[InlineData("bf01ff")] | ||
[InlineData("7f01ff")] | ||
public static void SkipValue_InvalidFormat_ShouldThrowFormatException(string hexEncoding) | ||
{ | ||
byte[] encoding = hexEncoding.HexToByteArray(); | ||
var reader = new CborReader(encoding); | ||
|
||
Assert.Throws<FormatException>(() => reader.SkipValue()); | ||
} | ||
|
||
[Theory] | ||
[InlineData("61ff")] | ||
[InlineData("62f090")] | ||
public static void SkipValue_InvalidUtf8_ShouldDecoderFallbackException(string hexEncoding) | ||
{ | ||
byte[] encoding = hexEncoding.HexToByteArray(); | ||
var reader = new CborReader(encoding); | ||
|
||
Assert.Throws<DecoderFallbackException>(() => reader.SkipValue()); | ||
} | ||
|
||
[Theory] | ||
[InlineData(50_000)] | ||
public static void SkipValue_ExtremelyNestedValues_ShouldNotStackOverflow(int depth) | ||
{ | ||
// Construct a valid CBOR encoding with extreme nesting: | ||
// defines a tower of `depth` nested singleton arrays, | ||
// with the innermost array containing zero. | ||
byte[] encoding = new byte[depth + 1]; | ||
encoding.AsSpan(0, depth).Fill(0x81); // array of length 1 | ||
encoding[depth] = 0; | ||
|
||
var reader = new CborReader(encoding); | ||
|
||
reader.SkipValue(); | ||
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 }); | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.SkipValue.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// 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. | ||
|
||
namespace System.Security.Cryptography.Encoding.Tests.Cbor | ||
{ | ||
internal partial class CborReader | ||
{ | ||
public void SkipValue() | ||
{ | ||
int depth = 0; | ||
|
||
do | ||
{ | ||
SkipNextNode(ref depth); | ||
} while (depth > 0); | ||
} | ||
|
||
private void SkipNextNode(ref int depth) | ||
{ | ||
CborReaderState state; | ||
|
||
// peek, skipping any tags we might encounter | ||
while ((state = Peek()) == CborReaderState.Tag) | ||
{ | ||
ReadTag(); | ||
} | ||
|
||
switch (state) | ||
{ | ||
case CborReaderState.UnsignedInteger: | ||
ReadUInt64(); | ||
break; | ||
|
||
case CborReaderState.NegativeInteger: | ||
ReadCborNegativeIntegerEncoding(); | ||
break; | ||
|
||
case CborReaderState.ByteString: | ||
SkipString(type: CborMajorType.ByteString); | ||
break; | ||
|
||
case CborReaderState.TextString: | ||
SkipString(type: CborMajorType.TextString); | ||
break; | ||
|
||
case CborReaderState.StartByteString: | ||
ReadStartByteStringIndefiniteLength(); | ||
depth++; | ||
break; | ||
|
||
case CborReaderState.EndByteString: | ||
ValidatePop(state, depth); | ||
ReadEndByteStringIndefiniteLength(); | ||
depth--; | ||
break; | ||
|
||
case CborReaderState.StartTextString: | ||
ReadStartTextStringIndefiniteLength(); | ||
depth++; | ||
break; | ||
|
||
case CborReaderState.EndTextString: | ||
ValidatePop(state, depth); | ||
ReadEndTextStringIndefiniteLength(); | ||
depth--; | ||
break; | ||
|
||
case CborReaderState.StartArray: | ||
ReadStartArray(); | ||
depth++; | ||
break; | ||
|
||
case CborReaderState.EndArray: | ||
ValidatePop(state, depth); | ||
ReadEndArray(); | ||
depth--; | ||
break; | ||
|
||
case CborReaderState.StartMap: | ||
ReadStartMap(); | ||
depth++; | ||
break; | ||
|
||
case CborReaderState.EndMap: | ||
ValidatePop(state, depth); | ||
ReadEndMap(); | ||
depth--; | ||
break; | ||
|
||
case CborReaderState.HalfPrecisionFloat: | ||
case CborReaderState.SinglePrecisionFloat: | ||
case CborReaderState.DoublePrecisionFloat: | ||
ReadDouble(); | ||
break; | ||
|
||
case CborReaderState.Null: | ||
case CborReaderState.Boolean: | ||
case CborReaderState.SpecialValue: | ||
ReadSpecialValue(); | ||
break; | ||
|
||
case CborReaderState.EndOfData: | ||
throw new FormatException("Unexpected end of buffer."); | ||
case CborReaderState.FormatError: | ||
throw new FormatException("Invalid CBOR format."); | ||
|
||
default: | ||
throw new InvalidOperationException($"Unexpected CBOR reader state {state}."); | ||
} | ||
|
||
// guards against cases where the caller attempts to skip when reader is not positioned at the start of a value | ||
static void ValidatePop(CborReaderState state, int depth) | ||
{ | ||
if (depth == 0) | ||
{ | ||
throw new InvalidOperationException($"Reader state {state} is not at start of a data item."); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters