1 /**
2 
3     Binary parsing and emitting from input ranges, or to
4     output ranges.
5 
6     Copyright: Guillaume Piolat 2015-2024.
7     License:   http://www.boost.org/LICENSE_1_0.txt
8 
9     It is used internally by Dplug, and also by the user for
10     emitting and parsing plugin state chunks.
11 
12     See_also: `futureBinState`.
13 
14  */
15 module dplug.core.binrange;
16 
17 // FUTURE: Monomorphism. only allow reading from [] and
18 // writing on Vec!ubyte
19 import std.range.primitives;
20 
21 import dplug.core.nogc;
22 import dplug.core.traits;
23 
24 @nogc nothrow:
25 
26 public:
27 
28 /* Public API in this file here:
29 
30   - popLE      Read  a little-endian integer (or FP number)
31   - popBE      Read  a    big-endian integer (or FP number)
32   - writeLE    Write a little-endian integer (or FP number)
33   - writeBE    Write a    big-endian integer (or FP number)
34   - skipBytes  Skip a number of bytes in input stream.
35   - readRIFFChunkHeader
36   - writeRIFFChunkHeader
37   - RIFFChunkId FourCC constant helper.
38 
39   Those functions operate on slices of `ubyte` for input,
40   and output ranges of `ubyte` for output only.
41 
42 */
43 
44 /**
45     Skip bytes while parsing an input slice.
46 
47     Params:
48        input    = `ubyte` input range.
49        numBytes = Number of bytes to skip.
50        err      = `true` if read error.
51 
52     Returns: On success, `*err` is set to `false`.
53              On failure, `*err` is set to `true`, and the
54              input range cannot be used anymore.
55 */
56 void skipBytes(ref const(ubyte)[] input,
57                int numBytes,
58                bool* err)
59 {
60     for (int i = 0; i < numBytes; ++i)
61     {
62         popUbyte(input, err);
63         if (*err)
64             return;
65     }
66     *err = false;
67 }
68 
69 /**
70     Reads a base type from input slice.
71     `popLE` parses bytes in little-endian order.
72     `popBE` parses bytes in big-endian order.
73 
74     Params:
75        input    = `ubyte` input range.
76        err      = `true` if read error.
77 
78     Supported types:
79         `byte`, `ubyte`, `short`, `ushort`,
80          `int`,  `uint`,  `long`,  `ulong`,
81          `float`, `double`
82 
83     Returns: On failure, `*err` is set to `true`, return 0.
84           The input range cannot be used anymore.
85 */
86 T popBE(T)(ref const(ubyte)[] input, bool* err)
87 {
88     return popFunction!(T, false)(input, err);
89 }
90 ///ditto
91 T popLE(T)(ref const(ubyte)[] input, bool* err)
92 {
93     return popFunction!(T, true)(input, err);
94 }
95 
96 /**
97     Writes a big-endian/little-endian base type to output
98     range.
99 
100     Params:
101        output   = `ubyte` output range.
102        n        = A base type to write.
103 
104     Supported types:
105         `byte`, `ubyte`, `short`, `ushort`,
106          `int`,  `uint`,  `long`,  `ulong`,
107          `float`, `double`
108 
109     Warning: Doesn't report write errors.
110 */
111 void writeBE(T, R)(ref R output, T n)
112     if (isOutputRange!(R, ubyte))
113 {
114     writeFunction!(T, R, false)(output, n);
115 }
116 ///ditto
117 void writeLE(T, R)(ref R output, T n)
118     if (isOutputRange!(R, ubyte))
119 {
120     writeFunction!(T, R, true)(output, n);
121 }
122 
123 /**
124     Reads a [RIFF] chunk header from an input range.
125 
126     Params:
127        input     = `ubyte` input range.
128        chunkId   = RIFF chunk id.
129        chunkSize = Chunk size.
130        err       = `true` if input error.
131 
132     On failure, `*err` is set to `true`
133     and `chunkId` and `chunkSize` are undefined.
134     The input range cannot be used anymore.
135     [RIFF]: http://www.daubnet.com/en/file-format-riff
136 */
137 void readRIFFChunkHeader(ref const(ubyte)[] input,
138                          out uint chunkId,
139                          out uint chunkSize,
140                          bool* err)
141 {
142     chunkId = popBE!uint(input, err);
143     if (*err)
144         return;
145     chunkSize = popLE!uint(input, err);
146     if (*err)
147         return;
148     *err = false;
149 }
150 
151 /**
152     Reads a [RIFF] chunk header to an output range.
153 
154     Params:
155        output    = `ubyte` output range.
156        chunkId   = RIFF chunk id.
157        chunkSize = Chunk size.    
158 
159     [RIFF]: http://www.daubnet.com/en/file-format-riff
160 */
161 void writeRIFFChunkHeader(R)(ref R output,
162                              uint chunkId,
163                              uint chunkSize)
164     if (isOutputRange!(R, ubyte))
165 {
166     writeBE!uint(output, chunkId);
167     writeLE!uint(output, chunkSize);
168 }
169 
170 /**
171     A RIFF chunk id. Also called "FourCC".
172 
173     [RIFF]: http://www.daubnet.com/en/file-format-riff
174 */
175 template RIFFChunkId(string id)
176 {
177     static assert(id.length == 4);
178     enum uint RIFFChunkId = (cast(ubyte)(id[0]) << 24)
179                           | (cast(ubyte)(id[1]) << 16)
180                           | (cast(ubyte)(id[2]) <<  8)
181                           | (cast(ubyte)(id[3])      );
182 }
183 
184 private:
185 
186 // read/write 64-bits float
187 union float_uint
188 {
189     float f;
190     uint i;
191 }
192 
193 // read/write 64-bits float
194 union double_ulong
195 {
196     double f;
197     ulong i;
198 }
199 
200 uint float2uint(float x) pure
201 {
202     float_uint fi;
203     fi.f = x;
204     return fi.i;
205 }
206 
207 float uint2float(int x) pure
208 {
209     float_uint fi;
210     fi.i = x;
211     return fi.f;
212 }
213 
214 ulong double2ulong(double x) pure
215 {
216     double_ulong fi;
217     fi.f = x;
218     return fi.i;
219 }
220 
221 double ulong2double(ulong x) pure
222 {
223     double_ulong fi;
224     fi.i = x;
225     return fi.f;
226 }
227 
228 private template IntegerLargerThan(int numBytes)
229     if (numBytes >= 1 && numBytes <= 8)
230 {
231     static if (numBytes == 1)
232         alias IntegerLargerThan = ubyte;
233     else static if (numBytes == 2)
234         alias IntegerLargerThan = ushort;
235     else static if (numBytes <= 4)
236         alias IntegerLargerThan = uint;
237     else
238         alias IntegerLargerThan = ulong;
239 }
240 
241 ubyte popUbyte(ref const(ubyte)[] input, bool* err)
242 {
243     if (input.length == 0)
244     {
245         *err = true;
246         return 0;
247     }
248     ubyte b = input[0];
249     input = input[1..$];
250     return b;
251 }
252 
253 auto popInteger(int NumBytes,
254                 bool WantSigned,
255                 bool LittleEndian)
256                (ref const(ubyte)[] input, bool* err)
257 {
258     alias T = IntegerLargerThan!NumBytes;
259 
260     T result = 0;
261 
262     static if (LittleEndian)
263     {
264         for (int i = 0; i < NumBytes; ++i)
265         {
266             ubyte b = popUbyte(input, err);
267             if (*err)
268                 return 0;
269             result |= ( cast(T)(b) << (8 * i) );
270         }
271     }
272     else
273     {
274         for (int i = 0; i < NumBytes; ++i)
275         {
276             ubyte b = popUbyte(input, err);
277             if (*err)
278                 return 0;
279             result = cast(T)( (result << 8) | b );
280         }
281     }
282 
283     *err = false;
284 
285     static if (WantSigned)
286         return cast(UnsignedToSigned!T)result;
287     else
288         return result;
289 }
290 
291 void writeInteger(R, int NumBytes, bool LittleEndian)
292      (ref R output, IntegerLargerThan!NumBytes n)
293      if (isOutputRange!(R, ubyte))
294 {
295     alias T = IntegerLargerThan!NumBytes;
296 
297     static assert(isUnsignedIntegral!T);
298     auto u = cast(T)n;
299 
300     static if (LittleEndian)
301     {
302         for (int i = 0; i < NumBytes; ++i)
303         {
304             ubyte b = (u >> (i * 8)) & 255;
305             output.put(b);
306         }
307     }
308     else
309     {
310         for (int i = 0; i < NumBytes; ++i)
311         {
312             ubyte b = (u >> ((NumBytes-1-i)*8)) & 255;
313             output.put(b);
314         }
315     }
316 }
317 
318 void writeFunction(T, R, bool endian)(ref R o, T n)
319     if (isOutputRange!(R, ubyte))
320 {
321     static if (isBuiltinIntegral!T)
322         writeInteger!(R, T.sizeof, endian)(o, n);
323     else static if (is(T : float))
324         writeInteger!(R, 4, endian)(o, float2uint(n));
325     else static if (is(T : double))
326         writeInteger!(R, 8, endian)(o, double2ulong(n));
327     else
328         static assert(false);
329 }
330 
331 T popFunction(T, bool endian)
332     (ref const(ubyte)[] i, bool* err)
333 {
334     static if(isBuiltinIntegral!T)
335     {
336         enum Signed = isSignedIntegral!T;
337         alias F = popInteger!(T.sizeof, Signed, endian);
338         return cast(T) F(i, err);
339     }
340     else static if (is(T == float))
341     {
342         alias F = popInteger!(float.sizeof, false, endian);
343         return uint2float(F(i, err));
344     }
345     else static if (is(T == double))
346     {
347         alias F = popInteger!(double.sizeof, false, endian);
348         return ulong2double(F(i, err));
349     }
350     else
351         static assert(false);
352 }
353 
354 unittest
355 {
356     static immutable ubyte[8] ARR =
357             [ 0x00, 0x01, 0x02, 0x03 ,
358               0x00, 0x01, 0x02, 0x03 ];
359 
360     // test 32-bit integer parsing
361     {
362         const(ubyte)[] arr = ARR[];
363         bool err;
364         assert(popLE!uint(arr, &err) == 0x03020100);
365         assert(!err);
366 
367         assert(popBE!int(arr, &err) == 0x00010203);
368         assert(!err);
369     }
370 
371     // test 64-bit integer parsing
372     {
373         bool err;
374         const(ubyte)[] arr = ARR[];
375         assert(popLE!ulong(arr, &err)==0x03020100_03020100);
376         assert(!err);
377     }
378     {
379         bool err;
380         const(ubyte)[] arr = ARR[];
381         assert(popBE!long(arr, &err)==0x00010203_00010203);
382         assert(!err);
383     }
384 
385     import dplug.core.vec;
386     auto app = makeVec!ubyte();
387     writeBE!float(app, 1.0f);
388     writeLE!double(app, 2.0);
389 }
390 
391 
392 unittest
393 {
394     static immutable ubyte[8] ARR1 =
395             [ 0, 0, 0, 0, 0, 0, 0xe0, 0x3f ];
396 
397     static immutable ubyte[8] ARR2 =
398             [ 0, 0, 0, 0, 0, 0, 0xe0, 0xbf ];
399 
400     bool err;
401     const(ubyte)[] arr = ARR1[];
402     double r = popLE!double(arr, &err);
403     assert(!err);
404     assert(r == 0.5);
405 
406     arr = ARR2[];
407     r = popLE!double(arr, &err);
408     assert(!err);
409     assert(r == -0.5);
410 }