1 /// Performant binary stream implementation using `IStream`. 2 module tern.stream.binary_stream; 3 4 public import tern.stream.impl; 5 import tern.serialization; 6 import tern.traits; 7 import tern.object; 8 9 /// Binary stream implementation backed by an array. 10 public class BinaryStream : IStream 11 { 12 public: 13 final: 14 ubyte[] data; 15 size_t position; 16 Endianness endianness; 17 18 this(T)(T data, Endianness endianness = Endianness.Native) 19 { 20 if (isArray!T) 21 this.data = cast(ubyte[])data; 22 else 23 this.data = data.serialize(); 24 this.endianness = endianness; 25 } 26 27 /** 28 * Checks if there are enough elements left in the data array to read `T`. 29 * 30 * Params: 31 * T = The type to check if can be read. 32 * 33 * Returns: 34 * True if there are at least `T.sizeof` bytes left to read from the current position. 35 */ 36 bool mayRead(T)() 37 { 38 return position + T.sizeof > data.length; 39 } 40 41 /** 42 * Checks if there are enough elements left in the data array to read. 43 * 44 * Params: 45 * size = The number of elements to try to read. Defaults to 1. 46 * 47 * Returns: 48 * True if there are at least size elements left to read from the current position. 49 */ 50 bool mayRead(size_t size = 1) 51 { 52 return position + size > data.length; 53 } 54 55 /** 56 * Moves the position in the stream by the size of type T. 57 * 58 * Params: 59 * T = The size of type to move the position by. 60 */ 61 void step(T)(size_t count = 1) 62 { 63 position += T.sizeof * count; 64 } 65 66 /** 67 * Moves the position in the stream forward by one until `val` is peeked. 68 * 69 * Params: 70 * val = The value to be peeked. 71 */ 72 void stepUntil(T)(T val) 73 { 74 static if (isSomeString!T) 75 { 76 while (peekString!(ElementType!T) != val) 77 position++; 78 } 79 else 80 { 81 while (peek!T != val) 82 position++; 83 } 84 } 85 86 /** 87 * Seeks to a new position in the stream based on the provided offset and seek direction. 88 * 89 * Params: 90 * SEEK = The direction of the seek operation (Start, Current, or End). 91 * offset = The offset from the seek direction to be set. 92 */ 93 void seek(Seek SEEK)(size_t offset) 94 { 95 static if (SEEK = Seek.Current) 96 position += offset; 97 else static if (SEEK = Seek.Start) 98 position = offset; 99 else 100 position = data.length - offset; 101 } 102 103 /** 104 * Reads the next value from the stream of type T. 105 * 106 * Params: 107 * T = The type of data to be read. 108 * 109 * Returns: 110 * The value read from the stream. 111 */ 112 T read(T)() 113 if (!isDynamicArray!T) 114 { 115 if (position + T.sizeof > data.length) 116 throw new Throwable("Tried to read past the end of stream!"); 117 118 return (*cast(T*)data[position..(position += T.sizeof)].ptr).makeEndian(endianness); 119 } 120 121 /** 122 * Peeks at the next value from the stream of type T without advancing the stream position. 123 * 124 * Params: 125 * T = The type of data to peek. 126 * 127 * Returns: 128 * The value peeked from the stream. 129 */ 130 T peek(T)() 131 if (!isDynamicArray!T) 132 { 133 if (position + T.sizeof > data.length) 134 throw new Throwable("Tried to read past the end of stream!"); 135 136 return (*cast(T*)data[position..(position + T.sizeof)].ptr).makeEndian(endianness); 137 } 138 139 /** 140 * Reads multiple values of type T from the stream. 141 * 142 * Params: 143 * T = The type of data to be read. 144 * count = The number of values to read from the stream. 145 * 146 * Returns: 147 * An array of values read from the stream. 148 */ 149 T[] read(T)(size_t count) 150 if (!isDynamicArray!T) 151 { 152 T[] arr; 153 foreach (i; 0..count) 154 arr ~= read!T; 155 return arr; 156 } 157 158 /** 159 * Peeks at multiple values of type T from the stream without advancing the stream position. 160 * 161 * Params: 162 * T = The type of data to peek. 163 * count = The number of values to peek from the stream. 164 * 165 * Returns: 166 * An array of values peeked from the stream. 167 */ 168 T[] peek(T)(size_t count) 169 if (!isDynamicArray!T) 170 { 171 auto _position = position; 172 scope (exit) position = _position; 173 T[] arr; 174 foreach (i; 0..count) 175 arr ~= read!T; 176 return arr; 177 } 178 179 /** 180 * Reads an array of type T from the stream. 181 * 182 * Params: 183 * T = The type of data to be read. 184 * 185 * Returns: 186 * An array read from the stream. 187 */ 188 T read(T : U[], U)() 189 if (isDynamicArray!T) 190 { 191 return read!(ElementType!T)(cast(size_t)read7EncodedInt()); 192 } 193 194 /** 195 * Peeks an array of type T from the stream without advancing the stream position. 196 * 197 * Params: 198 * T = The type of data to peek. 199 * 200 * Returns: 201 * An array peeked from the stream. 202 */ 203 T peek(T : U[], U)() 204 if (isDynamicArray!T) 205 { 206 return peek!(ElementType!T)(cast(size_t)read7EncodedInt()); 207 } 208 209 /** 210 * Writes the provided value to the stream. 211 * 212 * Params: 213 * T = The type of data to be written. 214 * val = The value to be written to the stream. 215 */ 216 void write(T)(T val) 217 { 218 if (position + T.sizeof > data.length) 219 throw new Throwable("Tried to write past the end of stream!"); 220 221 auto _val = val.makeEndian(endianness); 222 data[position..(position += T.sizeof)] = (cast(ubyte*)&_val)[0..T.sizeof]; 223 } 224 225 /** 226 * Writes the provided value to the stream without advancing the stream position. 227 * 228 * Params: 229 * T = The type of data to be written. 230 * val = The value to be written to the stream. 231 */ 232 void put(T)(T val) 233 { 234 if (position + T.sizeof > data.length) 235 throw new Throwable("Tried to write past the end of stream!"); 236 237 _val = val.makeEndian(endianness); 238 data[position..(position + T.sizeof)] = (cast(ubyte*)&_val)[0..T.sizeof]; 239 } 240 241 /** 242 * Writes multiple values of type T to the stream. 243 * 244 * Params: 245 * T = The type of data to be written. 246 * items = An array of values to be written to the stream. 247 */ 248 void write(T, bool PREFIXED = true)(T val) 249 if (isArray!T) 250 { 251 static if (PREFIXED) 252 write7EncodedInt(cast(uint)(val.length)); 253 254 foreach (u; val) 255 write(u); 256 } 257 258 /** 259 * Writes multiple values of type T to the stream without advancing the stream position. 260 * 261 * Params: 262 * T = The type of data to be written. 263 * items = An array of values to be written to the stream. 264 */ 265 void put(T, bool PREFIXED = true)(T val) 266 if (isArray!T) 267 { 268 auto _position = position; 269 scope (exit) position = _position; 270 static if (PREFIXED) 271 write7EncodedInt(cast(uint)(val.length)); 272 273 foreach (u; val) 274 write(u); 275 } 276 277 /** 278 * Reads a string from the stream considering the character width and prefixing. 279 * 280 * Params: 281 * CHAR = The character type used for reading the string (char, wchar, or dchar). 282 * PREFIXED = Indicates whether the string is prefixed. Default is false. 283 * 284 * Returns: 285 * The read string from the stream. 286 */ 287 immutable(CHAR)[] readString(CHAR, bool PREFIXED = false)() 288 { 289 static if (PREFIXED) 290 return cast(immutable(CHAR)[])read!(immutable(CHAR)[]); 291 else 292 { 293 immutable(CHAR)[] ret; 294 while (peek!CHAR != '\0') 295 ret ~= read!CHAR; 296 return ret; 297 } 298 } 299 300 /** 301 * Reads a string from the stream considering the character width and prefixing without advancing the stream position. 302 * 303 * Params: 304 * CHAR = The character type used for reading the string (char, wchar, or dchar). 305 * PREFIXED = Indicates whether the string is prefixed. Default is false. 306 * 307 * Returns: 308 * The read string from the stream. 309 */ 310 immutable(CHAR)[] peekString(CHAR, bool PREFIXED = false)() 311 { 312 auto _position = position; 313 scope (exit) position = _position; 314 static if (PREFIXED) 315 return cast(immutable(CHAR)[])read!(immutable(CHAR)[]); 316 else 317 { 318 immutable(CHAR)[] ret; 319 while (peek!CHAR != '\0') 320 ret ~= read!CHAR; 321 return ret; 322 } 323 } 324 325 /** 326 * Writes a string to the stream considering the character width and prefixing. 327 * 328 * Params: 329 * CHAR = The character type used for writing the string (char, wchar, or dchar). 330 * PREFIXED = Indicates whether the string is prefixed. Default is false. 331 * val = The string to be written to the stream. 332 */ 333 void writeString(CHAR, bool PREFIXED = false)(immutable(CHAR)[] val) 334 { 335 static if (!PREFIXED) 336 val ~= '\0'; 337 338 write!(immutable(CHAR)[], PREFIXED)(val); 339 } 340 341 /** 342 * Writes a string into the stream considering the character width and prefixing without advancing the stream position. 343 * 344 * Params: 345 * CHAR = The character type used for writing the string (char, wchar, or dchar). 346 * PREFIXED = Indicates whether the string is prefixed. Default is false. 347 * val = The string to be put into the stream. 348 */ 349 void putString(CHAR, bool PREFIXED = false)(immutable(CHAR)[] val) 350 { 351 static if (!PREFIXED) 352 val ~= '\0'; 353 354 put!(immutable(CHAR)[], PREFIXED)(val); 355 } 356 357 /** 358 * Reads an integer value encoded in 7 bits from the stream. 359 * 360 * Returns: 361 * The integer value read from the stream. 362 */ 363 uint read7EncodedInt() 364 { 365 uint result = 0; 366 uint shift = 0; 367 368 foreach (i; 0..5) 369 { 370 ubyte b = read!ubyte; 371 result |= cast(uint)(b & 0x7F) << shift; 372 if ((b & 0x80) == 0) 373 return result; 374 shift += 7; 375 } 376 377 return result; 378 } 379 380 /** 381 * Writes an integer value encoded in 7 bits to the stream. 382 * 383 * Params: 384 * val = The integer value to be written to the stream. 385 */ 386 void write7EncodedInt(uint val) 387 { 388 foreach (i; 0..5) 389 { 390 byte b = cast(byte)(val & 0x7F); 391 val >>= 7; 392 if (val != 0) 393 b |= 0x80; 394 write(b); 395 if (val == 0) 396 return; 397 } 398 } 399 400 /** 401 * Reads data from a byte stream into a structured type based on specified field names and read kinds. 402 * Designed specifically for better control reading string and array fields. 403 * 404 * Params: 405 * T = The type representing the structure to read into. 406 * ARGS = Variadic template parameter representing field names and read kinds. 407 * 408 * Returns: 409 * Returns an instance of type T with fields populated based on the specified read operations. 410 */ 411 T read(T, ARGS...)() 412 { 413 T val; 414 foreach (field; Fields!T) 415 { 416 alias M = TypeOf!(val, field); 417 bool cread; 418 static foreach (i, ARG; ARGS) 419 { 420 static if (i % 3 == 0) 421 { 422 static assert(is(typeof(ARG) == string), 423 "Field name expected, found " ~ ARG.stringof); 424 } 425 else static if (i % 3 == 1) 426 { 427 static assert(is(typeof(ARG) == ReadKind), 428 "Read kind expected, found " ~ ARG.stringof); 429 } 430 else 431 { 432 static if (field == ARGS[i - 2] || ARGS[i - 2] == "") 433 { 434 static if (!isStaticArray!M && is(M == string)) 435 { 436 cread = true; 437 static if (ARGS[i - 1] == ReadKind.Field) 438 { 439 __traits(getMember, val, field) = read!char(__traits(getMember, val, ARG)).to!string; 440 } 441 else static if (ARGS[i - 1] == ReadKind.Fixed) 442 { 443 __traits(getMember, val, field) = read!char(ARG).to!string; 444 } 445 else 446 { 447 __traits(getMember, val, field) = readString!(char, ARG); 448 } 449 } 450 else static if (!isStaticArray!M && is(M == wstring)) 451 { 452 cread = true; 453 static if (ARGS[i - 1] == ReadKind.Field) 454 { 455 __traits(getMember, val, field) = read!wchar(__traits(getMember, val, ARG)).to!string; 456 } 457 else static if (ARGS[i - 1] == ReadKind.Fixed) 458 { 459 __traits(getMember, val, field) = read!wchar(ARG).to!string; 460 } 461 else 462 { 463 __traits(getMember, val, field) = readString!(wchar, ARG); 464 } 465 } 466 static if (!isStaticArray!M && is(M == dstring)) 467 { 468 cread = true; 469 static if (ARGS[i - 1] == ReadKind.Field) 470 { 471 __traits(getMember, val, field) = read!dchar(__traits(getMember, val, ARG)).to!string; 472 } 473 else static if (ARGS[i - 1] == ReadKind.Fixed) 474 { 475 __traits(getMember, val, field) = read!dchar(ARG).to!string; 476 } 477 else 478 { 479 __traits(getMember, val, field) = readString!(dchar, ARG); 480 } 481 } 482 else static if (isDynamicArray!M) 483 { 484 cread = true; 485 static if (ARGS[i - 1] == ReadKind.Field) 486 { 487 __traits(getMember, val, field) = read!(ElementType!M)(__traits(getMember, val, ARG)); 488 } 489 else static if (ARGS[i - 1] == ReadKind.Fixed) 490 { 491 __traits(getMember, val, field) = read!(ElementType!M)(ARG); 492 } 493 else 494 { 495 __traits(getMember, val, field) = read!M; 496 } 497 } 498 } 499 } 500 } 501 if (!cread) 502 __traits(getMember, val, field) = read!M; 503 } 504 return val; 505 } 506 507 /// ditto 508 T[] read(T, ARGS...)(size_t count) 509 { 510 T[] items; 511 foreach (i; 0..count) 512 items ~= read!(T, ARGS); 513 return items; 514 } 515 516 /** 517 * Reads a type from the stream using optional fields. 518 * 519 * Params: 520 * T = The type to be read from the stream. 521 * ARGS... = The arguments for optional fields. 522 * 523 * Returns: 524 * The read type read from the stream. 525 */ 526 T readPlasticized(T, ARGS...)() 527 if (ARGS.length % 3 == 0) 528 { 529 T val; 530 foreach (field; Fields!T) 531 { 532 bool cread = true; 533 static foreach (i, ARG; ARGS) 534 { 535 static if (i % 3 == 0) 536 { 537 static assert(is(typeof(ARG) == string), 538 "Field name expected, found " ~ ARG.stringof); 539 } 540 else static if (i % 3 == 1) 541 { 542 static assert(is(typeof(ARG) == string), 543 "Conditional field name expected, found " ~ ARG.stringof); 544 } 545 else 546 { 547 if (field == ARGS[i - 2] && __traits(getMember, val, ARGS[i - 1]) != ARG) 548 cread = false; 549 } 550 } 551 if (cread) 552 __traits(getMember, val, field) = read!(TypeOf!(val, field)); 553 } 554 return val; 555 } 556 }