| File: System\Formats\Cbor\Reader\CborReader.Map.cs | Web Access |
| Project: src\runtime\src\libraries\System.Formats.Cbor\src\System.Formats.Cbor.csproj (System.Formats.Cbor) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; namespace System.Formats.Cbor { public partial class CborReader { private KeyEncodingComparer? _keyEncodingComparer; private Stack<HashSet<(int Offset, int Length)>>? _pooledKeyEncodingRangeAllocations; /// <summary>Reads the next data item as the start of a map (major type 5).</summary> /// <returns>The number of key-value pairs in a definite-length map, or <see langword="null" /> if the map is indefinite-length.</returns> /// <exception cref="InvalidOperationException">The next data item does not have the correct major type.</exception> /// <exception cref="CborContentException"><para>The next value has an invalid CBOR encoding.</para> /// <para>-or-</para> /// <para>There was an unexpected end of CBOR encoding data.</para> /// <para>-or-</para> /// <para>The next value uses a CBOR encoding that is not valid under the current conformance mode.</para></exception> /// <remarks> /// Map contents are consumed as if they were arrays twice the length of the map's declared size. /// For instance, a map of size 1 containing a key of type <see cref="int" /> with a value of type <see cref="string" /> /// must be consumed by successive calls to <see cref="ReadInt32" /> and <see cref="ReadTextString" />. /// It is up to the caller to keep track of whether the next value is a key or a value. /// Fundamentally, this is a technical restriction stemming from the fact that CBOR allows keys of arbitrary type, /// for instance a map can contain keys that are maps themselves. /// </remarks> public int? ReadStartMap() { int? length; CborInitialByte header = PeekInitialByte(expectedType: CborMajorType.Map); EnsureMaxDepthNotExceeded(); if (header.AdditionalInfo == CborAdditionalInfo.IndefiniteLength) { if (_isConformanceModeCheckEnabled && CborConformanceModeHelpers.RequiresDefiniteLengthItems(ConformanceMode)) { throw new CborContentException(SR.Format(SR.Cbor_ConformanceMode_RequiresDefiniteLengthItems, ConformanceMode)); } AdvanceBuffer(1); PushDataItem(CborMajorType.Map, null); length = null; } else { ReadOnlySpan<byte> buffer = GetRemainingBytes(); int mapSize = DecodeDefiniteLength(header, buffer, out int bytesRead); if (2 * (ulong)mapSize > (ulong)(buffer.Length - bytesRead)) { throw new CborContentException(SR.Cbor_Reader_DefiniteLengthExceedsBufferSize); } AdvanceBuffer(bytesRead); PushDataItem(CborMajorType.Map, 2 * mapSize); length = (int)mapSize; } _currentKeyOffset = _offset; return length; } /// <summary>Reads the end of a map (major type 5).</summary> /// <exception cref="InvalidOperationException"><para>The current context is not a map.</para> /// <para>-or-</para> /// <para>The reader is not at the end of the map.</para></exception> /// <exception cref="CborContentException"><para>The next value has an invalid CBOR encoding.</para> /// <para>-or-</para> /// <para>There was an unexpected end of CBOR encoding data.</para> /// <para>-or-</para> /// <para>The next value uses a CBOR encoding that is not valid under the current conformance mode.</para></exception> public void ReadEndMap() { if (_definiteLength is null) { ValidateNextByteIsBreakByte(); if (_itemsRead % 2 != 0) { throw new CborContentException(SR.Cbor_Reader_InvalidCbor_KeyMissingValue); } PopDataItem(expectedType: CborMajorType.Map); AdvanceDataItemCounters(); AdvanceBuffer(1); } else { PopDataItem(expectedType: CborMajorType.Map); AdvanceDataItemCounters(); } } // // Map decoding conformance // // conformance book-keeping after a key data item has been read private void HandleMapKeyRead() { Debug.Assert(_currentKeyOffset != null && _itemsRead % 2 == 0); (int Offset, int Length) currentKeyRange = (_currentKeyOffset.Value, _offset - _currentKeyOffset.Value); if (_isConformanceModeCheckEnabled) { if (CborConformanceModeHelpers.RequiresSortedKeys(ConformanceMode)) { ValidateSortedKeyEncoding(currentKeyRange); } else if (CborConformanceModeHelpers.RequiresUniqueKeys(ConformanceMode)) { // NB uniquess is validated separately in conformance modes requiring sorted keys ValidateKeyUniqueness(currentKeyRange); } } } // conformance book-keeping after a value data item has been read private void HandleMapValueRead() { Debug.Assert(_currentKeyOffset != null && _itemsRead % 2 != 0); _currentKeyOffset = _offset; } private void ValidateSortedKeyEncoding((int Offset, int Length) currentKeyEncodingRange) { Debug.Assert(_currentKeyOffset != null); if (_previousKeyEncodingRange != null) { (int Offset, int Length) previousKeyEncodingRange = _previousKeyEncodingRange.Value; ReadOnlySpan<byte> buffer = _data.Span; ReadOnlySpan<byte> previousKeyEncoding = buffer.Slice(previousKeyEncodingRange.Offset, previousKeyEncodingRange.Length); ReadOnlySpan<byte> currentKeyEncoding = buffer.Slice(currentKeyEncodingRange.Offset, currentKeyEncodingRange.Length); int cmp = CborConformanceModeHelpers.CompareKeyEncodings(previousKeyEncoding, currentKeyEncoding, ConformanceMode); if (cmp > 0) { ResetBuffer(currentKeyEncodingRange.Offset); throw new CborContentException(SR.Format(SR.Cbor_ConformanceMode_KeysNotInSortedOrder, ConformanceMode)); } else if (cmp == 0 && CborConformanceModeHelpers.RequiresUniqueKeys(ConformanceMode)) { ResetBuffer(currentKeyEncodingRange.Offset); throw new CborContentException(SR.Format(SR.Cbor_ConformanceMode_ContainsDuplicateKeys, ConformanceMode)); } } _previousKeyEncodingRange = currentKeyEncodingRange; } private void ValidateKeyUniqueness((int Offset, int Length) currentKeyEncodingRange) { Debug.Assert(_currentKeyOffset != null); HashSet<(int Offset, int Length)> keyEncodingRanges = GetKeyEncodingRanges(); if (!keyEncodingRanges.Add(currentKeyEncodingRange)) { ResetBuffer(currentKeyEncodingRange.Offset); throw new CborContentException(SR.Format(SR.Cbor_ConformanceMode_ContainsDuplicateKeys, ConformanceMode)); } } private HashSet<(int Offset, int Length)> GetKeyEncodingRanges() { if (_keyEncodingRanges != null) { return _keyEncodingRanges; } if (_pooledKeyEncodingRangeAllocations != null && _pooledKeyEncodingRangeAllocations.TryPop(out HashSet<(int Offset, int Length)>? result)) { result.Clear(); return _keyEncodingRanges = result; } _keyEncodingComparer ??= new KeyEncodingComparer(this); return _keyEncodingRanges = new HashSet<(int Offset, int Length)>(_keyEncodingComparer); } private void ReturnKeyEncodingRangeAllocation(HashSet<(int Offset, int Length)>? allocation) { if (allocation != null) { _pooledKeyEncodingRangeAllocations ??= new Stack<HashSet<(int Offset, int Length)>>(); _pooledKeyEncodingRangeAllocations.Push(allocation); } } // Comparing buffer slices up to their binary content private sealed class KeyEncodingComparer : IEqualityComparer<(int Offset, int Length)> { private readonly CborReader _reader; public KeyEncodingComparer(CborReader reader) { _reader = reader; } private ReadOnlySpan<byte> GetKeyEncoding((int Offset, int Length) range) { return _reader._data.Span.Slice(range.Offset, range.Length); } public int GetHashCode((int Offset, int Length) value) { return CborConformanceModeHelpers.GetKeyEncodingHashCode(GetKeyEncoding(value)); } public bool Equals((int Offset, int Length) x, (int Offset, int Length) y) { return CborConformanceModeHelpers.AreEqualKeyEncodings(GetKeyEncoding(x), GetKeyEncoding(y)); } } } }