blob: 8e30d5c787eff91aae129d6b292cd1428382bb97 [file] [log] [blame]
// 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_log_rpc/log_filter_service.h"
#include <array>
#include <cstdint>
#include <limits>
#include "gtest/gtest.h"
#include "pw_bytes/endian.h"
#include "pw_log/proto/log.pwpb.h"
#include "pw_log_rpc/log_filter.h"
#include "pw_log_rpc/log_filter_map.h"
#include "pw_protobuf/bytes_utils.h"
#include "pw_protobuf/decoder.h"
#include "pw_result/result.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/raw/test_method_context.h"
namespace pw::log_rpc {
namespace {
class FilterServiceTest : public ::testing::Test {
public:
FilterServiceTest() : filter_map_(filters_) {}
protected:
FilterMap filter_map_;
static constexpr size_t kMaxFilterRules = 4;
std::array<Filter::Rule, kMaxFilterRules> rules1_;
std::array<Filter::Rule, kMaxFilterRules> rules2_;
std::array<Filter::Rule, kMaxFilterRules> rules3_;
static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1_{
std::byte(65), std::byte(66), std::byte(67), std::byte(0)};
static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2_{
std::byte(68), std::byte(69), std::byte(70), std::byte(0)};
static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3_{
std::byte(71), std::byte(72), std::byte(73), std::byte(0)};
static constexpr size_t kMaxFilters = 3;
std::array<Filter, kMaxFilters> filters_ = {
Filter(filter_id1_, rules1_),
Filter(filter_id2_, rules2_),
Filter(filter_id3_, rules3_),
};
};
TEST_F(FilterServiceTest, GetFilterIds) {
PW_RAW_TEST_METHOD_CONTEXT(FilterService, ListFilterIds, 1)
context(filter_map_);
context.call({});
ASSERT_EQ(OkStatus(), context.status());
ASSERT_TRUE(context.done());
ASSERT_EQ(context.responses().size(), 1u);
protobuf::Decoder decoder(context.responses()[0]);
for (const auto& filter : filter_map_.filters()) {
ASSERT_EQ(decoder.Next(), OkStatus());
ASSERT_EQ(decoder.FieldNumber(), 1u); // filter_id
ConstByteSpan filter_id;
ASSERT_EQ(decoder.ReadBytes(&filter_id), OkStatus());
ASSERT_EQ(filter_id.size(), filter.id().size());
EXPECT_EQ(
std::memcmp(filter_id.data(), filter.id().data(), filter_id.size()), 0);
}
EXPECT_FALSE(decoder.Next().ok());
// No IDs reported when the filter map is empty.
FilterMap empty_filter_map({});
PW_RAW_TEST_METHOD_CONTEXT(FilterService, ListFilterIds, 1)
no_filter_context(empty_filter_map);
no_filter_context.call({});
ASSERT_EQ(OkStatus(), no_filter_context.status());
ASSERT_TRUE(no_filter_context.done());
ASSERT_EQ(no_filter_context.responses().size(), 1u);
protobuf::Decoder no_filter_decoder(no_filter_context.responses()[0]);
uint32_t filter_count = 0;
while (no_filter_decoder.Next().ok()) {
EXPECT_EQ(no_filter_decoder.FieldNumber(), 1u); // filter_id
++filter_count;
}
EXPECT_EQ(filter_count, 0u);
}
Status EncodeFilterRule(const Filter::Rule& rule,
log::FilterRule::StreamEncoder& encoder) {
PW_TRY(
encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal));
PW_TRY(encoder.WriteModuleEquals(rule.module_equals));
PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set));
PW_TRY(encoder.WriteThreadEquals(rule.thread_equals));
return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action));
}
Status EncodeFilter(const Filter& filter, log::Filter::StreamEncoder& encoder) {
for (auto& rule : filter.rules()) {
log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder();
PW_TRY(EncodeFilterRule(rule, rule_encoder));
}
return OkStatus();
}
Result<ConstByteSpan> EncodeFilterRequest(const Filter& filter,
ByteSpan buffer) {
stream::MemoryWriter writer(buffer);
std::byte encode_buffer[256];
protobuf::StreamEncoder encoder(writer, encode_buffer);
PW_TRY(encoder.WriteBytes(
static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER_ID),
filter.id()));
{
log::Filter::StreamEncoder filter_encoder = encoder.GetNestedEncoder(
static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER));
PW_TRY(EncodeFilter(filter, filter_encoder));
} // Let the StreamEncoder destructor finalize the data.
return ConstByteSpan(writer.data(), writer.bytes_written());
}
void VerifyRule(const Filter::Rule& rule, const Filter::Rule& expected_rule) {
EXPECT_EQ(rule.level_greater_than_or_equal,
expected_rule.level_greater_than_or_equal);
EXPECT_EQ(rule.module_equals, expected_rule.module_equals);
EXPECT_EQ(rule.any_flags_set, expected_rule.any_flags_set);
EXPECT_EQ(rule.thread_equals, expected_rule.thread_equals);
EXPECT_EQ(rule.action, expected_rule.action);
}
TEST_F(FilterServiceTest, SetFilterRules) {
const std::array<Filter::Rule, kMaxFilterRules> new_rules{{
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
.any_flags_set = 0x0f,
.module_equals{std::byte(123)},
.thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
},
{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xef,
.module_equals{},
.thread_equals{},
},
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = 0x1234,
.module_equals{std::byte(99)},
.thread_equals{std::byte('P'),
std::byte('O'),
std::byte('W'),
std::byte('E'),
std::byte('R')},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0,
.module_equals{std::byte(4)},
.thread_equals{std::byte('P'),
std::byte('O'),
std::byte('W'),
std::byte('E'),
std::byte('R')},
},
}};
const Filter new_filter(
filters_[0].id(),
const_cast<std::array<Filter::Rule, kMaxFilterRules>&>(new_rules));
std::byte request_buffer[512];
const auto request = EncodeFilterRequest(new_filter, request_buffer);
ASSERT_EQ(request.status(), OkStatus());
PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
context(filter_map_);
context.call(request.value());
ASSERT_EQ(OkStatus(), context.status());
size_t i = 0;
for (const auto& rule : filters_[0].rules()) {
VerifyRule(rule, new_rules[i++]);
}
}
TEST_F(FilterServiceTest, SetFilterRulesWhenUsedByDrain) {
const std::array<Filter::Rule, kMaxFilterRules> new_filter_rules{{
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::CRITICAL_LEVEL,
.any_flags_set = 0xfd,
.module_equals{std::byte(543)},
.thread_equals{std::byte('M'),
std::byte('0'),
std::byte('L'),
std::byte('O'),
std::byte('G')},
},
{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xca,
.module_equals{},
.thread_equals{},
},
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = 0xabcd,
.module_equals{std::byte(9000)},
.thread_equals{std::byte('P'),
std::byte('O'),
std::byte('W'),
std::byte('E'),
std::byte('R')},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0,
.module_equals{std::byte(123)},
.thread_equals{std::byte('P'),
std::byte('O'),
std::byte('W'),
std::byte('E'),
std::byte('R')},
},
}};
Filter& filter = filters_[0];
const Filter new_filter(
filter.id(),
const_cast<std::array<Filter::Rule, kMaxFilterRules>&>(new_filter_rules));
std::byte request_buffer[256];
const auto request = EncodeFilterRequest(new_filter, request_buffer);
ASSERT_EQ(request.status(), OkStatus());
PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
context(filter_map_);
context.call(request.value());
ASSERT_EQ(OkStatus(), context.status());
size_t i = 0;
for (const auto& rule : filter.rules()) {
VerifyRule(rule, new_filter_rules[i++]);
}
// An empty request should not modify the filter.
PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
context_no_filter(filter_map_);
context_no_filter.call({});
EXPECT_EQ(Status::OutOfRange(), context_no_filter.status());
i = 0;
for (const auto& rule : filter.rules()) {
VerifyRule(rule, new_filter_rules[i++]);
}
// A new request for logs with a new filter updates filter.
const std::array<Filter::Rule, kMaxFilterRules> second_filter_rules{{
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
.any_flags_set = 0xab,
.module_equals{},
.thread_equals{},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0x11,
.module_equals{std::byte(34)},
.thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
},
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xef,
.module_equals{std::byte(23)},
.thread_equals{std::byte('R'), std::byte('P'), std::byte('C')},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0x0f,
.module_equals{},
.thread_equals{std::byte('R'), std::byte('P'), std::byte('C')},
},
}};
const Filter second_filter(
filter.id(),
const_cast<std::array<Filter::Rule, kMaxFilterRules>&>(
second_filter_rules));
std::memset(request_buffer, 0, sizeof(request_buffer));
const auto second_filter_request =
EncodeFilterRequest(second_filter, request_buffer);
ASSERT_EQ(second_filter_request.status(), OkStatus());
PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
context_new_filter(filter_map_);
context_new_filter.call(second_filter_request.value());
ASSERT_EQ(OkStatus(), context.status());
i = 0;
for (const auto& rule : filter.rules()) {
VerifyRule(rule, second_filter_rules[i++]);
}
}
void VerifyFilterRule(protobuf::Decoder& decoder,
const Filter::Rule& expected_rule) {
ASSERT_TRUE(decoder.Next().ok());
ASSERT_EQ(decoder.FieldNumber(),
static_cast<uint32_t>(
log::FilterRule::Fields::LEVEL_GREATER_THAN_OR_EQUAL));
log::FilterRule::Level level_greater_than_or_equal;
ASSERT_EQ(decoder.ReadUint32(
reinterpret_cast<uint32_t*>(&level_greater_than_or_equal)),
OkStatus());
EXPECT_EQ(level_greater_than_or_equal,
expected_rule.level_greater_than_or_equal);
ASSERT_TRUE(decoder.Next().ok());
ASSERT_EQ(decoder.FieldNumber(),
static_cast<uint32_t>(log::FilterRule::Fields::MODULE_EQUALS));
ConstByteSpan module_equals;
ASSERT_EQ(decoder.ReadBytes(&module_equals), OkStatus());
ASSERT_EQ(module_equals.size(), expected_rule.module_equals.size());
EXPECT_EQ(std::memcmp(module_equals.data(),
expected_rule.module_equals.data(),
module_equals.size()),
0);
ASSERT_TRUE(decoder.Next().ok());
ASSERT_EQ(decoder.FieldNumber(),
static_cast<uint32_t>(log::FilterRule::Fields::ANY_FLAGS_SET));
uint32_t any_flags_set;
ASSERT_EQ(decoder.ReadUint32(&any_flags_set), OkStatus());
EXPECT_EQ(any_flags_set, expected_rule.any_flags_set);
ASSERT_TRUE(decoder.Next().ok());
ASSERT_EQ(decoder.FieldNumber(),
static_cast<uint32_t>(log::FilterRule::Fields::ACTION));
Filter::Rule::Action action;
ASSERT_EQ(decoder.ReadUint32(reinterpret_cast<uint32_t*>(&action)),
OkStatus());
EXPECT_EQ(action, expected_rule.action);
ASSERT_TRUE(decoder.Next().ok());
ASSERT_EQ(decoder.FieldNumber(),
static_cast<uint32_t>(log::FilterRule::Fields::THREAD_EQUALS));
ConstByteSpan thread;
ASSERT_EQ(decoder.ReadBytes(&thread), OkStatus());
ASSERT_EQ(thread.size(), expected_rule.thread_equals.size());
EXPECT_EQ(
std::memcmp(
thread.data(), expected_rule.thread_equals.data(), thread.size()),
0);
}
void VerifyFilterRules(protobuf::Decoder& decoder,
span<const Filter::Rule> expected_rules) {
size_t rules_found = 0;
while (decoder.Next().ok()) {
ConstByteSpan rule;
EXPECT_TRUE(decoder.ReadBytes(&rule).ok());
protobuf::Decoder rule_decoder(rule);
if (rules_found >= expected_rules.size()) {
break;
}
VerifyFilterRule(rule_decoder, expected_rules[rules_found]);
++rules_found;
}
EXPECT_EQ(rules_found, expected_rules.size());
}
TEST_F(FilterServiceTest, GetFilterRules) {
PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
context(filter_map_);
std::byte request_buffer[64];
log::GetFilterRequest::MemoryEncoder encoder(request_buffer);
ASSERT_EQ(OkStatus(), encoder.WriteFilterId(filter_id1_));
const auto request = ConstByteSpan(encoder);
context.call(request);
ASSERT_EQ(OkStatus(), context.status());
ASSERT_TRUE(context.done());
ASSERT_EQ(context.responses().size(), 1u);
// Verify against empty rules.
protobuf::Decoder decoder(context.responses()[0]);
VerifyFilterRules(decoder, rules1_);
// Partially populate rules.
rules1_[0].action = Filter::Rule::Action::kKeep;
rules1_[0].level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL;
rules1_[0].any_flags_set = 0xab;
const std::array<std::byte, 2> module1{std::byte(123), std::byte(0xab)};
rules1_[0].module_equals.assign(module1.begin(), module1.end());
const std::array<std::byte, 4> thread1{
std::byte('H'), std::byte('O'), std::byte('S'), std::byte('T')};
rules1_[0].thread_equals.assign(thread1.begin(), thread1.end());
rules1_[1].action = Filter::Rule::Action::kDrop;
rules1_[1].level_greater_than_or_equal = log::FilterRule::Level::ERROR_LEVEL;
rules1_[1].any_flags_set = 0;
PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
context2(filter_map_);
context2.call(request);
ASSERT_EQ(OkStatus(), context2.status());
ASSERT_EQ(context2.responses().size(), 1u);
protobuf::Decoder decoder2(context2.responses()[0]);
VerifyFilterRules(decoder2, rules1_);
// Modify the rest of the filter rules.
rules1_[2].action = Filter::Rule::Action::kKeep;
rules1_[2].level_greater_than_or_equal = log::FilterRule::Level::FATAL_LEVEL;
rules1_[2].any_flags_set = 0xcd;
const std::array<std::byte, 2> module2{std::byte(1), std::byte(2)};
rules1_[2].module_equals.assign(module2.begin(), module2.end());
const std::array<std::byte, 3> thread2{
std::byte('A'), std::byte('P'), std::byte('P')};
rules1_[2].thread_equals.assign(thread2.begin(), thread2.end());
rules1_[3].action = Filter::Rule::Action::kInactive;
PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
context3(filter_map_);
context3.call(request);
ASSERT_EQ(OkStatus(), context3.status());
ASSERT_EQ(context3.responses().size(), 1u);
protobuf::Decoder decoder3(context3.responses()[0]);
VerifyFilterRules(decoder3, rules1_);
}
} // namespace
} // namespace pw::log_rpc