// 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/map_utils.h" #include #include "gtest/gtest.h" #include "pw_stream/memory_stream.h" #include "pw_stream/stream.h" #define ASSERT_OK(status) ASSERT_EQ(OkStatus(), status) namespace pw::protobuf { TEST(ProtoHelper, WriteProtoStringToBytesMapEntry) { // The following defines an instance of the message below: // // message Maps { // map map_a = 1; // map map_b = 2; // } // // where // // Maps.map_a['key_foo'] = 'foo_a' // Maps.map_a['key_bar'] = 'bar_a' // // Maps.map_b['key_foo'] = 'foo_b' // Maps.map_b['key_bar'] = 'bar_b' // // 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', '_', 'b', // map_b["key_bar"] = "bar_b", key = 2 0x12, 0x10, 0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', 0x12, 0x05, 'b', 'a', 'r', '_', 'b', }; // clang-format on // Now construct the same message with WriteStringToBytesMapEntry std::byte dst_buffer[sizeof(encoded_proto)]; stream::MemoryWriter writer(dst_buffer); const struct { uint32_t field_number; std::string_view key; std::string_view value; } kMapData[] = { {1, "key_bar", "bar_a"}, {1, "key_foo", "foo_a"}, {2, "key_foo", "foo_b"}, {2, "key_bar", "bar_b"}, }; std::byte stream_pipe_buffer[1]; for (auto ele : kMapData) { stream::MemoryReader key_reader(as_bytes(span{ele.key})); stream::MemoryReader value_reader(as_bytes(span{ele.value})); ASSERT_OK(WriteProtoStringToBytesMapEntry(ele.field_number, key_reader, ele.key.size(), value_reader, ele.value.size(), stream_pipe_buffer, writer)); } ASSERT_EQ(memcmp(dst_buffer, encoded_proto, sizeof(dst_buffer)), 0); } TEST(ProtoHelper, WriteProtoStringToBytesMapEntryExceedsWriteLimit) { // Construct an instance of the message below: // // message Maps { // map map_a = 1; // } // // where // // Maps.map_a['key_bar'] = 'bar_a'. The needed buffer size is 18 in this // case: // // { // 0x0a, 0x10, // 0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', // 0x12, 0x05, 'b', 'a', 'r', '_', 'a', // } // // Use a smaller buffer. std::byte encode_buffer[17]; stream::MemoryWriter writer(encode_buffer); constexpr uint32_t kFieldNumber = 1; std::string_view key = "key_bar"; std::string_view value = "bar_a"; stream::MemoryReader key_reader(as_bytes(span{key})); stream::MemoryReader value_reader(as_bytes(span{value})); std::byte stream_pipe_buffer[1]; ASSERT_EQ( WriteProtoStringToBytesMapEntry(kFieldNumber, key_reader, key_reader.ConservativeReadLimit(), value_reader, value_reader.ConservativeReadLimit(), stream_pipe_buffer, writer), Status::ResourceExhausted()); } TEST(ProtoHelper, WriteProtoStringToBytesMapEntryInvalidArgument) { std::byte encode_buffer[17]; stream::MemoryWriter writer(encode_buffer); std::string_view key = "key_bar"; std::string_view value = "bar_a"; stream::MemoryReader key_reader(as_bytes(span{key})); stream::MemoryReader value_reader(as_bytes(span{value})); std::byte stream_pipe_buffer[1]; ASSERT_EQ( WriteProtoStringToBytesMapEntry(19091, key_reader, key_reader.ConservativeReadLimit(), value_reader, value_reader.ConservativeReadLimit(), stream_pipe_buffer, writer), Status::InvalidArgument()); } } // namespace pw::protobuf