1 module binary.reader; 2 3 import std.algorithm; 4 import std.array; 5 import std.range; 6 import std.traits; 7 import binary.common; 8 9 10 11 /** 12 * Creates new instance of Binary reader. 13 * 14 * Params: 15 * range = Input range to read from 16 * order = Byte order to use 17 */ 18 auto binaryReader(Range)(auto ref Range range, ByteOrder order = ByteOrder.Native) 19 { 20 return BinaryReader!(Range)(range, order); 21 } 22 23 24 /** 25 * Reads binary encoded data. 26 */ 27 struct BinaryReader(Range = ubyte[]) 28 if(isInputRange!Range && is(ElementType!Range == ubyte)) 29 { 30 /** 31 * InputRange of ubytes to read from. 32 */ 33 private Range _source; 34 35 36 /** 37 * Current position in stream. 38 */ 39 private ulong _position; 40 41 42 /** 43 * Used byte order 44 */ 45 ByteOrder byteOrder; 46 47 48 49 /** 50 * Creates instance of BinaryReader. 51 * 52 * Params: 53 * source = Range of ubytes to read from. 54 */ 55 this(ref Range source, ByteOrder byteOrder = ByteOrder.Native) 56 { 57 _source = source; 58 _position = 0; 59 this.byteOrder = byteOrder; 60 } 61 62 63 /** 64 * Creates instance of BinaryReader. 65 * 66 * Params: 67 * source = Range of ubytes to read from. 68 */ 69 /*this(Range source, ByteOrder byteOrder = ByteOrder.Native) 70 { 71 this(source, byteOrder); 72 }*/ 73 74 75 /** 76 * Skips `num` bytes from source. 77 * 78 * Params: 79 * num = Number of bytes to skip. 80 */ 81 void skipBytes(size_t num) 82 { 83 _source.popFrontN(num); 84 } 85 86 87 /** 88 * Moves cursor to specified position. 89 * 90 * If specified position is behind current cursor position, 91 * nothing happens. 92 * 93 * Params: 94 * offset = Offset to align to 95 */ 96 void skipTo(size_t offset) 97 { 98 if (cast(long)(offset - _position) < 0) 99 return; 100 101 skipBytes(cast(size_t)(offset - _position)); 102 } 103 104 105 /** 106 * Reads specified value from input stream. 107 * 108 * If there is insufficient data in input stream, DecodeException is thrown. 109 * 110 * Throws: 111 * DecodeException 112 * 113 * Params: 114 * value = Value to read to 115 */ 116 void read(T)(ref T value) 117 { 118 if (empty) 119 throw new DecodeException("Input stream is empty."); 120 121 static if (isStaticArray!T) { 122 foreach(ref el; value) 123 read!(ElementEncodingType!T)(el); 124 } 125 else static if (isDynamicArray!T) { 126 uint size; 127 read(size); 128 value.length = cast(size_t)size; 129 readArray!(ElementEncodingType!T)(value, value.length); 130 } 131 else { 132 value = decodeBinary!T(readBytes(T.sizeof), byteOrder); 133 } 134 } 135 136 137 /** 138 * Reads array with `length` elements. 139 * 140 * Throws: 141 * DecodeException 142 * 143 * Params: 144 * arr = Array to read to. 145 * length = Number of elements to read. 146 */ 147 void readArray(T)(ref T[] arr, size_t length) 148 { 149 arr.length = length; 150 for(size_t i=0; i<length; i++) { 151 read!T(arr[i]); 152 } 153 } 154 155 156 /** 157 * Reads string into `str`. 158 * 159 * Reads until null terminator. If not found, DecodeException is thrown. 160 * 161 * Throws: 162 * DecodeException 163 * 164 * Params: 165 * str = String to read to 166 */ 167 void readString(T)(ref T str) 168 if (isSomeString!T) 169 { 170 char[] data = cast(char[])readUntil(0); 171 str = cast(T)data; 172 } 173 174 175 /** 176 * Reads T type from stream and returns it. 177 * 178 * If there is insufficient data in input stream, DecodeException is thrown. 179 * 180 * Throws: 181 * DecodeException 182 * 183 * Returns: 184 * Read value of type T. 185 */ 186 T read(T)() 187 { 188 T value; 189 read!T(value); 190 return value; 191 } 192 193 /** 194 * Reads array of type T with `num` elemetns and returns it. 195 * 196 * Throws: 197 * DecodeException 198 * 199 * Returns: 200 * Array with `num` elements. 201 */ 202 T[] readArray(T)(size_t num) 203 { 204 T[] arr = new T[num]; 205 readArray(arr, num); 206 return arr; 207 } 208 209 210 /** 211 * Reads string and returns it. 212 * 213 * See_Also: 214 * `readString` 215 * 216 * Returns: 217 * Read string. 218 */ 219 string readString() 220 { 221 string str; 222 readString(str); 223 return str; 224 } 225 226 227 /** 228 * Reads specified values from input stream. 229 * 230 * Throws: 231 * DecodeException 232 * 233 * Params: 234 * value = Tuple of values to read to 235 */ 236 void read(T...)(ref T values) 237 if(T.length > 1) 238 { 239 foreach(ref value; values) 240 read(value); 241 } 242 243 244 /** 245 * Determines if input stream is empty. 246 */ 247 bool empty() 248 { 249 return source.empty; 250 } 251 252 253 /** 254 * Clears source range and position. 255 */ 256 void clear() 257 { 258 _source = _source.init; 259 _position = 0; 260 } 261 262 263 /** 264 * Reads array of bytes from input stream. 265 * 266 * Returned array may be smaller than requested if end 267 * of input occured. 268 * 269 * Params: 270 * bytes = Number of bytes to read 271 * 272 * Returns: 273 * Array of bytes read 274 */ 275 ubyte[] readBytes(size_t bytes) 276 { 277 ubyte[] arr = _source.take(bytes).array; 278 279 static if (isForwardRange!Range) 280 _source.popFrontN(bytes); 281 282 _position += arr.length; 283 284 return arr; 285 } 286 287 288 /** 289 * Reads bytes until `stop` is found. 290 * 291 * Returned array can be empty if input stream is empty. 292 * 293 * Params: 294 * stop = Value to read until 295 * next = If true, input stream is moved to next byte. 296 * 297 * Returns: 298 * Array of bytes. 299 */ 300 ubyte[] readUntil(ubyte stop, bool next = true) 301 { 302 ubyte[] arr = _source.until(0).array; 303 304 static if(isForwardRange!Range) 305 _source.popFrontN(arr.length); 306 307 if (!empty && next && _source.front == 0) { 308 _source.popFront(); 309 _position += 1; 310 } 311 312 _position += arr.length; 313 314 return arr; 315 } 316 317 318 /** 319 * Gets source range used. 320 */ 321 Range source() @property 322 { 323 return _source; 324 } 325 326 327 /** 328 * Sets new source range to read from. 329 */ 330 void source(ref Range source) @property 331 { 332 _position = 0; 333 _source = source; 334 } 335 336 337 /** 338 * Sets new source range to read from. 339 */ 340 void source(Range source) @property 341 { 342 _position = 0; 343 _source = source; 344 } 345 346 347 /** 348 * Gets current position in stream. 349 */ 350 ulong position() @property 351 { 352 return _position; 353 } 354 } 355 356 357 358 unittest 359 { 360 import std.exception; 361 362 short sh; 363 auto reader = binaryReader(cast(ubyte[])[15, 0, 0, 30]); 364 reader.byteOrder = ByteOrder.LittleEndian; 365 reader.read(sh); 366 assert(sh == 15); 367 assert(!reader.empty); 368 reader.byteOrder = ByteOrder.BigEndian; 369 reader.read(sh); 370 assert(sh == 30); 371 assert(reader.empty); 372 373 assertThrown!DecodeException(reader.read(sh), "Input stream is empty."); 374 reader.source = [9]; 375 assertThrown!DecodeException(reader.read(sh), 376 "Unexpected end of input stream. Trying to read 2 bytes (type short), but got 1."); 377 378 char[] str; 379 reader.source = ['a', 'b', 'c']; 380 reader.readArray(str, cast(size_t)3); 381 assert(str == "abc".dup); 382 assert(reader.empty); 383 384 reader.source = ['x', 'y', 'z', 0, 90, 0]; 385 reader.byteOrder = ByteOrder.LittleEndian; 386 reader.readString(str); 387 reader.read(sh); 388 assert(str == "xyz".dup); 389 assert(sh == 90); 390 assert(reader.empty); 391 392 reader.clear(); 393 assert(reader.empty); 394 assert(reader.position == 0); 395 396 long l; 397 reader.source = [1, 56, 0, 0, 0, 0, 0, 0, 0]; 398 assert(!reader.empty); 399 assert(reader.source.front == 1); 400 reader.skipBytes(1); 401 assert(reader.source.front == 56); 402 assert(!reader.empty); 403 reader.read(l); 404 assert(l == 56); 405 assert(reader.empty); 406 407 reader.source = ['a', 'b', 'c', 0, 0, 0, 0, 0, 15, 0]; 408 assert(reader.position == 0); 409 assert(reader.readString == "abc".dup); 410 assert(reader.source == [0, 0, 0, 0, 15, 0]); 411 reader.skipTo(8); 412 assert(reader.source == [15, 0]); 413 assert(reader.read!short == 15); 414 415 reader.source = [10, 20, 30, 40]; 416 assert(reader.readArray!byte(4) == [10, 20, 30, 40]); 417 418 reader.byteOrder = ByteOrder.BigEndian; 419 reader.source = [0, 0, 0, 3, 0, 99, 0, 55, 0, 44]; 420 assert(reader.read!(ushort[])() == [99, 55, 44]); 421 422 reader.source = [0, 0, 0, 5, 'H', 'e', 'l', 'l', 'o']; 423 reader.read(str); 424 assert(str == "Hello".dup); 425 }