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 @@
-
+
-
+
+