TLA Line data 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 HIT 34 : 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 68 : }
137 :
138 : } // namespace capy
139 : } // namespace boost
140 :
141 : #endif
|