include/boost/capy/read_at_least.hpp

100.0% Lines (2/2) 100.0% List of functions (3/3)
read_at_least.hpp
f(x) Functions (3)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_READ_AT_LEAST_HPP
11 #define BOOST_CAPY_READ_AT_LEAST_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/cond.hpp>
15 #include <boost/capy/io_task.hpp>
16 #include <boost/capy/buffers.hpp>
17 #include <boost/capy/buffers/consuming_buffers.hpp>
18 #include <boost/capy/concept/read_stream.hpp>
19
20 #include <cstddef>
21 #include <system_error>
22
23 namespace boost {
24 namespace capy {
25
26 /** Read at least a minimum number of bytes from a stream.
27
28 This is a straightforward extension of @ref read. While @ref read
29 transfers exactly `buffer_size(buffers)` bytes, `read_at_least`
30 transfers at least `n` bytes: the loop stops as soon as `n` bytes
31 have been read, even if `buffers` is not yet full. Any bytes beyond
32 `n` that a single `stream.read_some` happens to deliver (up to the
33 capacity of `buffers`) are kept, but no further awaiting is performed
34 to fill the remainder.
35
36 This is useful when a caller has a required amount of data `n` that
37 must be met or exceeded, while the subsequent capacity of `buffers`
38 is optional and should not block.
39
40 @par Await-effects
41
42 If `n > buffer_size(buffers)` the request is impossible to satisfy
43 and the operation fails immediately with
44 `{std::errc::invalid_argument, 0}` without awaiting `stream.read_some`.
45
46 Otherwise reads data from `stream` via awaiting `stream.read_some`
47 repeatedly until:
48
49 @li either at least `n` bytes have been read,
50 @li or a contingency occurs on `stream.read_some`.
51
52 If `n == 0` then no awaiting `stream.read_some` is performed. This is
53 not a contingency.
54
55 @par Await-returns
56 An object of type `io_result<std::size_t>` destructuring as `[ec, n]`.
57
58 Upon a contingency, the count represents the number of bytes read so
59 far, inclusive of the last partial read.
60
61 Contingencies:
62
63 @li The first contingency reported from awaiting @c stream.read_some
64 while fewer than `n` bytes have been read. A contingency that
65 accompanies the read which reaches `n` is not reported: a
66 satisfied request is a success.
67
68 Notable conditions:
69
70 @li @c std::errc::invalid_argument — `n` exceeds `buffer_size(buffers)`,
71 @li @c cond::canceled — Operation was cancelled,
72 @li @c cond::eof — Stream reached end before `n` bytes were read.
73
74 @par Await-postcondition
75 On success the returned count is greater than or equal to `n` and
76 less than or equal to `buffer_size(buffers)`, and `ec` is success;
77 otherwise `ec` is set.
78
79 @param stream The stream to read from. If the lifetime of `stream` ends
80 before the coroutine finishes, the behavior is undefined.
81
82 @param buffers The buffer sequence to read into. If the lifetime of the
83 buffer sequence represented by `buffers` ends before the coroutine
84 finishes, the behavior is undefined.
85
86 @param n The minimum number of bytes to read. Must not exceed
87 `buffer_size(buffers)`.
88
89 @par Remarks
90 Supports _IoAwaitable cancellation_.
91
92 @par Example
93
94 @code
95 capy::task<> fill_buffer(capy::ReadStream auto& stream)
96 {
97 std::vector<char> storage(4096); // generous capacity
98 // Require 16 header bytes; opportunistically take more.
99 auto [ec, n] = co_await capy::read_at_least(
100 stream, capy::make_buffer(storage), 16);
101 if(ec)
102 throw std::system_error(ec);
103
104 // at least 16 bytes are available; n may be larger
105 }
106 @endcode
107
108 @see read, ReadStream, MutableBufferSequence
109 */
110 template <typename S, typename MB>
111 requires ReadStream<S> && MutableBufferSequence<MB>
112 auto
113 34x read_at_least(S& stream, MB buffers, std::size_t n) ->
114 io_task<std::size_t>
115 {
116 consuming_buffers consuming(buffers);
117 std::size_t const total_size = buffer_size(buffers);
118
119 if(n > total_size)
120 co_return {make_error_code(std::errc::invalid_argument), 0};
121
122 std::size_t total_read = 0;
123
124 while(total_read < n)
125 {
126 auto [ec, m] = co_await stream.read_some(consuming.data());
127 consuming.consume(m);
128 total_read += m;
129 // A contingency that still satisfied the request is a success:
130 // report it only when fewer than n bytes were read.
131 if(ec && total_read < n)
132 co_return {ec, total_read};
133 }
134
135 co_return {{}, total_read};
136 68x }
137
138 } // namespace capy
139 } // namespace boost
140
141 #endif
142