1 module binary.unpack; 2 3 import std.ascii; 4 import std.algorithm; 5 import std.stdio; 6 import std.range; 7 import std.typecons; 8 import std.traits; 9 import std..string; 10 import binary.common; 11 import binary.format; 12 import binary.reader; 13 14 15 /** 16 * Decodes binary data. 17 * 18 * This function unpacks binary encoded data from `range` and puts them 19 * into variables passed as arguments. If `range` is too small, DecodeException 20 * is thrown. 21 * 22 * Throws: 23 * DecodeException 24 * 25 * Params: 26 * format = Format specifier 27 * byteOrder = Byte Order to use, ByteOrder.Native is default. 28 * range = Data to unpack 29 * values... = Values to un pack to 30 */ 31 void unpackTo(string format, ByteOrder byteOrder = ByteOrder.Native, Range, V...)(auto ref Range range, ref V values) 32 if (isInputRange!Range && is(ElementType!Range == ubyte)) 33 { 34 auto reader = binaryReader(range, byteOrder); 35 unpackTo!(format)(reader, values); 36 range = reader.source; 37 } 38 39 40 /** 41 * Decodes binary data from file. 42 * 43 * This function reads data from file, unpacks binary encoded data and puts them 44 * into variables passed as arguments. If read data is too small, DecodeException 45 * is thrown. 46 * 47 * Passed file must be opened with read access. 48 * 49 * Throws: 50 * DecodeException 51 * 52 * Params: 53 * format = Unpack format 54 * byteOrder = Byte Order to use, ByteOrder.Native is default. 55 * file = File to read from 56 * values... = Values to un pack to 57 */ 58 void unpackTo(string format, ByteOrder byteOrder = ByteOrder.Native, V...)(File file, ref V values) 59 { 60 if (file.tell > 0) file.seek(-1, SEEK_CUR); 61 62 auto reader = binaryReader(file.byChunk(1).joiner, byteOrder); 63 unpackTo!(format)(reader, values); 64 } 65 66 67 /** 68 * Decodes binary data. 69 * 70 * This function unpacks binary encoded data from `range` and puts them 71 * into variables passed as arguments. If `range` is too small, DecodeException 72 * is thrown. 73 * 74 * Format string is implied from passed parameter types. 75 * 76 * Throws: 77 * DecodeException 78 * 79 * Params: 80 * byteOrder = Byte Order to use, ByteOrder.Native is default. 81 * range = Data to unpack 82 * values... = Values to unpack to 83 */ 84 void unpackTo(ByteOrder byteOrder = ByteOrder.Native, Range, V...)(auto ref Range range, ref V values) 85 if (isInputRange!Range && is(ElementType!Range == ubyte)) 86 { 87 unpackTo!(formatOf!V, byteOrder)(range, values); 88 } 89 90 91 /** 92 * Decodes binary data from file. 93 * 94 * This function reads data from file, unpacks binary encoded data and puts them 95 * into variables passed as arguments. If read data is too small, DecodeException 96 * is thrown. 97 * 98 * Passed file must be opened with read access. 99 * Format string is implied from passed parameter types. 100 * 101 * Throws: 102 * DecodeException 103 * 104 * Params: 105 * byteOrder = Byte Order to use, ByteOrder.Native is default. 106 * file = File to read from 107 * values... = Values to un pack to 108 */ 109 void unpackTo(ByteOrder byteOrder = ByteOrder.Native, Range, V...)(File file, ref V values) 110 { 111 unpackTo!(formatOf!V, byteOrder)(file, values); 112 } 113 114 115 116 void unpackTo(string format, Range, V...)(ref BinaryReader!Range reader, ref V values) 117 if(format.length == 0) 118 { 119 } 120 121 122 123 /** 124 * Decodes binary data. 125 * 126 * Throws: 127 * DecodeException 128 * 129 * Params: 130 * format = Format specifier 131 * reader = Reader instance to use 132 * values... = Values to un pack to 133 */ 134 void unpackTo(string format, Range, V...)(ref BinaryReader!Range reader, ref V values) 135 if(format.length > 0) 136 { 137 enum char current = format[0]; 138 139 // Ignore whitespaces 140 static if (isWhite(current)) 141 unpackTo!(format[1..$])(reader, values); 142 143 // Endianess modifiers 144 else static if (formatCharToEndian!current != -1) { 145 reader.byteOrder = formatCharToEndian!current; 146 unpackTo!(format[1..$])(reader, values); 147 } 148 149 // Dynamic arrays 150 else static if (current == '*') { 151 static assert(format.length > 1, "Expected star to be followed by type character"); 152 static assert(V.length > 0, "Missing parameter for type character *"~format[1]); 153 static assert(isArray!(V[0]), .format("Expected parameter to be an array, %s given", V[0].stringof)); 154 alias TargetType = formatTypeOf!(format[1]); 155 reader.read(cast(TargetType[])values[0]); 156 157 static if (format.length > 2) 158 unpackTo!(format[2..$])(reader, values[1..$]); 159 } 160 161 // Static arrays 162 else static if (isDigit(current)) 163 { 164 // Creates result* variables in local scope 165 mixin formatRepeatCount!format; 166 static if(resultChar == 'x') 167 { 168 reader.skipBytes(resultCount); 169 unpackTo!(resultRest)(reader, values); 170 } 171 else static if(resultChar == 'X') 172 { 173 reader.skipTo(resultCount); 174 unpackTo!(resultRest)(reader, values); 175 } 176 else 177 { 178 static assert(V.length > 0, .format("No parameter specified for type %c", resultChar)); 179 alias T = V[0]; 180 181 static if(isArray!T) { 182 reader.read(values[0]); 183 unpackTo!(resultRest)(reader, values[1..$]); 184 } 185 else 186 { 187 static assert(values.length + 1 >= resultCount, .format("Expected %d parameters", resultCount)); 188 reader.read(values[0..resultCount]); 189 190 unpackTo!(resultRest)(reader, values[resultCount..$]); 191 } 192 } 193 } 194 195 // Pad byte. 196 else static if (current == 'x') 197 { 198 reader.skipBytes(1); 199 unpackTo!(format[1..$])(reader, values); 200 } 201 202 else static if (current == 'X') 203 { 204 static assert(0, "Format character 'X' must be preceded by number."); 205 } 206 207 // Type characters 208 else static if ( !is(formatTypeOf!(current) == void) ) 209 { 210 static assert(V.length > 0, .format("No parameter specified for character '%c'", current)); 211 212 // If value is convertible to format character 213 static if (__traits(compiles, cast(formatTypeOf!current)values[0])) { 214 formatTypeOf!current val; 215 static if (current == 's') 216 reader.readString(val); 217 else static if (current == 'S') { 218 if (val.length == 0) { 219 throw new DecodeException("Reading string with length 0 ('S' passed to unpack and parameter array length is 0)"); 220 } 221 reader.readArray(val, val.length); 222 } 223 else 224 reader.read(val); 225 226 values[0] = cast(V[0])val; 227 } 228 else 229 { 230 static assert(0, .format("Incompatible types: %s and %s, format character '%c'", 231 V[0].stringof, formatTypeOf!(current).stringof, format[0])); 232 } 233 234 unpackTo!(format[1..$])(reader, values[1..$]); 235 } 236 else { 237 static assert (0, .format("Invalid format specifier %c", current)); 238 } 239 } 240 241 242 /** 243 * Decodes binary data. 244 * 245 * This function works similar to other `unpack` functions, except that read data 246 * is returned as tuple. 247 * 248 * Params: 249 * format = Format specifier 250 * endianess = Endianess to use, Endian.Native is default. 251 * data = Binary encoded data 252 * 253 * Returns: 254 * Tuple with read data. 255 */ 256 auto unpack(string format, ByteOrder byteOrder = ByteOrder.Native, Range)(auto ref Range data) 257 if(isInputRange!Range && is(ElementType!Range == ubyte)) 258 { 259 Tuple!(formatTypeTupleOf!format) tup; 260 unpackTo!(format, byteOrder)(data, tup.expand); 261 return tup; 262 } 263 264 265 /** 266 * Decodes binary data from file. 267 * 268 * This function works similar to other `unpack` functions, except that read data 269 * is returned as tuple. 270 * 271 * Params: 272 * format = Format specifier 273 * endianess = Endianess to use, Endian.Native is default. 274 * file = File to read from 275 * 276 * Returns: 277 * Tuple with read data. 278 */ 279 auto unpack(string format, ByteOrder byteOrder = ByteOrder.Native)(File file) 280 { 281 Tuple!(formatTypeTupleOf!format) tup; 282 unpackTo!(format, byteOrder)(file, tup.expand); 283 return tup; 284 } 285 286 287 288 /** 289 * Returns an instance of unpacker of T. 290 * 291 * Params: 292 * format = Format specifier 293 * byteOrder = Byte order to use 294 * range = Range to read from 295 */ 296 auto unpacker(string format, ByteOrder byteOrder = ByteOrder.Native, R)(auto ref R range) 297 if (isInputRange!R && is(ElementType!R == ubyte)) 298 { 299 return Unpacker!(format, byteOrder, R)(range); 300 } 301 302 /** 303 * Returns an instance of unpacker of T. 304 * 305 * Params: 306 * format = Format specifier 307 * byteOrder = Byte order to use 308 * file = File to read from 309 */ 310 auto unpacker(string format, ByteOrder byteOrder = ByteOrder.Native)(File file) 311 { 312 return Unpacker!(format, byteOrder, File)(file); 313 } 314 315 316 /** 317 * Unpacker range. 318 * 319 * Allows to unpack repeated binary encoded entries with range interface. 320 * 321 * Examples: 322 * ---- 323 * ubyte[] bytes = pack!`<hshs`(1, "one", 2, "two"); 324 * 325 * foreach(num, str; bytes) { 326 * writeln(num, " ", str); 327 * } 328 * ---- 329 */ 330 struct Unpacker(string format, ByteOrder byteOrder = ByteOrder.Native, R) 331 if ((isInputRange!R && is(ElementType!R == ubyte)) || is(R == File)) 332 { 333 /** 334 * Alias for type tuple 335 */ 336 alias Type = formatTypeTupleOf!format; 337 338 /** 339 * Source range/file to read from. 340 */ 341 R source; 342 343 344 /** 345 * Tuple of unpacked elements 346 */ 347 Tuple!Type front; 348 349 350 /** 351 * Determines if more data can be unpacked. 352 */ 353 bool empty; 354 355 356 /** 357 * Creates instance of Unpacker. 358 * 359 * Params: 360 * range = Range of ubytes to unpack from. 361 */ 362 this(R range) 363 { 364 source = range; 365 popFront(); 366 } 367 368 369 /** 370 * Unpacks next element from source range. 371 */ 372 void popFront() 373 { 374 static if(is(R == File)) { 375 empty = source.eof; 376 } 377 else { 378 empty = source.empty; 379 } 380 381 if (empty) return; 382 383 front.expand = front.expand.init; 384 source.unpackTo!(format, byteOrder)(front.expand); 385 } 386 }