| // 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(as_bytes(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<Uint32>(); |
| ASSERT_OK(value.status()); |
| EXPECT_EQ(value.value(), count); |
| } |
| |
| EXPECT_EQ(count, static_cast<uint32_t>(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(as_bytes(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<uint32_t>(1)); |
| String str = first->As<String>(); |
| ASSERT_OK(str.status()); |
| Result<bool> 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<uint32_t>(2)); |
| Uint32 uint32_val = second->As<Uint32>(); |
| ASSERT_OK(uint32_val.status()); |
| ASSERT_EQ(uint32_val.value(), static_cast<uint32_t>(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(as_bytes(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(as_bytes(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<int32_t>(-123)); |
| } |
| |
| { |
| Uint32 value = parser.AsUint32(2); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<uint32_t>(123)); |
| } |
| |
| { |
| Sint32 value = parser.AsSint32(3); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<int32_t>(-456)); |
| } |
| |
| { |
| Fixed32 value = parser.AsFixed32(4); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<uint32_t>(268435457)); |
| } |
| |
| { |
| Sfixed32 value = parser.AsSfixed32(5); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<int32_t>(-268435457)); |
| } |
| |
| { |
| Int64 value = parser.AsInt64(6); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<int64_t>(-1099511627776)); |
| } |
| |
| { |
| Uint64 value = parser.AsUint64(7); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<uint64_t>(1099511627776)); |
| } |
| |
| { |
| Sint64 value = parser.AsSint64(8); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<int64_t>(-2199023255552)); |
| } |
| |
| { |
| Fixed64 value = parser.AsFixed64(9); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<uint64_t>(72057594037927937)); |
| } |
| |
| { |
| Sfixed64 value = parser.AsSfixed64(10); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<int64_t>(-72057594037927937)); |
| } |
| |
| { |
| Float value = parser.AsFloat(11); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<float>(123456.00)); |
| } |
| |
| { |
| Double value = parser.AsDouble(12); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<double>(-123456.789)); |
| } |
| |
| { |
| Bool value = parser.AsBool(13); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<bool>(true)); |
| } |
| |
| { |
| Bool value = parser.AsBool(14); |
| ASSERT_OK(value.status()); |
| ASSERT_EQ(value.value(), static_cast<bool>(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(as_bytes(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<bool> 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(as_bytes(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<bool> res = ele.Equal(expected[count++]); |
| ASSERT_OK(res.status()); |
| ASSERT_TRUE(res.value()); |
| } |
| |
| ASSERT_EQ(count, static_cast<size_t>(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<bool> res = ele.Equal(expected[count++]); |
| ASSERT_OK(res.status()); |
| ASSERT_TRUE(res.value()); |
| } |
| |
| ASSERT_EQ(count, static_cast<size_t>(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<size_t>(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(as_bytes(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<bool> 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(as_bytes(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(as_bytes(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(as_bytes(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(as_bytes(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<bool> 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(as_bytes(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<bool> 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<size_t>(2)); |
| } |
| |
| TEST(ProtoHelper, AsStringToBytesMap) { |
| // message Maps { |
| // map<string, string> map_a = 1; |
| // map<string, string> 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(as_bytes(span(encoded_proto))); |
| Message parser = Message(reader, sizeof(encoded_proto)); |
| |
| { |
| // Parse field 'map_a' |
| constexpr uint32_t kFieldNumber = 1; |
| StringMapParser<String> string_map = |
| parser.AsStringToStringMap(kFieldNumber); |
| |
| String value = string_map["key_foo"]; |
| ASSERT_OK(value.status()); |
| Result<bool> 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> string_map = |
| parser.AsStringToStringMap(kFieldNumber); |
| |
| String value = string_map["key_foo"]; |
| ASSERT_OK(value.status()); |
| Result<bool> 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<string, Contact> 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(as_bytes(span(encoded_proto))); |
| Message parser = Message(reader, sizeof(encoded_proto)); |
| |
| StringMapParser<Message> 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<bool> 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<bool> 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<bool> 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<bool> 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<string, string> map_a = 1; |
| // map<string, string> 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(as_bytes(span(encoded_proto))); |
| Message parser = Message(reader, sizeof(encoded_proto)); |
| |
| // Parse field 'map_a' |
| constexpr uint32_t kFieldNumber = 1; |
| StringMapParser<String> 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 |