pw_protobuf: Don't encode fields with default (zero) values
For better compatibility with other encoders, do not encode zero values
of fixed and varint fields.
Bug: pwbug/656
Change-Id: I2e0e0eee794208560876206d9978234afde0addc
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/91661
Pigweed-Auto-Submit: Scott James Remnant <keybuk@google.com>
Commit-Queue: Scott James Remnant <keybuk@google.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
diff --git a/pw_protobuf/codegen_message_test.cc b/pw_protobuf/codegen_message_test.cc
index 7f1741f..4fee12c 100644
--- a/pw_protobuf/codegen_message_test.cc
+++ b/pw_protobuf/codegen_message_test.cc
@@ -1051,13 +1051,13 @@
TEST(CodegenMessage, ReadOptionalPresent) {
// clang-format off
constexpr uint8_t proto_data[] = {
- // optional.always_present_fixed
- 0x0d, 0x2a, 0x00, 0x00, 0x00,
- // optional.always_present_varint
- 0x10, 0x2a,
// optional.sometimes_present_fixed
- 0x1d, 0x45, 0x00, 0x00, 0x00,
+ 0x0d, 0x2a, 0x00, 0x00, 0x00,
// optional.sometimes_present_varint
+ 0x10, 0x2a,
+ // optional.explicitly_present_fixed
+ 0x1d, 0x45, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
0x20, 0x45,
// optional.sometimes_empty_fixed
0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
@@ -1073,12 +1073,12 @@
const auto status = optional_test.Read(message);
ASSERT_EQ(status, OkStatus());
- EXPECT_EQ(message.always_present_fixed, 0x2a);
- EXPECT_EQ(message.always_present_varint, 0x2a);
- EXPECT_TRUE(message.sometimes_present_fixed);
- EXPECT_EQ(*message.sometimes_present_fixed, 0x45);
- EXPECT_TRUE(message.sometimes_present_varint);
- EXPECT_EQ(*message.sometimes_present_varint, 0x45);
+ EXPECT_EQ(message.sometimes_present_fixed, 0x2a);
+ EXPECT_EQ(message.sometimes_present_varint, 0x2a);
+ EXPECT_TRUE(message.explicitly_present_fixed);
+ EXPECT_EQ(*message.explicitly_present_fixed, 0x45);
+ EXPECT_TRUE(message.explicitly_present_varint);
+ EXPECT_EQ(*message.explicitly_present_varint, 0x45);
EXPECT_FALSE(message.sometimes_empty_fixed.empty());
EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
EXPECT_EQ(message.sometimes_empty_fixed[0], 0x63);
@@ -1097,12 +1097,60 @@
const auto status = optional_test.Read(message);
ASSERT_EQ(status, OkStatus());
- EXPECT_EQ(message.always_present_fixed, 0);
- EXPECT_EQ(message.always_present_varint, 0);
- EXPECT_FALSE(message.sometimes_present_fixed);
- EXPECT_FALSE(message.sometimes_present_varint);
+ // Non-optional fields have their default value.
+ EXPECT_EQ(message.sometimes_present_fixed, 0);
+ EXPECT_EQ(message.sometimes_present_varint, 0);
EXPECT_TRUE(message.sometimes_empty_fixed.empty());
EXPECT_TRUE(message.sometimes_empty_varint.empty());
+
+ // Optional fields are explicitly not present.
+ EXPECT_FALSE(message.explicitly_present_fixed);
+ EXPECT_FALSE(message.explicitly_present_varint);
+}
+
+TEST(CodegenMessage, ReadOptionalPresentDefaults) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // optional.sometimes_present_fixed
+ 0x0d, 0x00, 0x00, 0x00, 0x00,
+ // optional.sometimes_present_varint
+ 0x10, 0x00,
+ // optional.explicitly_present_fixed
+ 0x1d, 0x00, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
+ 0x20, 0x00,
+ // optional.sometimes_empty_fixed
+ 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
+ // optional.sometimes_empty_varint
+ 0x32, 0x01, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
+ OptionalTest::StreamDecoder optional_test(reader);
+
+ OptionalTest::Message message{};
+ const auto status = optional_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // Non-optional fields have their default value and aren't meaningfully
+ // different from missing.
+ EXPECT_EQ(message.sometimes_present_fixed, 0x00);
+ EXPECT_EQ(message.sometimes_present_varint, 0x00);
+
+ // Optional fields are explicitly present with a default value.
+ EXPECT_TRUE(message.explicitly_present_fixed);
+ EXPECT_EQ(*message.explicitly_present_fixed, 0x00);
+ EXPECT_TRUE(message.explicitly_present_varint);
+ EXPECT_EQ(*message.explicitly_present_varint, 0x00);
+
+ // Repeated fields with a default value are meaningfully non-empty.
+ EXPECT_FALSE(message.sometimes_empty_fixed.empty());
+ EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
+ EXPECT_EQ(message.sometimes_empty_fixed[0], 0x00);
+ EXPECT_FALSE(message.sometimes_empty_varint.empty());
+ EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
+ EXPECT_EQ(message.sometimes_empty_varint[0], 0x00);
}
class BreakableDecoder : public KeyValuePair::StreamDecoder {
@@ -1200,21 +1248,15 @@
// pigweed.bin
0x40, 0x01,
// pigweed.proto
- 0x4a, 0x1b,
- // pigweed.proto.bin
- 0x10, 0x00,
- // pigweed.proto.pigweed_pigweed_bin
- 0x18, 0x00,
+ 0x4a, 0x15,
// pigweed.proto.pigweed_protobuf_bin
0x20, 0x01,
// pigweed.proto.meta
- 0x2a, 0x13,
+ 0x2a, 0x11,
// pigweed.proto.meta.file_name
0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
// pigweed.proto.meta.status
0x10, 0x02,
- // pigweed.proto.meta.protobuf_bin
- 0x18, 0x00,
// pigweed.proto.meta.pigweed_bin
0x20, 0x01,
// pigweed.bytes
@@ -1244,40 +1286,14 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
- // pigweed.magic_number (default)
- 0x08, 0x00,
- // pigweed.ziggy (default)
- 0x10, 0x00,
- // pigweed.cycles (default)
- 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // pigweed.ratio (default)
- 0x25, 0x00, 0x00, 0x00, 0x00,
- // pigweed.pigweed (default)
- 0x3a, 0x02,
- // pigweed.pigweed.status (default)
- 0x08, 0x00,
- // pigweed.bin (default)
- 0x40, 0x00,
- // pigweed.proto (default)
- 0x4a, 0x0e,
- // pigweed.proto.bin (default)
- 0x10, 0x00,
- // pigweed.proto.pigweed_pigweed_bin (default)
- 0x18, 0x00,
- // pigweed.proto.pigweed_protobuf_bin (default)
- 0x20, 0x00,
- // pigweed.proto.meta (default)
- 0x2a, 0x06,
- // pigweed.proto.meta.status (default)
- 0x10, 0x00,
- // pigweed.proto.meta.protobuf_bin (default)
- 0x18, 0x00,
- // pigweed.proto.meta.pigweed_bin (default)
- 0x20, 0x00,
+ // pigweed.pigweed (empty)
+ 0x3a, 0x00,
+ // pigweed.proto
+ 0x4a, 0x02,
+ // pigweed.proto.meta (empty)
+ 0x2a, 0x00,
// pigweed.bytes (default)
0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // pigweed.bungle (default)
- 0x70, 0x00,
};
// clang-format on
@@ -1460,36 +1476,12 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
- // pigweed.magic_number (default)
- 0x08, 0x00,
- // pigweed.ziggy (default)
- 0x10, 0x00,
- // pigweed.cycles (default)
- 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // pigweed.ratio (default)
- 0x25, 0x00, 0x00, 0x00, 0x00,
- // pigweed.pigweed (default)
- 0x3a, 0x02,
- // pigweed.pigweed.status (default)
- 0x08, 0x00,
- // pigweed.bin (default)
- 0x40, 0x00,
+ // pigweed.pigweed (empty)
+ 0x3a, 0x00,
// pigweed.proto (default)
- 0x4a, 0x0e,
- // pigweed.proto.bin (default)
- 0x10, 0x00,
- // pigweed.proto.pigweed_pigweed_bin (default)
- 0x18, 0x00,
- // pigweed.proto.pigweed_protobuf_bin (default)
- 0x20, 0x00,
- // pigweed.proto.meta (default)
- 0x2a, 0x06,
- // pigweed.proto.meta.status (default)
- 0x10, 0x00,
- // pigweed.proto.meta.protobuf_bin (default)
- 0x18, 0x00,
- // pigweed.proto.meta.pigweed_bin (default)
- 0x20, 0x00,
+ 0x4a, 0x02,
+ // pigweed.proto.meta (empty)
+ 0x2a, 0x00,
// pigweed.bytes (default)
0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// pigweed.description
@@ -1500,8 +1492,6 @@
'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
'u', 'l', 'e', 's',
- // pigweed.bungle (default)
- 0x70, 0x00,
};
// clang-format on
@@ -1530,42 +1520,16 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
- // pigweed.magic_number (default)
- 0x08, 0x00,
- // pigweed.ziggy (default)
- 0x10, 0x00,
- // pigweed.cycles (default)
- 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // pigweed.ratio (default)
- 0x25, 0x00, 0x00, 0x00, 0x00,
- // pigweed.pigweed (default)
- 0x3a, 0x02,
- // pigweed.pigweed.status (default)
- 0x08, 0x00,
- // pigweed.bin (default)
- 0x40, 0x00,
+ // pigweed.pigweed (empty)
+ 0x3a, 0x00,
// pigweed.proto (default)
- 0x4a, 0x0e,
- // pigweed.proto.bin (default)
- 0x10, 0x00,
- // pigweed.proto.pigweed_pigweed_bin (default)
- 0x18, 0x00,
- // pigweed.proto.pigweed_protobuf_bin (default)
- 0x20, 0x00,
- // pigweed.proto.meta (default)
- 0x2a, 0x06,
- // pigweed.proto.meta.status (default)
- 0x10, 0x00,
- // pigweed.proto.meta.protobuf_bin (default)
- 0x18, 0x00,
- // pigweed.proto.meta.pigweed_bin (default)
- 0x20, 0x00,
+ 0x4a, 0x02,
+ // pigweed.proto.meta (empty)
+ 0x2a, 0x00,
// pigweed.bytes (default)
0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// pigweed.special_property
0x68, 0x2a,
- // pigweed.bungle (default)
- 0x70, 0x00,
};
// clang-format on
@@ -1592,17 +1556,13 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
// period.start
- 0x0a, 0x08,
+ 0x0a, 0x06,
// period.start.seconds v=1517949900
0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
- // period.start.nanoseconds v=0 (default)
- 0x10, 0x00,
// period.end
- 0x12, 0x08,
+ 0x12, 0x06,
// period.end.seconds, v=1517950378
0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
- // period.end.nanoseconds, v=0 (default)
- 0x10, 0x00,
};
// clang-format on
@@ -1709,60 +1669,32 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
- // pigweed.magic_number (default)
- 0x08, 0x00,
- // pigweed.ziggy (default)
- 0x10, 0x00,
- // pigweed.cycles (default)
- 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // pigweed.ratio (default)
- 0x25, 0x00, 0x00, 0x00, 0x00,
// pigweed.device_info
- 0x32, 0x32,
+ 0x32, 0x30,
// pigweed.device_info.device_name
0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
// pigweed.device_info.device_id
0x15, 0x08, 0x08, 0x08, 0x08,
- // pigweed.device_info.status
- 0x18, 0x00,
- // pigweed.proto.nested_pigweed.device_info.attributes[0]
+ // pigweed.device_info.attributes[0]
0x22, 0x10,
- // pigweed.proto.nested_pigweed.device_info.attributes[0].key
+ // pigweed.device_info.attributes[0].key
0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
- // pigweed.proto.nested_pigweed.device_info.attributes[0].value
+ // pigweed.device_info.attributes[0].value
0x12, 0x05, '5', '.', '3', '.', '1',
- // pigweed.proto.nested_pigweed.device_info.attributes[1]
+ // pigweed.device_info.attributes[1]
0x22, 0x10,
- // pigweed.proto.nested_pigweed.device_info.attributes[1].key
+ // pigweed.device_info.attributes[1].key
0x0a, 0x04, 'c', 'h', 'i', 'p',
- // pigweed.proto.nested_pigweed.device_info.attributes[1].value
+ // pigweed.device_info.attributes[1].value
0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
- // pigweed.pigweed (default)
- 0x3a, 0x02,
- // pigweed.pigweed.status (default)
- 0x08, 0x00,
- // pigweed.bin (default)
- 0x40, 0x00,
+ // pigweed.pigweed (empty)
+ 0x3a, 0x00,
// pigweed.proto (default)
- 0x4a, 0x0e,
- // pigweed.proto.bin (default)
- 0x10, 0x00,
- // pigweed.proto.pigweed_pigweed_bin (default)
- 0x18, 0x00,
- // pigweed.proto.pigweed_protobuf_bin (default)
- 0x20, 0x00,
- // pigweed.proto.meta (default)
- 0x2a, 0x06,
- // pigweed.proto.meta.status (default)
- 0x10, 0x00,
- // pigweed.proto.meta.protobuf_bin (default)
- 0x18, 0x00,
- // pigweed.proto.meta.pigweed_bin (default)
- 0x20, 0x00,
+ 0x4a, 0x02,
+ // pigweed.proto.meta (empty)
+ 0x2a, 0x00,
// pigweed.bytes (default)
0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // pigweed.bungle (default)
- 0x70, 0x00,
};
// clang-format on
@@ -1789,10 +1721,10 @@
TEST(CodegenMessage, WriteOptionalPresent) {
OptionalTest::Message message{};
- message.always_present_fixed = 0x2a;
- message.always_present_varint = 0x2a;
- message.sometimes_present_fixed = 0x45;
- message.sometimes_present_varint = 0x45;
+ message.sometimes_present_fixed = 0x2a;
+ message.sometimes_present_varint = 0x2a;
+ message.explicitly_present_fixed = 0x45;
+ message.explicitly_present_varint = 0x45;
message.sometimes_empty_fixed.push_back(0x63);
message.sometimes_empty_varint.push_back(0x63);
@@ -1806,13 +1738,13 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
- // optional.always_present_fixed
- 0x0d, 0x2a, 0x00, 0x00, 0x00,
- // optional.always_present_varint
- 0x10, 0x2a,
// optional.sometimes_present_fixed
- 0x1d, 0x45, 0x00, 0x00, 0x00,
+ 0x0d, 0x2a, 0x00, 0x00, 0x00,
// optional.sometimes_present_varint
+ 0x10, 0x2a,
+ // optional.explicitly_present_fixed
+ 0x1d, 0x45, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
0x20, 0x45,
// optional.sometimes_empty_fixed
0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
@@ -1840,10 +1772,46 @@
// clang-format off
constexpr uint8_t expected_proto[] = {
- // optional.always_present_fixed
- 0x0d, 0x00, 0x00, 0x00, 0x00,
- // optional.always_present_varint
- 0x10, 0x00,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteOptionalPresentDefaults) {
+ OptionalTest::Message message{};
+ // Non-optional fields with a default value are not explicitly encoded, so
+ // aren't meaningfully different from one that's just ommitted.
+ message.sometimes_present_fixed = 0x00;
+ message.sometimes_present_varint = 0x00;
+ // Optional fields, even with a default value, are explicitly encoded.
+ message.explicitly_present_fixed = 0x00;
+ message.explicitly_present_varint = 0x00;
+ // Repeated fields with a default value are meaningfully non-empty.
+ message.sometimes_empty_fixed.push_back(0x00);
+ message.sometimes_empty_varint.push_back(0x00);
+
+ std::byte encode_buffer[512];
+
+ stream::MemoryWriter writer(encode_buffer);
+ OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
+
+ const auto status = optional_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // optional.explicitly_present_fixed
+ 0x1d, 0x00, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
+ 0x20, 0x00,
+ // optional.sometimes_empty_fixed
+ 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
+ // optional.sometimes_empty_varint
+ 0x32, 0x01, 0x00,
};
// clang-format on
diff --git a/pw_protobuf/encoder.cc b/pw_protobuf/encoder.cc
index e6109dc..7d5db02 100644
--- a/pw_protobuf/encoder.cc
+++ b/pw_protobuf/encoder.cc
@@ -14,6 +14,7 @@
#include "pw_protobuf/encoder.h"
+#include <algorithm>
#include <cstddef>
#include <cstring>
#include <optional>
@@ -321,7 +322,11 @@
} else {
PW_CHECK(values.size() == field.elem_size(),
"Mismatched message field type and size");
- PW_TRY(WriteFixed(field.field_number(), values));
+ if (static_cast<size_t>(
+ std::count(values.begin(), values.end(), std::byte{0})) <
+ values.size()) {
+ PW_TRY(WriteFixed(field.field_number(), values));
+ }
}
break;
}
@@ -471,6 +476,9 @@
} else {
value = *reinterpret_cast<const uint64_t*>(values.data());
}
+ if (!value) {
+ continue;
+ }
} else if (field.elem_size() == sizeof(uint32_t)) {
if (field.varint_type() == VarintType::kZigZag) {
value = varint::ZigZagEncode(
@@ -480,8 +488,14 @@
} else {
value = *reinterpret_cast<const uint32_t*>(values.data());
}
+ if (!value) {
+ continue;
+ }
} else if (field.elem_size() == sizeof(bool)) {
value = *reinterpret_cast<const bool*>(values.data());
+ if (!value) {
+ continue;
+ }
}
PW_TRY(WriteVarintField(field.field_number(), value));
}
diff --git a/pw_protobuf/pw_protobuf_test_protos/optional.proto b/pw_protobuf/pw_protobuf_test_protos/optional.proto
index c17df5f..b75cd17 100644
--- a/pw_protobuf/pw_protobuf_test_protos/optional.proto
+++ b/pw_protobuf/pw_protobuf_test_protos/optional.proto
@@ -16,10 +16,10 @@
package pw.protobuf.test;
message OptionalTest {
- sfixed32 always_present_fixed = 1;
- int32 always_present_varint = 2;
- optional sfixed32 sometimes_present_fixed = 3;
- optional int32 sometimes_present_varint = 4;
+ sfixed32 sometimes_present_fixed = 1;
+ int32 sometimes_present_varint = 2;
+ optional sfixed32 explicitly_present_fixed = 3;
+ optional int32 explicitly_present_varint = 4;
repeated sfixed32 sometimes_empty_fixed = 5;
repeated int32 sometimes_empty_varint = 6;
};
diff --git a/pw_rpc/pwpb/serde_test.cc b/pw_rpc/pwpb/serde_test.cc
index b9ae4aa..c5348e6 100644
--- a/pw_rpc/pwpb/serde_test.cc
+++ b/pw_rpc/pwpb/serde_test.cc
@@ -30,12 +30,9 @@
StatusWithSize result = kTestRequest.Encode(kProto, buffer);
EXPECT_EQ(OkStatus(), result.status());
- EXPECT_EQ(result.size(), 4u);
+ EXPECT_EQ(result.size(), 2u);
EXPECT_EQ(buffer[0], std::byte{1} << 3);
EXPECT_EQ(buffer[1], std::byte{3});
- // pw_protobuf encodes zero fields
- EXPECT_EQ(buffer[2], std::byte{2} << 3);
- EXPECT_EQ(buffer[3], std::byte{0});
}
TEST(PwpbSerde, Encode_TooSmall) {