From 89cf1bad80cca654da716d820307a031865608af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Sun, 12 Sep 2021 10:40:40 +0200 Subject: [PATCH] Parse and decode a hex string supplied as argument on command line. --- CHANGES | 5 +++ src/bus.cc | 10 ++++- src/cmdline.cc | 10 ++--- src/config.cc | 26 +++++++++---- src/config.h | 2 +- src/testinternals.cc | 22 +++++++++++ src/util.cc | 24 ++++++++++++ src/util.h | 2 + src/wmbus.cc | 37 +++++++++++++----- src/wmbus.h | 10 +++-- src/wmbus_simulator.cc | 18 ++++++--- test.sh | 3 ++ tests/test_hex_cmdline.sh | 80 +++++++++++++++++++++++++++++++++++++++ 13 files changed, 215 insertions(+), 34 deletions(-) create mode 100755 tests/test_hex_cmdline.sh diff --git a/CHANGES b/CHANGES index 9a5ff2e..d3ddf12 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,9 @@ +You can now decode a telegram just by supplying hex on the command line: +wmbusmeters --format=json MyMeter auto 12345678 NOKEY +or just +wmbusmeters + Added --listunits to list all quantities and the units for each quantity that wmbusmeteres knows about. diff --git a/src/bus.cc b/src/bus.cc index 9ef97e8..d9c787c 100644 --- a/src/bus.cc +++ b/src/bus.cc @@ -383,7 +383,7 @@ void BusManager::detectAndConfigureWmbusDevices(Configuration *config, Detection continue; } } - if (specified_device.file == "" && specified_device.command == "") + if (specified_device.file == "" && specified_device.command == "" && specified_device.hex == "") { // File/tty/command not specified, use auto scan later to find actual device file/tty. must_auto_find_ttys |= usesTTY(specified_device.type); @@ -404,6 +404,12 @@ void BusManager::detectAndConfigureWmbusDevices(Configuration *config, Detection specified_device.handled = true; openBusDeviceAndPotentiallySetLinkmodes(config, "config", &detected); } + if (specified_device.hex != "") + { + Detected detected = detectWMBusDeviceWithFileOrHex(specified_device, config->default_device_linkmodes, serial_manager_); + openBusDeviceAndPotentiallySetLinkmodes(config, "config", &detected); + specified_device.handled = true; + } if (specified_device.file != "") { shared_ptr sd = serial_manager_->lookup(specified_device.file); @@ -450,7 +456,7 @@ void BusManager::detectAndConfigureWmbusDevices(Configuration *config, Detection continue; } - Detected detected = detectWMBusDeviceWithFile(specified_device, config->default_device_linkmodes, serial_manager_); + Detected detected = detectWMBusDeviceWithFileOrHex(specified_device, config->default_device_linkmodes, serial_manager_); if (detected.found_type == DEVICE_UNKNOWN) { diff --git a/src/cmdline.cc b/src/cmdline.cc index 6f3cc58..2f072cc 100644 --- a/src/cmdline.cc +++ b/src/cmdline.cc @@ -539,19 +539,19 @@ shared_ptr parseCommandLine(int argc, char **argv) { error("Unknown option \"%s\"\n", argv[i]); } - bool found_at_least_one_device = false; + bool found_at_least_one_device_or_hex = false; while (argv[i]) { - bool ok = handleDevice(c, argv[i]); + bool ok = handleDeviceOrHex(c, argv[i]); if (ok) { - found_at_least_one_device = true; + found_at_least_one_device_or_hex = true; } else { - if (!found_at_least_one_device) + if (!found_at_least_one_device_or_hex) { - error("At least one valid device must be supplied!\n"); + error("At least one valid device (or hex) must be supplied!\n"); } // There are more arguments... break; diff --git a/src/config.cc b/src/config.cc index c7cc33d..7c3f4c9 100644 --- a/src/config.cc +++ b/src/config.cc @@ -241,13 +241,23 @@ void handleResetAfter(Configuration *c, string s) } } -bool handleDevice(Configuration *c, string devicefile) +bool handleDeviceOrHex(Configuration *c, string devicefilehex) { - SpecifiedDevice specified_device; - bool ok = specified_device.parse(devicefile); - if (!ok && SpecifiedDevice::isLikelyDevice(devicefile)) + bool invalid_hex = false; + bool is_hex = isHexString(devicefilehex, &invalid_hex); + if (is_hex) { - error("Not a valid device \"%s\"\n", devicefile.c_str()); + if (invalid_hex) + { + error("Hex string must have an even lenght of hexadecimal characters.\n"); + } + } + + SpecifiedDevice specified_device; + bool ok = specified_device.parse(devicefilehex); + if (!ok && SpecifiedDevice::isLikelyDevice(devicefilehex)) + { + error("Not a valid device \"%s\"\n", devicefilehex.c_str()); } if (!ok) return false; @@ -259,7 +269,7 @@ bool handleDevice(Configuration *c, string devicefile) { if (!specified_device.linkmodes.empty()) { - error("An mbus device must not have linkmode set. \"%s\"\n", devicefile.c_str()); + error("An mbus device must not have linkmode set. \"%s\"\n", devicefilehex.c_str()); } } @@ -594,7 +604,7 @@ shared_ptr loadConfiguration(string root, string device_override, if (p.first == "loglevel") handleLoglevel(c, p.second); else if (p.first == "internaltesting") handleInternalTesting(c, p.second); else if (p.first == "ignoreduplicates") handleIgnoreDuplicateTelegrams(c, p.second); - else if (p.first == "device") handleDevice(c, p.second); + else if (p.first == "device") handleDeviceOrHex(c, p.second); else if (p.first == "donotprobe") handleDoNotProbe(c, p.second); else if (p.first == "listento") handleListenTo(c, p.second); else if (p.first == "logtelegrams") handleLogtelegrams(c, p.second); @@ -653,7 +663,7 @@ shared_ptr loadConfiguration(string root, string device_override, device_override = "rtlwmbus"; } debug("(config) overriding device with \"%s\"\n", device_override.c_str()); - handleDevice(c, device_override); + handleDeviceOrHex(c, device_override); } if (listento_override != "") { diff --git a/src/config.h b/src/config.h index 9fa5504..013032d 100644 --- a/src/config.h +++ b/src/config.h @@ -125,7 +125,7 @@ void parseMeterConfig(Configuration *c, vector &buf, string file); void handleConversions(Configuration *c, string s); void handleSelectedFields(Configuration *c, string s); void handleAddedFields(Configuration *c, string s); -bool handleDevice(Configuration *c, string devicefile); +bool handleDeviceOrHex(Configuration *c, string devicefilehex); enum class LinkModeCalculationResultType { diff --git a/src/testinternals.cc b/src/testinternals.cc index 4b1d40c..02d0133 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -42,6 +42,7 @@ void test_meters(); void test_months(); void test_aes(); void test_sbc(); +void test_hex(); int main(int argc, char **argv) { @@ -71,6 +72,7 @@ int main(int argc, char **argv) test_months(); test_aes(); test_sbc(); + test_hex(); return 0; } @@ -982,3 +984,23 @@ void test_aes() printf("ERROR! aes encrypt decrypt (no iv) failed!\n"); } } + +void test_is_hex(const char *hex, bool expected_ok, bool expected_invalid) +{ + bool got_invalid; + bool got_ok = isHexString(hex, &got_invalid); + + if (got_ok != expected_ok || got_invalid != expected_invalid) + { + printf("ERROR! hex string %s was expected to be %d (invalid %d) but got %d (invalid %d)\n", + hex, + expected_ok, expected_invalid, got_ok, got_invalid); + } +} +void test_hex() +{ + test_is_hex("00112233445566778899aabbccddeeff", true, false); + test_is_hex("00112233445566778899AABBCCDDEEFF", true, false); + test_is_hex("00112233445566778899AABBCCDDEEF", true, true); + test_is_hex("00112233445566778899AABBCCDDEEFG", false, false); +} diff --git a/src/util.cc b/src/util.cc index 460f5a2..4a60cb6 100644 --- a/src/util.cc +++ b/src/util.cc @@ -158,6 +158,30 @@ uchar reverse(uchar c) return ((c&15)<<4) | (c>>4); } +bool isHexString(const string &txt, bool *invalid) +{ + return isHexString(txt.c_str(), invalid); +} + +bool isHexString(const char* txt, bool *invalid) +{ + *invalid = false; + // An empty string is not an hex string. + if (*txt == 0) return false; + + const char *i = txt; + int n = 0; + for (;;) + { + char c = *i++; + if (c == 0) break; + n++; + if (char2int(c) == -1) return false; + } + if (n%2 == 1) *invalid = true; + return true; +} + bool hex2bin(const char* src, vector *target) { if (!src) return false; diff --git a/src/util.h b/src/util.h index 7b2c303..24bc6fb 100644 --- a/src/util.h +++ b/src/util.h @@ -39,6 +39,8 @@ typedef unsigned char uchar; uchar bcd2bin(uchar c); uchar revbcd2bin(uchar c); uchar reverse(uchar c); +bool isHexString(const char* txt, bool *invalid); +bool isHexString(const std::string &txt, bool *invalid); bool hex2bin(const char* src, std::vector *target); bool hex2bin(std::string &src, std::vector *target); bool hex2bin(std::vector &src, std::vector *target); diff --git a/src/wmbus.cc b/src/wmbus.cc index 18d23a0..7d9a467 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -4392,9 +4392,9 @@ bool is_command(string b, string *cmd) return true; } -bool check_file(string f, bool *is_tty, bool *is_stdin, bool *is_file, bool *is_simulation) +bool check_file(string f, bool *is_tty, bool *is_stdin, bool *is_file, bool *is_simulation, bool *is_hex_simulation) { - *is_tty = *is_stdin = *is_file = *is_simulation = false; + *is_tty = *is_stdin = *is_file = *is_simulation = *is_hex_simulation = false; if (f == "stdin") { *is_stdin = true; @@ -4407,6 +4407,16 @@ bool check_file(string f, bool *is_tty, bool *is_stdin, bool *is_file, bool *is_ // a file will confuse the user to no end.... return false; } + // A hex string becomes a simulation file with a single line containing a telegram defined by the hex string. + bool invalid_hex = false; + if (isHexString(f.c_str(), &invalid_hex)) + { + *is_simulation = true; + *is_hex_simulation = true; + assert(!invalid_hex); + return true; + } + // A command line arguments simulation_xxyyzz.txt is detected as a simulation file. if (checkIfSimulationFile(f.c_str())) { *is_simulation = true; @@ -4526,6 +4536,7 @@ string SpecifiedDevice::str() r += bus_alias+"="; } if (file != "") r += file+":"; + if (hex != "") r += "<"+hex+">:"; if (type != WMBusDeviceType::DEVICE_UNKNOWN) { r += toString(type); @@ -4604,10 +4615,17 @@ bool SpecifiedDevice::parse(string &arg) // then the specified device string is faulty. return false; } - if (!file_checked && check_file(p, &is_tty, &is_stdin, &is_file, &is_simulation)) + if (!file_checked && check_file(p, &is_tty, &is_stdin, &is_file, &is_simulation, &is_hex_simulation)) { file_checked = true; - file = p; + if (!is_hex_simulation) + { + file = p; + } + else + { + hex = p; + } } else if (!typeidextras_checked && is_type_id_extras(p, &type, &id, &extras)) { @@ -4750,16 +4768,17 @@ Detected detectWMBusDeviceOnTTY(string tty, return detected; } -Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device, +Detected detectWMBusDeviceWithFileOrHex(SpecifiedDevice &specified_device, LinkModeSet default_linkmodes, shared_ptr handler) { - assert(specified_device.file != ""); + assert(specified_device.file != "" || specified_device.hex != ""); assert(specified_device.command == ""); - debug("(lookup) with file \"%s\"\n", specified_device.file.c_str()); + debug("(lookup) with file/hex \"%s%s\"\n", specified_device.file.c_str(), specified_device.hex.c_str()); Detected detected; detected.found_file = specified_device.file; + detected.found_hex = specified_device.hex; detected.setSpecifiedDevice(specified_device); // If :c1 is missing :c1 then use --c1. LinkModeSet lms = specified_device.linkmodes; @@ -4769,8 +4788,8 @@ Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device, } if (specified_device.is_simulation) { - debug("(lookup) driver: simulation file\n"); - // A simulation file has a lms of all by default, eg no simulation_foo.txt:t1 nor --t1 + debug("(lookup) driver: simulation %s\n", specified_device.hex != "" ? "hex" : "file"); + // A simulation file/hex has a lms of all by default, eg no simulation_foo.txt:t1 nor --t1 if (specified_device.linkmodes.empty()) lms.setAll(); detected.setAsFound("", DEVICE_SIMULATION, 0 , false, lms); return detected; diff --git a/src/wmbus.h b/src/wmbus.h index 1d13801..9650c00 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -157,7 +157,8 @@ struct SpecifiedDevice std::string bus_alias; // A bus alias, necessary for C2/T2 meters and mbus. int index; // 0,1,2,3 the order on the command line / config file. std::string file; // simulation_meter.txt, stdin, file.raw, /dev/ttyUSB0 - bool is_tty{}, is_stdin{}, is_file{}, is_simulation{}; + std::string hex; // a hex string supplied on the command line becomes a hex simulation. + bool is_tty{}, is_stdin{}, is_file{}, is_simulation{}, is_hex_simulation{}; WMBusDeviceType type; // im871a, rtlwmbus std::string id; // 12345678 for wmbus dongles or 0,1 for rtlwmbus indexes. std::string extras; // Extra device specific settings. @@ -180,6 +181,7 @@ struct Detected SpecifiedDevice specified_device {}; // Device as specified from the command line / config file. string found_file; // The device file to use. + string found_hex; // An immediate hex string is supplied. string found_device_id; // An "unique" identifier, typically the id used by the dongle as its own wmbus id, if it transmits. WMBusDeviceType found_type {}; // IM871A, AMB8465 etc. int found_bps {}; // Serial speed of tty. @@ -610,9 +612,9 @@ struct WMBus virtual ~WMBus() = 0; }; -Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device, - LinkModeSet default_linkmodes, - shared_ptr manager); +Detected detectWMBusDeviceWithFileOrHex(SpecifiedDevice &specified_device, + LinkModeSet default_linkmodes, + shared_ptr manager); Detected detectWMBusDeviceWithCommand(SpecifiedDevice &specified_device, LinkModeSet default_linkmodes, shared_ptr handler); diff --git a/src/wmbus_simulator.cc b/src/wmbus_simulator.cc index 3127090..4c770b7 100644 --- a/src/wmbus_simulator.cc +++ b/src/wmbus_simulator.cc @@ -47,7 +47,7 @@ struct WMBusSimulator : public WMBusCommonImplementation void simulate(); string device() { return file_; } - WMBusSimulator(string alias, string file, shared_ptr manager); + WMBusSimulator(string alias, string file, string hex, shared_ptr manager); private: vector received_payload_; @@ -62,15 +62,23 @@ shared_ptr openSimulator(Detected detected, shared_ptr(imp); } -WMBusSimulator::WMBusSimulator(string bus_alias, string file, shared_ptr manager) +WMBusSimulator::WMBusSimulator(string bus_alias, string file, string hex, shared_ptr manager) : WMBusCommonImplementation(bus_alias, DEVICE_SIMULATION, manager, NULL, false), file_(file) { - assert(file != ""); - loadFile(file, &lines_); + assert(file != "" || hex != ""); + if (hex != "") + { + lines_.push_back("telegram="+hex); + } + if (file != "") + { + loadFile(file, &lines_); + } } bool WMBusSimulator::ping() diff --git a/test.sh b/test.sh index 33859b4..b2bdc40 100755 --- a/test.sh +++ b/test.sh @@ -111,6 +111,9 @@ if [ "$?" != "0" ]; then RC="1"; fi ./tests/test_meter_extras.sh $PROG if [ "$?" != "0" ]; then RC="1"; fi +./tests/test_hex_cmdline.sh $PROG +if [ "$?" != "0" ]; then RC="1"; fi + if [ -x ../additional_tests.sh ] then (cd ..; ./additional_tests.sh $PROG) diff --git a/tests/test_hex_cmdline.sh b/tests/test_hex_cmdline.sh new file mode 100755 index 0000000..3dd7131 --- /dev/null +++ b/tests/test_hex_cmdline.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +PROG="$1" +TEST=testoutput +mkdir -p $TEST + +TESTNAME="Test hex on commandline" +TESTRESULT="ERROR" + +cat > $TEST/test_expected.txt < $TEST/test_output.txt 1>&2 + +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 + +cat > $TEST/test_expected.txt < $TEST/test_output.txt 2> $TEST/test_stderr.txt + +if [ "$?" = "0" ] +then + cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt + diff $TEST/test_expected.txt $TEST/test_responses.txt + if [ "$?" = "0" ] + then + echo OK: $TESTNAME + TESTRESULT="OK" + fi +else + echo "wmbusmeters returned error code: $?" + cat $TEST/test_output.txt + cat $TEST/test_stderr.txt +fi + +TESTNAME="Test invalid hex on commandline" + +cat > $TEST/test_expected.txt < $TEST/test_output.txt 1>&2 + +if [ "$?" = "1" ] +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 0 but we expected failure!" + echo "=======================" + cat $TEST/test_output.txt + echo "=======================" + cat $TEST/test_stderr.txt +fi