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