blob: fa412158e819b2a54e9ae37f9adf92e0aa8515dd [file] [log] [blame]
// Copyright 2020 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.h"
#include <array>
#include <cstddef>
#include <cstring>
#include "gtest/gtest.h"
#include "pw_bytes/endian.h"
#include "pw_log/levels.h"
#include "pw_log/log.h"
#include "pw_log/proto/log.pwpb.h"
#include "pw_log/proto_utils.h"
#include "pw_log_rpc/log_filter_map.h"
#include "pw_log_tokenized/metadata.h"
#include "pw_result/result.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
namespace pw::log_rpc {
namespace {
constexpr uint32_t kSampleModule = 0x1234;
constexpr uint32_t kSampleFlags = 0x3;
constexpr char kSampleMessage[] = "message";
constexpr auto kSampleModuleLittleEndian =
bytes::CopyInOrder<uint32_t>(std::endian::little, kSampleModule);
// Creates and encodes a log entry in the provided buffer.
template <uintptr_t log_level, uintptr_t module, uintptr_t flags>
Result<ConstByteSpan> EncodeLogEntry(std::string_view message,
ByteSpan buffer) {
auto metadata = log_tokenized::Metadata::Set<log_level, module, flags, 0>();
return log::EncodeTokenizedLog(metadata,
std::as_bytes(std::span(message)),
/*ticks_since_epoch=*/0,
buffer);
}
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));
return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action));
}
Result<ConstByteSpan> EncodeFilter(const Filter& filter, ByteSpan buffer) {
log::Filter::MemoryEncoder encoder(buffer);
for (auto& rule : filter.rules()) {
log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder();
PW_TRY(EncodeFilterRule(rule, rule_encoder));
}
return ConstByteSpan(encoder);
}
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.action, expected_rule.action);
}
TEST(FilterMap, RetrieveFiltersById) {
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{
std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{
std::byte(0xca), std::byte(0xfe), std::byte(0xc0), std::byte(0xc0)};
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3{
std::byte(0xfa), std::byte(0xfe), std::byte(0xf1), std::byte(0xf0)};
std::array<Filter, 3> filters = {
Filter(filter_id1, {}),
Filter(filter_id2, {}),
Filter(filter_id3, {}),
};
FilterMap filter_map(filters);
// Check that each filters() element points to the same object provided.
std::span<const Filter> filter_list = filter_map.filters();
ASSERT_EQ(filter_list.size(), filters.size());
size_t i = 0;
for (auto& filter : filter_list) {
EXPECT_EQ(&filter, &filters[i++]);
}
auto filter_result = filter_map.GetFilterFromId(filter_id3);
ASSERT_TRUE(filter_result.ok());
EXPECT_EQ(filter_result.value(), &filters[2]);
filter_result = filter_map.GetFilterFromId(filter_id2);
ASSERT_TRUE(filter_result.ok());
EXPECT_EQ(filter_result.value(), &filters[1]);
filter_result = filter_map.GetFilterFromId(filter_id1);
ASSERT_TRUE(filter_result.ok());
EXPECT_EQ(filter_result.value(), &filters[0]);
const std::array<std::byte, cfg::kMaxFilterIdBytes> invalid_id{
std::byte(0xd0), std::byte(0x1c), std::byte(0xe7), std::byte(0xea)};
filter_result = filter_map.GetFilterFromId(invalid_id);
ASSERT_EQ(filter_result.status(), Status::NotFound());
}
TEST(Filter, UpdateFilterRules) {
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)};
std::array<Filter::Rule, 4> rules;
const std::array<Filter::Rule, 4> 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)},
},
{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xef,
.module_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)},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0,
.module_equals{std::byte(4)},
},
}};
Filter filter(filter_id, rules);
const Filter new_filter(filter_id,
const_cast<std::array<Filter::Rule, 4>&>(new_rules));
std::byte buffer[256];
auto encode_result = EncodeFilter(new_filter, buffer);
ASSERT_EQ(encode_result.status(), OkStatus());
EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
size_t i = 0;
for (const auto& rule : filter.rules()) {
VerifyRule(rule, new_rules[i++]);
}
// A new filter with no rules should clear filter.
const Filter empty_filter(filter_id, {});
std::memset(buffer, 0, sizeof(buffer));
encode_result = EncodeFilter(empty_filter, buffer);
ASSERT_EQ(encode_result.status(), OkStatus());
EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
const Filter::Rule empty_rule{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0,
.module_equals{},
};
for (const auto& rule : filter.rules()) {
VerifyRule(rule, empty_rule);
}
EXPECT_TRUE(empty_filter.rules().empty());
// Passing a new filter with less rules.
const std::array<Filter::Rule, 2> few_rules{{
{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xef,
.module_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)},
},
}};
const Filter filter_few_rules(
filter_id, const_cast<std::array<Filter::Rule, 2>&>(few_rules));
std::memset(buffer, 0, sizeof(buffer));
encode_result = EncodeFilter(filter_few_rules, buffer);
ASSERT_EQ(encode_result.status(), OkStatus());
EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
i = 0;
for (const auto& rule : filter.rules()) {
if (i >= few_rules.size()) {
VerifyRule(rule, empty_rule);
} else {
VerifyRule(rule, few_rules[i++]);
}
}
// Passing a new filter with extra rules.
const std::array<Filter::Rule, 6> extra_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)},
},
{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xef,
.module_equals{},
},
{
.action = Filter::Rule::Action::kInactive,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0xef,
.module_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)},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0,
.module_equals{std::byte(4)},
},
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = 0x1234,
.module_equals{std::byte(99)},
},
}};
const Filter filter_extra_rules(
filter_id, const_cast<std::array<Filter::Rule, 6>&>(extra_rules));
std::memset(buffer, 0, sizeof(buffer));
encode_result = EncodeFilter(filter_extra_rules, buffer);
ASSERT_EQ(encode_result.status(), OkStatus());
EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
i = 0;
for (const auto& rule : filter.rules()) {
VerifyRule(rule, extra_rules[i++]);
}
// A filter with no rules buffer cannot get rules updated.
Filter filter_no_rules(filter_id, {});
EXPECT_EQ(filter_no_rules.UpdateRulesFromProto(encode_result.value()),
Status::FailedPrecondition());
}
TEST(FilterTest, FilterLogsRuleDefaultDrop) {
const std::array<Filter::Rule, 2> rules{{
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = kSampleFlags,
.module_equals{kSampleModuleLittleEndian.begin(),
kSampleModuleLittleEndian.end()},
},
// This rule catches all logs.
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
.any_flags_set = 0,
.module_equals = {},
},
}};
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
const Filter filter(filter_id,
const_cast<std::array<Filter::Rule, 2>&>(rules));
std::array<std::byte, 50> buffer;
const Result<ConstByteSpan> log_entry_info =
EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_info.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_debug =
EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_debug.status(), OkStatus());
EXPECT_TRUE(filter.ShouldDropLog(log_entry_debug.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_warn =
EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_warn.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_error =
EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_error.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_info_different =
EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_info_different.status(), OkStatus());
EXPECT_TRUE(filter.ShouldDropLog(log_entry_info_different.value()));
// Because the last rule catches all logs, the filter default action is not
// applied.
const Filter filter_default_drop(
filter_id, const_cast<std::array<Filter::Rule, 2>&>(rules));
EXPECT_TRUE(
filter_default_drop.ShouldDropLog(log_entry_info_different.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_same_flags =
EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_flags.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_same_module =
EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_same_module.status(), OkStatus());
EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_module.value()));
}
TEST(FilterTest, FilterLogsKeepLogsWhenNoRuleMatches) {
// There is no rule that catches all logs.
const std::array<Filter::Rule, 1> rules{{
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = kSampleFlags,
.module_equals = {kSampleModuleLittleEndian.begin(),
kSampleModuleLittleEndian.end()},
},
}};
// Filters should not share rules if they are mutable, to avoid race
// conditions.
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
const Filter filter(filter_id,
const_cast<std::array<Filter::Rule, 1>&>(rules));
std::array<std::byte, 50> buffer;
const Result<ConstByteSpan> log_entry_info =
EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_info.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_debug =
EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_debug.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_warn =
EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_warn.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_error =
EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_error.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_info_different =
EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_info_different.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_same_flags =
EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_same_module =
EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_same_module.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value()));
}
TEST(FilterTest, FilterLogsKeepLogsWhenRulesEmpty) {
// Filters should not share rules if they are mutable, to avoid race
// conditions.
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
const Filter filter(filter_id, {});
std::array<std::byte, 50> buffer;
const Result<ConstByteSpan> log_entry_info =
EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_info.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_debug =
EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_debug.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_warn =
EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_warn.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_error =
EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_error.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_info_different =
EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_info_different.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_same_flags =
EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value()));
buffer.fill(std::byte(0));
const Result<ConstByteSpan> log_entry_same_module =
EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
ASSERT_EQ(log_entry_same_module.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value()));
}
TEST(FilterTest, FilterLogsFirstRuleWins) {
const std::array<Filter::Rule, 2> rules{{
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = kSampleFlags,
.module_equals = {kSampleModuleLittleEndian.begin(),
kSampleModuleLittleEndian.end()},
},
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = kSampleFlags,
.module_equals = {kSampleModuleLittleEndian.begin(),
kSampleModuleLittleEndian.end()},
},
}};
const std::array<Filter::Rule, 2> rules_reversed{{
{
.action = Filter::Rule::Action::kDrop,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = kSampleFlags,
.module_equals = {kSampleModuleLittleEndian.begin(),
kSampleModuleLittleEndian.end()},
},
{
.action = Filter::Rule::Action::kKeep,
.level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
.any_flags_set = kSampleFlags,
.module_equals = {kSampleModuleLittleEndian.begin(),
kSampleModuleLittleEndian.end()},
},
}};
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{
std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{
std::byte(0), std::byte(0), std::byte(0), std::byte(2)};
const Filter filter(filter_id1,
const_cast<std::array<Filter::Rule, 2>&>(rules));
const Filter filter_reverse_rules(
filter_id2, const_cast<std::array<Filter::Rule, 2>&>(rules_reversed));
std::array<std::byte, 50> buffer;
const Result<ConstByteSpan> log_entry_info =
EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
kSampleMessage, buffer);
ASSERT_EQ(log_entry_info.status(), OkStatus());
EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
EXPECT_TRUE(filter_reverse_rules.ShouldDropLog(log_entry_info.value()));
}
} // namespace
} // namespace pw::log_rpc