pw_protobuf: Extended decoder type support
This change adds decoder support for protobuf bool, fixed32,
fixed64, float, double, bytes, and string value.
Change-Id: Ie8369f5802d07290c0d6c12b5049c6bd8cc7a5da
diff --git a/pw_protobuf/decoder.cc b/pw_protobuf/decoder.cc
index 0e803e6..220c608 100644
--- a/pw_protobuf/decoder.cc
+++ b/pw_protobuf/decoder.cc
@@ -14,6 +14,8 @@
#include "pw_protobuf/decoder.h"
+#include <cstring>
+
#include "pw_varint/varint.h"
namespace pw::protobuf {
@@ -66,6 +68,67 @@
}
Status Decoder::ReadVarint(uint32_t field_number, uint64_t* out) {
+ Status status = ConsumeKey(field_number, WireType::kVarint);
+ if (!status.ok()) {
+ return status;
+ }
+
+ size_t bytes_read = varint::Decode(proto_, out);
+ if (bytes_read == 0) {
+ state_ = kDecodeFailed;
+ return Status::DATA_LOSS;
+ }
+
+ // Advance to the next field.
+ proto_ = proto_.subspan(bytes_read);
+ return Status::OK;
+}
+
+Status Decoder::ReadFixed(uint32_t field_number, std::byte* out, size_t size) {
+ WireType expected_wire_type =
+ size == sizeof(uint32_t) ? WireType::kFixed32 : WireType::kFixed64;
+ Status status = ConsumeKey(field_number, expected_wire_type);
+ if (!status.ok()) {
+ return status;
+ }
+
+ if (proto_.size() < size) {
+ return Status::DATA_LOSS;
+ }
+
+ std::memcpy(out, proto_.data(), size);
+ proto_ = proto_.subspan(size);
+
+ return Status::OK;
+}
+
+Status Decoder::ReadDelimited(uint32_t field_number,
+ span<const std::byte>* out) {
+ Status status = ConsumeKey(field_number, WireType::kDelimited);
+ if (!status.ok()) {
+ return status;
+ }
+
+ uint64_t length;
+ size_t bytes_read = varint::Decode(proto_, &length);
+ if (bytes_read == 0) {
+ state_ = kDecodeFailed;
+ return Status::DATA_LOSS;
+ }
+
+ proto_ = proto_.subspan(bytes_read);
+ if (proto_.size() < length) {
+ state_ = kDecodeFailed;
+ return Status::DATA_LOSS;
+ }
+
+ *out = proto_.first(length);
+ proto_ = proto_.subspan(length);
+
+ return Status::OK;
+}
+
+Status Decoder::ConsumeKey(uint32_t field_number, WireType expected_type) {
if (state_ != kDecodeInProgress) {
return Status::FAILED_PRECONDITION;
}
@@ -80,21 +143,12 @@
uint32_t field = key >> kFieldNumberShift;
WireType wire_type = static_cast<WireType>(key & kWireTypeMask);
- if (field != field_number || wire_type != WireType::kVarint) {
+ if (field != field_number || wire_type != expected_type) {
state_ = kDecodeFailed;
return Status::FAILED_PRECONDITION;
}
- // Advance to the varint value.
- proto_ = proto_.subspan(bytes_read);
-
- bytes_read = varint::Decode(proto_, out);
- if (bytes_read == 0) {
- state_ = kDecodeFailed;
- return Status::DATA_LOSS;
- }
-
- // Advance to the next field.
+ // Advance past the key.
proto_ = proto_.subspan(bytes_read);
return Status::OK;
}
diff --git a/pw_protobuf/decoder_test.cc b/pw_protobuf/decoder_test.cc
index 45fe93d..c655b09 100644
--- a/pw_protobuf/decoder_test.cc
+++ b/pw_protobuf/decoder_test.cc
@@ -23,6 +23,8 @@
class TestDecodeHandler : public DecodeHandler {
public:
Status ProcessField(Decoder* decoder, uint32_t field_number) override {
+ std::string_view str;
+
switch (field_number) {
case 1:
decoder->ReadInt32(field_number, &test_int32);
@@ -30,6 +32,20 @@
case 2:
decoder->ReadSint32(field_number, &test_sint32);
break;
+ case 3:
+ decoder->ReadBool(field_number, &test_bool);
+ break;
+ case 4:
+ decoder->ReadDouble(field_number, &test_double);
+ break;
+ case 5:
+ decoder->ReadFixed32(field_number, &test_fixed32);
+ break;
+ case 6:
+ decoder->ReadString(field_number, &str);
+ std::memcpy(test_string, str.data(), str.size());
+ test_string[str.size()] = '\0';
+ break;
}
called = true;
@@ -39,6 +55,10 @@
bool called = false;
int32_t test_int32 = 0;
int32_t test_sint32 = 0;
+ bool test_bool = true;
+ double test_double = 0;
+ uint32_t test_fixed32 = 0;
+ char test_string[16];
};
TEST(Decoder, Decode) {
@@ -57,6 +77,8 @@
0x21, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
// type=fixed32, k=5, v=0xdeadbeef
0x2d, 0xef, 0xbe, 0xad, 0xde,
+ // type=string, k=6, v="Hello world"
+ 0x32, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
};
// clang-format on
@@ -65,6 +87,10 @@
EXPECT_TRUE(handler.called);
EXPECT_EQ(handler.test_int32, 42);
EXPECT_EQ(handler.test_sint32, -13);
+ EXPECT_FALSE(handler.test_bool);
+ EXPECT_EQ(handler.test_double, 3.14159);
+ EXPECT_EQ(handler.test_fixed32, 0xdeadbeef);
+ EXPECT_STREQ(handler.test_string, "Hello world");
}
TEST(Decoder, Decode_OverridesDuplicateFields) {
diff --git a/pw_protobuf/public/pw_protobuf/decoder.h b/pw_protobuf/public/pw_protobuf/decoder.h
index e923c66..7e5feed 100644
--- a/pw_protobuf/public/pw_protobuf/decoder.h
+++ b/pw_protobuf/public/pw_protobuf/decoder.h
@@ -13,6 +13,8 @@
// the License.
#pragma once
+#include <string_view>
+
#include "pw_protobuf/wire_format.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
@@ -140,6 +142,72 @@
return Status::OK;
}
+ // Reads a proto bool value from the current cursor.
+ Status ReadBool(uint32_t field_number, bool* out) {
+ uint64_t value = 0;
+ Status status = ReadUint64(field_number, &value);
+ if (!status.ok()) {
+ return status;
+ }
+ *out = value;
+ return Status::OK;
+ }
+
+ // Reads a proto fixed32 value from the current cursor.
+ Status ReadFixed32(uint32_t field_number, uint32_t* out) {
+ return ReadFixed(field_number, out);
+ }
+
+ // Reads a proto fixed64 value from the current cursor.
+ Status ReadFixed64(uint32_t field_number, uint64_t* out) {
+ return ReadFixed(field_number, out);
+ }
+
+ // Reads a proto sfixed32 value from the current cursor.
+ Status ReadSfixed32(uint32_t field_number, int32_t* out) {
+ return ReadFixed32(field_number, reinterpret_cast<uint32_t*>(out));
+ }
+
+ // Reads a proto sfixed64 value from the current cursor.
+ Status ReadSfixed64(uint32_t field_number, int64_t* out) {
+ return ReadFixed64(field_number, reinterpret_cast<uint64_t*>(out));
+ }
+
+ // Reads a proto float value from the current cursor.
+ Status ReadFloat(uint32_t field_number, float* out) {
+ static_assert(sizeof(float) == sizeof(uint32_t),
+ "Float and uint32_t must be the same size for protobufs");
+ return ReadFixed(field_number, out);
+ }
+
+ // Reads a proto double value from the current cursor.
+ Status ReadDouble(uint32_t field_number, double* out) {
+ static_assert(sizeof(double) == sizeof(uint64_t),
+ "Double and uint64_t must be the same size for protobufs");
+ return ReadFixed(field_number, out);
+ }
+
+ // Reads a proto string value from the current cursor and returns a view of it
+ // in `out`. The raw protobuf data must outlive `out`. If the string field is
+ // invalid, `out` is not modified.
+ Status ReadString(uint32_t field_number, std::string_view* out) {
+ span<const std::byte> bytes;
+ Status status = ReadDelimited(field_number, &bytes);
+ if (!status.ok()) {
+ return status;
+ }
+ *out = std::string_view(reinterpret_cast<const char*>(bytes.data()),
+ bytes.size());
+ return Status::OK;
+ }
+
+ // Reads a proto bytes value from the current cursor and returns a view of it
+ // in `out`. The raw protobuf data must outlive the `out` span. If the bytes
+ // field is invalid, `out` is not modified.
+ Status ReadBytes(uint32_t field_number, span<const std::byte>* out) {
+ return ReadDelimited(field_number, out);
+ }
+
private:
enum State {
kReady,
@@ -150,6 +218,30 @@
// Reads a varint key-value pair from the current cursor position.
Status ReadVarint(uint32_t field_number, uint64_t* out);
+ // Reads a fixed-size key-value pair from the current cursor position.
+ Status ReadFixed(uint32_t field_number, std::byte* out, size_t size);
+
+ template <typename T>
+ Status ReadFixed(uint32_t field_number, T* out) {
+ static_assert(
+ sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
+ "Protobuf fixed-size fields must be 32- or 64-bit");
+ union {
+ T value;
+ std::byte bytes[sizeof(T)];
+ };
+ Status status = ReadFixed(field_number, bytes, sizeof(bytes));
+ if (!status.ok()) {
+ return status;
+ }
+ *out = value;
+ return Status::OK;
+ }
+
+ Status ReadDelimited(uint32_t field_number, span<const std::byte>* out);
+
+ Status ConsumeKey(uint32_t field_number, WireType expected_type);
+
// Advances the cursor to the next field in the proto.
void SkipField();
diff --git a/pw_protobuf/size_report/decoder_full.cc b/pw_protobuf/size_report/decoder_full.cc
index 37ac958..d879012 100644
--- a/pw_protobuf/size_report/decoder_full.cc
+++ b/pw_protobuf/size_report/decoder_full.cc
@@ -12,6 +12,8 @@
// License for the specific language governing permissions and limitations under
// the License.
+#include <cstring>
+
#include "pw_bloat/bloat_this_binary.h"
#include "pw_protobuf/decoder.h"
@@ -30,6 +32,8 @@
public:
pw::Status ProcessField(pw::protobuf::Decoder* decoder,
uint32_t field_number) override {
+ std::string_view str;
+
switch (field_number) {
case 1:
if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
@@ -41,6 +45,29 @@
test_sint32 = 0;
}
break;
+ case 3:
+ if (!decoder->ReadBool(field_number, &test_bool).ok()) {
+ test_bool = false;
+ }
+ break;
+ case 4:
+ if (!decoder->ReadDouble(field_number, &test_double).ok()) {
+ test_double = 0;
+ }
+ break;
+ case 5:
+ if (!decoder->ReadFixed32(field_number, &test_fixed32).ok()) {
+ test_fixed32 = 0;
+ }
+ break;
+ case 6:
+ if (decoder->ReadString(field_number, &str).ok()) {
+ // In real code:
+ // assert(str.size() < sizeof(test_string));
+ std::memcpy(test_string, str.data(), str.size());
+ test_string[str.size()] = '\0';
+ }
+ break;
}
return pw::Status::OK;
@@ -48,6 +75,10 @@
int32_t test_int32 = 0;
int32_t test_sint32 = 0;
+ bool test_bool = false;
+ double test_double = 0;
+ uint32_t test_fixed32 = 0;
+ char test_string[16];
};
int* volatile non_optimizable_pointer;
diff --git a/pw_protobuf/size_report/decoder_incremental.cc b/pw_protobuf/size_report/decoder_incremental.cc
index 3955aa7..ef6f2db 100644
--- a/pw_protobuf/size_report/decoder_incremental.cc
+++ b/pw_protobuf/size_report/decoder_incremental.cc
@@ -12,6 +12,8 @@
// License for the specific language governing permissions and limitations under
// the License.
+#include <cstring>
+
#include "pw_bloat/bloat_this_binary.h"
#include "pw_protobuf/decoder.h"
@@ -30,6 +32,8 @@
public:
pw::Status ProcessField(pw::protobuf::Decoder* decoder,
uint32_t field_number) override {
+ std::string_view str;
+
switch (field_number) {
case 1:
if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
@@ -42,26 +46,51 @@
}
break;
case 3:
- if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
- test_int32 = 0;
+ if (!decoder->ReadBool(field_number, &test_bool).ok()) {
+ test_bool = false;
}
break;
case 4:
- if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
- test_int32 = 0;
+ if (!decoder->ReadDouble(field_number, &test_double).ok()) {
+ test_double = 0;
}
break;
case 5:
+ if (!decoder->ReadFixed32(field_number, &test_fixed32).ok()) {
+ test_fixed32 = 0;
+ }
+ break;
+ case 6:
+ if (decoder->ReadString(field_number, &str).ok()) {
+ // In real code:
+ // assert(str.size() < sizeof(test_string));
+ std::memcpy(test_string, str.data(), str.size());
+ test_string[str.size()] = '\0';
+ }
+ break;
+
+ // Extra fields.
+ case 21:
if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
test_int32 = 0;
}
break;
- case 6:
+ case 22:
+ if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+ test_int32 = 0;
+ }
+ break;
+ case 23:
+ if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+ test_int32 = 0;
+ }
+ break;
+ case 24:
if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
test_sint32 = 0;
}
break;
- case 7:
+ case 25:
if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
test_sint32 = 0;
}
@@ -73,6 +102,10 @@
int32_t test_int32 = 0;
int32_t test_sint32 = 0;
+ bool test_bool = false;
+ double test_double = 0;
+ uint32_t test_fixed32 = 0;
+ char test_string[16];
};
int* volatile non_optimizable_pointer;