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 }