1//
2// MessagePack for C++ zero-copy buffer implementation
3//
4// Copyright (C) 2008-2013 FURUHASHI Sadayuki and KONDO Takatoshi
5//
6//    Distributed under the Boost Software License, Version 1.0.
7//    (See accompanying file LICENSE_1_0.txt or copy at
8//    http://www.boost.org/LICENSE_1_0.txt)
9//
10#ifndef MSGPACK_VREFBUFFER_HPP
11#define MSGPACK_VREFBUFFER_HPP
12
13#include "msgpack/versioning.hpp"
14
15#include <stdexcept>
16
17#ifndef MSGPACK_VREFBUFFER_REF_SIZE
18#define MSGPACK_VREFBUFFER_REF_SIZE 32
19#endif
20
21#ifndef MSGPACK_VREFBUFFER_CHUNK_SIZE
22#define MSGPACK_VREFBUFFER_CHUNK_SIZE 8192
23#endif
24
25#ifndef _WIN32
26#include <sys/uio.h>
27#else
28struct iovec {
29    void  *iov_base;
30    size_t iov_len;
31};
32#endif
33
34namespace msgpack {
35
36/// @cond
37MSGPACK_API_VERSION_NAMESPACE(v1) {
38/// @endcond
39
40namespace detail {
41    // int64, uint64, double
42    std::size_t const packer_max_buffer_size = 9;
43} // detail
44
45class vrefbuffer {
46private:
47    struct chunk {
48        chunk* next;
49    };
50    struct inner_buffer {
51        size_t free;
52        char*  ptr;
53        chunk* head;
54    };
55public:
56    vrefbuffer(size_t ref_size = MSGPACK_VREFBUFFER_REF_SIZE,
57               size_t chunk_size = MSGPACK_VREFBUFFER_CHUNK_SIZE)
58        :m_ref_size(std::max(ref_size, detail::packer_max_buffer_size + 1)),
59         m_chunk_size(chunk_size)
60    {
61        size_t nfirst = (sizeof(iovec) < 72/2) ?
62            72 / sizeof(iovec) : 8;
63
64        iovec* array = static_cast<iovec*>(::malloc(
65            sizeof(iovec) * nfirst));
66        if(!array) {
67            throw std::bad_alloc();
68        }
69
70        m_tail  = array;
71        m_end   = array + nfirst;
72        m_array = array;
73
74        chunk* c = static_cast<chunk*>(::malloc(sizeof(chunk) + chunk_size));
75        if(!c) {
76            ::free(array);
77            throw std::bad_alloc();
78        }
79        inner_buffer* const ib = &m_inner_buffer;
80
81        ib->free = chunk_size;
82        ib->ptr      = reinterpret_cast<char*>(c) + sizeof(chunk);
83        ib->head = c;
84        c->next = nullptr;
85
86    }
87
88    ~vrefbuffer()
89    {
90        chunk* c = m_inner_buffer.head;
91        while(true) {
92            chunk* n = c->next;
93            ::free(c);
94            if(n != NULL) {
95                c = n;
96            } else {
97                break;
98            }
99        }
100        ::free(m_array);
101    }
102
103public:
104    void write(const char* buf, size_t len)
105    {
106        if(len < m_ref_size) {
107            append_copy(buf, len);
108        } else {
109            append_ref(buf, len);
110        }
111    }
112
113    void append_ref(const char* buf, size_t len)
114    {
115        if(m_tail == m_end) {
116            const size_t nused = m_tail - m_array;
117            const size_t nnext = nused * 2;
118
119            iovec* nvec = static_cast<iovec*>(::realloc(
120                m_array, sizeof(iovec)*nnext));
121            if(!nvec) {
122                throw std::bad_alloc();
123            }
124
125            m_array = nvec;
126            m_end   = nvec + nnext;
127            m_tail  = nvec + nused;
128        }
129
130        m_tail->iov_base = const_cast<char*>(buf);
131        m_tail->iov_len     = len;
132        ++m_tail;
133    }
134
135    void append_copy(const char* buf, size_t len)
136    {
137        inner_buffer* const ib = &m_inner_buffer;
138
139        if(ib->free < len) {
140            size_t sz = m_chunk_size;
141            if(sz < len) {
142                sz = len;
143            }
144
145            chunk* c = static_cast<chunk*>(::malloc(sizeof(chunk) + sz));
146            if(!c) {
147                throw std::bad_alloc();
148            }
149
150            c->next = ib->head;
151            ib->head = c;
152            ib->free = sz;
153            ib->ptr      = reinterpret_cast<char*>(c) + sizeof(chunk);
154        }
155
156        char* m = ib->ptr;
157        std::memcpy(m, buf, len);
158        ib->free -= len;
159        ib->ptr      += len;
160
161        if(m_tail != m_array && m ==
162            static_cast<const char*>(
163                const_cast<const void *>((m_tail - 1)->iov_base)
164            ) + (m_tail - 1)->iov_len) {
165            (m_tail - 1)->iov_len += len;
166            return;
167        } else {
168            append_ref( m, len);
169        }
170    }
171
172    const struct iovec* vector() const
173    {
174        return m_array;
175    }
176
177    size_t vector_size() const
178    {
179        return m_tail - m_array;
180    }
181
182    void migrate(vrefbuffer* to)
183    {
184        size_t sz = m_chunk_size;
185
186        chunk* empty = static_cast<chunk*>(::malloc(sizeof(chunk) + sz));
187        if(!empty) {
188            throw std::bad_alloc();
189        }
190
191        empty->next = nullptr;
192
193        const size_t nused = m_tail - m_array;
194        if(to->m_tail + nused < m_end) {
195            const size_t tosize = to->m_tail - to->m_array;
196            const size_t reqsize = nused + tosize;
197            size_t nnext = (to->m_end - to->m_array) * 2;
198            while(nnext < reqsize) {
199                size_t tmp_nnext = nnext * 2;
200                if (tmp_nnext <= nnext) {
201                    nnext = reqsize;
202                    break;
203                }
204                nnext = tmp_nnext;
205            }
206
207            iovec* nvec = static_cast<iovec*>(::realloc(
208                to->m_array, sizeof(iovec)*nnext));
209            if(!nvec) {
210                ::free(empty);
211                throw std::bad_alloc();
212            }
213
214            to->m_array = nvec;
215            to->m_end   = nvec + nnext;
216            to->m_tail  = nvec + tosize;
217        }
218
219        std::memcpy(to->m_tail, m_array, sizeof(iovec)*nused);
220
221        to->m_tail += nused;
222        m_tail = m_array;
223
224
225        inner_buffer* const ib = &m_inner_buffer;
226        inner_buffer* const toib = &to->m_inner_buffer;
227
228        chunk* last = ib->head;
229        while(last->next) {
230            last = last->next;
231        }
232        last->next = toib->head;
233        toib->head = ib->head;
234
235        if(toib->free < ib->free) {
236            toib->free = ib->free;
237            toib->ptr  = ib->ptr;
238        }
239
240        ib->head = empty;
241        ib->free = sz;
242        ib->ptr      = reinterpret_cast<char*>(empty) + sizeof(chunk);
243
244    }
245
246    void clear()
247    {
248        chunk* c = m_inner_buffer.head->next;
249        chunk* n;
250        while(c) {
251            n = c->next;
252            ::free(c);
253            c = n;
254        }
255
256        inner_buffer* const ib = &m_inner_buffer;
257        c = ib->head;
258        c->next = nullptr;
259        ib->free = m_chunk_size;
260        ib->ptr      = reinterpret_cast<char*>(c) + sizeof(chunk);
261
262        m_tail = m_array;
263    }
264
265#if defined(MSGPACK_USE_CPP03)
266private:
267    vrefbuffer(const vrefbuffer&);
268    vrefbuffer& operator=(const vrefbuffer&);
269#else  // defined(MSGPACK_USE_CPP03)
270    vrefbuffer(const vrefbuffer&) = delete;
271    vrefbuffer& operator=(const vrefbuffer&) = delete;
272#endif // defined(MSGPACK_USE_CPP03)
273
274private:
275    iovec* m_tail;
276    iovec* m_end;
277    iovec* m_array;
278
279    size_t m_ref_size;
280    size_t m_chunk_size;
281
282    inner_buffer m_inner_buffer;
283
284};
285
286/// @cond
287}  // MSGPACK_API_VERSION_NAMESPACE(v1)
288/// @endcond
289
290}  // namespace msgpack
291
292#endif /* msgpack/vrefbuffer.hpp */
293