1 //
2 // basic_socket_streambuf.hpp
3 // ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6 //
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
9 //
10 
11 #ifndef BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP
12 #define BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP
13 
14 #if defined(_MSC_VER) && (_MSC_VER >= 1200)
15 # pragma once
16 #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
17 
18 #include <boost/asio/detail/config.hpp>
19 
20 #if !defined(BOOST_NO_IOSTREAM)
21 
22 #include <streambuf>
23 #include <boost/utility/base_from_member.hpp>
24 #include <boost/asio/basic_socket.hpp>
25 #include <boost/asio/deadline_timer_service.hpp>
26 #include <boost/asio/detail/array.hpp>
27 #include <boost/asio/detail/throw_error.hpp>
28 #include <boost/asio/io_service.hpp>
29 #include <boost/asio/stream_socket_service.hpp>
30 #include <boost/asio/time_traits.hpp>
31 
32 #include <boost/asio/detail/push_options.hpp>
33 #include <boost/date_time/posix_time/posix_time_types.hpp>
34 #include <boost/asio/detail/pop_options.hpp>
35 
36 #if !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES)
37 
38 # include <boost/preprocessor/arithmetic/inc.hpp>
39 # include <boost/preprocessor/repetition/enum_binary_params.hpp>
40 # include <boost/preprocessor/repetition/enum_params.hpp>
41 # include <boost/preprocessor/repetition/repeat_from_to.hpp>
42 
43 # if !defined(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY)
44 #  define BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY 5
45 # endif // !defined(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY)
46 
47 // A macro that should expand to:
48 //   template <typename T1, ..., typename Tn>
49 //   basic_socket_streambuf<Protocol, StreamSocketService,
50 //     Time, TimeTraits, TimerService>* connect(
51 //       T1 x1, ..., Tn xn)
52 //   {
53 //     init_buffers();
54 //     this->basic_socket<Protocol, StreamSocketService>::close(ec_);
55 //     typedef typename Protocol::resolver resolver_type;
56 //     typedef typename resolver_type::query resolver_query;
57 //     resolver_query query(x1, ..., xn);
58 //     resolve_and_connect(query);
59 //     return !ec_ ? this : 0;
60 //   }
61 // This macro should only persist within this file.
62 
63 # define BOOST_ASIO_PRIVATE_CONNECT_DEF( z, n, data ) \
64   template <BOOST_PP_ENUM_PARAMS(n, typename T)> \
65   basic_socket_streambuf<Protocol, StreamSocketService, \
66     Time, TimeTraits, TimerService>* connect( \
67       BOOST_PP_ENUM_BINARY_PARAMS(n, T, x)) \
68   { \
69     init_buffers(); \
70     this->basic_socket<Protocol, StreamSocketService>::close(ec_); \
71     typedef typename Protocol::resolver resolver_type; \
72     typedef typename resolver_type::query resolver_query; \
73     resolver_query query(BOOST_PP_ENUM_PARAMS(n, x)); \
74     resolve_and_connect(query); \
75     return !ec_ ? this : 0; \
76   } \
77   /**/
78 
79 #endif // !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES)
80 
81 #include <boost/asio/detail/push_options.hpp>
82 
83 namespace boost {
84 namespace asio {
85 
86 /// Iostream streambuf for a socket.
87 template <typename Protocol,
88     typename StreamSocketService = stream_socket_service<Protocol>,
89     typename Time = boost::posix_time::ptime,
90     typename TimeTraits = boost::asio::time_traits<Time>,
91     typename TimerService = deadline_timer_service<Time, TimeTraits> >
92 class basic_socket_streambuf
93   : public std::streambuf,
94     private boost::base_from_member<io_service>,
95     public basic_socket<Protocol, StreamSocketService>
96 {
97 public:
98   /// The endpoint type.
99   typedef typename Protocol::endpoint endpoint_type;
100 
101   /// The time type.
102   typedef typename TimeTraits::time_type time_type;
103 
104   /// The duration type.
105   typedef typename TimeTraits::duration_type duration_type;
106 
107   /// Construct a basic_socket_streambuf without establishing a connection.
basic_socket_streambuf()108   basic_socket_streambuf()
109     : basic_socket<Protocol, StreamSocketService>(
110         boost::base_from_member<boost::asio::io_service>::member),
111       unbuffered_(false),
112       timer_service_(0),
113       timer_state_(no_timer)
114   {
115     init_buffers();
116   }
117 
118   /// Destructor flushes buffered data.
~basic_socket_streambuf()119   virtual ~basic_socket_streambuf()
120   {
121     if (pptr() != pbase())
122       overflow(traits_type::eof());
123 
124     destroy_timer();
125   }
126 
127   /// Establish a connection.
128   /**
129    * This function establishes a connection to the specified endpoint.
130    *
131    * @return \c this if a connection was successfully established, a null
132    * pointer otherwise.
133    */
134   basic_socket_streambuf<Protocol, StreamSocketService,
connect(const endpoint_type & endpoint)135     Time, TimeTraits, TimerService>* connect(
136       const endpoint_type& endpoint)
137   {
138     init_buffers();
139 
140     this->basic_socket<Protocol, StreamSocketService>::close(ec_);
141 
142     if (timer_state_ == timer_has_expired)
143     {
144       ec_ = boost::asio::error::operation_aborted;
145       return 0;
146     }
147 
148     io_handler handler = { this };
149     this->basic_socket<Protocol, StreamSocketService>::async_connect(
150         endpoint, handler);
151 
152     ec_ = boost::asio::error::would_block;
153     this->get_service().get_io_service().reset();
154     do this->get_service().get_io_service().run_one();
155     while (ec_ == boost::asio::error::would_block);
156 
157     return !ec_ ? this : 0;
158   }
159 
160 #if defined(GENERATING_DOCUMENTATION)
161   /// Establish a connection.
162   /**
163    * This function automatically establishes a connection based on the supplied
164    * resolver query parameters. The arguments are used to construct a resolver
165    * query object.
166    *
167    * @return \c this if a connection was successfully established, a null
168    * pointer otherwise.
169    */
170   template <typename T1, ..., typename TN>
171   basic_socket_streambuf<Protocol, StreamSocketService>* connect(
172       T1 t1, ..., TN tn);
173 #elif defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES)
174   template <typename... T>
175   basic_socket_streambuf<Protocol, StreamSocketService,
connect(T...x)176     Time, TimeTraits, TimerService>* connect(T... x)
177   {
178     init_buffers();
179     this->basic_socket<Protocol, StreamSocketService>::close(ec_);
180     typedef typename Protocol::resolver resolver_type;
181     typedef typename resolver_type::query resolver_query;
182     resolver_query query(x...);
183     resolve_and_connect(query);
184     return !ec_ ? this : 0;
185   }
186 #else
187   BOOST_PP_REPEAT_FROM_TO(
BOOST_PP_INC(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY)188       1, BOOST_PP_INC(BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY),
189       BOOST_ASIO_PRIVATE_CONNECT_DEF, _ )
190 #endif
191 
192   /// Close the connection.
193   /**
194    * @return \c this if a connection was successfully established, a null
195    * pointer otherwise.
196    */
197   basic_socket_streambuf<Protocol, StreamSocketService,
198     Time, TimeTraits, TimerService>* close()
199   {
200     sync();
201     this->basic_socket<Protocol, StreamSocketService>::close(ec_);
202     if (!ec_)
203       init_buffers();
204     return !ec_ ? this : 0;
205   }
206 
207   /// Get the last error associated with the stream buffer.
208   /**
209    * @return An \c error_code corresponding to the last error from the stream
210    * buffer.
211    */
puberror() const212   const boost::system::error_code& puberror() const
213   {
214     return error();
215   }
216 
217   /// Get the stream buffer's expiry time as an absolute time.
218   /**
219    * @return An absolute time value representing the stream buffer's expiry
220    * time.
221    */
expires_at() const222   time_type expires_at() const
223   {
224     return timer_service_
225       ? timer_service_->expires_at(timer_implementation_)
226       : time_type();
227   }
228 
229   /// Set the stream buffer's expiry time as an absolute time.
230   /**
231    * This function sets the expiry time associated with the stream. Stream
232    * operations performed after this time (where the operations cannot be
233    * completed using the internal buffers) will fail with the error
234    * boost::asio::error::operation_aborted.
235    *
236    * @param expiry_time The expiry time to be used for the stream.
237    */
expires_at(const time_type & expiry_time)238   void expires_at(const time_type& expiry_time)
239   {
240     construct_timer();
241 
242     boost::system::error_code ec;
243     timer_service_->expires_at(timer_implementation_, expiry_time, ec);
244     boost::asio::detail::throw_error(ec, "expires_at");
245 
246     start_timer();
247   }
248 
249   /// Get the stream buffer's expiry time relative to now.
250   /**
251    * @return A relative time value representing the stream buffer's expiry time.
252    */
expires_from_now() const253   duration_type expires_from_now() const
254   {
255     return TimeTraits::subtract(expires_at(), TimeTraits::now());
256   }
257 
258   /// Set the stream buffer's expiry time relative to now.
259   /**
260    * This function sets the expiry time associated with the stream. Stream
261    * operations performed after this time (where the operations cannot be
262    * completed using the internal buffers) will fail with the error
263    * boost::asio::error::operation_aborted.
264    *
265    * @param expiry_time The expiry time to be used for the timer.
266    */
expires_from_now(const duration_type & expiry_time)267   void expires_from_now(const duration_type& expiry_time)
268   {
269     construct_timer();
270 
271     boost::system::error_code ec;
272     timer_service_->expires_from_now(timer_implementation_, expiry_time, ec);
273     boost::asio::detail::throw_error(ec, "expires_from_now");
274 
275     start_timer();
276   }
277 
278 protected:
underflow()279   int_type underflow()
280   {
281     if (gptr() == egptr())
282     {
283       if (timer_state_ == timer_has_expired)
284       {
285         ec_ = boost::asio::error::operation_aborted;
286         return traits_type::eof();
287       }
288 
289       io_handler handler = { this };
290       this->get_service().async_receive(this->get_implementation(),
291           boost::asio::buffer(boost::asio::buffer(get_buffer_) + putback_max),
292           0, handler);
293 
294       ec_ = boost::asio::error::would_block;
295       this->get_service().get_io_service().reset();
296       do this->get_service().get_io_service().run_one();
297       while (ec_ == boost::asio::error::would_block);
298       if (ec_)
299         return traits_type::eof();
300 
301       setg(&get_buffer_[0], &get_buffer_[0] + putback_max,
302           &get_buffer_[0] + putback_max + bytes_transferred_);
303       return traits_type::to_int_type(*gptr());
304     }
305     else
306     {
307       return traits_type::eof();
308     }
309   }
310 
overflow(int_type c)311   int_type overflow(int_type c)
312   {
313     if (unbuffered_)
314     {
315       if (traits_type::eq_int_type(c, traits_type::eof()))
316       {
317         // Nothing to do.
318         return traits_type::not_eof(c);
319       }
320       else
321       {
322         if (timer_state_ == timer_has_expired)
323         {
324           ec_ = boost::asio::error::operation_aborted;
325           return traits_type::eof();
326         }
327 
328         // Send the single character immediately.
329         char_type ch = traits_type::to_char_type(c);
330         io_handler handler = { this };
331         this->get_service().async_send(this->get_implementation(),
332             boost::asio::buffer(&ch, sizeof(char_type)), 0, handler);
333 
334         ec_ = boost::asio::error::would_block;
335         this->get_service().get_io_service().reset();
336         do this->get_service().get_io_service().run_one();
337         while (ec_ == boost::asio::error::would_block);
338         if (ec_)
339           return traits_type::eof();
340 
341         return c;
342       }
343     }
344     else
345     {
346       // Send all data in the output buffer.
347       boost::asio::const_buffer buffer =
348         boost::asio::buffer(pbase(), pptr() - pbase());
349       while (boost::asio::buffer_size(buffer) > 0)
350       {
351         if (timer_state_ == timer_has_expired)
352         {
353           ec_ = boost::asio::error::operation_aborted;
354           return traits_type::eof();
355         }
356 
357         io_handler handler = { this };
358         this->get_service().async_send(this->get_implementation(),
359             boost::asio::buffer(buffer), 0, handler);
360 
361         ec_ = boost::asio::error::would_block;
362         this->get_service().get_io_service().reset();
363         do this->get_service().get_io_service().run_one();
364         while (ec_ == boost::asio::error::would_block);
365         if (ec_)
366           return traits_type::eof();
367 
368         buffer = buffer + bytes_transferred_;
369       }
370       setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size());
371 
372       // If the new character is eof then our work here is done.
373       if (traits_type::eq_int_type(c, traits_type::eof()))
374         return traits_type::not_eof(c);
375 
376       // Add the new character to the output buffer.
377       *pptr() = traits_type::to_char_type(c);
378       pbump(1);
379       return c;
380     }
381   }
382 
sync()383   int sync()
384   {
385     return overflow(traits_type::eof());
386   }
387 
setbuf(char_type * s,std::streamsize n)388   std::streambuf* setbuf(char_type* s, std::streamsize n)
389   {
390     if (pptr() == pbase() && s == 0 && n == 0)
391     {
392       unbuffered_ = true;
393       setp(0, 0);
394       return this;
395     }
396 
397     return 0;
398   }
399 
400   /// Get the last error associated with the stream buffer.
401   /**
402    * @return An \c error_code corresponding to the last error from the stream
403    * buffer.
404    */
error() const405   virtual const boost::system::error_code& error() const
406   {
407     return ec_;
408   }
409 
410 private:
init_buffers()411   void init_buffers()
412   {
413     setg(&get_buffer_[0],
414         &get_buffer_[0] + putback_max,
415         &get_buffer_[0] + putback_max);
416     if (unbuffered_)
417       setp(0, 0);
418     else
419       setp(&put_buffer_[0], &put_buffer_[0] + put_buffer_.size());
420   }
421 
422   template <typename ResolverQuery>
resolve_and_connect(const ResolverQuery & query)423   void resolve_and_connect(const ResolverQuery& query)
424   {
425     typedef typename Protocol::resolver resolver_type;
426     typedef typename resolver_type::iterator iterator_type;
427     resolver_type resolver(
428         boost::base_from_member<boost::asio::io_service>::member);
429     iterator_type i = resolver.resolve(query, ec_);
430     if (!ec_)
431     {
432       iterator_type end;
433       ec_ = boost::asio::error::host_not_found;
434       while (ec_ && i != end)
435       {
436         this->basic_socket<Protocol, StreamSocketService>::close(ec_);
437 
438         if (timer_state_ == timer_has_expired)
439         {
440           ec_ = boost::asio::error::operation_aborted;
441           return;
442         }
443 
444         io_handler handler = { this };
445         this->basic_socket<Protocol, StreamSocketService>::async_connect(
446             *i, handler);
447 
448         ec_ = boost::asio::error::would_block;
449         this->get_service().get_io_service().reset();
450         do this->get_service().get_io_service().run_one();
451         while (ec_ == boost::asio::error::would_block);
452 
453         ++i;
454       }
455     }
456   }
457 
458   struct io_handler;
459   friend struct io_handler;
460   struct io_handler
461   {
462     basic_socket_streambuf* this_;
463 
operator ()boost::asio::basic_socket_streambuf::io_handler464     void operator()(const boost::system::error_code& ec,
465         std::size_t bytes_transferred = 0)
466     {
467       this_->ec_ = ec;
468       this_->bytes_transferred_ = bytes_transferred;
469     }
470   };
471 
472   struct timer_handler;
473   friend struct timer_handler;
474   struct timer_handler
475   {
476     basic_socket_streambuf* this_;
477 
operator ()boost::asio::basic_socket_streambuf::timer_handler478     void operator()(const boost::system::error_code&)
479     {
480       time_type now = TimeTraits::now();
481 
482       time_type expiry_time = this_->timer_service_->expires_at(
483             this_->timer_implementation_);
484 
485       if (TimeTraits::less_than(now, expiry_time))
486       {
487         this_->timer_state_ = timer_is_pending;
488         this_->timer_service_->async_wait(this_->timer_implementation_, *this);
489       }
490       else
491       {
492         this_->timer_state_ = timer_has_expired;
493         boost::system::error_code ec;
494         this_->basic_socket<Protocol, StreamSocketService>::close(ec);
495       }
496     }
497   };
498 
construct_timer()499   void construct_timer()
500   {
501     if (timer_service_ == 0)
502     {
503       TimerService& timer_service = use_service<TimerService>(
504           boost::base_from_member<boost::asio::io_service>::member);
505       timer_service.construct(timer_implementation_);
506       timer_service_ = &timer_service;
507     }
508   }
509 
destroy_timer()510   void destroy_timer()
511   {
512     if (timer_service_)
513       timer_service_->destroy(timer_implementation_);
514   }
515 
start_timer()516   void start_timer()
517   {
518     if (timer_state_ != timer_is_pending)
519     {
520       timer_handler handler = { this };
521       handler(boost::system::error_code());
522     }
523   }
524 
525   enum { putback_max = 8 };
526   enum { buffer_size = 512 };
527   boost::asio::detail::array<char, buffer_size> get_buffer_;
528   boost::asio::detail::array<char, buffer_size> put_buffer_;
529   bool unbuffered_;
530   boost::system::error_code ec_;
531   std::size_t bytes_transferred_;
532   TimerService* timer_service_;
533   typename TimerService::implementation_type timer_implementation_;
534   enum state { no_timer, timer_is_pending, timer_has_expired } timer_state_;
535 };
536 
537 } // namespace asio
538 } // namespace boost
539 
540 #include <boost/asio/detail/pop_options.hpp>
541 
542 #if !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES)
543 # undef BOOST_ASIO_PRIVATE_CONNECT_DEF
544 #endif // !defined(BOOST_ASIO_HAS_VARIADIC_TEMPLATES)
545 
546 #endif // !defined(BOOST_NO_IOSTREAM)
547 
548 #endif // BOOST_ASIO_BASIC_SOCKET_STREAMBUF_HPP
549