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 class Decoder {
46  public:
Decoder(std::span<const std::byte> proto)47   constexpr Decoder(std::span<const std::byte> proto)
48       : proto_(proto), previous_field_consumed_(true) {}
49 
50   Decoder(const Decoder& other) = delete;
51   Decoder& operator=(const Decoder& other) = delete;
52 
53   // Advances to the next field in the proto.
54   //
55   // If Next() returns OK, there is guaranteed to be a valid protobuf field at
56   // the current cursor position.
57   //
58   // Return values:
59   //
60   //             OK: Advanced to a valid proto field.
61   //   OUT_OF_RANGE: Reached the end of the proto message.
62   //      DATA_LOSS: Invalid protobuf data.
63   //
64   Status Next();
65 
66   // Returns the field number of the field at the current cursor position.
67   uint32_t FieldNumber() const;
68 
69   // Reads a proto int32 value from the current cursor.
ReadInt32(int32_t * out)70   Status ReadInt32(int32_t* out) {
71     return ReadUint32(reinterpret_cast<uint32_t*>(out));
72   }
73 
74   // Reads a proto uint32 value from the current cursor.
75   Status ReadUint32(uint32_t* out);
76 
77   // Reads a proto int64 value from the current cursor.
ReadInt64(int64_t * out)78   Status ReadInt64(int64_t* out) {
79     return ReadVarint(reinterpret_cast<uint64_t*>(out));
80   }
81 
82   // Reads a proto uint64 value from the current cursor.
ReadUint64(uint64_t * out)83   Status ReadUint64(uint64_t* out) { return ReadVarint(out); }
84 
85   // Reads a proto sint32 value from the current cursor.
86   Status ReadSint32(int32_t* out);
87 
88   // Reads a proto sint64 value from the current cursor.
89   Status ReadSint64(int64_t* out);
90 
91   // Reads a proto bool value from the current cursor.
92   Status ReadBool(bool* out);
93 
94   // Reads a proto fixed32 value from the current cursor.
ReadFixed32(uint32_t * out)95   Status ReadFixed32(uint32_t* out) { return ReadFixed(out); }
96 
97   // Reads a proto fixed64 value from the current cursor.
ReadFixed64(uint64_t * out)98   Status ReadFixed64(uint64_t* out) { return ReadFixed(out); }
99 
100   // Reads a proto sfixed32 value from the current cursor.
ReadSfixed32(int32_t * out)101   Status ReadSfixed32(int32_t* out) {
102     return ReadFixed32(reinterpret_cast<uint32_t*>(out));
103   }
104 
105   // Reads a proto sfixed64 value from the current cursor.
ReadSfixed64(int64_t * out)106   Status ReadSfixed64(int64_t* out) {
107     return ReadFixed64(reinterpret_cast<uint64_t*>(out));
108   }
109 
110   // Reads a proto float value from the current cursor.
ReadFloat(float * out)111   Status ReadFloat(float* out) {
112     static_assert(sizeof(float) == sizeof(uint32_t),
113                   "Float and uint32_t must be the same size for protobufs");
114     return ReadFixed(out);
115   }
116 
117   // Reads a proto double value from the current cursor.
ReadDouble(double * out)118   Status ReadDouble(double* out) {
119     static_assert(sizeof(double) == sizeof(uint64_t),
120                   "Double and uint64_t must be the same size for protobufs");
121     return ReadFixed(out);
122   }
123 
124   // Reads a proto string value from the current cursor and returns a view of it
125   // in `out`. The raw protobuf data must outlive `out`. If the string field is
126   // invalid, `out` is not modified.
127   Status ReadString(std::string_view* out);
128 
129   // Reads a proto bytes value from the current cursor and returns a view of it
130   // in `out`. The raw protobuf data must outlive the `out` std::span. If the
131   // bytes field is invalid, `out` is not modified.
ReadBytes(std::span<const std::byte> * out)132   Status ReadBytes(std::span<const std::byte>* out) {
133     return ReadDelimited(out);
134   }
135 
136   // Resets the decoder to start reading a new proto message.
Reset(std::span<const std::byte> proto)137   void Reset(std::span<const std::byte> proto) {
138     proto_ = proto;
139     previous_field_consumed_ = true;
140   }
141 
142  private:
143   // Advances the cursor to the next field in the proto.
144   Status SkipField();
145 
146   // Returns the size of the current field, or 0 if the field is invalid.
147   size_t FieldSize() const;
148 
149   Status ConsumeKey(WireType expected_type);
150 
151   // Reads a varint key-value pair from the current cursor position.
152   Status ReadVarint(uint64_t* out);
153 
154   // Reads a fixed-size key-value pair from the current cursor position.
155   Status ReadFixed(std::byte* out, size_t size);
156 
157   template <typename T>
ReadFixed(T * out)158   Status ReadFixed(T* out) {
159     static_assert(
160         sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
161         "Protobuf fixed-size fields must be 32- or 64-bit");
162     return ReadFixed(reinterpret_cast<std::byte*>(out), sizeof(T));
163   }
164 
165   Status ReadDelimited(std::span<const std::byte>* out);
166 
167   std::span<const std::byte> proto_;
168   bool previous_field_consumed_;
169 };
170 
171 class DecodeHandler;
172 
173 // A protobuf decoder that iterates over an encoded protobuf, calling a handler
174 // for each field it encounters.
175 //
176 // Example usage:
177 //
178 //   class FooProtoHandler : public DecodeHandler {
179 //    public:
180 //     Status ProcessField(CallbackDecoder& decoder,
181 //                         uint32_t field_number) override {
182 //       switch (field_number) {
183 //         case FooFields::kBar:
184 //           if (!decoder.ReadSint32(&bar).ok()) {
185 //             bar = 0;
186 //           }
187 //           break;
188 //         case FooFields::kBaz:
189 //           if (!decoder.ReadUint32(&baz).ok()) {
190 //             baz = 0;
191 //           }
192 //           break;
193 //       }
194 //
195 //       return OkStatus();
196 //     }
197 //
198 //     int bar;
199 //     unsigned int baz;
200 //   };
201 //
202 //   void DecodeFooProto(std::span<std::byte> raw_proto) {
203 //     Decoder decoder;
204 //     FooProtoHandler handler;
205 //
206 //     decoder.set_handler(&handler);
207 //     if (!decoder.Decode(raw_proto).ok()) {
208 //       LOG_FATAL("Invalid foo message!");
209 //     }
210 //
211 //     LOG_INFO("Read Foo proto message; bar: %d baz: %u",
212 //              handler.bar, handler.baz);
213 //   }
214 //
215 class CallbackDecoder {
216  public:
CallbackDecoder()217   constexpr CallbackDecoder()
218       : decoder_({}), handler_(nullptr), state_(kReady) {}
219 
220   CallbackDecoder(const CallbackDecoder& other) = delete;
221   CallbackDecoder& operator=(const CallbackDecoder& other) = delete;
222 
set_handler(DecodeHandler * handler)223   void set_handler(DecodeHandler* handler) { handler_ = handler; }
224 
225   // Decodes the specified protobuf data. The registered handler's ProcessField
226   // function is called on each field found in the data.
227   Status Decode(std::span<const std::byte> proto);
228 
229   // Reads a proto int32 value from the current cursor.
ReadInt32(int32_t * out)230   Status ReadInt32(int32_t* out) { return decoder_.ReadInt32(out); }
231 
232   // Reads a proto uint32 value from the current cursor.
ReadUint32(uint32_t * out)233   Status ReadUint32(uint32_t* out) { return decoder_.ReadUint32(out); }
234 
235   // Reads a proto int64 value from the current cursor.
ReadInt64(int64_t * out)236   Status ReadInt64(int64_t* out) { return decoder_.ReadInt64(out); }
237 
238   // Reads a proto uint64 value from the current cursor.
ReadUint64(uint64_t * out)239   Status ReadUint64(uint64_t* out) { return decoder_.ReadUint64(out); }
240 
241   // Reads a proto sint64 value from the current cursor.
ReadSint32(int32_t * out)242   Status ReadSint32(int32_t* out) { return decoder_.ReadSint32(out); }
243 
244   // Reads a proto sint64 value from the current cursor.
ReadSint64(int64_t * out)245   Status ReadSint64(int64_t* out) { return decoder_.ReadSint64(out); }
246 
247   // Reads a proto bool value from the current cursor.
ReadBool(bool * out)248   Status ReadBool(bool* out) { return decoder_.ReadBool(out); }
249 
250   // Reads a proto fixed32 value from the current cursor.
ReadFixed32(uint32_t * out)251   Status ReadFixed32(uint32_t* out) { return decoder_.ReadFixed32(out); }
252 
253   // Reads a proto fixed64 value from the current cursor.
ReadFixed64(uint64_t * out)254   Status ReadFixed64(uint64_t* out) { return decoder_.ReadFixed64(out); }
255 
256   // Reads a proto sfixed32 value from the current cursor.
ReadSfixed32(int32_t * out)257   Status ReadSfixed32(int32_t* out) { return decoder_.ReadSfixed32(out); }
258 
259   // Reads a proto sfixed64 value from the current cursor.
ReadSfixed64(int64_t * out)260   Status ReadSfixed64(int64_t* out) { return decoder_.ReadSfixed64(out); }
261 
262   // Reads a proto float value from the current cursor.
ReadFloat(float * out)263   Status ReadFloat(float* out) { return decoder_.ReadFloat(out); }
264 
265   // Reads a proto double value from the current cursor.
ReadDouble(double * out)266   Status ReadDouble(double* out) { return decoder_.ReadDouble(out); }
267 
268   // Reads a proto string value from the current cursor and returns a view of it
269   // in `out`. The raw protobuf data must outlive `out`. If the string field is
270   // invalid, `out` is not modified.
ReadString(std::string_view * out)271   Status ReadString(std::string_view* out) { return decoder_.ReadString(out); }
272 
273   // Reads a proto bytes value from the current cursor and returns a view of it
274   // in `out`. The raw protobuf data must outlive the `out` std::span. If the
275   // bytes field is invalid, `out` is not modified.
ReadBytes(std::span<const std::byte> * out)276   Status ReadBytes(std::span<const std::byte>* out) {
277     return decoder_.ReadBytes(out);
278   }
279 
cancelled()280   bool cancelled() const { return state_ == kDecodeCancelled; };
281 
282  private:
283   enum State {
284     kReady,
285     kDecodeInProgress,
286     kDecodeCancelled,
287     kDecodeFailed,
288   };
289 
290   Decoder decoder_;
291   DecodeHandler* handler_;
292 
293   State state_;
294 };
295 
296 // The event-handling interface implemented for a proto callback decoding
297 // operation.
298 class DecodeHandler {
299  public:
300   virtual ~DecodeHandler() = default;
301 
302   // Callback called for each field encountered in the decoded proto message.
303   // Receives a pointer to the decoder object, allowing the handler to call
304   // the appropriate method to extract the field's data.
305   //
306   // If the status returned is not OkStatus(), the decode operation is exited
307   // with the provided status. Returning Status::Cancelled() allows a convenient
308   // way of stopping a decode early (for example, if a desired field is found).
309   virtual Status ProcessField(CallbackDecoder& decoder,
310                               uint32_t field_number) = 0;
311 };
312 
313 }  // namespace pw::protobuf
314