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