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 }