< Summary

Information
Class: NanoRoute.AwsLambda.Base64BodyWriterStream
Assembly: NanoRoute.AwsLambda.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute.AwsLambda/Private/Base64BodyWriterStream.cs
Line coverage
97%
Covered lines: 79
Uncovered lines: 2
Coverable lines: 81
Total lines: 167
Line coverage: 97.5%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute.AwsLambda/Private/Base64BodyWriterStream.cs

#LineLine coverage
 1/********************************************************************************
 2* Base64BodyWriterStream.cs                                                     *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Buffers;
 8using System.IO;
 9using System.Text;
 10using System.Threading;
 11using System.Threading.Tasks;
 12
 13namespace NanoRoute.AwsLambda
 14{
 15    internal sealed class Base64BodyWriterStream : Stream
 16    {
 17        #region Private
 118        private static readonly ArrayPool<char> s_arrayPool = ArrayPool<char>.Create();
 19
 120        private StringBuilder _body = new();
 121        private byte[] _stagedBytes = new byte[3];
 122        private char[] _encodedChars = s_arrayPool.Rent(4096);
 23
 24        private int _stagedByteCount;
 25
 26        private void AppendEncoded(ReadOnlySpan<byte> bytes, CancellationToken cancellation)
 127        {
 128            while (!bytes.IsEmpty)
 129            {
 130                cancellation.ThrowIfCancellationRequested();
 31
 132                int bytesToEncode = Math.Min(bytes.Length, (_encodedChars.Length / 4) * 3);
 33
 134                if (!Convert.TryToBase64Chars(bytes.Slice(0, bytesToEncode), _encodedChars, out int charsWritten))
 035                    throw new InvalidOperationException();
 36
 137                _body.Append(_encodedChars, 0, charsWritten);
 138                bytes = bytes.Slice(bytesToEncode);
 139            }
 140        }
 41
 142        private void EnsureNotDisposed() => ObjectDisposedException.ThrowIf(_body is null, this);
 43
 44        private void WriteCore(ReadOnlySpan<byte> buffer, CancellationToken cancellation)
 145        {
 146            EnsureNotDisposed();
 47
 148            cancellation.ThrowIfCancellationRequested();
 49
 150            if (buffer.IsEmpty)
 151                return;
 52
 153            if (_stagedByteCount > 0)
 154            {
 155                int bytesToStage = Math.Min(_stagedBytes.Length - _stagedByteCount, buffer.Length);
 56
 157                buffer.Slice(0, bytesToStage).CopyTo(_stagedBytes.AsSpan(_stagedByteCount));
 158                _stagedByteCount += bytesToStage;
 159                buffer = buffer.Slice(bytesToStage);
 60
 161                if (_stagedByteCount < _stagedBytes.Length)
 162                    return;
 63
 164                AppendEncoded(_stagedBytes, cancellation);
 165                _stagedByteCount = 0;
 166            }
 67
 168            int blockLength = buffer.Length - (buffer.Length % 3);
 69
 170            if (blockLength > 0)
 171            {
 172                AppendEncoded(buffer.Slice(0, blockLength), cancellation);
 173                buffer = buffer.Slice(blockLength);
 174            }
 75
 176            if (!buffer.IsEmpty)
 177            {
 178                buffer.CopyTo(_stagedBytes);
 179                _stagedByteCount = buffer.Length;
 180            }
 181        }
 82
 83        protected override void Dispose(bool disposing)
 184        {
 185            _body = null!;
 186            _stagedBytes = null!;
 87
 188            s_arrayPool.Return(_encodedChars, clearArray: false);
 189            _encodedChars = null!;
 90
 191            base.Dispose(disposing);
 192        }
 93        #endregion
 94
 95        public override bool CanRead { get; }
 96
 97        public override bool CanSeek { get; }
 98
 199        public override bool CanWrite { get; } = true;
 100
 101        public string GetBody()
 1102        {
 1103            EnsureNotDisposed();
 104
 1105            if (_stagedByteCount is 0)
 1106                return _body.ToString();
 107
 1108            if (!Convert.TryToBase64Chars(_stagedBytes.AsSpan(0, _stagedByteCount), _encodedChars, out int charsWritten)
 0109                throw new InvalidOperationException();
 110
 1111            return string.Concat
 1112            (
 1113                _body.ToString(),
 1114                new string(_encodedChars, 0, charsWritten)
 1115            );
 1116        }
 117
 118        public override void Write(byte[] buffer, int offset, int count)
 1119        {
 1120            ArgumentNullException.ThrowIfNull(buffer);
 121
 1122            if (offset < 0 || offset > buffer.Length)
 1123                throw new ArgumentOutOfRangeException(nameof(offset));
 124
 1125            if (count < 0 || count > buffer.Length - offset)
 1126                throw new ArgumentOutOfRangeException(nameof(count));
 127
 1128            WriteCore(buffer.AsSpan(offset, count), CancellationToken.None);
 1129        }
 130
 131        public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellation)
 132        {
 133            await Task.Yield();
 134
 135            WriteCore(buffer.AsSpan(offset, count), cancellation);
 136        }
 137
 1138        public override void Write(ReadOnlySpan<byte> buffer) => WriteCore(buffer, CancellationToken.None);
 139
 140        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellation)
 141        {
 142            await Task.Yield();
 143
 144            WriteCore(buffer.Span, cancellation);
 145        }
 146
 147        #region Not Supported members
 1148        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 149
 1150        public override void SetLength(long value) => throw new NotSupportedException();
 151
 1152        public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
 153
 1154        public override long Length => throw new NotSupportedException();
 155
 156        public override long Position
 157        {
 1158            get => throw new NotSupportedException();
 1159            set => throw new NotSupportedException();
 160        }
 161
 1162        public override void Flush() { }
 163
 1164        public override Task FlushAsync(CancellationToken cancellation) => Task.CompletedTask;
 165        #endregion
 166    }
 167}