1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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_task.hpp>
15  
#include <boost/capy/io_task.hpp>
16  
#include <boost/capy/buffers.hpp>
16  
#include <boost/capy/buffers.hpp>
17  
#include <boost/capy/buffers/consuming_buffers.hpp>
17  
#include <boost/capy/buffers/consuming_buffers.hpp>
18  
#include <boost/capy/concept/dynamic_buffer.hpp>
18  
#include <boost/capy/concept/dynamic_buffer.hpp>
19  
#include <boost/capy/concept/read_source.hpp>
19  
#include <boost/capy/concept/read_source.hpp>
20  
#include <boost/capy/concept/read_stream.hpp>
20  
#include <boost/capy/concept/read_stream.hpp>
21  
#include <system_error>
21  
#include <system_error>
22  

22  

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

24  

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

27  

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

29  

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

32  

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

37  

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

41  

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

45  

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

52  

53  
    @par Example
53  
    @par Example
54  

54  

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

67  

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

79  

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

88  

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

91  

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

93  

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

97  

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

102  

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

106  

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

111  

112  
    @return An awaitable yielding `(error_code, std::size_t)`.
112  
    @return An awaitable yielding `(error_code, std::size_t)`.
113  
        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.
114  
        On error, `n` is bytes read before the error. Compare error
114  
        On error, `n` is bytes read before the error. Compare error
115  
        codes to conditions:
115  
        codes to conditions:
116  
        @li `cond::canceled` - Operation was cancelled
116  
        @li `cond::canceled` - Operation was cancelled
117  

117  

118  
    @par Example
118  
    @par Example
119  

119  

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

130  

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

157  

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

159  

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

163  

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

168  

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

172  

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

177  

178  
    @return An awaitable yielding `(error_code, std::size_t)`.
178  
    @return An awaitable yielding `(error_code, std::size_t)`.
179  
        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.
180  
        On error, `n` is bytes read before the error. Compare error
180  
        On error, `n` is bytes read before the error. Compare error
181  
        codes to conditions:
181  
        codes to conditions:
182  
        @li `cond::canceled` - Operation was cancelled
182  
        @li `cond::canceled` - Operation was cancelled
183  

183  

184  
    @par Example
184  
    @par Example
185  

185  

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

196  

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

223  

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

226  

227  
#endif
227  
#endif