• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <span>
17 #include <string_view>
18 
19 #include "pw_protobuf/wire_format.h"
20 #include "pw_status/status.h"
21 #include "pw_varint/varint.h"
22 
23 // This file defines a low-level event-based protobuf wire format decoder.
24 // The decoder processes an encoded message by iterating over its fields. The
25 // caller can extract the values of any fields it cares about.
26 //
27 // The decoder does not provide any in-memory data structures to represent a
28 // protobuf message's data. More sophisticated APIs can be built on top of the
29 // low-level decoder to provide additional functionality, if desired.
30 //
31 // Example usage:
32 //
33 //   Decoder decoder(proto);
34 //   while (decoder.Next().ok()) {
35 //     switch (decoder.FieldNumber()) {
36 //       case 1:
37 //         decoder.ReadUint32(&my_uint32);
38 //         break;
39 //       // ... and other fields.
40 //     }
41 //   }
42 //
43 namespace pw::protobuf {
44 
45 // TODO(frolv): Rename this to MemoryDecoder to match the encoder naming.
46 class Decoder {
47  public:
Decoder(std::span<const std::byte> proto)48   constexpr Decoder(std::span<const std::byte> proto)
49       : proto_(proto), previous_field_consumed_(true) {}
50 
51   Decoder(const Decoder& other) = delete;
52   Decoder& operator=(const Decoder& other) = delete;
53 
54   // Advances to the next field in the proto.
55   //
56   // If Next() returns OK, there is guaranteed to be a valid protobuf field at
57   // the current cursor position.
58   //
59   // Return values:
60   //
61   //             OK: Advanced to a valid proto field.
62   //   OUT_OF_RANGE: Reached the end of the proto message.
63   //      DATA_LOSS: Invalid protobuf data.
64   //
65   Status Next();
66 
67   // Returns the field number of the field at the current cursor position.
68   //
69   // A return value of 0 indicates that the field number is invalid. An invalid
70   // field number terminates the decode operation; any subsequent calls to
71   // Next() or Read*() will return DATA_LOSS.
72   //
73   // TODO(frolv): This should be refactored to return a Result<uint32_t>.
74   uint32_t FieldNumber() const;
75 
76   // Reads a proto int32 value from the current cursor.
ReadInt32(int32_t * out)77   Status ReadInt32(int32_t* out) {
78     return ReadUint32(reinterpret_cast<uint32_t*>(out));
79   }
80 
81   // Reads a proto uint32 value from the current cursor.
82   Status ReadUint32(uint32_t* out);
83 
84   // Reads a proto int64 value from the current cursor.
ReadInt64(int64_t * out)85   Status ReadInt64(int64_t* out) {
86     return ReadVarint(reinterpret_cast<uint64_t*>(out));
87   }
88 
89   // Reads a proto uint64 value from the current cursor.
ReadUint64(uint64_t * out)90   Status ReadUint64(uint64_t* out) { return ReadVarint(out); }
91 
92   // Reads a proto sint32 value from the current cursor.
93   Status ReadSint32(int32_t* out);
94 
95   // Reads a proto sint64 value from the current cursor.
96   Status ReadSint64(int64_t* out);
97 
98   // Reads a proto bool value from the current cursor.
99   Status ReadBool(bool* out);
100 
101   // Reads a proto fixed32 value from the current cursor.
ReadFixed32(uint32_t * out)102   Status ReadFixed32(uint32_t* out) { return ReadFixed(out); }
103 
104   // Reads a proto fixed64 value from the current cursor.
ReadFixed64(uint64_t * out)105   Status ReadFixed64(uint64_t* out) { return ReadFixed(out); }
106 
107   // Reads a proto sfixed32 value from the current cursor.
ReadSfixed32(int32_t * out)108   Status ReadSfixed32(int32_t* out) {
109     return ReadFixed32(reinterpret_cast<uint32_t*>(out));
110   }
111 
112   // Reads a proto sfixed64 value from the current cursor.
ReadSfixed64(int64_t * out)113   Status ReadSfixed64(int64_t* out) {
114     return ReadFixed64(reinterpret_cast<uint64_t*>(out));
115   }
116 
117   // Reads a proto float value from the current cursor.
ReadFloat(float * out)118   Status ReadFloat(float* out) {
119     static_assert(sizeof(float) == sizeof(uint32_t),
120                   "Float and uint32_t must be the same size for protobufs");
121     return ReadFixed(out);
122   }
123 
124   // Reads a proto double value from the current cursor.
ReadDouble(double * out)125   Status ReadDouble(double* out) {
126     static_assert(sizeof(double) == sizeof(uint64_t),
127                   "Double and uint64_t must be the same size for protobufs");
128     return ReadFixed(out);
129   }
130 
131   // Reads a proto string value from the current cursor and returns a view of it
132   // in `out`. The raw protobuf data must outlive `out`. If the string field is
133   // invalid, `out` is not modified.
134   Status ReadString(std::string_view* out);
135 
136   // Reads a proto bytes value from the current cursor and returns a view of it
137   // in `out`. The raw protobuf data must outlive the `out` std::span. If the
138   // bytes field is invalid, `out` is not modified.
ReadBytes(std::span<const std::byte> * out)139   Status ReadBytes(std::span<const std::byte>* out) {
140     return ReadDelimited(out);
141   }
142 
143   // Resets the decoder to start reading a new proto message.
Reset(std::span<const std::byte> proto)144   void Reset(std::span<const std::byte> proto) {
145     proto_ = proto;
146     previous_field_consumed_ = true;
147   }
148 
149  private:
150   // Advances the cursor to the next field in the proto.
151   Status SkipField();
152 
153   // Returns the size of the current field, or 0 if the field is invalid.
154   size_t FieldSize() const;
155 
156   Status ConsumeKey(WireType expected_type);
157 
158   // Reads a varint key-value pair from the current cursor position.
159   Status ReadVarint(uint64_t* out);
160 
161   // Reads a fixed-size key-value pair from the current cursor position.
162   Status ReadFixed(std::byte* out, size_t size);
163 
164   template <typename T>
ReadFixed(T * out)165   Status ReadFixed(T* out) {
166     static_assert(
167         sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
168         "Protobuf fixed-size fields must be 32- or 64-bit");
169     return ReadFixed(reinterpret_cast<std::byte*>(out), sizeof(T));
170   }
171 
172   Status ReadDelimited(std::span<const std::byte>* out);
173 
174   std::span<const std::byte> proto_;
175   bool previous_field_consumed_;
176 };
177 
178 class DecodeHandler;
179 
180 // A protobuf decoder that iterates over an encoded protobuf, calling a handler
181 // for each field it encounters.
182 //
183 // Example usage:
184 //
185 //   class FooProtoHandler : public DecodeHandler {
186 //    public:
187 //     Status ProcessField(CallbackDecoder& decoder,
188 //                         uint32_t field_number) override {
189 //       switch (field_number) {
190 //         case FooFields::kBar:
191 //           if (!decoder.ReadSint32(&bar).ok()) {
192 //             bar = 0;
193 //           }
194 //           break;
195 //         case FooFields::kBaz:
196 //           if (!decoder.ReadUint32(&baz).ok()) {
197 //             baz = 0;
198 //           }
199 //           break;
200 //       }
201 //
202 //       return OkStatus();
203 //     }
204 //
205 //     int bar;
206 //     unsigned int baz;
207 //   };
208 //
209 //   void DecodeFooProto(std::span<std::byte> raw_proto) {
210 //     Decoder decoder;
211 //     FooProtoHandler handler;
212 //
213 //     decoder.set_handler(&handler);
214 //     if (!decoder.Decode(raw_proto).ok()) {
215 //       LOG_FATAL("Invalid foo message!");
216 //     }
217 //
218 //     LOG_INFO("Read Foo proto message; bar: %d baz: %u",
219 //              handler.bar, handler.baz);
220 //   }
221 //
222 class CallbackDecoder {
223  public:
CallbackDecoder()224   constexpr CallbackDecoder()
225       : decoder_({}), handler_(nullptr), state_(kReady) {}
226 
227   CallbackDecoder(const CallbackDecoder& other) = delete;
228   CallbackDecoder& operator=(const CallbackDecoder& other) = delete;
229 
set_handler(DecodeHandler * handler)230   void set_handler(DecodeHandler* handler) { handler_ = handler; }
231 
232   // Decodes the specified protobuf data. The registered handler's ProcessField
233   // function is called on each field found in the data.
234   Status Decode(std::span<const std::byte> proto);
235 
236   // Reads a proto int32 value from the current cursor.
ReadInt32(int32_t * out)237   Status ReadInt32(int32_t* out) { return decoder_.ReadInt32(out); }
238 
239   // Reads a proto uint32 value from the current cursor.
ReadUint32(uint32_t * out)240   Status ReadUint32(uint32_t* out) { return decoder_.ReadUint32(out); }
241 
242   // Reads a proto int64 value from the current cursor.
ReadInt64(int64_t * out)243   Status ReadInt64(int64_t* out) { return decoder_.ReadInt64(out); }
244 
245   // Reads a proto uint64 value from the current cursor.
ReadUint64(uint64_t * out)246   Status ReadUint64(uint64_t* out) { return decoder_.ReadUint64(out); }
247 
248   // Reads a proto sint64 value from the current cursor.
ReadSint32(int32_t * out)249   Status ReadSint32(int32_t* out) { return decoder_.ReadSint32(out); }
250 
251   // Reads a proto sint64 value from the current cursor.
ReadSint64(int64_t * out)252   Status ReadSint64(int64_t* out) { return decoder_.ReadSint64(out); }
253 
254   // Reads a proto bool value from the current cursor.
ReadBool(bool * out)255   Status ReadBool(bool* out) { return decoder_.ReadBool(out); }
256 
257   // Reads a proto fixed32 value from the current cursor.
ReadFixed32(uint32_t * out)258   Status ReadFixed32(uint32_t* out) { return decoder_.ReadFixed32(out); }
259 
260   // Reads a proto fixed64 value from the current cursor.
ReadFixed64(uint64_t * out)261   Status ReadFixed64(uint64_t* out) { return decoder_.ReadFixed64(out); }
262 
263   // Reads a proto sfixed32 value from the current cursor.
ReadSfixed32(int32_t * out)264   Status ReadSfixed32(int32_t* out) { return decoder_.ReadSfixed32(out); }
265 
266   // Reads a proto sfixed64 value from the current cursor.
ReadSfixed64(int64_t * out)267   Status ReadSfixed64(int64_t* out) { return decoder_.ReadSfixed64(out); }
268 
269   // Reads a proto float value from the current cursor.
ReadFloat(float * out)270   Status ReadFloat(float* out) { return decoder_.ReadFloat(out); }
271 
272   // Reads a proto double value from the current cursor.
ReadDouble(double * out)273   Status ReadDouble(double* out) { return decoder_.ReadDouble(out); }
274 
275   // Reads a proto string value from the current cursor and returns a view of it
276   // in `out`. The raw protobuf data must outlive `out`. If the string field is
277   // invalid, `out` is not modified.
ReadString(std::string_view * out)278   Status ReadString(std::string_view* out) { return decoder_.ReadString(out); }
279 
280   // Reads a proto bytes value from the current cursor and returns a view of it
281   // in `out`. The raw protobuf data must outlive the `out` std::span. If the
282   // bytes field is invalid, `out` is not modified.
ReadBytes(std::span<const std::byte> * out)283   Status ReadBytes(std::span<const std::byte>* out) {
284     return decoder_.ReadBytes(out);
285   }
286 
cancelled()287   bool cancelled() const { return state_ == kDecodeCancelled; };
288 
289  private:
290   enum State {
291     kReady,
292     kDecodeInProgress,
293     kDecodeCancelled,
294     kDecodeFailed,
295   };
296 
297   Decoder decoder_;
298   DecodeHandler* handler_;
299 
300   State state_;
301 };
302 
303 // The event-handling interface implemented for a proto callback decoding
304 // operation.
305 class DecodeHandler {
306  public:
307   virtual ~DecodeHandler() = default;
308 
309   // Callback called for each field encountered in the decoded proto message.
310   // Receives a pointer to the decoder object, allowing the handler to call
311   // the appropriate method to extract the field's data.
312   //
313   // If the status returned is not OkStatus(), the decode operation is exited
314   // with the provided status. Returning Status::Cancelled() allows a convenient
315   // way of stopping a decode early (for example, if a desired field is found).
316   virtual Status ProcessField(CallbackDecoder& decoder,
317                               uint32_t field_number) = 0;
318 };
319 
320 }  // namespace pw::protobuf
321