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 }