// Copyright 2021 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_protobuf/message.h" #include "gtest/gtest.h" #include "pw_stream/memory_stream.h" #define ASSERT_OK(status) ASSERT_EQ(OkStatus(), status) namespace pw::protobuf { TEST(ProtoHelper, IterateMessage) { // clang-format off constexpr uint8_t encoded_proto[] = { // type=uint32, k=1, v=1 0x08, 0x01, // type=uint32, k=2, v=2 0x10, 0x02, // type=uint32, k=3, v=3 0x18, 0x03, }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); uint32_t count = 0; for (Message::Field field : parser) { ++count; EXPECT_EQ(field.field_number(), count); Uint32 value = field.As(); ASSERT_OK(value.status()); EXPECT_EQ(value.value(), count); } EXPECT_EQ(count, static_cast(3)); } TEST(ProtoHelper, MessageIterator) { // clang-format off std::uint8_t encoded_proto[] = { // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', // type=uint32, k=2, v=2 0x10, 0x02, }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); Message::iterator iter = parser.begin(); Message::iterator first = iter++; ASSERT_EQ(first, first); ASSERT_EQ(first->field_number(), static_cast(1)); String str = first->As(); ASSERT_OK(str.status()); Result cmp = str.Equal("foo 1"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); Message::iterator second = iter++; ASSERT_EQ(second, second); ASSERT_EQ(second->field_number(), static_cast(2)); Uint32 uint32_val = second->As(); ASSERT_OK(uint32_val.status()); ASSERT_EQ(uint32_val.value(), static_cast(2)); ASSERT_NE(first, second); ASSERT_NE(first, iter); ASSERT_NE(second, iter); ASSERT_EQ(iter, parser.end()); } TEST(ProtoHelper, MessageIteratorMalformedProto) { // clang-format off std::uint8_t encoded_proto[] = { // key = 1, str = "foo 1" 0x0a,0x05,'f','o','o',' ','1', // key = 0, str = "foo 2" (invalid) 0x02,0x05,'f','o','o',' ','2', // key = 3, str = "bar 1" 0x1a,0x05,'b','a','r',' ','1', }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); Message::iterator iter = parser.begin(); ASSERT_OK(iter.status()); // Second field has invalid field number ASSERT_FALSE((++iter).ok()); // Attempting to increment an invalid iterator result in it being end() ASSERT_EQ((++iter), parser.end()); // Test the c++ std loop behavior. bool expected_ok_status[] = {true, false}; size_t count = 0; for (Message::Field field : parser) { ASSERT_EQ(field.ok(), expected_ok_status[count++]); } // First element ok. Second element invalid. Iteration ends in the next // iteration. ASSERT_EQ(count, 2ULL); } TEST(ProtoHelper, InvalidMessageBeginIterator) { Message parser(Status::Internal()); ASSERT_FALSE(parser.begin().ok()); ASSERT_EQ(parser.begin(), parser.end()); } TEST(ProtoHelper, AsProtoInteger) { // clang-format off std::uint8_t encoded_proto[] = { // type: int32, k = 1, val = -123 0x08, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, // type: uint32, k = 2, val = 123 0x10, 0x7b, // type: sint32, k = 3, val = -456 0x18, 0x8f, 0x07, // type: fixed32, k = 4, val = 268435457 0x25, 0x01, 0x00, 0x00, 0x10, // type: sfixed32, k = 5, val = -268435457 0x2d, 0xff, 0xff, 0xff, 0xef, // type: int64, k = 6, val = -1099511627776 0x30, 0x80, 0x80, 0x80, 0x80, 0x80, 0xe0, 0xff, 0xff, 0xff, 0x01, // type: uint64, k = 7, val = 1099511627776 0x38, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20, // type: sint64, k = 8, val = -2199023255552 0x40, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, // type: fixed64, k = 9, val = 72057594037927937 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // type: sfixed64, k = 10, val = -72057594037927937 0x51, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, // type: float, k = 11, val = 123456.00 0x5d, 0x00, 0x20, 0xf1, 0x47, // type: double, k = 12, val = -123456.789 0x61, 0xc9, 0x76, 0xbe, 0x9f, 0x0c, 0x24, 0xfe, 0xc0, // type: bool, k = 13, val = true 0x68, 0x01, // type: bool, k = 14, val = false 0x70, 0x00 }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); { Int32 value = parser.AsInt32(1); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-123)); } { Uint32 value = parser.AsUint32(2); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(123)); } { Sint32 value = parser.AsSint32(3); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-456)); } { Fixed32 value = parser.AsFixed32(4); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(268435457)); } { Sfixed32 value = parser.AsSfixed32(5); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-268435457)); } { Int64 value = parser.AsInt64(6); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-1099511627776)); } { Uint64 value = parser.AsUint64(7); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(1099511627776)); } { Sint64 value = parser.AsSint64(8); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-2199023255552)); } { Fixed64 value = parser.AsFixed64(9); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(72057594037927937)); } { Sfixed64 value = parser.AsSfixed64(10); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-72057594037927937)); } { Float value = parser.AsFloat(11); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(123456.00)); } { Double value = parser.AsDouble(12); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(-123456.789)); } { Bool value = parser.AsBool(13); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(true)); } { Bool value = parser.AsBool(14); ASSERT_OK(value.status()); ASSERT_EQ(value.value(), static_cast(false)); } } TEST(ProtoHelper, AsString) { // message { // string str = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // `str`, k = 1, "string" 0x0a, 0x06, 's', 't', 'r', 'i', 'n', 'g', }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); constexpr uint32_t kFieldNumber = 1; String value = parser.AsString(kFieldNumber); ASSERT_OK(value.status()); Result cmp = value.Equal("string"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); cmp = value.Equal("other"); ASSERT_OK(cmp.status()); ASSERT_FALSE(cmp.value()); // The string is a prefix of the target string to compare. cmp = value.Equal("string and more"); ASSERT_OK(cmp.status()); ASSERT_FALSE(cmp.value()); // The target string to compare is a sub prefix of this string cmp = value.Equal("str"); ASSERT_OK(cmp.status()); ASSERT_FALSE(cmp.value()); } TEST(ProtoHelper, AsRepeatedStrings) { // Repeated field of string i.e. // // message RepeatedString { // repeated string msg_a = 1; // repeated string msg_b = 2; // } // clang-format off std::uint8_t encoded_proto[] = { // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', // key = 2, str = "foo 2" 0x12, 0x05, 'f', 'o', 'o', ' ', '2', // key = 1, str = "bar 1" 0x0a, 0x05, 'b', 'a', 'r', ' ', '1', // key = 2, str = "bar 2" 0x12, 0x05, 'b', 'a', 'r', ' ', '2', }; // clang-format on constexpr uint32_t kMsgAFieldNumber = 1; constexpr uint32_t kMsgBFieldNumber = 2; constexpr uint32_t kNonExistFieldNumber = 3; stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); // Field 'msg_a' { RepeatedStrings msg = parser.AsRepeatedStrings(kMsgAFieldNumber); std::string_view expected[] = { "foo 1", "bar 1", }; size_t count = 0; for (String ele : msg) { ASSERT_OK(ele.status()); Result res = ele.Equal(expected[count++]); ASSERT_OK(res.status()); ASSERT_TRUE(res.value()); } ASSERT_EQ(count, static_cast(2)); } // Field `msg_b` { RepeatedStrings msg = parser.AsRepeatedStrings(kMsgBFieldNumber); std::string_view expected[] = { "foo 2", "bar 2", }; size_t count = 0; for (String ele : msg) { ASSERT_OK(ele.status()); Result res = ele.Equal(expected[count++]); ASSERT_OK(res.status()); ASSERT_TRUE(res.value()); } ASSERT_EQ(count, static_cast(2)); } // non-existing field { RepeatedStrings msg = parser.AsRepeatedStrings(kNonExistFieldNumber); size_t count = 0; for ([[maybe_unused]] String ele : msg) { count++; } ASSERT_EQ(count, static_cast(0)); } } TEST(ProtoHelper, RepeatedFieldIterator) { // Repeated field of string i.e. // // message RepeatedString { // repeated string msg = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', // key = 1, str = "bar 1" 0x0a, 0x05, 'b', 'a', 'r', ' ', '1', }; // clang-format on constexpr uint32_t kFieldNumber = 1; stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); RepeatedStrings repeated_str = parser.AsRepeatedStrings(kFieldNumber); RepeatedStrings::iterator iter = repeated_str.begin(); RepeatedStrings::iterator first = iter++; ASSERT_EQ(first, first); Result cmp = first->Equal("foo 1"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); RepeatedStrings::iterator second = iter++; ASSERT_EQ(second, second); cmp = second->Equal("bar 1"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); ASSERT_NE(first, second); ASSERT_NE(first, iter); ASSERT_NE(second, iter); ASSERT_EQ(iter, repeated_str.end()); } TEST(ProtoHelper, RepeatedFieldIteratorMalformedFieldID) { // Repeated field of string i.e. // // message RepeatedString { // repeated string msg = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', // key = 0, str = "foo 1" (invalid) 0x02, 0x05, 'f', 'o', 'o', ' ', '1', // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); RepeatedStrings repeated_str = parser.AsRepeatedStrings(1); bool expected_ok[] = {true, false}; size_t count = 0; for (String s : repeated_str) { ASSERT_EQ(s.ok(), expected_ok[count++]); } // Iterator becomes invalid in the second iteration. Attempting to increment // causes it to become end(); Therefore, count should be incremented twice. ASSERT_EQ(count, 2ULL); } TEST(ProtoHelper, RepeatedFieldIteratorMalformedFieldIDBeginning) { // Repeated field of string i.e. // // message RepeatedString { // repeated string msg = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // key = 0, str = "foo 1" (invalid) 0x02, 0x05, 'f', 'o', 'o', ' ', '1', // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); RepeatedStrings repeated_str = parser.AsRepeatedStrings(1); bool expected_ok[] = {false}; size_t count = 0; for (String s : repeated_str) { ASSERT_EQ(s.ok(), expected_ok[count++]); } // Iterator becomes invalid in the second iteration. Attempting to increment // causes it to become end(); Therefore, count should be incremented twice. ASSERT_EQ(count, 1ULL); } TEST(ProtoHelper, RepeatedFieldIteratorMalformedDataLoss) { // Repeated field of string i.e. // // message RepeatedString { // repeated string msg = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // key = 1, str = "foo 1" 0x0a, 0x05, 'f', 'o', 'o', ' ', '1', // key = 0, str = "foo 1" (invalid) 0x0a, 0x10, 'f', 'o', 'o', ' ', '1', }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); RepeatedStrings repeated_str = parser.AsRepeatedStrings(1); bool expected_ok[] = {true, false}; size_t count = 0; for (String s : repeated_str) { ASSERT_EQ(s.ok(), expected_ok[count++]); } ASSERT_EQ(count, 2ULL); } TEST(ProtoHelper, AsMessage) { // A nested message: // // message Contact { // string number = 1; // string email = 2; // } // // message Person { // Contact info = 2; // } // clang-format off std::uint8_t encoded_proto[] = { // Person.info.number = "123456", .email = "foo@email.com" 0x12, 0x17, 0x0a, 0x06, '1', '2', '3', '4', '5', '6', 0x12, 0x0d, 'f', 'o', 'o', '@', 'e', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm', }; // clang-format on constexpr uint32_t kInfoFieldNumber = 2; constexpr uint32_t kNumberFieldNumber = 1; constexpr uint32_t kEmailFieldNumber = 2; stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); Message info = parser.AsMessage(kInfoFieldNumber); ASSERT_OK(info.status()); String number = info.AsString(kNumberFieldNumber); ASSERT_OK(number.status()); Result cmp = number.Equal("123456"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); String email = info.AsString(kEmailFieldNumber); ASSERT_OK(email.status()); cmp = email.Equal("foo@email.com"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); } TEST(ProtoHelper, AsRepeatedMessages) { // message Contact { // string number = 1; // string email = 2; // } // // message Person { // repeated Contact info = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // Person.Contact.number = "12345", .email = "foo@email.com" 0x0a, 0x16, 0x0a, 0x05, '1', '2', '3', '4', '5', 0x12, 0x0d, 'f', 'o', 'o', '@', 'e', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm', // Person.Contact.number = "67890", .email = "bar@email.com" 0x0a, 0x16, 0x0a, 0x05, '6', '7', '8', '9', '0', 0x12, 0x0d, 'b', 'a', 'r', '@', 'e', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm', }; // clang-format on constexpr uint32_t kInfoFieldNumber = 1; constexpr uint32_t kNumberFieldNumber = 1; constexpr uint32_t kEmailFieldNumber = 2; stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); RepeatedMessages messages = parser.AsRepeatedMessages(kInfoFieldNumber); ASSERT_OK(messages.status()); struct { std::string_view number; std::string_view email; } expected[] = { {"12345", "foo@email.com"}, {"67890", "bar@email.com"}, }; size_t count = 0; for (Message message : messages) { String number = message.AsString(kNumberFieldNumber); ASSERT_OK(number.status()); Result cmp = number.Equal(expected[count].number); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); String email = message.AsString(kEmailFieldNumber); ASSERT_OK(email.status()); cmp = email.Equal(expected[count].email); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); count++; } ASSERT_EQ(count, static_cast(2)); } TEST(ProtoHelper, AsStringToBytesMap) { // message Maps { // map map_a = 1; // map map_b = 2; // } // clang-format off std::uint8_t encoded_proto[] = { // map_a["key_bar"] = "bar_a", key = 1 0x0a, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', // map key 0x12, 0x05, 'b', 'a', 'r', '_', 'a', // map value // map_a["key_foo"] = "foo_a", key = 1 0x0a, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o', 0x12, 0x05, 'f', 'o', 'o', '_', 'a', // map_b["key_foo"] = "foo_b", key = 2 0x12, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o', 0x12, 0x05, 'f', 'o', 'o', 0x5f, 0x62, // map_b["key_bar"] = "bar_b", key = 2 0x12, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', 0x12, 0x05, 'b', 'a', 'r', 0x5f, 0x62, }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); { // Parse field 'map_a' constexpr uint32_t kFieldNumber = 1; StringMapParser string_map = parser.AsStringToStringMap(kFieldNumber); String value = string_map["key_foo"]; ASSERT_OK(value.status()); Result cmp = value.Equal("foo_a"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); value = string_map["key_bar"]; ASSERT_OK(value.status()); cmp = value.Equal("bar_a"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); // Non-existing key value = string_map["non-existing"]; ASSERT_EQ(value.status(), Status::NotFound()); } { // Parse field 'map_b' constexpr uint32_t kFieldNumber = 2; StringMapParser string_map = parser.AsStringToStringMap(kFieldNumber); String value = string_map["key_foo"]; ASSERT_OK(value.status()); Result cmp = value.Equal("foo_b"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); value = string_map["key_bar"]; ASSERT_OK(value.status()); cmp = value.Equal("bar_b"); ASSERT_OK(cmp.status()); ASSERT_TRUE(cmp.value()); // Non-existing key value = string_map["non-existing"]; ASSERT_EQ(value.status(), Status::NotFound()); } } TEST(ProtoHelper, AsStringToMessageMap) { // message Contact { // string number = 1; // string email = 2; // } // // message Contacts { // map staffs = 1; // } // clang-format off std::uint8_t encoded_proto[] = { // staffs['bar'] = {.number = '456, .email = "bar@email.com"} 0x0a, 0x1b, 0x0a, 0x03, 0x62, 0x61, 0x72, 0x12, 0x14, 0x0a, 0x03, 0x34, 0x35, 0x36, 0x12, 0x0d, 0x62, 0x61, 0x72, 0x40, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, // staffs['foo'] = {.number = '123', .email = "foo@email.com"} 0x0a, 0x1b, 0x0a, 0x03, 0x66, 0x6f, 0x6f, 0x12, 0x14, 0x0a, 0x03, 0x31, 0x32, 0x33, 0x12, 0x0d, 0x66, 0x6f, 0x6f, 0x40, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, }; // clang-format on constexpr uint32_t kStaffsFieldId = 1; constexpr uint32_t kNumberFieldId = 1; constexpr uint32_t kEmailFieldId = 2; stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); StringMapParser staffs = parser.AsStringToMessageMap(kStaffsFieldId); ASSERT_OK(staffs.status()); Message foo_staff = staffs["foo"]; ASSERT_OK(foo_staff.status()); String foo_number = foo_staff.AsString(kNumberFieldId); ASSERT_OK(foo_number.status()); Result foo_number_cmp = foo_number.Equal("123"); ASSERT_OK(foo_number_cmp.status()); ASSERT_TRUE(foo_number_cmp.value()); String foo_email = foo_staff.AsString(kEmailFieldId); ASSERT_OK(foo_email.status()); Result foo_email_cmp = foo_email.Equal("foo@email.com"); ASSERT_OK(foo_email_cmp.status()); ASSERT_TRUE(foo_email_cmp.value()); Message bar_staff = staffs["bar"]; ASSERT_OK(bar_staff.status()); String bar_number = bar_staff.AsString(kNumberFieldId); ASSERT_OK(bar_number.status()); Result bar_number_cmp = bar_number.Equal("456"); ASSERT_OK(bar_number_cmp.status()); ASSERT_TRUE(bar_number_cmp.value()); String bar_email = bar_staff.AsString(kEmailFieldId); ASSERT_OK(bar_email.status()); Result bar_email_cmp = bar_email.Equal("bar@email.com"); ASSERT_OK(bar_email_cmp.status()); ASSERT_TRUE(bar_email_cmp.value()); } TEST(ProtoHelper, AsStringToBytesMapMalformed) { // message Maps { // map map_a = 1; // map map_b = 2; // } // clang-format off std::uint8_t encoded_proto[] = { // map_a["key_bar"] = "bar_a", key = 1 0x0a, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', // map key 0x12, 0x05, 'b', 'a', 'r', '_', 'a', // map value // map_a["key_foo"] = "foo_a", key = 0 (invalid) 0x02, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o', 0x12, 0x05, 'f', 'o', 'o', '_', 'a', }; // clang-format on stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto))); Message parser = Message(reader, sizeof(encoded_proto)); // Parse field 'map_a' constexpr uint32_t kFieldNumber = 1; StringMapParser string_map = parser.AsStringToStringMap(kFieldNumber); bool expected_ok_status[] = {true, false}; size_t count = 0; for (StringToStringMapEntry entry : string_map) { ASSERT_EQ(entry.ok(), expected_ok_status[count]); ASSERT_EQ(entry.Key().ok(), expected_ok_status[count]); ASSERT_EQ(entry.Value().ok(), expected_ok_status[count]); count++; } ASSERT_EQ(count, 2ULL); } } // namespace pw::protobuf