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