1 module xbuffer.buffer; 2 3 import std.bitmanip : swapEndian; 4 import std.string : toUpper; 5 import std.system : Endian, endian; 6 import std.traits : isArray, isBoolean, isIntegral, isFloatingPoint, isSomeChar, Unqual; 7 8 import xbuffer.memory : xmalloc, xrealloc, xfree; 9 import xbuffer.varint : isVar, Var; 10 11 //TODO remove 12 import std.stdio : writeln; 13 14 alias ForeachType(T) = typeof(T.init[0]); 15 16 enum canSwapEndianness(T) = isBoolean!T || isIntegral!T || isFloatingPoint!T || isSomeChar!T || (is(T == struct) && canSwapEndiannessImpl!T); 17 18 private bool canSwapEndiannessImpl(T)() { 19 static if(is(T == struct)) { 20 import std.traits : Fields; 21 bool ret = true; 22 foreach(field ; Fields!T) { 23 if(!canSwapEndianness!field) ret = false; 24 } 25 return ret; 26 } else { 27 return false; 28 } 29 } 30 31 unittest { 32 33 static assert(canSwapEndianness!byte); 34 static assert(canSwapEndianness!int); 35 static assert(canSwapEndianness!double); 36 static assert(canSwapEndianness!char); 37 static assert(canSwapEndianness!dchar); 38 39 } 40 41 unittest { 42 43 static struct A {} 44 45 static struct B { int a, b; } 46 47 static class C {} 48 49 static struct D { int a; B b; } 50 51 static struct E { B b; C c; } 52 53 static struct F { void a(){} float b; } 54 55 static struct G { @property Object a(){ return null; } } 56 57 static struct H { ubyte[] a; } 58 59 static struct I { int* a; } 60 61 static assert(canSwapEndianness!A); 62 static assert(canSwapEndianness!B); 63 static assert(!canSwapEndianness!C); 64 static assert(canSwapEndianness!D); 65 static assert(!canSwapEndianness!E); 66 static assert(canSwapEndianness!F); 67 static assert(canSwapEndianness!G); 68 static assert(!canSwapEndianness!H); 69 static assert(!canSwapEndianness!I); 70 71 } 72 73 private union EndianSwapper(T) if(canSwapEndianness!T) { 74 75 enum builtInSwap = T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8; 76 77 T value; 78 void[T.sizeof] data; 79 ubyte[T.sizeof] bytes; 80 81 this(T value) { 82 this.value = value; 83 } 84 85 this(void[] data) { 86 this.data = data; 87 } 88 89 static if(T.sizeof == 2) ushort _swap; 90 else static if(T.sizeof == 4) uint _swap; 91 else static if(T.sizeof == 8) ulong _swap; 92 93 void swap() { 94 static if(builtInSwap) _swap = swapEndian(_swap); 95 else static if(T.sizeof > 1) { 96 import std.algorithm.mutation : swap; 97 foreach(i ; 0..T.sizeof>>1) { 98 swap(bytes[i], bytes[T.sizeof-i-1]); 99 } 100 } 101 } 102 103 } 104 105 unittest { 106 107 static struct Test { 108 109 int a, b, c; 110 111 } 112 113 static assert(Test.sizeof == 12); 114 115 EndianSwapper!Test swapper; 116 swapper.value = Test(1, 2, 3); 117 118 version(BigEndian) assert(swapper.bytes == [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 119 version(LittleEndian) assert(swapper.bytes == [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 120 121 swapper.swap(); 122 123 version(BigEndian) assert(swapper.bytes == [3, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0]); 124 version(LittleEndian) assert(swapper.bytes == [0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1]); 125 126 assert(swapper.value == Test(3 << 24, 2 << 24, 1 << 24)); 127 128 } 129 130 /** 131 * Exception thrown when the buffer cannot read the requested 132 * data. 133 */ 134 class BufferOverflowException : Exception { 135 136 this(string file=__FILE__, size_t line=__LINE__) pure nothrow @safe @nogc { 137 super("The buffer's index has exceeded its length", file, line); 138 } 139 140 } 141 142 static if(__traits(compiles, () @nogc { throw new Exception(""); })) version = DIP1008; 143 144 /** 145 * Buffer for writing and reading binary data. 146 */ 147 class Buffer { 148 149 private immutable size_t chunk; 150 151 private void[] _data; 152 private size_t _rindex, _windex; 153 154 /** 155 * Creates a buffer specifying the chunk size. 156 * This should be the default constructor for re-used buffers and 157 * input buffers. 158 */ 159 this(size_t chunk) pure nothrow @trusted @nogc { 160 this.chunk = chunk == 0 ? 1 : chunk; 161 //_data = malloc(this.chunk); 162 _data = xrealloc(_data.ptr, this.chunk); 163 } 164 165 /// 166 pure nothrow @safe unittest { 167 168 Buffer buffer = new Buffer(8); 169 170 // 8 bytes allocated by the constructor 171 assert(buffer.capacity == 8); 172 173 // writing 4 bytes does not alter the capacity 174 buffer.write(0); 175 assert(buffer.capacity == 8); 176 177 // writing 8 bytes requires a new allocation (because 4 + 8 is 178 // higher than the current capacity of 8). 179 // The new capacity is rounded up to the nearest multiple of the chunk size. 180 buffer.write(0L); 181 assert(buffer.capacity == 16); 182 183 } 184 185 /** 186 * Creates a buffer from an array of data. 187 * The chunk size is set to the size of the array. 188 */ 189 this(T)(in T[] data...) pure nothrow @trusted @nogc if(canSwapEndianness!T || is(T == void)) { 190 this(data.length * T.sizeof); 191 _windex = _data.length; 192 _data[0..$] = cast(void[])data; 193 } 194 195 /// 196 pure nothrow @safe unittest { 197 198 Buffer buffer = new Buffer(cast(ubyte[])[1, 2, 3, 4]); 199 assert(buffer.rindex == 0); 200 assert(buffer.windex == 4); 201 202 buffer = new Buffer([1, 2]); 203 assert(buffer.rindex == 0); 204 assert(buffer.windex == 8); 205 206 } 207 208 private void resize(size_t requiredSize) pure nothrow @trusted @nogc { 209 immutable rem = requiredSize / chunk; 210 immutable size = (requiredSize + chunk - 1) / chunk * chunk; 211 _data = xrealloc(_data.ptr, size); 212 } 213 214 @property size_t rindex() pure nothrow @safe @nogc { 215 return _rindex; 216 } 217 218 @property size_t windex() pure nothrow @safe @nogc { 219 return _windex; 220 } 221 222 @property T[] data(T=void)() pure nothrow @trusted @nogc if((canSwapEndianness!T || is(T == void)) && T.sizeof == 1) { 223 return cast(T[])_data[_rindex.._windex]; 224 } 225 226 @property T[] data(T)() pure nothrow @nogc if(canSwapEndianness!T && T.sizeof != 1) { 227 return cast(T[])_data[_rindex.._windex]; 228 } 229 230 /** 231 * Sets new data and resets the index. 232 */ 233 @property auto data(T)(in T[] data) pure nothrow @trusted @nogc { 234 _rindex = 0; 235 _windex = data.length * T.sizeof; 236 if(_windex > _data.length) this.resize(_windex); 237 _data[0.._windex] = cast(void[])data; 238 return data; 239 } 240 241 /// 242 pure nothrow @trusted unittest { 243 244 Buffer buffer = new Buffer(2); 245 246 buffer.data = cast(ubyte[])[0, 0, 0, 1]; 247 assert(buffer.rindex == 0); // resetted when setting new data 248 assert(buffer.windex == 4); 249 version(BigEndian) assert(buffer.data!uint == [1]); 250 version(LittleEndian) assert(buffer.data!uint == [1 << 24]); 251 252 buffer.data = "hello"; 253 assert(buffer.rindex == 0); 254 assert(buffer.windex == 5); 255 assert(buffer.data == "hello"); 256 257 } 258 259 /** 260 * Gets the current write/read index of the buffer. 261 * The index can be set to 0 using the `reset` method. 262 */ 263 deprecated("Use rindex instead") @property size_t index() pure nothrow @safe @nogc { 264 return _rindex; 265 } 266 267 /** 268 * Gets the length of the buffer. 269 */ 270 deprecated("Use windex instead") @property size_t length() pure nothrow @safe @nogc { 271 return _windex; 272 } 273 274 /** 275 * Resets the buffer setting the index and its length to 0. 276 */ 277 void reset() pure nothrow @safe @nogc { 278 _rindex = 0; 279 _windex = 0; 280 } 281 282 /** 283 * Gets the size of the data allocated by the buffer. 284 */ 285 @property size_t capacity() pure nothrow @safe @nogc { 286 return _data.length; 287 } 288 289 void back(size_t amount) pure nothrow @safe @nogc { 290 assert(amount <= _rindex); 291 _rindex -= amount; 292 } 293 294 // ---------- 295 // operations 296 // ---------- 297 298 /** 299 * Check whether the data of the buffer is equals to 300 * the given array. 301 */ 302 bool opEquals(T)(in T[] data) pure nothrow @safe @nogc if(T.sizeof == 1) { 303 return this.data!T == data; 304 } 305 306 /// ditto 307 bool opEquals(T)(in T[] data) pure nothrow @nogc if(T.sizeof != 1) { 308 return this.data!T == data; 309 } 310 311 /// 312 pure nothrow @trusted unittest { 313 314 Buffer buffer = new Buffer([1, 2, 3]); 315 316 assert(buffer == [1, 2, 3]); 317 version(BigEndian) assert(buffer == cast(ubyte[])[0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 318 version(LittleEndian) assert(buffer == cast(ubyte[])[1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 319 320 } 321 322 // ----- 323 // write 324 // ----- 325 326 private void need(size_t size) pure nothrow @safe @nogc { 327 size += _windex; 328 if(size > this.capacity) this.resize(size); 329 } 330 331 private void writeDataImpl(in void[] data) pure nothrow @trusted @nogc { 332 immutable start = _windex; 333 _windex += data.length; 334 _data[start.._windex] = data; 335 } 336 337 /** 338 * Writes data to the buffer and expands if it is not big enough. 339 */ 340 void writeData(in void[] data) pure nothrow @safe @nogc { 341 this.need(data.length); 342 this.writeDataImpl(data); 343 } 344 345 /** 346 * Writes data to buffer using the given endianness. 347 */ 348 void write(Endian endianness, T)(T value) pure nothrow @trusted @nogc if(canSwapEndianness!T) { 349 EndianSwapper!T swapper = EndianSwapper!T(value); 350 static if(endianness != endian && T.sizeof > 1) swapper.swap(); 351 this.writeData(swapper.data); 352 } 353 354 /// 355 unittest { 356 357 Buffer buffer = new Buffer(4); 358 buffer.write!(Endian.bigEndian)(4); 359 buffer.write!(Endian.littleEndian)(4); 360 assert(buffer.data!ubyte == [0, 0, 0, 4, 4, 0, 0, 0]); 361 362 } 363 364 /** 365 * Writes data to the buffer using the system's endianness. 366 */ 367 void write(T)(T value) pure nothrow @safe @nogc if(canSwapEndianness!T) { 368 this.write!(endian, T)(value); 369 } 370 371 /// 372 pure nothrow @safe unittest { 373 374 Buffer buffer = new Buffer(5); 375 buffer.write(ubyte(5)); 376 buffer.write(10); 377 version(BigEndian) assert(buffer.data!ubyte == [5, 0, 0, 0, 10]); 378 version(LittleEndian) assert(buffer.data!ubyte == [5, 10, 0, 0, 0]); 379 380 } 381 382 /** 383 * Writes an array using the given endianness. 384 */ 385 void write(Endian endianness, T)(in T value) pure nothrow @trusted @nogc if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 386 static if(endianness == endian || T.sizeof <= 1) { 387 this.writeData(value); 388 } else { 389 this.need(value.length * ForeachType!T.sizeof); 390 foreach(element ; value) { 391 auto swapper = EndianSwapper!(ForeachType!T)(element); 392 swapper.swap(); 393 this.writeDataImpl(swapper.data); 394 } 395 } 396 } 397 398 /// 399 pure nothrow @safe unittest { 400 401 Buffer buffer = new Buffer(8); 402 buffer.write!(Endian.bigEndian)([1, 2, 3]); 403 assert(buffer.capacity == 16); 404 assert(buffer.data!ubyte == [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 405 406 buffer.reset(); 407 buffer.write!(Endian.littleEndian)(cast(short[])[-2, 2]); 408 assert(buffer.data!ubyte == [254, 255, 2, 0]); 409 410 buffer.reset(); 411 buffer.write!(Endian.bigEndian, wstring)("test"w); 412 assert(buffer.data!ubyte == [0, 't', 0, 'e', 0, 's', 0, 't']); 413 414 } 415 416 /** 417 * Writes an array using the system's endianness. 418 */ 419 void write(T)(in T value) pure nothrow @safe @nogc if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 420 this.write!(endian, T)(value); 421 } 422 423 /// 424 pure nothrow @safe unittest { 425 426 Buffer buffer = new Buffer(8); 427 buffer.write(cast(ubyte[])[1, 2, 3, 4]); 428 buffer.write("test"); 429 assert(buffer.data!ubyte == [1, 2, 3, 4, 't', 'e', 's', 't']); 430 buffer.write([1, 2]); 431 version(BigEndian) assert(buffer.data!ubyte == [1, 2, 3, 4, 't', 'e', 's', 't', 0, 0, 0, 1, 0, 0, 0, 2]); 432 version(LittleEndian) assert(buffer.data!ubyte == [1, 2, 3, 4, 't', 'e', 's', 't', 1, 0, 0, 0, 2, 0, 0, 0]); 433 434 } 435 436 /** 437 * Writes a varint. 438 */ 439 void writeVar(T)(T value) pure nothrow @safe @nogc if(isIntegral!T && T.sizeof > 1) { 440 Var!T.encode(this, value); 441 } 442 443 /// ditto 444 void write(T:Var!B, B)(B value) pure nothrow @safe @nogc { 445 this.writeVar!(T.Base)(value); 446 } 447 448 /// 449 pure nothrow @safe unittest { 450 451 import xbuffer.varint; 452 453 Buffer buffer = new Buffer(8); 454 buffer.writeVar(1); 455 buffer.write!varuint(1); 456 assert(buffer.data!ubyte == [2, 1]); 457 458 } 459 460 /** 461 * Writes data at the given index. 462 */ 463 void write(alias E=endian, T)(in T value, size_t index) pure nothrow @trusted @nogc if(is(typeof(E) : Endian) && (canSwapEndianness!T || is(T == void) || isArray!T && (canSwapEndianness!(ForeachType!T) || is(ForeachType!T == void))) || is(E == struct) && isVar!E) { 464 void[] shift = xmalloc(this.data.length - index); 465 shift[0..$] = this.data[index..$]; 466 _windex = _rindex + index; 467 this.write!E(value); 468 this.writeData(shift); 469 xfree(shift.ptr); 470 } 471 472 /// ditto 473 pure @trusted unittest { 474 475 import xbuffer.varint; 476 477 Buffer buffer = new Buffer([1, 2, 3]); 478 buffer.write(0, 0); 479 assert(buffer.data!int == [0, 1, 2, 3]); 480 481 buffer.data = cast(ubyte[])[0, 1, 2, 5]; 482 buffer.write(cast(ubyte[])[3, 4], 3); 483 assert(buffer.data!ubyte == [0, 1, 2, 3, 4, 5]); 484 485 buffer.data = cast(ubyte[])[1, 1, 2]; 486 buffer.read!ubyte(); 487 buffer.write!varuint(118485, 1); 488 assert(buffer.data!ubyte == [1, 213, 157, 7, 2]); 489 490 buffer.data = "hellld"; 491 buffer.write('o', 4); 492 buffer.write(" wor", 5); 493 assert(buffer.data == "hello world"); 494 495 } 496 497 // ---- 498 // read 499 // ---- 500 501 /** 502 * Indicates whether an array of length `size` or the given type 503 * can be read without any exceptions thrown. 504 */ 505 bool canRead(size_t size) pure nothrow @safe @nogc { 506 return _rindex + size <= _windex; 507 } 508 509 /// ditto 510 bool canRead(T)() pure nothrow @safe @nogc if(canSwapEndianness!T) { 511 return this.canRead(T.sizeof); 512 } 513 514 /// 515 unittest { 516 517 import xbuffer.varint; 518 519 Buffer buffer = new Buffer(cast(ubyte[])[128, 200, 3]); 520 assert(buffer.canRead(2)); 521 assert(buffer.canRead(3)); 522 assert(!buffer.canRead(4)); 523 assert(buffer.canRead!byte()); 524 assert(buffer.canRead!short()); 525 assert(!buffer.canRead!int()); 526 527 } 528 529 /** 530 * Reads the amount of data requested. 531 * Throws: BufferOverflowException if there isn't enough data to read. 532 */ 533 void[] readData(size_t size) pure @safe { 534 if(!this.canRead(size)) throw new BufferOverflowException(); 535 _rindex += size; 536 return _data[_rindex-size.._rindex]; 537 } 538 539 /// 540 pure @safe unittest { 541 542 Buffer buffer = new Buffer([1]); 543 assert(buffer.read!int() == 1); 544 try { 545 buffer.read!int(); assert(0); 546 } catch(BufferOverflowException ex) { 547 assert(ex.file == __FILE__); 548 } 549 550 } 551 552 /** 553 * Reads a value with the given endianness. 554 * Throws: BufferOverflowException if there isn't enough data to read. 555 */ 556 T read(Endian endianness, T)() pure @trusted if(canSwapEndianness!T) { 557 EndianSwapper!T swapper = EndianSwapper!T(this.readData(T.sizeof)); 558 static if(endianness != endian) swapper.swap(); 559 return swapper.value; 560 } 561 562 /// 563 pure @safe unittest { 564 565 Buffer buffer = new Buffer(cast(ubyte[])[0, 0, 0, 1, 1, 0]); 566 assert(buffer.read!(Endian.bigEndian, int)() == 1); 567 assert(buffer.read!(Endian.littleEndian, short)() == 1); 568 569 } 570 571 /** 572 * Reads a value using the system's endianness. 573 * Throws: BufferOverflowException if there isn't enough data to read. 574 */ 575 T read(T)() pure @safe if(canSwapEndianness!T) { 576 return this.read!(endian, T)(); 577 } 578 579 /// 580 pure @safe unittest { 581 582 version(BigEndian) Buffer buffer = new Buffer([0, 0, 0, 1]); 583 version(LittleEndian) Buffer buffer = new Buffer([1, 0, 0, 0]); 584 assert(buffer.read!int() == 1); 585 586 } 587 588 /** 589 * Reads an array using the given endianness. 590 * Throws: BufferOverflowException if there isn't enough data to read. 591 */ 592 T read(Endian endianness, T)(size_t length) pure @trusted if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 593 T ret = cast(T)this.readData(length * ForeachType!T.sizeof); 594 static if(endianness != endian && T.sizeof != 1) { 595 foreach(ref element ; ret) { 596 EndianSwapper!(ForeachType!T) swapper = EndianSwapper!(ForeachType!T)(element); 597 swapper.swap(); 598 element = swapper.value; 599 } 600 } 601 return ret; 602 } 603 604 /// 605 pure @safe unittest { 606 607 Buffer buffer = new Buffer(16); 608 609 buffer.write!(Endian.bigEndian)(16); 610 buffer.write!(Endian.bigEndian)(32); 611 buffer.write!(Endian.littleEndian)(32); 612 buffer.write!(Endian.littleEndian)(16); 613 assert(buffer.data!ubyte == [0, 0, 0, 16, 0, 0, 0, 32, 32, 0, 0, 0, 16, 0, 0, 0]); 614 615 assert(buffer.read!(Endian.bigEndian, int[])(2) == [16, 32]); 616 assert(buffer.read!(Endian.littleEndian, int[])(2) == [32, 16]); 617 618 } 619 620 /** 621 * Reads an array using the system's endianness. 622 * Throws: BufferOverflowException if there isn't enough data to read. 623 */ 624 T read(T)(size_t size) pure @trusted if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 625 return this.read!(endian, T)(size); 626 } 627 628 /// 629 pure @safe unittest { 630 631 Buffer buffer = new Buffer("!hello"); 632 assert(buffer.read!(ubyte[])(1) == [33]); 633 assert(buffer.read!string(5) == "hello"); 634 635 buffer.data = [1, 2, 3]; 636 version(BigEndian) assert(buffer.data!ubyte == [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 637 version(LittleEndian) assert(buffer.data!ubyte == [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 638 assert(buffer.read!(int[])(3) == [1, 2, 3]); 639 640 } 641 642 /** 643 * Reads a varint. 644 * Throws: BufferOverflowException if there isn't enough data to read. 645 */ 646 T readVar(T)() pure @safe if(isIntegral!T && T.sizeof > 1) { 647 return Var!T.decode!true(this); 648 } 649 650 /// ditto 651 B read(T:Var!B, B)() pure @safe { 652 return this.readVar!B(); 653 } 654 655 /// 656 pure @safe unittest { 657 658 import xbuffer.varint; 659 660 Buffer buffer = new Buffer(cast(ubyte[])[2, 1]); 661 assert(buffer.readVar!int() == 1); 662 assert(buffer.read!varuint() == 1); 663 664 } 665 666 // ---- 667 // peek 668 // ---- 669 670 /** 671 * Peeks some data (read it but without changing the buffer's index). 672 * Throws: BufferOverflowException if there isn't enough data to read. 673 */ 674 void[] peekData(size_t size) pure { 675 if(!this.canRead(size)) throw new BufferOverflowException(); 676 return _data[_rindex.._rindex+size]; 677 } 678 679 unittest { 680 681 Buffer buffer = new Buffer([1]); 682 assert(buffer.rindex == 0); 683 buffer.peekData(4); 684 assert(buffer.rindex == 0); 685 686 } 687 688 /** 689 * Peeks a value using the given endianness. 690 * Throws: BufferOverflowException if there isn't enough data to read. 691 */ 692 T peek(Endian endianness, T)() pure @trusted if(canSwapEndianness!T) { 693 EndianSwapper!T swapper = EndianSwapper!T(this.peekData(T.sizeof)); 694 static if(endianness != endian) swapper.swap(); 695 return swapper.value; 696 } 697 698 /// 699 pure @safe unittest { 700 701 Buffer buffer = new Buffer(cast(ubyte[])[0, 0, 0, 1]); 702 assert(buffer.peek!(Endian.bigEndian, int)() == 1); 703 assert(buffer.peek!(Endian.littleEndian, int)() == 1 << 24); 704 assert(buffer.peek!(Endian.bigEndian, short)() == 0); 705 706 } 707 708 /** 709 * Peeks some data using the system's endianness. 710 * Throws: BufferOverflowException if there isn't enough data to read. 711 */ 712 T peek(T)() pure @safe if(canSwapEndianness!T) { 713 return this.peek!(endian, T)(); 714 } 715 716 /// 717 pure @safe unittest { 718 719 Buffer buffer = new Buffer([1, 2]); 720 assert(buffer.peek!int() == 1); 721 assert(buffer.rindex == 0); 722 assert(buffer.peek!int() == buffer.read!int()); 723 assert(buffer.rindex == 4); 724 assert(buffer.peek!int() == 2); 725 726 } 727 728 /** 729 * Peeks a varint. 730 * Throws: BufferOverflowException if there isn't enough data to read. 731 */ 732 T peekVar(T)() pure @safe if(isIntegral!T && T.sizeof > 1) { 733 return Var!T.decode!false(this); 734 } 735 736 /// ditto 737 B peek(T:Var!B, B)() pure @safe { 738 return this.peekVar!B(); 739 } 740 741 /// 742 pure @safe unittest { 743 744 import xbuffer.varint; 745 746 Buffer buffer = new Buffer(cast(ubyte[])[2]); 747 assert(buffer.peekVar!int() == 1); 748 assert(buffer.peek!varuint() == 2); 749 750 } 751 752 // ----------- 753 // destruction 754 // ----------- 755 756 void free() pure nothrow @nogc { 757 xfree(_data.ptr); 758 } 759 760 void __xdtor() pure nothrow @nogc { 761 this.free(); 762 } 763 764 ~this() { 765 this.free(); 766 } 767 768 } 769 770 /// 771 unittest { 772 773 import xbuffer.memory : xalloc; 774 775 // a buffer can be garbage collected 776 Buffer gc = new Buffer(16); 777 778 // or manually allocated 779 // alloc is a function provided by the xbuffer.memory module 780 Buffer b = xalloc!Buffer(16); 781 782 // the memory is realsed with free, which is called by the garbage 783 // collector or by the `free` function in the `xbuffer.memory` module 784 xfree(b); 785 786 } 787 788 unittest { 789 790 import xbuffer.memory : xalloc; 791 792 void[] data = xmalloc(923); 793 794 auto buffer = xalloc!Buffer(1024); 795 assert(buffer.windex == 0); 796 797 buffer.writeData(data); 798 assert(buffer.rindex == 0); 799 assert(buffer.windex == 923); 800 assert(buffer.capacity == 1024); 801 802 buffer.writeData(data); 803 assert(buffer.windex == 1846); 804 assert(buffer.capacity == 2048); 805 806 data = xrealloc(data.ptr, 1); 807 808 buffer.data = data; 809 assert(buffer.windex == 1); 810 assert(buffer.capacity == 2048); 811 812 data = xrealloc(data.ptr, 2049); 813 814 buffer.data = data; 815 assert(buffer.windex == 2049); 816 assert(buffer.capacity == 3072); 817 818 xfree(data.ptr); 819 xfree(buffer); 820 821 }