• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

icerpc / icerpc-csharp / 19598464025

22 Nov 2025 04:52PM UTC coverage: 83.424% (-0.1%) from 83.535%
19598464025

Pull #4141

github

web-flow
Merge 014d37568 into 6d38ae055
Pull Request #4141: Switch to macos-26 runners

11998 of 14382 relevant lines covered (83.42%)

2906.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.28
src/IceRpc/Internal/PipeReaderExtensions.cs
1
// Copyright (c) ZeroC, Inc.
2

3
using System.Diagnostics;
4
using System.IO.Pipelines;
5
using ZeroC.Slice;
6

7
namespace IceRpc.Internal;
8

9
/// <summary>Provides extension methods for <see cref="PipeReader" /> to decode payloads.</summary>
10
internal 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)
30
    {
2,290✔
31
        Debug.Assert(maxSize is > 0 and < int.MaxValue);
2,290✔
32

33
        // This method does not attempt to read the reader synchronously. A caller that wants a sync attempt can
34
        // call TryReadSegment.
35

36
        if (encoding == SliceEncoding.Slice1)
2,290✔
37
        {
16✔
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

42
            ReadResult readResult = await reader.ReadAtLeastAsync(maxSize + 1, cancellationToken).ConfigureAwait(false);
16✔
43

44
            if (readResult.IsCompleted && readResult.Buffer.Length <= maxSize)
16✔
45
            {
16✔
46
                return readResult;
16✔
47
            }
48
            else
49
            {
×
50
                reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
×
51
                throw new InvalidDataException("The segment size exceeds the maximum value.");
×
52
            }
53
        }
54
        else
55
        {
2,274✔
56
            ReadResult readResult;
57
            int segmentSize;
58

59
            while (true)
2,275✔
60
            {
2,275✔
61
                readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
2,275✔
62

63
                try
64
                {
2,250✔
65
                    if (IsCompleteSegment(ref readResult, maxSize, out segmentSize, out long consumed))
2,250✔
66
                    {
2,240✔
67
                        return readResult;
2,240✔
68
                    }
69
                    else if (segmentSize > 0)
1✔
70
                    {
×
71
                        Debug.Assert(consumed > 0);
×
72

73
                        // We decoded the segmentSize and examined the whole buffer but it was not sufficient.
74
                        reader.AdvanceTo(readResult.Buffer.GetPosition(consumed), readResult.Buffer.End);
×
75
                        break; // while
×
76
                    }
77
                    else
78
                    {
1✔
79
                        Debug.Assert(!readResult.IsCompleted); // see IsCompleteSegment
1✔
80
                        reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
1✔
81
                        // and continue loop with at least one additional byte
82
                    }
1✔
83
                }
1✔
84
                catch
9✔
85
                {
9✔
86
                    // A ReadAsync or TryRead method that throws an exception should not leave the reader in a
87
                    // "reading" state.
88
                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
9✔
89
                    throw;
9✔
90
                }
91
            }
1✔
92

93
            readResult = await reader.ReadAtLeastAsync(segmentSize, cancellationToken).ConfigureAwait(false);
×
94

95
            if (readResult.IsCanceled)
×
96
            {
×
97
                return readResult;
×
98
            }
99

100
            if (readResult.Buffer.Length < segmentSize)
×
101
            {
×
102
                Debug.Assert(readResult.IsCompleted);
×
103
                reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
×
104
                throw new InvalidDataException(
×
105
                    $"The payload has {readResult.Buffer.Length} bytes, but {segmentSize} bytes were expected.");
×
106
            }
107

108
            return readResult.Buffer.Length == segmentSize ? readResult :
×
109
                new ReadResult(readResult.Buffer.Slice(0, segmentSize), isCanceled: false, isCompleted: false);
×
110
        }
111
    }
2,256✔
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)
131
    {
307✔
132
        Debug.Assert(maxSize is > 0 and < int.MaxValue);
307✔
133

134
        if (encoding == SliceEncoding.Slice1)
307✔
135
        {
137✔
136
            if (reader.TryRead(out readResult))
137✔
137
            {
137✔
138
                if (readResult.IsCanceled)
137✔
139
                {
×
140
                    return true; // and the buffer does not matter
×
141
                }
142

143
                if (readResult.Buffer.Length > maxSize)
137✔
144
                {
×
145
                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
×
146
                    throw new InvalidDataException("The segment size exceeds the maximum value.");
×
147
                }
148

149
                if (readResult.IsCompleted)
137✔
150
                {
137✔
151
                    return true;
137✔
152
                }
153
                else
154
                {
×
155
                    // don't consume anything but mark the whole buffer as examined - we need more.
156
                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
×
157
                }
×
158
            }
×
159

160
            readResult = default;
×
161
            return false;
×
162
        }
163
        else
164
        {
170✔
165
            if (reader.TryRead(out readResult))
170✔
166
            {
166✔
167
                try
168
                {
166✔
169
                    if (IsCompleteSegment(ref readResult, maxSize, out int segmentSize, out long _))
166✔
170
                    {
163✔
171
                        return true;
163✔
172
                    }
173
                    else
174
                    {
1✔
175
                        // we don't consume anything but examined the whole buffer since it's not sufficient.
176
                        reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
1✔
177
                        readResult = default;
1✔
178
                        return false;
1✔
179
                    }
180
                }
181
                catch
2✔
182
                {
2✔
183
                    reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
2✔
184
                    throw;
2✔
185
                }
186
            }
187
            else
188
            {
4✔
189
                return false;
4✔
190
            }
191
        }
192
    }
305✔
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)
205
    {
2,416✔
206
        consumed = 0;
2,416✔
207
        segmentSize = -1;
2,416✔
208

209
        if (readResult.IsCanceled)
2,416✔
210
        {
×
211
            return true; // and buffer etc. does not matter
×
212
        }
213

214
        if (readResult.Buffer.IsEmpty)
2,416✔
215
        {
24✔
216
            Debug.Assert(readResult.IsCompleted);
24✔
217
            segmentSize = 0;
24✔
218
            return true; // the caller will call AdvanceTo on this buffer.
24✔
219
        }
220

221
        var decoder = new SliceDecoder(readResult.Buffer, SliceEncoding.Slice2);
2,392✔
222
        if (decoder.TryDecodeVarUInt62(out ulong ulongSize))
2,392✔
223
        {
2,386✔
224
            consumed = decoder.Consumed;
2,386✔
225

226
            try
227
            {
2,386✔
228
                segmentSize = checked((int)ulongSize);
2,386✔
229
            }
2,386✔
230
            catch (OverflowException exception)
×
231
            {
×
232
                throw new InvalidDataException("The segment size can't be larger than int.MaxValue.", exception);
×
233
            }
234

235
            if (segmentSize > maxSize)
2,386✔
236
            {
3✔
237
                throw new InvalidDataException("The segment size exceeds the maximum value.");
3✔
238
            }
239

240
            if (readResult.Buffer.Length >= consumed + segmentSize)
2,383✔
241
            {
2,379✔
242
                // When segmentSize is 0, we return a read result with an empty buffer.
243
                readResult = new ReadResult(
2,379✔
244
                    readResult.Buffer.Slice(readResult.Buffer.GetPosition(consumed), segmentSize),
2,379✔
245
                    isCanceled: false,
2,379✔
246
                    isCompleted: readResult.IsCompleted &&
2,379✔
247
                        readResult.Buffer.Length == consumed + segmentSize);
2,379✔
248

249
                return true;
2,379✔
250
            }
251

252
            if (readResult.IsCompleted && consumed + segmentSize > readResult.Buffer.Length)
4✔
253
            {
3✔
254
                throw new InvalidDataException(
3✔
255
                    $"The payload has {readResult.Buffer.Length} bytes, but {segmentSize} bytes were expected.");
3✔
256
            }
257

258
            // segmentSize and consumed are set and can be used by the caller.
259
            return false;
1✔
260
        }
261
        else if (readResult.IsCompleted)
6✔
262
        {
5✔
263
            throw new InvalidDataException("Received a Slice segment with fewer bytes than promised.");
5✔
264
        }
265
        else
266
        {
1✔
267
            segmentSize = -1;
1✔
268
            return false;
1✔
269
        }
270
    }
2,405✔
271
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc