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()