My 11th 2023 update

This commit is contained in:
Ned Wright
2023-05-11 18:54:44 +00:00
parent 240f58217a
commit 29bd82d125
92 changed files with 9301 additions and 135 deletions

View File

@@ -0,0 +1,2 @@
add_library(keywords keywords_rule.cc single_keyword.cc data_keyword.cc pcre_keyword.cc length_keyword.cc byte_extract_keyword.cc compare_keyword.cc jump_keyword.cc stateop_keyword.cc no_match_keyword.cc)
add_subdirectory(keywords_ut)

View File

@@ -0,0 +1,319 @@
#include "single_keyword.h"
#include "output.h"
#include "debug.h"
#include <map>
#include <strings.h>
#include "limits.h"
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class ByteExtractKeyword : public SingleKeyword
{
public:
explicit ByteExtractKeyword(const vector<KeywordAttr> &attr, VariablesMapping &vars);
MatchStatus isMatch(const I_KeywordRuntimeState *prev) const override;
private:
enum class BaseId
{
BIN,
HEX = 16,
DEC = 10,
OCT = 8
};
void
setOffset(const KeywordAttr &attr, const VariablesMapping &vars)
{
offset.setAttr(attr, vars, "byte_extract");
}
void
setRelative(const KeywordAttr &attr, const VariablesMapping &)
{
is_relative.setAttr(attr, "byte_extract");
}
void
setLittleEndian(const KeywordAttr &attr, const VariablesMapping &)
{
is_little_end.setAttr(attr, "byte_extract");
}
void
setDataType(const KeywordAttr &attr, const VariablesMapping &)
{
if (data_type != BaseId::BIN) {
throw KeywordError("Double definition of the data type in the 'byte_extract' keyword");
}
auto &vec = attr.getParams();
if (vec.size() != 2) throw KeywordError("Malformed data type in the 'byte_extract' keyword");
if (vec[1] == "hex") {
data_type = BaseId::HEX;
} else if (vec[1] == "dec") {
data_type = BaseId::DEC;
} else if (vec[1] == "oct") {
data_type = BaseId::OCT;
} else {
throw KeywordError("Unknown data type in the 'byte_extract' keyword: " + vec[1]);
}
}
void
setContext(const KeywordAttr &attr, const VariablesMapping &)
{
ctx.setAttr(attr, "byte_extract");
}
void
setAlign(const KeywordAttr &attr, const VariablesMapping &)
{
if (align != 1) throw KeywordError("Double definition of the 'align' in the 'byte_extract' keyword");
auto &vec = attr.getParams();
if (vec.size() != 2) throw KeywordError("Malformed 'align' in the 'byte_extract' keyword");
if (vec[1] == "2") {
align = 2;
} else if (vec[1] == "4") {
align = 4;
} else {
throw KeywordError("Unknown 'align' in the 'byte_extract' keyword: " + vec[1]);
}
}
bool
isConstant() const
{
return !is_relative && bytes.isConstant() && offset.isConstant();
}
pair<uint, uint> getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const;
uint applyAlignment(uint value) const;
Maybe<uint> readValue(uint start, uint length, const Buffer &buf) const;
Maybe<uint> readStringValue(uint start, uint length, const Buffer &buf) const;
NumericAttr bytes;
uint var_id;
NumericAttr offset;
BoolAttr is_relative;
BoolAttr is_little_end;
BaseId data_type = BaseId::BIN;
int align = 1;
CtxAttr ctx;
static const map<string, void(ByteExtractKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
};
const map<string, void(ByteExtractKeyword::*)(const KeywordAttr &, const VariablesMapping &)>
ByteExtractKeyword::setops = {
{ "offset", &ByteExtractKeyword::setOffset },
{ "relative", &ByteExtractKeyword::setRelative },
{ "little_endian", &ByteExtractKeyword::setLittleEndian },
{ "string", &ByteExtractKeyword::setDataType },
{ "part", &ByteExtractKeyword::setContext },
{ "align", &ByteExtractKeyword::setAlign }
};
ByteExtractKeyword::ByteExtractKeyword(const vector<KeywordAttr> &attrs, VariablesMapping &vars)
:
offset()
{
//two requied attributes - number of bytes and var name
if (attrs.size() < 2) throw KeywordError("Invalid number of attributes in the 'byte_extract' keyword");
//parisng first attribute (Required) - number of bytes
auto &bytes_param = attrs[0].getParams();
if (bytes_param.size() != 1) {
throw KeywordError("More than one element in the 'bytes' in the 'byte_extract' keyword");
}
bytes.setAttr("bytes", bytes_param[0], vars, "byte_extract", static_cast<uint>(BaseId::DEC), true);
if (bytes.isConstant() && bytes.evalAttr(nullptr) == 0) {
throw KeywordError("Number of bytes is zero in the 'byte_extract' keyword");
}
//parisng second attribute (Required) - variable name
auto &var_name_param = attrs[1].getParams();
if (var_name_param.size() != 1) {
throw KeywordError("More than one element in the variable name in the 'byte_extract' keyword");
}
const string &var_name = var_name_param[0];
auto curr = setops.find(var_name);
if (curr != setops.end()) {
throw KeywordError("'" + var_name + "' cannot be the variable name in the 'byte_extract' keyword");
}
if (isdigit(var_name[0]) || var_name[0] == '-') {
throw KeywordError("Malformed variable name in the 'byte_extract' keyword");
}
var_id = vars.addNewVariable(var_name);
//parsing the other optional attributes
for (uint i = 2; i < attrs.size(); i++) {
auto curr = setops.find(attrs[i].getAttrName());
if (curr == setops.end()) {
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'byte_extract' keyword");
}
auto set_func = curr->second;
(this->*set_func)(attrs[i], vars);
}
if (data_type == BaseId::BIN) {
if (!bytes.isConstant()) {
throw KeywordError("Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword");
}
int num_bytes = bytes.evalAttr(nullptr);
if (num_bytes != 1 && num_bytes != 2 && num_bytes != 4) {
throw KeywordError("Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword");
}
if (is_little_end && num_bytes == 1) {
throw KeywordError(
"Little endian is set, "
"but the number of bytes is invalid in the 'byte_extract' keyword"
);
}
if (align != 1) {
throw KeywordError("The 'align' is set and data type is binary in the 'byte_extract' keyword");
}
} else {
if (is_little_end) {
throw KeywordError("Little endian is set, but the data type is not binary in the 'byte_extract' keyword");
}
}
}
static uint
addOffset(uint offset, int add)
{
if (add < 0 && offset < static_cast<uint>(-add)) {
dbgWarning(D_KEYWORD)
<< "The offset was set to 0 "
<< "due to an attempt to jump before the beginning of the buffer in the 'jump' keyword";
return 0;
}
return offset + add;
}
pair<uint, uint>
ByteExtractKeyword::getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const
{
uint relative_offset = is_relative ? prev->getOffset(ctx) : 0;
int offset_attr = offset.evalAttr(prev);
uint start_offset = addOffset(relative_offset, offset_attr);
if (start_offset >= buf_size) return make_pair(0, 0);
uint length = buf_size - start_offset;
return make_pair(start_offset, length);
}
Maybe<uint>
ByteExtractKeyword::readValue(uint start, uint length, const Buffer &buf) const
{
if (data_type != BaseId::BIN) return readStringValue(start, length, buf);
uint res = 0;
for (uint i = 0; i < length; i++) {
uint ch = buf[start + i];
if (is_little_end) {
ch <<= 8*i;
res += ch;
} else {
res <<= 8;
res += ch;
}
}
return res;
}
Maybe<uint>
ByteExtractKeyword::readStringValue(uint start, uint length, const Buffer &buf) const
{
const u_char *data = buf.getPtr(start, length).unpack(); // start and length were checked outside of the function
string val_str(reinterpret_cast<const char *>(data), length);
uint base = static_cast<uint>(data_type);
try {
size_t idx;
auto res = stoul(val_str, &idx, base);
if (idx != val_str.length()) throw invalid_argument("");
if (res > INT_MAX) {
throw out_of_range("");
}
return res;
}
catch (invalid_argument &) {
return genError("Unable to convert the \"" + val_str + "\" to a number due to an invalid argument");
}
catch (out_of_range &) {
return genError(
"Unable to convert the \""
+ val_str
+ "\" to a number. The maximum is: "
+ to_string(INT_MAX)
);
}
}
uint
ByteExtractKeyword::applyAlignment(uint value) const
{
int reminder = value % align;
if (reminder != 0) {
value += (align - reminder);
}
return value;
}
MatchStatus
ByteExtractKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
if (!part.ok()) return MatchStatus::NoMatchFinal;
uint bytes_to_extr = bytes.evalAttr(prev);
if (bytes_to_extr == 0) {
dbgDebug(D_KEYWORD) << "Number of bytes is zero in the 'byte_extract' keyword";
return MatchStatus::NoMatch; //the case of constant number of bytes was checked during compilation
}
uint start_offset, length_to_end;
tie(start_offset, length_to_end) = getStartOffsetAndLength((*part).size(), prev);
uint offset_after_extracted_bytes = applyAlignment(start_offset + bytes_to_extr);
if (length_to_end == 0 || offset_after_extracted_bytes > (*part).size()) {
dbgDebug(D_KEYWORD)
<< "Offset after the number of bytes to extract exceeds the buffer size in the 'byte_extract' keyword";
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
}
auto res = readValue(start_offset, bytes_to_extr, *part);
if (!res.ok()) {
dbgDebug(D_KEYWORD) << "Trying to store an invalid value in the 'byte_extract' keyword: " + res.getErr();
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
}
uint extracted_val = res.unpack();
if (extracted_val > INT_MAX) {
dbgDebug(D_KEYWORD) << "Value exceeds the maximum in the 'byte_extract' keyword";
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
}
//add variable and move offset after number of extracted bytes
VariableRuntimeState new_var(prev, var_id, extracted_val);
OffsetRuntimeState new_offset(&new_var, ctx, offset_after_extracted_bytes);
return runNext(&new_offset);
}
unique_ptr<SingleKeyword>
genByteExtractKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<ByteExtractKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,70 @@
#include "single_keyword.h"
#include "output.h"
#include "debug.h"
#include <map>
#include <strings.h>
#include "limits.h"
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class CompareKeyword : public SingleKeyword
{
public:
explicit CompareKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &vars);
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
private:
bool
isConstant() const
{
return first_val.isConstant() && second_val.isConstant();
}
NumericAttr first_val;
NumericAttr second_val;
ComparisonAttr comparison;
};
CompareKeyword::CompareKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &vars)
{
if (attrs.size() != 3) throw KeywordError("Invalid number of attributes in the 'compare' keyword");
auto &first_val_param = attrs[0].getParams();
if (first_val_param.size() != 1) {
throw KeywordError("More than one element in the first value in the 'compare' keyword");
}
first_val.setAttr("first_val", first_val_param[0], vars, "compare");
auto &comparison_param = attrs[1].getParams();
if (comparison_param.size() != 1) {
throw KeywordError("More than one element in the comparison operator in the 'compare' keyword");
}
comparison.setAttr(comparison_param[0], "compare");
auto &second_val_param = attrs[2].getParams();
if (second_val_param.size() != 1) {
throw KeywordError("More than one element in the second value in the 'compare' keyword");
}
second_val.setAttr("second_val", second_val_param[0], vars, "compare");
}
MatchStatus
CompareKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
int keyword_first_val = first_val.evalAttr(prev);
int keyword_second_val = second_val.evalAttr(prev);
if (comparison(keyword_first_val, keyword_second_val)) return runNext(prev);
// If there was no matches and the keyword is effected by other keywords, then we know that the rule won't match
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
}
unique_ptr<SingleKeyword>
genCompareKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<CompareKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,409 @@
#include "single_keyword.h"
#include "output.h"
#include "debug.h"
#include <map>
#include <strings.h>
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class DataKeyword : public SingleKeyword
{
public:
explicit DataKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &vars);
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
private:
void
setOffset(const KeywordAttr &attr, const VariablesMapping &vars)
{
offset.setAttr(attr, vars, "data");
}
void
setDepth(const KeywordAttr &attr, const VariablesMapping &vars)
{
depth.setAttr(attr, vars, "data");
}
void
setCaret(const KeywordAttr &attr, const VariablesMapping &)
{
is_caret.setAttr(attr, "data");
}
void
setRelative(const KeywordAttr &attr, const VariablesMapping &)
{
is_relative.setAttr(attr, "data");
}
void
setCaseInsensitive(const KeywordAttr &attr, const VariablesMapping &)
{
is_case_insensitive.setAttr(attr, "data");
}
void
setContext(const KeywordAttr &attr, const VariablesMapping &)
{
ctx.setAttr(attr, "data");
}
void parseString(const string &str);
void
addChar(char ch)
{
pattern.push_back(static_cast<unsigned char>(ch));
}
void calcTables();
pair<uint, uint> getStartAndEndOffsets(uint buf_size, const I_KeywordRuntimeState *prev) const;
uint bytesMatched(const Buffer&, uint) const;
uint
moveOnMatch() const
{
return pattern.size();
}
uint
moveOnNoMatch(uint offset_from_end, unsigned char first_unmatched_byte) const
{
dbgAssert(shift.size() > offset_from_end) << "Shift table of the 'data' keyword is shorter than the offset";
uint skip_size;
if (skip[first_unmatched_byte]>offset_from_end) {
skip_size = skip[first_unmatched_byte]-offset_from_end;
} else {
skip_size = 1;
}
return max(shift[offset_from_end], skip_size);
}
bool
isConstant() const
{
return !is_relative && offset.isConstant() && depth.isConstant();
}
vector<unsigned char> pattern;
uint skip[256];
vector<uint> shift;
NumericAttr offset;
NumericAttr depth;
BoolAttr is_negative;
BoolAttr is_caret;
BoolAttr is_relative;
BoolAttr is_case_insensitive;
CtxAttr ctx;
static const map<string, void(DataKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
};
const map<string, void(DataKeyword::*)(const KeywordAttr &, const VariablesMapping &)> DataKeyword::setops = {
{ "relative", &DataKeyword::setRelative },
{ "offset", &DataKeyword::setOffset },
{ "depth", &DataKeyword::setDepth },
{ "caret", &DataKeyword::setCaret },
{ "nocase", &DataKeyword::setCaseInsensitive },
{ "part", &DataKeyword::setContext }
};
DataKeyword::DataKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &vars)
:
offset(),
depth()
{
auto &pattern_param = attrs[0].getParams();
if (pattern_param.size() != 1) throw KeywordError("More than one element in the 'data' keyword pattern");
const string &string_pattern = pattern_param[0];
if (string_pattern.length() == 0) throw KeywordError("No input for the 'data' keyword");
uint start = 0;
if (string_pattern[0] == '!') {
is_negative.setAttr("data", "negative");
start++;
}
if (string_pattern[start] != '"') throw KeywordError("The data pattern does not begin with '\"'");
uint end = string_pattern.length()-1;
if (string_pattern[end] != '"') throw KeywordError("The data pattern does not end with '\"'");
if (start+1 >= end) throw KeywordError("No input for the 'data' keyword");
parseString(string_pattern.substr(start+1, end-(start+1)));
for (uint i = 1; i<attrs.size(); i++) {
auto curr = setops.find(attrs[i].getAttrName());
if (curr == setops.end()) {
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'data' keyword");
}
auto set_func = curr->second;
(this->*set_func)(attrs[i], vars);
}
calcTables();
}
void
DataKeyword::calcTables()
{
if (is_case_insensitive) {
for (auto &ch : pattern) {
if (isupper(ch)) {
ch = tolower(ch);
}
}
}
// Initialize skip table - when we meet a charecter that isn't in the pattern, we skip the whole pattern
for (auto &ch_skip : skip) {
ch_skip = pattern.size();
}
// Go over the charecters in the pattern.
// We can skip from a charecter to the end of the pattern.
// If a charecter appear more than once, the latest occurence take precedent.
for (uint index = 0; index<pattern.size(); index++) {
unsigned char ch = pattern[index];
uint dist_to_end = pattern.size()-(index+1);
if (is_case_insensitive && islower(ch)) {
skip[toupper(ch)] = dist_to_end;
}
skip[ch] = dist_to_end;
}
// Initialize the shift table.
shift.resize(pattern.size(), 0);
uint end_offset = pattern.size()-1;
// Go over all the suffixes (from the empty to the pattern-1)
for (size_t suffix_len = 0; suffix_len<pattern.size(); suffix_len++) {
// Find the smallest shift, so when shifting the suffix left:
// 1. All chars overlapping between pattern and shifted suffix match.
// 2. If the character before the shifted suffix overlaps the pattern, it doesn't match.
// pattern = "hellololo", suff=2 (must match "[^o]lo"), shift=4 ("hel(lo)lolo")
// pattern = "olo" suff=2 (must match "[^o]lo"), shift=2 ("(.o)lo")
// characters before the patterns are considered wild.
for (uint shift_offset = 1; shift_offset<=pattern.size(); shift_offset++) {
// Verify that in the current offset matches the suffix
size_t num_of_overlapping_char;
unsigned char *suffix_start_ptr;
unsigned char *shifted_suffix_start_ptr;
if (shift_offset+suffix_len <= pattern.size()) {
// Shifted suffix doesn't exceed the pattern. Compare the whole suffix.
num_of_overlapping_char = suffix_len;
suffix_start_ptr = pattern.data() + pattern.size() - suffix_len;
shifted_suffix_start_ptr = suffix_start_ptr - shift_offset;
} else {
// Shifted suffix exceeds the pattern. Compare only the overlaping charecters.
num_of_overlapping_char = pattern.size() - shift_offset;
suffix_start_ptr = pattern.data() + shift_offset;
shifted_suffix_start_ptr = pattern.data();
}
if (bcmp(suffix_start_ptr, shifted_suffix_start_ptr, num_of_overlapping_char) != 0) continue;
// Verify that what comes after the suffix doesn't match
if (shift_offset+suffix_len < pattern.size()) {
if (pattern[end_offset-suffix_len] == pattern[end_offset-(shift_offset+suffix_len)]) continue;
}
// Set the currect shift offset
shift[suffix_len] = shift_offset;
break;
}
}
}
void
DataKeyword::parseString(const string &str)
{
string hex;
bool hex_mode = false;
bool after_bslash = false;
for (auto ch : str) {
if (after_bslash) {
if (!isprint(ch)) {
throw KeywordError(
"Illegal backslash character '" +
dumpHexChar(ch) +
"' in the pattern in the 'data' keyword"
);
}
addChar(ch);
after_bslash = false;
continue;
}
switch (ch) {
case '|': {
if (!hex_mode) {
hex = "";
hex_mode = true;
} else {
if (hex.size()>0) throw KeywordError("Stoping in the middle of hex string in the 'data' keyword");
hex_mode = false;
}
break;
}
case '\\': {
if (hex_mode) throw KeywordError("Backslash in hex string in the 'data' keyword");
after_bslash = true;
break;
}
case '"': {
throw KeywordError("Unescaped double quotation mark in the 'data' keyword");
break;
}
default:
if (hex_mode) {
if (!isxdigit(ch)) {
if (ch != ' ') {
throw KeywordError(
"Illegal character '" +
dumpHexChar(ch) +
"' in the hex string in the 'data' keyword"
);
}
if (hex.size()>0) {
throw KeywordError("Space separating nibbles in the hex string in the 'data' keyword");
}
break;
}
hex += ch;
if (hex.size()>=2) {
addChar(stol(hex, nullptr, 16));
hex = "";
}
} else {
if (!isprint(ch)) {
throw KeywordError(
"Illegal character '" +
dumpHexChar(ch) +
"' in the pattern in the 'data' keyword"
);
}
addChar(ch);
}
}
}
if ( hex_mode || after_bslash ) {
throw KeywordError("The 'data' keyword's pattern has ended in the middle of the parsing");
}
}
static uint
addOffset(uint offset, int add)
{
if (add<0 && offset<static_cast<uint>(-add)) return 0;
return offset + add;
}
pair<uint, uint>
DataKeyword::getStartAndEndOffsets(uint buf_size, const I_KeywordRuntimeState *prev) const
{
uint relative_offset = is_relative?prev->getOffset(ctx):0;
int offset_attr = offset.evalAttr(prev);
uint start_offset = addOffset(relative_offset, offset_attr);
if (depth.isSet()) {
uint depth_size = addOffset(start_offset, depth.evalAttr(prev));
buf_size = std::min(buf_size, depth_size);
}
if (is_caret) {
buf_size = std::min(buf_size, start_offset+static_cast<uint>(pattern.size()));
}
return make_pair(start_offset, buf_size);
}
uint
DataKeyword::bytesMatched(const Buffer &buf, uint offset) const
{
if (is_case_insensitive) {
for (uint i = 0; i<pattern.size(); i++) {
if (pattern[pattern.size()-(i+1)] != tolower(buf[offset-(i+1)])) return i;
}
} else {
for (uint i = 0 ; i < pattern.size() ; i++ ) {
if (pattern[pattern.size()-(i+1)] != buf[offset-(i+1)] ) return i;
}
}
return pattern.size();
}
MatchStatus
DataKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
dbgAssert(pattern.size()>0) << "Trying to run on an uninitialized keyword data";
dbgDebug(D_KEYWORD) << "Searching for " << dumpHex(pattern);
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
if (!part.ok()) {
if (is_negative) return runNext(prev);
return MatchStatus::NoMatchFinal;
}
const auto &buf = part.unpack();
dbgTrace(D_KEYWORD) << "Full buffer: " << dumpHex(buf);
uint offset, max_offset;
tie(offset, max_offset) = getStartAndEndOffsets(buf.size(), prev);
offset += pattern.size();
bool match_found = false;
while (offset<=max_offset) {
// Short circuit for the common, simple case where the last byte doesn't match
if (skip[buf[offset-1]]) {
offset += skip[buf[offset - 1]];
continue;
}
// Full search Boyer-Moore
uint match_size = bytesMatched(buf, offset);
if (match_size == pattern.size()) {
if (is_negative) {
return isConstant()?MatchStatus::NoMatchFinal:MatchStatus::NoMatch;
}
match_found = true;
OffsetRuntimeState new_offset(prev, ctx, offset);
auto next_keyword_result = runNext(&new_offset);
if (next_keyword_result!=MatchStatus::NoMatch) return next_keyword_result;
offset += moveOnMatch();
} else {
offset += moveOnNoMatch(match_size, buf[offset-(match_size+1)]);
}
}
// No matchs is a success for negative keywords
if (is_negative && !match_found) return runNext(prev);
// If there were no matchs and the keyword is an effected by other keywords, then we know that the rule won't match
if (isConstant() && !match_found) return MatchStatus::NoMatchFinal;
return MatchStatus::NoMatch;
}
unique_ptr<SingleKeyword>
genDataKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<DataKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,174 @@
#include "single_keyword.h"
#include "output.h"
#include "debug.h"
#include <map>
#include <strings.h>
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class jumpKeyword : public SingleKeyword
{
public:
explicit jumpKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &vars);
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
private:
enum class JumpFromId
{
RELATIVE,
FROM_BEGINNING,
FROM_END
};
void
setContext(const KeywordAttr &attr)
{
ctx.setAttr(attr, "byte_extract");
}
void
setAlign(const KeywordAttr &attr)
{
if (align != 1) throw KeywordError("Double definition of the 'align' in the 'jump' keyword");
auto &vec = attr.getParams();
if (vec.size() != 2) throw KeywordError("Malformed 'align' in the 'jump' keyword");
if (vec[1] == "2") {
align = 2;
} else if (vec[1] == "4") {
align = 4;
} else {
throw KeywordError("Unknown 'align' in the 'jump' keyword: " + vec[1]);
}
}
bool
isConstant() const
{
return jumping_from != JumpFromId::RELATIVE && jumping_val.isConstant();
}
JumpFromId jumping_from;
NumericAttr jumping_val;
int align = 1;
CtxAttr ctx;
static const map<string, void(jumpKeyword::*)(const KeywordAttr &)> setops;
uint getStartOffset(uint buf_size, const I_KeywordRuntimeState *prev) const;
uint applyAlignment(uint value) const;
uint addOffset(uint offset, int add) const;
};
const map<string, void(jumpKeyword::*)(const KeywordAttr &)> jumpKeyword::setops = {
{ "part", &jumpKeyword::setContext },
{ "align", &jumpKeyword::setAlign }
};
jumpKeyword::jumpKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &vars)
{
//two requied attributes - jumping value and jumping from
if (attrs.size() < 2) throw KeywordError("Invalid number of attributes in the 'jump' keyword");
//parisng first attribute (Required) - jumping value
auto &jumping_val_param = attrs[0].getParams();
if (jumping_val_param.size() != 1) {
throw KeywordError("More than one element in the jumping value in the 'jump' keyword");
}
jumping_val.setAttr("jumping value", jumping_val_param[0], vars, "jump");
//parisng second attribute (Required) - jumping from
auto &jumping_from_param = attrs[1].getParams();
if (jumping_from_param.size() != 1) {
throw KeywordError("More than one element in the jumping 'from' parameter in the 'jump' keyword");
}
if (jumping_from_param[0] == "from_beginning") {
jumping_from = JumpFromId::FROM_BEGINNING;
} else if (jumping_from_param[0] == "from_end") {
jumping_from = JumpFromId::FROM_END;
} else if (jumping_from_param[0] == "relative") {
jumping_from = JumpFromId::RELATIVE;
} else {
throw KeywordError("Unknown jumping 'from' parameter in the 'jump' keyword: " + jumping_from_param[0]);
}
//parisng optional attributes
for (uint i = 2; i < attrs.size(); i++) {
auto curr = setops.find(attrs[i].getAttrName());
if (curr == setops.end()) {
throw KeywordError("Unknown attribute " + attrs[i].getAttrName() + " in the 'jump' keyword");
}
auto set_func = curr->second;
(this->*set_func)(attrs[i]);
}
}
uint
jumpKeyword::applyAlignment(uint value) const
{
int reminder = value % align;
if (reminder != 0) {
value += (align - reminder);
}
return value;
}
uint
jumpKeyword::addOffset(uint offset, int add) const
{
if (add < 0 && offset < static_cast<uint>(-add)) {
dbgWarning(D_KEYWORD)
<< "The offset was set to 0 "
<< "due to an attempt to jump before the beginning of the buffer in the 'jump' keyword";
return 0;
}
return applyAlignment(offset + add);
}
uint
jumpKeyword::getStartOffset(uint buf_size, const I_KeywordRuntimeState *prev) const
{
switch (jumping_from) {
case JumpFromId::FROM_BEGINNING: {
return 0;
}
case JumpFromId::FROM_END: {
return buf_size;
}
case JumpFromId::RELATIVE: {
return prev->getOffset(ctx);
}
}
dbgAssert(false) << "Invalid jumping 'from' parameter";
return 0;
}
MatchStatus
jumpKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
if (!part.ok()) return MatchStatus::NoMatchFinal;
uint start_offset = getStartOffset((*part).size(), prev);
uint offset_to_jump = addOffset(start_offset, jumping_val.evalAttr(prev));
if (offset_to_jump > (*part).size()) {
dbgDebug(D_KEYWORD) << "New offset exceeds the buffer size in the 'jump' keyword";
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
}
OffsetRuntimeState new_offset(prev, ctx, offset_to_jump);
return runNext(&new_offset);
}
unique_ptr<SingleKeyword>
genJumpKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<jumpKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,171 @@
#include "keyword_comp.h"
#include <vector>
#include "sentinel_runtime_state.h"
using namespace std;
static const string whitespaces = " \t";
static string
getSubStrNoPadding(const string &str, uint start, uint end)
{
auto r_start = str.find_first_not_of(whitespaces, start);
auto r_end = str.find_last_not_of(whitespaces, end-1);
if (r_end==string::npos || r_start==string::npos || r_start>r_end) {
throw KeywordError("Found an empty section in the '"+ str + "'");
}
return str.substr(r_start, r_end-r_start+1);
}
static vector<string>
split(const string &str, const string &delim, uint start = 0)
{
vector<string> res;
uint part_start = start;
bool escape = false;
bool in_string = false;
for (uint index = start; index<str.size(); index++) {
if (escape) {
escape = false;
continue;
}
switch (str[index]) {
case '\\': {
escape = true;
break;
}
case '"': {
in_string = !in_string;
break;
}
default:
if (!in_string && delim.find(str[index])!=string::npos) {
res.push_back(getSubStrNoPadding(str, part_start, index));
part_start = index+1;
}
}
}
if (escape||in_string) throw KeywordError("Split has ended in the middle of the parsing");
if (str.find_first_not_of(whitespaces, part_start)!=string::npos) {
res.push_back(getSubStrNoPadding(str, part_start, str.size()));
}
return res;
}
KeywordAttr::KeywordAttr(const string &str) : params(split(str, whitespaces))
{
}
KeywordParsed::KeywordParsed(string const &keyword) {
auto index = keyword.find_first_of(':');
if (index!=string::npos) {
for (auto &str : split(keyword, ",", index+1)) {
attr.push_back(KeywordAttr(str));
}
} else {
index = keyword.size();
}
name = getSubStrNoPadding(keyword, 0, index);
if (name.find_first_of(whitespaces)!=string::npos) {
throw KeywordError("'" + name + "' - cannot be a keyword name");
}
}
uint
SentinelRuntimeState::getOffset(const std::string &) const
{
return 0;
}
// LCOV_EXCL_START Reason: this function is tested in one_element_list_negative_test but marked as not covered.
uint
SentinelRuntimeState::getVariable(uint var_id) const
{
dbgAssert(false) << "Could not find the variable ID: " << var_id;
return 0;
}
// LCOV_EXCL_STOP
class SentinelKeyword : public SingleKeyword
{
public:
MatchStatus
isMatch() const
{
SentinelRuntimeState curr_state;
return runNext(&curr_state);
}
private:
// LCOV_EXCL_START Reason: Unreachable function.
MatchStatus
isMatch(const I_KeywordRuntimeState *state) const override
{
return runNext(state);
}
// LCOV_EXCL_STOP
};
class KeywordComp::Impl : Singleton::Provide<I_KeywordsRule>::From<KeywordComp>
{
public:
Maybe<shared_ptr<VirtualRule>>
genRule(const string &rule)
{
shared_ptr<VirtualRule> res;
try {
res = KeywordsRuleImpl::genRule(rule);
} catch (const KeywordError &e) {
return genError(e.getErr());;
}
return move(res);
}
private:
class KeywordsRuleImpl : public VirtualRule
{
public:
bool isMatch() const override { return start.isMatch() == MatchStatus::Match; }
static unique_ptr<KeywordsRuleImpl>
genRule(const string &rule)
{
auto res = make_unique<KeywordsRuleImpl>();
auto pos = rule.find_last_not_of(whitespaces);
if (pos==string::npos) {
// Empty rule
return res;
}
if (rule[pos]!=';') throw KeywordError(rule + " - end of text pass rule");
VariablesMapping known_vars;
auto key_vec = split(rule, ";");
for (auto &keyword : key_vec) {
res->start.appendKeyword(getKeywordByName(keyword, known_vars));
}
return res;
}
private:
SentinelKeyword start;
};
};
KeywordComp::KeywordComp() : Component("KeywordComp"), pimpl(make_unique<KeywordComp::Impl>()) {}
KeywordComp::~KeywordComp() {}
string I_KeywordsRule::keywords_tag = "keywords_rule_tag";

View File

@@ -0,0 +1,5 @@
add_unit_test(
keywords_ut
"keywords_ut.cc;single_keyword_ut.cc"
"keywords;pcre2-8;buffers;singleton;table;event_is;metric;-lboost_regex"
)

View File

@@ -0,0 +1,996 @@
#include "keyword_comp.h"
#include "environment.h"
#include "mock/mock_table.h"
#include "cptest.h"
#include "mock/mock_time_get.h"
#include "mock/mock_mainloop.h"
#include "config.h"
#include "config_component.h"
using namespace std;
class KeywordsRuleTest : public ::testing::Test
{
public:
void
appendBuffer(const string &id, const string &str)
{
buffers[id] += Buffer(str);
}
string
ruleCompileFail(const string &_rule)
{
auto rule = Singleton::Consume<I_KeywordsRule>::from(comp)->genRule(_rule);
EXPECT_FALSE(rule.ok()) << "Compile supposed to fail";
return rule.getErr();
}
bool
ruleRun(const string &_rule, const string &default_ctx = "default")
{
auto rule = Singleton::Consume<I_KeywordsRule>::from(comp)->genRule(_rule);
EXPECT_TRUE(rule.ok()) << "Compile not supposed to fail: " << rule.getErr();
ScopedContext ctx;
ctx.registerValue(I_KeywordsRule::getKeywordsRuleTag(), default_ctx);
for (auto &value : buffers) {
ctx.registerValue(value.first, value.second);
}
return (*rule)->isMatch();
}
private:
KeywordComp comp;
::testing::NiceMock<MockMainLoop> mock_mainloop;
::testing::NiceMock<MockTimeGet> mock_timer;
Environment env;
map<string, Buffer> buffers;
};
TEST_F(KeywordsRuleTest, data_basic_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
EXPECT_TRUE(ruleRun("data: \"234\" , part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("data: \"234\";", "HTTP_RESPONSE_BODY"));
EXPECT_FALSE(ruleRun("data: \"75\", part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, data_relative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("data: \"567\", part HTTP_RESPONSE_BODY; data: \"234\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(
ruleRun("data: \"567\", part HTTP_RESPONSE_BODY; data: \"234\", part HTTP_RESPONSE_BODY, relative;")
);
EXPECT_TRUE(ruleRun("data: \"234\", part HTTP_RESPONSE_BODY; data: \"567\", part HTTP_RESPONSE_BODY, relative;"));
}
TEST_F(KeywordsRuleTest, data_depth_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("data: \"345\", depth 5, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("data: \"345\", depth 4, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, data_nocase_test) {
appendBuffer("HTTP_RESPONSE_BODY", "abcdefg");
EXPECT_TRUE(ruleRun("data: \"cde\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("data: \"CDE\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("data: \"CDE\", nocase, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, data_offset_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("data: \"345\", offset 2, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("data: \"345\", offset 3, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, data_caret_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY, caret;"));
EXPECT_TRUE(ruleRun("data: \"345\", caret, part HTTP_RESPONSE_BODY, offset 2;"));
}
TEST_F(KeywordsRuleTest, data_negative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_FALSE(ruleRun("data: !\"345\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("data: !\"365\", part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, data_part_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
appendBuffer("HTTP_REQUEST_BODY", "abcdefg");
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY; data: \"cde\", part HTTP_REQUEST_BODY;"));
EXPECT_FALSE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY; data: \"cde\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("data: \"345\", part HTTP_REQUEST_BODY; data: \"cde\", part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, pcre_basic_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/5..7/\", part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, pcre_relative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY; pcre: \"/2.4/\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY; pcre: \"/2.4/R\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(
ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY; pcre: \"/2.4/\", relative, part HTTP_RESPONSE_BODY;")
);
EXPECT_TRUE(ruleRun("pcre: \"/2.4/\", part HTTP_RESPONSE_BODY; pcre: \"/5.7/R\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(
ruleRun("pcre: \"/2.4/\", part HTTP_RESPONSE_BODY; pcre: \"/5.7/\", relative, part HTTP_RESPONSE_BODY;")
);
}
TEST_F(KeywordsRuleTest, pcre_depth_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", depth 5, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", depth 4, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, pcre_nocase_test) {
appendBuffer("HTTP_RESPONSE_BODY", "abcdefg");
EXPECT_TRUE(ruleRun("pcre: \"/c.e/\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/C.E/\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("pcre: \"/C.E/i\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("pcre: \"/C.E/\", nocase, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, pcre_offset_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", offset 2, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", offset 300, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, pcre_part_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
appendBuffer("HTTP_REQUEST_BODY", "abcdefg");
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY; pcre: \"/c.e/\", part HTTP_REQUEST_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY; pcre: \"/c.e/\", part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", part HTTP_REQUEST_BODY; pcre: \"/c.e/\", part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, pcre_negative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_FALSE(ruleRun("pcre: !\"/3.5/\", part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("pcre: !\"/3..5/\", part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, compare_comparison_test) {
EXPECT_TRUE(ruleRun("compare: 0, =, 0;"));
EXPECT_TRUE(ruleRun("compare: -1, =, -1;"));
EXPECT_FALSE(ruleRun("compare: 0, =, 1;"));
EXPECT_FALSE(ruleRun("compare: -1, =, -2;"));
EXPECT_FALSE(ruleRun("compare: 1, =, -1;"));
EXPECT_FALSE(ruleRun("compare: -1, =, 1;"));
EXPECT_TRUE(ruleRun("compare: 2, !=, 3;"));
EXPECT_TRUE(ruleRun("compare: 2, <=, 3;"));
EXPECT_TRUE(ruleRun("compare: 2, <, 3;"));
EXPECT_FALSE(ruleRun("compare: 2, >, 3;"));
EXPECT_FALSE(ruleRun("compare: 2, >=, 3;"));
EXPECT_TRUE(ruleRun("compare: -2, !=, -3;"));
EXPECT_TRUE(ruleRun("compare: -2, >=, -3;"));
EXPECT_TRUE(ruleRun("compare: -2, >, -3;"));
EXPECT_FALSE(ruleRun("compare: -2, <, -3;"));
EXPECT_FALSE(ruleRun("compare: -2, <=, -3;"));
EXPECT_TRUE(ruleRun("compare: -2, !=, 3;"));
EXPECT_TRUE(ruleRun("compare: -2, <=, 3;"));
EXPECT_TRUE(ruleRun("compare: -2, <, 3;"));
EXPECT_FALSE(ruleRun("compare: -2, >, 3;"));
EXPECT_FALSE(ruleRun("compare: -2, >=, 3;"));
EXPECT_TRUE(ruleRun("compare: 2, !=, -3;"));
EXPECT_TRUE(ruleRun("compare: 2, >=, -3;"));
EXPECT_TRUE(ruleRun("compare: 2, >, -3;"));
EXPECT_FALSE(ruleRun("compare: 2, <, -3;"));
EXPECT_FALSE(ruleRun("compare: 2, <=, -3;"));
}
TEST_F(KeywordsRuleTest, compare_compile_fail_test) {
EXPECT_EQ(ruleCompileFail("compare: 0;"), "Invalid number of attributes in the 'compare' keyword");
EXPECT_EQ(ruleCompileFail("compare: 0, =;"), "Invalid number of attributes in the 'compare' keyword");
EXPECT_EQ(ruleCompileFail("compare: 0, =, 0, 0;"), "Invalid number of attributes in the 'compare' keyword");
EXPECT_EQ(
ruleCompileFail("compare: 0 1, =, 0;"),
"More than one element in the first value in the 'compare' keyword"
);
EXPECT_EQ(
ruleCompileFail("compare: 0, = =, 0;"),
"More than one element in the comparison operator in the 'compare' keyword"
);
EXPECT_EQ(
ruleCompileFail("compare: 0, =, 0 1;"),
"More than one element in the second value in the 'compare' keyword"
);
EXPECT_EQ(
ruleCompileFail("compare: 0, ==, 0;"),
"Unknown comparison operator in the 'compare' keyword: Could not find the operator: =="
);
}
TEST_F(KeywordsRuleTest, length_basic_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
appendBuffer("HTTP_REQUEST_BODY", "");
EXPECT_TRUE(
ruleRun(
"length: length_var, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 9;"
)
);
EXPECT_TRUE(
ruleRun(
"length: length_var, part HTTP_REQUEST_BODY;"
"compare: length_var, =, 0;"
)
);
EXPECT_FALSE(
ruleRun(
"length: length_var, part HTTP_REQUEST_BODY;"
"compare: length_var, =, 1;"
)
);
}
TEST_F(KeywordsRuleTest, length_part_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("length: length_var, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("length: length_var, part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, length_relative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
EXPECT_TRUE(
ruleRun(
"data: \"234\", part HTTP_RESPONSE_BODY;"
"length: relative_length_var, part HTTP_RESPONSE_BODY, relative;"
"compare: relative_length_var, =, 5;"
)
);
EXPECT_FALSE(
ruleRun(
"data: \"234\", part HTTP_RESPONSE_BODY;"
"length: relative_length_var, part HTTP_RESPONSE_BODY;"
"compare: relative_length_var, =, 5;"
)
);
EXPECT_TRUE(
ruleRun(
"data: \"89\", part HTTP_RESPONSE_BODY;"
"length: zero_length_var, part HTTP_RESPONSE_BODY, relative;"
"compare: zero_length_var, =, 0;"
)
);
EXPECT_FALSE(
ruleRun(
"data: \"89\", part HTTP_RESPONSE_BODY;"
"length: zero_length_var, part HTTP_RESPONSE_BODY;"
"compare: zero_length_var, =, 0;"
)
);
}
TEST_F(KeywordsRuleTest, length_compare_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123");
EXPECT_FALSE(ruleRun("length: 6, min, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("length: 6, exact, part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("length: 6, max, part HTTP_RESPONSE_BODY;"));
appendBuffer("HTTP_RESPONSE_BODY", "456");
EXPECT_TRUE(ruleRun("length: 6, min, part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("length: 6, exact, part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(ruleRun("length: 6, max, part HTTP_RESPONSE_BODY;"));
appendBuffer("HTTP_RESPONSE_BODY", "789");
EXPECT_TRUE(ruleRun("length: 6, min, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("length: 6, exact, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("length: 6, max, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, length_compile_fail_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
EXPECT_EQ(
ruleCompileFail("length: two_elem 2, part HTTP_RESPONSE_BODY;"),
"More than one element in the variable name in the 'length' keyword"
);
EXPECT_EQ(
ruleCompileFail("length: relative, part HTTP_RESPONSE_BODY;"),
"The 'relative' cannot be the variable name in the 'length' keyword"
);
EXPECT_EQ(
ruleCompileFail("length: part, part HTTP_RESPONSE_BODY;"),
"The 'part' cannot be the variable name in the 'length' keyword"
);
EXPECT_EQ(
ruleCompileFail("length: -minus, part HTTP_RESPONSE_BODY;"),
"Malformed variable name in the 'length' keyword"
);
EXPECT_EQ(
ruleCompileFail("length: 1digit, part HTTP_RESPONSE_BODY;"),
"Malformed variable name in the 'length' keyword"
);
EXPECT_EQ(
ruleCompileFail("length: bad_attr, partt HTTP_RESPONSE_BODY;"),
"Unknown attribute 'partt' in the 'length' keyword"
);
EXPECT_EQ(
ruleCompileFail("length:;"),
"Invalid number of attributes in the 'length' keyword"
);
}
TEST_F(KeywordsRuleTest, byte_extract_dec_string_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234");
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, dec_var, string dec, part HTTP_RESPONSE_BODY;"
"data: \"234\", offset dec_var, part HTTP_RESPONSE_BODY;"
)
);
EXPECT_FALSE(
ruleRun(
"byte_extract: 1, dec_var, string dec, part HTTP_RESPONSE_BODY;"
"data: \"123\", offset dec_var, part HTTP_RESPONSE_BODY;"
)
);
appendBuffer("HTTP_REQUEST_BODY", "A");
EXPECT_FALSE(ruleRun("byte_extract: 1, bad_dec_var, string dec, part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, byte_extract_hex_string_test) {
appendBuffer("HTTP_RESPONSE_BODY", "A123");
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, hex_var, string hex, part HTTP_RESPONSE_BODY;"
"compare: hex_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"byte_extract: 2, hex_var, string hex, part HTTP_RESPONSE_BODY;"
"compare: hex_var, =, 161;"
)
);
appendBuffer("HTTP_REQUEST_BODY", "10G");
EXPECT_FALSE(
ruleRun(
"byte_extract: 2, hex_var, string hex, part HTTP_REQUEST_BODY;"
"compare: hex_var, =, 10;"
)
);
EXPECT_FALSE(ruleRun("byte_extract: 3, bad_hex_var, string oct, part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, byte_extract_oct_string_test) {
appendBuffer("HTTP_RESPONSE_BODY", "13ABC");
EXPECT_TRUE(
ruleRun(
"byte_extract: 2, oct_var, string oct, part HTTP_RESPONSE_BODY;"
"compare: oct_var, =, 11;"
)
);
appendBuffer("HTTP_REQUEST_BODY", "118");
EXPECT_FALSE(
ruleRun(
"byte_extract: 2, oct_var, string oct, part HTTP_REQUEST_BODY;"
"compare: oct_var, =, 13;"
)
);
EXPECT_FALSE(ruleRun("byte_extract: 3, bad_oct_var, string oct, part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, byte_extract_binary_data_test) {
string one_byte_binary_data = {10};
appendBuffer("HTTP_RESPONSE_BODY", one_byte_binary_data);
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, binary_data_var, part HTTP_RESPONSE_BODY;"
"compare: binary_data_var, =, 10;"
)
);
EXPECT_FALSE(
ruleRun(
"byte_extract: 1, dec_data_var, offset 2, string dec, part HTTP_RESPONSE_BODY;"
"compare: dec_data_var, =, 10;"
)
);
string two_bytes_binary_data = {1, 0, 0};
appendBuffer("HTTP_REQUEST_BODY", two_bytes_binary_data);
EXPECT_TRUE(
ruleRun(
"byte_extract: 2, binary_data_var, part HTTP_REQUEST_BODY;"
"compare: binary_data_var , =, 256;"
)
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 3, not1/2/4, part HTTP_REQUEST_BODY;"),
"Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail(
"byte_extract: 1, no_constant, part HTTP_REQUEST_BODY;"
"byte_extract: no_constant, var, part HTTP_REQUEST_BODY;"
),
"Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword"
);
}
TEST_F(KeywordsRuleTest, byte_extract_bad_num_of_bytes_test) {
appendBuffer("HTTP_RESPONSE_BODY", "0");
EXPECT_EQ(
ruleCompileFail("byte_extract: 0, zero_bytes_var, string dec, part HTTP_RESPONSE_BODY;"),
"Number of bytes is zero in the 'byte_extract' keyword"
);
EXPECT_FALSE(
ruleRun(
"byte_extract: 1, one_byte_var, string dec, part HTTP_RESPONSE_BODY;"
"byte_extract: one_byte_var, zero_bytes_var, string dec, part HTTP_RESPONSE_BODY;"
)
);
}
TEST_F(KeywordsRuleTest, byte_extract_part_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123");
EXPECT_TRUE(ruleRun("byte_extract: 1, part_var, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("byte_extract: 1, part_var, part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, byte_extract_offset_test) {
appendBuffer("HTTP_REQUEST_BODY", "1A23456789hello");
EXPECT_FALSE(
ruleRun(
"byte_extract: 1, hex_var, offset 1, string hex, part HTTP_REQUEST_BODY; "
"data: \"9hell\", offset hex_var, part HTTP_REQUEST_BODY;"
)
);
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, hex_var, offset 1, string hex, part HTTP_REQUEST_BODY;"
"data: \"hell\", offset hex_var, part HTTP_REQUEST_BODY;"
)
);
EXPECT_FALSE(
ruleRun(
"byte_extract: 1, dec_var, offset -1, string dec, part HTTP_REQUEST_BODY;"
"data: \"1A2\", offset dec_var, part HTTP_REQUEST_BODY;"
)
);
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, dec_var, offset -1, string dec, part HTTP_REQUEST_BODY;"
"data: \"A2\", offset dec_var, part HTTP_REQUEST_BODY;"
)
);
}
TEST_F(KeywordsRuleTest, byte_extract_relative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
EXPECT_TRUE(
ruleRun(
"data: \"12\", part HTTP_RESPONSE_BODY;"
"byte_extract: 1, relative_var, relative, string dec, part HTTP_RESPONSE_BODY;"
"compare: relative_var, =, 3;"
)
);
EXPECT_FALSE(
ruleRun(
"data: \"12\", part HTTP_RESPONSE_BODY;"
"byte_extract: 1, non_relative_var, string dec, part HTTP_RESPONSE_BODY;"
"compare: non_relative_var, =, 3;"
)
);
EXPECT_FALSE(
ruleRun(
"data: \"89\", part HTTP_RESPONSE_BODY;"
"byte_extract: 1, relative_var, string dec, relative, part HTTP_RESPONSE_BODY;"
)
);
}
TEST_F(KeywordsRuleTest, byte_extract_endianness_test) {
string little_end_test_str = {8, 0, 0};
appendBuffer("HTTP_RESPONSE_BODY", little_end_test_str);
EXPECT_TRUE(
ruleRun(
"byte_extract: 2, lit_end_var, little_endian, part HTTP_RESPONSE_BODY;"
"compare: lit_end_var, =, 8;"
)
);
EXPECT_FALSE(
ruleRun(
"byte_extract: 2, big_end_var, part HTTP_RESPONSE_BODY;"
"compare: big_end_var, =, 8;"
)
);
little_end_test_str[1] = 0;
little_end_test_str[2] = 1;
appendBuffer("HTTP_REQUEST_BODY", little_end_test_str);
EXPECT_TRUE(
ruleRun(
"byte_extract: 2, lit_end_with_offset_var,"
"offset 1, little_endian, part HTTP_REQUEST_BODY;"
"compare: lit_end_with_offset_var, =, 256;"
)
);
EXPECT_FALSE(
ruleRun(
"byte_extract: 2, big_end_with_offset_var, offset 1, part HTTP_REQUEST_BODY;"
"compare: big_end_with_offset_var, =, 256;"
)
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, var, little_endian, part HTTP_REQUEST_BODY;"),
"Little endian is set, but the number of bytes is invalid in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 2, no_binary, little_endian, string dec, part HTTP_REQUEST_BODY;"),
"Little endian is set, but the data type is not binary in the 'byte_extract' keyword"
);
}
TEST_F(KeywordsRuleTest, byte_extract_align_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234");
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, align2_var, align 2, string dec, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 2;"
)
);
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, align4_var, align 4, string dec, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 0;"
)
);
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, align2_var, offset 3, align 2, string dec, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 0;"
)
);
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, align4_var, offset 3, align 4, string dec, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 0;"
)
);
appendBuffer("HTTP_REQUEST_BODY", "123");
EXPECT_TRUE(
ruleRun(
"byte_extract: 1, align2_var, offset 1, align 2, string dec, part HTTP_REQUEST_BODY;"
"length: length_var, relative, part HTTP_REQUEST_BODY;"
"compare: length_var, =, 1;"
)
);
EXPECT_FALSE(ruleRun("byte_extract: 1, align4_var, align 4, string dec, part HTTP_REQUEST_BODY;"));
EXPECT_FALSE(ruleRun("byte_extract: 1, align2_var, offset 2, align 2, string dec, part HTTP_REQUEST_BODY;"));
string binary_data_str = { 1 };
appendBuffer("HTTP_REQUEST_BODY", binary_data_str);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, align_binary_var, align 2, part HTTP_REQUEST_BODY;"),
"The 'align' is set and data type is binary in the 'byte_extract' keyword"
);
}
TEST_F(KeywordsRuleTest, byte_extract_overflow_test) {
string overflow_dec_data_str = to_string((uint)INT_MAX + 1);
appendBuffer("HTTP_RESPONSE_BODY", overflow_dec_data_str);
EXPECT_FALSE(
ruleRun(
"byte_extract: " + to_string(overflow_dec_data_str.length()) + ","
"overflow_var, string dec, part HTTP_RESPONSE_BODY;"
)
);
string max_value_dec_data_str = to_string(INT_MAX);
appendBuffer("HTTP_REQUEST_BODY", max_value_dec_data_str);
EXPECT_TRUE(
ruleRun(
"byte_extract: " + to_string(max_value_dec_data_str.length()) + ","
"max_var, string dec, part HTTP_REQUEST_BODY;"
"compare: max_var, =, " + max_value_dec_data_str + ";"
)
);
string overflow_binary_data_str = { 0x7f, 0x7f, 0x7f, 0x7f, 0 };
appendBuffer("HTTP_REQUEST_HEADERS", overflow_binary_data_str);
EXPECT_FALSE(ruleRun("byte_extract: 5 ,overflow_num_var, string dec, part HTTP_REQUEST_HEADERS;"));
}
TEST_F(KeywordsRuleTest, byte_extract_compile_fail_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_EQ(
ruleCompileFail("byte_extract: 1 2, dec_var, string dec, part HTTP_RESPONSE_BODY;"),
"More than one element in the 'bytes' in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, dec_var 1, string dec, part HTTP_RESPONSE_BODY;"),
"More than one element in the variable name in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, align, string dec, part HTTP_RESPONSE_BODY;"),
"'align' cannot be the variable name in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, -1, string dec, part HTTP_RESPONSE_BODY;"),
"Malformed variable name in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_data_type, string dechex, part HTTP_RESPONSE_BODY;"),
"Unknown data type in the 'byte_extract' keyword: dechex"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, 1var, string dec, part HTTP_RESPONSE_BODY;"),
"Malformed variable name in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_align, align 3, part HTTP_RESPONSE_BODY;"),
"Unknown 'align' in the 'byte_extract' keyword: 3"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_constant, offset 0x;"),
"Malformed constant '0x' in the 'offset' in the 'byte_extract' keyword"
);
EXPECT_EQ(ruleCompileFail("byte_extract: 1;"), "Invalid number of attributes in the 'byte_extract' keyword");
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_attr, offset;"),
"Malformed offset' in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_attr, string hex dec;"),
"Malformed data type in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_attr, ofset 5;"),
"Unknown attribute 'ofset' in the 'byte_extract' keyword"
);
EXPECT_EQ(
ruleCompileFail("byte_extract: 1, bad_align, align 2 4;"),
"Malformed 'align' in the 'byte_extract' keyword"
);
}
TEST_F(KeywordsRuleTest, jump_from_beginning_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(
ruleRun(
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 9;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 9;"
)
);
EXPECT_FALSE(
ruleRun(
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: -1, from_beginning, part HTTP_RESPONSE_BODY;"
"length: length_var, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 10, from_beginning, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 0;"
)
);
EXPECT_FALSE(ruleRun("jump: 11, from_beginning, part HTTP_RESPONSE_BODY;"));
}
TEST_F(KeywordsRuleTest, jump_relative_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(
ruleRun(
"data: \"1\", part HTTP_RESPONSE_BODY;"
"jump: 1, relative, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 8;"
)
);
EXPECT_FALSE(
ruleRun(
"data: \"1\", part HTTP_RESPONSE_BODY;"
"jump: 1, relative, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 9;"
)
);
EXPECT_TRUE(
ruleRun(
"data: \"1\", part HTTP_RESPONSE_BODY;"
"jump: -2, relative, part HTTP_RESPONSE_BODY;"
"length: length_var, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"data: \"1\", part HTTP_RESPONSE_BODY;"
"jump: 9, relative, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 0;"
)
);
EXPECT_FALSE(
ruleRun(
"data: \"1\", part HTTP_RESPONSE_BODY;"
"jump: 10, relative, part HTTP_RESPONSE_BODY;"
)
);
}
TEST_F(KeywordsRuleTest, jump_from_end_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_FALSE(ruleRun("jump: 1, from_end, part HTTP_RESPONSE_BODY;"));
EXPECT_TRUE(
ruleRun(
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 1;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: -10, from_end, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: -11, from_end, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
}
TEST_F(KeywordsRuleTest, combined_jumps_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(
ruleRun(
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 1;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
"jump: -1, relative, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
"jump: 1, relative, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 0;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 1;"
)
);
}
TEST_F(KeywordsRuleTest, jump_alignment_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(
ruleRun(
"jump: 1, from_beginning, align 2, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 8;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 1, from_beginning, align 4, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 6;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 3, from_beginning, part HTTP_RESPONSE_BODY;"
"jump: 2, relative, align 2, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 4;"
)
);
EXPECT_FALSE(
ruleRun(
"jump: 3, from_beginning, part HTTP_RESPONSE_BODY;"
"jump: 2, relative, align 2, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 7;"
)
);
EXPECT_FALSE(
ruleRun(
"jump: 3, from_beginning, part HTTP_RESPONSE_BODY;"
"jump: 2, relative, align 4, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 3;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 2, from_beginning, align 2, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 8;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 4, from_beginning, align 4, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 6;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 0, from_beginning, align 2, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
EXPECT_TRUE(
ruleRun(
"jump: 0, from_beginning, align 4, part HTTP_RESPONSE_BODY;"
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
"compare: length_var, =, 10;"
)
);
}
TEST_F(KeywordsRuleTest, jump_part_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_TRUE(ruleRun("jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"));
EXPECT_FALSE(ruleRun("jump: 1, from_beginning, part HTTP_REQUEST_BODY;"));
}
TEST_F(KeywordsRuleTest, jump_compile_fail_test) {
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
EXPECT_EQ(ruleCompileFail("jump: 1;"), "Invalid number of attributes in the 'jump' keyword");
EXPECT_EQ(
ruleCompileFail("jump: 2 1, from_beginning;"),
"More than one element in the jumping value in the 'jump' keyword"
);
EXPECT_EQ(
ruleCompileFail("jump: 2, from_relative;"),
"Unknown jumping 'from' parameter in the 'jump' keyword: from_relative"
);
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align 3;"), "Unknown 'align' in the 'jump' keyword: 3");
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align 1;"), "Unknown 'align' in the 'jump' keyword: 1");
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align2 2;"), "Unknown attribute align2 in the 'jump' keyword");
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align 2 4;"), "Malformed 'align' in the 'jump' keyword");
EXPECT_EQ(
ruleCompileFail("jump: 2, from_beginning relative;"),
"More than one element in the jumping 'from' parameter in the 'jump' keyword"
);
}
TEST_F(KeywordsRuleTest, stateop)
{
using testing::_;
ConfigComponent conf;
testing::StrictMock<MockTable> table;
std::unique_ptr<TableOpaqueBase> opq;
TableOpaqueBase *opq_ptr;
bool has_stage = false;
EXPECT_CALL(table, createStateRValueRemoved(_, _))
.WillOnce(testing::DoAll(
testing::Invoke(
[&] (const type_index &, std::unique_ptr<TableOpaqueBase> &other)
{
opq = std::move(other);
opq_ptr = opq.get();
has_stage = true;
}
),
testing::Return(true)
));
EXPECT_CALL(table, getState(_)).WillRepeatedly(testing::ReturnPointee(&opq_ptr));
EXPECT_CALL(table, hasState(_)).WillRepeatedly(testing::ReturnPointee(&has_stage));
EXPECT_FALSE(ruleRun("stateop: state sss, isset;"));
EXPECT_TRUE(ruleRun("stateop: state sss, unset;"));
EXPECT_FALSE(ruleRun("stateop: state sss, isset;"));
EXPECT_TRUE(ruleRun("stateop: state sss, set;"));
EXPECT_TRUE(ruleRun("stateop: state sss, isset;"));
EXPECT_FALSE(ruleRun("stateop: state dd, isset;"));
EXPECT_TRUE(ruleRun("stateop: state sss, unset;"));
EXPECT_FALSE(ruleRun("stateop: state sss, isset;"));
}
TEST_F(KeywordsRuleTest, no_match)
{
EXPECT_FALSE(ruleRun("no_match;"));
}

View File

@@ -0,0 +1,171 @@
#include "../sentinel_runtime_state.h"
#include "../single_keyword.h"
#include "cptest.h"
#include <list>
using namespace std;
using namespace testing;
#define FIRST_VARIABLE_ID 1
#define FIRST_VARIABLE_VAL 2u
#define SECOND_VARIABLE_ID 3
#define SECOND_VARIABLE_VAL 4u
#define THIRD_VARIABLE_ID 5
#define THIRD_VARIABLE_VAL 6u
#define FIRST_OFFSET 4u
#define SECOND_OFFSET 5u
#define THIRD_OFFSET 6u
const static unsigned int zero = 0;
class I_KeywordRuntimeStateTest : public Test
{
public:
// Constructs SentinelKeyword as head because it is the only I_KeywordRuntimeState implementation
// that does not hold I_KeywordRuntimeState *prev
I_KeywordRuntimeStateTest() : list_head(&sentinel) {}
uint
getOffset(const string &id)
{
return list_head->getOffset(id);
}
uint
getVariable(uint requested_var_id)
{
return list_head->getVariable(requested_var_id);
}
void
addOffsetState(const string &_ctx, uint _offset)
{
offset_list.push_front(make_unique<OffsetRuntimeState>(list_head, _ctx, _offset));
list_head = offset_list.front().get();
}
void
addVariableState(uint _var_id, uint _val)
{
variable_list.push_front(make_unique<VariableRuntimeState>(list_head, _var_id, _val));
list_head = variable_list.front().get();
}
private:
list<unique_ptr<VariableRuntimeState>> variable_list;
list<unique_ptr<OffsetRuntimeState>> offset_list;
SentinelRuntimeState sentinel;
I_KeywordRuntimeState *list_head;
};
TEST_F(I_KeywordRuntimeStateTest, one_element_list_positive_test) {
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), zero);
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
}
TEST_F(I_KeywordRuntimeStateTest, one_variable_state_list_positive_test) {
addVariableState(FIRST_VARIABLE_ID, FIRST_VARIABLE_VAL);
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), zero);
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), FIRST_VARIABLE_VAL);
}
TEST_F(I_KeywordRuntimeStateTest, one_offset_state_list_positive_test) {
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
addOffsetState("HTTP_REQUEST_HEADERS", FIRST_OFFSET);
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), FIRST_OFFSET);
}
TEST_F(I_KeywordRuntimeStateTest, one_element_list_negative_test) {
cptestPrepareToDie();
EXPECT_DEATH(getVariable(FIRST_OFFSET), "");
EXPECT_DEATH(getVariable(SECOND_OFFSET), "");
EXPECT_DEATH(getVariable(THIRD_OFFSET), "");
}
TEST_F(I_KeywordRuntimeStateTest, variable_runtime_state_list_positive_test) {
// Notice that variables ids and values are different
addVariableState(FIRST_VARIABLE_ID, FIRST_VARIABLE_VAL);
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
addVariableState(THIRD_VARIABLE_ID, THIRD_VARIABLE_VAL);
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), zero);
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), zero);
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
EXPECT_EQ(getOffset("HTTP_REQUEST_BODY"), zero);
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), FIRST_VARIABLE_VAL);
EXPECT_EQ(getVariable(SECOND_VARIABLE_ID), SECOND_VARIABLE_VAL);
EXPECT_EQ(getVariable(THIRD_VARIABLE_ID), THIRD_VARIABLE_VAL);
}
TEST_F(I_KeywordRuntimeStateTest, OffsetRuntimeState_list_negative_test) {
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
addOffsetState("HTTP_REQ_COOKIE", SECOND_OFFSET);
addOffsetState("HTTP_METHOD", THIRD_OFFSET);
cptestPrepareToDie();
EXPECT_DEATH(getVariable(FIRST_OFFSET), "");
EXPECT_DEATH(getVariable(SECOND_OFFSET), "");
EXPECT_DEATH(getVariable(THIRD_OFFSET), "");
}
TEST_F(I_KeywordRuntimeStateTest, offset_runtime_state_list_positive_test) {
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), zero);
addOffsetState("HTTP_REQUEST_HEADERS", FIRST_OFFSET);
addOffsetState("HTTP_REQ_COOKIE", SECOND_OFFSET);
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), FIRST_OFFSET);
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), SECOND_OFFSET);
}
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_positive_test) {
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), zero);
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
addOffsetState("HTTP_METHOD", THIRD_OFFSET);
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), FIRST_OFFSET);
EXPECT_EQ(getOffset("HTTP_METHOD"), THIRD_OFFSET);
EXPECT_EQ(getVariable(SECOND_VARIABLE_ID), SECOND_VARIABLE_VAL);
}
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_negative_test) {
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
addOffsetState("HTTP_METHOD", THIRD_OFFSET);
cptestPrepareToDie();
EXPECT_DEATH(getVariable(FIRST_OFFSET), "");
EXPECT_DEATH(getVariable(THIRD_OFFSET), "");
}
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_offset_shadowing_test) {
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), FIRST_OFFSET);
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
addOffsetState("HTTP_COMPLETE_URL_ENCODED", THIRD_OFFSET);
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), THIRD_OFFSET);
}
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_variable_shadowing_test) {
addVariableState(FIRST_VARIABLE_ID, FIRST_VARIABLE_VAL);
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), FIRST_VARIABLE_VAL);
addOffsetState("HTTP_COMPLETE_URL_ENCODED", SECOND_OFFSET);
addVariableState(FIRST_VARIABLE_ID, THIRD_VARIABLE_VAL);
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), THIRD_VARIABLE_VAL);
}

View File

@@ -0,0 +1,162 @@
#include "single_keyword.h"
#include "output.h"
#include "debug.h"
#include "flags.h"
#include <map>
#include <strings.h>
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class LengthKeyword : public SingleKeyword
{
public:
explicit LengthKeyword(const vector<KeywordAttr> &attr, VariablesMapping &vars);
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
private:
enum class Mode { EXACT, MIN, MAX, COUNT };
using ModeFlags = Flags<Mode>;
void
setRelative(const KeywordAttr &attr, const VariablesMapping &)
{
is_relative.setAttr(attr, "length");
}
void
setExact(const KeywordAttr &, const VariablesMapping &)
{
if (!mode.empty()) throw KeywordError("Redefining 'length' keyword operation");
mode.setFlag(Mode::EXACT);
}
void
setMin(const KeywordAttr &, const VariablesMapping &)
{
if (!mode.empty()) throw KeywordError("Redefining 'length' keyword operation");
mode.setFlag(Mode::MIN);
}
void
setMax(const KeywordAttr &, const VariablesMapping &)
{
if (!mode.empty()) throw KeywordError("Redefining 'length' keyword operation");
mode.setFlag(Mode::MAX);
}
void
setContext(const KeywordAttr &attr, const VariablesMapping &)
{
ctx.setAttr(attr, "length");
}
bool
isConstant() const
{
return !is_relative && compare_size.isConstant();
}
BoolAttr is_relative;
ModeFlags mode;
CtxAttr ctx;
uint var_id;
NumericAttr compare_size;
static const map<string, void(LengthKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
};
const map<string, void(LengthKeyword::*)(const KeywordAttr &, const VariablesMapping &)> LengthKeyword::setops = {
{ "relative", &LengthKeyword::setRelative },
{ "exact", &LengthKeyword::setExact },
{ "min", &LengthKeyword::setMin },
{ "max", &LengthKeyword::setMax },
{ "part", &LengthKeyword::setContext }
};
LengthKeyword::LengthKeyword(const vector<KeywordAttr> &attrs, VariablesMapping &vars)
{
if (attrs.size() == 0) throw KeywordError("Invalid number of attributes in the 'length' keyword");
//parisng first attribute (Required) - variable name
auto &var_name_param = attrs[0].getParams();
if (var_name_param.size() != 1) {
throw KeywordError("More than one element in the variable name in the 'length' keyword");
}
const string &string_var_name = var_name_param[0];
if (string_var_name == "relative") {
throw KeywordError("The 'relative' cannot be the variable name in the 'length' keyword");
}
if (string_var_name == "part") {
throw KeywordError("The 'part' cannot be the variable name in the 'length' keyword");
}
if (string_var_name == "exact") {
throw KeywordError("The 'exact' cannot be the variable name in the 'length' keyword");
}
if (string_var_name == "min") {
throw KeywordError("The 'min' cannot be the variable name in the 'length' keyword");
}
if (string_var_name == "max") {
throw KeywordError("The 'max' cannot be the variable name in the 'length' keyword");
}
//parsing the other optional attributes
for (uint i = 1; i < attrs.size(); i++) {
auto curr = setops.find(attrs[i].getAttrName());
if (curr == setops.end()) {
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'length' keyword");
}
auto set_func = curr->second;
(this->*set_func)(attrs[i], vars);
}
if (mode.empty()) {
if (isdigit(string_var_name[0]) || string_var_name[0] == '-') {
throw KeywordError("Malformed variable name in the 'length' keyword");
}
var_id = vars.addNewVariable(string_var_name);
} else {
compare_size.setAttr("length value", string_var_name, vars, "length", 10, true);
}
}
MatchStatus
LengthKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
if (!part.ok()) return MatchStatus::NoMatchFinal;
uint offset = is_relative ? prev->getOffset(ctx) : 0;
uint size = (*part).size();
if (offset <= size) {
if (mode.isSet(Mode::EXACT)) {
if (size - offset == static_cast<uint>(compare_size.evalAttr(prev))) return runNext(prev);
} else if (mode.isSet(Mode::MIN)) {
if (size - offset >= static_cast<uint>(compare_size.evalAttr(prev))) return runNext(prev);
} else if (mode.isSet(Mode::MAX)) {
if (size - offset <= static_cast<uint>(compare_size.evalAttr(prev))) return runNext(prev);
} else {
VariableRuntimeState new_length_var(prev, var_id, size-offset);
return runNext(&new_length_var);
}
}
// If there was no matches and the keyword is effected by other keywords, then we know that the rule won't match
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
}
unique_ptr<SingleKeyword>
genLengthKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<LengthKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,29 @@
#include "single_keyword.h"
#include "table_opaque.h"
#include "debug.h"
#include <map>
#include <strings.h>
#include "cereal/types/set.hpp"
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class NoMatchKeyword : public SingleKeyword
{
public:
explicit NoMatchKeyword(const vector<KeywordAttr> &attr, VariablesMapping &)
{
if (!attr.empty()) throw KeywordError("The 'no_match' keyword doesn't take attributes");
}
MatchStatus isMatch(const I_KeywordRuntimeState *) const override { return MatchStatus::NoMatchFinal; }
};
unique_ptr<SingleKeyword>
genNoMatchKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<NoMatchKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,372 @@
#include "single_keyword.h"
#include <algorithm>
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#include "output.h"
#include "debug.h"
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class PCREKeyword : public SingleKeyword
{
public:
explicit PCREKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &known_vars);
MatchStatus isMatch(const I_KeywordRuntimeState *prev) const override;
private:
void
setOffset(const KeywordAttr &attr, const VariablesMapping &vars)
{
offset.setAttr(attr, vars, "pcre");
}
void
setDepth(const KeywordAttr &attr, const VariablesMapping &vars)
{
depth.setAttr(attr, vars, "pcre");
}
void
setRelative(const KeywordAttr &attr, const VariablesMapping &)
{
is_relative.setAttr(attr, "pcre");
}
void
setCaseInsensitive(const KeywordAttr &attr, const VariablesMapping &)
{
is_case_insensitive.setAttr(attr, "pcre");
}
void
setContext(const KeywordAttr &attr, const VariablesMapping &)
{
ctx.setAttr(attr, "pcre");
}
string parseString(const string &str);
pair<string, string> findExprInStr(const string &str, size_t start, size_t end);
void parseOptions(const string &str);
void compilePCRE(const string &str);
pair<uint, uint> getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const;
bool
isConstant() const
{
return !is_relative && offset.isConstant() && depth.isConstant();
}
class PCREDelete
{
public:
void
operator()(pcre2_code *ptr)
{
pcre2_code_free(ptr);
}
};
unique_ptr<pcre2_code, PCREDelete> pcre_machine;
class PCREResultDelete
{
public:
void
operator()(pcre2_match_data *ptr)
{
pcre2_match_data_free(ptr);
}
};
unique_ptr<pcre2_match_data, PCREResultDelete> pcre_result;
NumericAttr offset;
NumericAttr depth;
BoolAttr is_negative;
BoolAttr is_relative;
BoolAttr is_case_insensitive;
BoolAttr is_multiline;
BoolAttr is_dotall;
BoolAttr is_extended;
BoolAttr is_dollar_endonly;
BoolAttr is_anchor;
BoolAttr is_ungreedy;
CtxAttr ctx;
string pcre_expr;
static const map<string, void(PCREKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
};
const map<string, void(PCREKeyword::*)(const KeywordAttr&, const VariablesMapping&)> PCREKeyword::setops = {
{ "relative", &PCREKeyword::setRelative },
{ "offset", &PCREKeyword::setOffset },
{ "depth", &PCREKeyword::setDepth },
{ "nocase", &PCREKeyword::setCaseInsensitive },
{ "part", &PCREKeyword::setContext },
};
PCREKeyword::PCREKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &known_vars)
:
offset(),
depth()
{
auto &expr_param = attrs[0].getParams();
if (expr_param.size() != 1) throw KeywordError("More than one element in the 'pcre' keyword pattern");
auto expr = parseString(expr_param[0]);
dbgDebug(D_KEYWORD) << "Creating a new 'pcre' expression: " << expr;
for (uint i = 1; i<attrs.size(); i++) {
auto curr = setops.find(attrs[i].getAttrName());
if (curr == setops.end()) {
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'pcre' keyword");
}
auto set_func = curr->second;
(this->*set_func)(attrs[i], known_vars);
}
compilePCRE(expr);
}
string
PCREKeyword::parseString(const string &str)
{
size_t start_offset = 0, end_offset = str.size();
if (start_offset<end_offset && str[start_offset]=='!') {
is_negative.setAttr("pcre", "negative");
start_offset++;
}
if (start_offset+1>=end_offset || str[start_offset]!='"' || str[end_offset-1]!='"') {
throw KeywordError("The 'pcre' expression should be enclosed in quotation marks");
}
start_offset++;
end_offset--;
string expr, options;
tie(expr, options) = findExprInStr(str, start_offset, end_offset);
parseOptions(options);
return expr;
}
pair<string, string>
PCREKeyword::findExprInStr(const string &str, size_t start, size_t end)
{
if (start>=end) throw KeywordError("The 'pcre' string is empty");
// There are two way to write the regular expression:
// Either between '/' charecters: "/regexp/" (this is the default delimiter)
// Or use 'm' to set a delimiter: "mDregexpD" (here 'D' is used as the delimiter)
// The switch will set the parameter 'start' so the 'str[start]' is the first delimiter.
switch (str[start]) {
case '/': {
break;
}
case 'm': {
start++;
if (start>=end) {
throw KeywordError("Failed to detect a delimiter in the 'pcre' keyword regular expression");
}
break;
}
default:
KeywordError("Bad start for the 'pcre' regular expression");
}
size_t expr_end = str.find_last_of(str[start], end-1);
start++;
if (expr_end<=start) throw KeywordError("The 'pcre' regular expression is empty");
auto options_start = expr_end+1;
return make_pair(str.substr(start, expr_end-start), str.substr(options_start, end-options_start));
}
void
PCREKeyword::parseOptions(const string &options)
{
for (auto ch : options) {
switch (ch) {
case 'i': {
is_case_insensitive.setAttr("pcre", "nocase");
break;
}
case 'R': {
is_relative.setAttr("pcre", "relative");
break;
}
case 'm': {
is_multiline.setAttr("pcre", "multiline");
break;
}
case 's': {
is_dotall.setAttr("pcre", "dotall");
break;
}
case 'x': {
is_extended.setAttr("pcre", "extended");
break;
}
case 'E': {
is_dollar_endonly.setAttr("pcre", "dollar_endonly");
break;
}
case 'A': {
is_anchor.setAttr("pcre", "anchor");
break;
}
case 'G': {
is_ungreedy.setAttr("pcre", "ungreedy");
break;
}
default:
throw KeywordError("Unknown option " + dumpHexChar(ch) + " in the 'pcre' keyword");
}
}
}
void
PCREKeyword::compilePCRE(const string &expr)
{
uint32_t options = PCRE2_NO_AUTO_CAPTURE;
if (is_case_insensitive) options |= PCRE2_CASELESS;
if (is_multiline) options |= PCRE2_MULTILINE;
if (is_dotall) options |= PCRE2_DOTALL;
if (is_extended) options |= PCRE2_EXTENDED;
if (is_dollar_endonly) options |= PCRE2_DOLLAR_ENDONLY;
if (is_anchor) options |= PCRE2_ANCHORED;
if (is_ungreedy) options |= PCRE2_UNGREEDY;
int error;
PCRE2_SIZE error_offset;
auto pattern = reinterpret_cast<PCRE2_SPTR>(expr.c_str());
pcre_machine.reset(pcre2_compile(pattern, expr.size(), options, &error, &error_offset, nullptr));
if (pcre_machine == nullptr) {
vector<u_char> msg;
msg.reserve(128);
pcre2_get_error_message(error, msg.data(), msg.capacity());
throw KeywordError(
"Failed to compile the 'pcre' at offset "
+ to_string(error_offset)
+ " with error: "
+ reinterpret_cast<char *>(msg.data())
);
}
pcre_result.reset(pcre2_match_data_create_from_pattern(pcre_machine.get(), nullptr));
if (pcre_result == nullptr) {
throw KeywordError("Failed to allocate PCRE results container");
}
pcre_expr = expr;
}
static uint
addOffset(uint offset, int add)
{
if (add<0 && offset<static_cast<uint>(-add)) return 0;
return offset + add;
}
pair<uint, uint>
PCREKeyword::getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const
{
uint keyword_offset = is_relative?prev->getOffset(ctx):0;
uint start_offset = addOffset(keyword_offset, offset.evalAttr(prev));
if (start_offset>=buf_size) return make_pair(0, 0);
uint length = buf_size-start_offset;
if (depth.isSet()) {
length = min(length, static_cast<uint>(depth.evalAttr(prev)));
}
return make_pair(start_offset, length);
}
MatchStatus
PCREKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
dbgAssert(pcre_machine!=nullptr) << "Trying to run on an uninitialized keyword 'pcre'";
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
if (!part.ok()) {
if (is_negative) {
return runNext(prev);
}
return MatchStatus::NoMatchFinal;
}
uint offset, length;
tie(offset, length) = getStartOffsetAndLength((*part).size(), prev);
auto buf = (*part).getPtr(offset, length);
if (!buf.ok()) {
dbgTrace(D_KEYWORD) << "Could not get the buffer for the 'pcre' keyword";
return MatchStatus::NoMatchFinal;
}
const unsigned char *ptr = *buf;
bool match_found = false;
uint buf_offset_found;
for (uint buf_pos = 0; buf_pos<length; buf_pos = buf_offset_found) {
dbgDebug(D_KEYWORD) << "Looking for expression: " << pcre_expr;
dbgTrace(D_KEYWORD) << "Running pcre_exec for expression: " << ptr;
int result = pcre2_match(
pcre_machine.get(),
ptr,
length,
buf_pos,
0,
pcre_result.get(),
nullptr
);
if (result<0) {
// No match (possiblely due to an error)
dbgDebug(D_KEYWORD) << "No match, possiblely due to an error in 'pcre_exec'";
break;
} else {
dbgDebug(D_KEYWORD) << "Match found";
}
if (is_negative) {
return isConstant()?MatchStatus::NoMatchFinal:MatchStatus::NoMatch;
}
match_found = true;
buf_offset_found = pcre2_get_ovector_pointer(pcre_result.get())[0];
OffsetRuntimeState new_offset(prev, ctx, offset+buf_offset_found);
auto next_keyword_result = runNext(&new_offset);
if (next_keyword_result!=MatchStatus::NoMatch) return next_keyword_result;
if (buf_offset_found<=buf_pos) buf_offset_found = buf_pos+1; // Deal with empty matches
}
// No matchs is a success for negative keywords
if (is_negative && !match_found) {
return runNext(prev);
}
// If there were no matchs and the keyword is an effected by other keywords, then we know that the rule won't match
if (isConstant() && !match_found) {
return MatchStatus::NoMatchFinal;
}
return MatchStatus::NoMatch;
}
unique_ptr<SingleKeyword>
genPCREKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<PCREKeyword>(attr, known_vars);
}

View File

@@ -0,0 +1,13 @@
#ifndef ___SENTINEL_RUNTIME_STATE_H__
#define ___SENTINEL_RUNTIME_STATE_H__
#include "single_keyword.h"
class SentinelRuntimeState : public I_KeywordRuntimeState
{
public:
uint getOffset(const std::string &) const override;
uint getVariable(uint) const override;
};
#endif // ___SENTINEL_RUNTIME_STATE_H__

View File

@@ -0,0 +1,276 @@
#include "single_keyword.h"
#include <algorithm>
using namespace std;
void
SingleKeyword::appendKeyword(unique_ptr<SingleKeyword> &&_next)
{
if (next==nullptr) {
next = move(_next);
} else {
next->appendKeyword(move(_next));
}
}
MatchStatus
SingleKeyword::runNext(const I_KeywordRuntimeState *curr) const
{
if (next==nullptr) {
return MatchStatus::Match;
}
return next->isMatch(curr);
}
OffsetRuntimeState::OffsetRuntimeState(
const I_KeywordRuntimeState *_p,
const string &_ctx,
uint _offset)
:
prev(_p),
ctx(_ctx),
offset(_offset)
{
}
uint
OffsetRuntimeState::getOffset(const string &requested_ctx) const
{
if (ctx==requested_ctx) return offset;
return prev->getOffset(requested_ctx);
}
uint
OffsetRuntimeState::getVariable(uint requested_var_id) const
{
return prev->getVariable(requested_var_id);
}
VariableRuntimeState::VariableRuntimeState(
const I_KeywordRuntimeState *_p,
uint _var_id,
uint _val)
:
prev(_p),
var_id(_var_id),
value(_val)
{
}
uint
VariableRuntimeState::getOffset(const string &requested_ctx) const
{
return prev->getOffset(requested_ctx);
}
uint
VariableRuntimeState::getVariable(uint requested_var_id) const
{
if (var_id==requested_var_id) return value;
return prev->getVariable(requested_var_id);
}
uint
VariablesMapping::addNewVariable(const string &param)
{
auto iter = mapping.find(param);
if (iter==mapping.end()) {
mapping[param] = mapping.size();
}
return mapping[param];
}
Maybe<uint>
VariablesMapping::getVariableId(const string &param) const
{
auto iter = mapping.find(param);
if (iter==mapping.end()) {
return genError(string("Unknown parameter ")+param);
}
return iter->second;
}
void
NumericAttr::setAttr(
const KeywordAttr &attr,
const VariablesMapping &known_vars,
const string &keyword_name,
const uint base,
bool is_unsigned_val)
{
auto &vec = attr.getParams();
if (vec.size()!= 2) {
throw KeywordError("Malformed " + attr.getAttrName() + "' in the '" + keyword_name + "' keyword");
}
setAttr(attr.getAttrName(), vec[1], known_vars, keyword_name, base, is_unsigned_val);
}
void
NumericAttr::setAttr(
const string &attr_name,
const string &param,
const VariablesMapping &known_vars,
const string &keyword_name,
const uint base,
bool is_unsigned_val)
{
if (isSet()) {
throw KeywordError("Double definition of the '" + attr_name + "' in the '" + keyword_name + "' keyword");
}
if (is_unsigned_val && param[0]=='-') {
throw KeywordError(
"Negative constant '" +
param +
"' in the '" +
attr_name +
"' in the '" +
keyword_name +
"' keyword"
);
}
if (isdigit(param[0]) || param[0] == '-') {
status = Status::Const;
try {
size_t idx;
val = stol(param, &idx, base);
if (idx != param.length()) throw invalid_argument("");
}
catch (...) {
throw KeywordError(
"Malformed constant '" +
param +
"' in the '" +
attr_name +
"' in the '" +
keyword_name +
"' keyword"
);
}
} else {
status = Status::Var;
val = known_vars.getVariableId(param).unpack<KeywordError>(
"In " + keyword_name +
" in " + attr_name + ": "
);
}
}
int
NumericAttr::evalAttr(const I_KeywordRuntimeState *prev) const
{
if (status==Status::Var) {
return prev->getVariable(val);
}
return val;
}
void
BoolAttr::setAttr(const KeywordAttr &attr, const string &keyword_name)
{
if (attr.getParams().size()!=1) {
throw KeywordError("Malformed " + attr.getAttrName() + "' in the '" + keyword_name + "' keyword");
}
val = true;
}
void
BoolAttr::setAttr(const string &keyword_name, const string &attr_name)
{
if (val) throw KeywordError("Double definition of the '" + attr_name + "' in the '" + keyword_name + "' keyword");
val = true;
}
void
CtxAttr::setAttr(const KeywordAttr &attr, const string &keyword_name)
{
if (is_set) throw KeywordError("Double definition of the 'part' in the '" + keyword_name + "' keyword");
is_set = true;
auto vec = attr.getParams();
if (vec.size()!=2) throw KeywordError("Malformed 'part' in the '" + keyword_name + "' keyword");
ctx = vec[1];
}
const map<string, ComparisonAttr::CompId> ComparisonAttr::name_to_operator {
{ "=", CompId::EQUAL },
{ "!=", CompId::NOT_EQUAL },
{ "<", CompId::LESS_THAN },
{ ">", CompId::GREATER_THAN },
{ "<=", CompId::LESS_THAN_OR_EQUAL },
{ ">=", CompId::GREATER_THAN_OR_EQUAL }
};
Maybe<ComparisonAttr::CompId>
ComparisonAttr::getComparisonByName(const string &name)
{
auto iter = name_to_operator.find(name);
if (iter == name_to_operator.end()) {
return genError("Could not find the operator: " + name);
}
return iter->second;
}
void
ComparisonAttr::setAttr(const string &param, const string &keyword_name)
{
if (isSet()) {
throw KeywordError("Double definition of the comparison opearator in the '" + keyword_name + "' keyword");
}
is_set = true;
comp_val = getComparisonByName(param).unpack<KeywordError>(
"Unknown comparison operator in the '" + keyword_name + "' keyword: "
);
}
bool
ComparisonAttr::operator()(int first_val, int second_val) const
{
switch (comp_val) {
case ComparisonAttr::CompId::EQUAL: {
return first_val == second_val;
}
case ComparisonAttr::CompId::NOT_EQUAL: {
return first_val != second_val;
}
case ComparisonAttr::CompId::LESS_THAN: {
return first_val < second_val;
}
case ComparisonAttr::CompId::GREATER_THAN: {
return first_val > second_val;
}
case ComparisonAttr::CompId::LESS_THAN_OR_EQUAL: {
return first_val <= second_val;
}
case ComparisonAttr::CompId::GREATER_THAN_OR_EQUAL: {
return first_val >= second_val;
}
}
dbgAssert(false) << "ComparisonAttr::operator found an invalid comparison operator";
return false;
}
using InitFunc = unique_ptr<SingleKeyword>(*)(const vector<KeywordAttr> &, VariablesMapping &);
const map<string, InitFunc> initializers = {
{"data", genDataKeyword },
{"pcre", genPCREKeyword },
{"length", genLengthKeyword },
{"byte_extract", genByteExtractKeyword },
{"compare", genCompareKeyword },
{"stateop", genStateopKeyword },
{"no_match", genNoMatchKeyword },
{"jump", genJumpKeyword }
};
unique_ptr<SingleKeyword>
getKeywordByName(const KeywordParsed &keyword, VariablesMapping &known_vars)
{
auto iter = initializers.find(keyword.getName());
if (iter==initializers.end()) throw KeywordError(keyword.getName() + " - unknown keyword type");
return iter->second(keyword.getAttr(), known_vars);
}

View File

@@ -0,0 +1,300 @@
#ifndef ___SINGLE_KEYWORD_H__
#define ___SINGLE_KEYWORD_H__
#include <map>
#include <string>
#include <vector>
#include <memory>
#include "buffer.h"
#include "keyword_comp.h"
#include "debug.h"
USE_DEBUG_FLAG(D_KEYWORD);
enum class MatchStatus { Match, NoMatch, NoMatchFinal };
class KeywordError
{
public:
KeywordError(const std::string &str) : err(str)
{
}
const std::string &
getErr() const
{
return err;
}
private:
std::string err;
};
class KeywordAttr
{
public:
KeywordAttr(const std::string &str);
const std::string&
getAttrName() const
{
return params[0];
}
const std::vector<std::string> &
getParams() const
{
return params;
}
private:
std::vector<std::string> params;
};
class KeywordParsed
{
public:
KeywordParsed(const std::string &keyword);
const std::string &
getName() const
{
return name;
}
const std::vector<KeywordAttr> &
getAttr() const
{
return attr;
}
private:
std::string name;
std::vector<KeywordAttr> attr;
};
class I_KeywordRuntimeState
{
public:
virtual uint getOffset(const std::string &ctx) const = 0;
virtual uint getVariable(uint requested_var_id) const = 0;
protected:
virtual ~I_KeywordRuntimeState()
{
}
};
class OffsetRuntimeState : public I_KeywordRuntimeState
{
public:
OffsetRuntimeState(const I_KeywordRuntimeState *prev, const std::string &ctx, uint offset);
virtual ~OffsetRuntimeState()
{
}
uint getOffset(const std::string &requested_ctx_id) const override;
uint getVariable(uint requested_var_id) const override;
private:
const I_KeywordRuntimeState *prev;
std::string ctx;
uint offset;
};
class VariableRuntimeState : public I_KeywordRuntimeState
{
public:
VariableRuntimeState(const I_KeywordRuntimeState *prev, uint var_id, uint val);
virtual ~VariableRuntimeState()
{
}
uint getOffset(const std::string &requested_ctx_id) const override;
uint getVariable(uint requested_var_id) const override;
private:
const I_KeywordRuntimeState *prev;
uint var_id;
uint value;
};
class VariablesMapping
{
public:
uint addNewVariable(const std::string &name);
Maybe<uint> getVariableId(const std::string &name) const;
private:
std::map<std::string, uint> mapping;
};
class NumericAttr
{
enum class Status { Unset, Const, Var };
public:
void setAttr(
const KeywordAttr &attr,
const VariablesMapping &known_vars,
const std::string &keyword_name,
const uint base = 10,
bool is_unsigned_val = false);
void setAttr(
const std::string &attr_name,
const std::string &param,
const VariablesMapping &known_vars,
const std::string &keyword_name,
const uint base = 10,
bool is_unsigned_val = false);
int evalAttr(const I_KeywordRuntimeState *prev) const;
bool
isConstant() const
{
return status!=Status::Var;
}
bool
isSet() const
{
return status!=Status::Unset;
}
private:
Status status = Status::Unset;
int val = 0;
};
class BoolAttr
{
public:
void setAttr(const KeywordAttr &attr, const std::string &keyword_name);
void setAttr(const std::string &keyword_name, const std::string &attr_name);
operator bool() const
{
return val;
}
private:
bool val = false;
};
class CtxAttr
{
public:
void setAttr(const KeywordAttr &attr, const std::string &keyword_name);
operator std::string() const
{
if (!is_set) {
auto env = Singleton::Consume<I_Environment>::by<KeywordComp>();
auto default_ctx = env->get<std::string>(I_KeywordsRule::getKeywordsRuleTag());
if (default_ctx.ok()) return *default_ctx;
dbgError(D_KEYWORD) << "Running keyword rule without specific context and without default";
return "Missing Default Context";
}
return ctx;
}
private:
std::string ctx;
bool is_set = false;
};
class ComparisonAttr
{
public:
enum class CompId
{
EQUAL,
NOT_EQUAL,
LESS_THAN,
GREATER_THAN,
LESS_THAN_OR_EQUAL,
GREATER_THAN_OR_EQUAL
};
void setAttr(const std::string &param, const std::string &keyword_name);
bool operator()(int first_val, int second_val) const;
bool
isSet() const
{
return is_set;
}
private:
Maybe<CompId> getComparisonByName(const std::string &name);
const static std::map<std::string, CompId> name_to_operator;
bool is_set = false;
CompId comp_val;
};
class SingleKeyword
{
public:
SingleKeyword()
{
}
virtual ~SingleKeyword()
{
}
MatchStatus runNext(const I_KeywordRuntimeState *curr) const;
void appendKeyword(std::unique_ptr<SingleKeyword> &&_next);
virtual MatchStatus isMatch(const I_KeywordRuntimeState *prev) const = 0;
private:
std::unique_ptr<SingleKeyword> next;
};
std::unique_ptr<SingleKeyword> genDataKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genPCREKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genLengthKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genByteExtractKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genCompareKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genStateopKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genNoMatchKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> genJumpKeyword(
const std::vector<KeywordAttr> &attr,
VariablesMapping &vars
);
std::unique_ptr<SingleKeyword> getKeywordByName(
const KeywordParsed &parsed_data,
VariablesMapping &vars
);
#endif // ___SINGLE_KEYWORD_H__

View File

@@ -0,0 +1,145 @@
#include "single_keyword.h"
#include "table_opaque.h"
#include "debug.h"
#include "flags.h"
#include <map>
#include <strings.h>
#include "cereal/types/set.hpp"
using namespace std;
USE_DEBUG_FLAG(D_KEYWORD);
class StateopKeyword : public SingleKeyword
{
public:
explicit StateopKeyword(const vector<KeywordAttr> &attr, VariablesMapping &vars);
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
private:
enum class Operation { ISSET, SET, UNSET, COUNT };
using OpFlags = Flags<Operation>;
void
setState(const KeywordAttr &attr, const VariablesMapping &)
{
auto &var_name_param = attr.getParams();
if (var_name_param.size() != 2) {
throw KeywordError("More than one element in the state name in the 'stateop' keyword");
}
var_name = var_name_param[1];
}
void
setTesting(const KeywordAttr &, const VariablesMapping &)
{
if (!mode.empty()) throw KeywordError("Redefining 'stateop' keyword operation");
mode.setFlag(Operation::ISSET);
}
void
setSetting(const KeywordAttr &, const VariablesMapping &)
{
if (!mode.empty()) throw KeywordError("Redefining 'stateop' keyword operation");
mode.setFlag(Operation::SET);
}
void
setUnsetting(const KeywordAttr &, const VariablesMapping &)
{
if (!mode.empty()) throw KeywordError("Redefining 'stateop' keyword operation");
mode.setFlag(Operation::UNSET);
}
string var_name;
OpFlags mode;
static const map<string, void(StateopKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
};
const map<string, void(StateopKeyword::*)(const KeywordAttr &, const VariablesMapping &)> StateopKeyword::setops = {
{ "isset", &StateopKeyword::setTesting },
{ "set", &StateopKeyword::setSetting },
{ "unset", &StateopKeyword::setUnsetting },
{ "state", &StateopKeyword::setState }
};
StateopKeyword::StateopKeyword(const vector<KeywordAttr> &attrs, VariablesMapping &vars)
{
if (attrs.size() != 2) throw KeywordError("Invalid number of attributes in the 'stateop' keyword");
for (uint i = 0; i < attrs.size(); i++) {
auto curr = setops.find(attrs[i].getAttrName());
if (curr == setops.end()) {
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'stateop' keyword");
}
auto set_func = curr->second;
(this->*set_func)(attrs[i], vars);
}
if (var_name == "" || mode.empty()) {
throw KeywordError("Bad 'stateop' attribute configuration");
}
}
class KeywordStateop : public TableOpaqueSerialize<KeywordStateop>
{
public:
KeywordStateop() : TableOpaqueSerialize<KeywordStateop>(this) {}
bool hasVariable(const string &state) { return states.find(state) != states.end(); }
void addVariable(const string &state) { states.insert(state); }
void removeVariable(const string &state) { states.erase(state); }
// LCOV_EXCL_START - sync functions, can only be tested once the sync module exists
template <typename T>
void
serialize(T &ar, uint32_t)
{
ar(states);
}
static std::string name() { return "KeywordStateop"; }
static std::unique_ptr<TableOpaqueBase> prototype() { return std::make_unique<KeywordStateop>(); }
static uint currVer() { return 0; }
static uint minVer() { return 0; }
// LCOV_EXCL_STOP
private:
set<string> states;
};
MatchStatus
StateopKeyword::isMatch(const I_KeywordRuntimeState *prev) const
{
auto table = Singleton::Consume<I_Table>::by<KeywordComp>();
if (mode.isSet(Operation::ISSET)) {
if (!table->hasState<KeywordStateop>()) return MatchStatus::NoMatchFinal;
auto &state = table->getState<KeywordStateop>();
if (state.hasVariable(var_name)) return runNext(prev);
else return MatchStatus::NoMatchFinal;
} else if (mode.isSet(Operation::SET)) {
if (!table->hasState<KeywordStateop>()) table->createState<KeywordStateop>();
table->getState<KeywordStateop>().addVariable(var_name);
return runNext(prev);
} else if (mode.isSet(Operation::UNSET)) {
if (table->hasState<KeywordStateop>()) table->getState<KeywordStateop>().removeVariable(var_name);
return runNext(prev);
} else {
dbgAssert(false) << "Impossible 'stateop' keyword without operation";
}
// If there was no matches and the keyword is effected by other keywords, then we know that the rule won't match
return MatchStatus::NoMatchFinal;
}
unique_ptr<SingleKeyword>
genStateopKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
{
return make_unique<StateopKeyword>(attr, known_vars);
}