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 }