Repository navigation
Expand file tree
/
Copy pathtest_pattern_scan.cpp
More file actions
150 lines (128 loc) · 5.38 KB
/
Copy pathtest_pattern_scan.cpp
File metadata and controls
150 lines (128 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// test_pattern_scan.cpp — unit tests for pattern_scan.hpp.
//
// Includes a randomized check against a straightforward reference scanner,
// so the memchr-anchored search must agree with it on every input.
#include "pattern_scan.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
int failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "%s:%d: CHECK failed: %s\n", __FILE__, \
__LINE__, #cond); \
++failures; \
} \
} while (0)
bool throws(const char* pattern) {
try {
patscan::parse(pattern);
} catch (const std::invalid_argument&) {
return true;
}
return false;
}
// The obvious O(n*m) scanner: the specification the library must match.
std::vector<std::size_t> reference(const std::vector<std::uint8_t>& data,
const patscan::Signature& sig) {
std::vector<std::size_t> hits;
if (sig.size() == 0 || data.size() < sig.size()) return hits;
for (std::size_t i = 0; i + sig.size() <= data.size(); ++i) {
bool ok = true;
for (std::size_t j = 0; j < sig.size() && ok; ++j) {
ok = !sig.mask[j] || data[i + j] == sig.bytes[j];
}
if (ok) hits.push_back(i);
}
return hits;
}
void test_parse() {
const auto sig = patscan::parse("48 8B ?? E8 ? * 85 c0");
CHECK(sig.size() == 8);
CHECK(sig.bytes[0] == 0x48 && sig.mask[0]);
CHECK(sig.bytes[1] == 0x8B && sig.mask[1]);
CHECK(!sig.mask[2] && !sig.mask[4] && !sig.mask[5]);
CHECK(sig.bytes[7] == 0xC0); // lower case hex
const auto single = patscan::parse("A ?");
CHECK(single.size() == 2 && single.bytes[0] == 0x0A && single.mask[0]);
const auto spaced = patscan::parse(" \t48\n8B\r\n");
CHECK(spaced.size() == 2 && spaced.bytes[1] == 0x8B);
CHECK(throws(""));
CHECK(throws(" "));
CHECK(throws("4G"));
CHECK(throws("48 xx"));
CHECK(throws("48 8B !"));
}
void test_find() {
const std::vector<std::uint8_t> buf = {
0x90, 0x90, 0x48, 0x8B, 0xC4, 0xE8, 0x12, 0x34,
0x56, 0x78, 0x85, 0xC0, 0x74, 0x08, 0x48, 0x8B,
};
const auto sig = patscan::parse("48 8B ?? E8 ?? ?? ?? ?? 85 C0");
CHECK(patscan::find(buf.data(), buf.size(), sig) == buf.data() + 2);
CHECK(patscan::find(buf.data(), buf.size(), "74 08") == buf.data() + 12);
CHECK(patscan::find(buf.data(), buf.size(), "74 09") == nullptr);
// A match that ends exactly at the end of the buffer.
CHECK(patscan::find(buf.data(), buf.size(), "48 8B") == buf.data() + 2);
CHECK(patscan::find_all(buf.data(), buf.size(), patscan::parse("48 8B")) ==
(std::vector<std::size_t>{2, 14}));
// Leading wildcards: the anchor is not the first byte.
CHECK(patscan::find(buf.data(), buf.size(), "?? ?? E8") == buf.data() + 3);
// All wildcards match at every offset.
CHECK(patscan::find_all(buf.data(), buf.size(), patscan::parse("?? ??")).size() ==
buf.size() - 1);
// Edge cases: empty input, pattern longer than the data, null data.
CHECK(patscan::find(buf.data(), 0, sig) == nullptr);
CHECK(patscan::find(buf.data(), 3, sig) == nullptr);
CHECK(patscan::find(static_cast<const std::uint8_t*>(nullptr), 100, sig) == nullptr);
CHECK(patscan::find_all(buf.data(), 3, sig).empty());
// std::byte overload.
const auto* bytes = reinterpret_cast<const std::byte*>(buf.data());
CHECK(patscan::find(bytes, buf.size(), sig) == bytes + 2);
}
void test_matches_reference_on_random_data() {
std::mt19937 rng(185721);
for (int round = 0; round < 3000; ++round) {
// A small alphabet makes partial and overlapping matches common.
const int alphabet = 2 + static_cast<int>(rng() % 6);
std::vector<std::uint8_t> data(rng() % 300);
for (auto& b : data) b = static_cast<std::uint8_t>(rng() % alphabet);
std::string pattern;
const int len = 1 + static_cast<int>(rng() % 6);
for (int k = 0; k < len; ++k) {
char tok[4];
if (rng() % 3 == 0) {
std::snprintf(tok, sizeof(tok), "??");
} else {
std::snprintf(tok, sizeof(tok), "%02X", static_cast<unsigned>(rng() % alphabet));
}
pattern += tok;
pattern += ' ';
}
const auto sig = patscan::parse(pattern);
const auto expected = reference(data, sig);
const auto all = patscan::find_all(data.data(), data.size(), sig);
const auto* first = patscan::find(data.data(), data.size(), sig);
CHECK(all == expected);
CHECK(first == (expected.empty() ? nullptr : data.data() + expected.front()));
if (failures > 10) return;
}
}
} // namespace
int main() {
test_parse();
test_find();
test_matches_reference_on_random_data();
if (failures) {
std::fprintf(stderr, "%d check(s) failed\n", failures);
return 1;
}
std::printf("all pattern_scan tests passed\n");
return 0;
}