1 /** 2 * Defines streams for reading and writing primitive data and arrays of 3 * primitive values. So-called "data" input and output streams are defined as 4 * decorators around an existing "base" stream, so when you read or write on 5 * a data stream, it's just performing an analogous operation on its base 6 * stream. 7 */ 8 module streams.types.data; 9 10 import streams.primitives; 11 import std.traits; 12 13 struct DataInputStream(BaseStream) if (isByteInputStream!BaseStream) { 14 private BaseStream* stream; 15 16 this(ref BaseStream stream) { 17 this.stream = &stream; 18 } 19 20 int read(ubyte[] buffer) { 21 return this.stream.read(buffer); 22 } 23 24 DataType read(DataType)() { 25 static if (isSomeString!DataType) { 26 return cast(DataType) readArray!(char[])(); 27 } else static if (isStaticArray!DataType) { 28 return readStaticArray!DataType(); 29 } else static if (isArray!DataType) { 30 return readArray!DataType(); 31 } else { 32 return readPrimitive!DataType(); 33 } 34 } 35 36 private T readPrimitive(T)() { 37 union U { T value; ubyte[T.sizeof] bytes; } 38 U u; 39 int bytesRead = this.stream.read(u.bytes[]); 40 if (bytesRead != T.sizeof) { 41 import std.format; 42 throw new StreamException(format! 43 "Failed to read value of type %s (%d bytes) from range. Read %d bytes instead." 44 (T.stringof, T.sizeof, bytesRead) 45 ); 46 } 47 return u.value; 48 } 49 50 private T readArray(T)() if (isArray!T) { 51 uint size; 52 try { 53 size = readPrimitive!uint(); 54 } catch (StreamException e) { 55 throw new StreamException("Failed to read array size uint.", e); 56 } 57 alias ElementType = typeof(*T.init.ptr); 58 T tempData = new ElementType[size]; 59 for (uint i = 0; i < size; i++) { 60 try { 61 tempData[i] = readPrimitive!ElementType(); 62 } catch (StreamException e) { 63 import std.format; 64 throw new StreamException(format! 65 "Failed to read array element %d of %d, of type %s." 66 (i + 1, size, ElementType.stringof), 67 e 68 ); 69 } 70 } 71 return tempData; 72 } 73 74 private T readStaticArray(T)() if (isStaticArray!T) { 75 alias ElementType = typeof(*T.init.ptr); 76 T data; 77 for (uint i = 0; i < T.length; i++) { 78 try { 79 data[i] = readPrimitive!ElementType(); 80 } catch (StreamException e) { 81 import std.format; 82 throw new StreamException(format! 83 "Failed to read static array element %d of %d, of type %s." 84 (i + 1, T.length, ElementType.stringof), 85 e 86 ); 87 } 88 } 89 return data; 90 } 91 } 92 93 /** 94 * Creates and returns a data input stream that's wrapped around the given 95 * byte input stream. 96 * Params: 97 * stream = The stream to wrap in a data input stream. 98 * Returns: The data input stream. 99 */ 100 DataInputStream!S dataInputStreamFor(S)( 101 ref S stream 102 ) if (isByteInputStream!S) { 103 return DataInputStream!S(stream); 104 } 105 106 struct DataOutputStream(BaseStream) if (isByteOutputStream!BaseStream) { 107 private BaseStream* stream; 108 109 this(ref BaseStream stream) { 110 this.stream = &stream; 111 } 112 113 int write(ubyte[] buffer) { 114 return this.stream.write(buffer); 115 } 116 117 void write(T)(T value) { 118 static if (isSomeString!T) { 119 writeArray!(char[])(cast(char[]) value); 120 } else static if (isStaticArray!T) { 121 writeStaticArray!T(value); 122 } else static if (isArray!T) { 123 writeArray!T(value); 124 } else { 125 writePrimitive!T(value); 126 } 127 } 128 129 private void writePrimitive(T)(T value) { 130 union U { T value; ubyte[T.sizeof] bytes; } 131 U u; 132 u.value = value; 133 int bytesWritten = this.stream.write(u.bytes[]); 134 if (bytesWritten != T.sizeof) { 135 import std.format; 136 throw new StreamException(format! 137 "Failed to write value of type %s (%d bytes, value = %s) to range. Wrote %d bytes instead." 138 (T.stringof, T.sizeof, value, bytesWritten) 139 ); 140 } 141 } 142 143 private void writeArray(T)(T value) if (isArray!T) { 144 try { 145 writePrimitive!uint(cast(uint) value.length); 146 } catch (StreamException e) { 147 import std.format; 148 throw new StreamException("Failed to write array size uint."); 149 } 150 if (value.length == 0) return; 151 alias ElementType = typeof(value[0]); 152 for (uint i = 0; i < value.length; i++) { 153 try { 154 writePrimitive!ElementType(value[i]); 155 } catch (StreamException e) { 156 import std.format; 157 throw new StreamException(format! 158 "Failed to write array element %d of %d, of type %s." 159 (i + 1, value.length, ElementType.stringof), 160 e 161 ); 162 } 163 } 164 } 165 166 private void writeStaticArray(T)(T value) if (isStaticArray!T) { 167 static if (T.length == 0) return; 168 alias ElementType = typeof(*T.init.ptr); 169 for (uint i = 0; i < T.length; i++) { 170 try { 171 writePrimitive!ElementType(value[i]); 172 } catch (StreamException e) { 173 import std.format; 174 throw new StreamException(format! 175 "Failed to write static array element %d of %d, of type %s." 176 (i + 1, T.length, ElementType.stringof), 177 e 178 ); 179 } 180 } 181 } 182 } 183 184 /** 185 * Creates and returns a data output stream that's wrapped around the given 186 * byte output stream. 187 * Params: 188 * stream = The stream to wrap in a data output stream. 189 * Returns: The data output stream. 190 */ 191 DataOutputStream!S dataOutputStreamFor(S)( 192 ref S stream 193 ) if (isByteOutputStream!S) { 194 return DataOutputStream!S(stream); 195 } 196 197 unittest { 198 import streams.types.array; 199 200 auto sOut = ArrayOutputStream!ubyte(); 201 auto dataOut = dataOutputStreamFor(sOut); 202 dataOut.write(42); 203 dataOut.write(true); 204 dataOut.write("Hello"); 205 ubyte[] data = sOut.toArray(); 206 auto sIn = inputStreamFor(data); 207 auto dataIn = dataInputStreamFor(sIn); 208 assert(dataIn.read!int == 42); 209 assert(dataIn.read!bool == true); 210 assert(dataIn.read!string == "Hello"); 211 }