public static void InvalidAdvance()
        {
            {
                var output = new ArrayBufferWriter <string>();
                Assert.Throws <ArgumentException>(() => output.Advance(-1));
                Assert.Throws <InvalidOperationException>(() => output.Advance(output.Capacity + 1));
            }

            {
                var output = new ArrayBufferWriter <string>();
                WriteData(output, 100);
                Assert.Throws <InvalidOperationException>(() => output.Advance(output.FreeCapacity + 1));
            }
        }
Exemple #2
0
        public ReadOnlyMemory <byte> Sign(ReadOnlySpan <ValueTuple <AccountId, KeyPair> > signers, out Hash256 hash)
        {
            var signerArray = new Signer[signers.Length];

            for (int i = 0; i < signers.Length; ++i)
            {
                var signer = new Signer();
                signer.Account       = signers[i].Item1;
                signer.SigningPubKey = signers[i].Item2.PublicKey.GetCanoncialBytes();
                signer.TxnSignature  = null;
                signerArray[i]       = signer;
            }
            Signers       = Array.AsReadOnly(signerArray);
            TxnSignature  = null;
            SigningPubKey = null;
            var bufferWriter = new ArrayBufferWriter <byte>();

            // Calculate signatures and then serialize again
            for (int i = 0; i < signers.Length; ++i)
            {
                // For each signer we need to write the account being signed for the the buffer, sign that then rewind
                System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(bufferWriter.GetSpan(4), hpSMT);
                bufferWriter.Advance(4);
                Serialize(bufferWriter, true);
                Signers[i].Account.CopyTo(bufferWriter.GetSpan(20));
                bufferWriter.Advance(20);

                signerArray[i].TxnSignature = signers[i].Item2.Sign(bufferWriter.WrittenSpan);
                bufferWriter.Clear();
            }

            Array.Sort <Signer>(signerArray, (x, y) => x.Account.CompareTo(y.Account));
            Signers = Array.AsReadOnly(signerArray);

            System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(bufferWriter.GetSpan(4), hpTXN);
            bufferWriter.Advance(4);

            Serialize(bufferWriter, false);

            using (var sha512 = System.Security.Cryptography.SHA512.Create())
            {
                Span <byte> hashSpan = stackalloc byte[64];
                sha512.TryComputeHash(bufferWriter.WrittenSpan, hashSpan, out var bytesWritten);
                hash = new Hash256(hashSpan.Slice(0, 32));
            }

            return(bufferWriter.WrittenMemory.Slice(4));
        }
Exemple #3
0
        private async ValueTask <byte[]> ReadAsync(
            int length,
            CancellationToken cancellationToken = default)
        {
            if (length <= 0)
            {
                return(Array.Empty <byte>());
            }

            var bufferWriter = new ArrayBufferWriter <byte>(length);

            System.IO.Pipelines.ReadResult result;
            do
            {
                result = await _reader.ReadAsync(cancellationToken)
                         .ConfigureAwait(false);

                var buffer = result.Buffer.Slice(
                    0, Math.Min(bufferWriter.FreeCapacity, result.Buffer.Length));
                buffer.CopyTo(bufferWriter.GetSpan());
                bufferWriter.Advance((int)buffer.Length);

                _reader.AdvanceTo(buffer.End);
            } while (result.HasMoreData() && bufferWriter.WrittenCount < length);

            if (bufferWriter.WrittenCount < length)
            {
                throw new InvalidOperationException(
                          $"Expected {length} bytes, got {bufferWriter.WrittenCount}");
            }

            return(bufferWriter.WrittenMemory.ToArray());
        }
Exemple #4
0
        public static void Advance()
        {
            {
                var output   = new ArrayBufferWriter <byte>();
                int capacity = output.Capacity;
                Assert.Equal(capacity, output.FreeCapacity);
                output.Advance(output.FreeCapacity);
                Assert.Equal(capacity, output.WrittenCount);
                Assert.Equal(0, output.FreeCapacity);
            }

            {
                var output = new ArrayBufferWriter <byte>();
                output.Advance(output.Capacity);
                Assert.Equal(output.Capacity, output.WrittenCount);
                Assert.Equal(0, output.FreeCapacity);
                int         previousCapacity = output.Capacity;
                Span <byte> _ = output.GetSpan();
                Assert.True(output.Capacity > previousCapacity);
            }

            {
                var output = new ArrayBufferWriter <byte>();
                WriteData(output, 2);
                ReadOnlyMemory <byte> previousMemory = output.WrittenMemory;
                ReadOnlySpan <byte>   previousSpan   = output.WrittenSpan;
                Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
                output.Advance(10);
                Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
                Assert.False(previousSpan.SequenceEqual(output.WrittenSpan));
                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
            }
        }
Exemple #5
0
        public void ModbusFrameWriter_WriteFrame_CorrectlyWritesAReadInputRegistersRequest()
        {
            // Given
            var request = new RequestAdu
            {
                Header = new Header(transactionID: 1, unitID: 4),
                Pdu    = new RequestReadInputRegisters
                {
                    Address  = 0x0040,
                    Quantity = 0x000A,
                },
            };
            var arraybuffer = new ArrayBufferWriter <byte>();

            // When
            var writer   = new ModbusFrameWriter(arraybuffer);
            var position = writer.WriteFrame(request);

            arraybuffer.Advance(position);

            // Then
            var data = arraybuffer.WrittenSpan.ToArray();

            Assert.Equal(
                new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x04, 0x04, 0x00, 0x40, 0x00, 0x0A },
                data
                );
        }
Exemple #6
0
        /// <summary>
        /// Reads messages from web socket and writes them to channel.
        /// Complete() will be called on channel writer when a close message is received from socket.
        /// On exception or cancellation, channel writer will not be set to complete.
        /// </summary>
        /// <exception cref="WebSocketException"></exception>
        /// <exception cref="OperationCanceledException"></exception>
        internal static async Task ReceiveLoop(this ChannelWriter <Message> channelWriter, WebSocket socket, CancellationToken cancellationToken)
        {
            Requires.NotNull(channelWriter, nameof(channelWriter));
            Requires.NotNull(socket, nameof(socket));

            var bufferWriter = new ArrayBufferWriter <byte>();

            while (await ReadMessageAsync().ConfigureAwait(false))
            {
            }

            async Task <bool> ReadMessageAsync()
            {
                ValueWebSocketReceiveResult result;

                do
                {
                    result = await socket.ReceiveAsync(bufferWriter.GetMemory(), cancellationToken).ConfigureAwait(false);

                    switch (result.MessageType)
                    {
                    case WebSocketMessageType.Text:
                    case WebSocketMessageType.Binary:
                        bufferWriter.Advance(result.Count);
                        break;

                    case WebSocketMessageType.Close:
                        return(false);
                    }
                } while (!result.EndOfMessage);

                channelWriter.TryWrite(new Message(Convert(result.MessageType), bufferWriter.WrittenSpan.ToArray()));
                bufferWriter.Clear();

                return(true);
Exemple #7
0
        public void ModbusFrameWriter_WriteFrame_CorrectlyWritesAWriteSingleCoilRequest()
        {
            // Given
            var request = new RequestAdu
            {
                Header = new Header(transactionID: 1, unitID: 4),
                Pdu    = new RequestWriteSingleCoil
                {
                    Address   = 0x0041,
                    CoilState = true,
                },
            };
            var arraybuffer = new ArrayBufferWriter <byte>();

            // When
            var writer   = new ModbusFrameWriter(arraybuffer);
            var position = writer.WriteFrame(request);

            arraybuffer.Advance(position);

            // Then
            var data = arraybuffer.WrittenSpan.ToArray();

            Assert.Equal(
                new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x04, 0x05, 0x00, 0x41, 0xFF, 0x00 },
                data
                );
        }
Exemple #8
0
        public void Advance()
        {
            {
                var output   = new ArrayBufferWriter <T>();
                int capacity = output.Capacity;
                Assert.Equal(capacity, output.FreeCapacity);
                output.Advance(output.FreeCapacity);
                Assert.Equal(capacity, output.WrittenCount);
                Assert.Equal(0, output.FreeCapacity);
            }

            {
                var output = new ArrayBufferWriter <T>();
                output.Advance(output.Capacity);
                Assert.Equal(output.Capacity, output.WrittenCount);
                Assert.Equal(0, output.FreeCapacity);
                int      previousCapacity = output.Capacity;
                Span <T> _ = output.GetSpan();
                Assert.True(output.Capacity > previousCapacity);
            }

            {
                var output = new ArrayBufferWriter <T>(256);
                WriteData(output, 2);
                ReadOnlyMemory <T> previousMemory = output.WrittenMemory;
                ReadOnlySpan <T>   previousSpan   = output.WrittenSpan;
                Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
                output.Advance(10);
                Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
                Assert.False(previousSpan.SequenceEqual(output.WrittenSpan));
                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
            }

            {
                var output = new ArrayBufferWriter <T>();
                _ = output.GetSpan(20);
                WriteData(output, 10);
                ReadOnlyMemory <T> previousMemory = output.WrittenMemory;
                ReadOnlySpan <T>   previousSpan   = output.WrittenSpan;
                Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
                Assert.Throws <InvalidOperationException>(() => output.Advance(247));
                output.Advance(10);
                Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
                Assert.False(previousSpan.SequenceEqual(output.WrittenSpan));
                Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
            }
        }
Exemple #9
0
        public async Task RunAndBlockAsync(Uri url, CancellationToken cancel)
        {
            var error        = "Error.";
            var bufferWriter = new ArrayBufferWriter <byte>(CHUNK_SIZE);

            try
            {
                using (_ws = new ClientWebSocket())
                {
                    await _ws.ConnectAsync(url, cancel).ConfigureAwait(false);

                    // WebsocketConnected!.Invoke(this);

                    while (true)
                    {
                        var result = await _ws.ReceiveAsync(bufferWriter.GetMemory(CHUNK_SIZE), cancel);

                        bufferWriter.Advance(result.Count);
                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            var closeMessage = CloseCodes.GetErrorCodeMessage((int?)_ws.CloseStatus ?? 0).Message;
                            error = $"Websocket closed ({_ws.CloseStatus}): {_ws.CloseStatusDescription} {closeMessage}";
                            break;
                        }

                        if (result.EndOfMessage)
                        {
                            var pr   = PayloadReceived;
                            var data = bufferWriter.WrittenMemory.ToArray();
                            bufferWriter.Clear();

                            if (!(pr is null))
                            {
                                await pr.Invoke(data);
                            }
                        }
                    }
                }
            }
            catch (WebSocketException ex)
            {
                Log.Warning("Disconnected, check your internet connection...");
                Log.Debug(ex, "Websocket Exception in websocket client");
            }
            catch (OperationCanceledException)
            {
                // ignored
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error in websocket client. {Message}", ex.Message);
            }
            finally
            {
                bufferWriter.Clear();
                _ws = null;
                await ClosedAsync(error).ConfigureAwait(false);
            }
        }
Exemple #10
0
        private void Grow(int requiredSize)
        {
            if (_memory.Length == 0)
            {
                FirstGrow(requiredSize);
                return;
            }

            int sizeHint = Math.Max(DefaultGrowthSize, requiredSize);

            Debug.Assert(BytesPending != 0);

            if (_stream != null)
            {
                Debug.Assert(_arrayBufferWriter != null);

                _arrayBufferWriter.Advance(BytesPending);

                Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount);

                _stream.Write(_arrayBufferWriter.WrittenSpan);

                _arrayBufferWriter.Clear();

                _memory = _arrayBufferWriter.GetMemory(sizeHint);

                Debug.Assert(_memory.Length >= sizeHint);
            }
            else
            {
                Debug.Assert(_output != null);

                _output.Advance(BytesPending);

                _memory = _output.GetMemory(sizeHint);

                if (_memory.Length < sizeHint)
                {
                    // ToDo - replace this
                    throw new Exception("Need more Span size");
                }
            }

            BytesCommitted += BytesPending;
            BytesPending    = 0;
        }
        public void GetMemory_ExceedMaximumBufferSize()
        {
            var output = new ArrayBufferWriter <byte>(int.MaxValue / 2 + 1);

            output.Advance(int.MaxValue / 2 + 1);
            Memory <byte> memory = output.GetMemory(1); // Validate we can't double the buffer size, but can grow by sizeHint

            Assert.Equal(1, memory.Length);
            Assert.Throws <OutOfMemoryException>(() => output.GetMemory(int.MaxValue));
        }
Exemple #12
0
        private void Append(ReadOnlyMemory <byte> segment)
        {
            _writer ??= new ArrayBufferWriter <byte>();
            _segments ??= new List <ReadOnlyMemory <byte> >();

            var memory = _writer.GetMemory(segment.Length);

            segment.CopyTo(memory);
            _writer.Advance(segment.Length);
            _segments.Add(memory.Slice(0, segment.Length));
        }
Exemple #13
0
        /// <summary>
        /// Commits the JSON text written so far which makes it visible to the output destination.
        /// </summary>
        /// <remarks>
        /// In the case of IBufferWriter, this advances the underlying <see cref="IBufferWriter{Byte}" /> based on what has been written so far.
        /// In the case of Stream, this writes the data to the stream and flushes it.
        /// </remarks>
        /// <exception cref="ObjectDisposedException">
        ///   The instance of <see cref="Utf8JsonWriter"/> has been disposed.
        /// </exception>
        public void Flush()
        {
            CheckNotDisposed();

            _memory = default;

            if (_stream != null)
            {
                Debug.Assert(_arrayBufferWriter != null);
                if (BytesPending != 0)
                {
                    _arrayBufferWriter.Advance(BytesPending);
                    BytesPending = 0;

#if BUILDING_INBOX_LIBRARY
                    _stream.Write(_arrayBufferWriter.WrittenSpan);
#else
                    Debug.Assert(_arrayBufferWriter.WrittenMemory.Length == _arrayBufferWriter.WrittenCount);
                    bool result = MemoryMarshal.TryGetArray(_arrayBufferWriter.WrittenMemory, out ArraySegment <byte> underlyingBuffer);
                    Debug.Assert(result);
                    Debug.Assert(underlyingBuffer.Offset == 0);
                    Debug.Assert(_arrayBufferWriter.WrittenCount == underlyingBuffer.Count);
                    _stream.Write(underlyingBuffer.Array, underlyingBuffer.Offset, underlyingBuffer.Count);
#endif

                    BytesCommitted += _arrayBufferWriter.WrittenCount;
                    _arrayBufferWriter.Clear();
                }
                _stream.Flush();
            }
            else
            {
                Debug.Assert(_output != null);
                if (BytesPending != 0)
                {
                    _output.Advance(BytesPending);
                    BytesCommitted += BytesPending;
                    BytesPending    = 0;
                }
            }
        }
Exemple #14
0
        public void ModbusFrameWriter_WriteFrame_CorrectlyWritesAWriteMultipleCoilsRequest(object request, byte[] expected)
        {
            // Given
            var arraybuffer = new ArrayBufferWriter <byte>();

            // When
            var writer   = new ModbusFrameWriter(arraybuffer);
            var position = writer.WriteFrame((RequestAdu)request);

            arraybuffer.Advance(position);

            // Then
            var data = arraybuffer.WrittenSpan.ToArray();

            Assert.Equal(expected, data);
        }
        /// <summary>
        ///   Reads the data body of an AMQP message, transforming it for use
        ///   as the body of an <see cref="EventData" /> instance.
        /// </summary>
        ///
        /// <param name="body">The body data set of an AMQP message.</param>
        ///
        /// <returns>A <see cref="BinaryData" /> representation of the <paramref name="body"/>.</returns>
        ///
        private static BinaryData ReadAmqpDataBody(IEnumerable <Data> body)
        {
            var writer = new ArrayBufferWriter <byte>();

            foreach (var data in body)
            {
                var dataBytes = GetDataBytes(data);
                dataBytes.CopyTo(writer.GetMemory(dataBytes.Length));

                writer.Advance(dataBytes.Length);
            }

            return((writer.WrittenCount > 0)
                ? BinaryData.FromBytes(writer.WrittenMemory)
                : new BinaryData(Array.Empty <byte>()));
        }
Exemple #16
0
        public void AdvanceZero()
        {
            var output = new ArrayBufferWriter <T>();

            WriteData(output, 2);
            Assert.Equal(2, output.WrittenCount);
            ReadOnlyMemory <T> previousMemory = output.WrittenMemory;
            ReadOnlySpan <T>   previousSpan   = output.WrittenSpan;

            Assert.True(previousSpan.SequenceEqual(previousMemory.Span));
            output.Advance(0);
            Assert.Equal(2, output.WrittenCount);
            Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span));
            Assert.True(previousSpan.SequenceEqual(output.WrittenSpan));
            Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span));
        }
        // Returns via the out parameter the flattened collection of bytes.
        // A majority of the time, data will only contain 1 element.
        // The method is optimized for this situation to return the pre-existing array.
        public static BinaryData ConvertAndFlattenData(this IEnumerable <ReadOnlyMemory <byte> > dataList)
        {
            var           writer = new ArrayBufferWriter <byte>();
            Memory <byte> memory;

            foreach (ReadOnlyMemory <byte> data in dataList)
            {
                memory = writer.GetMemory(data.Length);
                data.CopyTo(memory);
                writer.Advance(data.Length);
            }
            if (writer.WrittenCount == 0)
            {
                return(new BinaryData(Array.Empty <byte>()));
            }
            return(BinaryData.FromBytes(writer.WrittenMemory));
        }
Exemple #18
0
        // Returns via the out parameter the flattened collection of bytes.
        // A majority of the time, data will only contain 1 element.
        // The method is optimized for this situation to return the pre-existing array.
        public static BinaryData ConvertAndFlattenData(this IEnumerable <BinaryData> dataList)
        {
            var           writer = new ArrayBufferWriter <byte>();
            Memory <byte> memory;

            foreach (BinaryData data in dataList)
            {
                ReadOnlyMemory <byte> bytes = data.ToBytes();
                memory = writer.GetMemory(bytes.Length);
                bytes.CopyTo(memory);
                writer.Advance(bytes.Length);
            }
            if (writer.WrittenCount == 0)
            {
                return(new BinaryData());
            }
            return(BinaryData.FromBytes(writer.WrittenMemory));
        }
Exemple #19
0
        public void GetMemory_ExceedMaximumBufferSize()
        {
            const int MaxArrayLength = 0X7FEFFFFF;

            int initialCapacity = int.MaxValue / 2 + 1;

            var output = new ArrayBufferWriter <byte>(initialCapacity);

            output.Advance(initialCapacity);

            // Validate we can't double the buffer size, but can grow
            Memory <byte> memory = output.GetMemory(1);

            // The buffer should grow more than the 1 byte requested otherwise performance will not be usable
            // between 1GB and 2GB. The current implementation maxes out the buffer size to MaxArrayLength.
            Assert.Equal(MaxArrayLength - initialCapacity, memory.Length);

            Assert.Throws <OutOfMemoryException>(() => output.GetMemory(int.MaxValue));
        }
Exemple #20
0
        // StorageKey.Key uses an atypical storage pattern relative to other models in Neo.
        // The byte array is written in blocks of BLOCK_SIZE (aka 16) bytes  followed by a byte
        // indicating how many bytes of the previous block were padding. Only the last block is
        // allowed to have padding. Read blocks of BLOCK_SIZE + 1 until padding indication byte
        // is greater than zero.

        public static bool TryRead(ref BufferReader <byte> reader, out StorageKey value)
        {
            const int READ_BLOCK_SIZE = BLOCK_SIZE + 1;

            if (UInt160.TryRead(ref reader, out var scriptHash))
            {
                using var bufferOwner = MemoryPool <byte> .Shared.Rent(READ_BLOCK_SIZE);

                var buffer = bufferOwner.Memory.Slice(0, READ_BLOCK_SIZE).Span;
                var writer = new ArrayBufferWriter <byte>();

                while (true)
                {
                    if (!reader.TryCopyTo(buffer))
                    {
                        value = default;
                        return(false);
                    }
                    reader.Advance(READ_BLOCK_SIZE);

                    var dataSize = BLOCK_SIZE - buffer[BLOCK_SIZE];
                    buffer.Slice(0, dataSize).CopyTo(writer.GetSpan(dataSize));
                    writer.Advance(dataSize);

                    if (dataSize < BLOCK_SIZE)
                    {
                        break;
                    }
                }

                // unfortunately, since we don't know a priori how many blocks there will be
                // or how much padding the last block will have, we have to make another copy
                // of the key array. However, we can use Unsafe.As to cast the mutable key array
                // into an ImmutableArray
                var keyArray = writer.WrittenSpan.ToArray();
                value = new StorageKey(scriptHash, Unsafe.As <byte[], ImmutableArray <byte> >(ref keyArray));

                return(true);
            }

            value = default;
            return(false);
        }
Exemple #21
0
            /// <summary>
            ///   Appends a memory segment to the continuous buffer.
            /// </summary>
            ///
            /// <param name="segment">The memory segment to append.</param>
            ///
            private void AppendSegment(Data segment)
            {
                _writer ??= new ArrayBufferWriter <byte>();
                _segments ??= new List <ReadOnlyMemory <byte> >();

                ReadOnlyMemory <byte> dataToAppend = segment.Value switch
                {
                    byte[] byteArray => byteArray,
                    ArraySegment <byte> arraySegment => arraySegment,
                    _ => ReadOnlyMemory <byte> .Empty
                };

                var memory = _writer.GetMemory(dataToAppend.Length);

                dataToAppend.CopyTo(memory);

                _writer.Advance(dataToAppend.Length);
                _segments.Add(memory.Slice(0, dataToAppend.Length));
            }
        }
Exemple #22
0
            private void Append(Data segment)
            {
                // fields are lazy initialized to not occupy unnecessary memory when there are no data segments
                _writer ??= new ArrayBufferWriter <byte>();
                _segments ??= new List <ReadOnlyMemory <byte> >();

                ReadOnlyMemory <byte> dataToAppend = segment.Value switch
                {
                    byte[] byteArray => byteArray,
                    ArraySegment <byte> arraySegment => arraySegment,
                    _ => ReadOnlyMemory <byte> .Empty
                };

                var memory = _writer.GetMemory(dataToAppend.Length);

                dataToAppend.CopyTo(memory);
                _writer.Advance(dataToAppend.Length);
                _segments.Add(memory.Slice(0, dataToAppend.Length));
            }
        }
Exemple #23
0
        public void GetMemory_ExceedMaximumBufferSize()
        {
            int initialCapacity = int.MaxValue / 2 + 1;

            try
            {
                var output = new ArrayBufferWriter <byte>(initialCapacity);
                output.Advance(initialCapacity);

                // Validate we can't double the buffer size, but can grow
                Memory <byte> memory;
                memory = output.GetMemory(1);

                // The buffer should grow more than the 1 byte requested otherwise performance will not be usable
                // between 1GB and 2GB. The current implementation maxes out the buffer size to Array.MaxLength.
                Assert.Equal(Array.MaxLength - initialCapacity, memory.Length);
                Assert.Throws <OutOfMemoryException>(() => output.GetMemory(int.MaxValue));
            }
            catch (OutOfMemoryException)
            {
                // On memory constrained devices, we can get an OutOfMemoryException, which we can safely ignore.
            }
        }
            public string[] ReadHeaderLines()
            {
                if (State != ReaderState.Headers)
                {
                    throw new InvalidOperationException();
                }

                List <string> lines        = new List <string>();
                var           outputBuffer = new ArrayBufferWriter <byte>();

                if (endOfClearText)
                {
                    // If we were reading clear text before then we consumed
                    // the first two dashes of the separator.
                    outputBuffer.Write(new[] { (byte)'-', (byte)'-' });
                }

                for (; ;)
                {
                    var b = innerStream.ReadByte();
                    switch (b)
                    {
                    case -1:
                        return(Array.Empty <string>());

                    case '\n':
                        if (!ignoreNL)
                        {
                            goto case '\r';
                        }
                        ignoreNL = false;
                        break;

                    case '\r':
                        ignoreNL = b == '\r';

                        // Non-empty line
                        string?line = null;
                        if (outputBuffer.WrittenCount > 0)
                        {
                            line = Encoding.ASCII.GetString(outputBuffer.WrittenSpan);
                            outputBuffer.Clear();
                        }

                        if (!string.IsNullOrWhiteSpace(line))
                        {
                            lines.Add(line);
                        }
                        else if (lines.Count > 0)
                        {
                            // TODO: Verify the headers
                            headerEndLineLength = lines.First().Length - 2;     // -2 for BEGIN -> END

                            State =
                                !endOfClearText && Equals(lines.FirstOrDefault(), "-----BEGIN PGP SIGNED MESSAGE-----") ?
                                ReaderState.ClearText : ReaderState.Base64;
                            return(lines.ToArray());
                        }

                        break;

                    default:
                        outputBuffer.GetSpan(1)[0] = (byte)b;
                        outputBuffer.Advance(1);
                        break;
                    }
                }
            }
            public int ReadClearText(Span <byte> buffer)
            {
                if (State != ReaderState.ClearText)
                {
                    throw new InvalidOperationException();
                }

                // We usually try to output one character for one input character
                // but new line and dash sequences can produce up to 4 characters
                // at once, so size the buffer to accommodate that. We can also
                // overshoot the buffer with pending whitespace.
                var outputBuffer = new ArrayBufferWriter <byte>(buffer.Length + 3);

                if (pendingData != null)
                {
                    outputBuffer.Write(pendingData);
                }

                while (!endOfClearText && outputBuffer.WrittenCount < buffer.Length)
                {
                    var b = innerStream.ReadByte();

                    // End of stream
                    if (b == -1)
                    {
                        break;
                    }

                    switch (b)
                    {
                    case ' ':
                    case '\t':
                        // Collect pending whitespace because it could be trailing whitespace
                        // at end of line
                        pendingWhitespace.GetSpan(1)[0] = (byte)b;
                        pendingWhitespace.Advance(1);
                        break;

                    case '\r':
                    case '\n':
                        // Ignore \n that was still part of header
                        if (ignoreNL && b == '\n')
                        {
                            ignoreNL = false;
                            break;
                        }

                        // Discard any pending whitespace
                        pendingWhitespace.Clear();

                        // Read next character after the new line
                        var nextB = innerStream.ReadByte();
                        if (b == '\r' && nextB == '\n')
                        {
                            nextB = innerStream.ReadByte();
                        }

                        if (nextB == '-')
                        {
                            // New line followed byt dash. Now we are looking either at the end of
                            // clear text or a dash escape
                            nextB = innerStream.ReadByte();
                            if (nextB == ' ')
                            {
                                // Dash escape, remove it and flush the new line
                                outputBuffer.Write(new[] { (byte)'\r', (byte)'\n' });
                            }
                            else if (nextB == '-')
                            {
                                // Possible end of clear text
                                endOfClearText = true;
                                break;
                            }
                            else
                            {
                                // Invalid clear text, flush the new lind and output it
                                var clearText = outputBuffer.GetSpan(4);
                                clearText[0] = (byte)'\r';
                                clearText[1] = (byte)'\n';
                                clearText[2] = (byte)'-';
                                clearText[3] = (byte)nextB;
                                outputBuffer.Advance(4);
                            }
                        }
                        else
                        {
                            // Flush the new line
                            outputBuffer.Write(new[] { (byte)'\r', (byte)'\n' });

                            if (nextB == '\r' || nextB == '\n')
                            {
                                b = nextB;
                                goto case '\r';
                            }
                            else if (nextB == ' ' || nextB == '\t')
                            {
                                pendingWhitespace.GetSpan(1)[0] = (byte)nextB;
                                pendingWhitespace.Advance(1);
                            }
                            else
                            {
                                outputBuffer.GetSpan(1)[0] = (byte)nextB;
                                outputBuffer.Advance(1);
                            }
                        }
                        break;

                    default:
                        // Flush any pending whitespace
                        if (pendingWhitespace.WrittenCount > 0)
                        {
                            outputBuffer.Write(pendingWhitespace.WrittenSpan);
                            pendingWhitespace.Clear();
                        }

                        outputBuffer.GetSpan(1)[0] = (byte)b;
                        outputBuffer.Advance(1);
                        break;
                    }
                }

                if (outputBuffer.WrittenCount > buffer.Length)
                {
                    pendingData = outputBuffer.WrittenSpan.Slice(buffer.Length).ToArray();
                    outputBuffer.WrittenSpan.Slice(0, buffer.Length).CopyTo(buffer);
                    return(buffer.Length);
                }
                else
                {
                    if (endOfClearText)
                    {
                        State = ReaderState.Headers;
                    }
                    pendingData = null;
                    outputBuffer.WrittenSpan.CopyTo(buffer);
                    return(outputBuffer.WrittenCount);
                }
            }