1 /*******************************************************************************
2
3 These classes represent a simple means of reading and writing
4 discrete data types as binary values, with an option to invert
5 the endian order of numeric values.
6
7 Arrays are treated as untyped byte streams, with an optional
8 length-prefix, and should otherwise be explicitly managed at
9 the application level. We'll add additional support for arrays
10 and aggregates in future.
11
12 Copyright:
13 Copyright (c) 2007 Kris Bell.
14 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
15 All rights reserved.
16
17 License:
18 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
19 See LICENSE_TANGO.txt for details.
20
21 Version: Initial release: Oct 2007
22
23 Authors: Kris
24
25 *******************************************************************************/
26
27 module ocean.io.stream.Data;
28
29 import ocean.meta.types.Qualifiers;
30
31 import ocean.core.Verify;
32
33 import ocean.core.ByteSwap;
34
35 import ocean.io.device.Conduit;
36
37 import ocean.io.stream.Buffered;
38
39 version (unittest) import ocean.core.Test;
40
41 /*******************************************************************************
42
43 A simple way to read binary data from an arbitrary InputStream,
44 such as a file:
45 ---
46 auto input = new DataInput (new File ("path"));
47 auto x = input.int32;
48 auto y = input.float64;
49 auto l = input.read (buffer); // read raw data directly
50 auto s = cast(char[]) input.array; // read length, allocate space
51 input.close;
52 ---
53
54 *******************************************************************************/
55
56 class DataInput : InputFilter
57 {
58 public alias array get; /// Old name aliases.
59 public alias boolean getBool; /// ditto
60 public alias int8 getByte; /// ditto
61 public alias int16 getShort; /// ditto
62 public alias int32 getInt; /// ditto
63 public alias int64 getLong; /// ditto
64 public alias float32 getFloat; /// ditto
65 public alias float64 getDouble; /// ditto
66
67 /// Endian variations.
68 public enum
69 {
70 Native = 0, ///
71 Network = 1, ///
72 Big = 1, ///
73 Little = 2 ///
74 }
75
76 private bool flip;
77 protected InputStream input;
78 private Allocate allocator;
79
80 private alias void[] delegate (uint) Allocate;
81
82 /***********************************************************************
83
84 Propagate ctor to superclass.
85
86 ***********************************************************************/
87
88 this (InputStream stream)
89 {
90 super (input = BufferedInput.create (stream));
91
92 allocator = (uint bytes){return new void[bytes];};
93 }
94
95 /***********************************************************************
96
97 Set the array allocator.
98
99 ***********************************************************************/
100
101 final DataInput allocate (Allocate allocate)
102 {
103 allocator = allocate;
104 return this;
105 }
106
107 /***********************************************************************
108
109 Set current endian translation.
110
111 ***********************************************************************/
112
113 final DataInput endian (int e)
114 {
115 version (BigEndian)
116 flip = e is Little;
117 else
118 flip = e is Network;
119 return this;
120 }
121
122 /***********************************************************************
123
124 Read an array back into a user-provided workspace. The
125 space must be sufficiently large enough to house all of
126 the array, and the actual number of bytes is returned.
127
128 Note that the size of the array is written as an integer
129 prefixing the array content itself. Use read(void[]) to
130 eschew this prefix.
131
132 ***********************************************************************/
133
134 final uint array (void[] dst)
135 {
136 auto len = int32;
137 if (len > dst.length)
138 conduit.error ("DataInput.readArray :: dst array is too small");
139 eat (dst.ptr, len);
140 return len;
141 }
142
143 /***********************************************************************
144
145 Read an array back from the source, with the assumption
146 it has been written using DataOutput.put() or otherwise
147 prefixed with an integer representing the total number
148 of bytes within the array content. That's *bytes*, not
149 elements.
150
151 An array of the appropriate size is allocated either via
152 the provided delegate, or from the heap, populated and
153 returned to the caller. Casting the return value to an
154 appropriate type will adjust the number of elements as
155 required:
156 ---
157 auto text = cast(char[]) input.get;
158 ---
159
160 ***********************************************************************/
161
162 final void[] array ()
163 {
164 auto len = int32;
165 auto dst = allocator (len);
166 eat (dst.ptr, len);
167 return dst;
168 }
169
170 /***********************************************************************
171
172 ***********************************************************************/
173
174 final bool boolean ()
175 {
176 bool x;
177 eat (&x, x.sizeof);
178 return x;
179 }
180
181 /***********************************************************************
182
183 ***********************************************************************/
184
185 final byte int8 ()
186 {
187 byte x;
188 eat (&x, x.sizeof);
189 return x;
190 }
191
192 /***********************************************************************
193
194 ***********************************************************************/
195
196 final short int16 ()
197 {
198 short x;
199 eat (&x, x.sizeof);
200 if (flip)
201 ByteSwap.swap16(&x, x.sizeof);
202 return x;
203 }
204
205 /***********************************************************************
206
207 ***********************************************************************/
208
209 final int int32 ()
210 {
211 int x;
212 eat (&x, x.sizeof);
213 if (flip)
214 ByteSwap.swap32(&x, x.sizeof);
215 return x;
216 }
217
218 /***********************************************************************
219
220 ***********************************************************************/
221
222 final long int64 ()
223 {
224 long x;
225 eat (&x, x.sizeof);
226 if (flip)
227 ByteSwap.swap64(&x, x.sizeof);
228 return x;
229 }
230
231 /***********************************************************************
232
233 ***********************************************************************/
234
235 final float float32 ()
236 {
237 float x;
238 eat (&x, x.sizeof);
239 if (flip)
240 ByteSwap.swap32(&x, x.sizeof);
241 return x;
242 }
243
244 /***********************************************************************
245
246 ***********************************************************************/
247
248 final double float64 ()
249 {
250 double x;
251 eat (&x, x.sizeof);
252 if (flip)
253 ByteSwap.swap64(&x, x.sizeof);
254 return x;
255 }
256
257 /***********************************************************************
258
259 ***********************************************************************/
260
261 final override size_t read (void[] data)
262 {
263 eat (data.ptr, data.length);
264 return data.length;
265 }
266
267 /***********************************************************************
268
269 ***********************************************************************/
270
271 private final void eat (void* dst, size_t bytes)
272 {
273 while (bytes)
274 {
275 auto i = input.read (dst [0 .. bytes]);
276 if (i is Eof)
277 input.conduit.error ("DataInput :: unexpected eof while reading");
278 bytes -= i;
279 dst += i;
280 }
281 }
282 }
283
284
285 /*******************************************************************************
286
287 A simple way to write binary data to an arbitrary OutputStream,
288 such as a file:
289 ---
290 auto output = new DataOutput (new File ("path", File.WriteCreate));
291 output.int32 (1024);
292 output.float64 (3.14159);
293 output.array ("string with length prefix");
294 output.write ("raw array, no prefix");
295 output.close;
296 ---
297
298 *******************************************************************************/
299
300 class DataOutput : OutputFilter
301 {
302 public alias array put; /// Old name aliases.
303 public alias boolean putBool; /// ditto
304 public alias int8 putByte; /// ditto
305 public alias int16 putShort; /// ditto
306 public alias int32 putInt; /// ditto
307 public alias int64 putLong; /// ditto
308 public alias float32 putFloat; /// ditto
309 public alias float64 putFloat; /// ditto
310
311 /// Endian variations.
312 public enum
313 {
314 Native = 0, ///
315 Network = 1, ///
316 Big = 1, ///
317 Little = 2 ///
318 }
319
320 private bool flip;
321 private OutputStream output;
322
323 /***********************************************************************
324
325 Propagate ctor to superclass.
326
327 ***********************************************************************/
328
329 this (OutputStream stream)
330 {
331 super (output = BufferedOutput.create (stream));
332 }
333
334 /***********************************************************************
335
336 Set current endian translation.
337
338 ***********************************************************************/
339
340 final DataOutput endian (int e)
341 {
342 version (BigEndian)
343 flip = e is Little;
344 else
345 flip = e is Network;
346 return this;
347 }
348
349 /***********************************************************************
350
351 Write an array to the target stream. Note that the size
352 of the array is written as an integer prefixing the array
353 content itself. Use write(void[]) to eschew this prefix.
354
355 ***********************************************************************/
356
357 final uint array (const(void)[] src)
358 {
359 auto len = src.length;
360 int32 (cast(int) len);
361 output.write (src);
362 return cast(uint) len;
363 }
364
365 /***********************************************************************
366
367 ***********************************************************************/
368
369 final void boolean (bool x)
370 {
371 eat (&x, x.sizeof);
372 }
373
374 /***********************************************************************
375
376 ***********************************************************************/
377
378 final void int8 (byte x)
379 {
380 eat (&x, x.sizeof);
381 }
382
383 /***********************************************************************
384
385 ***********************************************************************/
386
387 final void int16 (short x)
388 {
389 if (flip)
390 ByteSwap.swap16 (&x, x.sizeof);
391 eat (&x, x.sizeof);
392 }
393
394 /***********************************************************************
395
396 ***********************************************************************/
397
398 final void int32 (int x)
399 {
400 if (flip)
401 ByteSwap.swap32 (&x, x.sizeof);
402 eat (&x, uint.sizeof);
403 }
404
405 /***********************************************************************
406
407 ***********************************************************************/
408
409 final void int64 (long x)
410 {
411 if (flip)
412 ByteSwap.swap64 (&x, x.sizeof);
413 eat (&x, x.sizeof);
414 }
415
416 /***********************************************************************
417
418 ***********************************************************************/
419
420 final void float32 (float x)
421 {
422 if (flip)
423 ByteSwap.swap32 (&x, x.sizeof);
424 eat (&x, x.sizeof);
425 }
426
427 /***********************************************************************
428
429 ***********************************************************************/
430
431 final void float64 (double x)
432 {
433 if (flip)
434 ByteSwap.swap64 (&x, x.sizeof);
435 eat (&x, x.sizeof);
436 }
437
438 /***********************************************************************
439
440 ***********************************************************************/
441
442 final override size_t write (const(void)[] data)
443 {
444 eat (data.ptr, data.length);
445 return data.length;
446 }
447
448 /***********************************************************************
449
450 ***********************************************************************/
451
452 private final void eat (const(void)* src, size_t bytes)
453 {
454 auto count = output.write (src[0..bytes]);
455 verify(count == bytes);
456 }
457 }
458
459
460 /*******************************************************************************
461
462 *******************************************************************************/
463
464 version (unittest)
465 {
466 import ocean.io.device.Array;
467 }
468
469 unittest
470 {
471 auto buf = new Array(32);
472
473 auto output = new DataOutput (buf);
474 output.array ("blah blah");
475 output.int32 (1024);
476
477 auto input = new DataInput (buf);
478 test (input.array(new char[9]) is 9);
479 test (input.int32 is 1024);
480 }