kopia lustrzana https://github.com/weetmuts/wmbusmeters
Significantly improve --analyze.
rodzic
cb794cae42
commit
6823279b05
|
@ -95,15 +95,56 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
|
|||
{
|
||||
c->analyze_format = OutputFormat::PLAIN;
|
||||
}
|
||||
c->analyze_driver = "";
|
||||
c->analyze_key = "";
|
||||
c->analyze_verbose = false;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--analyze=", 10)) {
|
||||
c->analyze = true;
|
||||
string format = string(argv[i]+10);
|
||||
if (format == "plain") c->analyze_format = OutputFormat::PLAIN;
|
||||
else if (format == "terminal") c->analyze_format = OutputFormat::TERMINAL;
|
||||
else if (format == "json") c->analyze_format = OutputFormat::JSON;
|
||||
if (isatty(1))
|
||||
{
|
||||
c->analyze_format = OutputFormat::TERMINAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
c->analyze_format = OutputFormat::PLAIN;
|
||||
}
|
||||
c->analyze_driver = "";
|
||||
c->analyze_key = "";
|
||||
c->analyze_verbose = false;
|
||||
string arg = string(argv[i]+10);
|
||||
vector<string> args = splitString(arg, ':');
|
||||
|
||||
for (auto s : args)
|
||||
{
|
||||
bool inv = false;
|
||||
if (isHexStringStrict(s, &inv))
|
||||
{
|
||||
if (inv)
|
||||
{
|
||||
error("Bad key \"%s\"", s.c_str());
|
||||
}
|
||||
c->analyze_key = s;
|
||||
}
|
||||
else if (s == "plain") c->analyze_format = OutputFormat::PLAIN;
|
||||
else if (s == "terminal") c->analyze_format = OutputFormat::TERMINAL;
|
||||
else if (s == "json") c->analyze_format = OutputFormat::JSON;
|
||||
else if (s == "verbose") c->analyze_verbose = true;
|
||||
else
|
||||
{
|
||||
MeterInfo mi;
|
||||
mi.parse("x", s, "00000000", "");
|
||||
|
||||
if (mi.driver == MeterDriver::UNKNOWN &&
|
||||
mi.driver_name.str() == "")
|
||||
{
|
||||
error("Not a valid meter driver \"%s\"\n", s.c_str());
|
||||
}
|
||||
c->analyze_driver = s;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,9 @@ struct Configuration
|
|||
bool license {};
|
||||
bool analyze {};
|
||||
OutputFormat analyze_format {};
|
||||
string analyze_driver {};
|
||||
string analyze_key {};
|
||||
bool analyze_verbose {};
|
||||
bool debug {};
|
||||
bool trace {};
|
||||
AddLogTimestamps addtimestamps {};
|
||||
|
|
|
@ -42,16 +42,9 @@ static bool ok = registerDriver([](DriverInfo&di)
|
|||
|
||||
MeterApator08::MeterApator08(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addFieldWithExtractor(
|
||||
addField(
|
||||
"total",
|
||||
Quantity::Volume,
|
||||
NoDifVifKey,
|
||||
VifScaling::Auto,
|
||||
MeasurementType::Instantaneous,
|
||||
ValueInformation::Volume,
|
||||
StorageNr(0),
|
||||
TariffNr(0),
|
||||
IndexNr(1),
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
"The total water consumption recorded by this meter.",
|
||||
SET_FUNC(total_water_consumption_m3_, Unit::M3),
|
||||
|
@ -84,7 +77,7 @@ void MeterApator08::processContent(Telegram *t)
|
|||
total_water_consumption_m3_ /= 3.0;
|
||||
|
||||
total = "*** 10|"+total+" total consumption (%f m3)";
|
||||
t->addSpecialExplanation(offset, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -497,7 +497,11 @@ bool start(Configuration *config)
|
|||
// and creates meters on demand when the telegram arrives
|
||||
// or on startup for 2-way communication meters like mbus or T2.
|
||||
meter_manager_ = createMeterManager(config->daemon);
|
||||
meter_manager_->analyzeEnabled(config->analyze, config->analyze_format);
|
||||
meter_manager_->analyzeEnabled(config->analyze,
|
||||
config->analyze_format,
|
||||
config->analyze_driver,
|
||||
config->analyze_key,
|
||||
config->analyze_verbose);
|
||||
|
||||
// The bus manager detects new/lost wmbus devices and
|
||||
// configures the devices according to the specification.
|
||||
|
|
|
@ -129,13 +129,13 @@ void MeterApator162::processContent(Telegram *t)
|
|||
int offset;
|
||||
extractDVdouble(&vendor_values, "0413", &offset, &total_water_consumption_m3_);
|
||||
total = "*** 10|"+total+" total consumption (%f m3)";
|
||||
t->addSpecialExplanation(offset, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
}
|
||||
else
|
||||
{
|
||||
string msg = "*** ";
|
||||
msg += bin2hex(content, i-1, 1)+"|"+bin2hex(content, i, size);
|
||||
t->addSpecialExplanation(i-1+t->header_size, KindOfData::CONTENT, Understanding::NONE, msg.c_str());
|
||||
t->addSpecialExplanation(i-1+t->header_size, size, KindOfData::CONTENT, Understanding::NONE, msg.c_str());
|
||||
}
|
||||
i += size;
|
||||
}
|
||||
|
|
415
src/meters.cc
415
src/meters.cc
|
@ -108,6 +108,9 @@ private:
|
|||
bool is_daemon_ {};
|
||||
bool should_analyze_ {};
|
||||
OutputFormat analyze_format_ {};
|
||||
string analyze_driver_;
|
||||
string analyze_key_;
|
||||
bool analyze_verbose_;
|
||||
vector<MeterInfo> meter_templates_;
|
||||
vector<shared_ptr<Meter>> meters_;
|
||||
function<void(AboutTelegram&,vector<uchar>)> on_telegram_;
|
||||
|
@ -343,16 +346,145 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void analyzeEnabled(bool b, OutputFormat f)
|
||||
void analyzeEnabled(bool b, OutputFormat f, string force_driver, string key, bool verbose)
|
||||
{
|
||||
should_analyze_ = b;
|
||||
analyze_format_ = f;
|
||||
analyze_driver_ = force_driver;
|
||||
analyze_key_ = key;
|
||||
analyze_verbose_ = verbose;
|
||||
}
|
||||
|
||||
string findBestOldStyleDriver(MeterInfo &mi,
|
||||
int *best_length,
|
||||
int *best_understood,
|
||||
Telegram &t,
|
||||
AboutTelegram &about,
|
||||
vector<uchar> &input_frame,
|
||||
bool simulated,
|
||||
string only)
|
||||
{
|
||||
vector<MeterDriver> old_drivers;
|
||||
#define X(mname,linkmode,info,type,cname) old_drivers.push_back(MeterDriver::type);
|
||||
LIST_OF_METERS
|
||||
#undef X
|
||||
|
||||
string best_driver = "";
|
||||
for (MeterDriver odr : old_drivers)
|
||||
{
|
||||
if (odr == MeterDriver::AUTO) continue;
|
||||
if (odr == MeterDriver::UNKNOWN) continue;
|
||||
string driver_name = toString(odr);
|
||||
if (only != "" && driver_name != only) continue;
|
||||
|
||||
if (!isMeterDriverReasonableForMedia(odr, "", t.dll_type) &&
|
||||
!isMeterDriverReasonableForMedia(odr, "", t.tpl_type))
|
||||
{
|
||||
// Sanity check, skip this driver since it is not relevant for this media.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
debug("Testing old style driver %s...\n", driver_name.c_str());
|
||||
mi.driver = odr;
|
||||
mi.driver_name = DriverName("");
|
||||
|
||||
auto meter = createMeter(&mi);
|
||||
|
||||
bool match = false;
|
||||
string id;
|
||||
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
|
||||
if (!match)
|
||||
{
|
||||
debug("no match!\n");
|
||||
}
|
||||
else if (!h)
|
||||
{
|
||||
// Oups, we added a new meter object tailored for this telegram
|
||||
// but it still did not handle it! This can happen if the wrong
|
||||
// decryption key was used. But it is ok if analyzing....
|
||||
debug("Newly created meter (%s %s %s) did not handle telegram!\n",
|
||||
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
int l = 0;
|
||||
int u = 0;
|
||||
t.analyzeParse(OutputFormat::NONE, &l, &u);
|
||||
if (analyze_verbose_ && only == "") printf("(verbose) old %02d/%02d %s\n", u, l, driver_name.c_str());
|
||||
if (u > *best_understood)
|
||||
{
|
||||
*best_understood = u;
|
||||
*best_length = l;
|
||||
best_driver = driver_name;
|
||||
if (analyze_verbose_ && only == "") printf("(verbose) old best so far: %s %02d/%02d\n", best_driver.c_str(), u, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
return best_driver;
|
||||
}
|
||||
|
||||
string findBestNewStyleDriver(MeterInfo &mi,
|
||||
int *best_length,
|
||||
int *best_understood,
|
||||
Telegram &t,
|
||||
AboutTelegram &about,
|
||||
vector<uchar> &input_frame,
|
||||
bool simulated,
|
||||
string only)
|
||||
{
|
||||
string best_driver = "";
|
||||
|
||||
for (DriverInfo ndr : all_registered_drivers_list_)
|
||||
{
|
||||
string driver_name = toString(ndr);
|
||||
if (only != "" && driver_name != only) continue;
|
||||
|
||||
debug("Testing new style driver %s...\n", driver_name.c_str());
|
||||
mi.driver = MeterDriver::UNKNOWN;
|
||||
mi.driver_name = driver_name;
|
||||
|
||||
auto meter = createMeter(&mi);
|
||||
|
||||
bool match = false;
|
||||
string id;
|
||||
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
|
||||
|
||||
if (!match)
|
||||
{
|
||||
debug("no match!\n");
|
||||
}
|
||||
else if (!h)
|
||||
{
|
||||
// Oups, we added a new meter object tailored for this telegram
|
||||
// but it still did not handle it! This can happen if the wrong
|
||||
// decryption key was used. But it is ok if analyzing....
|
||||
debug("Newly created meter (%s %s %s) did not handle telegram!\n",
|
||||
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
int l = 0;
|
||||
int u = 0;
|
||||
t.analyzeParse(OutputFormat::NONE, &l, &u);
|
||||
if (analyze_verbose_ && only == "") printf("(verbose) new %02d/%02d %s\n", u, l, driver_name.c_str());
|
||||
if (u > *best_understood)
|
||||
{
|
||||
*best_understood = u;
|
||||
*best_length = l;
|
||||
best_driver = ndr.name().str();
|
||||
if (analyze_verbose_ && only == "") printf("(verbose) new best so far: %s %02d/%02d\n", best_driver.c_str(), u, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
return best_driver;
|
||||
}
|
||||
|
||||
void analyzeTelegram(AboutTelegram &about, vector<uchar> &input_frame, bool simulated)
|
||||
{
|
||||
Telegram t;
|
||||
t.about = about;
|
||||
|
||||
bool ok = t.parseHeader(input_frame);
|
||||
if (simulated) t.markAsSimulated();
|
||||
t.markAsBeingAnalyzed();
|
||||
|
@ -363,168 +495,142 @@ public:
|
|||
return;
|
||||
}
|
||||
|
||||
vector<MeterDriver> drivers;
|
||||
#define X(mname,linkmode,info,type,cname) drivers.push_back(MeterDriver::type);
|
||||
LIST_OF_METERS
|
||||
#undef X
|
||||
|
||||
MeterInfo mi;
|
||||
if (meter_templates_.size() > 0)
|
||||
{
|
||||
if (meter_templates_.size() > 1)
|
||||
{
|
||||
error("When analyzing you can only specify a single meter quadruple.\n");
|
||||
}
|
||||
if (meter_templates_[0].driver != MeterDriver::AUTO)
|
||||
{
|
||||
drivers.clear();
|
||||
drivers.push_back(meter_templates_[0].driver);
|
||||
mi = meter_templates_[0];
|
||||
}
|
||||
error("You cannot specify a meter quadruple when analyzing.\n"
|
||||
"Instead use --analyze=<format>:<driver>:<key>\n"
|
||||
"where <formt> <driver> <key> are all optional.\n"
|
||||
"E.g. --analyze=terminal:multical21:001122334455667788001122334455667788\n"
|
||||
" --analyze=001122334455667788001122334455667788\n"
|
||||
" --analyze\n");
|
||||
}
|
||||
|
||||
// Overwrite the id with the id from the telegram to be analyzed.
|
||||
MeterInfo mi;
|
||||
mi.key = analyze_key_;
|
||||
mi.ids.clear();
|
||||
mi.ids.push_back(t.ids.back());
|
||||
mi.idsc = t.ids.back();
|
||||
|
||||
bool handled = false;
|
||||
DriverInfo best_driver;
|
||||
// For the best driver we have:
|
||||
int best_content_length = 0;
|
||||
int best_understood_content_length = 0;
|
||||
// This will be the driver that will actually decode and print with.
|
||||
string using_driver = "";
|
||||
int using_length = 0;
|
||||
int using_understood = 0;
|
||||
|
||||
for (MeterDriver dr : drivers)
|
||||
// Driver that understands most of the telegram content.
|
||||
string best_driver = "";
|
||||
int best_length = 0;
|
||||
int best_understood = 0;
|
||||
|
||||
int old_best_length = 0;
|
||||
int old_best_understood = 0;
|
||||
string best_old_driver = findBestOldStyleDriver(mi, &old_best_length, &old_best_understood, t, about, input_frame, simulated, "");
|
||||
|
||||
int new_best_length = 0;
|
||||
int new_best_understood = 0;
|
||||
string best_new_driver = findBestNewStyleDriver(mi, &new_best_length, &new_best_understood, t, about, input_frame, simulated, "");
|
||||
|
||||
mi.driver = MeterDriver::UNKNOWN;
|
||||
mi.driver_name = DriverName("");
|
||||
|
||||
// Use the existing mapping from mfct/media/version to driver.
|
||||
DriverInfo auto_di = pickMeterDriver(&t);
|
||||
string auto_driver = auto_di.name().str();
|
||||
if (auto_driver == "")
|
||||
{
|
||||
if (dr == MeterDriver::AUTO) continue;
|
||||
if (dr == MeterDriver::UNKNOWN) continue;
|
||||
if (!isMeterDriverReasonableForMedia(dr, "", t.dll_type) &&
|
||||
!isMeterDriverReasonableForMedia(dr, "", t.tpl_type))
|
||||
{
|
||||
// Skip this driver since it is not relevant for this media.
|
||||
continue;
|
||||
}
|
||||
auto_driver = toString(auto_di.driver());
|
||||
}
|
||||
|
||||
string driver_name = toString(dr);
|
||||
debug("Testing driver %s...\n", driver_name.c_str());
|
||||
mi.driver = dr;
|
||||
auto meter = createMeter(&mi);
|
||||
bool match = false;
|
||||
string id;
|
||||
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
|
||||
if (!match)
|
||||
// Will be non-empty if an explicit driver has been selected.
|
||||
string force_driver = analyze_driver_;
|
||||
int force_length = 0;
|
||||
int force_understood = 0;
|
||||
|
||||
if (force_driver == "" && auto_driver == "")
|
||||
{
|
||||
force_driver = auto_driver;
|
||||
}
|
||||
|
||||
if (force_driver != "")
|
||||
{
|
||||
using_driver = findBestOldStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
|
||||
force_driver);
|
||||
|
||||
if (using_driver != "")
|
||||
{
|
||||
}
|
||||
else if (!h)
|
||||
{
|
||||
// Oups, we added a new meter object tailored for this telegram
|
||||
// but it still did not handle it! This can happen if the wrong
|
||||
// decryption key was used. But it is ok if analyzing....
|
||||
debug("(meter) newly created meter (%s %s %s) did not handle telegram!\n",
|
||||
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
|
||||
mi.driver = toMeterDriver(using_driver);
|
||||
mi.driver_name = DriverName("");
|
||||
}
|
||||
else
|
||||
{
|
||||
handled = true;
|
||||
int l = 0;
|
||||
int u = 0;
|
||||
t.analyzeParse(OutputFormat::NONE, &l, &u);
|
||||
verbose("(analyze) %s %d/%d\n", driver_name.c_str(), u, l);
|
||||
if (u > best_understood_content_length)
|
||||
{
|
||||
// Understood so many bytes
|
||||
best_understood_content_length = u;
|
||||
// Out of this many bytes of content total.
|
||||
best_content_length = l;
|
||||
best_driver = dr;
|
||||
}
|
||||
using_driver = findBestNewStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
|
||||
force_driver);
|
||||
mi.driver_name = using_driver;
|
||||
mi.driver = MeterDriver::UNKNOWN;
|
||||
}
|
||||
using_length = force_length;
|
||||
using_understood = force_understood;
|
||||
}
|
||||
for (DriverInfo dr : all_registered_drivers_list_)
|
||||
{
|
||||
string driver_name = toString(dr);
|
||||
debug("Testing driver %s...\n", driver_name.c_str());
|
||||
mi.driver_name = driver_name;
|
||||
auto meter = createMeter(&mi);
|
||||
bool match = false;
|
||||
string id;
|
||||
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
|
||||
if (!match)
|
||||
{
|
||||
|
||||
}
|
||||
else if (!h)
|
||||
if (old_best_understood > new_best_understood)
|
||||
{
|
||||
best_length = old_best_length;
|
||||
best_understood = old_best_understood;
|
||||
best_driver = best_old_driver;
|
||||
if (using_driver == "")
|
||||
{
|
||||
// Oups, we added a new meter object tailored for this telegram
|
||||
// but it still did not handle it! This can happen if the wrong
|
||||
// decryption key was used. But it is ok if analyzing....
|
||||
debug("(meter) newly created meter (%s %s %s) did not handle telegram!\n",
|
||||
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
handled = true;
|
||||
int l = 0;
|
||||
int u = 0;
|
||||
t.analyzeParse(OutputFormat::NONE, &l, &u);
|
||||
verbose("(analyze) %s %d/%d\n", driver_name.c_str(), u, l);
|
||||
if (u > best_understood_content_length)
|
||||
{
|
||||
// Understood so many bytes
|
||||
best_understood_content_length = u;
|
||||
// Out of this many bytes of content total.
|
||||
best_content_length = l;
|
||||
best_driver = dr;
|
||||
}
|
||||
mi.driver = toMeterDriver(best_old_driver);
|
||||
mi.driver_name = DriverName("");
|
||||
using_driver = best_old_driver;
|
||||
using_length = best_length;
|
||||
using_understood = best_understood;
|
||||
}
|
||||
}
|
||||
if (handled)
|
||||
else if (new_best_understood > old_best_understood)
|
||||
{
|
||||
DriverInfo auto_driver = pickMeterDriver(&t);
|
||||
string ad = toString(auto_driver);
|
||||
string bd = toString(best_driver);
|
||||
if (auto_driver.driver() != MeterDriver::UNKNOWN)
|
||||
best_length = new_best_length;
|
||||
best_understood = new_best_understood;
|
||||
best_driver = best_new_driver;
|
||||
if (using_driver == "")
|
||||
{
|
||||
if (ad != bd)
|
||||
{
|
||||
printf("\nUsing driver \"%s\" based on mfct/type/version driver lookup table.\n", ad.c_str());
|
||||
printf("But a better match could perhaps be driver \"%s\" with %d/%d content bytes understood.\n",
|
||||
bd.c_str(), best_understood_content_length, best_content_length);
|
||||
mi.driver_name = auto_driver.name();
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\nUsing driver \"%s\" based on mfct/type/version driver lookup table.\n", ad.c_str());
|
||||
printf("Which is also the best matching driver with %d/%d content bytes understood.\n",
|
||||
best_understood_content_length, best_content_length);
|
||||
mi.driver_name = best_driver.name();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\nUsing driver \"%s\" based on best content match with %d/%d content bytes understood.\n",
|
||||
bd.c_str(), best_understood_content_length, best_content_length);
|
||||
printf("The mfct/type/version combo was not found in the driver lookup table.\n");
|
||||
mi.driver_name = best_driver.name();
|
||||
mi.driver_name = best_new_driver;
|
||||
mi.driver = MeterDriver::UNKNOWN;
|
||||
using_driver = best_new_driver;
|
||||
using_length = best_length;
|
||||
using_understood = best_understood;
|
||||
}
|
||||
}
|
||||
|
||||
auto meter = createMeter(&mi);
|
||||
bool match = false;
|
||||
string id;
|
||||
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
|
||||
int l = 0;
|
||||
int u = 0;
|
||||
t.analyzeParse(analyze_format_, &l, &u);
|
||||
string hr, fields, json;
|
||||
vector<string> envs, more_json, selected_fields;
|
||||
meter->printMeter(&t, &hr, &fields, '\t', &json,
|
||||
&envs, &more_json, &selected_fields, true);
|
||||
printf("%s\n", json.c_str());
|
||||
}
|
||||
else
|
||||
auto meter = createMeter(&mi);
|
||||
|
||||
bool match = false;
|
||||
string id;
|
||||
|
||||
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
|
||||
|
||||
int u = 0;
|
||||
int l = 0;
|
||||
|
||||
string output = t.analyzeParse(analyze_format_, &u, &l);
|
||||
|
||||
string hr, fields, json;
|
||||
vector<string> envs, more_json, selected_fields;
|
||||
|
||||
meter->printMeter(&t, &hr, &fields, '\t', &json,
|
||||
&envs, &more_json, &selected_fields, true);
|
||||
|
||||
if (auto_driver == "")
|
||||
{
|
||||
printf("No suitable driver found.\n");
|
||||
auto_driver = "not found!";
|
||||
}
|
||||
|
||||
printf("Auto driver : %s\n", auto_driver.c_str());
|
||||
printf("Best driver : %s %02d/%02d\n", best_driver.c_str(), best_understood, best_length);
|
||||
printf("Using driver : %s %02d/%02d\n", using_driver.c_str(), using_understood, using_length);
|
||||
|
||||
printf("%s\n", output.c_str());
|
||||
|
||||
printf("%s\n", json.c_str());
|
||||
}
|
||||
|
||||
MeterManagerImplementation(bool daemon) : is_daemon_(daemon) {}
|
||||
|
@ -809,6 +915,44 @@ void MeterCommonImplementation::addFieldWithExtractor(
|
|||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addField(
|
||||
string vname,
|
||||
Quantity vquantity,
|
||||
int print_properties,
|
||||
string help,
|
||||
function<void(Unit,double)> setValueFunc,
|
||||
function<double(Unit)> getValueFunc)
|
||||
{
|
||||
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||
string field_name = vname+"_"+default_unit;
|
||||
fields_.push_back(field_name);
|
||||
|
||||
prints_.push_back(
|
||||
FieldInfo(vname,
|
||||
vquantity,
|
||||
defaultUnitForQuantity(vquantity),
|
||||
DifVifKey(""),
|
||||
VifScaling::None,
|
||||
MeasurementType::Unknown,
|
||||
ValueInformation::Volume,
|
||||
StorageNr(0),
|
||||
TariffNr(0),
|
||||
0,
|
||||
help,
|
||||
(print_properties & PrintProperty::FIELD) != 0,
|
||||
(print_properties & PrintProperty::JSON) != 0,
|
||||
(print_properties & PrintProperty::IMPORTANT) != 0,
|
||||
field_name,
|
||||
getValueFunc,
|
||||
NULL,
|
||||
setValueFunc,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup
|
||||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addStringFieldWithExtractor(
|
||||
string vname,
|
||||
Quantity vquantity,
|
||||
|
@ -1442,6 +1586,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
|
|||
ok = t.parse(input_frame, &meter_keys_, true);
|
||||
if (!ok)
|
||||
{
|
||||
if (out_analyzed != NULL) *out_analyzed = t;
|
||||
// Ignoring telegram since it could not be parsed.
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -456,7 +456,7 @@ struct MeterManager
|
|||
virtual void onTelegram(function<void(AboutTelegram&,vector<uchar>)> cb) = 0;
|
||||
virtual void whenMeterUpdated(std::function<void(Telegram*t,Meter*)> cb) = 0;
|
||||
virtual void pollMeters(shared_ptr<BusManager> bus) = 0;
|
||||
virtual void analyzeEnabled(bool b, OutputFormat f) = 0;
|
||||
virtual void analyzeEnabled(bool b, OutputFormat f, string force_driver, string key, bool verbose) = 0;
|
||||
virtual void analyzeTelegram(AboutTelegram &about, vector<uchar> &input_frame, bool simulated) = 0;
|
||||
|
||||
virtual ~MeterManager() = default;
|
||||
|
|
|
@ -106,6 +106,14 @@ protected:
|
|||
function<void(Unit,double)> setValueFunc, // Use the SET macro above.
|
||||
function<double(Unit)> getValueFunc); // Use the GET macro above.
|
||||
|
||||
void addField(
|
||||
string vname, // Name of value without unit, eg total
|
||||
Quantity vquantity, // Value belongs to this quantity.
|
||||
int print_properties, // Should this be printed by default in fields,json and hr.
|
||||
string help,
|
||||
function<void(Unit,double)> setValueFunc, // Use the SET macro above.
|
||||
function<double(Unit)> getValueFunc); // Use the GET macro above.
|
||||
|
||||
#define SET_STRING_FUNC(varname) {[=](string s){varname = s;}}
|
||||
#define GET_STRING_FUNC(varname) {[=](){return varname; }}
|
||||
|
||||
|
|
100
src/wmbus.cc
100
src/wmbus.cc
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2021 Fredrik Öhrström
|
||||
Copyright (C) 2017-2022 Fredrik Öhrström
|
||||
|
||||
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
|
||||
|
@ -808,7 +808,7 @@ void Telegram::addMoreExplanation(int pos, const char* fmt, ...)
|
|||
}
|
||||
}
|
||||
|
||||
void Telegram::addSpecialExplanation(int offset, KindOfData k, Understanding u, const char* fmt, ...)
|
||||
void Telegram::addSpecialExplanation(int offset, int len, KindOfData k, Understanding u, const char* fmt, ...)
|
||||
{
|
||||
char buf[1024];
|
||||
buf[1023] = 0;
|
||||
|
@ -818,7 +818,7 @@ void Telegram::addSpecialExplanation(int offset, KindOfData k, Understanding u,
|
|||
vsnprintf(buf, 1023, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
explanations.push_back({offset, 1, buf, k, u});
|
||||
explanations.push_back({offset, len, buf, k, u});
|
||||
}
|
||||
|
||||
bool expectedMore(int line)
|
||||
|
@ -1044,18 +1044,28 @@ bool Telegram::parseELL(vector<uchar>::iterator &pos)
|
|||
int len = distance(pos+2, frame.end());
|
||||
uint16_t check = crc16_EN13757(&(frame[dist]), len);
|
||||
|
||||
addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x payload crc (calculated %02x%02x %s)",
|
||||
ell_pl_crc_b[0], ell_pl_crc_b[1],
|
||||
check & 0xff, check >> 8, (ell_pl_crc==check?"OK":"ERROR"));
|
||||
|
||||
if (ell_pl_crc != check && !FUZZING)
|
||||
if (ell_pl_crc == check || FUZZING)
|
||||
{
|
||||
addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL,
|
||||
"%02x%02x payload crc (calculated %02x%02x %s)",
|
||||
ell_pl_crc_b[0], ell_pl_crc_b[1],
|
||||
check & 0xff, check >> 8, (ell_pl_crc==check?"OK":"ERROR"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ouch, checksum of the payload does not match.
|
||||
// A wrong key was probably used for decryption.
|
||||
// A wrong key, or no key was probably used for decryption.
|
||||
decryption_failed = true;
|
||||
|
||||
// Log the content as encrypted.
|
||||
int num_encrypted_bytes = frame.end()-pos;
|
||||
string info = bin2hex(pos, frame.end(), num_encrypted_bytes);
|
||||
info += " encrypted";
|
||||
addExplanationAndIncrementPos(pos, num_encrypted_bytes, KindOfData::CONTENT, Understanding::ENCRYPTED, info.c_str());
|
||||
|
||||
if (parser_warns_)
|
||||
{
|
||||
if (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a))
|
||||
if (!beingAnalyzed() && (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a)))
|
||||
{
|
||||
// Print this warning only once! Unless you are using verbose or debug.
|
||||
warning("(wmbus) WARNING! decrypted payload crc failed check, did you use the correct decryption key? "
|
||||
|
@ -1493,8 +1503,21 @@ bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos)
|
|||
{
|
||||
addDefaultManufacturerKeyIfAny(frame, tpl_sec_mode, meter_keys);
|
||||
}
|
||||
bool ok = decrypt_TPL_AES_CBC_IV(this, frame, pos, meter_keys->confidentiality_key);
|
||||
if (!ok) return false;
|
||||
int num_encrypted_bytes = 0;
|
||||
int num_not_encrypted_at_end = 0;
|
||||
bool ok = decrypt_TPL_AES_CBC_IV(this, frame, pos, meter_keys->confidentiality_key,
|
||||
&num_encrypted_bytes, &num_not_encrypted_at_end);
|
||||
if (!ok)
|
||||
{
|
||||
string info = bin2hex(pos, frame.end(), num_encrypted_bytes);
|
||||
info += " encrypted";
|
||||
addExplanationAndIncrementPos(pos, num_encrypted_bytes, KindOfData::CONTENT, Understanding::ENCRYPTED, info.c_str());
|
||||
if (meter_keys->confidentiality_key.size() > 0)
|
||||
{
|
||||
// Only fail if we gave an explicit key.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Now the frame from pos and onwards has been decrypted.
|
||||
|
||||
CHECK(2);
|
||||
|
@ -1502,7 +1525,7 @@ bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos)
|
|||
{
|
||||
if (parser_warns_)
|
||||
{
|
||||
if (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a))
|
||||
if (!beingAnalyzed() && (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a)))
|
||||
{
|
||||
// Print this warning only once! Unless you are using verbose or debug.
|
||||
warning("(wmbus) WARNING! decrypted content failed check, did you use the correct decryption key? "
|
||||
|
@ -1537,7 +1560,7 @@ bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos)
|
|||
{
|
||||
if (parser_warns_)
|
||||
{
|
||||
if (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a))
|
||||
if (!beingAnalyzed() && (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a)))
|
||||
{
|
||||
// Print this warning only once! Unless you are using verbose or debug.
|
||||
warning("(wmbus) WARNING! telegram mac check failed, did you use the correct decryption key? "
|
||||
|
@ -1553,8 +1576,17 @@ bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ok = decrypt_TPL_AES_CBC_NO_IV(this, frame, pos, tpl_generated_key);
|
||||
if (!ok) return false;
|
||||
int num_encrypted_bytes = 0;
|
||||
int num_not_encrypted_at_end = 0;
|
||||
bool ok = decrypt_TPL_AES_CBC_NO_IV(this, frame, pos, tpl_generated_key,
|
||||
&num_encrypted_bytes,
|
||||
&num_not_encrypted_at_end);
|
||||
if (!ok)
|
||||
{
|
||||
addExplanationAndIncrementPos(pos, num_encrypted_bytes, KindOfData::CONTENT, Understanding::FULL,
|
||||
"encrypted data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now the frame from pos and onwards has been decrypted.
|
||||
CHECK(2);
|
||||
|
@ -1562,7 +1594,7 @@ bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos)
|
|||
{
|
||||
if (parser_warns_)
|
||||
{
|
||||
if (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a))
|
||||
if (!beingAnalyzed() && (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(this, dll_a)))
|
||||
{
|
||||
// Print this warning only once! Unless you are using verbose or debug.
|
||||
warning("(wmbus) WARNING! decrypted content failed check, did you use the correct decryption key? "
|
||||
|
@ -1749,12 +1781,22 @@ bool Telegram::parseTPL(vector<uchar>::iterator &pos)
|
|||
{
|
||||
header_size = distance(frame.begin(), pos);
|
||||
suffix_size = 0;
|
||||
int num_mfct_bytes = frame.end()-pos;
|
||||
string info = bin2hex(pos, frame.end(), num_mfct_bytes);
|
||||
info += " mfct specific";
|
||||
addExplanationAndIncrementPos(pos, num_mfct_bytes, KindOfData::CONTENT, Understanding::NONE, info.c_str());
|
||||
|
||||
return true; // Manufacturer specific telegram payload. Oh well....
|
||||
}
|
||||
case CI_Field_Values::MFCT_SPECIFIC_A3: // Another stoopid non-conformat wmbus Diehl/Sappel/Izar water meter addon.
|
||||
{
|
||||
header_size = distance(frame.begin(), pos);
|
||||
suffix_size = 0;
|
||||
int num_mfct_bytes = frame.end()-pos;
|
||||
string info = bin2hex(pos, frame.end(), num_mfct_bytes);
|
||||
info += " mfct specific";
|
||||
addExplanationAndIncrementPos(pos, num_mfct_bytes, KindOfData::CONTENT, Understanding::NONE, info.c_str());
|
||||
|
||||
return true; // Manufacturer specific telegram payload. Oh well....
|
||||
}
|
||||
}
|
||||
|
@ -1989,6 +2031,7 @@ void Telegram::explainParse(string intro, int from)
|
|||
const char *u = "?";
|
||||
if (p.understanding == Understanding::FULL) u = "!";
|
||||
if (p.understanding == Understanding::PARTIAL) u = "p";
|
||||
if (p.understanding == Understanding::ENCRYPTED) u = "E";
|
||||
|
||||
// Do not print ok for understood protocol, it is implicit.
|
||||
// However if a protocol is not full understood then print p or ?.
|
||||
|
@ -1998,8 +2041,10 @@ void Telegram::explainParse(string intro, int from)
|
|||
}
|
||||
}
|
||||
|
||||
void printAnalysisAsText(vector<Explanation> &explanations, bool use_ansi)
|
||||
string renderAnalysisAsText(vector<Explanation> &explanations, bool use_ansi)
|
||||
{
|
||||
string s;
|
||||
|
||||
const char *green;
|
||||
const char *yellow;
|
||||
const char *red;
|
||||
|
@ -2027,6 +2072,7 @@ void printAnalysisAsText(vector<Explanation> &explanations, bool use_ansi)
|
|||
const char *u = "?";
|
||||
if (p.understanding == Understanding::FULL) u = "!";
|
||||
if (p.understanding == Understanding::PARTIAL) u = "p";
|
||||
if (p.understanding == Understanding::ENCRYPTED) u = "E";
|
||||
|
||||
// Do not print ok for understood protocol, it is implicit.
|
||||
// However if a protocol is not full understood then print p or ?.
|
||||
|
@ -2053,16 +2099,17 @@ void printAnalysisAsText(vector<Explanation> &explanations, bool use_ansi)
|
|||
pre = red;
|
||||
}
|
||||
|
||||
printf("%03d %s%s: %s%s%s\n", p.pos, c, u, pre, p.info.c_str(), post);
|
||||
s += tostrprintf("%03d %s%s: %s%s%s\n", p.pos, c, u, pre, p.info.c_str(), post);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void printAnalysisAsJson(vector<Explanation> &explanations)
|
||||
string renderAnalysisAsJson(vector<Explanation> &explanations)
|
||||
{
|
||||
printf("{ \"TODO\": true }\n");
|
||||
return "{ \"TODO\": true }\n";
|
||||
}
|
||||
|
||||
void Telegram::analyzeParse(OutputFormat format, int *content_length, int *understood_content_length)
|
||||
string Telegram::analyzeParse(OutputFormat format, int *content_length, int *understood_content_length)
|
||||
{
|
||||
int u = 0;
|
||||
int l = 0;
|
||||
|
@ -2073,7 +2120,8 @@ void Telegram::analyzeParse(OutputFormat format, int *content_length, int *under
|
|||
if (e.kind == KindOfData::CONTENT)
|
||||
{
|
||||
l += e.len;
|
||||
if (e.understanding != Understanding::NONE)
|
||||
if (e.understanding == Understanding::PARTIAL ||
|
||||
e.understanding == Understanding::FULL)
|
||||
{
|
||||
// Its content and we have at least some understanding.
|
||||
u += e.len;
|
||||
|
@ -2089,16 +2137,18 @@ void Telegram::analyzeParse(OutputFormat format, int *content_length, int *under
|
|||
case OutputFormat::TERMINAL:
|
||||
{
|
||||
bool use_ansi = format == OutputFormat::TERMINAL;
|
||||
printAnalysisAsText(explanations, use_ansi);
|
||||
return renderAnalysisAsText(explanations, use_ansi);
|
||||
break;
|
||||
}
|
||||
case OutputFormat::JSON:
|
||||
printAnalysisAsJson(explanations);
|
||||
return renderAnalysisAsJson(explanations);
|
||||
break;
|
||||
case OutputFormat::NONE:
|
||||
// Do nothing
|
||||
return "";
|
||||
break;
|
||||
}
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
void detectMeterDrivers(int manufacturer, int media, int version, std::vector<std::string> *drivers);
|
||||
|
|
|
@ -364,7 +364,7 @@ enum class KindOfData
|
|||
// been partially decoded, or FULL when the volume or energy field is by itself complete.
|
||||
enum class Understanding
|
||||
{
|
||||
NONE, PARTIAL, FULL
|
||||
NONE, ENCRYPTED, PARTIAL, FULL
|
||||
};
|
||||
|
||||
struct Explanation
|
||||
|
@ -514,9 +514,9 @@ struct Telegram
|
|||
void addMoreExplanation(int pos, const char* fmt, ...);
|
||||
void addMoreExplanation(int pos, string json);
|
||||
// Add an explanation of data inside manufacturer specific data.
|
||||
void addSpecialExplanation(int offset, KindOfData k, Understanding u, const char* fmt, ...);
|
||||
void addSpecialExplanation(int offset, int len, KindOfData k, Understanding u, const char* fmt, ...);
|
||||
void explainParse(string intro, int from);
|
||||
void analyzeParse(OutputFormat o, int *content_length, int *understood_content_length);
|
||||
string analyzeParse(OutputFormat o, int *content_length, int *understood_content_length);
|
||||
|
||||
bool parserWarns() { return parser_warns_; }
|
||||
bool isSimulated() { return is_simulated_; }
|
||||
|
|
|
@ -30,7 +30,6 @@ bool decrypt_ELL_AES_CTR(Telegram *t, vector<uchar> &frame, vector<uchar>::itera
|
|||
vector<uchar> encrypted_bytes;
|
||||
vector<uchar> decrypted_bytes;
|
||||
encrypted_bytes.insert(encrypted_bytes.end(), pos, frame.end());
|
||||
frame.erase(pos, frame.end());
|
||||
debugPayload("(ELL) decrypting", encrypted_bytes);
|
||||
|
||||
uchar iv[16];
|
||||
|
@ -82,7 +81,12 @@ bool decrypt_ELL_AES_CTR(Telegram *t, vector<uchar> &frame, vector<uchar>::itera
|
|||
incrementIV(iv, sizeof(iv));
|
||||
}
|
||||
debugPayload("(ELL) decrypted", decrypted_bytes);
|
||||
|
||||
// Remove the encrypted bytes.
|
||||
frame.erase(pos, frame.end());
|
||||
// Insert the decrypted bytes.
|
||||
frame.insert(frame.end(), decrypted_bytes.begin(), decrypted_bytes.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -92,25 +96,33 @@ string frameTypeKamstrupC1(int ft) {
|
|||
return "?";
|
||||
}
|
||||
|
||||
bool decrypt_TPL_AES_CBC_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey)
|
||||
bool decrypt_TPL_AES_CBC_IV(Telegram *t,
|
||||
vector<uchar> &frame,
|
||||
vector<uchar>::iterator &pos,
|
||||
vector<uchar> &aeskey,
|
||||
int *num_encrypted_bytes,
|
||||
int *num_not_encrypted_at_end)
|
||||
{
|
||||
if (aeskey.size() == 0) return true;
|
||||
|
||||
vector<uchar> buffer;
|
||||
buffer.insert(buffer.end(), pos, frame.end());
|
||||
frame.erase(pos, frame.end());
|
||||
debugPayload("(TPL) AES CBC IV decrypting", buffer);
|
||||
|
||||
size_t len = buffer.size();
|
||||
size_t len = frame.end()-pos;
|
||||
|
||||
if (t->tpl_num_encr_blocks)
|
||||
{
|
||||
len = t->tpl_num_encr_blocks*16;
|
||||
}
|
||||
|
||||
*num_encrypted_bytes = len;
|
||||
*num_not_encrypted_at_end = buffer.size()-len;
|
||||
|
||||
debug("(TPL) num encrypted blocks %zu (%d bytes and remaining unencrypted %zu bytes)\n",
|
||||
t->tpl_num_encr_blocks, len, buffer.size()-len);
|
||||
|
||||
if (aeskey.size() == 0) return false;
|
||||
|
||||
debugPayload("(TPL) AES CBC IV decrypting", buffer);
|
||||
|
||||
if (buffer.size() < len)
|
||||
{
|
||||
warning("(TPL) warning: decryption received less bytes than expected for decryption! "
|
||||
|
@ -162,7 +174,11 @@ bool decrypt_TPL_AES_CBC_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::it
|
|||
uchar decrypted_data[buffer.size()];
|
||||
AES_CBC_decrypt_buffer(decrypted_data, buffer_data, len, &aeskey[0], iv);
|
||||
|
||||
// Remove the encrypted bytes.
|
||||
frame.erase(pos, frame.end());
|
||||
// Insert the decrypted bytes.
|
||||
frame.insert(frame.end(), decrypted_data, decrypted_data+len);
|
||||
|
||||
debugPayload("(TPL) decrypted ", frame, pos);
|
||||
|
||||
if (len < buffer.size())
|
||||
|
@ -173,7 +189,9 @@ bool decrypt_TPL_AES_CBC_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::it
|
|||
return true;
|
||||
}
|
||||
|
||||
bool decrypt_TPL_AES_CBC_NO_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey)
|
||||
bool decrypt_TPL_AES_CBC_NO_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey,
|
||||
int *num_encrypted_bytes,
|
||||
int *num_not_encrypted_at_end)
|
||||
{
|
||||
if (aeskey.size() == 0) return true;
|
||||
|
||||
|
|
|
@ -23,8 +23,13 @@
|
|||
#include "wmbus.h"
|
||||
|
||||
bool decrypt_ELL_AES_CTR(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey);
|
||||
bool decrypt_TPL_AES_CBC_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey);
|
||||
bool decrypt_TPL_AES_CBC_NO_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey);
|
||||
bool decrypt_TPL_AES_CBC_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey,
|
||||
int *num_encrypted_bytes,
|
||||
int *num_not_encrypted_at_end);
|
||||
bool decrypt_TPL_AES_CBC_NO_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey,
|
||||
int *num_encrypted_bytes,
|
||||
int *num_not_encrypted_at_end);
|
||||
|
||||
string frameTypeKamstrupC1(int ft);
|
||||
|
||||
#endif
|
||||
|
|
4
test.sh
4
test.sh
|
@ -145,8 +145,8 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
|||
./tests/test_drivers.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
#./tests/test_analyze.sh $PROG
|
||||
#if [ "$?" != "0" ]; then RC="1"; fi
|
||||
./tests/test_analyze.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
if [ -x ../additional_tests.sh ]
|
||||
then
|
||||
|
|
|
@ -4,13 +4,72 @@ PROG="$1"
|
|||
TEST=testoutput
|
||||
mkdir -p $TEST
|
||||
|
||||
TESTNAME="Test analyze compact telegram"
|
||||
performCheck() {
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_response.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_response.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo "OK: $TESTNAME"
|
||||
TESTRESULT="OK"
|
||||
else
|
||||
echo "ERROR: $TESTNAME $0"
|
||||
fi
|
||||
else
|
||||
echo "ERROR: $TESTNAME $0"
|
||||
echo "wmbusmeters returned error code: $?"
|
||||
cat $TEST/test_output.txt
|
||||
fi
|
||||
}
|
||||
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
|
||||
TESTNAME="Test analyze encrypted (no-key) ctr full telegram"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : 00/00
|
||||
Using driver : 00/00
|
||||
000 : 2a length (42 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : 2d2c dll-mfct (KAM)
|
||||
004 : 99873476 dll-id (76348799)
|
||||
008 : 1b dll-version
|
||||
009 : 16 dll-type (Cold water meter)
|
||||
010 : 8d ell-ci-field (ELL: Extended Link Layer II (8 Byte))
|
||||
011 : 20 ell-cc (slow_resp sync)
|
||||
012 : 91 ell-acc
|
||||
013 : d37cac21 sn (AES_CTR)
|
||||
017 CE: E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24 encrypted
|
||||
|
||||
Using driver "multical21" based on mfct/type/version driver lookup table.
|
||||
Which is also the best matching driver with 12/12 content bytes understood.
|
||||
{
|
||||
"media":"cold water",
|
||||
"meter":"unknown",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
||||
$PROG --analyze 2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24 > $TEST/test_output.txt 2>&1
|
||||
|
||||
performCheck
|
||||
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
|
||||
TESTNAME="Test analyze encrypted (no-key) ctr compact telegram"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : 00/00
|
||||
Using driver : 00/00
|
||||
000 : 23 length (35 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : 2d2c dll-mfct (KAM)
|
||||
|
@ -19,129 +78,186 @@ Which is also the best matching driver with 12/12 content bytes understood.
|
|||
009 : 16 dll-type (Cold water meter)
|
||||
010 : 8d ell-ci-field (ELL: Extended Link Layer II (8 Byte))
|
||||
011 : 20 ell-cc (slow_resp sync)
|
||||
012 : 87 ell-acc
|
||||
013 : d19ead21 sn (AES_CTR)
|
||||
017 : 7f17 payload crc (calculated 7f17 OK)
|
||||
019 : 79 tpl-ci-field (EN 13757-3 Application Layer with Compact frame (no tplh))
|
||||
020 : eda8 format signature
|
||||
022 : 6ab6 data crc
|
||||
024 C!: 7100 info codes (DRY(dry 22-31 days))
|
||||
026 C!: 08190000 total consumption (6.408000 m3)
|
||||
030 C!: 08190000 target consumption (6.408000 m3)
|
||||
034 C!: 7F flow temperature (127.000000 °C)
|
||||
035 C!: 13 external temperature (19.000000 °C)
|
||||
012 : 98 ell-acc
|
||||
013 : 3081b222 sn (AES_CTR)
|
||||
017 CE: 7A6FA1F10E1B79B5EB4B17E81F930E937EE06C encrypted
|
||||
|
||||
{
|
||||
"media":"cold water",
|
||||
"meter":"unknown",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
||||
$PROG --analyze=plain 23442D2C998734761B168D2087D19EAD217F1779EDA86AB6710008190000081900007F13 2> $TEST/test_output.txt 1>&2
|
||||
$PROG --analyze 23442D2C998734761B168D20983081B2227A6FA1F10E1B79B5EB4B17E81F930E937EE06C > $TEST/test_output.txt 2>&1
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
diff $TEST/test_expected.txt $TEST/test_output.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo OK: $TESTNAME
|
||||
TESTRESULT="OK"
|
||||
fi
|
||||
else
|
||||
echo ERROR: $TESTNAME
|
||||
echo "wmbusmeters returned error code: $?"
|
||||
cat $TEST/test_output.txt
|
||||
fi
|
||||
performCheck
|
||||
|
||||
TESTNAME="Test analyze normal telegram"
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
|
||||
TESTNAME="Test analyze encrypted (with-key) ctr full telegram"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
|
||||
Using driver "supercom587" based on mfct/type/version driver lookup table.
|
||||
But a better match could perhaps be driver "evo868" with 20/100 content bytes understood.
|
||||
000 : a2 length (162 bytes)
|
||||
Auto driver : multical21
|
||||
Best driver : multical21 12/12
|
||||
Using driver : multical21 12/12
|
||||
000 : 2a length (42 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : ee4d dll-mfct (SON)
|
||||
004 : 78563412 dll-id (12345678)
|
||||
008 : 3c dll-version
|
||||
009 : 06 dll-type (Warm Water (30°C-90°C) meter)
|
||||
010 : 7a tpl-ci-field (EN 13757-3 Application Layer (short tplh))
|
||||
011 : 8f tpl-acc-field
|
||||
012 : 00 tpl-sts-field (OK)
|
||||
013 : 0000 tpl-cfg 0000 ( )
|
||||
015 : 0C dif (8 digit BCD Instantaneous value)
|
||||
016 : 13 vif (Volume l)
|
||||
017 C!: 48550000 total consumption (5.548000 m3)
|
||||
021 : 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
022 : 6C vif (Date type G)
|
||||
023 C?: E1F1
|
||||
025 : 4C dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
002 : 2d2c dll-mfct (KAM)
|
||||
004 : 99873476 dll-id (76348799)
|
||||
008 : 1b dll-version
|
||||
009 : 16 dll-type (Cold water meter)
|
||||
010 : 8d ell-ci-field (ELL: Extended Link Layer II (8 Byte))
|
||||
011 : 20 ell-cc (slow_resp sync)
|
||||
012 : 91 ell-acc
|
||||
013 : d37cac21 sn (AES_CTR)
|
||||
017 : 576c payload crc (calculated 576c OK)
|
||||
019 : 78 tpl-ci-field (EN 13757-3 Application Layer (no tplh))
|
||||
020 : 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
021 : FF vif (Vendor extension)
|
||||
022 : 20 vife (per second)
|
||||
023 C!: 7100 info codes (DRY(dry 22-31 days))
|
||||
025 : 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
026 : 13 vif (Volume l)
|
||||
027 C?: 00000000
|
||||
031 : 82 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
032 : 04 dife (subunit=0 tariff=0 storagenr=8)
|
||||
033 : 6C vif (Date type G)
|
||||
034 C?: 2129
|
||||
036 : 8C dif (8 digit BCD Instantaneous value)
|
||||
037 : 04 dife (subunit=0 tariff=0 storagenr=8)
|
||||
038 : 13 vif (Volume l)
|
||||
039 C?: 33000000
|
||||
043 : 8D dif (variable length Instantaneous value)
|
||||
044 : 04 dife (subunit=0 tariff=0 storagenr=8)
|
||||
045 : 93 vif (Volume l)
|
||||
046 : 1E vife (Compact profile with register)
|
||||
047 : 3A varlen=58
|
||||
048 C?: 3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000
|
||||
106 : 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
107 : 6D vif (Date and time type)
|
||||
108 C?: 0D0B5C2B
|
||||
112 : 03 dif (24 Bit Integer/Binary Instantaneous value)
|
||||
113 : FD vif (Second extension FD of VIF-codes)
|
||||
114 : 6C vife (Operating time battery [hour(s)])
|
||||
115 C?: 5E1500
|
||||
118 : 82 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
119 : 20 dife (subunit=0 tariff=2 storagenr=0)
|
||||
120 : 6C vif (Date type G)
|
||||
121 C?: 5C29
|
||||
123 : 0B dif (6 digit BCD Instantaneous value)
|
||||
124 : FD vif (Second extension FD of VIF-codes)
|
||||
125 : 0F vife (Software version #)
|
||||
126 C?: 020001
|
||||
129 : 8C dif (8 digit BCD Instantaneous value)
|
||||
130 : 40 dife (subunit=1 tariff=0 storagenr=0)
|
||||
131 : 79 vif (Enhanced identification)
|
||||
132 C?: 67888523
|
||||
136 : 83 dif (24 Bit Integer/Binary Instantaneous value)
|
||||
137 : 10 dife (subunit=0 tariff=1 storagenr=0)
|
||||
138 : FD vif (Second extension FD of VIF-codes)
|
||||
139 : 31 vife (Duration of tariff [minute(s)])
|
||||
140 C?: 000000
|
||||
143 : 82 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
144 : 10 dife (subunit=0 tariff=1 storagenr=0)
|
||||
145 : 6C vif (Date type G)
|
||||
146 C?: 0101
|
||||
148 : 81 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
149 : 10 dife (subunit=0 tariff=1 storagenr=0)
|
||||
150 : FD vif (Second extension FD of VIF-codes)
|
||||
151 : 61 vife (Cumulation counter)
|
||||
152 C?: 00
|
||||
153 : 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
154 : FD vif (Second extension FD of VIF-codes)
|
||||
155 : 66 vife (State of parameter activation)
|
||||
156 C?: 0200
|
||||
158 : 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
159 : FD vif (Second extension FD of VIF-codes)
|
||||
160 : 17 vife (Error flags (binary))
|
||||
161 C?: 0000
|
||||
027 C!: 08190000 total consumption (6.408000 m3)
|
||||
031 : 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
032 : 13 vif (Volume l)
|
||||
033 C!: 08190000 target consumption (6.408000 m3)
|
||||
037 : 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
|
||||
038 : 5B vif (Flow temperature °C)
|
||||
039 C!: 7F flow temperature (127.000000 °C)
|
||||
040 : 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
|
||||
041 : 67 vif (External temperature °C)
|
||||
042 C!: 13 external temperature (19.000000 °C)
|
||||
|
||||
{
|
||||
"media":"cold water",
|
||||
"meter":"multical21",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"total_m3":6.408,
|
||||
"target_m3":6.408,
|
||||
"max_flow_m3h":0,
|
||||
"flow_temperature_c":127,
|
||||
"external_temperature_c":19,
|
||||
"current_status":"DRY",
|
||||
"time_dry":"22-31 days",
|
||||
"time_reversed":"",
|
||||
"time_leaking":"",
|
||||
"time_bursting":"",
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
||||
$PROG --analyze=plain A244EE4D785634123C067A8F0000000C1348550000426CE1F14C130000000082046C21298C0413330000008D04931E3A3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000046D0D0B5C2B03FD6C5E150082206C5C290BFD0F0200018C4079678885238310FD3100000082106C01018110FD610002FD66020002FD170000 2> $TEST/test_output.txt 1>&2
|
||||
$PROG --analyze=28F64A24988064A079AA2C807D6102AE 2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24 > $TEST/test_output.txt 2>&1
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
diff $TEST/test_expected.txt $TEST/test_output.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo OK: $TESTNAME
|
||||
TESTRESULT="OK"
|
||||
fi
|
||||
else
|
||||
echo "wmbusmeters returned error code: $?"
|
||||
cat $TEST/test_output.txt
|
||||
fi
|
||||
performCheck
|
||||
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
|
||||
TESTNAME="Test analyze encrypted (no-key) ctr compact telegram"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : multical21 12/12
|
||||
Using driver : multical21 12/12
|
||||
000 : 23 length (35 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : 2d2c dll-mfct (KAM)
|
||||
004 : 99873476 dll-id (76348799)
|
||||
008 : 1b dll-version
|
||||
009 : 16 dll-type (Cold water meter)
|
||||
010 : 8d ell-ci-field (ELL: Extended Link Layer II (8 Byte))
|
||||
011 : 20 ell-cc (slow_resp sync)
|
||||
012 : 98 ell-acc
|
||||
013 : 3081b222 sn (AES_CTR)
|
||||
017 : c33d payload crc (calculated c33d OK)
|
||||
019 : 79 tpl-ci-field (EN 13757-3 Application Layer with Compact frame (no tplh))
|
||||
020 : eda8 format signature
|
||||
022 : e475 data crc
|
||||
024 C!: 7100 info codes (DRY(dry 22-31 days))
|
||||
026 C!: 09190000 total consumption (6.409000 m3)
|
||||
030 C!: 09190000 target consumption (6.409000 m3)
|
||||
034 C!: 7F flow temperature (127.000000 °C)
|
||||
035 C!: 16 external temperature (22.000000 °C)
|
||||
|
||||
{
|
||||
"media":"cold water",
|
||||
"meter":"multical21",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"total_m3":6.409,
|
||||
"target_m3":6.409,
|
||||
"max_flow_m3h":0,
|
||||
"flow_temperature_c":127,
|
||||
"external_temperature_c":22,
|
||||
"current_status":"DRY",
|
||||
"time_dry":"22-31 days",
|
||||
"time_reversed":"",
|
||||
"time_leaking":"",
|
||||
"time_bursting":"",
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
||||
$PROG --analyze=28F64A24988064A079AA2C807D6102AE 23442D2C998734761B168D20983081B2227A6FA1F10E1B79B5EB4B17E81F930E937EE06C > $TEST/test_output.txt 2>&1
|
||||
|
||||
performCheck
|
||||
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
|
||||
TESTNAME="Test analyze CBC IV (no-key)"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : supercom587
|
||||
Best driver : 00/00
|
||||
Using driver : 00/00
|
||||
000 : ae length (174 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : ee4d dll-mfct (SON)
|
||||
004 : 77777777 dll-id (77777777)
|
||||
008 : 3c dll-version
|
||||
009 : 07 dll-type (Water meter)
|
||||
010 : 7a tpl-ci-field (EN 13757-3 Application Layer (short tplh))
|
||||
011 : 44 tpl-acc-field
|
||||
012 : 00 tpl-sts-field (OK)
|
||||
013 : a025 tpl-cfg 25a0 (synchronous AES_CBC_IV nb=10 cntn=0 ra=0 hc=0 )
|
||||
015 CE: E78F4A01F9DCA029EDA03BA452686E8FA917507B29E5358B52D77C111EA4C41140290523F3F6B9F9261705E041C0CA41305004605F42D6C9464E5A04EEE227510BD0DC0983C665C3A5E4739C2082975476AC637BCDD39766AEF030502B6A7697BE9E1C49AF535C15470FCF8ADA36CAB9D0B2A1A8690F8DDCF70859F18B3414D8315B311A0AFA57325531587CB7E9CC110E807F24C190D7E635BEDAF4CAE8A161 encrypted
|
||||
|
||||
{
|
||||
"media":"water",
|
||||
"meter":"unknown",
|
||||
"name":"",
|
||||
"id":"77777777",
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
||||
$PROG --analyze AE44EE4D777777773C077A4400A025E78F4A01F9DCA029EDA03BA452686E8FA917507B29E5358B52D77C111EA4C41140290523F3F6B9F9261705E041C0CA41305004605F42D6C9464E5A04EEE227510BD0DC0983C665C3A5E4739C2082975476AC637BCDD39766AEF030502B6A7697BE9E1C49AF535C15470FCF8ADA36CAB9D0B2A1A8690F8DDCF70859F18B3414D8315B311A0AFA57325531587CB7E9CC110E807F24C190D7E635BEDAF4CAE8A161 > $TEST/test_output.txt 2>&1
|
||||
|
||||
performCheck
|
||||
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
########################################################################################################################
|
||||
|
||||
#TESTNAME="Test analyze CBC IV (with-key)"
|
||||
#TESTRESULT="ERROR"
|
||||
|
||||
#cat > $TEST/test_expected.txt <<EOF
|
||||
#EOF
|
||||
|
||||
#$PROG --analyze=5065747220486F6C79737A6577736B69 AE44EE4D777777773C077A4400A025E78F4A01F9DCA029EDA03BA452686E8FA917507B29E5358B52D77C111EA4C41140290523F3F6B9F9261705E041C0CA41305004605F42D6C9464E5A04EEE227510BD0DC0983C665C3A5E4739C2082975476AC637BCDD39766AEF030502B6A7697BE9E1C49AF535C15470FCF8ADA36CAB9D0B2A1A8690F8DDCF70859F18B3414D8315B311A0AFA57325531587CB7E9CC110E807F24C190D7E635BEDAF4CAE8A161 > $TEST/test_output.txt 2>&1
|
||||
|
||||
#performCheck
|
||||
|
|
Ładowanie…
Reference in New Issue