• 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 <string_view>
17 
18 #include "pw_protobuf/wire_format.h"
19 #include "pw_span/span.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(span<const std::byte> proto)48   constexpr Decoder(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` span. If the
138   // bytes field is invalid, `out` is not modified.
ReadBytes(span<const std::byte> * out)139   Status ReadBytes(span<const std::byte>* out) { return ReadDelimited(out); }
140 
141   // Resets the decoder to start reading a new proto message.
Reset(span<const std::byte> proto)142   void Reset(span<const std::byte> proto) {
143     proto_ = proto;
144     previous_field_consumed_ = true;
145   }
146 
147  private:
148   // Advances the cursor to the next field in the proto.
149   Status SkipField();
150 
151   // Returns the size of the current field, or 0 if the field is invalid.
152   size_t FieldSize() const;
153 
154   Status ConsumeKey(WireType expected_type);
155 
156   // Reads a varint key-value pair from the current cursor position.
157   Status ReadVarint(uint64_t* out);
158 
159   // Reads a fixed-size key-value pair from the current cursor position.
160   Status ReadFixed(std::byte* out, size_t size);
161 
162   template <typename T>
ReadFixed(T * out)163   Status ReadFixed(T* out) {
164     static_assert(
165         sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
166         "Protobuf fixed-size fields must be 32- or 64-bit");
167     return ReadFixed(reinterpret_cast<std::byte*>(out), sizeof(T));
168   }
169 
170   Status ReadDelimited(span<const std::byte>* out);
171 
172   span<const std::byte> proto_;
173   bool previous_field_consumed_;
174 };
175 
176 class DecodeHandler;
177 
178 // A protobuf decoder that iterates over an encoded protobuf, calling a handler
179 // for each field it encounters.
180 //
181 // Example usage:
182 //
183 //   class FooProtoHandler : public DecodeHandler {
184 //    public:
185 //     Status ProcessField(CallbackDecoder& decoder,
186 //                         uint32_t field_number) override {
187 //       switch (field_number) {
188 //         case FooFields::kBar:
189 //           if (!decoder.ReadSint32(&bar).ok()) {
190 //             bar = 0;
191 //           }
192 //           break;
193 //         case FooFields::kBaz:
194 //           if (!decoder.ReadUint32(&baz).ok()) {
195 //             baz = 0;
196 //           }
197 //           break;
198 //       }
199 //
200 //       return OkStatus();
201 //     }
202 //
203 //     int bar;
204 //     unsigned int baz;
205 //   };
206 //
207 //   void DecodeFooProto(span<std::byte> raw_proto) {
208 //     Decoder decoder;
209 //     FooProtoHandler handler;
210 //
211 //     decoder.set_handler(&handler);
212 //     if (!decoder.Decode(raw_proto).ok()) {
213 //       LOG_FATAL("Invalid foo message!");
214 //     }
215 //
216 //     LOG_INFO("Read Foo proto message; bar: %d baz: %u",
217 //              handler.bar, handler.baz);
218 //   }
219 //
220 class CallbackDecoder {
221  public:
CallbackDecoder()222   constexpr CallbackDecoder()
223       : decoder_({}), handler_(nullptr), state_(kReady) {}
224 
225   CallbackDecoder(const CallbackDecoder& other) = delete;
226   CallbackDecoder& operator=(const CallbackDecoder& other) = delete;
227 
set_handler(DecodeHandler * handler)228   void set_handler(DecodeHandler* handler) { handler_ = handler; }
229 
230   // Decodes the specified protobuf data. The registered handler's ProcessField
231   // function is called on each field found in the data.
232   Status Decode(span<const std::byte> proto);
233 
234   // Reads a proto int32 value from the current cursor.
ReadInt32(int32_t * out)235   Status ReadInt32(int32_t* out) { return decoder_.ReadInt32(out); }
236 
237   // Reads a proto uint32 value from the current cursor.
ReadUint32(uint32_t * out)238   Status ReadUint32(uint32_t* out) { return decoder_.ReadUint32(out); }
239 
240   // Reads a proto int64 value from the current cursor.
ReadInt64(int64_t * out)241   Status ReadInt64(int64_t* out) { return decoder_.ReadInt64(out); }
242 
243   // Reads a proto uint64 value from the current cursor.
ReadUint64(uint64_t * out)244   Status ReadUint64(uint64_t* out) { return decoder_.ReadUint64(out); }
245 
246   // Reads a proto sint64 value from the current cursor.
ReadSint32(int32_t * out)247   Status ReadSint32(int32_t* out) { return decoder_.ReadSint32(out); }
248 
249   // Reads a proto sint64 value from the current cursor.
ReadSint64(int64_t * out)250   Status ReadSint64(int64_t* out) { return decoder_.ReadSint64(out); }
251 
252   // Reads a proto bool value from the current cursor.
ReadBool(bool * out)253   Status ReadBool(bool* out) { return decoder_.ReadBool(out); }
254 
255   // Reads a proto fixed32 value from the current cursor.
ReadFixed32(uint32_t * out)256   Status ReadFixed32(uint32_t* out) { return decoder_.ReadFixed32(out); }
257 
258   // Reads a proto fixed64 value from the current cursor.
ReadFixed64(uint64_t * out)259   Status ReadFixed64(uint64_t* out) { return decoder_.ReadFixed64(out); }
260 
261   // Reads a proto sfixed32 value from the current cursor.
ReadSfixed32(int32_t * out)262   Status ReadSfixed32(int32_t* out) { return decoder_.ReadSfixed32(out); }
263 
264   // Reads a proto sfixed64 value from the current cursor.
ReadSfixed64(int64_t * out)265   Status ReadSfixed64(int64_t* out) { return decoder_.ReadSfixed64(out); }
266 
267   // Reads a proto float value from the current cursor.
ReadFloat(float * out)268   Status ReadFloat(float* out) { return decoder_.ReadFloat(out); }
269 
270   // Reads a proto double value from the current cursor.
ReadDouble(double * out)271   Status ReadDouble(double* out) { return decoder_.ReadDouble(out); }
272 
273   // Reads a proto string value from the current cursor and returns a view of it
274   // in `out`. The raw protobuf data must outlive `out`. If the string field is
275   // invalid, `out` is not modified.
ReadString(std::string_view * out)276   Status ReadString(std::string_view* out) { return decoder_.ReadString(out); }
277 
278   // Reads a proto bytes value from the current cursor and returns a view of it
279   // in `out`. The raw protobuf data must outlive the `out` span. If the
280   // bytes field is invalid, `out` is not modified.
ReadBytes(span<const std::byte> * out)281   Status ReadBytes(span<const std::byte>* out) {
282     return decoder_.ReadBytes(out);
283   }
284 
cancelled()285   bool cancelled() const { return state_ == kDecodeCancelled; }
286 
287  private:
288   enum State {
289     kReady,
290     kDecodeInProgress,
291     kDecodeCancelled,
292     kDecodeFailed,
293   };
294 
295   Decoder decoder_;
296   DecodeHandler* handler_;
297 
298   State state_;
299 };
300 
301 // The event-handling interface implemented for a proto callback decoding
302 // operation.
303 class DecodeHandler {
304  public:
305   virtual ~DecodeHandler() = default;
306 
307   // Callback called for each field encountered in the decoded proto message.
308   // Receives a pointer to the decoder object, allowing the handler to call
309   // the appropriate method to extract the field's data.
310   //
311   // If the status returned is not OkStatus(), the decode operation is exited
312   // with the provided status. Returning Status::Cancelled() allows a convenient
313   // way of stopping a decode early (for example, if a desired field is found).
314   virtual Status ProcessField(CallbackDecoder& decoder,
315                               uint32_t field_number) = 0;
316 };
317 
318 }  // namespace pw::protobuf
319