1 /// Protobuf ZigZag decoding.
2 module pbd.decode;
3 
4 import std.typecons;
5 
6 import pbd.codegen : ProtoTag, ProtoArray, pstring, protoTagOf, protoMemberOf, isZigZag, ZigZag;
7 
8 struct VarintElem
9 {
10   import std.bitmanip : bitfields;
11 
12   mixin(bitfields!(
13       byte, "i", 7,
14       bool, "cont", 1));
15 }
16 
17 /// Decodes varint and consumes given bytes.
18 T fromVarint(T)(scope ref ubyte[] encoded)
19 {
20   import std.bitmanip : BitArray;
21 
22   T val = 0;
23   T shift = 0;
24   foreach (adv, b; encoded)
25   {
26     // This pointer should be valid.
27     const ba = BitArray((&b)[0 .. 1], 8);
28     static foreach (i; 0 .. 7)
29     {
30       // Decodes bit to integer.
31       val += ba[i] << shift;
32       ++shift;
33     }
34 
35     // MSB is used as sentinel.
36     if (ba[7] == 0)
37     {
38       // Consumes bytes.
39       encoded = encoded[adv + 1 .. $];
40       // two's complement = ones' complement + 1
41       if (val < 0) val += 1;
42       return val;
43     }
44   }
45   assert(false, "no sentinel found");
46 }
47 
48 /// Reverts a given zigzag-encoded ((n << 1) ^ (n >> (T.sizeof * 8 - 1)))
49 /// unsigned value to signed.
50 T fromZigzag(T)(T n)
51 {
52   return (n >> 1) ^ (-(n & 1));
53 }
54 
55 /// Varint examples.
56 version (pbd_test)
57 pure nothrow
58 unittest
59 {
60   ubyte[] b1 = [0b0000_0001];
61   assert(fromVarint!int(b1) == 1);
62 
63   ubyte[] b128 = [0b1000_0000, 0b0000_0001];
64   assert(fromVarint!int(b128) == 128);
65 
66   ubyte[] b300 = [0b1010_1100, 0b0000_0010];
67   assert(fromVarint!int(b300) == 300);
68 
69   ubyte[] bneg1 = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01];
70   assert(-1 == fromVarint!int(bneg1));
71 
72   ubyte[] bneg1_sint = [0x01];
73   assert(-1 == fromVarint!int(bneg1_sint).fromZigzag);
74 
75   ubyte[] b1_sint = [0x02];
76   assert(1 == fromVarint!int(b1_sint).fromZigzag);
77 }
78 
79 
80 T decode(T)(ubyte[] encoded)
81 {
82   import std.range.primitives : ElementType;
83   import std.traits : isScalarType, hasUDA;
84 
85   T ret;
86   while (encoded.length > 0)
87   {
88     // TODO(karita): support sint32/64
89     // https://developers.google.com/protocol-buffers/docs/encoding#signed-integers
90     auto k = encoded[0];
91     auto tag = k / 8;
92     auto mode = k % 8;
93     // writeln("tag: ", tag, ", mode:", mode);
94 
95     encoded = encoded[1 .. $];
96     static foreach (name; __traits(allMembers, T))
97     {
98       static if (hasUDA!(__traits(getMember, ret, name), ProtoTag))
99         if(protoTagOf!(T, name) == tag)
100       {
101         // writeln(name);
102         alias member = __traits(getMember, ret, name);
103         alias Member = typeof(member);
104         switch (k % 8)
105         {
106           case 0:
107             // varint
108             // https://developers.google.com/protocol-buffers/docs/encoding#varints
109             static if (isScalarType!Member)
110             {
111               static if (isZigZag!(T, name))
112               {
113                 __traits(getMember, ret, name) = fromVarint!Member(encoded).fromZigzag;
114               }
115               else
116               {
117                 __traits(getMember, ret, name) = fromVarint!Member(encoded);
118               }
119               break;
120             }
121             else
122             {
123               assert(false, "varint was encoded for array type.");
124             }
125           case 1:
126             // 64 bit
127             break;
128           case 2:
129             // length deliminated i.e., packed
130             // https://developers.google.com/protocol-buffers/docs/encoding#packed
131             static if (is(Member : ProtoArray!T, T))
132             {
133               alias E = ElementType!Member;
134               auto numBytes = fromVarint!size_t(encoded);
135               auto len = numBytes / E.sizeof;
136               auto ptr = cast(E*) encoded.ptr;
137               // TODO(karita): make this nogc?
138               // __traits(getMember, ret, name) ~= ptr[0 .. len].dup;
139               foreach (i; 0 .. len)
140               {
141                 __traits(getMember, ret, name).insertBack(ptr[i]);
142               }
143 
144               encoded = encoded[numBytes .. $];
145               break;
146             }
147             else
148             {
149               assert(false, "length-delimited value was encoded for non-array type.");
150             }
151           case 5:
152             // 32 bit
153             break;
154           default:
155             assert(false, "unknown type in key.");
156         }
157       }
158     }
159   }
160   return ret;
161 }
162 
163 ///
164 version (pbd_test)
165 pure nothrow
166 unittest
167 {
168   struct Foo
169   {
170     @ProtoTag(1) int a;
171     @ZigZag @ProtoTag(2) int b;
172     @ProtoTag(3) pstring c;
173   }
174 
175   ubyte[] encoded = [
176       /* tag(1), varint(0) */ 0x08, /* 1 */ 0x01,
177       /* tag(2), varint(0) */ 0x10, /* 1 but -1 in zigzag */ 0x01,
178       /* tag(3), length-delim */ 0x1a, /* len(3)*/ 03, /* abc */ 0x61, 0x62, 0x63];
179   auto foo = decode!Foo(encoded);
180   assert(foo == Foo(1, -1, pstring("abc")));
181 }
182