]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // |
2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) | |
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/boostorg/beast | |
8 | // | |
9 | ||
10 | #ifndef BOOST_BEAST_TEST_STREAM_HPP | |
11 | #define BOOST_BEAST_TEST_STREAM_HPP | |
12 | ||
13 | #include <boost/beast/core/detail/config.hpp> | |
14 | #include <boost/beast/core/bind_handler.hpp> | |
15 | #include <boost/beast/core/flat_buffer.hpp> | |
16 | #include <boost/beast/core/role.hpp> | |
17 | #include <boost/beast/core/string.hpp> | |
18 | #include <boost/beast/_experimental/test/fail_count.hpp> | |
19 | #include <boost/asio/async_result.hpp> | |
20 | #include <boost/asio/buffer.hpp> | |
21 | #include <boost/asio/error.hpp> | |
22 | #include <boost/asio/executor_work_guard.hpp> | |
23 | #include <boost/asio/io_context.hpp> | |
24 | #include <boost/asio/post.hpp> | |
25 | #include <boost/assert.hpp> | |
26 | #include <boost/shared_ptr.hpp> | |
27 | #include <boost/weak_ptr.hpp> | |
28 | #include <boost/throw_exception.hpp> | |
29 | #include <condition_variable> | |
30 | #include <limits> | |
31 | #include <memory> | |
32 | #include <mutex> | |
33 | #include <utility> | |
34 | ||
35 | #if ! BOOST_BEAST_DOXYGEN | |
36 | namespace boost { | |
37 | namespace asio { | |
38 | namespace ssl { | |
39 | template<typename> class stream; | |
40 | } // ssl | |
41 | } // asio | |
42 | } // boost | |
43 | #endif | |
44 | ||
45 | namespace boost { | |
46 | namespace beast { | |
47 | namespace test { | |
48 | ||
49 | /** A two-way socket useful for unit testing | |
50 | ||
51 | An instance of this class simulates a traditional socket, | |
52 | while also providing features useful for unit testing. | |
53 | Each endpoint maintains an independent buffer called | |
54 | the input area. Writes from one endpoint append data | |
55 | to the peer's pending input area. When an endpoint performs | |
56 | a read and data is present in the input area, the data is | |
57 | delivered to the blocking or asynchronous operation. Otherwise | |
58 | the operation is blocked or deferred until data is made | |
59 | available, or until the endpoints become disconnected. | |
60 | ||
61 | These streams may be used anywhere an algorithm accepts a | |
62 | reference to a synchronous or asynchronous read or write | |
63 | stream. It is possible to use a test stream in a call to | |
64 | `net::read_until`, or in a call to | |
65 | @ref boost::beast::http::async_write for example. | |
66 | ||
67 | As with Boost.Asio I/O objects, a @ref stream constructs | |
68 | with a reference to the `net::io_context` to use for | |
69 | handling asynchronous I/O. For asynchronous operations, the | |
70 | stream follows the same rules as a traditional asio socket | |
71 | with respect to how completion handlers for asynchronous | |
72 | operations are performed. | |
73 | ||
74 | To facilitate testing, these streams support some additional | |
75 | features: | |
76 | ||
77 | @li The input area, represented by a @ref beast::basic_flat_buffer, | |
78 | may be directly accessed by the caller to inspect the contents | |
79 | before or after the remote endpoint writes data. This allows | |
80 | a unit test to verify that the received data matches. | |
81 | ||
82 | @li Data may be manually appended to the input area. This data | |
83 | will delivered in the next call to | |
84 | @ref stream::read_some or @ref stream::async_read_some. | |
85 | This allows predefined test vectors to be set up for testing | |
86 | read algorithms. | |
87 | ||
88 | @li The stream may be constructed with a fail count. The | |
89 | stream will eventually fail with a predefined error after a | |
90 | certain number of operations, where the number of operations | |
91 | is controlled by the test. When a test loops over a range of | |
92 | operation counts, it is possible to exercise every possible | |
93 | point of failure in the algorithm being tested. When used | |
94 | correctly the technique allows the tests to reach a high | |
95 | percentage of code coverage. | |
96 | ||
97 | @par Thread Safety | |
98 | @e Distinct @e objects: Safe.@n | |
99 | @e Shared @e objects: Unsafe. | |
100 | The application must also ensure that all asynchronous | |
101 | operations are performed within the same implicit or explicit strand. | |
102 | ||
103 | @par Concepts | |
104 | @li <em>SyncReadStream</em> | |
105 | @li <em>SyncWriteStream</em> | |
106 | @li <em>AsyncReadStream</em> | |
107 | @li <em>AsyncWriteStream</em> | |
108 | */ | |
109 | class stream | |
110 | { | |
111 | struct state; | |
112 | ||
113 | boost::shared_ptr<state> in_; | |
114 | boost::weak_ptr<state> out_; | |
115 | ||
116 | enum class status | |
117 | { | |
118 | ok, | |
119 | eof, | |
120 | }; | |
121 | ||
122 | class service; | |
123 | struct service_impl; | |
124 | ||
125 | struct read_op_base | |
126 | { | |
127 | virtual ~read_op_base() = default; | |
128 | virtual void operator()(error_code ec) = 0; | |
129 | }; | |
130 | ||
131 | struct state | |
132 | { | |
133 | friend class stream; | |
134 | ||
135 | net::io_context& ioc; | |
136 | boost::weak_ptr<service_impl> wp; | |
137 | std::mutex m; | |
138 | flat_buffer b; | |
139 | std::condition_variable cv; | |
140 | std::unique_ptr<read_op_base> op; | |
141 | status code = status::ok; | |
142 | fail_count* fc = nullptr; | |
143 | std::size_t nread = 0; | |
144 | std::size_t nread_bytes = 0; | |
145 | std::size_t nwrite = 0; | |
146 | std::size_t nwrite_bytes = 0; | |
147 | std::size_t read_max = | |
148 | (std::numeric_limits<std::size_t>::max)(); | |
149 | std::size_t write_max = | |
150 | (std::numeric_limits<std::size_t>::max)(); | |
151 | ||
152 | BOOST_BEAST_DECL | |
153 | state( | |
154 | net::io_context& ioc_, | |
155 | boost::weak_ptr<service_impl> wp_, | |
156 | fail_count* fc_); | |
157 | ||
158 | ||
159 | BOOST_BEAST_DECL | |
160 | ~state(); | |
161 | ||
162 | BOOST_BEAST_DECL | |
163 | void | |
164 | remove() noexcept; | |
165 | ||
166 | BOOST_BEAST_DECL | |
167 | void | |
168 | notify_read(); | |
169 | ||
170 | BOOST_BEAST_DECL | |
171 | void | |
172 | cancel_read(); | |
173 | }; | |
174 | ||
175 | template<class Handler, class Buffers> | |
176 | class read_op; | |
177 | ||
178 | struct run_read_op; | |
179 | struct run_write_op; | |
180 | ||
181 | BOOST_BEAST_DECL | |
182 | static | |
183 | void | |
184 | initiate_read( | |
185 | boost::shared_ptr<state> const& in, | |
186 | std::unique_ptr<read_op_base>&& op, | |
187 | std::size_t buf_size); | |
188 | ||
189 | #if ! BOOST_BEAST_DOXYGEN | |
190 | // boost::asio::ssl::stream needs these | |
191 | // DEPRECATED | |
192 | template<class> | |
193 | friend class boost::asio::ssl::stream; | |
194 | // DEPRECATED | |
195 | using lowest_layer_type = stream; | |
196 | // DEPRECATED | |
197 | lowest_layer_type& | |
198 | lowest_layer() noexcept | |
199 | { | |
200 | return *this; | |
201 | } | |
202 | // DEPRECATED | |
203 | lowest_layer_type const& | |
204 | lowest_layer() const noexcept | |
205 | { | |
206 | return *this; | |
207 | } | |
208 | #endif | |
209 | ||
210 | public: | |
211 | using buffer_type = flat_buffer; | |
212 | ||
213 | /** Destructor | |
214 | ||
215 | If an asynchronous read operation is pending, it will | |
216 | simply be discarded with no notification to the completion | |
217 | handler. | |
218 | ||
219 | If a connection is established while the stream is destroyed, | |
220 | the peer will see the error `net::error::connection_reset` | |
221 | when performing any reads or writes. | |
222 | */ | |
223 | BOOST_BEAST_DECL | |
224 | ~stream(); | |
225 | ||
226 | /** Move Constructor | |
227 | ||
228 | Moving the stream while asynchronous operations are pending | |
229 | results in undefined behavior. | |
230 | */ | |
231 | BOOST_BEAST_DECL | |
232 | stream(stream&& other); | |
233 | ||
234 | /** Move Assignment | |
235 | ||
236 | Moving the stream while asynchronous operations are pending | |
237 | results in undefined behavior. | |
238 | */ | |
239 | BOOST_BEAST_DECL | |
240 | stream& | |
241 | operator=(stream&& other); | |
242 | ||
243 | /** Construct a stream | |
244 | ||
245 | The stream will be created in a disconnected state. | |
246 | ||
247 | @param ioc The `io_context` object that the stream will use to | |
248 | dispatch handlers for any asynchronous operations. | |
249 | */ | |
250 | BOOST_BEAST_DECL | |
251 | explicit | |
252 | stream(net::io_context& ioc); | |
253 | ||
254 | /** Construct a stream | |
255 | ||
256 | The stream will be created in a disconnected state. | |
257 | ||
258 | @param ioc The `io_context` object that the stream will use to | |
259 | dispatch handlers for any asynchronous operations. | |
260 | ||
261 | @param fc The @ref fail_count to associate with the stream. | |
262 | Each I/O operation performed on the stream will increment the | |
263 | fail count. When the fail count reaches its internal limit, | |
264 | a simulated failure error will be raised. | |
265 | */ | |
266 | BOOST_BEAST_DECL | |
267 | stream( | |
268 | net::io_context& ioc, | |
269 | fail_count& fc); | |
270 | ||
271 | /** Construct a stream | |
272 | ||
273 | The stream will be created in a disconnected state. | |
274 | ||
275 | @param ioc The `io_context` object that the stream will use to | |
276 | dispatch handlers for any asynchronous operations. | |
277 | ||
278 | @param s A string which will be appended to the input area, not | |
279 | including the null terminator. | |
280 | */ | |
281 | BOOST_BEAST_DECL | |
282 | stream( | |
283 | net::io_context& ioc, | |
284 | string_view s); | |
285 | ||
286 | /** Construct a stream | |
287 | ||
288 | The stream will be created in a disconnected state. | |
289 | ||
290 | @param ioc The `io_context` object that the stream will use to | |
291 | dispatch handlers for any asynchronous operations. | |
292 | ||
293 | @param fc The @ref fail_count to associate with the stream. | |
294 | Each I/O operation performed on the stream will increment the | |
295 | fail count. When the fail count reaches its internal limit, | |
296 | a simulated failure error will be raised. | |
297 | ||
298 | @param s A string which will be appended to the input area, not | |
299 | including the null terminator. | |
300 | */ | |
301 | BOOST_BEAST_DECL | |
302 | stream( | |
303 | net::io_context& ioc, | |
304 | fail_count& fc, | |
305 | string_view s); | |
306 | ||
307 | /// Establish a connection | |
308 | BOOST_BEAST_DECL | |
309 | void | |
310 | connect(stream& remote); | |
311 | ||
312 | /// The type of the executor associated with the object. | |
313 | using executor_type = | |
314 | net::io_context::executor_type; | |
315 | ||
316 | /// Return the executor associated with the object. | |
317 | executor_type | |
318 | get_executor() noexcept | |
319 | { | |
320 | return in_->ioc.get_executor(); | |
321 | }; | |
322 | ||
323 | /// Set the maximum number of bytes returned by read_some | |
324 | void | |
325 | read_size(std::size_t n) noexcept | |
326 | { | |
327 | in_->read_max = n; | |
328 | } | |
329 | ||
330 | /// Set the maximum number of bytes returned by write_some | |
331 | void | |
332 | write_size(std::size_t n) noexcept | |
333 | { | |
334 | in_->write_max = n; | |
335 | } | |
336 | ||
337 | /// Direct input buffer access | |
338 | buffer_type& | |
339 | buffer() noexcept | |
340 | { | |
341 | return in_->b; | |
342 | } | |
343 | ||
344 | /// Returns a string view representing the pending input data | |
345 | BOOST_BEAST_DECL | |
346 | string_view | |
347 | str() const; | |
348 | ||
349 | /// Appends a string to the pending input data | |
350 | BOOST_BEAST_DECL | |
351 | void | |
352 | append(string_view s); | |
353 | ||
354 | /// Clear the pending input area | |
355 | BOOST_BEAST_DECL | |
356 | void | |
357 | clear(); | |
358 | ||
359 | /// Return the number of reads | |
360 | std::size_t | |
361 | nread() const noexcept | |
362 | { | |
363 | return in_->nread; | |
364 | } | |
365 | ||
366 | /// Return the number of bytes read | |
367 | std::size_t | |
368 | nread_bytes() const noexcept | |
369 | { | |
370 | return in_->nread_bytes; | |
371 | } | |
372 | ||
373 | /// Return the number of writes | |
374 | std::size_t | |
375 | nwrite() const noexcept | |
376 | { | |
377 | return in_->nwrite; | |
378 | } | |
379 | ||
380 | /// Return the number of bytes written | |
381 | std::size_t | |
382 | nwrite_bytes() const noexcept | |
383 | { | |
384 | return in_->nwrite_bytes; | |
385 | } | |
386 | ||
387 | /** Close the stream. | |
388 | ||
389 | The other end of the connection will see | |
390 | `error::eof` after reading all the remaining data. | |
391 | */ | |
392 | BOOST_BEAST_DECL | |
393 | void | |
394 | close(); | |
395 | ||
396 | /** Close the other end of the stream. | |
397 | ||
398 | This end of the connection will see | |
399 | `error::eof` after reading all the remaining data. | |
400 | */ | |
401 | BOOST_BEAST_DECL | |
402 | void | |
403 | close_remote(); | |
404 | ||
405 | /** Read some data from the stream. | |
406 | ||
407 | This function is used to read data from the stream. The function call will | |
408 | block until one or more bytes of data has been read successfully, or until | |
409 | an error occurs. | |
410 | ||
411 | @param buffers The buffers into which the data will be read. | |
412 | ||
413 | @returns The number of bytes read. | |
414 | ||
415 | @throws boost::system::system_error Thrown on failure. | |
416 | ||
417 | @note The `read_some` operation may not read all of the requested number of | |
418 | bytes. Consider using the function `net::read` if you need to ensure | |
419 | that the requested amount of data is read before the blocking operation | |
420 | completes. | |
421 | */ | |
422 | template<class MutableBufferSequence> | |
423 | std::size_t | |
424 | read_some(MutableBufferSequence const& buffers); | |
425 | ||
426 | /** Read some data from the stream. | |
427 | ||
428 | This function is used to read data from the stream. The function call will | |
429 | block until one or more bytes of data has been read successfully, or until | |
430 | an error occurs. | |
431 | ||
432 | @param buffers The buffers into which the data will be read. | |
433 | ||
434 | @param ec Set to indicate what error occurred, if any. | |
435 | ||
436 | @returns The number of bytes read. | |
437 | ||
438 | @note The `read_some` operation may not read all of the requested number of | |
439 | bytes. Consider using the function `net::read` if you need to ensure | |
440 | that the requested amount of data is read before the blocking operation | |
441 | completes. | |
442 | */ | |
443 | template<class MutableBufferSequence> | |
444 | std::size_t | |
445 | read_some(MutableBufferSequence const& buffers, | |
446 | error_code& ec); | |
447 | ||
448 | /** Start an asynchronous read. | |
449 | ||
450 | This function is used to asynchronously read one or more bytes of data from | |
451 | the stream. The function call always returns immediately. | |
452 | ||
453 | @param buffers The buffers into which the data will be read. Although the | |
454 | buffers object may be copied as necessary, ownership of the underlying | |
455 | buffers is retained by the caller, which must guarantee that they remain | |
456 | valid until the handler is called. | |
457 | ||
458 | @param handler The completion handler to invoke when the operation | |
459 | completes. The implementation takes ownership of the handler by | |
460 | performing a decay-copy. The equivalent function signature of | |
461 | the handler must be: | |
462 | @code | |
463 | void handler( | |
464 | error_code const& ec, // Result of operation. | |
465 | std::size_t bytes_transferred // Number of bytes read. | |
466 | ); | |
467 | @endcode | |
468 | Regardless of whether the asynchronous operation completes | |
469 | immediately or not, the handler will not be invoked from within | |
470 | this function. Invocation of the handler will be performed in a | |
471 | manner equivalent to using `net::post`. | |
472 | ||
473 | @note The `async_read_some` operation may not read all of the requested number of | |
474 | bytes. Consider using the function `net::async_read` if you need | |
475 | to ensure that the requested amount of data is read before the asynchronous | |
476 | operation completes. | |
477 | */ | |
478 | template< | |
479 | class MutableBufferSequence, | |
480 | BOOST_BEAST_ASYNC_TPARAM2 ReadHandler> | |
481 | BOOST_BEAST_ASYNC_RESULT2(ReadHandler) | |
482 | async_read_some( | |
483 | MutableBufferSequence const& buffers, | |
484 | ReadHandler&& handler); | |
485 | ||
486 | /** Write some data to the stream. | |
487 | ||
488 | This function is used to write data on the stream. The function call will | |
489 | block until one or more bytes of data has been written successfully, or | |
490 | until an error occurs. | |
491 | ||
492 | @param buffers The data to be written. | |
493 | ||
494 | @returns The number of bytes written. | |
495 | ||
496 | @throws boost::system::system_error Thrown on failure. | |
497 | ||
498 | @note The `write_some` operation may not transmit all of the data to the | |
499 | peer. Consider using the function `net::write` if you need to | |
500 | ensure that all data is written before the blocking operation completes. | |
501 | */ | |
502 | template<class ConstBufferSequence> | |
503 | std::size_t | |
504 | write_some(ConstBufferSequence const& buffers); | |
505 | ||
506 | /** Write some data to the stream. | |
507 | ||
508 | This function is used to write data on the stream. The function call will | |
509 | block until one or more bytes of data has been written successfully, or | |
510 | until an error occurs. | |
511 | ||
512 | @param buffers The data to be written. | |
513 | ||
514 | @param ec Set to indicate what error occurred, if any. | |
515 | ||
516 | @returns The number of bytes written. | |
517 | ||
518 | @note The `write_some` operation may not transmit all of the data to the | |
519 | peer. Consider using the function `net::write` if you need to | |
520 | ensure that all data is written before the blocking operation completes. | |
521 | */ | |
522 | template<class ConstBufferSequence> | |
523 | std::size_t | |
524 | write_some( | |
525 | ConstBufferSequence const& buffers, error_code& ec); | |
526 | ||
527 | /** Start an asynchronous write. | |
528 | ||
529 | This function is used to asynchronously write one or more bytes of data to | |
530 | the stream. The function call always returns immediately. | |
531 | ||
532 | @param buffers The data to be written to the stream. Although the buffers | |
533 | object may be copied as necessary, ownership of the underlying buffers is | |
534 | retained by the caller, which must guarantee that they remain valid until | |
535 | the handler is called. | |
536 | ||
537 | @param handler The completion handler to invoke when the operation | |
538 | completes. The implementation takes ownership of the handler by | |
539 | performing a decay-copy. The equivalent function signature of | |
540 | the handler must be: | |
541 | @code | |
542 | void handler( | |
543 | error_code const& ec, // Result of operation. | |
544 | std::size_t bytes_transferred // Number of bytes written. | |
545 | ); | |
546 | @endcode | |
547 | Regardless of whether the asynchronous operation completes | |
548 | immediately or not, the handler will not be invoked from within | |
549 | this function. Invocation of the handler will be performed in a | |
550 | manner equivalent to using `net::post`. | |
551 | ||
552 | @note The `async_write_some` operation may not transmit all of the data to | |
553 | the peer. Consider using the function `net::async_write` if you need | |
554 | to ensure that all data is written before the asynchronous operation completes. | |
555 | */ | |
556 | template< | |
557 | class ConstBufferSequence, | |
558 | BOOST_BEAST_ASYNC_TPARAM2 WriteHandler> | |
559 | BOOST_BEAST_ASYNC_RESULT2(WriteHandler) | |
560 | async_write_some( | |
561 | ConstBufferSequence const& buffers, | |
562 | WriteHandler&& handler); | |
563 | ||
564 | #if ! BOOST_BEAST_DOXYGEN | |
565 | friend | |
566 | BOOST_BEAST_DECL | |
567 | void | |
568 | teardown( | |
569 | role_type, | |
570 | stream& s, | |
571 | boost::system::error_code& ec); | |
572 | ||
573 | template<class TeardownHandler> | |
574 | friend | |
575 | BOOST_BEAST_DECL | |
576 | void | |
577 | async_teardown( | |
578 | role_type role, | |
579 | stream& s, | |
580 | TeardownHandler&& handler); | |
581 | #endif | |
582 | }; | |
583 | ||
584 | #if ! BOOST_BEAST_DOXYGEN | |
585 | inline | |
586 | void | |
587 | beast_close_socket(stream& s) | |
588 | { | |
589 | s.close(); | |
590 | } | |
591 | #endif | |
592 | ||
593 | #if BOOST_BEAST_DOXYGEN | |
594 | /** Return a new stream connected to the given stream | |
595 | ||
596 | @param to The stream to connect to. | |
597 | ||
598 | @param args Optional arguments forwarded to the new stream's constructor. | |
599 | ||
600 | @return The new, connected stream. | |
601 | */ | |
602 | template<class... Args> | |
603 | stream | |
604 | connect(stream& to, Args&&... args); | |
605 | ||
606 | #else | |
607 | BOOST_BEAST_DECL | |
608 | stream | |
609 | connect(stream& to); | |
610 | ||
611 | BOOST_BEAST_DECL | |
612 | void | |
613 | connect(stream& s1, stream& s2); | |
614 | ||
615 | template<class Arg1, class... ArgN> | |
616 | stream | |
617 | connect(stream& to, Arg1&& arg1, ArgN&&... argn); | |
618 | #endif | |
619 | ||
620 | } // test | |
621 | } // beast | |
622 | } // boost | |
623 | ||
624 | #include <boost/beast/_experimental/test/impl/stream.hpp> | |
625 | #ifdef BOOST_BEAST_HEADER_ONLY | |
626 | #include <boost/beast/_experimental/test/impl/stream.ipp> | |
627 | #endif | |
628 | ||
629 | #endif |