1 module binary.pack; 2 3 import std.ascii; 4 import std.algorithm; 5 import std.array; 6 import std.stdio; 7 import std.range; 8 import std.typetuple; 9 import std.typecons; 10 import std.traits; 11 import std..string; 12 import binary.common; 13 import binary.writer; 14 import binary.format; 15 16 17 18 /** 19 * Encodes data to binary. 20 * 21 * This function packs all specified values into binary data and returns it. 22 * Any invalid character specified in format string results in static assert failure. 23 * 24 * Available modifier characters 25 * 26 * Character | Effect 27 * ------------|-------------------- 28 * `=` | Change to native endian 29 * `<` | Change to little endian 30 * `>` | Change to big endian 31 * '@' | Change to network byte order(big endian) 32 * 33 * 34 * Available type specifiers 35 * 36 * Character | Type | Size 37 * -----------|------------|---------- 38 * `c` | `char` | 1 39 * `b` | `byte` | 1 40 * `B` | `ubyte` | 1 41 * `h` | `short` | 2 42 * `H` | `ushort` | 2 43 * `i` | `int` | 4 44 * `I` | `uint` | 4 45 * `p` | `ptrdiff_t`| 4/8 46 * `P` | `size_t` | 4/8 47 * `l` | `long` | 8 48 * `L` | `ulong` | 8 49 * `f` | `float` | 4 50 * `d` | `double` | 8 51 * `s` | `string` | string length + nul 52 * `S` | `string` | string length 53 * `x` | - | 1 (zero byte) 54 * `X` | - | Skip/Pad to position 55 * 56 * 57 * Array behavior (All examples in little endian) 58 * 59 * Type spec. | Data | Description | Encoded 60 * ----------------|------------|-------------------------------|--------------- 61 * `int`(`i`) | 12 | Binary encoded integer | `[0, 0, 0, 12]` 62 * `int`(`i`) | [12, 21] | Written element by element | `[0, 0, 0, 12, 0, 0, 0, 21]` 63 * `int[]`(`*i`) | [12, 21] | Length and elements written | `[0, 0, 0, 2, 0, 0, 0, 12, 0, 0, 0, 21]` 64 * `int[2]`(`2i`) | [12, 21] | Written same as `i` | `[0, 0, 0, 12, 0, 0, 0, 21]` 65 * `int[1]`(`1i`) | [12, 21] | Only 1 element is written | `[0, 0, 0, 12]` 66 * 67 * Quick Notes: 68 * - `s` is an alias for `Sx` 69 * - To write strings like all other arrays use `*c` format. 70 * - In `pack` using `#i` on array of elements will write exacly # elements. 71 * If array is too big it is sliced, if too small range error is thrown. 72 * 73 * Params: 74 * format = Format specifier 75 * endianess = Endianess to use, Endian.Native is default 76 * values... = Values to encode 77 */ 78 ubyte[] pack(string format, ByteOrder byteOrder = ByteOrder.Native, V...)(V values) 79 if (!isFirstArgFile!V) 80 { 81 BinaryWriter writer = BinaryWriter(byteOrder); 82 pack!(format)(writer, values); 83 return writer.buffer; 84 } 85 86 /** 87 * Binary encodes data to file 88 * 89 * Specified file must be opened with write access. 90 * 91 * Params: 92 * file = File to write to 93 * values = Values to encode 94 */ 95 void pack(string format, ByteOrder byteOrder = ByteOrder.Native, V...)(File file, V values) 96 { 97 BinaryWriter writer = BinaryWriter(byteOrder); 98 pack!(format)(writer, values); 99 file.rawWrite(writer.buffer); 100 } 101 102 103 /** 104 * Binary encodes data. 105 * 106 * In this overload format string is infered from V type tuple. 107 * 108 * Params: 109 * value = Values to encode 110 */ 111 ubyte[] pack(ByteOrder byteOrder = ByteOrder.Native, V...)(V values) 112 if(!isFirstArgFile!V) 113 { 114 return pack!(formatOf!V, byteOrder)(values); 115 } 116 117 118 /** 119 * Binary encodes data. 120 * 121 * In this overload format string is infered from V type tuple. 122 * 123 * Params: 124 * file = File to write to 125 * value = Values to encode 126 */ 127 ubyte[] pack(ByteOrder byteOrder = ByteOrder.Native, V...)(File file, V values) 128 { 129 return pack!(formatOf!V, byteOrder)(file, values); 130 } 131 132 133 void pack(string format, V...)(ref BinaryWriter writer, V values) 134 if(format.length == 0) 135 { 136 } 137 138 /** 139 * Encodes data to binary. 140 * 141 * Writes all encoded `values` to `writer`. 142 * 143 * Params: 144 * writer = Writer to write to 145 * values = Values to encode 146 */ 147 void pack(string format, V...)(ref BinaryWriter writer, V values) 148 if (format.length > 0) 149 { 150 enum char current = format[0]; 151 152 // Ignore whitespaces 153 static if (isWhite(current)) 154 pack!(format[1..$])(writer, values); 155 156 // Endianess modifiers 157 else static if (formatCharToEndian!current != -1) { 158 writer.byteOrder = formatCharToEndian!current; 159 pack!(format[1..$])(writer, values); 160 } 161 162 // Dynamic arrays 163 else static if (current == '*') { 164 static assert(format.length > 1, "Expected star to be followed by type character"); 165 static assert(V.length > 0, "Missing parameter for type character *"~format[1]); 166 static assert(isArray!(V[0]), .format("Expected parameter to be an array, %s given", V[0].stringof)); 167 writer.write(cast(formatTypeOf!(format[1])[])values[0]); 168 169 static if (format.length > 2) 170 pack!(format[2..$])(writer, values[1..$]); 171 } 172 173 // Static arrays 174 else static if (isDigit(current)) 175 { 176 // Creates result* variables in local scope 177 mixin formatRepeatCount!format; 178 static if(resultChar == 'x') 179 { 180 writer.writeArray( (cast(ubyte)0).repeat(resultCount).array ); 181 pack!(resultRest)(writer, values); 182 } 183 else static if(resultChar == 'X') 184 { 185 writer.padFill(resultCount); 186 pack!(resultRest)(writer, values); 187 } 188 else 189 { 190 static assert(V.length > 0, .format("No parameter specified for type %c", resultChar)); 191 alias T = V[0]; 192 193 static if(isArray!T) { 194 alias TargetType = formatTypeOf!resultChar; 195 auto sliced = values[0][0..resultCount]; 196 writer.writeArray(sliced.map!(x => cast(TargetType)x).array); 197 pack!(resultRest)(writer, values[1..$]); 198 } 199 else 200 static assert(0, .format("Specified static array in format string but parameter is not an array")); 201 } 202 } 203 204 // Pad byte. 205 else static if (current == 'x') 206 { 207 writer.write!byte(0); 208 pack!(format[1..$])(writer, values); 209 } 210 211 else static if (current == 'X') 212 { 213 static assert(0, "Format character 'X' must be preceded by number."); 214 } 215 216 // Type characters 217 else static if ( !is(formatTypeOf!(current) == void) ) 218 { 219 static assert(V.length > 0, .format("No parameter specified for character '%c'", current)); 220 221 static if (isArray!(V[0])) { 222 static if (current == 's') 223 writer.writeString(values[0]); 224 else static if (current == 'S') 225 writer.writeArray(values[0]); 226 else 227 writer.writeArray(values[0][].map!(x => cast(formatTypeOf!current)x).array); 228 } 229 // If value is convertible to format character 230 else static if (__traits(compiles, cast(formatTypeOf!(current))values[0])) { 231 writer.write(cast(formatTypeOf!current)values[0]); 232 } 233 else 234 { 235 static assert(0, .format("Incompatible types: %s and %s, format character '%c'", 236 V[0].stringof, formatTypeOf!(current).stringof, format[0])); 237 } 238 239 pack!(format[1..$])(writer, values[1..$]); 240 } 241 else { 242 static assert (0, .format("Invalid format specifier %c", current)); 243 } 244 } 245 246 247 248 private template isFirstArgFile(V...) 249 { 250 static if(V.length > 0 && is(V[0] == File)) 251 enum bool isFirstArgFile = true; 252 else 253 enum bool isFirstArgFile = false; 254 } 255 256 257 unittest 258 { 259 /// TODO: Check every static if 260 261 import binary.unpack; 262 263 assert(pack!(ByteOrder.LittleEndian)(10, '0') == [10, 0, 0, 0, '0']); 264 265 { 266 ubyte[] bytes = pack!`c c`('a', 'b'); 267 assert(bytes == ['a', 'b']); 268 assert(bytes.unpack!`cc` == tuple('a', 'b')); 269 assert(bytes == []); 270 } 271 272 273 { 274 ubyte[] bytes = pack!`<h8Xi`(18, -3); 275 assert(bytes == [18, 0, 0, 0, 0, 0, 0, 0, 253, 255, 255, 255]); 276 assert(bytes.unpack!`<h8Xi` == tuple(18, -3)); 277 assert(bytes == []); 278 } 279 280 { 281 ubyte[] bytes = pack!`<oboh`(true, true, false, false); 282 assert(bytes == [1, 1, 0, 0, 0]); 283 assert(bytes.unpack!`<oboh` == tuple(true, true, false, 0)); 284 assert(bytes == []); 285 } 286 287 { 288 long l; 289 int i; 290 char a, z; 291 292 ubyte[] bytes = pack(1, 22L, 'a', 'z'); 293 bytes.unpackTo(i, l, a, z); 294 assert(i == 1); 295 assert(l == 22); 296 assert(a == 'a'); 297 assert(z == 'z'); 298 } 299 300 { 301 ubyte[] bytes = pack!`4xx`; 302 assert(bytes == [0, 0, 0, 0, 0]); 303 assert(bytes.save.unpack!`5c` == tuple(['\0', '\0', '\0', '\0', '\0'])); 304 ubyte[5] arr; 305 bytes.unpackTo!`5c`(arr); 306 assert(arr[0] == 0); 307 assert(arr[1] == 0); 308 assert(arr[2] == 0); 309 assert(arr[3] == 0); 310 assert(arr[4] == 0); 311 assert(bytes == []); 312 } 313 314 { 315 ubyte[] bytes = pack!`<h>h`(15, 30); 316 assert(bytes == [15, 0, 0, 30]); 317 assert(bytes.save.unpack!`<h>h` == tuple(15, 30)); 318 assert(bytes.unpack!`>I`() == tuple(251658270)); 319 assert(bytes == []); 320 } 321 322 { 323 ubyte[] bytes = pack!`s`("a"); 324 assert(bytes == ['a', 0]); 325 assert(bytes.unpack!`s`() == tuple("a")); 326 assert(bytes == []); 327 } 328 329 { 330 ubyte[] bytes = pack!`<h3xcc`(56, 'a', 'c'); 331 assert(bytes == [56, 0, 0, 0, 0, 'a', 'c']); 332 assert(bytes.unpack!`h3x2c`() == tuple(56, ['a', 'c'])); 333 assert(bytes == []); 334 } 335 336 { 337 ubyte[] bytes = pack!`S`("Hello"); 338 assert(bytes == ['H', 'e', 'l', 'l', 'o']); 339 auto values = bytes.save.unpack!`5c`(); 340 assert(values == tuple(['H', 'e', 'l', 'l', 'o'])); 341 assert(bytes.unpack!`s`() == tuple("Hello")); 342 } 343 344 345 346 { 347 auto file = File.tmpfile; 348 scope(exit) file.close(); 349 350 file.pack!`<hxsH`(95, "Hello", 42); 351 file.rewind(); 352 assert(file.unpack!`<hxsH` == tuple(95, "Hello", 42)); 353 assert(file.eof); 354 } 355 356 { 357 auto file = File.tmpfile; 358 scope(exit) file.close(); 359 360 file.pack!`<hxhx`(95, 51); 361 file.rewind(); 362 assert(file.unpack!`<hx` == tuple(95)); 363 assert(file.unpack!`<hx` == tuple(51)); 364 assert(file.eof); 365 } 366 367 { 368 ubyte[] bytes = pack!`<bhsbhs`(65, 105, "Hello", 'z', 510, " World"); 369 370 auto unpacker = unpacker!`<bhs`(bytes); 371 static assert(isInputRange!(typeof(unpacker))); 372 assert(!unpacker.empty); 373 assert(unpacker.front == tuple(65, 105, "Hello")); 374 unpacker.popFront(); 375 assert(!unpacker.empty); 376 assert(unpacker.front == tuple('z', 510, " World")); 377 unpacker.popFront(); 378 assert(unpacker.empty); 379 } 380 381 { 382 File file = File.tmpfile; 383 scope(exit) file.close; 384 file.pack!`<bhsbhs`(65, 105, "Hello", 'z', 510, " World"); 385 386 file.rewind(); 387 auto unpacker = unpacker!`<bhs`(file); 388 static assert(isInputRange!(typeof(unpacker))); 389 assert(!unpacker.empty); 390 assert(unpacker.front == tuple(65, 105, "Hello")); 391 unpacker.popFront(); 392 assert(!unpacker.empty); 393 assert(unpacker.front == tuple('z', 510, " World")); 394 unpacker.popFront(); 395 assert(unpacker.empty); 396 assert(file.eof); 397 } 398 399 { 400 ubyte[] bytes = pack!`>*i2h`([12, 22, 32], [88, 99]); 401 assert(bytes == [0, 0, 0, 3, 0, 0, 0, 12, 0, 0, 0, 22, 0, 0, 0, 32, 0, 88, 0, 99]); 402 assert(bytes.save.unpack!`>*i2h` == tuple([12, 22, 32], [88, 99])); 403 } 404 405 { 406 ubyte[] bytes = pack!`>*c`("foobar"); 407 assert(bytes == [0, 0, 0, 6, 'f', 'o', 'o', 'b', 'a', 'r']); 408 assert(bytes.save.unpack!`>*c` == tuple("foobar")); 409 } 410 411 { 412 ubyte[] bytes = pack!`>5c`("foobar"); 413 assert(bytes == ['f', 'o', 'o', 'b', 'a']); 414 assert(bytes.save.unpack!`>5c` == tuple("fooba")); 415 } 416 417 { 418 ubyte[] bytes = pack!`>*s`(["c", "c++", "d"]); 419 assert(bytes == [0, 0, 0, 3, 0, 0, 0, 1, 'c', 0, 0, 0, 3, 'c', '+', '+', 0, 0, 0, 1, 'd']); 420 assert(bytes.save.unpack!`>*s` == tuple(["c", "c++", "d"])); 421 } 422 423 { 424 ushort[3] arr = [13, 31, 33]; 425 ubyte[] bytes = pack!`<*H`(arr); 426 assert(bytes == [3, 0, 0, 0, 13, 0, 31, 0, 33, 0]); 427 assert(bytes.save.unpack!`<I3H` == tuple(3, [13, 31, 33])); 428 429 bytes = pack!`<H`(arr); 430 assert(bytes == [13, 0, 31, 0, 33, 0]); 431 assert(bytes.unpack!`<3H` == tuple(arr)); 432 } 433 434 { 435 // Issue #4 436 ubyte[] bytes = pack!`>L`(12); 437 assert(bytes == [0, 0, 0, 0, 0, 0, 0, 12]); 438 assert(bytes.unpack!`>L`[0] == 12UL); 439 } 440 }