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