1 module google.protobuf.decoding;
2 
3 import std.algorithm : map;
4 import std.exception : enforce;
5 import std.range : ElementType, empty, isInputRange;
6 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral, KeyType, ValueType;
7 import google.protobuf.common;
8 import google.protobuf.internal;
9 
10 T fromProtobuf(T, R)(ref R inputRange)
11 if (isInputRange!R && isBoolean!T)
12 {
13     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
14 
15     return cast(T) fromVarint(inputRange);
16 }
17 
18 unittest
19 {
20     import std.array : array;
21     import google.protobuf.encoding : toProtobuf;
22 
23     auto buffer = true.toProtobuf.array;
24     assert(buffer.fromProtobuf!bool);
25 }
26 
27 T fromProtobuf(T, Wire wire = Wire.none, R)(ref R inputRange)
28 if (isInputRange!R && isIntegral!T)
29 {
30     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
31 
32     static if (wire == Wire.none)
33     {
34         return cast(T) fromVarint(inputRange);
35     }
36     else static if (wire == Wire.fixed)
37     {
38         return inputRange.decodeFixed!T;
39     }
40     else static if (wire == Wire.zigzag)
41     {
42         return cast(T) zagZig(cast(ulong) fromVarint(inputRange));
43     }
44     else
45     {
46         assert(0, "Invalid wire encoding");
47     }
48 }
49 
50 unittest
51 {
52     import std.array : array;
53     import google.protobuf.encoding : toProtobuf;
54 
55     auto buffer = 10.toProtobuf.array;
56     assert(buffer.fromProtobuf!int == 10);
57     buffer = (-1).toProtobuf.array;
58     assert(buffer.fromProtobuf!int == -1);
59     buffer = (-1L).toProtobuf.array;
60     assert(buffer.fromProtobuf!long == -1L);
61     buffer = 0xffffffffffffffffUL.toProtobuf.array;
62     assert(buffer.fromProtobuf!long == 0xffffffffffffffffUL);
63 
64     buffer = 1.toProtobuf!(Wire.fixed).array;
65     assert(buffer.fromProtobuf!(int, Wire.fixed) == 1);
66     buffer = (-1).toProtobuf!(Wire.fixed).array;
67     assert(buffer.fromProtobuf!(int, Wire.fixed) == -1);
68     buffer = 0xffffffffU.toProtobuf!(Wire.fixed).array;
69     assert(buffer.fromProtobuf!(uint, Wire.fixed) == 0xffffffffU);
70     buffer = 1L.toProtobuf!(Wire.fixed).array;
71     assert(buffer.fromProtobuf!(long, Wire.fixed) == 1L);
72 
73     buffer = 1.toProtobuf!(Wire.zigzag).array;
74     assert(buffer.fromProtobuf!(int, Wire.zigzag) == 1);
75     buffer = (-1).toProtobuf!(Wire.zigzag).array;
76     assert(buffer.fromProtobuf!(int, Wire.zigzag) == -1);
77     buffer = 1L.toProtobuf!(Wire.zigzag).array;
78     assert(buffer.fromProtobuf!(long, Wire.zigzag) == 1L);
79     buffer = (-1L).toProtobuf!(Wire.zigzag).array;
80     assert(buffer.fromProtobuf!(long, Wire.zigzag) == -1L);
81 }
82 
83 T fromProtobuf(T, R)(ref R inputRange)
84 if (isInputRange!R && isFloatingPoint!T)
85 {
86     return inputRange.decodeFixed!T;
87 }
88 
89 unittest
90 {
91     import std.array : array;
92     import google.protobuf.encoding : toProtobuf;
93 
94     auto buffer = (0.0).toProtobuf.array;
95     assert(buffer.fromProtobuf!double == 0.0);
96     buffer = (0.0f).toProtobuf.array;
97     assert(buffer.fromProtobuf!float == 0.0f);
98 }
99 
100 T fromProtobuf(T, R)(ref R inputRange)
101 if (isInputRange!R && (is(T == string) || is(T == bytes)))
102 {
103     import std.array : array;
104 
105     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
106 
107     R fieldRange = inputRange.takeLengthPrefixed;
108 
109     return cast(T) fieldRange.array;
110 }
111 
112 unittest
113 {
114     import std.array : array;
115     import google.protobuf.encoding : toProtobuf;
116 
117     auto buffer = "abc".toProtobuf.array;
118     assert(buffer.fromProtobuf!string == "abc");
119     buffer = "".toProtobuf.array;
120     assert(buffer.fromProtobuf!string.empty);
121     buffer = (cast(bytes) [1, 2, 3]).toProtobuf.array;
122     assert(buffer.fromProtobuf!bytes == (cast(bytes) [1, 2, 3]));
123     buffer = (cast(bytes) []).toProtobuf.array;
124     assert(buffer.fromProtobuf!bytes.empty);
125 }
126 
127 T fromProtobuf(T, Wire wire = Wire.none, R)(ref R inputRange)
128 if (isInputRange!R && isArray!T && !is(T == string) && !is(T == bytes))
129 {
130     import std.array : Appender;
131 
132     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
133 
134     R fieldRange = inputRange.takeLengthPrefixed;
135 
136     Appender!T result;
137     static if (wire == Wire.none)
138     {
139         while (!fieldRange.empty)
140             result ~= fieldRange.fromProtobuf!(ElementType!T);
141     }
142     else
143     {
144         static assert(isIntegral!(ElementType!T), "Cannot specify wire format for non-integral arrays");
145 
146         while (!fieldRange.empty)
147             result ~= fieldRange.fromProtobuf!(ElementType!T, wire);
148     }
149 
150     return result.data;
151 }
152 
153 T fromProtobuf(T, R)(ref R inputRange, T result = protoDefaultValue!T)
154 if (isInputRange!R && (is(T == class) || is(T == struct)))
155 {
156     import std.format : format;
157     import std.traits : hasMember;
158 
159     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
160 
161     static if (is(T == class))
162     {
163         if (result is null)
164             result = new T;
165     }
166 
167     static if (hasMember!(T, "fromProtobuf"))
168     {
169         return result.fromProtobuf(inputRange);
170     }
171     else
172     {
173         while (!inputRange.empty)
174         {
175 
176             auto tagWire = inputRange.decodeTag;
177 
178             chooseFieldDecoder:
179             switch (tagWire.tag)
180             {
181             foreach (fieldName; Message!T.fieldNames)
182             {
183                 case protoByField!(mixin("T." ~ fieldName)).tag:
184                 {
185                     enum proto = protoByField!(mixin("T." ~ fieldName));
186                     enum wireTypeExpected = wireType!(proto, typeof(mixin("T." ~ fieldName)));
187 
188                     enforce!ProtobufException(tagWire.wireType == wireTypeExpected,
189                         "Wrong wire format '%s' of field %s, expected '%s' "
190                             .format(tagWire.wireType, T.stringof ~ "." ~ fieldName, wireTypeExpected));
191                     inputRange.fromProtobufByField!(mixin("T." ~ fieldName))(result);
192                     break chooseFieldDecoder;
193                 }
194             }
195             default:
196                 skipUnknown(inputRange, tagWire.wireType);
197                 break;
198             }
199         }
200         return result;
201     }
202 }
203 
204 unittest
205 {
206     static class Foo
207     {
208         @Proto(1) int bar;
209         @Proto(3) bool qux;
210         @Proto(2, Wire.fixed) long baz;
211     }
212 
213     ubyte[] buff = [0x08, 0x05, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01];
214     auto foo = buff.fromProtobuf!Foo;
215     assert(buff.empty);
216     assert(foo.bar == 5);
217     assert(foo.baz == 1);
218     assert(foo.qux);
219 }
220 
221 unittest
222 {
223     static class EmptyMessage
224     {
225     }
226 
227     ubyte[] buff = [];
228     auto emptyMessage = buff.fromProtobuf!EmptyMessage;
229     assert(buff.empty);
230 }
231 
232 private static void fromProtobufByField(alias field, T, R)(ref R inputRange, ref T message)
233 if (isInputRange!R)
234 {
235     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
236 
237     enum proto = protoByField!field;
238     static assert(validateProto!(proto, typeof(field)));
239 
240     fromProtobufByProto!proto(inputRange, mixin("message." ~ __traits(identifier, field)));
241     static if (isOneof!field)
242     {
243         enum oneofCase = "message." ~ oneofCaseFieldName!field;
244         enum fieldCase = "T." ~ typeof(mixin(oneofCase)).stringof ~ "." ~ oneofAccessorName!field;
245 
246         mixin(oneofCase) = mixin(fieldCase);
247     }
248 }
249 
250 private void fromProtobufByProto(Proto proto, T, R)(ref R inputRange, ref T field)
251 if (isInputRange!R && (isBoolean!T || isFloatingPoint!T || is(T == string) || is(T == bytes)))
252 {
253     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
254     static assert(validateProto!(proto, T));
255 
256     field = inputRange.fromProtobuf!T;
257 }
258 
259 private void fromProtobufByProto(Proto proto, T, R)(ref R inputRange, ref T field)
260 if (isInputRange!R && isIntegral!T)
261 {
262     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
263     static assert(validateProto!(proto, T));
264 
265     enum wire = proto.wire;
266     field = inputRange.fromProtobuf!(T, wire);
267 }
268 
269 private void fromProtobufByProto(Proto proto, T, R)(ref R inputRange, ref T field)
270 if (isInputRange!R && isArray!T && !is(T == string) && !is(T == bytes) && proto.packed)
271 {
272     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
273     static assert(validateProto!(proto, T));
274 
275     field ~= inputRange.fromProtobuf!T;
276 }
277 
278 private void fromProtobufByProto(Proto proto, T, R)(ref R inputRange, ref T field)
279 if (isInputRange!R && isArray!T && !is(T == string) && !is(T == bytes) && !proto.packed)
280 {
281     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
282     static assert(validateProto!(proto, T));
283 
284     ElementType!T newElement = protoDefaultValue!(ElementType!T);
285     inputRange.fromProtobufByProto!proto(newElement);
286     field ~= newElement;
287 }
288 
289 private void fromProtobufByProto(Proto proto, T, R)(ref R inputRange, ref T field)
290 if (isInputRange!R && isAssociativeArray!T)
291 {
292     import std.conv : to;
293 
294     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
295     static assert(validateProto!(proto, T));
296 
297     enum keyProto = Proto(MapFieldTag.key, keyWireToWire(proto.wire));
298     enum valueProto = Proto(MapFieldTag.value, valueWireToWire(proto.wire));
299     KeyType!T key;
300     ValueType!T value;
301     ubyte decodingState;
302     R fieldRange = inputRange.takeLengthPrefixed;
303 
304     while (!fieldRange.empty)
305     {
306         auto tagWire = fieldRange.decodeTag;
307 
308         switch (tagWire.tag)
309         {
310         case MapFieldTag.key:
311             enforce!ProtobufException((decodingState & 0x01) == 0, "Double map key");
312             decodingState |= 0x01;
313             enum wireTypeExpected = wireType!(keyProto, KeyType!T);
314             enforce!ProtobufException(tagWire.wireType == wireTypeExpected, "Wrong wire format");
315             static if (keyProto.wire == Wire.none)
316             {
317                 key = fieldRange.fromProtobuf!(KeyType!T);
318             }
319             else
320             {
321                 static assert(isIntegral!(KeyType!T), "Cannot specify wire format for non-integral map key");
322 
323                 enum wire = keyProto.wire;
324                 key = fieldRange.fromProtobuf!(KeyType!T, wire);
325             }
326             break;
327         case MapFieldTag.value:
328             enforce!ProtobufException((decodingState & 0x02) == 0, "Double map value");
329             decodingState |= 0x02;
330             enum wireTypeExpected = wireType!(valueProto, KeyType!T);
331             enforce!ProtobufException(tagWire.wireType == wireTypeExpected, "Wrong wire format");
332             static if (valueProto.wire == Wire.none)
333             {
334                 value = fieldRange.fromProtobuf!(ValueType!T);
335             }
336             else
337             {
338                 static assert(isIntegral!(ValueType!T), "Cannot specify wire format for non-integral map value");
339 
340                 enum wire = valueProto.wire;
341                 value = fieldRange.fromProtobuf!(ValueType!T, wire);
342             }
343             break;
344         default:
345             throw new ProtobufException("Unexpected field tag " ~ tagWire.tag.to!string ~ " while decoding a map");
346         }
347     }
348     enforce!ProtobufException((decodingState & 0x03) == 0x03, "Incomplete map element");
349     field[key] = value;
350 }
351 
352 private void fromProtobufByProto(Proto proto, T, R)(ref R inputRange, ref T field)
353 if (isInputRange!R && (is(T == class) || is(T == struct)))
354 {
355     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
356     static assert(validateProto!(proto, T));
357 
358     R fieldRange = inputRange.takeLengthPrefixed;
359 
360     field = fieldRange.fromProtobuf!T;
361 }
362 
363 void skipUnknown(R)(ref R inputRange, WireType wireType)
364 if (isInputRange!R)
365 {
366     static assert(is(ElementType!R == ubyte), "Input range should be an ubyte range");
367 
368     switch (wireType) with (WireType)
369     {
370     case varint:
371         inputRange.fromVarint;
372         break;
373     case bits64:
374         inputRange.takeN(8);
375         break;
376     case withLength:
377         inputRange.takeLengthPrefixed;
378         break;
379     case bits32:
380         inputRange.takeN(4);
381         break;
382     default:
383         enforce!ProtobufException(false, "Unknown wire format");
384         break;
385     }
386 }