From 623447732c99b131502b0aa8b690f193254cd911 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 23 Sep 2023 08:29:30 -0400 Subject: [PATCH 1/2] Implement InitialCapacity on CborWriter. --- .../ref/System.Formats.Cbor.cs | 4 ++- .../System/Formats/Cbor/Writer/CborWriter.cs | 34 +++++++++++++++++-- .../tests/Writer/CborWriterTests.cs | 32 ++++++++++++++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/ref/System.Formats.Cbor.cs b/src/libraries/System.Formats.Cbor/ref/System.Formats.Cbor.cs index 0ac1ee2bb234e2..f5c698a1d17e26 100644 --- a/src/libraries/System.Formats.Cbor/ref/System.Formats.Cbor.cs +++ b/src/libraries/System.Formats.Cbor/ref/System.Formats.Cbor.cs @@ -124,7 +124,9 @@ public enum CborTag : ulong } public partial class CborWriter { - public CborWriter(System.Formats.Cbor.CborConformanceMode conformanceMode = System.Formats.Cbor.CborConformanceMode.Strict, bool convertIndefiniteLengthEncodings = false, bool allowMultipleRootLevelValues = false) { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public CborWriter(System.Formats.Cbor.CborConformanceMode conformanceMode, bool convertIndefiniteLengthEncodings, bool allowMultipleRootLevelValues) { } + public CborWriter(System.Formats.Cbor.CborConformanceMode conformanceMode = System.Formats.Cbor.CborConformanceMode.Strict, bool convertIndefiniteLengthEncodings = false, bool allowMultipleRootLevelValues = false, int initialCapacity = -1) { } public bool AllowMultipleRootLevelValues { get { throw null; } } public int BytesWritten { get { throw null; } } public System.Formats.Cbor.CborConformanceMode ConformanceMode { get { throw null; } } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs index 3f9b25978f23d2..06a3ed599f0a8b 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; @@ -11,9 +12,10 @@ namespace System.Formats.Cbor /// A writer for Concise Binary Object Representation (CBOR) encoded data. public partial class CborWriter { + private const int DefaultCapacitySentinel = -1; private static readonly ArrayPool s_bufferPool = ArrayPool.Create(); - private byte[] _buffer = null!; + private byte[] _buffer; private int _offset; private Stack? _nestedDataItems; @@ -60,7 +62,28 @@ public partial class CborWriter /// to enable automatically converting indefinite-length encodings into definite-length equivalents and allow use of indefinite-length write APIs in conformance modes that otherwise do not permit it; otherwise, . /// to allow multiple root-level values to be written by the writer; otherwise, . /// is not a defined . - public CborWriter(CborConformanceMode conformanceMode = CborConformanceMode.Strict, bool convertIndefiniteLengthEncodings = false, bool allowMultipleRootLevelValues = false) + [EditorBrowsable(EditorBrowsableState.Never)] + public CborWriter(CborConformanceMode conformanceMode, bool convertIndefiniteLengthEncodings, bool allowMultipleRootLevelValues) + : this(conformanceMode, convertIndefiniteLengthEncodings, allowMultipleRootLevelValues, DefaultCapacitySentinel) + { + } + + /// Initializes a new instance of class using the specified configuration. + /// One of the enumeration values that specifies the guidance on the conformance checks performed on the encoded data. + /// Defaults to conformance mode. + /// to enable automatically converting indefinite-length encodings into definite-length equivalents and allow use of indefinite-length write APIs in conformance modes that otherwise do not permit it; otherwise, . + /// to allow multiple root-level values to be written by the writer; otherwise, . + /// The initial capacity of the underlying buffer. The value -1 can be used to use the default capacity. + /// + /// is not a defined . + /// -or- + /// is not zero, positive, or the default value indicator -1. + /// + public CborWriter( + CborConformanceMode conformanceMode = CborConformanceMode.Strict, + bool convertIndefiniteLengthEncodings = false, + bool allowMultipleRootLevelValues = false, + int initialCapacity = DefaultCapacitySentinel) { CborConformanceModeHelpers.Validate(conformanceMode); @@ -68,6 +91,13 @@ public CborWriter(CborConformanceMode conformanceMode = CborConformanceMode.Stri ConvertIndefiniteLengthEncodings = convertIndefiniteLengthEncodings; AllowMultipleRootLevelValues = allowMultipleRootLevelValues; _definiteLength = allowMultipleRootLevelValues ? null : (int?)1; + + _buffer = initialCapacity switch + { + DefaultCapacitySentinel or 0 => null!, + < -1 => throw new ArgumentOutOfRangeException(nameof(initialCapacity)), + _ => new byte[initialCapacity] + }; } /// Resets the writer to have no data, without releasing resources. diff --git a/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs b/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs index 28faa0ecddc4cd..b614fd901dc18d 100644 --- a/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs +++ b/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Security.Cryptography; using Test.Cryptography; using Xunit; @@ -332,7 +333,36 @@ public static void WriteEncodedValue_ValidPayloadWithTrailingBytes_ShouldThrowAr [InlineData((CborConformanceMode)(-1))] public static void InvalidConformanceMode_ShouldThrowArgumentOutOfRangeException(CborConformanceMode mode) { - Assert.Throws(() => new CborWriter(conformanceMode: mode)); + Assert.Throws("conformanceMode", () => new CborWriter(conformanceMode: mode)); + } + + [Theory] + [InlineData(-2)] + [InlineData(int.MinValue)] + public static void InvalidInitialCapacity_ShouldThrowArgumentOutOfRangeException(int capacity) + { + Assert.Throws("initialCapacity", () => new CborWriter(initialCapacity: capacity)); + } + + [Theory] + [InlineData(-1, null)] + [InlineData(0, null)] + [InlineData(1, 1)] + [InlineData(1023, 1023)] + public static void InitialCapacity_ShouldSetInitialBuffer(int capacity, int? expectedBufferLength) + { + CborWriter writer = new CborWriter(initialCapacity: capacity); + byte[]? buffer = (byte[]?)typeof(CborWriter).GetField("_buffer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(writer); + + if (expectedBufferLength is null) + { + Assert.Null(buffer); + } + else + { + Assert.NotNull(buffer); + Assert.Equal(expectedBufferLength.Value, buffer.Length); + } } public static IEnumerable EncodedValueInputs => CborReaderTests.SampleCborValues.Select(x => new [] { x }); From 738b230ac37ee60353e243e11d7b316b39140136 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 24 Sep 2023 09:57:43 -0400 Subject: [PATCH 2/2] Code review feedback. Changed the underlying buffer to never be null. --- .../System/Formats/Cbor/Writer/CborWriter.cs | 6 ++-- .../tests/Writer/CborWriterTests.cs | 31 ++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs index 06a3ed599f0a8b..3cbad0bd761d93 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.cs @@ -94,9 +94,9 @@ public CborWriter( _buffer = initialCapacity switch { - DefaultCapacitySentinel or 0 => null!, + DefaultCapacitySentinel or 0 => Array.Empty(), < -1 => throw new ArgumentOutOfRangeException(nameof(initialCapacity)), - _ => new byte[initialCapacity] + _ => new byte[initialCapacity], }; } @@ -235,7 +235,7 @@ private void EnsureWriteCapacity(int pendingCount) throw new OverflowException(); } - if (_buffer is null || _buffer.Length - _offset < pendingCount) + if (_buffer.Length - _offset < pendingCount) { const int BlockSize = 1024; int blocks = checked(_offset + pendingCount + (BlockSize - 1)) / BlockSize; diff --git a/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs b/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs index b614fd901dc18d..baf9496266c75f 100644 --- a/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs +++ b/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.cs @@ -345,24 +345,31 @@ public static void InvalidInitialCapacity_ShouldThrowArgumentOutOfRangeException } [Theory] - [InlineData(-1, null)] - [InlineData(0, null)] + [InlineData(-1, 0)] + [InlineData(0, 0)] [InlineData(1, 1)] [InlineData(1023, 1023)] - public static void InitialCapacity_ShouldSetInitialBuffer(int capacity, int? expectedBufferLength) + public static void InitialCapacity_ShouldSetInitialBuffer(int capacity, int expectedBufferLength) { CborWriter writer = new CborWriter(initialCapacity: capacity); byte[]? buffer = (byte[]?)typeof(CborWriter).GetField("_buffer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(writer); - if (expectedBufferLength is null) - { - Assert.Null(buffer); - } - else - { - Assert.NotNull(buffer); - Assert.Equal(expectedBufferLength.Value, buffer.Length); - } + Assert.NotNull(buffer); + Assert.Equal(expectedBufferLength, buffer.Length); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(1)] + public static void Encode_InitialCapacity_Grows(int capacity) + { + CborWriter writer = new CborWriter(initialCapacity: capacity); + writer.WriteByteString((ReadOnlySpan)new byte[] { 1, 2, 3, 4, 5, 6 }); + byte[] encoded = writer.Encode(); + + ReadOnlySpan expected = new byte[] { (2 << 5) | 6, 1, 2, 3, 4, 5, 6 }; + AssertExtensions.SequenceEqual(expected, encoded); } public static IEnumerable EncodedValueInputs => CborReaderTests.SampleCborValues.Select(x => new [] { x });