1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_READ_HPP
10  
#ifndef BOOST_CAPY_READ_HPP
11  
#define BOOST_CAPY_READ_HPP
11  
#define BOOST_CAPY_READ_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/cond.hpp>
14  
#include <boost/capy/cond.hpp>
15 -
#include <boost/capy/io_result.hpp>
15 +
#include <boost/capy/io_task.hpp>
16 -
#include <boost/capy/task.hpp>
 
17  
#include <boost/capy/buffers.hpp>
16  
#include <boost/capy/buffers.hpp>
18  
#include <boost/capy/buffers/consuming_buffers.hpp>
17  
#include <boost/capy/buffers/consuming_buffers.hpp>
19  
#include <boost/capy/concept/dynamic_buffer.hpp>
18  
#include <boost/capy/concept/dynamic_buffer.hpp>
20  
#include <boost/capy/concept/read_source.hpp>
19  
#include <boost/capy/concept/read_source.hpp>
21  
#include <boost/capy/concept/read_stream.hpp>
20  
#include <boost/capy/concept/read_stream.hpp>
22  
#include <system_error>
21  
#include <system_error>
23  

22  

24  
#include <cstddef>
23  
#include <cstddef>
25  

24  

26  
namespace boost {
25  
namespace boost {
27  
namespace capy {
26  
namespace capy {
28  

27  

29  
/** Asynchronously read until the buffer sequence is full.
28  
/** Asynchronously read until the buffer sequence is full.
30  

29  

31  
    Reads data from the stream by calling `read_some` repeatedly
30  
    Reads data from the stream by calling `read_some` repeatedly
32  
    until the entire buffer sequence is filled or an error occurs.
31  
    until the entire buffer sequence is filled or an error occurs.
33  

32  

34  
    @li The operation completes when:
33  
    @li The operation completes when:
35  
    @li The buffer sequence is completely filled
34  
    @li The buffer sequence is completely filled
36  
    @li An error occurs (including `cond::eof`)
35  
    @li An error occurs (including `cond::eof`)
37  
    @li The operation is cancelled
36  
    @li The operation is cancelled
38  

37  

39  
    @par Cancellation
38  
    @par Cancellation
40  
    Supports cancellation via `stop_token` propagated through the
39  
    Supports cancellation via `stop_token` propagated through the
41  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
40  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
42  

41  

43  
    @param stream The stream to read from. The caller retains ownership.
42  
    @param stream The stream to read from. The caller retains ownership.
44  
    @param buffers The buffer sequence to fill. The caller retains
43  
    @param buffers The buffer sequence to fill. The caller retains
45  
        ownership and must ensure validity until the operation completes.
44  
        ownership and must ensure validity until the operation completes.
46  

45  

47  
    @return An awaitable yielding `(error_code, std::size_t)`.
46  
    @return An awaitable yielding `(error_code, std::size_t)`.
48  
        On success, `n` equals `buffer_size(buffers)`. On error,
47  
        On success, `n` equals `buffer_size(buffers)`. On error,
49  
        `n` is the number of bytes read before the error. Compare
48  
        `n` is the number of bytes read before the error. Compare
50  
        error codes to conditions:
49  
        error codes to conditions:
51  
        @li `cond::eof` - Stream reached end before buffer was filled
50  
        @li `cond::eof` - Stream reached end before buffer was filled
52  
        @li `cond::canceled` - Operation was cancelled
51  
        @li `cond::canceled` - Operation was cancelled
53  

52  

54  
    @par Example
53  
    @par Example
55  

54  

56  
    @code
55  
    @code
57  
    task<> read_message( ReadStream auto& stream )
56  
    task<> read_message( ReadStream auto& stream )
58  
    {
57  
    {
59  
        char header[16];
58  
        char header[16];
60  
        auto [ec, n] = co_await read( stream, mutable_buffer( header ) );
59  
        auto [ec, n] = co_await read( stream, mutable_buffer( header ) );
61  
        if( ec == cond::eof )
60  
        if( ec == cond::eof )
62  
            co_return;  // Connection closed
61  
            co_return;  // Connection closed
63 -
        if( ec.failed() )
62 +
        if( ec )
64  
            detail::throw_system_error( ec );
63  
            detail::throw_system_error( ec );
65  
        // header contains exactly 16 bytes
64  
        // header contains exactly 16 bytes
66  
    }
65  
    }
67  
    @endcode
66  
    @endcode
68  

67  

69  
    @see read_some, ReadStream, MutableBufferSequence
68  
    @see read_some, ReadStream, MutableBufferSequence
70  
*/
69  
*/
71  
auto
70  
auto
72  
read(
71  
read(
73  
    ReadStream auto& stream,
72  
    ReadStream auto& stream,
74  
    MutableBufferSequence auto const& buffers) ->
73  
    MutableBufferSequence auto const& buffers) ->
75 -
        task<io_result<std::size_t>>
74 +
        io_task<std::size_t>
76  
{
75  
{
77  
    consuming_buffers consuming(buffers);
76  
    consuming_buffers consuming(buffers);
78  
    std::size_t const total_size = buffer_size(buffers);
77  
    std::size_t const total_size = buffer_size(buffers);
79  
    std::size_t total_read = 0;
78  
    std::size_t total_read = 0;
80  

79  

81  
    while(total_read < total_size)
80  
    while(total_read < total_size)
82  
    {
81  
    {
83  
        auto [ec, n] = co_await stream.read_some(consuming);
82  
        auto [ec, n] = co_await stream.read_some(consuming);
84  
        if(ec)
83  
        if(ec)
85  
            co_return {ec, total_read};
84  
            co_return {ec, total_read};
86  
        consuming.consume(n);
85  
        consuming.consume(n);
87  
        total_read += n;
86  
        total_read += n;
88  
    }
87  
    }
89  

88  

90  
    co_return {{}, total_read};
89  
    co_return {{}, total_read};
91  
}
90  
}
92  

91  

93  
/** Asynchronously read all data from a stream into a dynamic buffer.
92  
/** Asynchronously read all data from a stream into a dynamic buffer.
94  

93  

95  
    Reads data by calling `read_some` repeatedly until EOF is reached
94  
    Reads data by calling `read_some` repeatedly until EOF is reached
96  
    or an error occurs. Data is appended using prepare/commit semantics.
95  
    or an error occurs. Data is appended using prepare/commit semantics.
97  
    The buffer grows with 1.5x factor when filled.
96  
    The buffer grows with 1.5x factor when filled.
98  

97  

99  
    @li The operation completes when:
98  
    @li The operation completes when:
100  
    @li End-of-stream is reached (`cond::eof`)
99  
    @li End-of-stream is reached (`cond::eof`)
101  
    @li An error occurs
100  
    @li An error occurs
102  
    @li The operation is cancelled
101  
    @li The operation is cancelled
103  

102  

104  
    @par Cancellation
103  
    @par Cancellation
105  
    Supports cancellation via `stop_token` propagated through the
104  
    Supports cancellation via `stop_token` propagated through the
106  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
105  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
107  

106  

108  
    @param stream The stream to read from. The caller retains ownership.
107  
    @param stream The stream to read from. The caller retains ownership.
109  
    @param buffers The dynamic buffer to append data to. Must remain
108  
    @param buffers The dynamic buffer to append data to. Must remain
110  
        valid until the operation completes.
109  
        valid until the operation completes.
111  
    @param initial_amount Initial bytes to prepare (default 2048).
110  
    @param initial_amount Initial bytes to prepare (default 2048).
112  

111  

113  
    @return An awaitable yielding `(error_code, std::size_t)`.
112  
    @return An awaitable yielding `(error_code, std::size_t)`.
114  
        On success (EOF), `ec` is clear and `n` is total bytes read.
113  
        On success (EOF), `ec` is clear and `n` is total bytes read.
115  
        On error, `n` is bytes read before the error. Compare error
114  
        On error, `n` is bytes read before the error. Compare error
116  
        codes to conditions:
115  
        codes to conditions:
117  
        @li `cond::canceled` - Operation was cancelled
116  
        @li `cond::canceled` - Operation was cancelled
118  

117  

119  
    @par Example
118  
    @par Example
120  

119  

121  
    @code
120  
    @code
122  
    task<std::string> read_body( ReadStream auto& stream )
121  
    task<std::string> read_body( ReadStream auto& stream )
123  
    {
122  
    {
124  
        std::string body;
123  
        std::string body;
125  
        auto [ec, n] = co_await read( stream, string_dynamic_buffer( &body ) );
124  
        auto [ec, n] = co_await read( stream, string_dynamic_buffer( &body ) );
126 -
        if( ec.failed() )
125 +
        if( ec )
127  
            detail::throw_system_error( ec );
126  
            detail::throw_system_error( ec );
128  
        return body;
127  
        return body;
129  
    }
128  
    }
130  
    @endcode
129  
    @endcode
131  

130  

132  
    @see read_some, ReadStream, DynamicBufferParam
131  
    @see read_some, ReadStream, DynamicBufferParam
133  
*/
132  
*/
134  
auto
133  
auto
135  
read(
134  
read(
136  
    ReadStream auto& stream,
135  
    ReadStream auto& stream,
137  
    DynamicBufferParam auto&& buffers,
136  
    DynamicBufferParam auto&& buffers,
138  
    std::size_t initial_amount = 2048) ->
137  
    std::size_t initial_amount = 2048) ->
139 -
        task<io_result<std::size_t>>
138 +
        io_task<std::size_t>
140  
{
139  
{
141  
    std::size_t amount = initial_amount;
140  
    std::size_t amount = initial_amount;
142  
    std::size_t total_read = 0;
141  
    std::size_t total_read = 0;
143  
    for(;;)
142  
    for(;;)
144  
    {
143  
    {
145  
        auto mb = buffers.prepare(amount);
144  
        auto mb = buffers.prepare(amount);
146  
        auto const mb_size = buffer_size(mb);
145  
        auto const mb_size = buffer_size(mb);
147  
        auto [ec, n] = co_await stream.read_some(mb);
146  
        auto [ec, n] = co_await stream.read_some(mb);
148  
        buffers.commit(n);
147  
        buffers.commit(n);
149  
        total_read += n;
148  
        total_read += n;
150  
        if(ec == cond::eof)
149  
        if(ec == cond::eof)
151  
            co_return {{}, total_read};
150  
            co_return {{}, total_read};
152  
        if(ec)
151  
        if(ec)
153  
            co_return {ec, total_read};
152  
            co_return {ec, total_read};
154  
        if(n == mb_size)
153  
        if(n == mb_size)
155  
            amount = amount / 2 + amount;
154  
            amount = amount / 2 + amount;
156  
    }
155  
    }
157  
}
156  
}
158  

157  

159  
/** Asynchronously read all data from a source into a dynamic buffer.
158  
/** Asynchronously read all data from a source into a dynamic buffer.
160  

159  

161  
    Reads data by calling `source.read` repeatedly until EOF is reached
160  
    Reads data by calling `source.read` repeatedly until EOF is reached
162  
    or an error occurs. Data is appended using prepare/commit semantics.
161  
    or an error occurs. Data is appended using prepare/commit semantics.
163  
    The buffer grows with 1.5x factor when filled.
162  
    The buffer grows with 1.5x factor when filled.
164  

163  

165  
    @li The operation completes when:
164  
    @li The operation completes when:
166  
    @li End-of-stream is reached (`cond::eof`)
165  
    @li End-of-stream is reached (`cond::eof`)
167  
    @li An error occurs
166  
    @li An error occurs
168  
    @li The operation is cancelled
167  
    @li The operation is cancelled
169  

168  

170  
    @par Cancellation
169  
    @par Cancellation
171  
    Supports cancellation via `stop_token` propagated through the
170  
    Supports cancellation via `stop_token` propagated through the
172  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
171  
    IoAwaitable protocol. When cancelled, returns with `cond::canceled`.
173  

172  

174  
    @param source The source to read from. The caller retains ownership.
173  
    @param source The source to read from. The caller retains ownership.
175  
    @param buffers The dynamic buffer to append data to. Must remain
174  
    @param buffers The dynamic buffer to append data to. Must remain
176  
        valid until the operation completes.
175  
        valid until the operation completes.
177  
    @param initial_amount Initial bytes to prepare (default 2048).
176  
    @param initial_amount Initial bytes to prepare (default 2048).
178  

177  

179  
    @return An awaitable yielding `(error_code, std::size_t)`.
178  
    @return An awaitable yielding `(error_code, std::size_t)`.
180  
        On success (EOF), `ec` is clear and `n` is total bytes read.
179  
        On success (EOF), `ec` is clear and `n` is total bytes read.
181  
        On error, `n` is bytes read before the error. Compare error
180  
        On error, `n` is bytes read before the error. Compare error
182  
        codes to conditions:
181  
        codes to conditions:
183  
        @li `cond::canceled` - Operation was cancelled
182  
        @li `cond::canceled` - Operation was cancelled
184  

183  

185  
    @par Example
184  
    @par Example
186  

185  

187  
    @code
186  
    @code
188  
    task<std::string> read_body( ReadSource auto& source )
187  
    task<std::string> read_body( ReadSource auto& source )
189  
    {
188  
    {
190  
        std::string body;
189  
        std::string body;
191  
        auto [ec, n] = co_await read( source, string_dynamic_buffer( &body ) );
190  
        auto [ec, n] = co_await read( source, string_dynamic_buffer( &body ) );
192 -
        if( ec.failed() )
191 +
        if( ec )
193  
            detail::throw_system_error( ec );
192  
            detail::throw_system_error( ec );
194  
        return body;
193  
        return body;
195  
    }
194  
    }
196  
    @endcode
195  
    @endcode
197  

196  

198  
    @see ReadSource, DynamicBufferParam
197  
    @see ReadSource, DynamicBufferParam
199  
*/
198  
*/
200  
auto
199  
auto
201  
read(
200  
read(
202  
    ReadSource auto& source,
201  
    ReadSource auto& source,
203  
    DynamicBufferParam auto&& buffers,
202  
    DynamicBufferParam auto&& buffers,
204  
    std::size_t initial_amount = 2048) ->
203  
    std::size_t initial_amount = 2048) ->
205 -
        task<io_result<std::size_t>>
204 +
        io_task<std::size_t>
206  
{
205  
{
207  
    std::size_t amount = initial_amount;
206  
    std::size_t amount = initial_amount;
208  
    std::size_t total_read = 0;
207  
    std::size_t total_read = 0;
209  
    for(;;)
208  
    for(;;)
210  
    {
209  
    {
211  
        auto mb = buffers.prepare(amount);
210  
        auto mb = buffers.prepare(amount);
212  
        auto const mb_size = buffer_size(mb);
211  
        auto const mb_size = buffer_size(mb);
213  
        auto [ec, n] = co_await source.read(mb);
212  
        auto [ec, n] = co_await source.read(mb);
214  
        buffers.commit(n);
213  
        buffers.commit(n);
215  
        total_read += n;
214  
        total_read += n;
216  
        if(ec == cond::eof)
215  
        if(ec == cond::eof)
217  
            co_return {{}, total_read};
216  
            co_return {{}, total_read};
218  
        if(ec)
217  
        if(ec)
219  
            co_return {ec, total_read};
218  
            co_return {ec, total_read};
220  
        if(n == mb_size)
219  
        if(n == mb_size)
221  
            amount = amount / 2 + amount; // 1.5x growth
220  
            amount = amount / 2 + amount; // 1.5x growth
222  
    }
221  
    }
223  
}
222  
}
224  

223  

225  
} // namespace capy
224  
} // namespace capy
226  
} // namespace boost
225  
} // namespace boost
227  

226  

228  
#endif
227  
#endif