1 //
2 // basic_streambuf.hpp
3 // ~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2016 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     if (pptr() + n > epptr())
236       n = epptr() - pptr();
237     pbump(static_cast<int>(n));
238     setg(eback(), gptr(), pptr());
239   }
240 
241   /// Remove characters from the input sequence.
242   /**
243    * Removes @c n characters from the beginning of the input sequence.
244    *
245    * @note If @c n is greater than the size of the input sequence, the entire
246    * input sequence is consumed and no error is issued.
247    */
consume(std::size_t n)248   void consume(std::size_t n)
249   {
250     if (egptr() < pptr())
251       setg(&buffer_[0], gptr(), pptr());
252     if (gptr() + n > pptr())
253       n = pptr() - gptr();
254     gbump(static_cast<int>(n));
255   }
256 
257 protected:
258   enum { buffer_delta = 128 };
259 
260   /// Override std::streambuf behaviour.
261   /**
262    * Behaves according to the specification of @c std::streambuf::underflow().
263    */
underflow()264   int_type underflow()
265   {
266     if (gptr() < pptr())
267     {
268       setg(&buffer_[0], gptr(), pptr());
269       return traits_type::to_int_type(*gptr());
270     }
271     else
272     {
273       return traits_type::eof();
274     }
275   }
276 
277   /// Override std::streambuf behaviour.
278   /**
279    * Behaves according to the specification of @c std::streambuf::overflow(),
280    * with the specialisation that @c std::length_error is thrown if appending
281    * the character to the input sequence would require the condition
282    * <tt>size() > max_size()</tt> to be true.
283    */
overflow(int_type c)284   int_type overflow(int_type c)
285   {
286     if (!traits_type::eq_int_type(c, traits_type::eof()))
287     {
288       if (pptr() == epptr())
289       {
290         std::size_t buffer_size = pptr() - gptr();
291         if (buffer_size < max_size_ && max_size_ - buffer_size < buffer_delta)
292         {
293           reserve(max_size_ - buffer_size);
294         }
295         else
296         {
297           reserve(buffer_delta);
298         }
299       }
300 
301       *pptr() = traits_type::to_char_type(c);
302       pbump(1);
303       return c;
304     }
305 
306     return traits_type::not_eof(c);
307   }
308 
reserve(std::size_t n)309   void reserve(std::size_t n)
310   {
311     // Get current stream positions as offsets.
312     std::size_t gnext = gptr() - &buffer_[0];
313     std::size_t pnext = pptr() - &buffer_[0];
314     std::size_t pend = epptr() - &buffer_[0];
315 
316     // Check if there is already enough space in the put area.
317     if (n <= pend - pnext)
318     {
319       return;
320     }
321 
322     // Shift existing contents of get area to start of buffer.
323     if (gnext > 0)
324     {
325       pnext -= gnext;
326       std::memmove(&buffer_[0], &buffer_[0] + gnext, pnext);
327     }
328 
329     // Ensure buffer is large enough to hold at least the specified size.
330     if (n > pend - pnext)
331     {
332       if (n <= max_size_ && pnext <= max_size_ - n)
333       {
334         pend = pnext + n;
335         buffer_.resize((std::max<std::size_t>)(pend, 1));
336       }
337       else
338       {
339         std::length_error ex("asio::streambuf too long");
340         asio::detail::throw_exception(ex);
341       }
342     }
343 
344     // Update stream positions.
345     setg(&buffer_[0], &buffer_[0], &buffer_[0] + pnext);
346     setp(&buffer_[0] + pnext, &buffer_[0] + pend);
347   }
348 
349 private:
350   std::size_t max_size_;
351   std::vector<char_type, Allocator> buffer_;
352 
353   // Helper function to get the preferred size for reading data.
read_size_helper(basic_streambuf & sb,std::size_t max_size)354   friend std::size_t read_size_helper(
355       basic_streambuf& sb, std::size_t max_size)
356   {
357     return std::min<std::size_t>(
358         std::max<std::size_t>(512, sb.buffer_.capacity() - sb.size()),
359         std::min<std::size_t>(max_size, sb.max_size() - sb.size()));
360   }
361 };
362 
363 /// Adapts basic_streambuf to the dynamic buffer sequence type requirements.
364 #if defined(GENERATING_DOCUMENTATION)
365 template <typename Allocator = std::allocator<char> >
366 #else
367 template <typename Allocator>
368 #endif
369 class basic_streambuf_ref
370 {
371 public:
372   /// The type used to represent the input sequence as a list of buffers.
373   typedef typename basic_streambuf<Allocator>::const_buffers_type
374     const_buffers_type;
375 
376   /// The type used to represent the output sequence as a list of buffers.
377   typedef typename basic_streambuf<Allocator>::mutable_buffers_type
378     mutable_buffers_type;
379 
380   /// Construct a basic_streambuf_ref for the given basic_streambuf object.
basic_streambuf_ref(basic_streambuf<Allocator> & sb)381   explicit basic_streambuf_ref(basic_streambuf<Allocator>& sb)
382     : sb_(sb)
383   {
384   }
385 
386   /// Copy construct a basic_streambuf_ref.
basic_streambuf_ref(const basic_streambuf_ref & other)387   basic_streambuf_ref(const basic_streambuf_ref& other) ASIO_NOEXCEPT
388     : sb_(other.sb_)
389   {
390   }
391 
392 #if defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
393   /// Move construct a basic_streambuf_ref.
basic_streambuf_ref(basic_streambuf_ref && other)394   basic_streambuf_ref(basic_streambuf_ref&& other) ASIO_NOEXCEPT
395     : sb_(other.sb_)
396   {
397   }
398 #endif // defined(ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
399 
400   /// Get the size of the input sequence.
size() const401   std::size_t size() const ASIO_NOEXCEPT
402   {
403     return sb_.size();
404   }
405 
406   /// Get the maximum size of the dynamic buffer.
max_size() const407   std::size_t max_size() const ASIO_NOEXCEPT
408   {
409     return sb_.max_size();
410   }
411 
412   /// Get the current capacity of the dynamic buffer.
capacity() const413   std::size_t capacity() const ASIO_NOEXCEPT
414   {
415     return sb_.capacity();
416   }
417 
418   /// Get a list of buffers that represents the input sequence.
data() const419   const_buffers_type data() const ASIO_NOEXCEPT
420   {
421     return sb_.data();
422   }
423 
424   /// Get a list of buffers that represents the output sequence, with the given
425   /// size.
prepare(std::size_t n)426   mutable_buffers_type prepare(std::size_t n)
427   {
428     return sb_.prepare(n);
429   }
430 
431   /// Move bytes from the output sequence to the input sequence.
commit(std::size_t n)432   void commit(std::size_t n)
433   {
434     return sb_.commit(n);
435   }
436 
437   /// Remove characters from the input sequence.
consume(std::size_t n)438   void consume(std::size_t n)
439   {
440     return sb_.consume(n);
441   }
442 
443 private:
444   basic_streambuf<Allocator>& sb_;
445 };
446 
447 } // namespace asio
448 
449 #include "asio/detail/pop_options.hpp"
450 
451 #endif // !defined(ASIO_NO_IOSTREAM)
452 
453 #endif // ASIO_BASIC_STREAMBUF_HPP
454