diff --git a/src/libraries/System.Net.Mail/src/System/Net/Base64Stream.cs b/src/libraries/System.Net.Mail/src/System/Net/Base64Stream.cs index e279a5b4b2f3fc..83dfbb84cf0417 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Base64Stream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Base64Stream.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Buffers; namespace System.Net { @@ -20,7 +21,7 @@ internal sealed class Base64Stream : DelegatedStream, IEncodableStream 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, // 2 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255, // 3 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 4 - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // 5 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // 5 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 6 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, // 7 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 8 @@ -52,6 +53,9 @@ internal Base64Stream(Base64WriteStateInfo writeStateInfo) : base(new MemoryStre _encoder = new Base64Encoder(_writeState, writeStateInfo.MaxLineLength); } + public override bool CanRead => BaseStream.CanRead; + public override bool CanWrite => BaseStream.CanWrite; + private ReadStateInfo ReadState => _readState ??= new ReadStateInfo(); internal WriteStateInfoBase WriteState @@ -63,12 +67,6 @@ internal WriteStateInfoBase WriteState } } - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => - TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state); - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => - TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), callback, state); - public override void Close() { if (_writeState != null && WriteState.Length > 0) @@ -80,14 +78,14 @@ public override void Close() base.Close(); } - public unsafe int DecodeBytes(byte[] buffer, int offset, int count) + public unsafe int DecodeBytes(Span buffer) { fixed (byte* pBuffer = buffer) { - byte* start = pBuffer + offset; + byte* start = pBuffer; byte* source = start; byte* dest = start; - byte* end = start + count; + byte* end = start + buffer.Length; while (source < end) { @@ -133,24 +131,18 @@ public unsafe int DecodeBytes(byte[] buffer, int offset, int count) } } - public int EncodeBytes(byte[] buffer, int offset, int count) => - EncodeBytes(buffer, offset, count, true, true); + public int EncodeBytes(ReadOnlySpan buffer) => + _encoder.EncodeBytes(buffer, true, true); - internal int EncodeBytes(byte[] buffer, int offset, int count, bool dontDeferFinalBytes, bool shouldAppendSpaceToCRLF) + internal int EncodeBytes(ReadOnlySpan buffer, bool dontDeferFinalBytes, bool shouldAppendSpaceToCRLF) { - return _encoder.EncodeBytes(buffer, offset, count, dontDeferFinalBytes, shouldAppendSpaceToCRLF); + return _encoder.EncodeBytes(buffer, dontDeferFinalBytes, shouldAppendSpaceToCRLF); } public int EncodeString(string value, Encoding encoding) => _encoder.EncodeString(value, encoding); public string GetEncodedString() => _encoder.GetEncodedString(); - public override int EndRead(IAsyncResult asyncResult) => - TaskToAsyncResult.End(asyncResult); - - public override void EndWrite(IAsyncResult asyncResult) => - TaskToAsyncResult.End(asyncResult); - public override void Flush() { if (_writeState != null && WriteState.Length > 0) @@ -163,90 +155,78 @@ public override void Flush() public override async Task FlushAsync(CancellationToken cancellationToken) { - if (_writeState != null && WriteState.Length > 0) - { - await base.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken).ConfigureAwait(false); - WriteState.Reset(); - } - + await FlushInternalAsync(cancellationToken).ConfigureAwait(false); await base.FlushAsync(cancellationToken).ConfigureAwait(false); } private void FlushInternal() { - base.Write(WriteState.Buffer, 0, WriteState.Length); + BaseStream.Write(WriteState.Buffer.AsSpan(0, WriteState.Length)); WriteState.Reset(); } - public override int Read(byte[] buffer, int offset, int count) + private async ValueTask FlushInternalAsync(CancellationToken cancellationToken) { - ValidateBufferArguments(buffer, offset, count); + await BaseStream.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken).ConfigureAwait(false); + WriteState.Reset(); + } + protected override int ReadInternal(Span buffer) + { while (true) { // read data from the underlying stream - int read = base.Read(buffer, offset, count); + int read = BaseStream.Read(buffer); // if the underlying stream returns 0 then there - // is no more data - ust return 0. + // is no more data - just return 0. if (read == 0) { return 0; } - // while decoding, we may end up not having - // any bytes to return pending additional data - // from the underlying stream. - read = DecodeBytes(buffer, offset, read); + // Decode the read bytes and update the input buffer with decoded bytes + read = DecodeBytes(buffer.Slice(0, read)); if (read > 0) { return read; } } } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - ValidateBufferArguments(buffer, offset, count); - return ReadAsyncCore(buffer, offset, count, cancellationToken); - async Task ReadAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + protected override async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken = default) + { + while (true) { - while (true) - { - // read data from the underlying stream - int read = await base.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false); + // read data from the underlying stream + int read = await BaseStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - // if the underlying stream returns 0 then there - // is no more data - ust return 0. - if (read == 0) - { - return 0; - } + // if the underlying stream returns 0 then there + // is no more data - just return 0. + if (read == 0) + { + return 0; + } - // while decoding, we may end up not having - // any bytes to return pending additional data - // from the underlying stream. - read = DecodeBytes(buffer, offset, read); - if (read > 0) - { - return read; - } + // Decode the read bytes and update the input buffer with decoded bytes + read = DecodeBytes(buffer.Span.Slice(0, read)); + if (read > 0) + { + return read; } } } - public override void Write(byte[] buffer, int offset, int count) + protected override void WriteInternal(ReadOnlySpan buffer) { - ValidateBufferArguments(buffer, offset, count); - int written = 0; // do not append a space when writing from a stream since this means // it's writing the email body while (true) { - written += EncodeBytes(buffer, offset + written, count - written, false, false); - if (written < count) + written += EncodeBytes(buffer.Slice(written), false, false); + if (written < buffer.Length) { FlushInternal(); } @@ -257,28 +237,22 @@ public override void Write(byte[] buffer, int offset, int count) } } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + protected override async ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { - ValidateBufferArguments(buffer, offset, count); - return WriteAsyncCore(buffer, offset, count, cancellationToken); + int written = 0; - async Task WriteAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + // do not append a space when writing from a stream since this means + // it's writing the email body + while (true) { - int written = 0; - - // do not append a space when writing from a stream since this means - // it's writing the email body - while (true) + written += EncodeBytes(buffer.Span.Slice(written), false, false); + if (written < buffer.Length) { - written += EncodeBytes(buffer, offset + written, count - written, false, false); - if (written < count) - { - await FlushAsync(cancellationToken).ConfigureAwait(false); - } - else - { - break; - } + await FlushInternalAsync(cancellationToken).ConfigureAwait(false); + } + else + { + break; } } } diff --git a/src/libraries/System.Net.Mail/src/System/Net/BufferedReadStream.cs b/src/libraries/System.Net.Mail/src/System/Net/BufferedReadStream.cs index 5e635d6d53159d..a760d2113c7f53 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/BufferedReadStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/BufferedReadStream.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using System.Buffers; namespace System.Net { @@ -23,96 +24,76 @@ internal BufferedReadStream(Stream stream, bool readMore) : base(stream) _readMore = readMore; } - public override bool CanWrite - { - get - { - return false; - } - } + public override bool CanWrite => false; + public override bool CanRead => BaseStream.CanRead; - public override bool CanSeek - { - get - { - return false; - } - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => - TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state); + public override bool CanSeek => false; - public override int EndRead(IAsyncResult asyncResult) => - TaskToAsyncResult.End(asyncResult); - - public override int Read(byte[] buffer, int offset, int count) + protected override int ReadInternal(Span buffer) { - int read = 0; if (_storedOffset < _storedLength) { - read = Math.Min(count, _storedLength - _storedOffset); - Buffer.BlockCopy(_storedBuffer!, _storedOffset, buffer, offset, read); + int read = Math.Min(buffer.Length, _storedLength - _storedOffset); + _storedBuffer.AsSpan(_storedOffset, read).CopyTo(buffer); _storedOffset += read; - if (read == count || !_readMore) + if (read == buffer.Length || !_readMore) { return read; } - offset += read; - count -= read; + // Need to read more from the underlying stream + return read + BaseStream.Read(buffer.Slice(read)); } - return read + base.Read(buffer, offset, count); + + return BaseStream.Read(buffer); } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + protected override ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken = default) { - int read; if (_storedOffset >= _storedLength) { - return base.ReadAsync(buffer, offset, count, cancellationToken); + return BaseStream.ReadAsync(buffer, cancellationToken); } - read = Math.Min(count, _storedLength - _storedOffset); - Buffer.BlockCopy(_storedBuffer!, _storedOffset, buffer, offset, read); + int read = Math.Min(buffer.Length, _storedLength - _storedOffset); + _storedBuffer.AsMemory(_storedOffset, read).CopyTo(buffer); _storedOffset += read; - if (read == count || !_readMore) + if (read == buffer.Length || !_readMore) { - return Task.FromResult(read); + return new ValueTask(read); } - offset += read; - count -= read; - - return ReadMoreAsync(read, buffer, offset, count, cancellationToken); + // Need to read more from the underlying stream + return ReadMoreAsync(read, buffer.Slice(read), cancellationToken); } - private async Task ReadMoreAsync(int bytesAlreadyRead, byte[] buffer, int offset, int count, CancellationToken cancellationToken) + private async ValueTask ReadMoreAsync(int bytesAlreadyRead, Memory buffer, CancellationToken cancellationToken) { - int returnValue = await base.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false); + int returnValue = await BaseStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); return bytesAlreadyRead + returnValue; } - public override int ReadByte() + protected override void WriteInternal(ReadOnlySpan buffer) { - if (_storedOffset < _storedLength) - { - return _storedBuffer![_storedOffset++]; - } - else - { - return base.ReadByte(); - } + throw new NotImplementedException(); + } + + protected override ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); } // adds additional content to the beginning of the buffer // so the layout of the storedBuffer will be // // after calling push - internal void Push(byte[] buffer, int offset, int count) + internal void Push(ReadOnlySpan buffer) { - if (count == 0) + if (buffer.Length == 0) return; + int count = buffer.Length; + if (_storedOffset == _storedLength) { if (_storedBuffer == null || _storedBuffer.Length < count) @@ -146,7 +127,7 @@ internal void Push(byte[] buffer, int offset, int count) } } - Buffer.BlockCopy(buffer, offset, _storedBuffer!, _storedOffset, count); + buffer.CopyTo(_storedBuffer.AsSpan(_storedOffset)); } } } diff --git a/src/libraries/System.Net.Mail/src/System/Net/CloseableStream.cs b/src/libraries/System.Net.Mail/src/System/Net/CloseableStream.cs index adf9f88c0c10df..e5455abf9519a5 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/CloseableStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/CloseableStream.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; namespace System.Net { @@ -17,6 +18,9 @@ internal ClosableStream(Stream stream, EventHandler? onClose) : base(stream) _onClose = onClose; } + public override bool CanRead => BaseStream.CanRead; + public override bool CanWrite => BaseStream.CanWrite; + public override void Close() { if (Interlocked.Increment(ref _closed) == 1) @@ -24,5 +28,25 @@ public override void Close() _onClose?.Invoke(this, new EventArgs()); } } + + protected override void WriteInternal(ReadOnlySpan buffer) + { + BaseStream.Write(buffer); + } + + protected override ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + return BaseStream.WriteAsync(buffer, cancellationToken); + } + + protected override int ReadInternal(Span buffer) + { + return BaseStream.Read(buffer); + } + + protected override ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken = default) + { + return BaseStream.ReadAsync(buffer, cancellationToken); + } } } diff --git a/src/libraries/System.Net.Mail/src/System/Net/DelegatedStream.cs b/src/libraries/System.Net.Mail/src/System/Net/DelegatedStream.cs index d2321b3dfe2c56..1a8b3fa54cbdad 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/DelegatedStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/DelegatedStream.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using System.Buffers; namespace System.Net { @@ -18,37 +19,13 @@ protected DelegatedStream(Stream stream) _stream = stream; } - protected Stream BaseStream - { - get - { - return _stream; - } - } + protected Stream BaseStream => _stream; - public override bool CanRead - { - get - { - return _stream.CanRead; - } - } + public override bool CanSeek => _stream.CanSeek; - public override bool CanSeek - { - get - { - return _stream.CanSeek; - } - } + public abstract override bool CanRead { get; } - public override bool CanWrite - { - get - { - return _stream.CanWrite; - } - } + public abstract override bool CanWrite { get; } public override long Length { @@ -79,43 +56,38 @@ public override long Position } } - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) + public sealed override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { - if (!CanRead) - throw new NotSupportedException(SR.ReadNotSupported); - - return _stream.BeginRead(buffer, offset, count, callback, state); + return TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state); } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) + public sealed override int EndRead(IAsyncResult asyncResult) { - if (!CanWrite) - throw new NotSupportedException(SR.WriteNotSupported); - - return _stream.BeginWrite(buffer, offset, count, callback, state); + return TaskToAsyncResult.End(asyncResult); } - //This calls close on the inner stream - //however, the stream may not be actually closed, but simpy flushed - public override void Close() + public sealed override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { - _stream.Close(); + return TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), callback, state); } - public override int EndRead(IAsyncResult asyncResult) + public sealed override void EndWrite(IAsyncResult asyncResult) { - if (!CanRead) - throw new NotSupportedException(SR.ReadNotSupported); - - return _stream.EndRead(asyncResult); + TaskToAsyncResult.End(asyncResult); } - public override void EndWrite(IAsyncResult asyncResult) + public override void Close() { - if (!CanWrite) - throw new NotSupportedException(SR.WriteNotSupported); + _stream.Close(); + base.Close(); + } - _stream.EndWrite(asyncResult); + protected override void Dispose(bool disposing) + { + if (disposing) + { + _stream.Dispose(); + } + base.Dispose(disposing); } public override void Flush() @@ -128,31 +100,60 @@ public override Task FlushAsync(CancellationToken cancellationToken) return _stream.FlushAsync(cancellationToken); } - public override int Read(byte[] buffer, int offset, int count) + // Abstract methods for derived classes to implement core logic + protected abstract int ReadInternal(Span buffer); + + protected abstract ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken); + + protected abstract void WriteInternal(ReadOnlySpan buffer); + + protected abstract ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken); + + // Sealed methods implementing the Stream Read/Write methods + public sealed override int Read(Span buffer) { if (!CanRead) throw new NotSupportedException(SR.ReadNotSupported); - return _stream.Read(buffer, offset, count); + return ReadInternal(buffer); } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public sealed override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) { if (!CanRead) throw new NotSupportedException(SR.ReadNotSupported); - return _stream.ReadAsync(buffer, offset, count, cancellationToken); + return ReadAsyncInternal(buffer, cancellationToken); } - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + public sealed override int Read(byte[] buffer, int offset, int count) { + ValidateBufferArguments(buffer, offset, count); if (!CanRead) throw new NotSupportedException(SR.ReadNotSupported); - return _stream.ReadAsync(buffer, cancellationToken); + return ReadInternal(buffer.AsSpan(offset, count)); } - public override long Seek(long offset, SeekOrigin origin) + public sealed override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + ValidateBufferArguments(buffer, offset, count); + if (!CanRead) + throw new NotSupportedException(SR.ReadNotSupported); + + return ReadAsyncInternal(buffer.AsMemory(offset, count), cancellationToken).AsTask(); + } + + public sealed override int ReadByte() + { + if (!CanRead) + throw new NotSupportedException(SR.ReadNotSupported); + + byte b = 0; + return ReadInternal(new Span(ref b)) != 0 ? b : -1; + } + + public sealed override long Seek(long offset, SeekOrigin origin) { if (!CanSeek) throw new NotSupportedException(SR.SeekNotSupported); @@ -160,7 +161,7 @@ public override long Seek(long offset, SeekOrigin origin) return _stream.Seek(offset, origin); } - public override void SetLength(long value) + public sealed override void SetLength(long value) { if (!CanSeek) throw new NotSupportedException(SR.SeekNotSupported); @@ -168,28 +169,38 @@ public override void SetLength(long value) _stream.SetLength(value); } - public override void Write(byte[] buffer, int offset, int count) + public sealed override void Write(ReadOnlySpan buffer) + { + if (!CanWrite) + throw new NotSupportedException(SR.WriteNotSupported); + + WriteInternal(buffer); + } + + public sealed override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { if (!CanWrite) throw new NotSupportedException(SR.WriteNotSupported); - _stream.Write(buffer, offset, count); + return WriteAsyncInternal(buffer, cancellationToken); } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public sealed override void Write(byte[] buffer, int offset, int count) { + ValidateBufferArguments(buffer, offset, count); if (!CanWrite) throw new NotSupportedException(SR.WriteNotSupported); - return _stream.WriteAsync(buffer, offset, count, cancellationToken); + WriteInternal(buffer.AsSpan(offset, count)); } - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + public sealed override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + ValidateBufferArguments(buffer, offset, count); if (!CanWrite) throw new NotSupportedException(SR.WriteNotSupported); - return _stream.WriteAsync(buffer, cancellationToken); + return WriteAsyncInternal(buffer.AsMemory(offset, count), cancellationToken).AsTask(); } } } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs index dc60300bce1c1e..03d32a7e44d608 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpReplyReaderFactory.cs @@ -75,7 +75,7 @@ internal void Close(SmtpReplyReader caller) { _byteBuffer ??= new byte[SmtpReplyReaderFactory.DefaultBufferSize]; - while (0 != Read(caller, _byteBuffer, 0, _byteBuffer.Length)) ; + while (0 != Read(caller, _byteBuffer)) ; } _currentReader = null; @@ -106,10 +106,10 @@ internal SmtpReplyReader GetNextReplyReader() return _currentReader; } - private int ProcessRead(byte[] buffer, int offset, int read, bool readLine) + private int ProcessRead(ReadOnlySpan buffer, bool readLine) { // if 0 bytes were read,there was a failure - if (read == 0) + if (buffer.Length == 0) { throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); } @@ -118,9 +118,9 @@ private int ProcessRead(byte[] buffer, int offset, int read, bool readLine) { fixed (byte* pBuffer = buffer) { - byte* start = pBuffer + offset; + byte* start = pBuffer; byte* ptr = start; - byte* end = ptr + read; + byte* end = ptr + buffer.Length; switch (_readState) { @@ -263,20 +263,20 @@ private int ProcessRead(byte[] buffer, int offset, int read, bool readLine) } } - internal int Read(SmtpReplyReader caller, byte[] buffer, int offset, int count) + internal int Read(SmtpReplyReader caller, Span buffer) { - // if we've already found the delimitter, then return 0 indicating + // if we've already found the delimiter, then return 0 indicating // end of stream. - if (count == 0 || _currentReader != caller || _readState == ReadState.Done) + if (buffer.Length == 0 || _currentReader != caller || _readState == ReadState.Done) { return 0; } - int read = _bufferedStream.Read(buffer, offset, count); - int actual = ProcessRead(buffer, offset, read, false); + int read = _bufferedStream.Read(buffer); + int actual = ProcessRead(buffer.Slice(0, read), false); if (actual < read) { - _bufferedStream.Push(buffer, offset + actual, read - actual); + _bufferedStream.Push(buffer.Slice(actual, read - actual)); } return actual; @@ -316,11 +316,11 @@ internal LineInfo[] ReadLines(SmtpReplyReader caller, bool oneLine) { if (start == read) { - read = _bufferedStream.Read(_byteBuffer, 0, _byteBuffer.Length); + read = _bufferedStream.Read(_byteBuffer); start = 0; } - int actual = ProcessRead(_byteBuffer, start, read - start, true); + int actual = ProcessRead(_byteBuffer.AsSpan(start, read), true); if (statusRead < 4) { @@ -344,7 +344,7 @@ internal LineInfo[] ReadLines(SmtpReplyReader caller, bool oneLine) if (oneLine) { - _bufferedStream.Push(_byteBuffer, start, read - start); + _bufferedStream.Push(_byteBuffer.AsSpan(start, read - start)); return lines.ToArray(); } builder = new StringBuilder(); @@ -352,7 +352,7 @@ internal LineInfo[] ReadLines(SmtpReplyReader caller, bool oneLine) else if (_readState == ReadState.Done) { lines.Add(new LineInfo(_statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF - _bufferedStream.Push(_byteBuffer, start, read - start); + _bufferedStream.Push(_byteBuffer.AsSpan(start, read - start)); return lines.ToArray(); } } @@ -454,7 +454,7 @@ private bool ProcessRead() for (int start = 0; start != _read;) { - int actual = _parent.ProcessRead(_parent._byteBuffer!, start, _read - start, true); + int actual = _parent.ProcessRead(_parent._byteBuffer!.AsSpan(start, _read - start), true); if (_statusRead < 4) { @@ -479,7 +479,7 @@ private bool ProcessRead() if (_oneLine) { - _parent._bufferedStream.Push(_parent._byteBuffer!, start, _read - start); + _parent._bufferedStream.Push(_parent._byteBuffer!.AsSpan(start, _read - start)); InvokeCallback(); return false; } @@ -487,7 +487,7 @@ private bool ProcessRead() else if (_parent._readState == ReadState.Done) { _lines!.Add(new LineInfo(_parent._statusCode, _builder.ToString(0, _builder.Length - 2))); // return everything except CRLF - _parent._bufferedStream.Push(_parent._byteBuffer!, start, _read - start); + _parent._bufferedStream.Push(_parent._byteBuffer!.AsSpan(start, _read - start)); InvokeCallback(); return false; } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/Base64Encoder.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/Base64Encoder.cs index 65dacb82da5e8a..56e2cbe8f422aa 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/Base64Encoder.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/Base64Encoder.cs @@ -42,9 +42,9 @@ protected override bool LineBreakNeeded(byte b) return LineBreakNeeded(1); } - protected override bool LineBreakNeeded(byte[] bytes, int count) + protected override bool LineBreakNeeded(ReadOnlySpan bytes) { - return LineBreakNeeded(count); + return LineBreakNeeded(bytes.Length); } private bool LineBreakNeeded(int numberOfBytesToAppend) @@ -117,7 +117,7 @@ public override void AppendPadding() } } - protected override void ApppendEncodedByte(byte b) + protected override void AppendEncodedByte(byte b) { // Base64 encoding transforms a group of 3 bytes into a group of 4 Base64 characters switch (_writeState.Padding) diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/ByteEncoder.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/ByteEncoder.cs index 0e7f6d964c2815..9fca67e1ece992 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/ByteEncoder.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/ByteEncoder.cs @@ -14,15 +14,15 @@ internal abstract class ByteEncoder : IByteEncoder public string GetEncodedString() => Encoding.ASCII.GetString(WriteState.Buffer, 0, WriteState.Length); - public int EncodeBytes(byte[] buffer, int offset, int count, bool dontDeferFinalBytes, bool shouldAppendSpaceToCRLF) + public int EncodeBytes(ReadOnlySpan buffer, bool dontDeferFinalBytes, bool shouldAppendSpaceToCRLF) { // Add Encoding header, if any. e.g. =?encoding?b? WriteState.AppendHeader(); bool _hasSpecialEncodingForCRLF = HasSpecialEncodingForCRLF; - int cur = offset; - for (; cur < count + offset; cur++) + int cur = 0; + for (; cur < buffer.Length; cur++) { if (LineBreakNeeded(buffer[cur])) { @@ -30,14 +30,14 @@ public int EncodeBytes(byte[] buffer, int offset, int count, bool dontDeferFinal WriteState.AppendCRLF(shouldAppendSpaceToCRLF); } - if (_hasSpecialEncodingForCRLF && IsCRLF(buffer, cur, count + offset)) + if (_hasSpecialEncodingForCRLF && buffer.Slice(cur).StartsWith("\r\n"u8)) { AppendEncodedCRLF(); cur++; // Transformed two chars, so shift the index to account for that } else { - ApppendEncodedByte(buffer[cur]); + AppendEncodedByte(buffer[cur]); } } @@ -48,7 +48,7 @@ public int EncodeBytes(byte[] buffer, int offset, int count, bool dontDeferFinal // Write out the last footer, if any. e.g. ?= WriteState.AppendFooter(); - return cur - offset; + return cur; } public int EncodeString(string value, Encoding encoding) @@ -57,10 +57,11 @@ public int EncodeString(string value, Encoding encoding) Debug.Assert(WriteState != null, "writestate was null"); Debug.Assert(WriteState.Buffer != null, "writestate.buffer was null"); + byte[] buffer; if (encoding == Encoding.Latin1) // we don't need to check for codepoints { - byte[] buffer = encoding.GetBytes(value); - return EncodeBytes(buffer, 0, buffer.Length, true, true); + buffer = encoding.GetBytes(value); + return EncodeBytes(buffer, true, true); } // Add Encoding header, if any. e.g. =?encoding?b? @@ -69,31 +70,33 @@ public int EncodeString(string value, Encoding encoding) bool _hasSpecialEncodingForCRLF = HasSpecialEncodingForCRLF; int totalBytesCount = 0; - byte[] bytes = new byte[encoding.GetMaxByteCount(2)]; + buffer = new byte[encoding.GetMaxByteCount(2)]; for (int i = 0; i < value.Length; ++i) { int codepointSize = GetCodepointSize(value, i); Debug.Assert(codepointSize == 1 || codepointSize == 2, "codepointSize was not 1 or 2"); - int bytesCount = encoding.GetBytes(value, i, codepointSize, bytes, 0); + int bytesCount = encoding.GetBytes(value, i, codepointSize, buffer, 0); + Span bytes = buffer.AsSpan(0, bytesCount); + if (codepointSize == 2) { ++i; // Transformed two chars, so shift the index to account for that } - if (LineBreakNeeded(bytes, bytesCount)) + if (LineBreakNeeded(bytes)) { AppendPadding(); WriteState.AppendCRLF(true); } - if (_hasSpecialEncodingForCRLF && IsCRLF(bytes, bytesCount)) + if (_hasSpecialEncodingForCRLF && IsCRLF(bytes)) { AppendEncodedCRLF(); } else { - AppendEncodedCodepoint(bytes, bytesCount); + AppendEncodedCodepoint(bytes); } totalBytesCount += bytesCount; } @@ -109,19 +112,19 @@ public int EncodeString(string value, Encoding encoding) protected abstract void AppendEncodedCRLF(); protected abstract bool LineBreakNeeded(byte b); - protected abstract bool LineBreakNeeded(byte[] bytes, int count); + protected abstract bool LineBreakNeeded(ReadOnlySpan bytes); protected abstract int GetCodepointSize(string value, int i); public abstract void AppendPadding(); - protected abstract void ApppendEncodedByte(byte b); + protected abstract void AppendEncodedByte(byte b); - private void AppendEncodedCodepoint(byte[] bytes, int count) + private void AppendEncodedCodepoint(ReadOnlySpan bytes) { - for (int i = 0; i < count; ++i) + foreach (byte b in bytes) { - ApppendEncodedByte(bytes[i]); + AppendEncodedByte(b); } } @@ -130,14 +133,9 @@ protected static bool IsSurrogatePair(string value, int i) return char.IsSurrogate(value[i]) && i + 1 < value.Length && char.IsSurrogatePair(value[i], value[i + 1]); } - protected static bool IsCRLF(byte[] bytes, int count) - { - return count == 2 && IsCRLF(bytes, 0, count); - } - - private static bool IsCRLF(byte[] buffer, int i, int bufferSize) + protected static bool IsCRLF(ReadOnlySpan buffer) { - return buffer[i] == '\r' && i + 1 < bufferSize && buffer[i + 1] == '\n'; + return buffer.SequenceEqual("\r\n"u8); } } } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/EightBitStream.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/EightBitStream.cs index f9aed4bc21ea0a..ae92cf7aeb8def 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/EightBitStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/EightBitStream.cs @@ -3,6 +3,9 @@ using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Buffers; namespace System.Net.Mime { @@ -43,59 +46,48 @@ internal EightBitStream(Stream stream, bool shouldEncodeLeadingDots) : this(stre _shouldEncodeLeadingDots = shouldEncodeLeadingDots; } - /// - /// Writes the specified content to the underlying stream - /// - /// Buffer to write - /// Offset within buffer to start writing - /// Count of bytes to write - /// Callback to call when write completes - /// State to pass to callback - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) + public override bool CanRead => false; + public override bool CanWrite => BaseStream.CanWrite; + + protected override int ReadInternal(Span buffer) { - ValidateBufferArguments(buffer, offset, count); + throw new NotImplementedException(); + } - IAsyncResult result; + protected override ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + // Implement abstract Write methods + protected override void WriteInternal(ReadOnlySpan buffer) + { if (_shouldEncodeLeadingDots) { - EncodeLines(buffer, offset, count); - result = base.BeginWrite(WriteState.Buffer, 0, WriteState.Length, callback, state); + EncodeLines(buffer); + BaseStream.Write(WriteState.Buffer.AsSpan(0, WriteState.Length)); + WriteState.BufferFlushed(); } else { // Note: for legacy reasons we are not enforcing buffer[i] <= 127. - result = base.BeginWrite(buffer, offset, count, callback, state); + BaseStream.Write(buffer); } - - return result; - } - - public override void EndWrite(IAsyncResult asyncResult) - { - base.EndWrite(asyncResult); - WriteState.BufferFlushed(); } - /// - /// Writes the specified content to the underlying stream - /// - /// Buffer to write - /// Offset within buffer to start writing - /// Count of bytes to write - public override void Write(byte[] buffer, int offset, int count) + protected override ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { - ValidateBufferArguments(buffer, offset, count); - if (_shouldEncodeLeadingDots) { - EncodeLines(buffer, offset, count); - base.Write(WriteState.Buffer, 0, WriteState.Length); - WriteState.BufferFlushed(); + EncodeLines(buffer.Span); + ValueTask task = BaseStream.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken); + WriteState.BufferFlushed(); // Reset state after initiating async write + return task; } else { // Note: for legacy reasons we are not enforcing buffer[i] <= 127. - base.Write(buffer, offset, count); + return BaseStream.WriteAsync(buffer, cancellationToken); } } @@ -103,14 +95,14 @@ public override void Write(byte[] buffer, int offset, int count) // Despite not having to encode content, we still have to implement // RFC 2821 Section 4.5.2 about leading dots on a line - private void EncodeLines(byte[] buffer, int offset, int count) + private void EncodeLines(ReadOnlySpan buffer) { - for (int i = offset; (i < offset + count) && (i < buffer.Length); i++) + for (int i = 0; i < buffer.Length; i++) { // Note: for legacy reasons we are not enforcing buffer[i] <= 127. // Detect CRLF line endings - if ((buffer[i] == '\r') && ((i + 1) < (offset + count)) && (buffer[i + 1] == '\n')) + if ((buffer[i] == '\r') && ((i + 1) < buffer.Length) && (buffer[i + 1] == '\n')) { WriteState.AppendCRLF(false); // Resets CurrentLineLength to 0 i++; // Skip past the recorded CRLF @@ -130,9 +122,9 @@ private void EncodeLines(byte[] buffer, int offset, int count) } } - public int DecodeBytes(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } + public int DecodeBytes(Span buffer) { throw new NotImplementedException(); } - public int EncodeBytes(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } + public int EncodeBytes(ReadOnlySpan buffer) { throw new NotImplementedException(); } public int EncodeString(string value, Encoding encoding) { throw new NotImplementedException(); } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/IByteEncoder.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/IByteEncoder.cs index 33af3546eeb71c..4fe429f3c59ed0 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/IByteEncoder.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/IByteEncoder.cs @@ -8,7 +8,7 @@ namespace System.Net.Mime internal interface IByteEncoder { // This method does not account for codepoint boundaries. If encoding a string, consider using EncodeString - int EncodeBytes(byte[] buffer, int offset, int count, bool dontDeferFinalBytes, bool shouldAppendSpaceToCRLF); + int EncodeBytes(ReadOnlySpan buffer, bool dontDeferFinalBytes, bool shouldAppendSpaceToCRLF); void AppendPadding(); int EncodeString(string value, Encoding encoding); string GetEncodedString(); diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/IEncodableStream.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/IEncodableStream.cs index 43bd90210af34b..6a1122f8845379 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/IEncodableStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/IEncodableStream.cs @@ -7,9 +7,9 @@ namespace System.Net.Mime { internal interface IEncodableStream { - int DecodeBytes(byte[] buffer, int offset, int count); + int DecodeBytes(Span buffer); // This method does not account for codepoint boundaries. If encoding a string, consider using EncodeString - int EncodeBytes(byte[] buffer, int offset, int count); + int EncodeBytes(ReadOnlySpan buffer); int EncodeString(string value, Encoding encoding); string GetEncodedString(); } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs index 332f9db055e27f..804c067eb201cb 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/MimeBasePart.cs @@ -76,7 +76,7 @@ internal static string DecodeHeaderValue(string? value) IEncodableStream s = EncodedStreamFactory.GetEncoderForHeader(Encoding.GetEncoding(charSet), base64Encoding, 0); - newLength = s.DecodeBytes(buffer, 0, buffer.Length); + newLength = s.DecodeBytes(buffer); Encoding encoding = Encoding.GetEncoding(charSet); newValue += encoding.GetString(buffer, 0, newLength); diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs index 47d3026e6aafd9..cafb9b0577e4ff 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Buffers; namespace System.Net.Mime { @@ -52,8 +53,8 @@ internal QEncodedStream(WriteStateInfoBase wsi) : base(new MemoryStream()) internal WriteStateInfoBase WriteState => _writeState; - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => - TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), callback, state); + public override bool CanRead => BaseStream.CanRead; + public override bool CanWrite => BaseStream.CanWrite; public override void Close() { @@ -61,14 +62,14 @@ public override void Close() base.Close(); } - public unsafe int DecodeBytes(byte[] buffer, int offset, int count) + public unsafe int DecodeBytes(Span buffer) { fixed (byte* pBuffer = buffer) { - byte* start = pBuffer + offset; + byte* start = pBuffer; byte* source = start; byte* dest = start; - byte* end = start + count; + byte* end = start + buffer.Length; // if the last read ended in a partially decoded // sequence, pick up where we left off. @@ -81,7 +82,7 @@ public unsafe int DecodeBytes(byte[] buffer, int offset, int count) // if we only read one byte from the underlying // stream, we'll need to save the byte and // ask for more. - if (count == 1) + if (buffer.Length == 1) { ReadState.Byte = *source; return 0; @@ -179,15 +180,12 @@ public unsafe int DecodeBytes(byte[] buffer, int offset, int count) } } - public int EncodeBytes(byte[] buffer, int offset, int count) => _encoder.EncodeBytes(buffer, offset, count, true, true); + public int EncodeBytes(ReadOnlySpan buffer) => _encoder.EncodeBytes(buffer, true, true); public int EncodeString(string value, Encoding encoding) => _encoder.EncodeString(value, encoding); public string GetEncodedString() => _encoder.GetEncodedString(); - public override void EndWrite(IAsyncResult asyncResult) => - TaskToAsyncResult.End(asyncResult); - public override void Flush() { FlushInternal(); @@ -195,34 +193,46 @@ public override void Flush() } public override async Task FlushAsync(CancellationToken cancellationToken) + { + await FlushInternalAsync(cancellationToken).ConfigureAwait(false); + await base.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + private void FlushInternal() { if (_writeState != null && _writeState.Length > 0) { - await base.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken).ConfigureAwait(false); + BaseStream.Write(WriteState.Buffer.AsSpan(0, WriteState.Length)); WriteState.Reset(); } - - await base.FlushAsync(cancellationToken).ConfigureAwait(false); } - private void FlushInternal() + private async ValueTask FlushInternalAsync(CancellationToken cancellationToken) { if (_writeState != null && _writeState.Length > 0) { - base.Write(WriteState.Buffer, 0, WriteState.Length); + await BaseStream.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken).ConfigureAwait(false); WriteState.Reset(); } } - public override void Write(byte[] buffer, int offset, int count) + protected override int ReadInternal(Span buffer) { - ValidateBufferArguments(buffer, offset, count); + throw new NotImplementedException(); + } + protected override ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + protected override void WriteInternal(ReadOnlySpan buffer) + { int written = 0; while (true) { - written += EncodeBytes(buffer, offset + written, count - written); - if (written < count) + written += EncodeBytes(buffer.Slice(written)); + if (written < buffer.Length) { FlushInternal(); } @@ -233,31 +243,23 @@ public override void Write(byte[] buffer, int offset, int count) } } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + protected override async ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { - ValidateBufferArguments(buffer, offset, count); - return WriteAsyncCore(buffer, offset, count, cancellationToken); - - async Task WriteAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + int written = 0; + while (true) { - int written = 0; - while (true) + written += EncodeBytes(buffer.Span.Slice(written)); + if (written < buffer.Length) { - written += EncodeBytes(buffer, offset + written, count - written); - if (written < count) - { - await FlushAsync(cancellationToken).ConfigureAwait(false); - } - else - { - break; - } + await FlushInternalAsync(cancellationToken).ConfigureAwait(false); + } + else + { + break; } } } - - private sealed class ReadStateInfo { internal bool IsEscaped { get; set; } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs index 9ba70456d66567..8bf8b7d67fdf0e 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncoder.cs @@ -44,14 +44,14 @@ protected override bool LineBreakNeeded(byte b) return false; } - protected override bool LineBreakNeeded(byte[] bytes, int count) + protected override bool LineBreakNeeded(ReadOnlySpan bytes) { - if (count == 1 || IsCRLF(bytes, count)) // preserve same behavior as in EncodeBytes + if (bytes.Length == 1 || IsCRLF(bytes)) // preserve same behavior as in EncodeBytes { return LineBreakNeeded(bytes[0]); } - int numberOfCharsToAppend = count * SizeOfQEncodedChar; + int numberOfCharsToAppend = bytes.Length * SizeOfQEncodedChar; return WriteState.CurrentLineLength + numberOfCharsToAppend + _writeState.FooterLength > WriteState.MaxLineLength; } @@ -74,7 +74,7 @@ protected override int GetCodepointSize(string value, int i) // no padding in q-encoding public override void AppendPadding() { } - protected override void ApppendEncodedByte(byte b) + protected override void AppendEncodedByte(byte b) { if (b == ' ') { diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/QuotedPrintableStream.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/QuotedPrintableStream.cs index 6440ea0d7cc3dd..773e12b430f6c5 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/QuotedPrintableStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/QuotedPrintableStream.cs @@ -78,27 +78,27 @@ internal QuotedPrintableStream(Stream stream, bool encodeCRLF) : this(stream, En _encodeCRLF = encodeCRLF; } + public override bool CanRead => false; + public override bool CanWrite => BaseStream.CanWrite; + private ReadStateInfo ReadState => _readState ??= new ReadStateInfo(); internal WriteStateInfoBase WriteState => _writeState ??= new WriteStateInfoBase(1024, null, null, _lineLength); - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => - TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count, CancellationToken.None), callback, state); - public override void Close() { FlushInternal(); base.Close(); } - public unsafe int DecodeBytes(byte[] buffer, int offset, int count) + public unsafe int DecodeBytes(Span buffer) { fixed (byte* pBuffer = buffer) { - byte* start = pBuffer + offset; + byte* start = pBuffer; byte* source = start; byte* dest = start; - byte* end = start + count; + byte* end = start + buffer.Length; // if the last read ended in a partially decoded // sequence, pick up where we left off. @@ -111,7 +111,7 @@ public unsafe int DecodeBytes(byte[] buffer, int offset, int count) // if we only read one byte from the underlying // stream, we'll need to save the byte and // ask for more. - if (count == 1) + if (buffer.Length == 1) { ReadState.Byte = *source; return 0; @@ -201,20 +201,20 @@ public unsafe int DecodeBytes(byte[] buffer, int offset, int count) } } - public int EncodeBytes(byte[] buffer, int offset, int count) + public int EncodeBytes(ReadOnlySpan buffer) { - int cur = offset; - for (; cur < count + offset; cur++) + int processed = 0; + for (; processed < buffer.Length; processed++) { //only fold if we're before a whitespace or if we're at the line limit //add two to the encoded Byte Length to be conservative so that we guarantee that the line length is acceptable - if ((_lineLength != -1 && WriteState.CurrentLineLength + SizeOfEncodedChar + 2 >= _lineLength && (buffer[cur] == ' ' || - buffer[cur] == '\t' || buffer[cur] == '\r' || buffer[cur] == '\n')) || + if ((_lineLength != -1 && WriteState.CurrentLineLength + SizeOfEncodedChar + 2 >= _lineLength && (buffer[processed] == ' ' || + buffer[processed] == '\t' || buffer[processed] == '\r' || buffer[processed] == '\n')) || _writeState!.CurrentLineLength + SizeOfEncodedChar + 2 >= EncodedStreamFactory.DefaultMaxLineLength) { if (WriteState.Buffer.Length - WriteState.Length < SizeOfSoftCRLF) { - return cur - offset; //ok because folding happens externally + return processed; //ok because folding happens externally } WriteState.Append((byte)'='); @@ -225,13 +225,13 @@ public int EncodeBytes(byte[] buffer, int offset, int count) // it is done by the underlying 7BitStream //detect a CRLF in the input and encode it. - if (buffer[cur] == '\r' && cur + 1 < count + offset && buffer[cur + 1] == '\n') + if (buffer[processed] == '\r' && processed + 1 < buffer.Length && buffer[processed + 1] == '\n') { if (WriteState.Buffer.Length - WriteState.Length < (_encodeCRLF ? SizeOfEncodedCRLF : SizeOfNonEncodedCRLF)) { - return cur - offset; + return processed; } - cur++; + processed++; if (_encodeCRLF) { @@ -244,65 +244,62 @@ public int EncodeBytes(byte[] buffer, int offset, int count) } } //ascii chars less than 32 (control chars) and greater than 126 (non-ascii) are not allowed so we have to encode - else if ((buffer[cur] < 32 && buffer[cur] != '\t') || - buffer[cur] == '=' || - buffer[cur] > 126) + else if ((buffer[processed] < 32 && buffer[processed] != '\t') || + buffer[processed] == '=' || + buffer[processed] > 126) { if (WriteState.Buffer.Length - WriteState.Length < SizeOfSoftCRLF) { - return cur - offset; + return processed; } //append an = to indicate an encoded character WriteState.Append((byte)'='); //shift 4 to get the first four bytes only and look up the hex digit - WriteState.Append(HexEncodeMap[buffer[cur] >> 4]); + WriteState.Append(HexEncodeMap[buffer[processed] >> 4]); //clear the first four bytes to get the last four and look up the hex digit - WriteState.Append(HexEncodeMap[buffer[cur] & 0xF]); + WriteState.Append(HexEncodeMap[buffer[processed] & 0xF]); } else { if (WriteState.Buffer.Length - WriteState.Length < 1) { - return cur - offset; + return processed; } //detect special case: is whitespace at end of line? we must encode it if it is - if ((buffer[cur] == (byte)'\t' || buffer[cur] == (byte)' ') && - (cur + 1 >= count + offset)) + if ((buffer[processed] == (byte)'\t' || buffer[processed] == (byte)' ') && + (processed + 1 >= buffer.Length)) { if (WriteState.Buffer.Length - WriteState.Length < SizeOfEncodedChar) { - return cur - offset; + return processed; } //append an = to indicate an encoded character WriteState.Append((byte)'='); //shift 4 to get the first four bytes only and look up the hex digit - WriteState.Append(HexEncodeMap[buffer[cur] >> 4]); + WriteState.Append(HexEncodeMap[buffer[processed] >> 4]); //clear the first four bytes to get the last four and look up the hex digit - WriteState.Append(HexEncodeMap[buffer[cur] & 0xF]); + WriteState.Append(HexEncodeMap[buffer[processed] & 0xF]); } else { - WriteState.Append(buffer[cur]); + WriteState.Append(buffer[processed]); } } } - return cur - offset; + return processed; } public int EncodeString(string value, Encoding encoding) { byte[] buffer = encoding.GetBytes(value); - return EncodeBytes(buffer, 0, buffer.Length); + return EncodeBytes(buffer); } public string GetEncodedString() => Encoding.ASCII.GetString(WriteState.Buffer, 0, WriteState.Length); - public override void EndWrite(IAsyncResult asyncResult) => - TaskToAsyncResult.End(asyncResult); - public override void Flush() { FlushInternal(); @@ -310,34 +307,46 @@ public override void Flush() } public override async Task FlushAsync(CancellationToken cancellationToken) + { + await FlushInternalAsync(cancellationToken).ConfigureAwait(false); + await base.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + private async ValueTask FlushInternalAsync(CancellationToken cancellationToken) { if (_writeState != null && _writeState.Length > 0) { - await base.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken).ConfigureAwait(false); + await BaseStream.WriteAsync(WriteState.Buffer.AsMemory(0, WriteState.Length), cancellationToken).ConfigureAwait(false); WriteState.BufferFlushed(); } - - await base.FlushAsync(cancellationToken).ConfigureAwait(false); } private void FlushInternal() { if (_writeState != null && _writeState.Length > 0) { - base.Write(WriteState.Buffer, 0, WriteState.Length); + BaseStream.Write(WriteState.Buffer, 0, WriteState.Length); WriteState.BufferFlushed(); } } - public override void Write(byte[] buffer, int offset, int count) + protected override int ReadInternal(Span buffer) { - ValidateBufferArguments(buffer, offset, count); + throw new NotImplementedException(); + } + protected override ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + protected override void WriteInternal(ReadOnlySpan buffer) + { int written = 0; while (true) { - written += EncodeBytes(buffer, offset + written, count - written); - if (written < count) + written += EncodeBytes(buffer.Slice(written)); + if (written < buffer.Length) { FlushInternal(); } @@ -348,30 +357,23 @@ public override void Write(byte[] buffer, int offset, int count) } } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + protected override async ValueTask WriteAsyncInternal(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { - ValidateBufferArguments(buffer, offset, count); - return WriteAsyncCore(buffer, offset, count, cancellationToken); - - async Task WriteAsyncCore(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + int written = 0; + while (true) { - int written = 0; - while (true) + written += EncodeBytes(buffer.Span.Slice(written)); + if (written < buffer.Length) { - written += EncodeBytes(buffer, offset + written, count - written); - if (written < count) - { - await FlushAsync(cancellationToken).ConfigureAwait(false); - } - else - { - break; - } + await FlushInternalAsync(cancellationToken).ConfigureAwait(false); + } + else + { + break; } } } - private sealed class ReadStateInfo { internal bool IsEscaped { get; set; } diff --git a/src/libraries/System.Net.Mail/tests/Unit/Base64EncodingTest.cs b/src/libraries/System.Net.Mail/tests/Unit/Base64EncodingTest.cs index d441cb2bbc9ad1..926ded2cabb795 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/Base64EncodingTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/Base64EncodingTest.cs @@ -15,7 +15,7 @@ public void Base64Stream_WithBasicAsciiString_ShouldEncodeAndDecode(string testH { var s = new Base64Stream(new Base64WriteStateInfo()); var testHeaderBytes = Encoding.UTF8.GetBytes(testHeader); - s.EncodeBytes(testHeaderBytes, 0, testHeaderBytes.Length); + s.EncodeBytes(testHeaderBytes); string encodedString = s.GetEncodedString(); for (int i = 0; i < encodedString.Length; i++) @@ -24,7 +24,7 @@ public void Base64Stream_WithBasicAsciiString_ShouldEncodeAndDecode(string testH } byte[] stringToDecode = Encoding.ASCII.GetBytes(encodedString); - int result = s.DecodeBytes(stringToDecode, 0, encodedString.Length); + int result = s.DecodeBytes(stringToDecode.AsSpan(0, encodedString.Length)); Assert.Equal(testHeader, Encoding.UTF8.GetString(stringToDecode, 0, result)); } @@ -43,7 +43,7 @@ public void Base64Stream_EncodeString_WithBasicAsciiString_ShouldEncodeAndDecode } byte[] stringToDecode = Encoding.ASCII.GetBytes(encodedString); - int result = s.DecodeBytes(stringToDecode, 0, encodedString.Length); + int result = s.DecodeBytes(stringToDecode.AsSpan(0, encodedString.Length)); Assert.Equal(testHeader, Encoding.UTF8.GetString(stringToDecode, 0, result)); } @@ -55,13 +55,13 @@ public void Base64Stream_WithVerySmallBuffer_ShouldTriggerBufferResize_AndShould const string TestString = "0123456789abcdef"; byte[] buffer = Encoding.UTF8.GetBytes(TestString); - s.EncodeBytes(buffer, 0, buffer.Length); + s.EncodeBytes(buffer); string encodedString = s.GetEncodedString(); Assert.Equal("MDEyMzQ1Njc4OWFiY2RlZg==", encodedString); byte[] stringToDecode = Encoding.ASCII.GetBytes(encodedString); - int result = s.DecodeBytes(stringToDecode, 0, encodedString.Length); + int result = s.DecodeBytes(stringToDecode.AsSpan(0, encodedString.Length)); Assert.Equal(TestString, Encoding.UTF8.GetString(stringToDecode, 0, result)); } @@ -79,7 +79,7 @@ public void Base64Stream_EncodeString_WithVerySmallBuffer_ShouldTriggerBufferRes Assert.Equal("MDEyMzQ1Njc4OWFiY2RlZg==", encodedString); byte[] stringToDecode = Encoding.ASCII.GetBytes(encodedString); - int result = s.DecodeBytes(stringToDecode, 0, encodedString.Length); + int result = s.DecodeBytes(stringToDecode.AsSpan(0, encodedString.Length)); Assert.Equal(TestString, Encoding.UTF8.GetString(stringToDecode, 0, result)); } @@ -91,11 +91,11 @@ public void Base64Stream_WithVeryLongString_ShouldEncodeProperly() var s = new Base64Stream(writeStateInfo); byte[] buffer = Encoding.UTF8.GetBytes(LongString); - s.EncodeBytes(buffer, 0, buffer.Length); + s.EncodeBytes(buffer); string encodedString = s.GetEncodedString(); byte[] stringToDecode = Encoding.ASCII.GetBytes(encodedString); - int result = s.DecodeBytes(stringToDecode, 0, encodedString.Length); + int result = s.DecodeBytes(stringToDecode.AsSpan(0, encodedString.Length)); Assert.Equal(LongString, Encoding.UTF8.GetString(stringToDecode, 0, result)); } @@ -110,7 +110,7 @@ public void Base64Stream_EncodeString_WithVeryLongString_ShouldEncodeProperly() string encodedString = s.GetEncodedString(); byte[] stringToDecode = Encoding.ASCII.GetBytes(encodedString); - int result = s.DecodeBytes(stringToDecode, 0, encodedString.Length); + int result = s.DecodeBytes(stringToDecode.AsSpan(0, encodedString.Length)); Assert.Equal(LongString, Encoding.UTF8.GetString(stringToDecode, 0, result)); } diff --git a/src/libraries/System.Net.Mail/tests/Unit/ByteEncodingTest.cs b/src/libraries/System.Net.Mail/tests/Unit/ByteEncodingTest.cs index 37c7e0aefcf59a..1d0fc9db466d57 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/ByteEncodingTest.cs +++ b/src/libraries/System.Net.Mail/tests/Unit/ByteEncodingTest.cs @@ -121,7 +121,7 @@ public void EncodeString_IsSameAsEncodeBytes_IfOneByteCodepointOnLineWrap(bool u string encodeStringResult = streamForEncodeString.GetEncodedString(); byte[] bytes = Encoding.UTF8.GetBytes(value); - streamForEncodeBytes.EncodeBytes(bytes, 0, bytes.Length); + streamForEncodeBytes.EncodeBytes(bytes); string encodeBytesResult = streamForEncodeBytes.GetEncodedString(); Assert.Equal(encodeBytesResult, encodeStringResult);