diff --git a/src/Tmds.DBus.Protocol/Polyfill/Nerdbank.Streams.Sequence.cs b/src/Tmds.DBus.Protocol/Polyfill/Nerdbank.Streams.Sequence.cs new file mode 100644 index 00000000..a305cd73 --- /dev/null +++ b/src/Tmds.DBus.Protocol/Polyfill/Nerdbank.Streams.Sequence.cs @@ -0,0 +1,532 @@ +// Copyright (c) Andrew Arnott. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Nerdbank.Streams +{ + using System; + using System.Buffers; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using Microsoft; + + /// + /// Manages a sequence of elements, readily castable as a . + /// + /// The type of element stored by the sequence. + /// + /// Instance members are not thread-safe. + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + internal class Sequence : IBufferWriter, IDisposable + { + private const int MaximumAutoGrowSize = 32 * 1024; + + private static readonly int DefaultLengthFromArrayPool = 1 + (4095 / Unsafe.SizeOf()); + + private static readonly ReadOnlySequence Empty = new ReadOnlySequence(SequenceSegment.Empty, 0, SequenceSegment.Empty, 0); + + private readonly Stack segmentPool = new Stack(); + + private readonly MemoryPool? memoryPool; + + private readonly ArrayPool? arrayPool; + + private SequenceSegment? first; + + private SequenceSegment? last; + + /// + /// Initializes a new instance of the class + /// that uses a private for recycling arrays. + /// + public Sequence() + : this(ArrayPool.Create()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The pool to use for recycling backing arrays. + public Sequence(MemoryPool memoryPool) + { + this.memoryPool = memoryPool ?? throw new ArgumentNullException(nameof(memoryPool)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The pool to use for recycling backing arrays. + public Sequence(ArrayPool arrayPool) + { + this.arrayPool = arrayPool ?? throw new ArgumentNullException(nameof(arrayPool)); + } + + /// + /// Gets or sets the minimum length for any array allocated as a segment in the sequence. + /// Any non-positive value allows the pool to determine the length of the array. + /// + /// The default value is 0. + /// + /// + /// Each time or is called, + /// previously allocated memory is used if it is large enough to satisfy the length demand. + /// If new memory must be allocated, the argument to one of these methods typically dictate + /// the length of array to allocate. When the caller uses very small values (just enough for its immediate need) + /// but the high level scenario can predict that a large amount of memory will be ultimately required, + /// it can be advisable to set this property to a value such that just a few larger arrays are allocated + /// instead of many small ones. + /// + /// + /// The in use may itself have a minimum array length as well, + /// in which case the higher of the two minimums dictate the minimum array size that will be allocated. + /// + /// + /// If is , this value may be automatically increased as the length of a sequence grows. + /// + /// + public int MinimumSpanLength { get; set; } = 0; + + /// + /// Gets or sets a value indicating whether the should be + /// intelligently increased as the length of the sequence grows. + /// + /// + /// This can help prevent long sequences made up of many very small arrays. + /// + public bool AutoIncreaseMinimumSpanLength { get; set; } = true; + + /// + /// Gets this sequence expressed as a . + /// + /// A read only sequence representing the data in this object. + public ReadOnlySequence AsReadOnlySequence => this; + + /// + /// Gets the length of the sequence. + /// + public long Length => this.AsReadOnlySequence.Length; + + /// + /// Gets the value to display in a debugger datatip. + /// + private string DebuggerDisplay => $"Length: {this.AsReadOnlySequence.Length}"; + + /// + /// Expresses this sequence as a . + /// + /// The sequence to convert. + public static implicit operator ReadOnlySequence(Sequence sequence) + { + return sequence.first is { } first && sequence.last is { } last + ? new ReadOnlySequence(first, first.Start, last, last!.End) + : Empty; + } + + /// + /// Removes all elements from the sequence from its beginning to the specified position, + /// considering that data to have been fully processed. + /// + /// + /// The position of the first element that has not yet been processed. + /// This is typically after reading all elements from that instance. + /// + public void AdvanceTo(SequencePosition position) + { + var firstSegment = (SequenceSegment?)position.GetObject(); + if (firstSegment == null) + { + // Emulate PipeReader behavior which is to just return for default(SequencePosition) + return; + } + + if (ReferenceEquals(firstSegment, SequenceSegment.Empty) && this.Length == 0) + { + // We were called with our own empty buffer segment. + return; + } + + int firstIndex = position.GetInteger(); + + // Before making any mutations, confirm that the block specified belongs to this sequence. + Sequence.SequenceSegment? current = this.first; + while (current != firstSegment && current != null) + { + current = current.Next; + } + + if (current == null) + throw new ArgumentException("Position does not represent a valid position in this sequence.", + nameof(position)); + + // Also confirm that the position is not a prior position in the block. + if (firstIndex < current.Start) + throw new ArgumentException("Position must not be earlier than current position.", nameof(position)); + + // Now repeat the loop, performing the mutations. + current = this.first; + while (current != firstSegment) + { + current = this.RecycleAndGetNext(current!); + } + + firstSegment.AdvanceTo(firstIndex); + + this.first = firstSegment.Length == 0 ? this.RecycleAndGetNext(firstSegment) : firstSegment; + + if (this.first == null) + { + this.last = null; + } + } + + /// + /// Advances the sequence to include the specified number of elements initialized into memory + /// returned by a prior call to . + /// + /// The number of elements written into memory. + public void Advance(int count) + { + SequenceSegment? last = this.last; + if(last==null) + throw new InvalidOperationException("Cannot advance before acquiring memory."); + last.Advance(count); + this.ConsiderMinimumSizeIncrease(); + } + + /// + /// Gets writable memory that can be initialized and added to the sequence via a subsequent call to . + /// + /// The size of the memory required, or 0 to just get a convenient (non-empty) buffer. + /// The requested memory. + public Memory GetMemory(int sizeHint) => this.GetSegment(sizeHint).RemainingMemory; + + /// + /// Gets writable memory that can be initialized and added to the sequence via a subsequent call to . + /// + /// The size of the memory required, or 0 to just get a convenient (non-empty) buffer. + /// The requested memory. + public Span GetSpan(int sizeHint) => this.GetSegment(sizeHint).RemainingSpan; + + /// + /// Adds an existing memory location to this sequence without copying. + /// + /// The memory to add. + /// + /// This *may* leave significant slack space in a previously allocated block if calls to + /// follow calls to or . + /// + public void Append(ReadOnlyMemory memory) + { + if (memory.Length > 0) + { + Sequence.SequenceSegment? segment = this.segmentPool.Count > 0 ? this.segmentPool.Pop() : new SequenceSegment(); + segment.AssignForeign(memory); + this.Append(segment); + } + } + + /// + /// Clears the entire sequence, recycles associated memory into pools, + /// and resets this instance for reuse. + /// This invalidates any previously produced by this instance. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public void Dispose() => this.Reset(); + + /// + /// Clears the entire sequence and recycles associated memory into pools. + /// This invalidates any previously produced by this instance. + /// + public void Reset() + { + Sequence.SequenceSegment? current = this.first; + while (current != null) + { + current = this.RecycleAndGetNext(current); + } + + this.first = this.last = null; + } + + private SequenceSegment GetSegment(int sizeHint) + { + if (sizeHint < 0) + throw new ArgumentOutOfRangeException(nameof(sizeHint)); + int? minBufferSize = null; + if (sizeHint == 0) + { + if (this.last == null || this.last.WritableBytes == 0) + { + // We're going to need more memory. Take whatever size the pool wants to give us. + minBufferSize = -1; + } + } + else + { + if (this.last == null || this.last.WritableBytes < sizeHint) + { + minBufferSize = Math.Max(this.MinimumSpanLength, sizeHint); + } + } + + if (minBufferSize.HasValue) + { + Sequence.SequenceSegment? segment = this.segmentPool.Count > 0 ? this.segmentPool.Pop() : new SequenceSegment(); + if (this.arrayPool != null) + { + segment.Assign(this.arrayPool.Rent(minBufferSize.Value == -1 ? DefaultLengthFromArrayPool : minBufferSize.Value)); + } + else + { + segment.Assign(this.memoryPool!.Rent(minBufferSize.Value)); + } + + this.Append(segment); + } + + return this.last!; + } + + private void Append(SequenceSegment segment) + { + if (this.last == null) + { + this.first = this.last = segment; + } + else + { + if (this.last.Length > 0) + { + // Add a new block. + this.last.SetNext(segment); + } + else + { + // The last block is completely unused. Replace it instead of appending to it. + Sequence.SequenceSegment? current = this.first; + if (this.first != this.last) + { + while (current!.Next != this.last) + { + current = current.Next; + } + } + else + { + this.first = segment; + } + + current!.SetNext(segment); + this.RecycleAndGetNext(this.last); + } + + this.last = segment; + } + } + + private SequenceSegment? RecycleAndGetNext(SequenceSegment segment) + { + Sequence.SequenceSegment? recycledSegment = segment; + Sequence.SequenceSegment? nextSegment = segment.Next; + recycledSegment.ResetMemory(this.arrayPool); + this.segmentPool.Push(recycledSegment); + return nextSegment; + } + + private void ConsiderMinimumSizeIncrease() + { + if (this.AutoIncreaseMinimumSpanLength && this.MinimumSpanLength < MaximumAutoGrowSize) + { + int autoSize = Math.Min(MaximumAutoGrowSize, (int)Math.Min(int.MaxValue, this.Length / 2)); + if (this.MinimumSpanLength < autoSize) + { + this.MinimumSpanLength = autoSize; + } + } + } + + private class SequenceSegment : ReadOnlySequenceSegment + { + internal static readonly SequenceSegment Empty = new SequenceSegment(); + + /// + /// A value indicating whether the element may contain references (and thus must be cleared). + /// + private static readonly bool MayContainReferences = !typeof(T).GetTypeInfo().IsPrimitive; + +#pragma warning disable SA1011 // Closing square brackets should be spaced correctly + /// + /// Gets the backing array, when using an instead of a . + /// + private T[]? array; +#pragma warning restore SA1011 // Closing square brackets should be spaced correctly + + /// + /// Gets the position within where the data starts. + /// + /// This may be nonzero as a result of calling . + internal int Start { get; private set; } + + /// + /// Gets the position within where the data ends. + /// + internal int End { get; private set; } + + /// + /// Gets the tail of memory that has not yet been committed. + /// + internal Memory RemainingMemory => this.AvailableMemory.Slice(this.End); + + /// + /// Gets the tail of memory that has not yet been committed. + /// + internal Span RemainingSpan => this.AvailableMemory.Span.Slice(this.End); + + /// + /// Gets the tracker for the underlying array for this segment, which can be used to recycle the array when we're disposed of. + /// Will be if using an array pool, in which case the memory is held by . + /// + internal IMemoryOwner? MemoryOwner { get; private set; } + + /// + /// Gets the full memory owned by the . + /// + internal Memory AvailableMemory => this.array ?? this.MemoryOwner?.Memory ?? default; + + /// + /// Gets the number of elements that are committed in this segment. + /// + internal int Length => this.End - this.Start; + + /// + /// Gets the amount of writable bytes in this segment. + /// It is the amount of bytes between and . + /// + internal int WritableBytes => this.AvailableMemory.Length - this.End; + + /// + /// Gets or sets the next segment in the singly linked list of segments. + /// + internal new SequenceSegment? Next + { + get => (SequenceSegment?)base.Next; + set => base.Next = value; + } + + /// + /// Gets a value indicating whether this segment refers to memory that came from outside and that we cannot write to nor recycle. + /// + internal bool IsForeignMemory => this.array == null && this.MemoryOwner == null; + + /// + /// Assigns this (recyclable) segment a new area in memory. + /// + /// The memory and a means to recycle it. + internal void Assign(IMemoryOwner memoryOwner) + { + this.MemoryOwner = memoryOwner; + this.Memory = memoryOwner.Memory; + } + + /// + /// Assigns this (recyclable) segment a new area in memory. + /// + /// An array drawn from an . + internal void Assign(T[] array) + { + this.array = array; + this.Memory = array; + } + + /// + /// Assigns this (recyclable) segment a new area in memory. + /// + /// A memory block obtained from outside, that we do not own and should not recycle. + internal void AssignForeign(ReadOnlyMemory memory) + { + this.Memory = memory; + this.End = memory.Length; + } + + /// + /// Clears all fields in preparation to recycle this instance. + /// + internal void ResetMemory(ArrayPool? arrayPool) + { + this.ClearReferences(this.Start, this.End - this.Start); + this.Memory = default; + this.Next = null; + this.RunningIndex = 0; + this.Start = 0; + this.End = 0; + if (this.array != null) + { + arrayPool!.Return(this.array); + this.array = null; + } + else + { + this.MemoryOwner?.Dispose(); + this.MemoryOwner = null; + } + } + + /// + /// Adds a new segment after this one. + /// + /// The next segment in the linked list. + internal void SetNext(SequenceSegment segment) + { + this.Next = segment; + segment.RunningIndex = this.RunningIndex + this.Start + this.Length; + + // Trim any slack on this segment. + if (!this.IsForeignMemory) + { + // When setting Memory, we start with index 0 instead of this.Start because + // the first segment has an explicit index set anyway, + // and we don't want to double-count it here. + this.Memory = this.AvailableMemory.Slice(0, this.Start + this.Length); + } + } + + /// + /// Commits more elements as written in this segment. + /// + /// The number of elements written. + internal void Advance(int count) + { + if (!(count >= 0 && this.End + count <= this.Memory.Length)) + throw new ArgumentOutOfRangeException(nameof(count)); + + this.End += count; + } + + /// + /// Removes some elements from the start of this segment. + /// + /// The number of elements to ignore from the start of the underlying array. + internal void AdvanceTo(int offset) + { + Debug.Assert(offset >= this.Start, "Trying to rewind."); + this.ClearReferences(this.Start, offset - this.Start); + this.Start = offset; + } + + private void ClearReferences(int startIndex, int length) + { + // Clear the array to allow the objects to be GC'd. + // Reference types need to be cleared. Value types can be structs with reference type members too, so clear everything. + if (MayContainReferences) + { + this.AvailableMemory.Span.Slice(startIndex, length).Clear(); + } + } + } + } +} diff --git a/src/Tmds.DBus.Protocol/Polyfill/SequenceReader.cs b/src/Tmds.DBus.Protocol/Polyfill/SequenceReader.cs new file mode 100644 index 00000000..16da4a4b --- /dev/null +++ b/src/Tmds.DBus.Protocol/Polyfill/SequenceReader.cs @@ -0,0 +1,417 @@ +// // Copied from https://github.com/dotnet/runtime/raw/cf5b231fcbea483df3b081939b422adfb6fd486a/src/libraries/System.Memory/src/System/Buffers/SequenceReader.cs +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +#if NETSTANDARD2_0 + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + /// + /// Provides methods for reading binary and text data out of a with a focus on performance and minimal or zero heap allocations. + /// + /// The type of element stored by the . + internal ref partial struct SequenceReader where T : unmanaged, IEquatable + { + private SequencePosition _currentPosition; + private SequencePosition _nextPosition; + private bool _moreData; + private readonly long _length; + + /// + /// Create a over the given . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SequenceReader(ReadOnlySequence sequence) + { + CurrentSpanIndex = 0; + Consumed = 0; + Sequence = sequence; + _currentPosition = sequence.Start; + _length = -1; + + var first = sequence.First.Span; + _nextPosition = sequence.GetPosition(first.Length); + CurrentSpan = first; + + _moreData = first.Length > 0; + + if (!_moreData && !sequence.IsSingleSegment) + { + _moreData = true; + GetNextSpan(); + } + } + + /// + /// True when there is no more data in the . + /// + public readonly bool End => !_moreData; + + /// + /// The underlying for the reader. + /// + public readonly ReadOnlySequence Sequence { get; } + + /// + /// The current position in the . + /// + public readonly SequencePosition Position + => Sequence.GetPosition(CurrentSpanIndex, _currentPosition); + + /// + /// The current segment in the as a span. + /// + public ReadOnlySpan CurrentSpan { readonly get; private set; } + + /// + /// The index in the . + /// + public int CurrentSpanIndex { readonly get; private set; } + + /// + /// The unread portion of the . + /// + public readonly ReadOnlySpan UnreadSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => CurrentSpan.Slice(CurrentSpanIndex); + } + + /// + /// The total number of 's processed by the reader. + /// + public long Consumed { readonly get; private set; } + + /// + /// Remaining 's in the reader's . + /// + public readonly long Remaining => Length - Consumed; + + /// + /// Count of in the reader's . + /// + public readonly long Length + { + get + { + if (_length < 0) + { + // Cast-away readonly to initialize lazy field + Unsafe.AsRef(_length) = Sequence.Length; + } + return _length; + } + } + + /// + /// Peeks at the next value without advancing the reader. + /// + /// The next value or default if at the end. + /// False if at the end of the reader. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryPeek(out T value) + { + if (_moreData) + { + value = CurrentSpan[CurrentSpanIndex]; + return true; + } + else + { + value = default; + return false; + } + } + + /// + /// Read the next value and advance the reader. + /// + /// The next value or default if at the end. + /// False if at the end of the reader. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryRead(out T value) + { + if (End) + { + value = default; + return false; + } + + value = CurrentSpan[CurrentSpanIndex]; + CurrentSpanIndex++; + Consumed++; + + if (CurrentSpanIndex >= CurrentSpan.Length) + { + GetNextSpan(); + } + + return true; + } + + /// + /// Move the reader back the specified number of items. + /// + /// + /// Thrown if trying to rewind a negative amount or more than . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Rewind(long count) + { + if ((ulong)count > (ulong)Consumed) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count)); + } + + Consumed -= count; + + if (CurrentSpanIndex >= count) + { + CurrentSpanIndex -= (int)count; + _moreData = true; + } + else + { + // Current segment doesn't have enough data, scan backward through segments + RetreatToPreviousSpan(Consumed); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RetreatToPreviousSpan(long consumed) + { + ResetReader(); + Advance(consumed); + } + + private void ResetReader() + { + CurrentSpanIndex = 0; + Consumed = 0; + _currentPosition = Sequence.Start; + _nextPosition = _currentPosition; + + if (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory memory, advance: true)) + { + _moreData = true; + + if (memory.Length == 0) + { + CurrentSpan = default; + // No data in the first span, move to one with data + GetNextSpan(); + } + else + { + CurrentSpan = memory.Span; + } + } + else + { + // No data in any spans and at end of sequence + _moreData = false; + CurrentSpan = default; + } + } + + /// + /// Get the next segment with available data, if any. + /// + private void GetNextSpan() + { + if (!Sequence.IsSingleSegment) + { + SequencePosition previousNextPosition = _nextPosition; + while (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory memory, advance: true)) + { + _currentPosition = previousNextPosition; + if (memory.Length > 0) + { + CurrentSpan = memory.Span; + CurrentSpanIndex = 0; + return; + } + else + { + CurrentSpan = default; + CurrentSpanIndex = 0; + previousNextPosition = _nextPosition; + } + } + } + _moreData = false; + } + + /// + /// Move the reader ahead the specified number of items. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(long count) + { + const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000); + if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count) + { + CurrentSpanIndex += (int)count; + Consumed += count; + } + else + { + // Can't satisfy from the current span + AdvanceToNextSpan(count); + } + } + + /// + /// Unchecked helper to avoid unnecessary checks where you know count is valid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdvanceCurrentSpan(long count) + { + Debug.Assert(count >= 0); + + Consumed += count; + CurrentSpanIndex += (int)count; + if (CurrentSpanIndex >= CurrentSpan.Length) + GetNextSpan(); + } + + /// + /// Only call this helper if you know that you are advancing in the current span + /// with valid count and there is no need to fetch the next one. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AdvanceWithinSpan(long count) + { + Debug.Assert(count >= 0); + + Consumed += count; + CurrentSpanIndex += (int)count; + + Debug.Assert(CurrentSpanIndex < CurrentSpan.Length); + } + + private void AdvanceToNextSpan(long count) + { + if (count < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count)); + } + + Consumed += count; + while (_moreData) + { + int remaining = CurrentSpan.Length - CurrentSpanIndex; + + if (remaining > count) + { + CurrentSpanIndex += (int)count; + count = 0; + break; + } + + // As there may not be any further segments we need to + // push the current index to the end of the span. + CurrentSpanIndex += remaining; + count -= remaining; + Debug.Assert(count >= 0); + + GetNextSpan(); + + if (count == 0) + { + break; + } + } + + if (count != 0) + { + // Not enough data left- adjust for where we actually ended and throw + Consumed -= count; + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count)); + } + } + + /// + /// Copies data from the current to the given span if there + /// is enough data to fill it. + /// + /// + /// This API is used to copy a fixed amount of data out of the sequence if possible. It does not advance + /// the reader. To look ahead for a specific stream of data can be used. + /// + /// Destination span to copy to. + /// True if there is enough data to completely fill the span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryCopyTo(Span destination) + { + // This API doesn't advance to facilitate conditional advancement based on the data returned. + // We don't provide an advance option to allow easier utilizing of stack allocated destination spans. + // (Because we can make this method readonly we can guarantee that we won't capture the span.) + + ReadOnlySpan firstSpan = UnreadSpan; + if (firstSpan.Length >= destination.Length) + { + firstSpan.Slice(0, destination.Length).CopyTo(destination); + return true; + } + + // Not enough in the current span to satisfy the request, fall through to the slow path + return TryCopyMultisegment(destination); + } + + internal readonly bool TryCopyMultisegment(Span destination) + { + // If we don't have enough to fill the requested buffer, return false + if (Remaining < destination.Length) + return false; + + ReadOnlySpan firstSpan = UnreadSpan; + Debug.Assert(firstSpan.Length < destination.Length); + firstSpan.CopyTo(destination); + int copied = firstSpan.Length; + + SequencePosition next = _nextPosition; + while (Sequence.TryGet(ref next, out ReadOnlyMemory nextSegment, true)) + { + if (nextSegment.Length > 0) + { + ReadOnlySpan nextSpan = nextSegment.Span; + int toCopy = Math.Min(nextSpan.Length, destination.Length - copied); + nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied)); + copied += toCopy; + if (copied >= destination.Length) + { + break; + } + } + } + + return true; + } + + static class ThrowHelper + { + public static void ThrowArgumentOutOfRangeException(string name) => throw new ArgumentOutOfRangeException(name); + } + } +} + +#else + +using System.Buffers; +using System.Runtime.CompilerServices; + +#pragma warning disable RS0026 +#pragma warning disable RS0016 +#pragma warning disable RS0041 +[assembly: TypeForwardedTo(typeof(SequenceReader<>))] +#pragma warning restore RS0041 +#pragma warning restore RS0016 +#pragma warning restore RS0026 + +#endif diff --git a/src/Tmds.DBus.Protocol/Polyfill/SequenceReaderExtensions.cs b/src/Tmds.DBus.Protocol/Polyfill/SequenceReaderExtensions.cs new file mode 100644 index 00000000..1e7d3e8f --- /dev/null +++ b/src/Tmds.DBus.Protocol/Polyfill/SequenceReaderExtensions.cs @@ -0,0 +1,194 @@ +// // Copied from https://raw.githubusercontent.com/dotnet/runtime/cf5b231fcbea483df3b081939b422adfb6fd486a/src/libraries/System.Memory/src/System/Buffers/SequenceReaderExtensions.Binary.cs +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +#if NETSTANDARD2_0 + +using System.Buffers.Binary; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + /// + /// Provides extended functionality for the class that allows reading of endian specific numeric values from binary data. + /// + internal static partial class SequenceReaderExtensions + { + /// + /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary + /// structs- see remarks for full details. + /// + /// + /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to + /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit + /// overloads such as + /// + /// + /// True if successful. will be default if failed (due to lack of space). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe bool TryRead(ref this SequenceReader reader, out T value) where T : unmanaged + { + ReadOnlySpan span = reader.UnreadSpan; + if (span.Length < sizeof(T)) + return TryReadMultisegment(ref reader, out value); + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); + reader.Advance(sizeof(T)); + return true; + } + + private static unsafe bool TryReadMultisegment(ref SequenceReader reader, out T value) where T : unmanaged + { + Debug.Assert(reader.UnreadSpan.Length < sizeof(T)); + + // Not enough data in the current segment, try to peek for the data we need. + T buffer = default; + Span tempSpan = new Span(&buffer, sizeof(T)); + + if (!reader.TryCopyTo(tempSpan)) + { + value = default; + return false; + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); + reader.Advance(sizeof(T)); + return true; + } + + /// + /// Reads an as little endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadLittleEndian(ref this SequenceReader reader, out short value) + { + if (BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out short value) + { + if (!BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + private static bool TryReadReverseEndianness(ref SequenceReader reader, out short value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + /// + /// Reads an as little endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadLittleEndian(ref this SequenceReader reader, out int value) + { + if (BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out int value) + { + if (!BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + private static bool TryReadReverseEndianness(ref SequenceReader reader, out int value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + /// + /// Reads an as little endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadLittleEndian(ref this SequenceReader reader, out long value) + { + if (BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + /// + /// Reads an as big endian. + /// + /// False if there wasn't enough data for an . + public static bool TryReadBigEndian(ref this SequenceReader reader, out long value) + { + if (!BitConverter.IsLittleEndian) + { + return reader.TryRead(out value); + } + + return TryReadReverseEndianness(ref reader, out value); + } + + private static bool TryReadReverseEndianness(ref SequenceReader reader, out long value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + } +} + +#else + +using System.Buffers; +using System.Runtime.CompilerServices; + +#pragma warning disable RS0016 +#pragma warning disable RS0041 +[assembly: TypeForwardedTo(typeof(SequenceReaderExtensions))] +#pragma warning restore RS0041 +#pragma warning restore RS0016 + +#endif diff --git a/src/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj b/src/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj index ff9f6159..c2eeec3f 100644 --- a/src/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj +++ b/src/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj @@ -23,10 +23,11 @@ - + - + +