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