From 9d27ab3fb395a980db8a8ad82e2e0e45a96f9151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Mon, 26 Feb 2024 11:44:47 +0100 Subject: [PATCH] New source file address.cc for mbus addressing. --- Makefile | 1 + src/address.cc | 258 +++++++++++++++++++++++++++++++++++++++++++ src/address.h | 52 +++++++++ src/meters.cc | 18 +-- src/meters.h | 17 +-- src/testinternals.cc | 56 ++++++---- src/util.cc | 238 --------------------------------------- src/util.h | 9 -- src/wmbus.cc | 1 + 9 files changed, 357 insertions(+), 293 deletions(-) create mode 100644 src/address.cc create mode 100644 src/address.h diff --git a/Makefile b/Makefile index 115fb84..c109a31 100644 --- a/Makefile +++ b/Makefile @@ -153,6 +153,7 @@ $(BUILD)/%.o: src/%.c $(wildcard src/%.h) $(CXX) -I/usr/include/libxml2 -fpermissive $(CXXFLAGS) $< -MMD -c -o $@ PROG_OBJS:=\ + $(BUILD)/address.o \ $(BUILD)/aes.o \ $(BUILD)/aescmac.o \ $(BUILD)/bus.o \ diff --git a/src/address.cc b/src/address.cc new file mode 100644 index 0000000..c4bd6fb --- /dev/null +++ b/src/address.cc @@ -0,0 +1,258 @@ +/* + Copyright (C) 2017-2024 Fredrik Öhrström (gpl-3.0-or-later) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include"address.h" + +using namespace std; + +bool isValidMatchExpression(const string& s, bool non_compliant) +{ + string me = s; + + // Examples of valid match expressions: + // 12345678 + // * + // 123* + // !12345677 + // 2222222* + // !22222222 + + // 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) + { + // Some non-compliant meters have full hex in the id, + // but according to the standard there should only be bcd here... + 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(const string& mes, bool non_compliant) +{ + vector v = splitMatchExpressions(mes); + + for (string me : v) + { + if (!isValidMatchExpression(me, non_compliant)) return false; + } + return true; +} + +bool isValidId(const string& id, bool accept_non_compliant) +{ + + for (size_t i=0; i= '0' && id[i] <= '9') continue; + if (accept_non_compliant) + { + if (id[i] >= 'a' && id[i] <= 'f') continue; + if (id[i] >= 'A' && id[i] <= 'F') continue; + } + return false; + } + return true; +} + +bool doesIdMatchExpression(const string& s, string match) +{ + string id = s; + 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.length() && 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 hasWildCard(const string& mes) +{ + return mes.find('*') != string::npos; +} + +bool doesIdsMatchExpressions(vector &ids, vector& mes, bool *used_wildcard) +{ + bool match = false; + for (string &id : ids) + { + if (doesIdMatchExpressions(id, mes, used_wildcard)) + { + match = true; + } + // Go through all ids even though there is an early match. + // This way we can see if theres an exact match later. + } + return match; +} + +bool doesIdMatchExpressions(const string& id, vector& mes, bool *used_wildcard) +{ + bool found_match = false; + bool found_negative_match = false; + bool exact_match = false; + *used_wildcard = 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. + + // If a positive match is found, using a wildcard not any exact match, + // then *used_wildcard is set to true. + + for (string me : mes) + { + bool has_wildcard = hasWildCard(me); + 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 (!has_wildcard) + { + exact_match = true; + } + } + } + } + + if (found_negative_match) + { + return false; + } + if (found_match) + { + if (exact_match) + { + *used_wildcard = false; + } + else + { + *used_wildcard = true; + } + return true; + } + return false; +} + +string toIdsCommaSeparated(vector &ids) +{ + string cs; + for (string& s: ids) + { + cs += s; + cs += ","; + } + if (cs.length() > 0) cs.pop_back(); + return cs; +} diff --git a/src/address.h b/src/address.h new file mode 100644 index 0000000..3f13306 --- /dev/null +++ b/src/address.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ADDRESS_H_ +#define ADDRESS_H_ + +#include "util.h" +#include + +struct Address +{ + // Example address: 12345678 + // Or fully qualified: 12345678.M=PII.T=1b.V=01 + // which means manufacturer triplet PII, type/media=0x1b, version=0x01 + std::string id; + bool wildcard_used {}; // The id contains a * + bool mbus_primary {}; // Signals that the id is 0-250 + uint16_t mfct {}; // If 0xffff then any mfct matches this address. + uchar type {}; // If 0xff then any type matches this address. + uchar version {}; // If 0xff then any version matches this address. + bool negate {}; // When used for testing this address was negated. !12345678 + + bool parse(std::string &s); + bool match(Address *a); +}; + +bool isValidMatchExpression(const std::string& s, bool non_compliant); +bool isValidMatchExpressions(const std::string& s, bool non_compliant); +bool doesIdMatchExpression(const std::string& id, std::string match_rule); +bool doesIdMatchExpressions(const std::string& id, std::vector& match_rules, bool *used_wildcard); +bool doesIdsMatchExpressions(std::vector &ids, std::vector& match_rules, bool *used_wildcard); +std::string toIdsCommaSeparated(std::vector &ids); + +bool isValidId(const std::string& id, bool accept_non_compliant); + +std::vector splitMatchExpressions(const std::string& mes); + +#endif diff --git a/src/meters.cc b/src/meters.cc index 0e26872..385d40c 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -2593,17 +2593,19 @@ bool Address::parse(string &s) // Example: 12345678 // or 12345678.M=PII.T=1B.V=01 // or 1234* - // or 1234*.PII - // or 1234*.V01 + // or 1234*.M=PII + // or 1234*.V=01 // or 12 // mbus primary // or 0 // mbus primary - // or 250.MPII.T1B.V01 // mbus primary - + // or 250.MPII.V01.T1B // mbus primary + // or !12345678 + // or !*.M=ABC id = ""; mbus_primary = false; - mfct = 0; - type = 0; - version = 0; + mfct = 0xffff; + type = 0xff; + version = 0xff; + negate = false; if (s.size() == 0) return false; @@ -2626,7 +2628,7 @@ bool Address::parse(string &s) } id = parts[0]; - for (size_t i=1; i 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(const string& mes, bool non_compliant) -{ - vector v = splitMatchExpressions(mes); - - for (string me : v) - { - if (!isValidMatchExpression(me, non_compliant)) return false; - } - return true; -} - -bool isValidId(const string& id, bool accept_non_compliant) -{ - - for (size_t i=0; i= '0' && id[i] <= '9') continue; - if (accept_non_compliant) - { - if (id[i] >= 'a' && id[i] <= 'f') continue; - if (id[i] >= 'A' && id[i] <= 'F') continue; - } - return false; - } - return true; -} - -bool doesIdMatchExpression(const string& s, string match) -{ - string id = s; - 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.length() && 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 hasWildCard(const string& mes) -{ - return mes.find('*') != string::npos; -} - -bool doesIdsMatchExpressions(vector &ids, vector& mes, bool *used_wildcard) -{ - bool match = false; - for (string &id : ids) - { - if (doesIdMatchExpressions(id, mes, used_wildcard)) - { - match = true; - } - // Go through all ids even though there is an early match. - // This way we can see if theres an exact match later. - } - return match; -} - -bool doesIdMatchExpressions(const string& id, vector& mes, bool *used_wildcard) -{ - bool found_match = false; - bool found_negative_match = false; - bool exact_match = false; - *used_wildcard = 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. - - // If a positive match is found, using a wildcard not any exact match, - // then *used_wildcard is set to true. - - for (string me : mes) - { - bool has_wildcard = hasWildCard(me); - 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 (!has_wildcard) - { - exact_match = true; - } - } - } - } - - if (found_negative_match) - { - return false; - } - if (found_match) - { - if (exact_match) - { - *used_wildcard = false; - } - else - { - *used_wildcard = true; - } - return true; - } - return false; -} - -string toIdsCommaSeparated(vector &ids) -{ - string cs; - for (string& s: ids) - { - cs += s; - cs += ","; - } - if (cs.length() > 0) cs.pop_back(); - return cs; -} - bool isFrequency(const string& fq) { int len = fq.length(); diff --git a/src/util.h b/src/util.h index e787617..957fcef 100644 --- a/src/util.h +++ b/src/util.h @@ -155,19 +155,10 @@ void setAlarmShells(std::vector &alarm_shells); bool isValidAlias(const std::string& alias); bool isValidBps(const std::string& b); -bool isValidMatchExpression(const std::string& s, bool non_compliant); -bool isValidMatchExpressions(const std::string& s, bool non_compliant); -bool doesIdMatchExpression(const std::string& id, std::string match_rule); -bool doesIdMatchExpressions(const std::string& id, std::vector& match_rules, bool *used_wildcard); -bool doesIdsMatchExpressions(std::vector &ids, std::vector& match_rules, bool *used_wildcard); -std::string toIdsCommaSeparated(std::vector &ids); - -bool isValidId(const std::string& id, bool accept_non_compliant); bool isFrequency(const std::string& fq); bool isNumber(const std::string& fq); -std::vector splitMatchExpressions(const std::string& mes); // Split s into strings separated by c. std::vector splitString(const std::string &s, char c); // Split s into strings separated by c and store inte set. diff --git a/src/wmbus.cc b/src/wmbus.cc index 4a786e4..b5529a9 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -231,6 +231,7 @@ LIST_OF_MANUFACTURERS void Telegram::addId(const vector::iterator &pos) { +// string id = tostrprintf("%02x%02x%02x%02x_%02x_%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0), *(pos+4), *(pos+5)); string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); ids.push_back(id); if (idsc.empty()) {