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