< Summary

Information
Class: IceRpc.Internal.PipeReaderExtensions
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/Internal/PipeReaderExtensions.cs
Tag: 1321_24790053727
Line coverage
88%
Covered lines: 88
Uncovered lines: 12
Coverable lines: 100
Total lines: 213
Line coverage: 88%
Branch coverage
80%
Covered branches: 29
Total branches: 36
Branch coverage: 80.5%
Method coverage
100%
Covered methods: 3
Fully covered methods: 1
Total methods: 3
Method coverage: 100%
Full method coverage: 33.3%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ReadSliceSegmentAsync()66.66%131281.08%
TryReadSliceSegment(...)66.66%6689.47%
IsCompleteSegment(...)83.33%231875%

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.Codec;
 6
 7namespace IceRpc.Internal;
 8
 9/// <summary>Provides extension methods for <see cref="PipeReader" /> to read Slice segments.</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="maxSize">The maximum size of this segment.</param>
 15    /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param>
 16    /// <returns>A read result with the segment read from the reader unless <see cref="ReadResult.IsCanceled" /> is
 17    /// <see langword="true" />.</returns>
 18    /// <exception cref="InvalidDataException">Thrown when the segment size could not be decoded or the segment size
 19    /// exceeds <paramref name="maxSize" />.</exception>
 20    /// <remarks>The caller must call AdvanceTo on the reader, as usual. This method reads the segment size in the
 21    /// segment and returns exactly segment size bytes. This method often examines the buffer it returns as part of
 22    /// ReadResult, therefore the caller should never examine less than Buffer.End.</remarks>
 23    internal static async ValueTask<ReadResult> ReadSliceSegmentAsync(
 24        this PipeReader reader,
 25        int maxSize,
 26        CancellationToken cancellationToken)
 232227    {
 232228        Debug.Assert(maxSize is > 0 and < int.MaxValue);
 29
 30        // This method does not attempt to read the reader synchronously. A caller that wants a sync attempt can
 31        // call TryReadSliceSegment.
 32
 33        ReadResult readResult;
 34        int segmentSize;
 35
 232336        while (true)
 232337        {
 232338            readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
 39
 40            try
 230241            {
 230242                if (IsCompleteSegment(ref readResult, maxSize, out segmentSize, out long consumed))
 229043                {
 229044                    return readResult;
 45                }
 346                else if (segmentSize > 0)
 247                {
 248                    Debug.Assert(consumed > 0);
 49
 50                    // We decoded the segmentSize and examined the whole buffer but it was not sufficient.
 251                    reader.AdvanceTo(readResult.Buffer.GetPosition(consumed), readResult.Buffer.End);
 252                    break; // while
 53                }
 54                else
 155                {
 156                    Debug.Assert(!readResult.IsCompleted); // see IsCompleteSegment
 157                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 58                    // and continue loop with at least one additional byte
 159                }
 160            }
 961            catch
 962            {
 63                // A ReadAsync or TryRead method that throws an exception should not leave the reader in a
 64                // "reading" state.
 965                reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 966                throw;
 67            }
 168        }
 69
 270        readResult = await reader.ReadAtLeastAsync(segmentSize, cancellationToken).ConfigureAwait(false);
 71
 172        if (readResult.IsCanceled)
 073        {
 074            return readResult;
 75        }
 76
 177        if (readResult.Buffer.Length < segmentSize)
 078        {
 079            Debug.Assert(readResult.IsCompleted);
 080            reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 081            throw new InvalidDataException(
 082                $"The payload has {readResult.Buffer.Length} bytes, but {segmentSize} bytes were expected.");
 83        }
 84
 185        return readResult.Buffer.Length == segmentSize ? readResult :
 186            new ReadResult(readResult.Buffer.Slice(0, segmentSize), isCanceled: false, isCompleted: false);
 229187    }
 88
 89    /// <summary>Attempts to read a Slice segment from a pipe reader.</summary>
 90    /// <param name="reader">The pipe reader.</param>
 91    /// <param name="maxSize">The maximum size of this segment.</param>
 92    /// <param name="readResult">The read result.</param>
 93    /// <returns><see langword="true" /> when <paramref name="readResult" /> contains the segment read synchronously, or
 94    /// the call was cancelled; otherwise, <see langword="false" />.</returns>
 95    /// <exception cref="InvalidDataException">Thrown when the segment size could not be decoded or the segment size
 96    /// exceeds the max segment size.</exception>
 97    /// <remarks>When this method returns <see langword="true" />, the caller must call AdvanceTo on the reader, as
 98    /// usual. This method often examines the buffer it returns as part of ReadResult, therefore the caller should never
 99    /// examine less than Buffer.End when the return value is <see langword="true" />. When this method returns
 100    /// <see langword="false" />, the caller must call <see cref="ReadSliceSegmentAsync" />.</remarks>
 101    internal static bool TryReadSliceSegment(
 102        this PipeReader reader,
 103        int maxSize,
 104        out ReadResult readResult)
 172105    {
 172106        Debug.Assert(maxSize is > 0 and < int.MaxValue);
 107
 172108        if (reader.TryRead(out readResult))
 168109        {
 110            try
 168111            {
 168112                if (IsCompleteSegment(ref readResult, maxSize, out int segmentSize, out long _))
 165113                {
 165114                    return true;
 115                }
 116                else
 1117                {
 118                    // we don't consume anything but examined the whole buffer since it's not sufficient.
 1119                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 1120                    readResult = default;
 1121                    return false;
 122                }
 123            }
 2124            catch
 2125            {
 2126                reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
 2127                throw;
 128            }
 129        }
 130        else
 4131        {
 4132            return false;
 133        }
 170134    }
 135
 136    /// <summary>Checks if a read result holds a complete Slice segment and if the segment size does not exceed the
 137    /// maximum size.</summary>
 138    /// <returns><see langword="true" /> when <paramref name="readResult" /> holds a complete segment or is canceled;
 139    /// otherwise, <see langword="false" />.</returns>
 140    /// <remarks><paramref name="segmentSize" /> and <paramref name="consumed" /> can be set when this method returns
 141    /// <see langword="false" />. In this case, both segmentSize and consumed are greater than 0.</remarks>
 142    private static bool IsCompleteSegment(
 143        ref ReadResult readResult,
 144        int maxSize,
 145        out int segmentSize,
 146        out long consumed)
 2470147    {
 2470148        consumed = 0;
 2470149        segmentSize = -1;
 150
 2470151        if (readResult.IsCanceled)
 0152        {
 0153            return true; // and buffer etc. does not matter
 154        }
 155
 2470156        if (readResult.Buffer.IsEmpty)
 27157        {
 27158            Debug.Assert(readResult.IsCompleted);
 27159            segmentSize = 0;
 27160            return true; // the caller will call AdvanceTo on this buffer.
 161        }
 162
 2443163        var decoder = new SliceDecoder(readResult.Buffer);
 2443164        if (decoder.TryDecodeVarUInt62(out ulong ulongSize))
 2437165        {
 2437166            consumed = decoder.Consumed;
 167
 168            try
 2437169            {
 2437170                segmentSize = checked((int)ulongSize);
 2437171            }
 0172            catch (OverflowException exception)
 0173            {
 0174                throw new InvalidDataException("The segment size can't be larger than int.MaxValue.", exception);
 175            }
 176
 2437177            if (segmentSize > maxSize)
 3178            {
 3179                throw new InvalidDataException("The segment size exceeds the maximum value.");
 180            }
 181
 2434182            if (readResult.Buffer.Length >= consumed + segmentSize)
 2428183            {
 184                // When segmentSize is 0, we return a read result with an empty buffer.
 2428185                readResult = new ReadResult(
 2428186                    readResult.Buffer.Slice(readResult.Buffer.GetPosition(consumed), segmentSize),
 2428187                    isCanceled: false,
 2428188                    isCompleted: readResult.IsCompleted &&
 2428189                        readResult.Buffer.Length == consumed + segmentSize);
 190
 2428191                return true;
 192            }
 193
 6194            if (readResult.IsCompleted && consumed + segmentSize > readResult.Buffer.Length)
 3195            {
 3196                throw new InvalidDataException(
 3197                    $"The payload has {readResult.Buffer.Length} bytes, but {segmentSize} bytes were expected.");
 198            }
 199
 200            // segmentSize and consumed are set and can be used by the caller.
 3201            return false;
 202        }
 6203        else if (readResult.IsCompleted)
 5204        {
 5205            throw new InvalidDataException("Received a Slice segment with fewer bytes than promised.");
 206        }
 207        else
 1208        {
 1209            segmentSize = -1;
 1210            return false;
 211        }
 2459212    }
 213}