}
[Fact]
- public static void CborReader_ReadingTwoPrimitiveValues_ShouldThrowInvalidOperationException()
+ public static void Read_EmptyBuffer_ShouldThrowFormatException()
+ {
+ var reader = new CborReader(ReadOnlyMemory<byte>.Empty);
+ Assert.Throws<FormatException>(() => reader.ReadInt64());
+ }
+
+ [Fact]
+ public static void Read_BeyondEndOfFirstValue_ShouldThrowInvalidOperationException()
+ {
+ var reader = new CborReader("01".HexToByteArray());
+ reader.ReadInt64();
+ Assert.Equal(CborReaderState.Finished, reader.PeekState());
+ Assert.Throws<InvalidOperationException>(() => reader.ReadInt64());
+ }
+
+ [Fact]
+ public static void CborReader_ReadingTwoRootLevelValues_ShouldThrowInvalidOperationException()
{
ReadOnlyMemory<byte> buffer = new byte[] { 0, 0 };
var reader = new CborReader(buffer);
reader.ReadInt64();
- Assert.Equal(CborReaderState.Finished, reader.PeekState());
int bytesRemaining = reader.BytesRemaining;
+ Assert.Equal(CborReaderState.FinishedWithTrailingBytes, reader.PeekState());
Assert.Throws<InvalidOperationException>(() => reader.ReadInt64());
Assert.Equal(bytesRemaining, reader.BytesRemaining);
}
[Theory]
+ [InlineData(1, 2, "0101")]
+ [InlineData(10, 10, "0a0a0a0a0a0a0a0a0a0a")]
+ [InlineData(new object[] { 1, 2 }, 3, "820102820102820102")]
+ public static void CborReader_MultipleRootValuesAllowed_ReadingMultipleValues_HappyPath(object expectedValue, int repetitions, string hexEncoding)
+ {
+ var reader = new CborReader(hexEncoding.HexToByteArray(), allowMultipleRootLevelValues: true);
+
+ for (int i = 0; i < repetitions; i++)
+ {
+ Helpers.VerifyValue(reader, expectedValue);
+ }
+
+ Assert.Equal(CborReaderState.Finished, reader.PeekState());
+ }
+
+ [Fact]
+ public static void CborReader_MultipleRootValuesAllowed_ReadingBeyondEndOfBuffer_ShouldThrowInvalidOperationException()
+ {
+ string hexEncoding = "810102";
+ var reader = new CborReader(hexEncoding.HexToByteArray(), allowMultipleRootLevelValues: true);
+
+ Assert.Equal(CborReaderState.StartArray, reader.PeekState());
+ reader.ReadStartArray();
+ reader.ReadInt32();
+ reader.ReadEndArray();
+
+ Assert.Equal(CborReaderState.UnsignedInteger, reader.PeekState());
+ reader.ReadInt32();
+
+ Assert.Equal(CborReaderState.Finished, reader.PeekState());
+ Assert.Throws<InvalidOperationException>(() => reader.ReadInt32());
+ }
+
+ [Theory]
[MemberData(nameof(EncodedValueInputs))]
public static void ReadEncodedValue_RootValue_HappyPath(string hexEncoding)
{
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Threading;
namespace System.Security.Cryptography.Encoding.Tests.Cbor
{
DoublePrecisionFloat,
SpecialValue,
Finished,
+ FinishedWithTrailingBytes,
EndOfData,
FormatError,
}
private ReadOnlyMemory<byte> _buffer;
private int _bytesRead = 0;
- // remaining number of data items in current cbor context
- // with null representing indefinite length data items.
- // The root context ony permits one data item to be read.
- private uint? _remainingDataItems = 1;
private Stack<StackFrame>? _nestedDataItems;
+ private int? _remainingDataItems; // remaining data items to read if context is definite-length collection
private int _frameOffset = 0; // buffer offset particular to the current data item context
private bool _isTagContext = false; // true if reader is expecting a tagged value
// keeps a cached copy of the reader state; 'Unknown' denotes uncomputed state
private CborReaderState _cachedState = CborReaderState.Unknown;
- internal CborReader(ReadOnlyMemory<byte> buffer, CborConformanceLevel conformanceLevel = CborConformanceLevel.Lax)
+ internal CborReader(ReadOnlyMemory<byte> buffer, CborConformanceLevel conformanceLevel = CborConformanceLevel.Lax, bool allowMultipleRootLevelValues = false)
{
CborConformanceLevelHelpers.Validate(conformanceLevel);
_originalBuffer = buffer;
_buffer = buffer;
ConformanceLevel = conformanceLevel;
+ AllowMultipleRootLevelValues = allowMultipleRootLevelValues;
+ _remainingDataItems = allowMultipleRootLevelValues ? null : (int?)1;
}
public CborConformanceLevel ConformanceLevel { get; }
+ public bool AllowMultipleRootLevelValues { get; }
public int BytesRead => _bytesRead;
public int BytesRemaining => _buffer.Length;
{
if (_nestedDataItems?.Count > 0)
{
- return _nestedDataItems.Peek().MajorType switch
+ // is at the end of a definite-length collection
+ switch (_nestedDataItems.Peek().MajorType)
{
- CborMajorType.Array => CborReaderState.EndArray,
- CborMajorType.Map => CborReaderState.EndMap,
- _ => throw new Exception("CborReader internal error. Invalid CBOR major type pushed to stack."),
+ case CborMajorType.Array: return CborReaderState.EndArray;
+ case CborMajorType.Map: return CborReaderState.EndMap;
+ default:
+ Debug.Fail("CborReader internal error. Invalid CBOR major type pushed to stack.");
+ throw new Exception("CborReader internal error. Invalid CBOR major type pushed to stack.");
};
}
else
{
- return CborReaderState.Finished;
+ // is at the end of the root value
+ return _buffer.IsEmpty ? CborReaderState.Finished : CborReaderState.FinishedWithTrailingBytes;
}
}
if (_buffer.IsEmpty)
{
+ if (_remainingDataItems is null && (_nestedDataItems?.Count ?? 0) == 0)
+ {
+ // is at the end of a well-defined sequence of root-level values
+ return CborReaderState.Finished;
+ }
+
return CborReaderState.EndOfData;
}
return CborReaderState.FormatError;
}
- if (_remainingDataItems == null)
+ if (_remainingDataItems is null)
{
// stack guaranteed to be populated since root context cannot be indefinite-length
Debug.Assert(_nestedDataItems != null && _nestedDataItems.Count > 0);
- return _nestedDataItems.Peek().MajorType switch
+ switch (_nestedDataItems.Peek().MajorType)
{
- CborMajorType.ByteString => CborReaderState.EndByteString,
- CborMajorType.TextString => CborReaderState.EndTextString,
- CborMajorType.Array => CborReaderState.EndArray,
- CborMajorType.Map when !_curentItemIsKey => CborReaderState.FormatError,
- CborMajorType.Map => CborReaderState.EndMap,
- _ => throw new Exception("CborReader internal error. Invalid CBOR major type pushed to stack."),
+ case CborMajorType.ByteString: return CborReaderState.EndByteString;
+ case CborMajorType.TextString: return CborReaderState.EndTextString;
+ case CborMajorType.Array: return CborReaderState.EndArray;
+ case CborMajorType.Map when !_curentItemIsKey: return CborReaderState.FormatError;
+ case CborMajorType.Map: return CborReaderState.EndMap;
+ default:
+ Debug.Fail("CborReader internal error. Invalid CBOR major type pushed to stack.");
+ throw new Exception("CborReader internal error. Invalid CBOR major type pushed to stack.");
};
}
else
}
}
- if (_remainingDataItems == null)
+ if (_remainingDataItems is null && _nestedDataItems?.Count > 0)
{
- // stack guaranteed to be populated since root context cannot be indefinite-length
- Debug.Assert(_nestedDataItems != null && _nestedDataItems.Count > 0);
-
CborMajorType parentType = _nestedDataItems.Peek().MajorType;
switch (parentType)
}
}
- return initialByte.MajorType switch
+ switch (initialByte.MajorType)
{
- 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.Simple => MapSpecialValueTagToReaderState(initialByte.AdditionalInfo),
- _ => throw new Exception("CborReader internal error. Invalid major type."),
+ case CborMajorType.UnsignedInteger: return CborReaderState.UnsignedInteger;
+ case CborMajorType.NegativeInteger: return CborReaderState.NegativeInteger;
+ case CborMajorType.ByteString:
+ return (initialByte.AdditionalInfo == CborAdditionalInfo.IndefiniteLength) ?
+ CborReaderState.StartByteString :
+ CborReaderState.ByteString;
+
+ case CborMajorType.TextString:
+ return (initialByte.AdditionalInfo == CborAdditionalInfo.IndefiniteLength) ?
+ CborReaderState.StartTextString :
+ CborReaderState.TextString;
+
+ case CborMajorType.Array: return CborReaderState.StartArray;
+ case CborMajorType.Map: return CborReaderState.StartMap;
+ case CborMajorType.Tag: return CborReaderState.Tag;
+ case CborMajorType.Simple: return MapSpecialValueTagToReaderState(initialByte.AdditionalInfo);
+ default:
+ Debug.Fail("CborReader internal error. Invalid major type.");
+ throw new Exception("CborReader internal error. Invalid major type.");
};
static CborReaderState MapSpecialValueTagToReaderState (CborAdditionalInfo value)
if (_buffer.IsEmpty)
{
- throw new FormatException("unexpected end of buffer.");
+ if (_remainingDataItems is null && _bytesRead > 0 && (_nestedDataItems?.Count ?? 0) == 0)
+ {
+ throw new InvalidOperationException("No remaining root-level CBOR data items in the buffer.");
+ }
+
+ throw new FormatException("Unexpected end of buffer.");
}
var result = new CborInitialByte(_buffer.Span[0]);
}
}
- private void PushDataItem(CborMajorType type, uint? expectedNestedItems)
+ private void PushDataItem(CborMajorType type, int? expectedNestedItems)
{
var frame = new StackFrame(
type: type,
_nestedDataItems ??= new Stack<StackFrame>();
_nestedDataItems.Push(frame);
- _remainingDataItems = checked((uint?)expectedNestedItems);
+ _remainingDataItems = expectedNestedItems;
_frameOffset = _bytesRead;
_isTagContext = false;
_currentKeyOffset = (type == CborMajorType.Map) ? (int?)_bytesRead : null;
private readonly struct StackFrame
{
- public StackFrame(CborMajorType type, int frameOffset, uint? remainingDataItems,
+ public StackFrame(CborMajorType type, int frameOffset, int? remainingDataItems,
int? currentKeyOffset, bool currentItemIsKey,
(int Offset, int Length)? previousKeyRange, HashSet<(int Offset, int Length)>? previousKeyRanges)
{
public CborMajorType MajorType { get; }
public int FrameOffset { get; }
- public uint? RemainingDataItems { get; }
+ public int? RemainingDataItems { get; }
public int? CurrentKeyOffset { get; }
public bool CurrentItemIsKey { get; }
// reader is within the original context in which the checkpoint was created
private readonly struct Checkpoint
{
- public Checkpoint(int bytesRead, int stackDepth, int frameOffset, uint? remainingDataItems,
+ public Checkpoint(int bytesRead, int stackDepth, int frameOffset, int? remainingDataItems,
int? currentKeyOffset, bool currentItemIsKey, (int Offset, int Length)? previousKeyRange,
HashSet<(int Offset, int Length)>? previousKeyRanges)
{
public int BytesRead { get; }
public int StackDepth { get; }
public int FrameOffset { get; }
- public uint? RemainingDataItems { get; }
+ public int? RemainingDataItems { get; }
public int? CurrentKeyOffset { get; }
public bool CurrentItemIsKey { get; }