1 // Written in the D programming language.
2 
3 module msgpack.buffer;
4 
5 //import std.traits;
6 import std.range;
7 
8 
9 version(Posix)
10 {
11     import core.sys.posix.sys.uio : iovec;
12 }
13 else
14 {
15     /**
16      * from core.sys.posix.sys.uio.iovec for compatibility with posix.
17      */
18     struct iovec
19     {
20         void*  iov_base;
21         size_t iov_len;
22     }
23 }
24 
25 
26 /**
27  * $(D RefBuffer) is a reference stored buffer for more efficient serialization
28  *
29  * Example:
30  * -----
31  * auto packer = packer(RefBuffer(16));  // threshold is 16
32  *
33  * // packs data
34  *
35  * writev(fd, cast(void*)packer.buffer.vector.ptr, packer.buffer.vector.length);
36  * -----
37  */
38 struct RefBuffer
39 {
40   private:
41     static struct Chunk
42     {
43         ubyte[] data;  // storing serialized value
44         size_t  used;  // used size of data
45     }
46 
47     immutable size_t Threshold;
48     immutable size_t ChunkSize;
49 
50     // for putCopy
51     Chunk[] chunks_;  // memory chunk for buffer
52     size_t  index_;   // index for cunrrent chunk
53 
54     // for putRef
55     iovec[] vecList_;  // reference to large data or copied data.
56 
57 
58   public:
59     /**
60      * Constructs a buffer.
61      *
62      * Params:
63      *  threshold = the threshold of writing value or stores reference.
64      *  chunkSize = the default size of chunk for allocation.
65      */
66     @safe
67     this(in size_t threshold, in size_t chunkSize = 8192)
68     {
69         Threshold = threshold;
70         ChunkSize = chunkSize;
71 
72         chunks_.length = 1;
73         chunks_[index_].data.length = chunkSize;
74     }
75 
76 
77     /**
78      * Returns the buffer contents that excluding references.
79      *
80      * Returns:
81      *  the non-contiguous copied contents.
82      */
83     @property @safe
84     nothrow ubyte[] data()
85     {
86         ubyte[] result;
87 
88         foreach (ref chunk; chunks_)
89             result ~= chunk.data[0..chunk.used];
90 
91         return result;
92     }
93 
94 
95     /**
96      * Forwards to all buffer contents.
97      *
98      * Returns:
99      *  the array of iovec struct that stores references.
100      */
101     @property @safe
102     nothrow ref iovec[] vector() return
103     {
104         return vecList_;
105     }
106 
107 
108     /**
109      * Writes the argument to buffer and stores the reference of writed content
110      * if the argument size is smaller than threshold,
111      * otherwise stores the reference of argument directly.
112      *
113      * Params:
114      *  value = the content to write.
115      */
116     @safe
117     void put(in ubyte value)
118     {
119         ubyte[1] values = [value];
120         putCopy(values);
121     }
122 
123 
124     /// ditto
125     @safe
126     void put(in ubyte[] value)
127     {
128         if (value.length < Threshold)
129             putCopy(value);
130         else
131             putRef(value);
132     }
133 
134 
135   private:
136     /*
137      * Stores the reference of $(D_PARAM value).
138      *
139      * Params:
140      *  value = the content to write.
141      */
142     @trusted
143     void putRef(in ubyte[] value)
144     {
145         vecList_.length += 1;
146         vecList_[$ - 1]  = iovec(cast(void*)value.ptr, value.length);
147     }
148 
149 
150     /*
151      * Writes $(D_PARAM value) to buffer and appends to its reference.
152      *
153      * Params:
154      *  value = the contents to write.
155      */
156     @trusted
157     void putCopy(const scope ubyte[] value)
158     {
159         /*
160          * Helper for expanding new space.
161          */
162         void expand(in size_t size)
163         {
164             const newSize = size < ChunkSize ? ChunkSize : size;
165 
166             index_++;
167             chunks_.length = 1;
168             chunks_[index_].data.length = newSize;
169         }
170 
171         const size = value.length;
172 
173         // lacks current chunk?
174         if (chunks_[index_].data.length - chunks_[index_].used < size)
175             expand(size);
176 
177         const base = chunks_[index_].used;                     // start index
178         auto  data = chunks_[index_].data[base..base + size];  // chunk to write
179 
180         data[] = value[];
181         chunks_[index_].used += size;
182 
183         // Optimization for avoiding iovec allocation.
184         if (vecList_.length && data.ptr == (vecList_[$ - 1].iov_base +
185                                             vecList_[$ - 1].iov_len))
186             vecList_[$ - 1].iov_len += size;
187         else
188             putRef(data);
189     }
190 }
191 
192 
193 unittest
194 {
195     static assert(isOutputRange!(RefBuffer, ubyte) &&
196                   isOutputRange!(RefBuffer, ubyte[]));
197 
198     auto buffer = RefBuffer(2, 4);
199 
200     ubyte[] tests = [1, 2];
201     foreach (v; tests)
202         buffer.put(v);
203     buffer.put(tests);
204 
205     assert(buffer.data == tests, "putCopy failed");
206 
207     iovec[] vector = buffer.vector;
208     ubyte[] result;
209 
210     assert(vector.length == 2, "Optimization failed");
211 
212     foreach (v; vector)
213         result ~= (cast(ubyte*)v.iov_base)[0..v.iov_len];
214 
215     assert(result == tests ~ tests);
216 }