New source file address.cc for mbus addressing.

pull/1196/head
Fredrik Öhrström 2024-02-26 11:44:47 +01:00
rodzic c21efd1d69
commit 9d27ab3fb3
9 zmienionych plików z 357 dodań i 293 usunięć

Wyświetl plik

@ -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 \

258
src/address.cc 100644
Wyświetl plik

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<string> 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<id.length(); ++i)
{
if (id[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<string> &ids, vector<string>& 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<string>& 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<string> &ids)
{
string cs;
for (string& s: ids)
{
cs += s;
cs += ",";
}
if (cs.length() > 0) cs.pop_back();
return cs;
}

52
src/address.h 100644
Wyświetl plik

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ADDRESS_H_
#define ADDRESS_H_
#include "util.h"
#include <string>
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<std::string>& match_rules, bool *used_wildcard);
bool doesIdsMatchExpressions(std::vector<std::string> &ids, std::vector<std::string>& match_rules, bool *used_wildcard);
std::string toIdsCommaSeparated(std::vector<std::string> &ids);
bool isValidId(const std::string& id, bool accept_non_compliant);
std::vector<std::string> splitMatchExpressions(const std::string& mes);
#endif

Wyświetl plik

@ -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<parts[i].size(); ++i)
for (size_t i=1; i<parts.size(); ++i)
{
if (parts[i].size() == 4) // V=xy or T=xy
{

Wyświetl plik

@ -18,6 +18,7 @@
#ifndef METER_H_
#define METER_H_
#include"address.h"
#include"dvparser.h"
#include"formula.h"
#include"util.h"
@ -80,22 +81,6 @@ bool isValidKey(const string& key, MeterInfo &mt);
using namespace std;
typedef unsigned char uchar;
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
string id;
bool wildcard_used {}; // The id contains a *
bool mbus_primary {}; // Signals that the id is 0-250
uint16_t mfct {};
uchar type {};
uchar version {};
bool parse(string &s);
};
struct MeterInfo
{

Wyświetl plik

@ -37,7 +37,8 @@ using namespace std;
bool verbose_ = false;
#define LIST_OF_TESTS \
X(dynamic_loading)\
X(addresses) \
X(dynamic_loading) \
X(crc) \
X(dvparser) \
X(devices) \
@ -75,6 +76,7 @@ bool verbose_ = false;
X(formulas_dventries) \
X(formulas_stringinterpolation) \
#define X(t) void test_##t();
LIST_OF_TESTS
#undef X
@ -495,7 +497,9 @@ void test_ids()
test_does_id_match_expression("78563413", "*,!00156327,!00048713", true, true);
}
void tst_address(string s, bool valid, string id, string mfct, uchar type, uchar version)
void tst_address(string s, bool valid, string id,
string mfct, uchar version, uchar type,
bool mbus_primary, bool wildcard_used)
{
Address a;
bool ok = a.parse(s);
@ -511,38 +515,46 @@ void tst_address(string s, bool valid, string id, string mfct, uchar type, uchar
string smfct = manufacturerFlag(a.mfct);
if (id != a.id ||
mfct != smfct ||
version != a.version ||
type != a.type ||
version != a.version)
wildcard_used != a.wildcard_used ||
mbus_primary != a.mbus_primary ||
wildcard_used != a.wildcard_used)
{
printf("Expected parse of address \"%s\" to return (id=%s mfct=%s type=%02x version=%02x) "
"but got (id=%s mfct=%s type=%02x version=%02x)\n",
printf("Expected parse of address \"%s\" to return (id=%s mfct=%s version=%02x type=%02x mp=%d wu=%d)\n"
"but got (id=%s mfct=%s version=%02x type=%02x mp=%d wu=%d)\n",
s.c_str(),
id.c_str(), mfct.c_str(), type, version,
a.id.c_str(), smfct.c_str(), a.type, a.version);
id.c_str(), mfct.c_str(), version, type, mbus_primary, wildcard_used,
a.id.c_str(), smfct.c_str(), a.version, a.type, a.mbus_primary, a.wildcard_used);
}
}
}
void test_addresses()
{
/*
tst_address("12345678",
true,
"12345678", // id
"@@@", // mfct
0, // type
0 // version
true,
"12345678", // id
"___", // mfct
0xff, // type
0xff, // version
false, // mbus primary
false // wildcard used
);
tst_address("123k45678", false, "", "", 0xff, 0xff, false, false);
tst_address("1234", false, "", "", 0xff, 0xff, false, false);
tst_address("0", true, "0", "___", 0xff, 0xff, true,false);
tst_address("250", true, "250", "___", 0xff, 0xff, true, false);
tst_address("251", false, "", "", 0xff, 0xff, false, false);
tst_address("0.M=PII.V=01.T=1b", true, "0", "PII", 0x01, 0x1b, true, false);
tst_address("123.V=11.M=FOO.T=ff", true, "123", "FOO", 0x11, 0xff, true, false);
tst_address("123.M=FOO", true, "123", "FOO", 0xff, 0xff, true, false);
tst_address("123.M=FOO.V=33", true, "123", "FOO", 0x33, 0xff, true, false);
tst_address("123.T=33", true, "123", "___", 0xff, 0x33, true, false);
tst_address("1.V=33", true, "1", "___", 0x33, 0xff, true, false);
tst_address("16.M=BAR", true, "16", "BAR", 0xff, 0xff, true, false);
tst_address("123k45678", false, "", "", 0, 0);
tst_address("1234", false, "", "", 0, 0);
tst_address("0", true, "0", "@@@", 0, 0);
tst_address("250", true, "250", "@@@", 0, 0);
tst_address("251", false, "", "", 0, 0);
tst_address("0.M=PII.T=1b.V=01", true, "0", "PII", 0x1b, 0x01);
tst_address("123.V=11.M=FOO.T=ff", true, "123", "FOO", 0xff, 0x11);
tst_address("16.M=BAR", true, "16", "BAR", 0, 0);
*/
// tst_address("12*", true, "12*", "___", 0xff, 0xff, false, true);
}
void eq(string a, string b, const char *tn)

Wyświetl plik

@ -654,244 +654,6 @@ bool isValidAlias(const string& alias)
return true;
}
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<string> 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<id.length(); ++i)
{
if (id[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<string> &ids, vector<string>& 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<string>& 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<string> &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();

Wyświetl plik

@ -155,19 +155,10 @@ void setAlarmShells(std::vector<std::string> &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<std::string>& match_rules, bool *used_wildcard);
bool doesIdsMatchExpressions(std::vector<std::string> &ids, std::vector<std::string>& match_rules, bool *used_wildcard);
std::string toIdsCommaSeparated(std::vector<std::string> &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<std::string> splitMatchExpressions(const std::string& mes);
// Split s into strings separated by c.
std::vector<std::string> splitString(const std::string &s, char c);
// Split s into strings separated by c and store inte set.

Wyświetl plik

@ -231,6 +231,7 @@ LIST_OF_MANUFACTURERS
void Telegram::addId(const vector<uchar>::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()) {