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