#include #include #include #include "esphome/components/api/api_buffer.h" #include "esphome/components/api/proto.h" namespace esphome::api::testing { // Sub-message whose body is raw bytes, so the body size is set directly by the test. struct BlobMessage { const uint8_t *data; uint32_t len; static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); // An empty vector's data() may be null, and memcpy needs a valid source even for zero bytes if (msg.len == 0) return pos; return ProtoEncode::encode_raw(pos PROTO_ENCODE_DEBUG_ARG, msg.data, msg.len); } static uint32_t calc_size_msg(const void *self) { return static_cast(self)->len; } }; static void append_varint(std::vector &out, uint32_t value) { while (value > 0x7F) { out.push_back(static_cast(value | 0x80)); value >>= 7; } out.push_back(static_cast(value)); } static std::vector make_body(uint32_t len) { std::vector body(len); for (uint32_t i = 0; i < len; i++) body[i] = static_cast(i * 7 + 1); return body; } static std::vector expected_field(uint32_t field_id, const std::vector &body) { std::vector out; append_varint(out, (field_id << 3) | 2); append_varint(out, body.size()); out.insert(out.end(), body.begin(), body.end()); return out; } // Encodes into a buffer of exactly the expected size, so any overrun trips ASan or the debug bounds check. template static void verify(uint32_t field_id, uint32_t body_len) { std::vector body = make_body(body_len); std::vector expected = expected_field(field_id, body); if (OPTIONAL && body_len == 0) expected.clear(); BlobMessage msg{body.data(), body_len}; APIBuffer buf; ASSERT_TRUE(buf.resize(expected.empty() ? 1 : expected.size())); uint8_t *pos = buf.data(); #ifdef ESPHOME_DEBUG_API uint8_t *proto_debug_end_ = buf.data() + buf.size(); #endif uint8_t *end; if constexpr (OPTIONAL) { end = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, field_id, msg); } else { end = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, field_id, msg); } ASSERT_EQ(static_cast(end - buf.data()), expected.size()) << "field " << field_id << " body " << body_len; EXPECT_EQ(std::vector(buf.data(), end), expected) << "field " << field_id << " body " << body_len; } TEST(ProtoSubMessage, OneByteTag) { verify(4, 10); } TEST(ProtoSubMessage, TwoByteTag) { verify(20, 10); } TEST(ProtoSubMessage, EmptyBody) { verify(20, 0); } TEST(ProtoSubMessage, LongestOneByteLength) { verify(20, 127); } // The length outgrows its reserved byte, so the body is moved forward TEST(ProtoSubMessage, TwoByteLength) { verify(20, 128); verify(25, 200); } TEST(ProtoSubMessage, ThreeByteLength) { verify(4, 20000); } TEST(ProtoOptionalSubMessage, EmptyIsSkipped) { verify(22, 0); } TEST(ProtoOptionalSubMessage, OneByteTag) { verify(1, 10); } TEST(ProtoOptionalSubMessage, TwoByteTagAndLength) { verify(22, 200); } } // namespace esphome::api::testing