/ protobuf_serialization / reader.nim
reader.nim
1 #Parses the Protobuf binary wire protocol into the specified type. 2 3 {.push raises: [], gcsafe.} 4 5 import 6 std/[typetraits, sets, tables], 7 stew/assign2, 8 stew/objects, 9 stew/shims/macros, 10 faststreams/inputs, 11 serialization, 12 "."/[codec, internal, types] 13 14 export inputs, serialization, codec, types 15 16 proc readValueInternal[T: object](stream: InputStream, value: var T, silent: bool = false) {.raises: [SerializationError, IOError].} 17 18 macro unsupported(T: typed): untyped = 19 error "Assignment of the type " & humaneTypeName(T) & " is not supported" 20 21 template requireKind(header: FieldHeader, expected: WireKind) = 22 mixin number 23 if header.kind() != expected: 24 raise (ref ProtobufValueError)( 25 msg: "Unexpected data kind " & $(header.number()) & ": " & $header.kind() & 26 ", exprected " & $expected) 27 28 proc readFieldInto[T: object and not Table]( 29 stream: InputStream, 30 value: var T, 31 header: FieldHeader, 32 ProtoType: type 33 ) {.raises: [SerializationError, IOError].} = 34 header.requireKind(WireKind.LengthDelim) 35 36 let len = stream.readLength() 37 if len > 0: 38 # TODO: https://github.com/status-im/nim-faststreams/issues/31 39 # TODO: check that all bytes were read 40 # stream.withReadableRange(len, inner): 41 # inner.readValueInternal(value) 42 43 var tmp = newSeqUninitialized[byte](len) 44 if not stream.readInto(tmp): 45 raise (ref ProtobufValueError)(msg: "not enough bytes") 46 memoryInput(tmp).readValueInternal(value) 47 48 when defined(ConformanceTest): 49 proc readFieldInto[T: enum]( 50 stream: InputStream, 51 value: var T, 52 header: FieldHeader, 53 ProtoType: type 54 ) {.raises: [SerializationError, IOError].} = 55 # TODO: This function doesn't work for proto2 edge cases. Make it work 56 when 0 notin T and T.isProto3(): 57 {.fatal: $T & " definition must contain a constant that maps to zero".} 58 header.requireKind(WireKind.Varint) 59 let enumValue = stream.readValue(ProtoType) 60 if not checkedEnumAssign(value, enumValue.int32): 61 discard checkedEnumAssign(value, 0) 62 63 proc readFieldInto[K, V]( 64 stream: InputStream, 65 value: var Table[K, V], 66 header: FieldHeader, 67 ProtoType: type 68 ) {.raises: [SerializationError, IOError].} = 69 tableObject(TableObject, K, V) 70 var tmp = default(TableObject) 71 stream.readFieldInto(tmp, header, ProtoType) 72 value[tmp.key] = tmp.value 73 74 proc readFieldInto[T: not object and not enum and (seq[byte] or not seq)]( 75 stream: InputStream, 76 value: var T, 77 header: FieldHeader, 78 ProtoType: type 79 ) {.raises: [SerializationError, IOError].} = 80 when ProtoType is SomeVarint: 81 header.requireKind(WireKind.Varint) 82 assign(value, T(stream.readValue(ProtoType))) 83 elif ProtoType is SomeFixed64: 84 header.requireKind(WireKind.Fixed64) 85 assign(value, T(stream.readValue(ProtoType))) 86 elif ProtoType is SomeLengthDelim: 87 header.requireKind(WireKind.LengthDelim) 88 assign(value, T(stream.readValue(ProtoType))) 89 elif ProtoType is SomeFixed32: 90 header.requireKind(WireKind.Fixed32) 91 assign(value, T(stream.readValue(ProtoType))) 92 else: 93 static: unsupported(ProtoType) 94 95 proc readFieldInto[T: not byte]( 96 stream: InputStream, 97 value: var seq[T], 98 header: FieldHeader, 99 ProtoType: type 100 ) {.raises: [SerializationError, IOError].} = 101 value.add(default(T)) 102 stream.readFieldInto(value[^1], header, ProtoType) 103 104 proc readFieldInto( 105 stream: InputStream, 106 value: var PBOption, 107 header: FieldHeader, 108 ProtoType: type 109 ) {.raises: [SerializationError, IOError].} = 110 stream.readFieldInto(value.mget(), header, ProtoType) 111 112 proc readFieldPackedInto[T]( 113 stream: InputStream, 114 value: var seq[T], 115 header: FieldHeader, 116 ProtoType: type 117 ) {.raises: [SerializationError, IOError].} = 118 # TODO make more efficient 119 var 120 bytes = seq[byte](stream.readValue(pbytes)) 121 inner = memoryInput(bytes) 122 while inner.readable(): 123 value.add(default(T)) 124 125 let kind = when ProtoType is SomeVarint: 126 WireKind.Varint 127 elif ProtoType is SomeFixed32: 128 WireKind.Fixed32 129 else: 130 static: doAssert ProtoType is SomeFixed64 131 WireKind.Fixed64 132 133 inner.readFieldInto(value[^1], FieldHeader.init(header.number, kind), ProtoType) 134 135 proc readValueInternal[T: object](stream: InputStream, value: var T, silent: bool = false) {.raises: [SerializationError, IOError].} = 136 const 137 isProto2: bool = T.isProto2() 138 139 when isProto2: 140 var requiredSets: HashSet[int] 141 if not silent: 142 var i: int = -1 143 enumInstanceSerializedFields(value, fieldName, fieldVar): 144 inc(i) 145 146 when T.hasCustomPragmaFixed(fieldName, required): 147 requiredSets.incl(i) 148 149 while stream.readable(): 150 let header = stream.readHeader() 151 var i = -1 152 enumInstanceSerializedFields(value, fieldName, fieldVar): 153 inc i 154 const 155 fieldNum = T.fieldNumberOf(fieldName) 156 157 if header.number() == fieldNum: 158 when isProto2: 159 if not silent: requiredSets.excl i 160 161 protoType(ProtoType, T, typeof(fieldVar), fieldName) 162 163 # TODO should we allow reading packed fields into non-repeated fields? 164 when ProtoType is SomePrimitive and fieldVar is seq and fieldVar isnot seq[byte]: 165 if header.kind() == WireKind.LengthDelim: 166 stream.readFieldPackedInto(fieldVar, header, ProtoType) 167 else: 168 stream.readFieldInto(fieldVar, header, ProtoType) 169 elif typeof(fieldVar) is ref and defined(ConformanceTest): 170 fieldVar = new typeof(fieldVar) 171 stream.readFieldInto(fieldVar[], header, ProtoType) 172 else: 173 stream.readFieldInto(fieldVar, header, ProtoType) 174 175 when isProto2: 176 if (requiredSets.len != 0): 177 raise newException( 178 ProtobufReadError, 179 "Message didn't encode a required field: " & $requiredSets) 180 181 proc readValue*[T: object](reader: ProtobufReader, value: var T) {.raises: [SerializationError, IOError].} = 182 static: verifySerializable(T) 183 184 # TODO skip length header 185 try: 186 reader.stream.readValueInternal(value) 187 finally: 188 if reader.closeAfter: 189 reader.stream.close()