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 
invariant()35     invariant()
36     {
37         assert(offset <= data.length);
38     }
39 
40   pure nothrow @safe
41   {
42     /*********************************
43      * Convert to array of bytes.
44      */
inout(ubyte)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      */
reserve(size_t nbytes)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 
write(scope const (ubyte)[]bytes)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 
write(scope const (wchar)[]chars)89     void write(scope const(wchar)[] chars) @trusted
90         {
91         write(cast(ubyte[]) chars);
92         }
93 
write(scope const (dchar)[]chars)94     void write(scope const(dchar)[] chars) @trusted
95         {
96         write(cast(ubyte[]) chars);
97         }
98 
write(ubyte b)99     void write(ubyte b)         /// ditto
100         {
101             reserve(ubyte.sizeof);
102             this.data[offset] = b;
103             offset += ubyte.sizeof;
104         }
105 
write(byte b)106     void write(byte b) { write(cast(ubyte) b); }         /// ditto
write(char c)107     void write(char c) { write(cast(ubyte) c); }         /// ditto
write(dchar c)108     void write(dchar c) { write(cast(uint) c); }         /// ditto
109 
write(ushort w)110     void write(ushort w) @trusted                /// ditto
111     {
112         reserve(ushort.sizeof);
113         *cast(ushort *)&data[offset] = w;
114         offset += ushort.sizeof;
115     }
116 
write(short s)117     void write(short s) { write(cast(ushort) s); }               /// ditto
118 
write(wchar c)119     void write(wchar c) @trusted        /// ditto
120     {
121         reserve(wchar.sizeof);
122         *cast(wchar *)&data[offset] = c;
123         offset += wchar.sizeof;
124     }
125 
write(uint w)126     void write(uint w) @trusted         /// ditto
127     {
128         reserve(uint.sizeof);
129         *cast(uint *)&data[offset] = w;
130         offset += uint.sizeof;
131     }
132 
write(int i)133     void write(int i) { write(cast(uint) i); }           /// ditto
134 
write(ulong l)135     void write(ulong l) @trusted         /// ditto
136     {
137         reserve(ulong.sizeof);
138         *cast(ulong *)&data[offset] = l;
139         offset += ulong.sizeof;
140     }
141 
write(long l)142     void write(long l) { write(cast(ulong) l); }         /// ditto
143 
write(float f)144     void write(float f) @trusted         /// ditto
145     {
146         reserve(float.sizeof);
147         *cast(float *)&data[offset] = f;
148         offset += float.sizeof;
149     }
150 
write(double f)151     void write(double f) @trusted               /// ditto
152     {
153         reserve(double.sizeof);
154         *cast(double *)&data[offset] = f;
155         offset += double.sizeof;
156     }
157 
write(real f)158     void write(real f) @trusted         /// ditto
159     {
160         reserve(real.sizeof);
161         *cast(real *)&data[offset] = f;
162         offset += real.sizeof;
163     }
164 
write(scope const (char)[]s)165     void write(scope const(char)[] s) @trusted             /// ditto
166     {
167         write(cast(ubyte[]) s);
168     }
169 
write(scope const OutBuffer buf)170     void write(scope const OutBuffer buf)           /// ditto
171     {
172         write(buf.toBytes());
173     }
174 
175     /****************************************
176      * Append nbytes of 0 to the internal buffer.
177      */
178 
fill0(size_t nbytes)179     void fill0(size_t nbytes)
180     {
181         reserve(nbytes);
182         data[offset .. offset + nbytes] = 0;
183         offset += nbytes;
184     }
185 
186     /**********************************
187      * 0-fill to align on power of 2 boundary.
188      */
189 
alignSize(size_t alignsize)190     void alignSize(size_t alignsize)
191     in
192     {
193         assert(alignsize && (alignsize & (alignsize - 1)) == 0);
194     }
195     out
196     {
197         assert((offset & (alignsize - 1)) == 0);
198     }
199     do
200     {
201         auto nbytes = offset & (alignsize - 1);
202         if (nbytes)
203             fill0(alignsize - nbytes);
204     }
205 
206     /// Clear the data in the buffer
clear()207     void clear()
208     {
209         offset = 0;
210     }
211 
212     /****************************************
213      * Optimize common special case alignSize(2)
214      */
215 
align2()216     void align2()
217     {
218         if (offset & 1)
219             write(cast(byte) 0);
220     }
221 
222     /****************************************
223      * Optimize common special case alignSize(4)
224      */
225 
align4()226     void align4()
227     {
228         if (offset & 3)
229         {   auto nbytes = (4 - offset) & 3;
230             fill0(nbytes);
231         }
232     }
233 
234     /**************************************
235      * Convert internal buffer to array of chars.
236      */
237 
toString()238     override string toString() const
239     {
240         //printf("OutBuffer.toString()\n");
241         return cast(string) data[0 .. offset].idup;
242     }
243   }
244 
245     /*****************************************
246      * Append output of C's vprintf() to internal buffer.
247      */
248 
vprintf(scope string format,va_list args)249     void vprintf(scope string format, va_list args) @trusted nothrow
250     {
251         import core.stdc.stdio : vsnprintf;
252         import core.stdc.stdlib : alloca;
253         import std.string : toStringz;
254 
255         version (StdUnittest)
256             char[3] buffer = void;      // trigger reallocation
257         else
258             char[128] buffer = void;
259         int count;
260 
261         // Can't use `tempCString()` here as it will result in compilation error:
262         // "cannot mix core.std.stdlib.alloca() and exception handling".
263         auto f = toStringz(format);
264         auto p = buffer.ptr;
265         auto psize = buffer.length;
266         for (;;)
267         {
268             va_list args2;
269             va_copy(args2, args);
270             count = vsnprintf(p, psize, f, args2);
271             va_end(args2);
272             if (count == -1)
273             {
274                 if (psize > psize.max / 2) assert(0); // overflow check
275                 psize *= 2;
276             }
277             else if (count >= psize)
278             {
279                 if (count == count.max) assert(0); // overflow check
280                 psize = count + 1;
281             }
282             else
283                 break;
284 
285             p = cast(char *) alloca(psize); // buffer too small, try again with larger size
286         }
287         write(cast(ubyte[]) p[0 .. count]);
288     }
289 
290     /*****************************************
291      * Append output of C's printf() to internal buffer.
292      */
293 
printf(scope string format,...)294     void printf(scope string format, ...) @trusted
295     {
296         va_list ap;
297         va_start(ap, format);
298         vprintf(format, ap);
299         va_end(ap);
300     }
301 
302     /**
303      * Formats and writes its arguments in text format to the OutBuffer.
304      *
305      * Params:
306      *  fmt = format string as described in $(REF formattedWrite, std,format)
307      *  args = arguments to be formatted
308      *
309      * See_Also:
310      *  $(REF _writef, std,stdio);
311      *  $(REF formattedWrite, std,format);
312      */
writef(Char,A...)313     void writef(Char, A...)(scope const(Char)[] fmt, A args)
314     {
315         import std.format.write : formattedWrite;
316         formattedWrite(this, fmt, args);
317     }
318 
319     ///
320     @safe unittest
321     {
322         OutBuffer b = new OutBuffer();
323         b.writef("a%sb", 16);
324         assert(b.toString() == "a16b");
325     }
326 
327     /// ditto
328     void writef(alias fmt, A...)(A args)
329     if (isSomeString!(typeof(fmt)))
330     {
331         import std.format : checkFormatException;
332 
333         alias e = checkFormatException!(fmt, A);
334         static assert(!e, e);
335         return this.writef(fmt, args);
336     }
337 
338     ///
339     @safe unittest
340     {
341         OutBuffer b = new OutBuffer();
342         b.writef!"a%sb"(16);
343         assert(b.toString() == "a16b");
344     }
345 
346     /**
347      * Formats and writes its arguments in text format to the OutBuffer,
348      * followed by a newline.
349      *
350      * Params:
351      *  fmt = format string as described in $(REF formattedWrite, std,format)
352      *  args = arguments to be formatted
353      *
354      * See_Also:
355      *  $(REF _writefln, std,stdio);
356      *  $(REF formattedWrite, std,format);
357      */
writefln(Char,A...)358     void writefln(Char, A...)(scope const(Char)[] fmt, A args)
359     {
360         import std.format.write : formattedWrite;
361         formattedWrite(this, fmt, args);
362         put('\n');
363     }
364 
365     ///
366     @safe unittest
367     {
368         OutBuffer b = new OutBuffer();
369         b.writefln("a%sb", 16);
370         assert(b.toString() == "a16b\n");
371     }
372 
373     /// ditto
374     void writefln(alias fmt, A...)(A args)
375     if (isSomeString!(typeof(fmt)))
376     {
377         import std.format : checkFormatException;
378 
379         alias e = checkFormatException!(fmt, A);
380         static assert(!e, e);
381         return this.writefln(fmt, args);
382     }
383 
384     ///
385     @safe unittest
386     {
387         OutBuffer b = new OutBuffer();
388         b.writefln!"a%sb"(16);
389         assert(b.toString() == "a16b\n");
390     }
391 
392     /*****************************************
393      * At offset index into buffer, create nbytes of space by shifting upwards
394      * all data past index.
395      */
396 
spread(size_t index,size_t nbytes)397     void spread(size_t index, size_t nbytes) pure nothrow @safe
398         in
399         {
400             assert(index <= offset);
401         }
402         do
403         {
404             reserve(nbytes);
405 
406             // This is an overlapping copy - should use memmove()
407             for (size_t i = offset; i > index; )
408             {
409                 --i;
410                 data[i + nbytes] = data[i];
411             }
412             offset += nbytes;
413         }
414 }
415 
416 ///
417 @safe unittest
418 {
419     import std.string : cmp;
420 
421     OutBuffer buf = new OutBuffer();
422 
423     assert(buf.offset == 0);
424     buf.write("hello");
425     buf.write(cast(byte) 0x20);
426     buf.write("world");
427     buf.printf(" %d", 62665);
428     assert(cmp(buf.toString(), "hello world 62665") == 0);
429 
430     buf.clear();
431     assert(cmp(buf.toString(), "") == 0);
432     buf.write("New data");
433     assert(cmp(buf.toString(),"New data") == 0);
434 }
435 
436 @safe unittest
437 {
438     import std.range;
439     static assert(isOutputRange!(OutBuffer, char));
440 
441     import std.algorithm;
442   {
443     OutBuffer buf = new OutBuffer();
444     "hello".copy(buf);
445     assert(buf.toBytes() == "hello");
446   }
447   {
448     OutBuffer buf = new OutBuffer();
449     "hello"w.copy(buf);
450     version (LittleEndian)
451         assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00");
452     version (BigEndian)
453         assert(buf.toBytes() == "\x00h\x00e\x00l\x00l\x00o");
454   }
455   {
456     OutBuffer buf = new OutBuffer();
457     "hello"d.copy(buf);
458     version (LittleEndian)
459         assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00");
460     version (BigEndian)
461         assert(buf.toBytes() == "\x00\x00\x00h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o");
462   }
463 }
464