From cd4820a357821af3144f64014d1e4198b96c6fc5 Mon Sep 17 00:00:00 2001 From: weetmuts Date: Mon, 16 Sep 2019 16:32:24 +0200 Subject: [PATCH] Added support for negative id match rules. --- CHANGES | 11 +++ README.md | 4 +- src/cmdline.cc | 2 +- src/config.cc | 4 +- src/meters.cc | 23 +----- src/testinternals.cc | 67 +++++++++++++++ src/util.cc | 163 ++++++++++++++++++++++++++++++++++--- src/util.h | 8 +- tests/test_multiple_ids.sh | 2 + 9 files changed, 247 insertions(+), 37 deletions(-) diff --git a/CHANGES b/CHANGES index 85263c5..e909a38 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,14 @@ + +Version 0.9.14: 2019-09-16 + +Added negative match rule for ids. You can now write: +id=78*,!7812345*,!78222222 +which will match any meter whose id begins with 78 +but not match any meter whose id begins with 7812345, +nor the meter with the exact id 78222222. + +The order of the match rules does not matter. + Version 0.9.13: 2019-08-14 Fix bug that prevented rtl_wmbus to run inside daemon. diff --git a/README.md b/README.md index 5bebc87..e85e4b5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,9 @@ The latest reading of the meter can also be found here: /var/log/wmbusmeters/met You can use several ids using `id=1111111,2222222,3333333` or you can listen to all meters of a certain type `id=*` or you can suffix with star `id=8765*` to match -all meters with a given prefix. +all meters with a given prefix. If you supply at least one positive match rule, then you +can add negative match rules as well. For example `id=*,!2222*` +which will match all meter ids, except those that begin with 2222. If you are running on a Raspberry PI with flash storage and you relay the data to another computer using a shell command (mosquitto_pub or curl or similar) then you might want to remove diff --git a/src/cmdline.cc b/src/cmdline.cc index dbb49a1..b473cc6 100644 --- a/src/cmdline.cc +++ b/src/cmdline.cc @@ -338,7 +338,7 @@ unique_ptr parseCommandLine(int argc, char **argv) { mt = toMeterType(type); if (mt == MeterType::UNKNOWN) error("Not a valid meter type \"%s\"\n", type.c_str()); - if (!isValidId(id)) error("Not a valid meter id \"%s\"\n", id.c_str()); + if (!isValidMatchExpressions(id, false)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str()); if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key.c_str()); vector no_meter_shells; c->meters.push_back(MeterInfo(name, type, id, key, modes, no_meter_shells)); diff --git a/src/config.cc b/src/config.cc index b559786..a088d46 100644 --- a/src/config.cc +++ b/src/config.cc @@ -116,8 +116,8 @@ void parseMeterConfig(Configuration *c, vector &buf, string file) warning("Not a valid meter type \"%s\"\n", type.c_str()); use = false; } - if (!isValidId(id)) { - warning("Not a valid meter id \"%s\"\n", id.c_str()); + if (!isValidMatchExpression(id, false)) { + warning("Not a valid meter id nor a valid meter match expression \"%s\"\n", id.c_str()); use = false; } if (!isValidKey(key)) { diff --git a/src/meters.cc b/src/meters.cc index d4382f3..7e4925e 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -29,7 +29,7 @@ MeterCommonImplementation::MeterCommonImplementation(WMBus *bus, MeterInfo &mi, type_(type), name_(mi.name), bus_(bus) { use_aes_ = true; - ids_ = splitIds(mi.id); + ids_ = splitMatchExpressions(mi.id); if (mi.key.length() == 0) { use_aes_ = false; } else { @@ -169,26 +169,7 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t) { debug("(meter) %s: for me? %s\n", name_.c_str(), t->id.c_str()); - bool id_match = false; - for (auto id : ids_) - { - if (id == t->id) { - id_match = true; - break; - } - if (id == "*") { - id_match = true; - break; - } - if (id.length() > 1 && id.back() == '*') - { - if (t->id.length() >= id.length()) - { - string prefix = t->id.substr(0, id.length()-1); - id_match = !strncmp(prefix.c_str(), id.c_str(), id.length()-1); - } - } - } + bool id_match = doesIdMatchExpressions(t->id, ids_); if (!id_match) { // The id must match. diff --git a/src/testinternals.cc b/src/testinternals.cc index d85ef9e..fac3705 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -31,6 +31,7 @@ using namespace std; int test_crc(); int test_dvparser(); int test_linkmodes(); +void test_ids(); int main(int argc, char **argv) { @@ -41,6 +42,7 @@ int main(int argc, char **argv) test_crc(); test_dvparser(); test_linkmodes(); + test_ids(); return 0; } @@ -317,3 +319,68 @@ int test_linkmodes() return 0; } + +void test_valid_match_expression(string s, bool expected) +{ + bool b = isValidMatchExpressions(s, false); + if (b == expected) return; + if (expected == true) + { + printf("ERROR! Expected \"%s\" to be valid! But it was not!\n", s.c_str()); + } + else + { + printf("ERROR! Expected \"%s\" to be invalid! But it was reported as valid!\n", s.c_str()); + } +} + +void test_does_id_match_expression(string id, string mes, bool expected) +{ + vector expressions = splitMatchExpressions(mes); + bool b = doesIdMatchExpressions(id, expressions); + if (b == expected) return; + if (expected == true) + { + printf("ERROR! Expected \"%s\" to match \"%s\" ! But it did not!\n", id.c_str(), mes.c_str()); + } + else + { + printf("ERROR! Expected \"%s\" to NOT match \"%s\" ! But it did!\n", id.c_str(), mes.c_str()); + } +} + +void test_ids() +{ + test_valid_match_expression("12345678", true); + test_valid_match_expression("*", true); + test_valid_match_expression("!12345678", true); + test_valid_match_expression("12345*", true); + test_valid_match_expression("!123456*", true); + + test_valid_match_expression("1234567", false); + test_valid_match_expression("", false); + test_valid_match_expression("z1234567", false); + test_valid_match_expression("123456789", false); + test_valid_match_expression("!!12345678", false); + test_valid_match_expression("12345678*", false); + test_valid_match_expression("**", false); + test_valid_match_expression("123**", false); + + test_valid_match_expression("2222*,!22224444", true); + + test_does_id_match_expression("12345678", "12345678", true); + test_does_id_match_expression("12345678", "*", true); + test_does_id_match_expression("12345678", "2*", false); + + test_does_id_match_expression("12345678", "*,!2*", true); + + test_does_id_match_expression("22222222", "22*,!22222222", false); + test_does_id_match_expression("22222223", "22*,!22222222", true); + test_does_id_match_expression("22222223", "*,!22*", false); + test_does_id_match_expression("12333333", "123*,!1234*,!1235*,!1236*", true); + test_does_id_match_expression("12366666", "123*,!1234*,!1235*,!1236*", false); + test_does_id_match_expression("11223344", "22*,33*,44*,55*", false); + test_does_id_match_expression("55223344", "22*,33*,44*,55*", true); + + test_does_id_match_expression("78563413", "78563412,78563413", true); +} diff --git a/src/util.cc b/src/util.cc index 224931e..04accf2 100644 --- a/src/util.cc +++ b/src/util.cc @@ -371,22 +371,165 @@ void error(const char* fmt, ...) exit(1); } -bool isValidId(string& ids) +bool isValidMatchExpression(string me, bool non_compliant) { - vector v = splitIds(ids); + // Examples of valid match expressions: + // 12345678 + // * + // 123* + // !12345677 + // 2222222* + // !22222222 - for (auto id : v) + // A match expression cannot be empty. + if (me.length() == 0) return false; + + // An me can be negated with an exclamation mark first. + if (me.front() == '!') me.erase(0, 1); + + // A match expression cannot be only a negation mark. + if (me.length() == 0) return false; + + int count = 0; + if (non_compliant) { - if (id == "*") return true; - if (id.back() == '*') return true; - if (id.length() != 8) return false; - for (int i=0; i<8; ++i) { - if (id[i]<'0' || id[i]>'9') return false; + // Some non-compliant meters have full hex in the me.... + while (me.length() > 0 && + ((me.front() >= '0' && me.front() <= '9') || + (me.front() >= 'a' && me.front() <= 'f'))) + { + me.erase(0,1); + count++; } } + else + { + // But compliant meters use only a bcd subset. + while (me.length() > 0 && + (me.front() >= '0' && me.front() <= '9')) + { + me.erase(0,1); + count++; + } + } + + bool wildcard_used = false; + // An expression can end with a * + if (me.length() > 0 && me.front() == '*') + { + me.erase(0,1); + wildcard_used = true; + } + + // Now we should have eaten the whole expression. + if (me.length() > 0) return false; + + // Check the length of the matching bcd/hex + // If no wildcard is used, then the match expression must be exactly 8 digits. + if (!wildcard_used) return count == 8; + + // If wildcard is used, then the match expressions must be 7 or less digits, + // even zero is allowed which means a single *, which matches any bcd/hex id. + return count <= 7; +} + +bool isValidMatchExpressions(string mes, bool non_compliant) +{ + vector v = splitMatchExpressions(mes); + + for (string me : v) + { + if (!isValidMatchExpression(me, non_compliant)) return false; + } return true; } +bool doesIdMatchExpression(string id, string match) +{ + if (id.length() == 0) return false; + + // Here we assume that the match expression has been + // verified to be valid. + bool can_match = true; + + // Now match bcd/hex until end of id, or '*' in match. + while (id.length() > 0 && match.length() > 0 && match.front() != '*') + { + if (id.front() != match.front()) + { + // We hit a difference, it cannot match. + can_match = false; + break; + } + id.erase(0,1); + match.erase(0,1); + } + + bool wildcard_used = false; + if (match.front() == '*') + { + wildcard_used = true; + match.erase(0,1); + } + + if (can_match) + { + // Ok, now the match expression should be empty. + // If wildcard is true, then the id can still have digits, + // otherwise it must also be empty. + if (wildcard_used) + { + can_match = match.length() == 0; + } + else + { + can_match = match.length() == 0 && id.length() == 0; + } + } + + return can_match; +} + +bool doesIdMatchExpressions(string& id, vector& mes) +{ + bool found_match = false; + bool found_negative_match = false; + + // Goes through all possible match expressions. + // If no expression matches, neither positive nor negative, + // then the result is false. (ie no match) + + // If more than one positive match is found, and no negative, + // then the result is true. + + // If more than one negative match is found, irrespective + // if there is any positive matches or not, then the result is false. + + for (string me : mes) + { + bool is_negative_rule = (me.length() > 0 && me.front() == '!'); + if (is_negative_rule) + { + me.erase(0, 1); + } + + bool m = doesIdMatchExpression(id, me); + + if (is_negative_rule) + { + if (m) found_negative_match = true; + } + else + { + if (m) found_match = true; + } + } + + if (found_negative_match) return false; + if (found_match) return true; + return false; +} + bool isValidKey(string& key) { if (key.length() == 0) return true; @@ -406,11 +549,11 @@ bool isFrequency(std::string& fq) return true; } -vector splitIds(string& ids) +vector splitMatchExpressions(string& mes) { vector r; bool eof, err; - vector v (ids.begin(), ids.end()); + vector v (mes.begin(), mes.end()); auto i = v.begin(); for (;;) { diff --git a/src/util.h b/src/util.h index bb1b4fd..3886aec 100644 --- a/src/util.h +++ b/src/util.h @@ -70,11 +70,15 @@ bool isLogTelegramsEnabled(); void debugPayload(std::string intro, std::vector &payload); void logTelegram(std::string intro, std::vector &header, std::vector &content); -bool isValidId(std::string& id); +bool isValidMatchExpression(std::string id, bool non_compliant); +bool isValidMatchExpressions(std::string ids, bool non_compliant); +bool doesIdMatchExpression(std::string id, std::string match); +bool doesIdMatchExpressions(std::string& id, std::vector& ids); + bool isValidKey(std::string& key); bool isFrequency(std::string& fq); -std::vector splitIds(std::string& id); +std::vector splitMatchExpressions(std::string& mes); void incrementIV(uchar *iv, size_t len); diff --git a/tests/test_multiple_ids.sh b/tests/test_multiple_ids.sh index 507e4c8..5f7947e 100755 --- a/tests/test_multiple_ids.sh +++ b/tests/test_multiple_ids.sh @@ -26,10 +26,12 @@ else fi cat $SIM | grep '^{' | grep 8856 > $TEST/test_expected.txt + $PROG --format=json $SIM \ Element qcaloric '8856*' '' \ > $TEST/test_output.txt + if [ "$?" == "0" ] then cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt