1 /******************************************************************************* 2 * Holds the Buffer types used in created classes 3 * 4 * Authors: Matthew Soucy, msoucy@csh.rit.edu 5 * Date: Oct 5, 2013 6 * Version: 0.0.2 7 */ 8 module dproto.buffers; 9 10 import std.algorithm; 11 import std.array; 12 import std.conv; 13 import std.exception; 14 15 import dproto.serialize; 16 import dproto.exception; 17 18 /******************************************************************************* 19 * Optional buffers can be optionally not sent/received. 20 * 21 * If this type is not set, then it does not send the default value. 22 * 23 * Params: 24 * id = The numeric ID for the message 25 * TypeString = The encoding type of the data 26 * RealType = The type the data is stored as internally 27 * isDeprecated = Deprecates the accessors if true 28 * defaultValue = The default value for the internal storage 29 */ 30 struct OptionalBuffer(ulong id, string TypeString, RealType, bool isDeprecated=false, alias defaultValue=RealType.init) { 31 private { 32 alias ValueType = RealType; 33 static if(is(ValueType == enum)) { 34 alias BufferType = ENUM_SERIALIZATION; 35 } else { 36 alias BufferType = TypeString; 37 } 38 39 bool isset=false; 40 ValueType raw = defaultValue; 41 } 42 43 44 /*************************************************************************** 45 * Test the existence of a value 46 */ 47 bool exists() const @property nothrow { 48 return isset; 49 } 50 /*************************************************************************** 51 * Clears the value, marks as not set 52 */ 53 void clean() nothrow { 54 isset = false; 55 raw = defaultValue; 56 } 57 58 /*************************************************************************** 59 * Create a Buffer 60 * 61 * Params: 62 * val = The value to populate with 63 */ 64 this(ValueType val) { 65 isset = true; 66 raw = val; 67 } 68 69 static if(isDeprecated) { 70 deprecated auto opAssign(ValueType val) { 71 isset = true; 72 raw = val; 73 return this; 74 } 75 deprecated ref ValueType opGet() @property { 76 return raw; 77 } 78 } else { 79 auto opAssign(ValueType val) { 80 isset = true; 81 raw = val; 82 return this; 83 } 84 ref ValueType opGet() @property { 85 return raw; 86 } 87 } 88 alias opGet this; 89 90 /*************************************************************************** 91 * Serialize the buffer 92 * 93 * Returns: The proto-encoded data, or an empty array if the buffer is not set 94 */ 95 ubyte[] serialize() { 96 if(isset) { 97 static if(IsBuiltinType(BufferType)) { 98 return (MsgType!BufferType | (id << 3)).toVarint() ~ raw.writeProto!BufferType(); 99 } else { 100 auto tmp = raw.serialize(); 101 return (MsgType!BufferType | (id << 3)).toVarint() ~ tmp.length.toVarint() ~ tmp; 102 } 103 } else { 104 return []; 105 } 106 } 107 /*************************************************************************** 108 * Deserialize data into a buffer 109 * 110 * This marks the buffer as being set. 111 * 112 * Params: 113 * msgdata = The message's ID and type 114 * data = The data to decode 115 */ 116 void deserialize(long msgdata, ref ubyte[] data) { 117 enforce(msgdata.msgNum() == id, new DProtoException("Incorrect message number")); 118 enforce(msgdata.wireType() == MsgType!BufferType, new DProtoException("Type mismatch")); 119 static if(IsBuiltinType(BufferType)) { 120 raw = data.readProto!BufferType().to!RealType(); // Changes data by ref 121 } else { 122 raw.deserialize(data.readProto!"bytes"()); 123 } 124 isset = true; 125 } 126 127 } 128 129 /******************************************************************************* 130 * Required buffers must be both sent and received 131 * 132 * Params: 133 * id = The numeric ID for the message 134 * TypeString = The encoding type of the data 135 * RealType = The type the data is stored as internally 136 * isDeprecated = Deprecates the accessors if true 137 */ 138 struct RequiredBuffer(ulong id, string TypeString, RealType, bool isDeprecated=false) { 139 private { 140 alias ValueType = RealType; 141 static if(is(ValueType == enum)) { 142 alias BufferType = ENUM_SERIALIZATION; 143 } else { 144 alias BufferType = TypeString; 145 } 146 147 ValueType raw; 148 } 149 150 /*************************************************************************** 151 * Create a Buffer 152 * 153 * Params: 154 * val = The value to populate with 155 */ 156 this(ValueType val) { 157 raw = val; 158 } 159 160 static if(isDeprecated) { 161 deprecated ref ValueType opGet() @property { 162 return raw; 163 } 164 } else { 165 ref ValueType opGet() @property { 166 return raw; 167 } 168 } 169 alias opGet this; 170 171 172 /*************************************************************************** 173 * Serialize the buffer 174 * 175 * Returns: The proto-encoded data 176 */ 177 ubyte[] serialize() { 178 static if(IsBuiltinType(BufferType)) { 179 return (MsgType!BufferType | (id << 3)).toVarint() ~ raw.writeProto!BufferType(); 180 } else { 181 auto tmp = raw.serialize(); 182 return (MsgType!BufferType | (id << 3)).toVarint() ~ tmp.length.toVarint() ~ tmp; 183 } 184 } 185 /*************************************************************************** 186 * Deserialize data into a buffer 187 * 188 * Params: 189 * msgdata = The message's ID and type 190 * data = The data to decode 191 */ 192 void deserialize(long msgdata, ref ubyte[] data) { 193 enforce(msgdata.msgNum() == id, new DProtoException("Incorrect message number")); 194 enforce(msgdata.wireType() == MsgType!BufferType, new DProtoException("Type mismatch")); 195 static if(IsBuiltinType(BufferType)) { 196 raw = data.readProto!BufferType().to!RealType(); // Changes data by ref 197 } else { 198 raw.deserialize(data.readProto!"bytes"()); 199 } 200 } 201 202 } 203 204 /******************************************************************************* 205 * Repeated buffers can store multiple values 206 * 207 * They also support Packed data for primitives, 208 * which is a more efficient encoding method. 209 * 210 * Params: 211 * id = The numeric ID for the message 212 * TypeString = The encoding type of the data 213 * RealType = The type the data is stored as internally 214 * isDeprecated = Deprecates the accessors if true 215 * packed = The default value for the internal storage 216 */ 217 struct RepeatedBuffer(ulong id, string TypeString, RealType, bool isDeprecated=false, bool packed=false) { 218 private { 219 alias ValueType = RealType; 220 static if(is(ValueType == enum)) { 221 alias BufferType = ENUM_SERIALIZATION; 222 } else { 223 alias BufferType = TypeString; 224 } 225 226 ValueType[] raw = []; 227 } 228 229 /*************************************************************************** 230 * Clears the stored values 231 */ 232 void clean() nothrow { 233 raw.length = 0; 234 } 235 236 /*************************************************************************** 237 * Create a Buffer 238 * 239 * Params: 240 * val = The value to populate with 241 */ 242 this(inout ValueType[] val ...) inout @safe { 243 raw = val; 244 } 245 246 static if(isDeprecated) { 247 deprecated auto opAssign(ValueType[] val) { 248 raw = val; 249 return this; 250 } 251 deprecated ref ValueType[] opGet() @property { 252 return raw; 253 } 254 } else { 255 auto opAssign(ValueType[] val) { 256 raw = val; 257 return this; 258 } 259 ref ValueType[] opGet() @property { 260 return raw; 261 } 262 } 263 alias opGet this; 264 265 inout(RepeatedBuffer) save() @property inout 266 { 267 return this; 268 } 269 270 inout(RepeatedBuffer) opSlice(size_t i, size_t j) @property inout 271 { 272 return inout(RepeatedBuffer)(raw[i .. j]); 273 } 274 275 size_t length() @property const 276 { 277 return raw.length; 278 } 279 280 /*************************************************************************** 281 * Serialize the buffer 282 * 283 * If the buffer is marked as packed and the type is primitive, 284 * then it will attempt to pack the data. 285 * 286 * Returns: The proto-encoded data 287 */ 288 ubyte[] serialize() { 289 static if(packed) { 290 static if(IsBuiltinType(BufferType)) { 291 auto msg = raw.map!(writeProto!BufferType)().join(); 292 return (PACKED_MSG_TYPE | (id << 3)).toVarint() ~ msg.length.toVarint() ~ msg; 293 } else { 294 static assert(0, "Cannot have packed repeated message member"); 295 } 296 } else { 297 static if(IsBuiltinType(BufferType)) { 298 return raw.map!(a=>(MsgType!BufferType | (id << 3)).toVarint() ~ a.writeProto!BufferType())().join(); 299 } else { 300 return raw.map!((RealType a) { 301 auto msg = a.serialize(); 302 return (MsgType!BufferType | (id << 3)).toVarint() ~ msg.length.toVarint() ~ msg; 303 })().join(); 304 } 305 } 306 } 307 /*************************************************************************** 308 * Deserialize data into a buffer 309 * 310 * Received data is appended to the array. 311 * 312 * If the buffer is marked as packed, then it will attempt to parse the data 313 * as a packed buffer. Otherwise, it unpacks an individual element. 314 * 315 * Params: 316 * msgdata = The message's ID and type 317 * data = The data to decode 318 */ 319 void deserialize(long msgdata, ref ubyte[] data) { 320 enforce(msgdata.msgNum() == id, new DProtoException("Incorrect message number")); 321 static if(packed) { 322 enforce(msgdata.wireType() == PACKED_MSG_TYPE, new DProtoException("Type mismatch")); 323 static if(IsBuiltinType(BufferType)) { 324 auto myData = data.readProto!"bytes"(); 325 while(myData.length) { 326 raw ~= myData.readProto!BufferType().to!RealType(); 327 } 328 } else { 329 static assert(0, "Cannot have packed repeated message member"); 330 } 331 } else { 332 enforce(msgdata.wireType() == MsgType!BufferType, new DProtoException("Type mismatch")); 333 static if(IsBuiltinType(BufferType)) { 334 raw ~= data.readProto!BufferType().to!RealType(); // Changes data by ref 335 } else { 336 raw ~= ValueType(data.readProto!"bytes"()); 337 } 338 } 339 } 340 341 }