blob: f9184228d8b2443191782441acf0865e9162b143 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/forensics/utils/redact/replacer.h"
#include <lib/inspect/cpp/vmo/types.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/utils/redact/cache.h"
namespace forensics {
namespace {
template <typename T>
std::string GetTestName(const testing::TestParamInfo<T>& info) {
return info.param.test_name;
}
struct RegexpTestParam {
std::string test_name;
std::string pattern;
std::string replacement;
// nullopt means |pattern| is bad.
std::optional<std::string> text{std::nullopt};
std::optional<std::string> expected_output{std::nullopt};
};
class TextReplacerTest : public ::testing::Test,
public testing::WithParamInterface<RegexpTestParam> {};
INSTANTIATE_TEST_SUITE_P(TextReplacement, TextReplacerTest,
::testing::ValuesIn(std::vector<RegexpTestParam>({
{
"BadRegexp",
"[",
"unused",
},
{
"Numbers",
"\\d+",
"<NUMBER>",
"9 8 7 abc65",
"<NUMBER> <NUMBER> <NUMBER> abc<NUMBER>",
},
})),
GetTestName<RegexpTestParam>);
TEST_P(TextReplacerTest, ReplaceWithText) {
auto param = GetParam();
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer = ReplaceWithText(param.pattern, param.replacement);
if (param.text == std::nullopt || param.expected_output == std::nullopt) {
EXPECT_EQ(replacer, nullptr);
} else {
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, *param.text), *param.expected_output);
}
}
class IdReplacerTest : public ::testing::Test,
public testing::WithParamInterface<RegexpTestParam> {};
INSTANTIATE_TEST_SUITE_P(IdReplacement, IdReplacerTest,
::testing::ValuesIn(std::vector<RegexpTestParam>({
{
"BadRegexp",
"[",
"unused",
},
{
"MissingCapture",
"\\d+",
"unused",
},
{
"TooManyCaptures",
"(\\d+) (\\d+)",
"unused",
},
{
"MissingFormatSpecifier",
"(\\d+)",
"unused",
},
{
"TooManyFormatSpecifiers",
"(\\d+)",
"%d %d",
},
{
"Numbers",
"(\\d+)",
"<NUMBER: %d>",
"9 8 7 abc65",
"<NUMBER: 1> <NUMBER: 2> <NUMBER: 3> abc<NUMBER: 4>",
},
{
"OverlappingMatches",
"(b?c)",
"<bc_or_c: %d>",
"9 8 7 abc65",
"9 8 7 a<bc_or_c: 1>65",
},
})),
GetTestName<RegexpTestParam>);
TEST_P(IdReplacerTest, ReplaceWithIdFormatString) {
auto param = GetParam();
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer =
ReplaceWithIdFormatString(param.pattern, param.replacement, /*ignore_prefixes=*/{});
if (param.text == std::nullopt || param.expected_output == std::nullopt) {
EXPECT_EQ(replacer, nullptr);
} else {
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, *param.text), *param.expected_output);
}
}
TEST(IdReplacerTests, ReplacementIsShorter) {
RedactionIdCache cache(inspect::UintProperty{});
std::string content =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:1234567890abcdefABCDEF0123456789
[00050.220][forensics, feedback] INFO: [file_name.cc:80] ID:1234567890abcdefABCDEF0123456789
[00050.221][forensics, feedback] INFO: [file_name.cc:80] ID:1234567890abcdefABCDEF0123456789)";
static constexpr std::string_view kExpectedContent =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>
[00050.220][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>
[00050.221][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>)";
Replacer replacer = ReplaceWithIdFormatString(R"((\b[0-9a-fA-F]{32}\b))", "<REDACTED-HEX: %d>",
/*ignore_prefixes=*/{});
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, content), kExpectedContent);
}
TEST(IdReplacerTests, ReplacementIsLonger) {
RedactionIdCache cache(inspect::UintProperty{});
std::string content =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:12345678
[00050.220][forensics, feedback] INFO: [file_name.cc:80] ID:12345678
[00050.221][forensics, feedback] INFO: [file_name.cc:80] ID:12345678)";
static constexpr std::string_view kExpectedContent =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>
[00050.220][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>
[00050.221][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>)";
Replacer replacer = ReplaceWithIdFormatString(R"((\b[0-9a-fA-F]{8}\b))", "<REDACTED-HEX: %d>",
/*ignore_prefixes=*/{});
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, content), kExpectedContent);
}
TEST(IdReplacerTests, VariableOffset) {
RedactionIdCache cache(inspect::UintProperty{});
std::string content =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:1234567890abcdefABCDEF0123456789
[00050.220][forensics, feedback] INFO: [file_name.cc:80] ID:abcdef1234567890ABCDEF012345678
[00050.221][forensics, feedback] INFO: [file_name.cc:80] ID:1234567890abcdefABCDEF0123456789)";
static constexpr std::string_view kExpectedContent =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>
[00050.220][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 2>
[00050.221][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>)";
Replacer replacer = ReplaceWithIdFormatString(R"((\b[0-9a-fA-F]{31,32}\b))", "<REDACTED-HEX: %d>",
/*ignore_prefixes=*/{});
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, content), kExpectedContent);
}
TEST(IdReplacerTests, IgnoresPrefixes) {
RedactionIdCache cache(inspect::UintProperty{});
std::string content =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:1234567890abcdefABCDEF0123456789
[00050.220][forensics, feedback] INFO: [file_name.cc:80] elf:1234567890abcdefABCDEF0123456789
[00050.221][forensics, feedback] INFO: [file_name.cc:80] build_id: 1234567890abcdefABCDEF0123456789)";
static constexpr std::string_view kExpectedContent =
R"([00050.219][forensics, feedback] INFO: [file_name.cc:80] ID:<REDACTED-HEX: 1>
[00050.220][forensics, feedback] INFO: [file_name.cc:80] elf:1234567890abcdefABCDEF0123456789
[00050.221][forensics, feedback] INFO: [file_name.cc:80] build_id: 1234567890abcdefABCDEF0123456789)";
const std::vector<std::string> kHexIgnorePrefixes({
"elf:",
"build_id: ",
});
Replacer replacer = ReplaceWithIdFormatString(R"((\b[0-9a-fA-F]{32}\b))", "<REDACTED-HEX: %d>",
/*ignore_prefixes=*/kHexIgnorePrefixes);
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, content), kExpectedContent);
}
TEST(IdReplacerTests, RedactsIfPrefixWouldBeBeforeBuffer) {
RedactionIdCache cache(inspect::UintProperty{});
std::string content = R"(lf:1234567890abcdefABCDEF0123456789)";
static constexpr std::string_view kExpectedContent = R"(lf:<REDACTED-HEX: 1>)";
const std::vector<std::string> kHexIgnorePrefixes({"elf:"});
Replacer replacer = ReplaceWithIdFormatString(R"((\b[0-9a-fA-F]{32}\b))", "<REDACTED-HEX: %d>",
/*ignore_prefixes=*/kHexIgnorePrefixes);
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, content), kExpectedContent);
}
struct IpTestParam {
std::string test_name;
std::string text;
std::string expected_output;
};
class IPv4ReplacerTest : public ::testing::Test, public testing::WithParamInterface<IpTestParam> {};
INSTANTIATE_TEST_SUITE_P(IPv4Replacement, IPv4ReplacerTest,
::testing::ValuesIn(std::vector<IpTestParam>({
{
"IPv4",
"IPv4: 8.8.8.8",
"IPv4: <REDACTED-IPV4: 1>",
},
{
"IPv46",
"IPv46: ::ffff:12.34.56.78",
"IPv46: ::ffff:<REDACTED-IPV4: 1>",
},
{
"Cleartext",
"current: 0.8.8.8",
"current: 0.8.8.8",
},
{
"Loopback",
"loopback: 127.8.8.8",
"loopback: 127.8.8.8",
},
{
"LinkLocal",
"link_local: 169.254.8.8",
"link_local: 169.254.8.8",
},
{
"LinkLocalMulticast",
"link_local_multicast: 224.0.0.8",
"link_local_multicast: 224.0.0.8",
},
{
"Broadcast",
"broadcast: 255.255.255.255",
"broadcast: 255.255.255.255",
},
{
"NotBroadcast",
"not_broadcast: 255.255.255.254",
"not_broadcast: <REDACTED-IPV4: 1>",
},
{
"NotLinkLocalMulticast",
"not_link_local_multicast: 224.0.1.8",
"not_link_local_multicast: <REDACTED-IPV4: 1>",
},
{
"Partial",
"partial: 192.168.42.x",
"partial: <REDACTED-IPV4: 1>",
},
{
"NotPartial",
"not-partial: 192.168.42 x",
"not-partial: 192.168.42 x",
},
{
"WrongDigits",
"wrong-digits: 192.168.420",
"wrong-digits: 192.168.420",
},
})),
GetTestName<IpTestParam>);
TEST_P(IPv4ReplacerTest, ReplaceIPv4) {
auto param = GetParam();
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer = ReplaceIPv4();
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, param.text), param.expected_output);
}
class IPv6ReplacerTest : public ::testing::Test, public testing::WithParamInterface<IpTestParam> {};
INSTANTIATE_TEST_SUITE_P(IPv6Replacement, IPv6ReplacerTest,
::testing::ValuesIn(std::vector<IpTestParam>({
{
"IPv46H",
"IPv46h: ::ffff:ab12:34cd",
"IPv46h: ::ffff:<REDACTED-IPV4: 1>",
},
{
"NotIPv46h",
"not_IPv46h: ::ffff:ab12:34cd:5",
"not_IPv46h: <REDACTED-IPV6: 1>",
},
{
"IPv6",
"IPv6: 2001:503:eEa3:0:0:0:0:30",
"IPv6: <REDACTED-IPV6: 1>",
},
{
"IPv6Colon",
"IPv6C: [::/0 via 2082::7d84:c1dc:ab34:656a nic 4]",
"IPv6C: [::/0 via <REDACTED-IPV6: 1> nic 4]",
},
{
"IPv6LL",
"IPv6LL: fe80::7d84:c1dc:ab34:656a",
"IPv6LL: fe80:<REDACTED-IPV6-LL: 1>",
},
{
"IPv6LocalMulticast1",
"local_multicast_1: fF41::1234:5678:9aBc",
"local_multicast_1: fF41::1234:5678:9aBc",
},
{
"IPv6LocalMulticast2",
"local_multicast_2: Ffe2:1:2:33:abcd:ef0:6789:456",
"local_multicast_2: Ffe2:1:2:33:abcd:ef0:6789:456",
},
{
"IPv6Multicast3",
"multicast: fF43:abcd::ef0:6789:456",
"multicast: fF43:<REDACTED-IPV6-MULTI: 1>",
},
{
"IPv6fe89",
"link_local_8: fe89:123::4567:8:90",
"link_local_8: fe89:<REDACTED-IPV6-LL: 1>",
},
{
"IPv6feb2",
"link_local_b: FEB2:123::4567:8:90",
"link_local_b: FEB2:<REDACTED-IPV6-LL: 1>",
},
{
"IPv6fec1",
"not_link_local: fec1:123::4567:8:90",
"not_link_local: <REDACTED-IPV6: 1>",
},
{
"IPv6fe71",
"not_link_local_2: fe71:123::4567:8:90",
"not_link_local_2: <REDACTED-IPV6: 1>",
},
{
"ShortColons",
"not_address_1: 12:34::",
"not_address_1: 12:34::",
},
{
"ColonsShort",
"not_address_2: ::12:34",
"not_address_2: ::12:34",
},
{
"ColonsFields3",
"v6_colons_3_fields: ::12:34:5",
"v6_colons_3_fields: <REDACTED-IPV6: 1>",
},
{
"V6Fields3Colons",
"v6_3_fields_colons: 12:34:5::",
"v6_3_fields_colons: <REDACTED-IPV6: 1>",
},
{
"ColonsFields7",
"v6_colons_7_fields: ::12:234:35:46:5:6:7",
"v6_colons_7_fields: <REDACTED-IPV6: 1>",
},
{
"V6Fields7Colons",
"v6_7_fields_colons: 12:234:35:46:5:6:7::",
"v6_7_fields_colons: <REDACTED-IPV6: 1>",
},
{
"ColonsFields8",
"v6_colons_8_fields: ::12:234:35:46:5:6:7:8",
"v6_colons_8_fields: <REDACTED-IPV6: 1>:8",
},
{
"V6Fields8Colons",
"v6_8_fields_colons: 12:234:35:46:5:6:7:8::",
"v6_8_fields_colons: <REDACTED-IPV6: 1>::",
},
})),
GetTestName<IpTestParam>);
TEST_P(IPv6ReplacerTest, ReplaceIPv6) {
auto param = GetParam();
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer = ReplaceIPv6();
ASSERT_NE(replacer, nullptr);
EXPECT_EQ(replacer(cache, param.text), param.expected_output);
}
TEST(MacReplacerTest, GetOuiPrefix) {
std::string mac_colons = "12:34:56:78:90:ff";
EXPECT_EQ(mac_utils::GetOuiPrefix(mac_colons), "12:34:56:");
std::string mac_dashes = "12-34-56-78-90-ff";
EXPECT_EQ(mac_utils::GetOuiPrefix(mac_dashes), "12-34-56-");
std::string mac_dots = "12.34.56.78.90.ff";
EXPECT_EQ(mac_utils::GetOuiPrefix(mac_dots), "12.34.56.");
}
TEST(MacReplacerTest, CanonicalizeMac) {
EXPECT_EQ(mac_utils::CanonicalizeMac("12:34:56:78:90:ff"), "12:34:56:78:90:ff");
EXPECT_EQ(mac_utils::CanonicalizeMac("12:34:56:78:90:FF"), "12:34:56:78:90:ff");
EXPECT_EQ(mac_utils::CanonicalizeMac("12-34-56-78-90-ff"), "12:34:56:78:90:ff");
EXPECT_EQ(mac_utils::CanonicalizeMac("12.34.56.78.90.ff"), "12:34:56:78:90:ff");
}
TEST(MacReplacerTest, ReplaceMac) {
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer = ReplaceMac();
ASSERT_NE(replacer, nullptr);
std::string text = R"(
00:0a:95:9F:68:16
12-34-95-9F-68-16
d.e.a.d.be.ef
ff.f-ff:f-ff:f
)";
EXPECT_EQ(replacer(cache, text),
R"(
00:0a:95:<REDACTED-MAC: 1>
12-34-95-<REDACTED-MAC: 2>
d.e.a.<REDACTED-MAC: 3>
ff.f-ff:<REDACTED-MAC: 4>
)");
}
TEST(MacReplacerTest, ReplaceMacIgnoresDelimitersAndCaseForIds) {
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer = ReplaceMac();
ASSERT_NE(replacer, nullptr);
std::string text = R"(
12-3f-95-9f-68-6
12:3F:95:9F:68:06
12.3f.95.9F.68.06
)";
EXPECT_EQ(replacer(cache, text),
R"(
12-3f-95-<REDACTED-MAC: 1>
12:3F:95:<REDACTED-MAC: 1>
12.3f.95.<REDACTED-MAC: 1>
)");
}
TEST(SsidReplacerTest, ReplaceSsid) {
RedactionIdCache cache(inspect::UintProperty{});
Replacer replacer = ReplaceSsid();
ASSERT_NE(replacer, nullptr);
std::string text = R"(
<ssid->
<ssid-4567fedcba>
<ssid-0123456789012345678901234567890123456789012345678901234567890123>
<ssid-01234567890123456789012345678901234567890123456789012345678901234>
)";
EXPECT_EQ(replacer(cache, text), R"(
<REDACTED-SSID: 1>
<REDACTED-SSID: 2>
<REDACTED-SSID: 3>
<REDACTED-SSID: 4>
)");
}
} // namespace
} // namespace forensics