< Summary

Information
Class: IceRpc.Internal.PipeReaderExtensions
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/PipeReaderExtensions.cs
Tag: 275_13775359185
Line coverage
80%
Covered lines: 105
Uncovered lines: 26
Coverable lines: 131
Total lines: 271
Line coverage: 80.1%
Branch coverage
75%
Covered branches: 39
Total branches: 52
Branch coverage: 75%
Method coverage
100%
Covered methods: 3
Total methods: 3
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ReadSegmentAsync()66.66%21.121878.72%
TryReadSegment(...)62.5%24.791667.5%
IsCompleteSegment(...)88.88%19.31884.09%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/PipeReaderExtensions.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4using System.IO.Pipelines;
 5using ZeroC.Slice;
 6
 7namespace IceRpc.Internal;
 8
 9/// <summary>Provides extension methods for <see cref="PipeReader" /> to decode payloads.</summary>
 10internal static class PipeReaderExtensions
 11{
 12    /// <summary>Reads a Slice segment from a pipe reader.</summary>
 13    /// <param name="reader">The pipe reader.</param>
 14    /// <param name="encoding">The encoding.</param>
 15    /// <param name="maxSize">The maximum size of this segment.</param>
 16    /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param>
 17    /// <returns>A read result with the segment read from the reader unless <see cref="ReadResult.IsCanceled" /> is
 18    /// <see langword="true" />.</returns>
 19    /// <exception cref="InvalidDataException">Thrown when the segment size could not be decoded or the segment size
 20    /// exceeds <paramref name="maxSize" />.</exception>
 21    /// <remarks>The caller must call AdvanceTo on the reader, as usual. With Slice1, this method reads all
 22    /// the remaining bytes in the reader; otherwise, this method reads the segment size in the segment and returns
 23    /// exactly segment size bytes. This method often examines the buffer it returns as part of ReadResult,
 24    /// therefore the caller should never examine less than Buffer.End.</remarks>
 25    internal static async ValueTask<ReadResult> ReadSegmentAsync(
 26        this PipeReader reader,
 27        SliceEncoding encoding,
 28        int maxSize,
 29        CancellationToken cancellationToken)
 434830    {
 434831        Debug.Assert(maxSize is > 0 and < int.MaxValue);
 32
 33        // This method does not attempt to read the reader synchronously. A caller that wants a sync attempt can
 34        // call TryReadSegment.
 35
 434836        if (encoding == SliceEncoding.Slice1)
 1637        {
 38            // We read everything up to the maxSize + 1.
 39            // It's maxSize + 1 and not maxSize because if the segment's size is maxSize, we could get
 40            // readResult.IsCompleted == false even though the full segment was read.
 41
 1642            ReadResult readResult = await reader.ReadAtLeastAsync(maxSize + 1, cancellationToken).ConfigureAwait(false);
 43
 1644            if (readResult.IsCompleted && readResult.Buffer.Length <= maxSize)
 1645            {
 1646                return readResult;
 47            }
 48            else
 049            {
 050                reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 051                throw new InvalidDataException("The segment size exceeds the maximum value.");
 52            }
 53        }
 54        else
 433255        {
 56            ReadResult readResult;
 57            int segmentSize;
 58
 433359            while (true)
 433360            {
 433361                readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
 62
 63                try
 428164                {
 428165                    if (IsCompleteSegment(ref readResult, maxSize, out segmentSize, out long consumed))
 426366                    {
 426367                        return readResult;
 68                    }
 269                    else if (segmentSize > 0)
 170                    {
 171                        Debug.Assert(consumed > 0);
 72
 73                        // We decoded the segmentSize and examined the whole buffer but it was not sufficient.
 174                        reader.AdvanceTo(readResult.Buffer.GetPosition(consumed), readResult.Buffer.End);
 175                        break; // while
 76                    }
 77                    else
 178                    {
 179                        Debug.Assert(!readResult.IsCompleted); // see IsCompleteSegment
 180                        reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 81                        // and continue loop with at least one additional byte
 182                    }
 183                }
 1684                catch
 1685                {
 86                    // A ReadAsync or TryRead method that throws an exception should not leave the reader in a
 87                    // "reading" state.
 1688                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 1689                    throw;
 90                }
 191            }
 92
 193            readResult = await reader.ReadAtLeastAsync(segmentSize, cancellationToken).ConfigureAwait(false);
 94
 195            if (readResult.IsCanceled)
 096            {
 097                return readResult;
 98            }
 99
 1100            if (readResult.Buffer.Length < segmentSize)
 0101            {
 0102                Debug.Assert(readResult.IsCompleted);
 0103                reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 0104                throw new InvalidDataException(
 0105                    $"The payload has {readResult.Buffer.Length} bytes, but {segmentSize} bytes were expected.");
 106            }
 107
 1108            return readResult.Buffer.Length == segmentSize ? readResult :
 1109                new ReadResult(readResult.Buffer.Slice(0, segmentSize), isCanceled: false, isCompleted: false);
 110        }
 4280111    }
 112
 113    /// <summary>Attempts to read a Slice segment from a pipe reader.</summary>
 114    /// <param name="reader">The pipe reader.</param>
 115    /// <param name="encoding">The encoding.</param>
 116    /// <param name="maxSize">The maximum size of this segment.</param>
 117    /// <param name="readResult">The read result.</param>
 118    /// <returns><see langword="true" /> when <paramref name="readResult" /> contains the segment read synchronously, or
 119    /// the call was cancelled; otherwise, <see langword="false" />.</returns>
 120    /// <exception cref="InvalidDataException">Thrown when the segment size could not be decoded or the segment size
 121    /// exceeds the max segment size.</exception>
 122    /// <remarks>When this method returns <see langword="true" />, the caller must call AdvanceTo on the reader, as
 123    /// usual. This method often examines the buffer it returns as part of ReadResult, therefore the caller should never
 124    /// examine less than Buffer.End when the return value is <see langword="true" />. When this method returns
 125    /// <see langword="false" />, the caller must call <see cref="ReadSegmentAsync" />.</remarks>
 126    internal static bool TryReadSegment(
 127        this PipeReader reader,
 128        SliceEncoding encoding,
 129        int maxSize,
 130        out ReadResult readResult)
 313131    {
 313132        Debug.Assert(maxSize is > 0 and < int.MaxValue);
 133
 313134        if (encoding == SliceEncoding.Slice1)
 137135        {
 137136            if (reader.TryRead(out readResult))
 137137            {
 137138                if (readResult.IsCanceled)
 0139                {
 0140                    return true; // and the buffer does not matter
 141                }
 142
 137143                if (readResult.Buffer.Length > maxSize)
 0144                {
 0145                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 0146                    throw new InvalidDataException("The segment size exceeds the maximum value.");
 147                }
 148
 137149                if (readResult.IsCompleted)
 137150                {
 137151                    return true;
 152                }
 153                else
 0154                {
 155                    // don't consume anything but mark the whole buffer as examined - we need more.
 0156                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 0157                }
 0158            }
 159
 0160            readResult = default;
 0161            return false;
 162        }
 163        else
 176164        {
 176165            if (reader.TryRead(out readResult))
 172166            {
 167                try
 172168                {
 172169                    if (IsCompleteSegment(ref readResult, maxSize, out int segmentSize, out long _))
 169170                    {
 169171                        return true;
 172                    }
 173                    else
 1174                    {
 175                        // we don't consume anything but examined the whole buffer since it's not sufficient.
 1176                        reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 1177                        readResult = default;
 1178                        return false;
 179                    }
 180                }
 2181                catch
 2182                {
 2183                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 2184                    throw;
 185                }
 186            }
 187            else
 4188            {
 4189                return false;
 190            }
 191        }
 311192    }
 193
 194    /// <summary>Checks if a read result holds a complete Slice segment and if the segment size does not exceed the
 195    /// maximum size.</summary>
 196    /// <returns><see langword="true" /> when <paramref name="readResult" /> holds a complete segment or is canceled;
 197    /// otherwise, <see langword="false" />.</returns>
 198    /// <remarks><paramref name="segmentSize" /> and <paramref name="consumed" /> can be set when this method returns
 199    /// <see langword="false" />. In this case, both segmentSize and consumed are greater than 0.</remarks>
 200    private static bool IsCompleteSegment(
 201        ref ReadResult readResult,
 202        int maxSize,
 203        out int segmentSize,
 204        out long consumed)
 4453205    {
 4453206        consumed = 0;
 4453207        segmentSize = -1;
 208
 4453209        if (readResult.IsCanceled)
 0210        {
 0211            return true; // and buffer etc. does not matter
 212        }
 213
 4453214        if (readResult.Buffer.IsEmpty)
 27215        {
 27216            Debug.Assert(readResult.IsCompleted);
 27217            segmentSize = 0;
 27218            return true; // the caller will call AdvanceTo on this buffer.
 219        }
 220
 4426221        var decoder = new SliceDecoder(readResult.Buffer, SliceEncoding.Slice2);
 4426222        if (decoder.TryDecodeVarUInt62(out ulong ulongSize))
 4417223        {
 4417224            consumed = decoder.Consumed;
 225
 226            try
 4417227            {
 4417228                segmentSize = checked((int)ulongSize);
 4417229            }
 0230            catch (OverflowException exception)
 0231            {
 0232                throw new InvalidDataException("The segment size can't be larger than int.MaxValue.", exception);
 233            }
 234
 4417235            if (segmentSize > maxSize)
 6236            {
 6237                throw new InvalidDataException("The segment size exceeds the maximum value.");
 238            }
 239
 4411240            if (readResult.Buffer.Length >= consumed + segmentSize)
 4405241            {
 242                // When segmentSize is 0, we return a read result with an empty buffer.
 4405243                readResult = new ReadResult(
 4405244                    readResult.Buffer.Slice(readResult.Buffer.GetPosition(consumed), segmentSize),
 4405245                    isCanceled: false,
 4405246                    isCompleted: readResult.IsCompleted &&
 4405247                        readResult.Buffer.Length == consumed + segmentSize);
 248
 4405249                return true;
 250            }
 251
 6252            if (readResult.IsCompleted && consumed + segmentSize > readResult.Buffer.Length)
 4253            {
 4254                throw new InvalidDataException(
 4255                    $"The payload has {readResult.Buffer.Length} bytes, but {segmentSize} bytes were expected.");
 256            }
 257
 258            // segmentSize and consumed are set and can be used by the caller.
 2259            return false;
 260        }
 9261        else if (readResult.IsCompleted)
 8262        {
 8263            throw new InvalidDataException("Received a Slice segment with fewer bytes than promised.");
 264        }
 265        else
 1266        {
 1267            segmentSize = -1;
 1268            return false;
 269        }
 4435270    }
 271}