1 // Written in the D programming language.
2 
3 /**
4 Serialize data to $(D ubyte) arrays.
5 
6  * Copyright: Copyright Digital Mars 2000 - 2015.
7  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8  * Authors:   $(HTTP digitalmars.com, Walter Bright)
9  * Source:    $(PHOBOSSRC std/_outbuffer.d)
10  *
11  * $(SCRIPT inhibitQuickIndex = 1;)
12  */
13 module std.outbuffer;
14 
15 import core.stdc.stdarg; // : va_list;
16 
17 /*********************************************
18  * OutBuffer provides a way to build up an array of bytes out
19  * of raw data. It is useful for things like preparing an
20  * array of bytes to write out to a file.
21  * OutBuffer's byte order is the format native to the computer.
22  * To control the byte order (endianness), use a class derived
23  * from OutBuffer.
24  * OutBuffer's internal buffer is allocated with the GC. Pointers
25  * stored into the buffer are scanned by the GC, but you have to
26  * ensure proper alignment, e.g. by using alignSize((void*).sizeof).
27  */
28 
29 class OutBuffer
30 {
31     ubyte[] data;
32     size_t offset;
33 
invariant()34     invariant()
35     {
36         assert(offset <= data.length);
37     }
38 
39   pure nothrow @safe
40   {
41     /*********************************
42      * Convert to array of bytes.
43      */
toBytes()44     ubyte[] toBytes() { return data[0 .. offset]; }
45 
46     /***********************************
47      * Preallocate nbytes more to the size of the internal buffer.
48      *
49      * This is a
50      * speed optimization, a good guess at the maximum size of the resulting
51      * buffer will improve performance by eliminating reallocations and copying.
52      */
reserve(size_t nbytes)53     void reserve(size_t nbytes) @trusted
54         in
55         {
56             assert(offset + nbytes >= offset);
57         }
58         out
59         {
60             assert(offset + nbytes <= data.length);
61         }
62         body
63         {
64             if (data.length < offset + nbytes)
65             {
66                 void[] vdata = data;
67                 vdata.length = (offset + nbytes + 7) * 2; // allocates as void[] to not set BlkAttr.NO_SCAN
68                 data = cast(ubyte[]) vdata;
69             }
70         }
71 
72     /**********************************
73      * put enables OutBuffer to be used as an OutputRange.
74      */
75     alias put = write;
76 
77     /*************************************
78      * Append data to the internal buffer.
79      */
80 
write(const (ubyte)[]bytes)81     void write(const(ubyte)[] bytes)
82         {
83             reserve(bytes.length);
84             data[offset .. offset + bytes.length] = bytes[];
85             offset += bytes.length;
86         }
87 
write(in wchar[]chars)88     void write(in wchar[] chars) @trusted
89         {
90         write(cast(ubyte[]) chars);
91         }
92 
write(const (dchar)[]chars)93     void write(const(dchar)[] chars) @trusted
94         {
95         write(cast(ubyte[]) chars);
96         }
97 
write(ubyte b)98     void write(ubyte b)         /// ditto
99         {
100             reserve(ubyte.sizeof);
101             this.data[offset] = b;
102             offset += ubyte.sizeof;
103         }
104 
write(byte b)105     void write(byte b) { write(cast(ubyte) b); }         /// ditto
write(char c)106     void write(char c) { write(cast(ubyte) c); }         /// ditto
write(dchar c)107     void write(dchar c) { write(cast(uint) c); }         /// ditto
108 
write(ushort w)109     void write(ushort w) @trusted                /// ditto
110     {
111         reserve(ushort.sizeof);
112         *cast(ushort *)&data[offset] = w;
113         offset += ushort.sizeof;
114     }
115 
write(short s)116     void write(short s) { write(cast(ushort) s); }               /// ditto
117 
write(wchar c)118     void write(wchar c) @trusted        /// ditto
119     {
120         reserve(wchar.sizeof);
121         *cast(wchar *)&data[offset] = c;
122         offset += wchar.sizeof;
123     }
124 
write(uint w)125     void write(uint w) @trusted         /// ditto
126     {
127         reserve(uint.sizeof);
128         *cast(uint *)&data[offset] = w;
129         offset += uint.sizeof;
130     }
131 
write(int i)132     void write(int i) { write(cast(uint) i); }           /// ditto
133 
write(ulong l)134     void write(ulong l) @trusted         /// ditto
135     {
136         reserve(ulong.sizeof);
137         *cast(ulong *)&data[offset] = l;
138         offset += ulong.sizeof;
139     }
140 
write(long l)141     void write(long l) { write(cast(ulong) l); }         /// ditto
142 
write(float f)143     void write(float f) @trusted         /// ditto
144     {
145         reserve(float.sizeof);
146         *cast(float *)&data[offset] = f;
147         offset += float.sizeof;
148     }
149 
write(double f)150     void write(double f) @trusted               /// ditto
151     {
152         reserve(double.sizeof);
153         *cast(double *)&data[offset] = f;
154         offset += double.sizeof;
155     }
156 
write(real f)157     void write(real f) @trusted         /// ditto
158     {
159         reserve(real.sizeof);
160         *cast(real *)&data[offset] = f;
161         offset += real.sizeof;
162     }
163 
write(in char[]s)164     void write(in char[] s) @trusted             /// ditto
165     {
166         write(cast(ubyte[]) s);
167     }
168 
write(OutBuffer buf)169     void write(OutBuffer buf)           /// ditto
170     {
171         write(buf.toBytes());
172     }
173 
174     /****************************************
175      * Append nbytes of 0 to the internal buffer.
176      */
177 
fill0(size_t nbytes)178     void fill0(size_t nbytes)
179     {
180         reserve(nbytes);
181         data[offset .. offset + nbytes] = 0;
182         offset += nbytes;
183     }
184 
185     /**********************************
186      * 0-fill to align on power of 2 boundary.
187      */
188 
alignSize(size_t alignsize)189     void alignSize(size_t alignsize)
190     in
191     {
192         assert(alignsize && (alignsize & (alignsize - 1)) == 0);
193     }
194     out
195     {
196         assert((offset & (alignsize - 1)) == 0);
197     }
198     body
199     {
200         auto nbytes = offset & (alignsize - 1);
201         if (nbytes)
202             fill0(alignsize - nbytes);
203     }
204 
205     /// Clear the data in the buffer
clear()206     void clear()
207     {
208         offset = 0;
209     }
210 
211     /****************************************
212      * Optimize common special case alignSize(2)
213      */
214 
align2()215     void align2()
216     {
217         if (offset & 1)
218             write(cast(byte) 0);
219     }
220 
221     /****************************************
222      * Optimize common special case alignSize(4)
223      */
224 
align4()225     void align4()
226     {
227         if (offset & 3)
228         {   auto nbytes = (4 - offset) & 3;
229             fill0(nbytes);
230         }
231     }
232 
233     /**************************************
234      * Convert internal buffer to array of chars.
235      */
236 
toString()237     override string toString() const
238     {
239         //printf("OutBuffer.toString()\n");
240         return cast(string) data[0 .. offset].idup;
241     }
242   }
243 
244     /*****************************************
245      * Append output of C's vprintf() to internal buffer.
246      */
247 
vprintf(string format,va_list args)248     void vprintf(string format, va_list args) @trusted nothrow
249     {
250         import core.stdc.stdio : vsnprintf;
251         import core.stdc.stdlib : alloca;
252         import std.string : toStringz;
253 
254         version (unittest)
255             char[3] buffer = void;      // trigger reallocation
256         else
257             char[128] buffer = void;
258         int count;
259 
260         // Can't use `tempCString()` here as it will result in compilation error:
261         // "cannot mix core.std.stdlib.alloca() and exception handling".
262         auto f = toStringz(format);
263         auto p = buffer.ptr;
264         auto psize = buffer.length;
265         for (;;)
266         {
267             va_list args2;
268             va_copy(args2, args);
269             count = vsnprintf(p, psize, f, args2);
270             va_end(args2);
271             if (count == -1)
272             {
273                 if (psize > psize.max / 2) assert(0); // overflow check
274                 psize *= 2;
275             }
276             else if (count >= psize)
277             {
278                 if (count == count.max) assert(0); // overflow check
279                 psize = count + 1;
280             }
281             else
282                 break;
283 
284             p = cast(char *) alloca(psize); // buffer too small, try again with larger size
285         }
286         write(cast(ubyte[]) p[0 .. count]);
287     }
288 
289     /*****************************************
290      * Append output of C's printf() to internal buffer.
291      */
292 
printf(string format,...)293     void printf(string format, ...) @trusted
294     {
295         va_list ap;
296         va_start(ap, format);
297         vprintf(format, ap);
298         va_end(ap);
299     }
300 
301     /**
302      * Formats and writes its arguments in text format to the OutBuffer.
303      *
304      * Params:
305      *  fmt = format string as described in $(REF formattedWrite, std,format)
306      *  args = arguments to be formatted
307      *
308      * See_Also:
309      *  $(REF _writef, std,stdio);
310      *  $(REF formattedWrite, std,format);
311      */
writef(Char,A...)312     void writef(Char, A...)(in Char[] fmt, A args)
313     {
314         import std.format : formattedWrite;
315         formattedWrite(this, fmt, args);
316     }
317 
318     ///
319     @safe unittest
320     {
321         OutBuffer b = new OutBuffer();
322         b.writef("a%sb", 16);
323         assert(b.toString() == "a16b");
324     }
325 
326     /**
327      * Formats and writes its arguments in text format to the OutBuffer,
328      * followed by a newline.
329      *
330      * Params:
331      *  fmt = format string as described in $(REF formattedWrite, std,format)
332      *  args = arguments to be formatted
333      *
334      * See_Also:
335      *  $(REF _writefln, std,stdio);
336      *  $(REF formattedWrite, std,format);
337      */
writefln(Char,A...)338     void writefln(Char, A...)(in Char[] fmt, A args)
339     {
340         import std.format : formattedWrite;
341         formattedWrite(this, fmt, args);
342         put('\n');
343     }
344 
345     ///
346     @safe unittest
347     {
348         OutBuffer b = new OutBuffer();
349         b.writefln("a%sb", 16);
350         assert(b.toString() == "a16b\n");
351     }
352 
353     /*****************************************
354      * At offset index into buffer, create nbytes of space by shifting upwards
355      * all data past index.
356      */
357 
spread(size_t index,size_t nbytes)358     void spread(size_t index, size_t nbytes) pure nothrow @safe
359         in
360         {
361             assert(index <= offset);
362         }
363         body
364         {
365             reserve(nbytes);
366 
367             // This is an overlapping copy - should use memmove()
368             for (size_t i = offset; i > index; )
369             {
370                 --i;
371                 data[i + nbytes] = data[i];
372             }
373             offset += nbytes;
374         }
375 }
376 
377 ///
378 @safe unittest
379 {
380     import std.string : cmp;
381 
382     OutBuffer buf = new OutBuffer();
383 
384     assert(buf.offset == 0);
385     buf.write("hello");
386     buf.write(cast(byte) 0x20);
387     buf.write("world");
388     buf.printf(" %d", 62665);
389     assert(cmp(buf.toString(), "hello world 62665") == 0);
390 
391     buf.clear();
392     assert(cmp(buf.toString(), "") == 0);
393     buf.write("New data");
394     assert(cmp(buf.toString(),"New data") == 0);
395 }
396 
397 @safe unittest
398 {
399     import std.range;
400     static assert(isOutputRange!(OutBuffer, char));
401 
402     import std.algorithm;
403   {
404     OutBuffer buf = new OutBuffer();
405     "hello".copy(buf);
406     assert(buf.toBytes() == "hello");
407   }
408   {
409     OutBuffer buf = new OutBuffer();
410     "hello"w.copy(buf);
411     version (LittleEndian)
412         assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00");
413     version (BigEndian)
414         assert(buf.toBytes() == "\x00h\x00e\x00l\x00l\x00o");
415   }
416   {
417     OutBuffer buf = new OutBuffer();
418     "hello"d.copy(buf);
419     version (LittleEndian)
420         assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00");
421     version (BigEndian)
422         assert(buf.toBytes() == "\x00\x00\x00h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o");
423   }
424 }
425