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