1 module binary.writer;
2 
3 import core.stdc..string;
4 import std.range;
5 import std.traits;
6 import binary.common;
7 
8 
9 /**
10  * Writes binary data.
11  * 
12  * To write data to buffer, use `write` or `put` function.
13  */
14 struct BinaryWriter
15 {
16 	/**
17 	 * Buffer with binary encoded data.
18 	 */
19 	ubyte[] buffer;
20 
21 
22 	/**
23 	 * Byte orded used
24 	 */
25 	ByteOrder byteOrder;
26 
27 	
28 	/**
29 	 * Creates instance of BinaryWriter
30 	 * 
31 	 * Params:
32 	 *  byteOrder = Byte order to use
33 	 */
34 	this(ByteOrder byteOrder)
35 	{
36 		this.byteOrder = byteOrder;
37 	}
38 
39 	
40 	/**
41 	 * Writes `value` to buffer.
42 	 * 
43 	 * For simple types:
44 	 *  Writes binary encoded value to buffer.
45 	 * 
46 	 * For Static arrays:
47 	 *  All array elements are written as-is, without terminator or length indicator.
48 	 * 
49 	 * For Dynamic arrays and strings:
50 	 *  First, an array length is written as 4-byte unsigned integer (regardless if 64 bit or not)
51 	 *  followed by array elements.
52 	 * 
53 	 * For C strings (const char*):
54 	 *  String is written with null terminator.
55 	 * 
56 	 * To write terminated strings use `writeString` instead or pass string wrapped in NullTerminated struct.
57 	 * To write arrays without length use `writeArray` instead.
58 	 * 
59 	 * Params:
60 	 *  value = Value to write
61 	 * 
62 	 * Examples:
63 	 * -----
64 	 * BinaryWriter writer;
65 	 * writer.write("abc");
66 	 * writer.write!byte(10);
67 	 * writeln(writer.buffer); // ['a', 'b', 'c', 10]
68 	 * -----
69 	 */
70 	void write(T)(T value)
71 	{
72 		static if (isStaticArray!T) {
73 			buffer.reserve(T[0].sizeof * value.length);
74 			foreach(ref el; value) {
75 				write!(ElementEncodingType!T)(el);
76 			}
77 		}
78 		else static if (isDynamicArray!T) {
79 			if (value.length > uint.max) {
80 				throw new Exception("Trying to write array with length bigger than uint.max");
81 			}
82 
83 			write(cast(uint)value.length);
84 			writeArray(value);
85 		}
86 		else static if (is(T == immutable(char)*)) {
87 			size_t len = strlen(value) + 1;
88 			buffer.reserve(len);
89 			for (size_t i; i<len; i++) {
90 				buffer ~= value[i];
91 			}
92 		}
93 		else {
94 			ubyte[] data = encodeBinary!T(value, byteOrder);
95 			buffer ~= data;
96 		}
97 	}
98 
99 	/**
100 	 * Writes `array` to buffer.
101 	 * 
102 	 * This function writes `array` elements to buffer without terminator or length indicator.
103 	 * To write array with length indicator use `write` instead.
104 	 * 
105 	 * Params:
106 	 *  array = Array to write
107 	 */
108 	void writeArray(T)(T[] array)
109 	{
110 		buffer.reserve(T.sizeof * array.length);
111 
112 		foreach(el; array) {
113 			write!T(el);
114 		}
115 	}
116 
117 	/**
118 	 * Writes `str` to buffer.
119 	 * 
120 	 * This function writes `str` to buffer and a null terminator.
121 	 * 
122 	 * Params:
123 	 *  str = String to write.
124 	 */
125 	void writeString(T)(T str)
126 		if (isSomeString!T)
127 	{
128 		alias ElType = ElementEncodingType!T;
129 		writeArray(str);
130 		write!byte(0);
131 	}
132 
133 	
134 	/**
135 	 * Writes `values` to buffer.
136 	 * 
137 	 * Params:
138 	 *  values = Values to write.
139 	 */
140 	void write(T...)(T values)
141 		if(T.length > 1)
142 	{
143 		foreach(value; values)
144 			write(value);
145 	}
146 
147 	
148 	/**
149 	 * Fills `value` specified number of `times`.
150 	 * 
151 	 * Params:
152 	 *  times  = Number of repeats
153 	 *  value  = Value to fill
154 	 */
155 	void fill(size_t times, ubyte value = 0)
156 	{
157 		buffer ~= repeat(value, times).array;
158 	}
159 
160 	/**
161 	 * Moves cursor to specified position filling stream with zeros if necessary.
162 	 * 
163 	 * If specified position is behind current cursor position, 
164 	 * nothing happens.
165 	 * 
166 	 * Params:
167 	 *  offset = Offset to align to
168 	 *  value  = Value to fill with if needed
169 	 */
170 	void padFill(size_t offset, ubyte value = 0)
171 	{
172 		if (cast(ptrdiff_t)(offset - position) < 0) 
173 			return;
174 		
175 		fill(cast(size_t)(offset - position), value);
176 	}
177 	
178 	/**
179 	 * Alias to write.
180 	 * 
181 	 * Makes BinaryWriter an OutputRange.
182 	 */
183 	alias put = write;
184 
185 	/**
186 	 * Current position in buffer.
187 	 */
188 	size_t position() @property
189 	{
190 		return buffer.length;
191 	}
192 
193 	/**
194 	 * Sets new position in buffer.
195 	 */
196 	void position(size_t newpos) @property
197 	{
198 		buffer.length = newpos;
199 	}
200 
201 	
202 	/**
203 	 * Clears buffer and resets current position.
204 	 */
205 	void clear()
206 	{
207 		buffer.length = 0;
208 		buffer.assumeSafeAppend();
209 	}
210 
211 }
212 
213 unittest
214 {
215 	import std.stdio;
216 	import std..string;
217 
218 	static assert(isOutputRange!(BinaryWriter, int));
219 	static assert(isOutputRange!(BinaryWriter, int[]));
220 	static assert(isOutputRange!(BinaryWriter, string));
221 	static assert(isOutputRange!(BinaryWriter, char));
222 
223 	BinaryWriter writer = BinaryWriter(ByteOrder.LittleEndian);
224 	writer.write(15);
225 	assert(writer.buffer == [15, 0, 0, 0]);
226 	assert(writer.position == 4);
227 	writer.write('0');
228 	assert(writer.buffer == [15, 0, 0, 0, '0']);
229 	assert(writer.position == 5);
230 
231 	writer.byteOrder = ByteOrder.BigEndian;
232 	writer.write!short(70);
233 	assert(writer.position == 7);
234 	assert(writer.buffer == [15, 0, 0, 0, '0', 0, 70]);
235 	writer.clear();
236 	assert(writer.position == 0);
237 	assert(writer.buffer == []);
238 	writer.write("abc");
239 	assert(writer.buffer == [0, 0, 0, 3, 'a', 'b', 'c']);
240 
241 	writer.clear();
242 	writer.writeString("abc");
243 	assert(writer.buffer == ['a', 'b', 'c', 0]);
244 
245 	writer.clear();
246 	writer.put("name");
247 	writer.padFill(1);
248 	assert(writer.buffer == [0, 0, 0, 4, 'n', 'a', 'm', 'e']);
249 	assert(writer.position == 8);
250 	writer.padFill(10);
251 	assert(writer.buffer == [0, 0, 0, 4, 'n', 'a', 'm', 'e', 0, 0]);
252 	assert(writer.position == 10);
253 
254 	writer.position = 4;
255 	assert(writer.buffer == [0, 0, 0, 4]);
256 	assert(writer.position == 4);
257 	writer.write!ushort(20);
258 	writer.write!byte(50);
259 	assert(writer.position == 7);
260 	assert(writer.buffer == [0, 0, 0, 4, 0, 20, 50]);
261 
262 	writer.clear();
263 	assert(writer.buffer == []);
264 	assert(writer.position == 0);
265 
266 	// Arrays
267 	writer.write!(ushort[])([10, 20, 30]);
268 	assert(writer.buffer == [0, 0, 0, 3,  0, 10,  0, 20,  0, 30]);
269 	assert(writer.position == 10);
270 	writer.clear();
271 
272 	writer.writeArray([50, 30, 120]);
273 	assert(writer.buffer == [0, 0, 0, 50,  0, 0, 0, 30,  0, 0, 0, 120]);
274 	assert(writer.position == 12);
275 	writer.clear();
276 
277 	writer.write(15, 'c', "foo", false);
278 	assert(writer.buffer == [0, 0, 0, 15,  'c', 0, 0, 0, 3, 102, 111, 111, 0]);
279 	writer.clear();
280 
281 
282 	writer.write("abc"w);
283 	assert(writer.buffer == [0, 0, 0, 3,  0, 'a', 0, 'b', 0, 'c']);
284 	writer.clear();
285 	writer.write("abc"d);
286 	assert(writer.buffer == [0, 0, 0, 3,  0, 0, 0, 'a', 0, 0, 0, 'b', 0, 0, 0, 'c']);
287 	writer.clear();
288 
289 	writer.write(std..string.toStringz("Hello, World!"));
290 	assert(writer.buffer == ['H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', 0]);
291 	assert(writer.position == 14);
292 	writer.clear();
293 
294 	// Issue #4
295 	writer.byteOrder = ByteOrder.BigEndian;
296 	writer.write(cast(ulong)12);
297 	assert(writer.position == 8);
298 	assert(writer.buffer == [0, 0, 0, 0,  0, 0, 0, 12]);
299 }