1 //
2 // basic_streambuf.hpp
3 // ~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2017 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 ASIO_BASIC_STREAMBUF_HPP
12 #define ASIO_BASIC_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 "asio/detail/config.hpp"
19 
20 #if !defined(ASIO_NO_IOSTREAM)
21 
22 #include <algorithm>
23 #include <cstring>
24 #include <stdexcept>
25 #include <streambuf>
26 #include <vector>
27 #include "asio/basic_streambuf_fwd.hpp"
28 #include "asio/buffer.hpp"
29 #include "asio/detail/limits.hpp"
30 #include "asio/detail/noncopyable.hpp"
31 #include "asio/detail/throw_exception.hpp"
32 
33 #include "asio/detail/push_options.hpp"
34 
35 namespace asio {
36 
37 /// Automatically resizable buffer class based on std::streambuf.
38 /**
39  * The @c basic_streambuf class is derived from @c std::streambuf to associate
40  * the streambuf's input and output sequences with one or more character
41  * arrays. These character arrays are internal to the @c basic_streambuf
42  * object, but direct access to the array elements is provided to permit them
43  * to be used efficiently with I/O operations. Characters written to the output
44  * sequence of a @c basic_streambuf object are appended to the input sequence
45  * of the same object.
46  *
47  * The @c basic_streambuf class's public interface is intended to permit the
48  * following implementation strategies:
49  *
50  * @li A single contiguous character array, which is reallocated as necessary
51  * to accommodate changes in the size of the character sequence. This is the
52  * implementation approach currently used in Asio.
53  *
54  * @li A sequence of one or more character arrays, where each array is of the
55  * same size. Additional character array objects are appended to the sequence
56  * to accommodate changes in the size of the character sequence.
57  *
58  * @li A sequence of one or more character arrays of varying sizes. Additional
59  * character array objects are appended to the sequence to accommodate changes
60  * in the size of the character sequence.
61  *
62  * The constructor for basic_streambuf accepts a @c size_t argument specifying
63  * the maximum of the sum of the sizes of the input sequence and output
64  * sequence. During the lifetime of the @c basic_streambuf object, the following
65  * invariant holds:
66  * @code size() <= max_size()@endcode
67  * Any member function that would, if successful, cause the invariant to be
68  * violated shall throw an exception of class @c std::length_error.
69  *
70  * The constructor for @c basic_streambuf takes an Allocator argument. A copy
71  * of this argument is used for any memory allocation performed, by the
72  * constructor and by all member functions, during the lifetime of each @c
73  * basic_streambuf object.
74  *
75  * @par Examples
76  * Writing directly from an streambuf to a socket:
77  * @code
78  * asio::streambuf b;
79  * std::ostream os(&b);
80  * os << "Hello, World!\n";
81  *
82  * // try sending some data in input sequence
83  * size_t n = sock.send(b.data());
84  *
85  * b.consume(n); // sent data is removed from input sequence
86  * @endcode
87  *
88  * Reading from a socket directly into a streambuf:
89  * @code
90  * asio::streambuf b;
91  *
92  * // reserve 512 bytes in output sequence
93  * asio::streambuf::mutable_buffers_type bufs = b.prepare(512);
94  *
95  * size_t n = sock.receive(bufs);
96  *
97  * // received data is "committed" from output sequence to input sequence
98  * b.commit(n);
99  *
100  * std::istream is(&b);
101  * std::string s;
102  * is >> s;
103  * @endcode
104  */
105 #if defined(GENERATING_DOCUMENTATION)
106 template <typename Allocator = std::allocator<char> >
107 #else
108 template <typename Allocator>
109 #endif
110 class basic_streambuf
111   : public std::streambuf,
112     private noncopyable
113 {
114 public:
115 #if defined(GENERATING_DOCUMENTATION)
116   /// The type used to represent the input sequence as a list of buffers.
117   typedef implementation_defined const_buffers_type;
118 
119   /// The type used to represent the output sequence as a list of buffers.
120   typedef implementation_defined mutable_buffers_type;
121 #else
122   typedef ASIO_CONST_BUFFER const_buffers_type;
123   typedef ASIO_MUTABLE_BUFFER mutable_buffers_type;
124 #endif
125 
126   /// Construct a basic_streambuf object.
127   /**
128    * Constructs a streambuf with the specified maximum size. The initial size
129    * of the streambuf's input sequence is 0.
130    */
basic_streambuf(std::size_t maximum_size=(std::numeric_limits<std::size_t>::max)(),const Allocator & allocator=Allocator ())131   explicit basic_streambuf(
132       std::size_t maximum_size = (std::numeric_limits<std::size_t>::max)(),
133       const Allocator& allocator = Allocator())
134     : max_size_(maximum_size),
135       buffer_(allocator)
136   {
137     std::size_t pend = (std::min<std::size_t>)(max_size_, buffer_delta);
138     buffer_.resize((std::max<std::size_t>)(pend, 1));
139     setg(&buffer_[0], &buffer_[0], &buffer_[0]);
140     setp(&buffer_[0], &buffer_[0] + pend);
141   }
142 
143   /// Get the size of the input sequence.
144   /**
145    * @returns The size of the input sequence. The value is equal to that
146    * calculated for @c s in the following code:
147    * @code
148    * size_t s = 0;
149    * const_buffers_type bufs = data();
150    * const_buffers_type::const_iterator i = bufs.begin();
151    * while (i != bufs.end())
152    * {
153    *   const_buffer buf(*i++);
154    *   s += buf.size();
155    * }
156    * @endcode
157    */
size() const158   std::size_t size() const ASIO_NOEXCEPT
159   {
160     return pptr() - gptr();
161   }
162 
163   /// Get the maximum size of the basic_streambuf.
164   /**
165    * @returns The allowed maximum of the sum of the sizes of the input sequence
166    * and output sequence.
167    */
max_size() const168   std::size_t max_size() const ASIO_NOEXCEPT
169   {
170     return max_size_;
171   }
172 
173   /// Get the current capacity of the basic_streambuf.
174   /**
175    * @returns The current total capacity of the streambuf, i.e. for both the
176    * input sequence and output sequence.
177    */
capacity() const178   std::size_t capacity() const ASIO_NOEXCEPT
179   {
180     return buffer_.capacity();
181   }
182 
183   /// Get a list of buffers that represents the input sequence.
184   /**
185    * @returns An object of type @c const_buffers_type that satisfies
186    * ConstBufferSequence requirements, representing all character arrays in the
187    * input sequence.
188    *
189    * @note The returned object is invalidated by any @c basic_streambuf member
190    * function that modifies the input sequence or output sequence.
191    */
data() const192   const_buffers_type data() const ASIO_NOEXCEPT
193   {
194     return asio::buffer(asio::const_buffer(gptr(),
195           (pptr() - gptr()) * sizeof(char_type)));
196   }
197 
198   /// Get a list of buffers that represents the output sequence, with the given
199   /// size.
200   /**
201    * Ensures that the output sequence can accommodate @c n characters,
202    * reallocating character array objects as necessary.
203    *
204    * @returns An object of type @c mutable_buffers_type that satisfies
205    * MutableBufferSequence requirements, representing character array objects
206    * at the start of the output sequence such that the sum of the buffer sizes
207    * is @c n.
208    *
209    * @throws std::length_error If <tt>size() + n > max_size()</tt>.
210    *
211    * @note The returned object is invalidated by any @c basic_streambuf member
212    * function that modifies the input sequence or output sequence.
213    */
prepare(std::size_t n)214   mutable_buffers_type prepare(std::size_t n)
215   {
216     reserve(n);
217     return asio::buffer(asio::mutable_buffer(
218           pptr(), n * sizeof(char_type)));
219   }
220 
221   /// Move characters from the output sequence to the input sequence.
222   /**
223    * Appends @c n characters from the start of the output sequence to the input
224    * sequence. The beginning of the output sequence is advanced by @c n
225    * characters.
226    *
227    * Requires a preceding call <tt>prepare(x)</tt> where <tt>x >= n</tt>, and
228    * no intervening operations that modify the input or output sequence.
229    *
230    * @note If @c n is greater than the size of the output sequence, the entire
231    * output sequence is moved to the input sequence and no error is issued.
232    */
commit(std::size_t n)233   void commit(std::size_t n)
234   {
235     n = std::min<std::size_t>(n, epptr() - pptr());
236     pbump(static_cast<int>(n));
237     setg(eback(), gptr(), pptr());
238   }
239 
240   /// Remove characters from the input sequence.
241   /**
242    * Removes @c n characters from the beginning of the input sequence.
243    *
244    * @note If @c n is greater than the size of the input sequence, the entire
245    * input sequence is consumed and no error is issued.
246    */
consume(std::size_t n)247   void consume(std::size_t n)
248   {
249     if (egptr() < pptr())
250       setg(&buffer_[0], gptr(), pptr());
251     if (gptr() + n > pptr())
252       n = pptr() - gptr();
253     gbump(static_cast<int>(n));
254   }
255 
256 protected:
257   enum { buffer_delta = 128 };
258 
259   /// Override std::streambuf behaviour.
260   /**
261    * Behaves according to the specification of @c std::streambuf::underflow().
262    */
underflow()263   int_type underflow()
264   {
265     if (gptr() < pptr())
266     {
267       setg(&buffer_[0], gptr(), pptr());
268       return traits_type::to_int_type(*gptr());
269     }
270     else
271     {
272       return traits_type::eof();
273     }
274   }
275 
276   /// Override std::streambuf behaviour.
277   /**
278    * Behaves according to the specification of @c std::streambuf::overflow(),
279    * with the specialisation that @c std::length_error is thrown if appending
280    * the character to the input sequence would require the condition
281    * <tt>size() > max_size()</tt> to be true.
282    */
overflow(int_type c)283   int_type overflow(int_type c)
284   {
285     if (!traits_type::eq_int_type(c, traits_type::eof()))
286     {
287       if (pptr() == epptr())
288       {
289         std::size_t buffer_size = pptr() - gptr();
290         if (buffer_size < max_size_ && max_size_ - buffer_size < buffer_delta)
291         {
292           reserve(max_size_ - buffer_size);
293         }
294         else
295         {
296           reserve(buffer_delta);
297         }
298       }
299 
300       *pptr() = traits_type::to_char_type(c);
301       pbump(1);
302       return c;
303     }
304 
305     return traits_type::not_eof(c);
306   }
307 
reserve(std::size_t n)308   void reserve(std::size_t n)
309   {
310     // Get current stream positions as offsets.
311     std::size_t gnext = gptr() - &buffer_[0];
312     std::size_t pnext = pptr() - &buffer_[0];
313     std::size_t pend = epptr() - &buffer_[0];
314 
315     // Check if there is already enough space in the put area.
316     if (n <= pend - pnext)
317     {
318       return;
319     }
320 
321     // Shift existing contents of get area to start of buffer.
322     if (gnext > 0)
323     {
324       pnext -= gnext;
325       std::memmove(&buffer_[0], &buffer_[0] + gnext, pnext);
326     }
327 
328     // Ensure buffer is large enough to hold at least the specified size.
329     if (n > pend - pnext)
330     {
331       if (n <= max_size_ && pnext <= max_size_ - n)
332       {
333         pend = pnext + n;
334         buffer_.resize((std::max<std::size_t>)(pend, 1));
335       }
336       else
337       {
338         std::length_error ex("asio::streambuf too long");
339         asio::detail::throw_exception(ex);
340       }
341     }
342 
343     // Update stream positions.
344     setg(&buffer_[0], &buffer_[0], &buffer_[0] + pnext);
345     setp(&buffer_[0] + pnext, &buffer_[0] + pend);
346   }
347 
348 private:
349   std::size_t max_size_;
350   std::vector<char_type, Allocator> buffer_;
351 
352   // Helper function to get the preferred size for reading data.
read_size_helper(basic_streambuf & sb,std::size_t max_size)353   friend std::size_t read_size_helper(
354       basic_streambuf& sb, std::size_t max_size)
355   {
356     return std::min<std::size_t>(
357         std::max<std::size_t>(512, sb.buffer_.capacity() - sb.size()),
358         std::min<std::size_t>(max_size, sb.max_size() - sb.size()));
359   }
360 };
361 
362 /// Adapts basic_streambuf to the dynamic buffer sequence type requirements.
363 #if defined(GENERATING_DOCUMENTATION)
364 template <typename Allocator = std::allocator<char> >
365 #else
366 template <typename Allocator>
367 #endif
368 class basic_streambuf_ref
369 {
370 public:
371   /// The type used to represent the input sequence as a list of buffers.
372   typedef typename basic_streambuf<Allocator>::const_buffers_type
373     const_buffers_type;
374 
375   /// The type used to represent the output sequence as a list of buffers.
376   typedef typename basic_streambuf<Allocator>::mutable_buffers_type
377     mutable_buffers_type;
378 
379   /// Construct a basic_streambuf_ref for the given basic_streambuf object.
basic_streambuf_ref(basic_streambuf<Allocator> & sb)380   explicit basic_streambuf_ref(basic_streambuf<Allocator>& sb)
381     : sb_(sb)
382   {
383   }
384 
385   /// Copy construct a basic_streambuf_ref.
basic_streambuf_ref(const basic_streambuf_ref & other)386   basic_streambuf_ref(const basic_streambuf_ref& other) ASIO_NOEXCEPT
387     : sb_(other.sb_)
388   {
389   }
390 
391 #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
392   /// Move construct a basic_streambuf_ref.
basic_streambuf_ref(basic_streambuf_ref && other)393   basic_streambuf_ref(basic_streambuf_ref&& other) ASIO_NOEXCEPT
394     : sb_(other.sb_)
395   {
396   }
397 #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
398 
399   /// Get the size of the input sequence.
size() const400   std::size_t size() const ASIO_NOEXCEPT
401   {
402     return sb_.size();
403   }
404 
405   /// Get the maximum size of the dynamic buffer.
max_size() const406   std::size_t max_size() const ASIO_NOEXCEPT
407   {
408     return sb_.max_size();
409   }
410 
411   /// Get the current capacity of the dynamic buffer.
capacity() const412   std::size_t capacity() const ASIO_NOEXCEPT
413   {
414     return sb_.capacity();
415   }
416 
417   /// Get a list of buffers that represents the input sequence.
data() const418   const_buffers_type data() const ASIO_NOEXCEPT
419   {
420     return sb_.data();
421   }
422 
423   /// Get a list of buffers that represents the output sequence, with the given
424   /// size.
prepare(std::size_t n)425   mutable_buffers_type prepare(std::size_t n)
426   {
427     return sb_.prepare(n);
428   }
429 
430   /// Move bytes from the output sequence to the input sequence.
commit(std::size_t n)431   void commit(std::size_t n)
432   {
433     return sb_.commit(n);
434   }
435 
436   /// Remove characters from the input sequence.
consume(std::size_t n)437   void consume(std::size_t n)
438   {
439     return sb_.consume(n);
440   }
441 
442 private:
443   basic_streambuf<Allocator>& sb_;
444 };
445 
446 } // namespace asio
447 
448 #include "asio/detail/pop_options.hpp"
449 
450 #endif // !defined(ASIO_NO_IOSTREAM)
451 
452 #endif // ASIO_BASIC_STREAMBUF_HPP
453