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 }