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