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 }