1 module d2d.core.bytestream;
2 
3 public import std.system : Endian;
4 public import std.bitmanip;
5 import std.traits;
6 
7 /// Struct for wrapping a ubyte[] with read functions and endianness.
8 struct ByteStreamImpl (Endian endianness)
9 {
10 	/// Contains the data.
11 	ubyte[] stream;
12 	alias stream this;
13 
14 	/// Sets the stream to data.
15 	this(ubyte[] data)
16 	{
17 		stream = data;
18 	}
19 
20 	/// Sets the stream to data.
21 	void opAssign(ubyte[] data)
22 	{
23 		stream = data;
24 	}
25 
26 	/// Advances the stream by `amount` bytes.
27 	void skip(size_t amount)
28 	{
29 		stream = stream[amount .. $];
30 	}
31 
32 	/// Returns the first `T.sizeof` bytes from the stream and advances the stream.
33 	T read(T = ubyte)()
34 	{
35 		return stream.read!(T, endianness)();
36 	}
37 
38 	/// Returns a `T[]` from the stream and advances the stream.
39 	T[] read(T = ubyte)(size_t length)
40 	{
41 		T[] data = new T[length];
42 		for (size_t i = 0; i < length; i++)
43 			data[i] = read!(T)();
44 		return data;
45 	}
46 
47 	/// Reads until value is `value` and returns everything read excluding `value`. Advances after found `value`. Returns `null` if `value` was not found.
48 	T readTo(T)(ubyte value)
49 	{
50 		// Check for ForeachType!T.sizeof == 1 maybe?
51 		static assert(isArray!T, "T must be an Array!");
52 		for (size_t i = 0; i < stream.length; i++)
53 		{
54 			if (stream[i] == value)
55 			{
56 				scope (exit) stream = stream[i + 1 .. $];
57 				return cast(T) stream[0 .. i];
58 			}
59 		}
60 		return cast(T) null;
61 	}
62 
63 	/// Reads until value is `value` and returns everything read including `value`. Advances after found `value`. Returns `null` if `value` was not found.
64 	T readToIncluding(T)(ubyte value)
65 	{
66 		// Check for ForeachType!T.sizeof == 1 maybe?
67 		static assert(isArray!T, "T must be an Array!");
68 		for (size_t i = 0; i < stream.length; i++)
69 		{
70 			if (stream[i] == value)
71 			{
72 				scope (exit) stream = stream[i + 1 .. $];
73 				return cast(T) stream[0 .. i + 1];
74 			}
75 		}
76 		return cast(T) null;
77 	}
78 
79 	/// Returns `T.sizeof` bytes from the stream at position `index` without advancing the stream.
80 	T peek(T = ubyte)(size_t index = 0)
81 	{
82 		return stream.peek!(T, endianness)(index);
83 	}
84 
85 	/// Returns a `T[]` from the stream.
86 	T[] peek(T = ubyte)(size_t index, size_t length)
87 	{
88 		T[] data = new T[length];
89 		for (size_t i = 0; i < length; i++)
90 			data[i] = peek!(T)(index + i * T.sizeof);
91 		return data;
92 	}
93 
94 	/// Writes `data` to the stream at position `index`.
95 	void write(T)(T data, size_t index)
96 	{
97 		static if (isArray!(T))
98 		{
99 			for (size_t i = 0; i < data.length; i++)
100 				write(data[i], index + i * typeof(data[0]).sizeof);
101 		}
102 		else
103 		{
104 			stream.write!(T, endianness)(data, index);
105 		}
106 	}
107 
108 	/// Appends `data` to the stream.
109 	void append(T)(T data)
110 	{
111 		static if (isArray!(T))
112 		{
113 			for (size_t i = 0; i < data.length; i++)
114 				append(data[i]);
115 		}
116 		else
117 		{
118 			stream.length += T.sizeof;
119 			write(data, stream.length - T.sizeof);
120 		}
121 	}
122 }
123 
124 alias ByteStream = ByteStreamImpl!(Endian.bigEndian);
125 alias BigByteStream = ByteStream;
126 alias LittleByteStream = ByteStreamImpl!(Endian.littleEndian);
127 
128 ///
129 unittest
130 {
131 	ByteStream stream = cast(ubyte[])[0x01, 0x05, 0x16, 0x09, 0x2C, 0xFF, 0x08];
132 
133 	assert(stream.peek!uint () == 0x01_05_16_09);
134 
135 	stream.append!(ushort[])([0x4A_2B, 0x59_12]);
136 	assert(stream.read!ulong () == 0x01_05_16_09_2C_FF_08_4AUL);
137 	assert(stream == [0x2B, 0x59, 0x12]);
138 
139 	stream.write!ubyte (0x44, 1);
140 	assert(stream == [0x2B, 0x44, 0x12]);
141 
142 	stream.write!(ubyte[])([0x01, 0x02, 0x03], 0);
143 	assert(stream == [0x01, 0x02, 0x03]);
144 }
145 
146 ///
147 unittest
148 {
149 	ByteStream stream = (cast(ubyte[]) "abc\0") ~cast(ubyte[])[0x44];
150 	assert(stream.readTo!string(cast(ubyte) '\0') == "abc");
151 	assert(stream == [0x44]);
152 }
153 
154 ///
155 unittest
156 {
157 	ByteStream stream = (cast(ubyte[]) "abc\0") ~cast(ubyte[])[0x44];
158 	assert(stream.readToIncluding!string(cast(ubyte) '\0') == "abc\0");
159 	assert(stream == [0x44]);
160 }
161 
162 unittest
163 {
164 	ByteStream stream = cast(ubyte[])[0x01, 0x05, 0x16, 0x09, 0x2C, 0xFF, 0x08];
165 
166 	assert(stream.peek!uint () == 0x01_05_16_09);
167 	assert(stream.peek!ushort () == 0x01_05);
168 	assert(stream.peek!ubyte () == 0x01);
169 
170 	assert(stream.peek!uint (2) == 0x16_09_2C_FF);
171 	assert(stream.peek!ushort (2) == 0x16_09);
172 	assert(stream.peek!ubyte (2) == 0x16);
173 
174 	assert(stream.peek!ushort (1, 2) == [0x05_16, 0x09_2C]);
175 
176 	stream ~= 0x4A;
177 
178 	assert(stream.peek!uint (0, 2) == [0x01_05_16_09, 0x2C_FF_08_4A]);
179 }
180 
181 unittest
182 {
183 	ByteStream stream = cast(ubyte[])[0x01, 0x05];
184 
185 	stream.append!ushort (0x4A_2B);
186 	assert(stream == [0x01, 0x05, 0x4A, 0x2B]);
187 
188 	stream.append!(ubyte[])(cast(ubyte[])[0x16, 0x09, 0x2C, 0xFF, 0x08]);
189 	assert(stream == [0x01, 0x05, 0x4A, 0x2B, 0x16, 0x09, 0x2C, 0xFF, 0x08]);
190 }
191 
192 unittest
193 {
194 	ByteStream stream = cast(ubyte[])[0x01, 0x05, 0x16, 0x09, 0x2C, 0xFF, 0x08];
195 
196 	stream.write!ubyte (0x06, 1);
197 	assert(stream == [0x01, 0x06, 0x16, 0x09, 0x2C, 0xFF, 0x08]);
198 
199 	stream.write!(ushort[])(cast(ushort[])[0x07_4A, 0x2B_EA], 1);
200 	assert(stream == [0x01, 0x07, 0x4A, 0x2B, 0xEA, 0xFF, 0x08]);
201 }
202 
203 unittest
204 {
205 	ByteStream stream = cast(ubyte[])[0x01, 0x05, 0x16, 0x09, 0x2C, 0xFF, 0x08];
206 
207 	assert(stream.read() == 0x01);
208 	assert(stream.read!ushort () == 0x05_16);
209 	assert(stream.read!uint () == 0x09_2C_FF_08);
210 	assert(stream.length == 0);
211 }