| // Copyright 2022 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 <array> |
| #include <span> |
| #include <stdexcept> |
| #include <string_view> |
| |
| #include "gtest/gtest.h" |
| #include "pw_bytes/span.h" |
| #include "pw_status/status.h" |
| #include "pw_status/status_with_size.h" |
| #include "pw_stream/memory_stream.h" |
| |
| // These header files contain the code generated by the pw_protobuf plugin. |
| // They are re-generated every time the tests are built and are used by the |
| // tests to ensure that the interface remains consistent. |
| // |
| // The purpose of the tests in this file is primarily to verify that the |
| // generated C++ interface is valid rather than the correctness of the |
| // low-level encoder. |
| #include "pw_protobuf_test_protos/full_test.pwpb.h" |
| #include "pw_protobuf_test_protos/importer.pwpb.h" |
| #include "pw_protobuf_test_protos/non_pw_package.pwpb.h" |
| #include "pw_protobuf_test_protos/proto2.pwpb.h" |
| #include "pw_protobuf_test_protos/repeated.pwpb.h" |
| |
| namespace pw::protobuf { |
| namespace { |
| |
| using namespace pw::protobuf::test; |
| |
| TEST(Codegen, StreamDecoder) { |
| // clang-format off |
| constexpr uint8_t proto_data[] = { |
| // pigweed.magic_number |
| 0x08, 0x49, |
| // pigweed.ziggy |
| 0x10, 0xdd, 0x01, |
| // pigweed.error_message |
| 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ', |
| 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r', |
| // pigweed.bin |
| 0x40, 0x01, |
| // pigweed.pigweed |
| 0x3a, 0x02, |
| // pigweed.pigweed.status |
| 0x08, 0x02, |
| // pigweed.proto |
| 0x4a, 0x56, |
| // pigweed.proto.bin |
| 0x10, 0x00, |
| // pigweed.proto.pigweed_pigweed_bin |
| 0x18, 0x00, |
| // pigweed.proto.pigweed_protobuf_bin |
| 0x20, 0x01, |
| // pigweed.proto.meta |
| 0x2a, 0x0f, |
| // pigweed.proto.meta.file_name |
| 0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd', |
| // pigweed.proto.meta.status |
| 0x10, 0x02, |
| // pigweed.proto.nested_pigweed |
| 0x0a, 0x3d, |
| // pigweed.proto.nested_pigweed.error_message |
| 0x2a, 0x10, 'h', 'e', 'r', 'e', ' ', 'w', 'e', ' ', |
| 'g', 'o', ' ', 'a', 'g', 'a', 'i', 'n', |
| // pigweed.proto.nested_pigweed.magic_number |
| 0x08, 0xe8, 0x04, |
| // pigweed.proto.nested_pigweed.device_info |
| 0x32, 0x26, |
| // pigweed.proto.nested_pigweed.device_info.attributes[0] |
| 0x22, 0x10, |
| // pigweed.proto.nested_pigweed.device_info.attributes[0].key |
| 0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', |
| // pigweed.proto.nested_pigweed.device_info.attributes[0].value |
| 0x12, 0x05, '5', '.', '3', '.', '1', |
| // pigweed.proto.nested_pigweed.device_info.attributes[1] |
| 0x22, 0x10, |
| // pigweed.proto.nested_pigweed.device_info.attributes[1].key |
| 0x0a, 0x04, 'c', 'h', 'i', 'p', |
| // pigweed.proto.nested_pigweed.device_info.attributes[1].value |
| 0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c', |
| // pigweed.proto.nested_pigweed.device_info.status |
| 0x18, 0x03, |
| // pigweed.id[0] |
| 0x52, 0x02, |
| // pigweed.id[0].id |
| 0x08, 0x31, |
| // pigweed.id[1] |
| 0x52, 0x02, |
| // pigweed.id[1].id |
| 0x08, 0x39, |
| // pigweed.id[2] |
| 0x52, 0x02, |
| // pigweed.id[2].id |
| 0x08, 0x4b, |
| // pigweed.id[3] |
| 0x52, 0x02, |
| // pigweed.id[3].id |
| 0x08, 0x67, |
| // pigweed.id[4] |
| 0x52, 0x03, |
| // pigweed.id[4].id |
| 0x08, 0x8d, 0x01 |
| |
| }; |
| // clang-format on |
| |
| stream::MemoryReader reader(std::as_bytes(std::span(proto_data))); |
| Pigweed::StreamDecoder pigweed(reader); |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::MAGIC_NUMBER); |
| Result<uint32_t> magic_number = pigweed.ReadMagicNumber(); |
| EXPECT_EQ(magic_number.status(), OkStatus()); |
| EXPECT_EQ(magic_number.value(), 0x49u); |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ZIGGY); |
| Result<int32_t> ziggy = pigweed.ReadZiggy(); |
| EXPECT_EQ(ziggy.status(), OkStatus()); |
| EXPECT_EQ(ziggy.value(), -111); |
| |
| constexpr std::string_view kExpectedErrorMessage{"not a typewriter"}; |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE); |
| std::array<char, 32> error_message{}; |
| StatusWithSize error_message_status = pigweed.ReadErrorMessage(error_message); |
| EXPECT_EQ(error_message_status.status(), OkStatus()); |
| EXPECT_EQ(error_message_status.size(), kExpectedErrorMessage.size()); |
| EXPECT_EQ(std::memcmp(error_message.data(), |
| kExpectedErrorMessage.data(), |
| kExpectedErrorMessage.size()), |
| 0); |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::BIN); |
| Result<Pigweed::Protobuf::Binary> bin = pigweed.ReadBin(); |
| EXPECT_EQ(bin.status(), OkStatus()); |
| EXPECT_EQ(bin.value(), Pigweed::Protobuf::Binary::ZERO); |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::PIGWEED); |
| { |
| Pigweed::Pigweed::StreamDecoder pigweed_pigweed = |
| pigweed.GetPigweedDecoder(); |
| |
| EXPECT_EQ(pigweed_pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed_pigweed.Field().value(), |
| Pigweed::Pigweed::Fields::STATUS); |
| Result<Bool> pigweed_status = pigweed_pigweed.ReadStatus(); |
| EXPECT_EQ(pigweed_status.status(), OkStatus()); |
| EXPECT_EQ(pigweed_status.value(), Bool::FILE_NOT_FOUND); |
| |
| EXPECT_EQ(pigweed_pigweed.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::PROTO); |
| { |
| Proto::StreamDecoder proto = pigweed.GetProtoDecoder(); |
| |
| EXPECT_EQ(proto.Next(), OkStatus()); |
| EXPECT_EQ(proto.Field().value(), Proto::Fields::BIN); |
| Result<Proto::Binary> proto_bin = proto.ReadBin(); |
| EXPECT_EQ(proto_bin.status(), OkStatus()); |
| EXPECT_EQ(proto_bin.value(), Proto::Binary::OFF); |
| |
| EXPECT_EQ(proto.Next(), OkStatus()); |
| EXPECT_EQ(proto.Field().value(), Proto::Fields::PIGWEED_PIGWEED_BIN); |
| Result<Pigweed::Pigweed::Binary> proto_pigweed_bin = |
| proto.ReadPigweedPigweedBin(); |
| EXPECT_EQ(proto_pigweed_bin.status(), OkStatus()); |
| EXPECT_EQ(proto_pigweed_bin.value(), Pigweed::Pigweed::Binary::ZERO); |
| |
| EXPECT_EQ(proto.Next(), OkStatus()); |
| EXPECT_EQ(proto.Field().value(), Proto::Fields::PIGWEED_PROTOBUF_BIN); |
| Result<Pigweed::Protobuf::Binary> proto_protobuf_bin = |
| proto.ReadPigweedProtobufBin(); |
| EXPECT_EQ(proto_protobuf_bin.status(), OkStatus()); |
| EXPECT_EQ(proto_protobuf_bin.value(), Pigweed::Protobuf::Binary::ZERO); |
| |
| EXPECT_EQ(proto.Next(), OkStatus()); |
| EXPECT_EQ(proto.Field().value(), Proto::Fields::META); |
| { |
| Pigweed::Protobuf::Compiler::StreamDecoder meta = proto.GetMetaDecoder(); |
| |
| constexpr std::string_view kExpectedFileName{"/etc/passwd"}; |
| |
| EXPECT_EQ(meta.Next(), OkStatus()); |
| EXPECT_EQ(meta.Field().value(), |
| Pigweed::Protobuf::Compiler::Fields::FILE_NAME); |
| std::array<char, 32> meta_file_name{}; |
| StatusWithSize meta_file_name_status = meta.ReadFileName(meta_file_name); |
| EXPECT_EQ(meta_file_name_status.status(), OkStatus()); |
| EXPECT_EQ(meta_file_name_status.size(), kExpectedFileName.size()); |
| EXPECT_EQ(std::memcmp(meta_file_name.data(), |
| kExpectedFileName.data(), |
| kExpectedFileName.size()), |
| 0); |
| |
| EXPECT_EQ(meta.Next(), OkStatus()); |
| EXPECT_EQ(meta.Field().value(), |
| Pigweed::Protobuf::Compiler::Fields::STATUS); |
| Result<Pigweed::Protobuf::Compiler::Status> meta_status = |
| meta.ReadStatus(); |
| EXPECT_EQ(meta_status.status(), OkStatus()); |
| EXPECT_EQ(meta_status.value(), |
| Pigweed::Protobuf::Compiler::Status::FUBAR); |
| |
| EXPECT_EQ(meta.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(proto.Next(), OkStatus()); |
| EXPECT_EQ(proto.Field().value(), Proto::Fields::PIGWEED); |
| { |
| Pigweed::StreamDecoder proto_pigweed = proto.GetPigweedDecoder(); |
| |
| constexpr std::string_view kExpectedProtoErrorMessage{"here we go again"}; |
| |
| EXPECT_EQ(proto_pigweed.Next(), OkStatus()); |
| EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE); |
| std::array<char, 32> proto_pigweed_error_message{}; |
| StatusWithSize proto_pigweed_error_message_status = |
| proto_pigweed.ReadErrorMessage(proto_pigweed_error_message); |
| EXPECT_EQ(proto_pigweed_error_message_status.status(), OkStatus()); |
| EXPECT_EQ(proto_pigweed_error_message_status.size(), |
| kExpectedProtoErrorMessage.size()); |
| EXPECT_EQ(std::memcmp(proto_pigweed_error_message.data(), |
| kExpectedProtoErrorMessage.data(), |
| kExpectedProtoErrorMessage.size()), |
| 0); |
| |
| EXPECT_EQ(proto_pigweed.Next(), OkStatus()); |
| EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::MAGIC_NUMBER); |
| Result<uint32_t> proto_pigweed_magic_number = |
| proto_pigweed.ReadMagicNumber(); |
| EXPECT_EQ(proto_pigweed_magic_number.status(), OkStatus()); |
| EXPECT_EQ(proto_pigweed_magic_number.value(), 616u); |
| |
| EXPECT_EQ(proto_pigweed.Next(), OkStatus()); |
| EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::DEVICE_INFO); |
| { |
| DeviceInfo::StreamDecoder device_info = |
| proto_pigweed.GetDeviceInfoDecoder(); |
| |
| EXPECT_EQ(device_info.Next(), OkStatus()); |
| EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::ATTRIBUTES); |
| { |
| KeyValuePair::StreamDecoder key_value_pair = |
| device_info.GetAttributesDecoder(); |
| |
| constexpr std::string_view kExpectedKey{"version"}; |
| constexpr std::string_view kExpectedValue{"5.3.1"}; |
| |
| EXPECT_EQ(key_value_pair.Next(), OkStatus()); |
| EXPECT_EQ(key_value_pair.Field().value(), KeyValuePair::Fields::KEY); |
| std::array<char, 32> key{}; |
| StatusWithSize key_status = key_value_pair.ReadKey(key); |
| EXPECT_EQ(key_status.status(), OkStatus()); |
| EXPECT_EQ(key_status.size(), kExpectedKey.size()); |
| EXPECT_EQ( |
| std::memcmp(key.data(), kExpectedKey.data(), kExpectedKey.size()), |
| 0); |
| |
| EXPECT_EQ(key_value_pair.Next(), OkStatus()); |
| EXPECT_EQ(key_value_pair.Field().value(), |
| KeyValuePair::Fields::VALUE); |
| std::array<char, 32> value{}; |
| StatusWithSize value_status = key_value_pair.ReadValue(value); |
| EXPECT_EQ(value_status.status(), OkStatus()); |
| EXPECT_EQ(value_status.size(), kExpectedValue.size()); |
| EXPECT_EQ( |
| std::memcmp( |
| value.data(), kExpectedValue.data(), kExpectedValue.size()), |
| 0); |
| |
| EXPECT_EQ(key_value_pair.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(device_info.Next(), OkStatus()); |
| EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::ATTRIBUTES); |
| { |
| KeyValuePair::StreamDecoder key_value_pair = |
| device_info.GetAttributesDecoder(); |
| |
| constexpr std::string_view kExpectedKey{"chip"}; |
| constexpr std::string_view kExpectedValue{"left-soc"}; |
| |
| EXPECT_EQ(key_value_pair.Next(), OkStatus()); |
| EXPECT_EQ(key_value_pair.Field().value(), KeyValuePair::Fields::KEY); |
| std::array<char, 32> key{}; |
| StatusWithSize key_status = key_value_pair.ReadKey(key); |
| EXPECT_EQ(key_status.status(), OkStatus()); |
| EXPECT_EQ(key_status.size(), kExpectedKey.size()); |
| EXPECT_EQ( |
| std::memcmp(key.data(), kExpectedKey.data(), kExpectedKey.size()), |
| 0); |
| |
| EXPECT_EQ(key_value_pair.Next(), OkStatus()); |
| EXPECT_EQ(key_value_pair.Field().value(), |
| KeyValuePair::Fields::VALUE); |
| std::array<char, 32> value{}; |
| StatusWithSize value_status = key_value_pair.ReadValue(value); |
| EXPECT_EQ(value_status.status(), OkStatus()); |
| EXPECT_EQ(value_status.size(), kExpectedValue.size()); |
| EXPECT_EQ( |
| std::memcmp( |
| value.data(), kExpectedValue.data(), kExpectedValue.size()), |
| 0); |
| |
| EXPECT_EQ(key_value_pair.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(device_info.Next(), OkStatus()); |
| EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::STATUS); |
| Result<DeviceInfo::DeviceStatus> device_info_status = |
| device_info.ReadStatus(); |
| EXPECT_EQ(device_info_status.status(), OkStatus()); |
| EXPECT_EQ(device_info_status.value(), DeviceInfo::DeviceStatus::PANIC); |
| |
| EXPECT_EQ(device_info.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(proto_pigweed.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(proto.Next(), Status::OutOfRange()); |
| } |
| |
| for (int i = 0; i < 5; ++i) { |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ID); |
| |
| Proto::ID::StreamDecoder id = pigweed.GetIdDecoder(); |
| |
| EXPECT_EQ(id.Next(), OkStatus()); |
| EXPECT_EQ(id.Field().value(), Proto::ID::Fields::ID); |
| Result<uint32_t> id_id = id.ReadId(); |
| EXPECT_EQ(id_id.status(), OkStatus()); |
| EXPECT_EQ(id_id.value(), 5u * i * i + 3 * i + 49); |
| |
| EXPECT_EQ(id.Next(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(pigweed.Next(), Status::OutOfRange()); |
| } |
| |
| TEST(Codegen, ResourceExhausted) { |
| // clang-format off |
| constexpr uint8_t proto_data[] = { |
| // pigweed.error_message |
| 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ', |
| 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r', |
| }; |
| // clang-format on |
| |
| stream::MemoryReader reader(std::as_bytes(std::span(proto_data))); |
| Pigweed::StreamDecoder pigweed(reader); |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE); |
| std::array<char, 8> error_message{}; |
| StatusWithSize error_message_status = pigweed.ReadErrorMessage(error_message); |
| EXPECT_EQ(error_message_status.status(), Status::ResourceExhausted()); |
| EXPECT_EQ(error_message_status.size(), 0u); |
| |
| EXPECT_EQ(pigweed.Next(), Status::OutOfRange()); |
| } |
| |
| TEST(Codegen, BytesReader) { |
| // clang-format off |
| constexpr uint8_t proto_data[] = { |
| // pigweed.error_message |
| 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ', |
| 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r', |
| }; |
| // clang-format on |
| |
| stream::MemoryReader reader(std::as_bytes(std::span(proto_data))); |
| Pigweed::StreamDecoder pigweed(reader); |
| |
| constexpr std::string_view kExpectedErrorMessage{"not a typewriter"}; |
| |
| EXPECT_EQ(pigweed.Next(), OkStatus()); |
| EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE); |
| { |
| StreamDecoder::BytesReader bytes_reader = pigweed.GetErrorMessageReader(); |
| EXPECT_EQ(bytes_reader.field_size(), kExpectedErrorMessage.size()); |
| |
| std::array<std::byte, 32> error_message{}; |
| Result<ByteSpan> result = bytes_reader.Read(error_message); |
| EXPECT_EQ(result.status(), OkStatus()); |
| EXPECT_EQ(result.value().size(), kExpectedErrorMessage.size()); |
| EXPECT_EQ(std::memcmp(result.value().data(), |
| kExpectedErrorMessage.data(), |
| kExpectedErrorMessage.size()), |
| 0); |
| |
| result = bytes_reader.Read(error_message); |
| EXPECT_EQ(result.status(), Status::OutOfRange()); |
| } |
| |
| EXPECT_EQ(pigweed.Next(), Status::OutOfRange()); |
| } |
| |
| } // namespace |
| } // namespace pw::protobuf |