diff --git a/src/cmdline.cc b/src/cmdline.cc index 5b36ae0..a543e13 100644 --- a/src/cmdline.cc +++ b/src/cmdline.cc @@ -20,6 +20,7 @@ #include"util.h" #include +#include using namespace std; @@ -104,9 +105,27 @@ shared_ptr parseCommandLine(int argc, char **argv) { } if (!strcmp(argv[i], "--analyze")) { c->analyze = true; + if (isatty(1)) + { + c->analyze_format = OutputFormat::TERMINAL; + } + else + { + c->analyze_format = OutputFormat::PLAIN; + } 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; + i++; + continue; + } + if (!strcmp(argv[i], "--debug")) { c->debug = true; i++; diff --git a/src/config.h b/src/config.h index 7066e5f..3588976 100644 --- a/src/config.h +++ b/src/config.h @@ -59,6 +59,7 @@ struct Configuration bool version {}; bool license {}; bool analyze {}; + OutputFormat analyze_format {}; bool debug {}; bool trace {}; AddLogTimestamps addtimestamps {}; diff --git a/src/dvparser.cc b/src/dvparser.cc index 9d78c74..d86a787 100644 --- a/src/dvparser.cc +++ b/src/dvparser.cc @@ -144,14 +144,14 @@ bool parseDV(Telegram *t, string value = bin2hex(data+1, data_end, datalen-1); t->mfct_0f_index = 1+std::distance(data_start, data); assert(t->mfct_0f_index >= 0); - t->addExplanationAndIncrementPos(data, datalen, "%02X manufacturer specific data %s", dif, value.c_str()); + t->addExplanationAndIncrementPos(data, datalen, KindOfData::PROTOCOL, Understanding::NONE, "%02X manufacturer specific data %s", dif, value.c_str()); break; } debug("(dvparser) cannot handle dif %02X ignoring rest of telegram.\n", dif); break; } if (dif == 0x2f) { - t->addExplanationAndIncrementPos(*format, 1, "%02X skip", dif); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X skip", dif); DEBUG_PARSER("\n"); continue; } @@ -163,7 +163,7 @@ bool parseDV(Telegram *t, if (data_has_difvifs) { format_bytes.push_back(dif); id_bytes.push_back(dif); - t->addExplanationAndIncrementPos(*format, 1, "%02X dif (%s)", dif, difType(dif).c_str()); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X dif (%s)", dif, difType(dif).c_str()); } else { id_bytes.push_back(**format); (*format)++; @@ -193,8 +193,9 @@ bool parseDV(Telegram *t, if (data_has_difvifs) { format_bytes.push_back(dife); id_bytes.push_back(dife); - t->addExplanationAndIncrementPos(*format, 1, "%02X dife (subunit=%d tariff=%d storagenr=%d)", - dife, subunit, tariff, storage_nr); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02X dife (subunit=%d tariff=%d storagenr=%d)", + dife, subunit, tariff, storage_nr); } else { id_bytes.push_back(**format); (*format)++; @@ -211,7 +212,8 @@ bool parseDV(Telegram *t, if (data_has_difvifs) { format_bytes.push_back(vif); id_bytes.push_back(vif); - t->addExplanationAndIncrementPos(*format, 1, "%02X vif (%s)", vif, vifType(vif).c_str()); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02X vif (%s)", vif, vifType(vif).c_str()); } else { id_bytes.push_back(**format); (*format)++; @@ -224,13 +226,15 @@ bool parseDV(Telegram *t, DEBUG_PARSER("(dvparser debug) variable length vif found\n"); if (*format == format_end) { debug("(dvparser) warning: unexpected end of data (vif varlen expected)\n"); break; } uchar viflen = **format; - t->addExplanationAndIncrementPos(*format, 1, "%02X viflen (%d)", viflen, viflen); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02X viflen (%d)", viflen, viflen); for (uchar i = 0; i < viflen; ++i) { if (*format == format_end) { debug("(dvparser) warning: unexpected end of data (vif varlen byte %d/%d expected)\n", i+1, viflen); break; } uchar v = **format; - t->addExplanationAndIncrementPos(*format, 1, "%02X vif (%c)", v, v); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02X vif (%c)", v, v); id_bytes.push_back(v); } } @@ -243,7 +247,8 @@ bool parseDV(Telegram *t, if (data_has_difvifs) { format_bytes.push_back(vife); id_bytes.push_back(vife); - t->addExplanationAndIncrementPos(*format, 1, "%02X vife (%s)", vife, vifeType(dif, vif, vife).c_str()); + t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02X vife (%s)", vife, vifeType(dif, vif, vife).c_str()); } else { id_bytes.push_back(**format); (*format)++; @@ -281,14 +286,14 @@ bool parseDV(Telegram *t, // Skip the length byte in the variable length data. if (variable_length) { - t->addExplanationAndIncrementPos(data, 1, "%02X varlen=%d", *(data+0), datalen); + t->addExplanationAndIncrementPos(data, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X varlen=%d", *(data+0), datalen); } string value = bin2hex(data, data_end, datalen); int offset = start_parse_here+data-data_start; (*values)[key] = { offset, DVEntry(mt, vif&0x7f, storage_nr, tariff, subunit, value) }; if (value.length() > 0) { // This call increments data with datalen. - t->addExplanationAndIncrementPos(data, datalen, "%s", value.c_str()); + t->addExplanationAndIncrementPos(data, datalen, KindOfData::CONTENT, Understanding::NONE, "%s", value.c_str()); DEBUG_PARSER("(dvparser debug) data \"%s\"\n\n", value.c_str()); } if (remaining == datalen || data == databytes.end()) { diff --git a/src/main.cc b/src/main.cc index 0f5066e..9e2728e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -451,7 +451,7 @@ 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); + meter_manager_->analyzeEnabled(config->analyze, config->analyze_format); // The bus manager detects new/lost wmbus devices and // configures the devices according to the specification. diff --git a/src/meter_apator162.cc b/src/meter_apator162.cc index 8f521a0..b75abca 100644 --- a/src/meter_apator162.cc +++ b/src/meter_apator162.cc @@ -109,9 +109,13 @@ void MeterApator162::processContent(Telegram *t) vector frame; t->extractFrame(&frame); string hex = bin2hex(frame); - warning("(apator162) telegram contains a register (%02x) with unknown size.\n" - "Please open an issue at https://github.com/weetmuts/wmbusmeters/\n" - "and report this telegram: %s\n", c, hex.c_str()); + + if (t->beingAnalyzed() == false) + { + warning("(apator162) telegram contains a register (%02x) with unknown size.\n" + "Please open an issue at https://github.com/weetmuts/wmbusmeters/\n" + "and report this telegram: %s\n", c, hex.c_str()); + } break; } if (c == 0x10 && size == 4 && i+size < content.size()) @@ -123,13 +127,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, total.c_str(), total_water_consumption_m3_); + t->addSpecialExplanation(offset, 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, msg.c_str()); + t->addSpecialExplanation(i-1+t->header_size, KindOfData::CONTENT, Understanding::NONE, msg.c_str()); } i += size; } diff --git a/src/meter_compact5.cc b/src/meter_compact5.cc index 08c8745..78f2025 100644 --- a/src/meter_compact5.cc +++ b/src/meter_compact5.cc @@ -106,7 +106,8 @@ void MeterCompact5::processContent(Telegram *t) strprintf(prevs, "%02x%02x", prev_lo, prev_hi); int offset = t->parsed.size()+3; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, prevs) }; - t->explanations.push_back({ offset, prevs }); + Explanation pe(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL); + t->explanations.push_back(pe); t->addMoreExplanation(offset, " energy used in previous billing period (%f KWH)", prev); uchar curr_lo = content[7]; @@ -117,7 +118,8 @@ void MeterCompact5::processContent(Telegram *t) strprintf(currs, "%02x%02x", curr_lo, curr_hi); offset = t->parsed.size()+7; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, currs) }; - t->explanations.push_back({ offset, currs }); + Explanation ce(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL); + t->explanations.push_back(ce); t->addMoreExplanation(offset, " energy used in current billing period (%f KWH)", curr); total_energy_kwh_ = prev+curr; diff --git a/src/meter_fhkvdataiii.cc b/src/meter_fhkvdataiii.cc index bd0369f..b22ab98 100644 --- a/src/meter_fhkvdataiii.cc +++ b/src/meter_fhkvdataiii.cc @@ -134,6 +134,12 @@ void MeterFHKVDataIII::processContent(Telegram *t) t->extractPayload(&content); + if (content.size() < 14) + { + // Not enough data. + debugPayload("(fhkvdataiii) not enough data", content); + return; + } // Consumption // Previous Consumption uchar prev_lo = content[3]; diff --git a/src/meter_izar.cc b/src/meter_izar.cc index 07b28a8..2970b33 100644 --- a/src/meter_izar.cc +++ b/src/meter_izar.cc @@ -241,7 +241,10 @@ void MeterIzar::processContent(Telegram *t) if (decoded_content.empty()) { - warning("(izar) Decoding PRIOS data failed. Ignoring telegram.\n"); + if (t->beingAnalyzed() == false) + { + warning("(izar) Decoding PRIOS data failed. Ignoring telegram.\n"); + } return; } diff --git a/src/meter_izar3.cc b/src/meter_izar3.cc index 8fc7089..94c49f5 100644 --- a/src/meter_izar3.cc +++ b/src/meter_izar3.cc @@ -70,6 +70,9 @@ void MeterIzar3::processContent(Telegram *t) vector frame; t->extractFrame(&frame); - warning("(izar3) cannot decode content of telegram!\n"); + if (t->beingAnalyzed() == false) + { + warning("(izar3) cannot decode content of telegram!\n"); + } total_water_consumption_l_ = 123456789; } diff --git a/src/meter_lansendw.cc b/src/meter_lansendw.cc index d9c6f79..169cd48 100644 --- a/src/meter_lansendw.cc +++ b/src/meter_lansendw.cc @@ -108,8 +108,10 @@ void MeterLansenDW::processContent(Telegram *t) */ int offset; - extractDVuint16(&t->values, "02FD1B", &offset, &info_codes_); - t->addMoreExplanation(offset, " info codes (%s)", status().c_str()); + if (extractDVuint16(&t->values, "02FD1B", &offset, &info_codes_)) + { + t->addMoreExplanation(offset, " info codes (%s)", status().c_str()); + } } string MeterLansenDW::status() diff --git a/src/meter_mkradio3.cc b/src/meter_mkradio3.cc index b27bb82..f19f552 100644 --- a/src/meter_mkradio3.cc +++ b/src/meter_mkradio3.cc @@ -99,7 +99,7 @@ void MKRadio3::processContent(Telegram *t) strprintf(prev_date_str, "%04x", prev_date); uint offset = t->parsed.size() + 1; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Unknown, 0x6c, 0, 0, 0, prev_date_str) }; - t->explanations.push_back({ offset, prev_date_str }); + t->explanations.push_back(Explanation(offset, 1, prev_date_str, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " previous date (%s)", previous_date_.c_str()); // Previous consumption @@ -111,7 +111,7 @@ void MKRadio3::processContent(Telegram *t) strprintf(prevs, "%02x%02x", prev_lo, prev_hi); offset = t->parsed.size()+3; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, prevs) }; - t->explanations.push_back({ offset, prevs }); + t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " prev consumption (%f m3)", prev); // Current date @@ -124,7 +124,7 @@ void MKRadio3::processContent(Telegram *t) strprintf(current_date_str, "%04x", current_date); offset = t->parsed.size() + 5; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Unknown, 0x6c, 0, 0, 0, current_date_str) }; - t->explanations.push_back({ offset, current_date_str }); + t->explanations.push_back(Explanation(offset, 1, current_date_str, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " current date (%s)", current_date_.c_str()); // Current consumption @@ -136,7 +136,7 @@ void MKRadio3::processContent(Telegram *t) strprintf(currs, "%02x%02x", curr_lo, curr_hi); offset = t->parsed.size()+7; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, currs) }; - t->explanations.push_back({ offset, currs }); + t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " curr consumption (%f m3)", curr); total_water_consumption_m3_ = prev+curr; diff --git a/src/meter_mkradio4.cc b/src/meter_mkradio4.cc index ca4e62c..4107845 100644 --- a/src/meter_mkradio4.cc +++ b/src/meter_mkradio4.cc @@ -82,7 +82,7 @@ void MKRadio4::processContent(Telegram *t) strprintf(prevs, "%02x%02x", prev_lo, prev_hi); int offset = t->parsed.size()+3; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, prevs) }; - t->explanations.push_back({ offset, prevs }); + t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " prev consumption (%f m3)", prev); uchar curr_lo = content[7]; @@ -93,7 +93,7 @@ void MKRadio4::processContent(Telegram *t) strprintf(currs, "%02x%02x", curr_lo, curr_hi); offset = t->parsed.size()+7; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, currs) }; - t->explanations.push_back({ offset, currs }); + t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " curr consumption (%f m3)", curr); total_water_consumption_m3_ = prev+curr; diff --git a/src/meter_tsd2.cc b/src/meter_tsd2.cc index b891290..e117bc3 100644 --- a/src/meter_tsd2.cc +++ b/src/meter_tsd2.cc @@ -95,7 +95,7 @@ void MeterTSD2::processContent(Telegram *t) string prev_date_str; strprintf(prev_date_str, "%04x", prev_date); uint offset = t->parsed.size() + 1; - t->explanations.push_back({ offset, prev_date_str }); + t->explanations.push_back(Explanation(offset, 1, prev_date_str, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " previous date (%s)", previous_date_.c_str()); } diff --git a/src/meter_vario451.cc b/src/meter_vario451.cc index 190bdd6..f025950 100644 --- a/src/meter_vario451.cc +++ b/src/meter_vario451.cc @@ -106,7 +106,7 @@ void MeterVario451::processContent(Telegram *t) strprintf(prevs, "%02x%02x", prev_lo, prev_hi); int offset = t->parsed.size()+3; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, prevs) }; - t->explanations.push_back({ offset, prevs }); + t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " energy used in previous billing period (%f GJ)", prev); uchar curr_lo = content[7]; @@ -117,7 +117,7 @@ void MeterVario451::processContent(Telegram *t) strprintf(currs, "%02x%02x", curr_lo, curr_hi); offset = t->parsed.size()+7; vendor_values["0215"] = { offset, DVEntry(MeasurementType::Instantaneous, 0x15, 0, 0, 0, currs) }; - t->explanations.push_back({ offset, currs }); + t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " energy used in current billing period (%f GJ)", curr); total_energy_gj_ = prev+curr; diff --git a/src/meters.cc b/src/meters.cc index cf66c80..7d32001 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -34,6 +34,7 @@ struct MeterManagerImplementation : public virtual MeterManager private: bool is_daemon_ {}; bool should_analyze_ {}; + OutputFormat analyze_format_ {}; vector meter_templates_; vector> meters_; function)> on_telegram_; @@ -179,7 +180,11 @@ public: tmp.driver = pickMeterDriver(&t); if (tmp.driver == MeterDriver::UNKNOWN) { - warnForUnknownDriver(mi.name, &t); + if (should_analyze_ == false) + { + // We are not analyzing, so warn here. + warnForUnknownDriver(mi.name, &t); + } } } // Now build a meter object with for this exact id. @@ -260,18 +265,109 @@ public: } } - void analyzeEnabled(bool b) + void analyzeEnabled(bool b, OutputFormat f) { should_analyze_ = b; + analyze_format_ = f; } void analyzeTelegram(AboutTelegram &about, vector &input_frame, bool simulated) { Telegram t; t.about = about; + bool ok = t.parseHeader(input_frame); + if (simulated) t.markAsSimulated(); t.markAsBeingAnalyzed(); - printf("Analyzing...\n"); + if (!ok) + { + printf("Could not even analyze header, giving up.\n"); + return; + } + + vector 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]; + } + } + + // Overwrite the id with the id from the telegram to be analyzed. + mi.ids.clear(); + mi.ids.push_back(t.ids.back()); + mi.idsc = t.ids.back(); + + bool hide_output = drivers.size() > 1; + bool handled = false; + MeterDriver best_driver {}; + // For the best driver we have: + int best_content_length = 0; + int best_understood_content_length = 0; + + + for (MeterDriver dr : drivers) + { + if (dr == MeterDriver::AUTO) continue; + if (dr == MeterDriver::UNKNOWN) continue; + string s = toString(dr); + debug("Testing driver %s...\n", s.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) + { + + } + 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. + warning("(meter) newly created meter (%s %s %s) did not handle telegram!\n", + meter->name().c_str(), meter->idsc().c_str(), toString(meter->driver()).c_str()); + } + else + { + handled = true; + int l = 0; + int u = 0; + OutputFormat of = analyze_format_; + if (hide_output) of = OutputFormat::NONE; + t.analyzeParse(of, &l, &u); + 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; + } + } + } + if (handled) + { + string s = toString(best_driver); + printf("Best driver %s %d/%d\n", s.c_str(), best_understood_content_length, best_content_length); + } + else + { + printf("No suitable driver found.\n"); + } } MeterManagerImplementation(bool daemon) : is_daemon_(daemon) {} @@ -533,22 +629,25 @@ bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, Me if (isVerboseEnabled() || isDebugEnabled() || !warned_for_telegram_before(t, t->dll_a)) { string possible_drivers = t->autoDetectPossibleDrivers(); - warning("(meter) %s: meter detection did not match the selected driver %s! correct driver is: %s\n" - "(meter) Not printing this warning again for id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x\n", - name.c_str(), - toString(driver).c_str(), - possible_drivers.c_str(), - t->dll_id_b[3], t->dll_id_b[2], t->dll_id_b[1], t->dll_id_b[0], - manufacturerFlag(t->dll_mfct).c_str(), - manufacturer(t->dll_mfct).c_str(), - t->dll_mfct, - mediaType(t->dll_type, t->dll_mfct).c_str(), t->dll_type, - t->dll_version); - - if (possible_drivers == "unknown!") + if (t->beingAnalyzed() == false) { - warning("(meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/\n"); - warning("(meter) to add support for this unknown mfct,media,version combination\n"); + warning("(meter) %s: meter detection did not match the selected driver %s! correct driver is: %s\n" + "(meter) Not printing this warning again for id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x\n", + name.c_str(), + toString(driver).c_str(), + possible_drivers.c_str(), + t->dll_id_b[3], t->dll_id_b[2], t->dll_id_b[1], t->dll_id_b[0], + manufacturerFlag(t->dll_mfct).c_str(), + manufacturer(t->dll_mfct).c_str(), + t->dll_mfct, + mediaType(t->dll_type, t->dll_mfct).c_str(), t->dll_type, + t->dll_version); + + if (possible_drivers == "unknown!") + { + warning("(meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/\n"); + warning("(meter) to add support for this unknown mfct,media,version combination\n"); + } } } } @@ -796,13 +895,15 @@ string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector return buf; } -bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector input_frame, bool simulated, string *ids, bool *id_match) +bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector input_frame, + bool simulated, string *ids, bool *id_match, Telegram *out_analyzed) { Telegram t; t.about = about; bool ok = t.parseHeader(input_frame); if (simulated) t.markAsSimulated(); + if (out_analyzed != NULL) t.markAsBeingAnalyzed(); *ids = t.idsc; @@ -843,6 +944,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector input_frame, bool simulated, string *id, bool *id_match) = 0; + virtual bool handleTelegram(AboutTelegram &about, vector input_frame, + bool simulated, string *id, bool *id_match, Telegram *out_t = NULL) = 0; virtual MeterKeys *meterKeys() = 0; // Dynamically access all data received for the meter. @@ -270,7 +271,7 @@ struct MeterManager virtual void onTelegram(function)> cb) = 0; virtual void whenMeterUpdated(std::function cb) = 0; virtual void pollMeters(shared_ptr bus) = 0; - virtual void analyzeEnabled(bool b) = 0; + virtual void analyzeEnabled(bool b, OutputFormat f) = 0; virtual void analyzeTelegram(AboutTelegram &about, vector &input_frame, bool simulated) = 0; virtual ~MeterManager() = default; diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index b021926..d2f4dee 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -82,7 +82,8 @@ protected: // The default implementation of poll does nothing. // Override for mbus meters that need to be queried and likewise for C2/T2 wmbus-meters. void poll(shared_ptr bus); - bool handleTelegram(AboutTelegram &about, vector frame, bool simulated, string *id, bool *id_match); + bool handleTelegram(AboutTelegram &about, vector frame, + bool simulated, string *id, bool *id_match, Telegram *out_analyzed = NULL); void printMeter(Telegram *t, string *human_readable, string *fields, char separator, diff --git a/src/util.h b/src/util.h index 5f49712..7e569a1 100644 --- a/src/util.h +++ b/src/util.h @@ -228,6 +228,11 @@ void checkIfMultipleWmbusMetersRunning(); size_t findBytes(std::vector &v, uchar a, uchar b, uchar c); +enum class OutputFormat +{ + NONE, PLAIN, TERMINAL, JSON +}; + #ifndef FUZZING #define FUZZING false #endif diff --git a/src/wmbus.cc b/src/wmbus.cc index c9a9380..2a03ebe 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -739,7 +739,7 @@ string ciType(int ci_field) return "?"; } -void Telegram::addExplanationAndIncrementPos(vector::iterator &pos, int len, const char* fmt, ...) +void Telegram::addExplanationAndIncrementPos(vector::iterator &pos, int len, KindOfData k, Understanding u, const char* fmt, ...) { char buf[1024]; buf[1023] = 0; @@ -749,7 +749,9 @@ void Telegram::addExplanationAndIncrementPos(vector::iterator &pos, int l vsnprintf(buf, 1023, fmt, args); va_end(args); - explanations.push_back({parsed.size(), buf}); + + Explanation e(parsed.size(), len, buf, k, u); + explanations.push_back(e); parsed.insert(parsed.end(), pos, pos+len); pos += len; } @@ -767,11 +769,12 @@ void Telegram::addMoreExplanation(int pos, const char* fmt, ...) bool found = false; for (auto& p : explanations) { - if (p.first == pos) { - if (p.second[0] == '*') { - debug("(wmbus) warning: already added more explanations to offset %d!\n"); - } - p.second = string("* ")+p.second+buf; + if (p.pos == pos) + { + // Append more information. + p.info = p.info+buf; + // Since we are adding more information, we assume that we have a full understanding. + p.understanding = Understanding::FULL; found = true; } } @@ -781,7 +784,7 @@ void Telegram::addMoreExplanation(int pos, const char* fmt, ...) } } -void Telegram::addSpecialExplanation(int offset, const char* fmt, ...) +void Telegram::addSpecialExplanation(int offset, KindOfData k, Understanding u, const char* fmt, ...) { char buf[1024]; buf[1023] = 0; @@ -791,7 +794,7 @@ void Telegram::addSpecialExplanation(int offset, const char* fmt, ...) vsnprintf(buf, 1023, fmt, args); va_end(args); - explanations.push_back({offset, buf}); + explanations.push_back({offset, 1, buf, k, u}); } bool expectedMore(int line) @@ -808,13 +811,13 @@ bool Telegram::parseMBusDLL(vector::iterator &pos) debug("(wmbus) parse MBUS DLL @%d %d\n", distance(frame.begin(), pos), remaining); dll_len = *pos; if (remaining < dll_len) return expectedMore(__LINE__); - addExplanationAndIncrementPos(pos, 1, "%02x length (%d bytes)", dll_len, dll_len); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x length (%d bytes)", dll_len, dll_len); dll_c = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x dll-c (%s)", dll_c, mbusCField(dll_c).c_str()); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-c (%s)", dll_c, mbusCField(dll_c).c_str()); mbus_primary_address = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x dll-a primary (%d)", mbus_primary_address, mbus_primary_address); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-a primary (%d)", mbus_primary_address, mbus_primary_address); // Add dll_id to ids. string id = tostrprintf("%02x", dll_a[0]); @@ -832,16 +835,16 @@ bool Telegram::parseDLL(vector::iterator &pos) debug("(wmbus) parseDLL @%d %d\n", distance(frame.begin(), pos), remaining); dll_len = *pos; if (remaining < dll_len) return expectedMore(__LINE__); - addExplanationAndIncrementPos(pos, 1, "%02x length (%d bytes)", dll_len, dll_len); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x length (%d bytes)", dll_len, dll_len); dll_c = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x dll-c (%s)", dll_c, cType(dll_c).c_str()); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-c (%s)", dll_c, cType(dll_c).c_str()); dll_mfct_b[0] = *(pos+0); dll_mfct_b[1] = *(pos+1); dll_mfct = dll_mfct_b[1] <<8 | dll_mfct_b[0]; string man = manufacturerFlag(dll_mfct); - addExplanationAndIncrementPos(pos, 2, "%02x%02x dll-mfct (%s)", + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x dll-mfct (%s)", dll_mfct_b[0], dll_mfct_b[1], man.c_str()); dll_a.resize(6); @@ -859,13 +862,13 @@ bool Telegram::parseDLL(vector::iterator &pos) string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); ids.push_back(id); idsc = id; - addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x dll-id (%s)", + addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x dll-id (%s)", *(pos+0), *(pos+1), *(pos+2), *(pos+3), ids.back().c_str()); dll_version = *(pos+0); dll_type = *(pos+1); - addExplanationAndIncrementPos(pos, 1, "%02x dll-version", dll_version); - addExplanationAndIncrementPos(pos, 1, "%02x dll-type (%s)", dll_type, + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-version", dll_version); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-type (%s)", dll_type, mediaType(dll_type, dll_mfct).c_str()); return true; @@ -894,7 +897,7 @@ bool Telegram::parseELL(vector::iterator &pos) debug("(wmbus) parseELL @%d %d\n", distance(frame.begin(), pos), remaining); int ci_field = *pos; if (!isCiFieldOfType(ci_field, CI_TYPE::ELL)) return true; - addExplanationAndIncrementPos(pos, 1, "%02x ell-ci-field (%s)", + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x ell-ci-field (%s)", ci_field, ciType(ci_field).c_str()); ell_ci = ci_field; int len = ciFieldLength(ell_ci); @@ -904,10 +907,10 @@ bool Telegram::parseELL(vector::iterator &pos) // All ELL:s (including ELL I) start with cc,acc. ell_cc = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x ell-cc (%s)", ell_cc, ccType(ell_cc).c_str()); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x ell-cc (%s)", ell_cc, ccType(ell_cc).c_str()); ell_acc = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x ell-acc", ell_acc); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x ell-acc", ell_acc); bool has_target_mft_address = false; bool has_session_number_pl_crc = false; @@ -938,7 +941,7 @@ bool Telegram::parseELL(vector::iterator &pos) ell_mfct_b[1] = *(pos+1); ell_mfct = ell_mfct_b[1] << 8 | ell_mfct_b[0]; string man = manufacturerFlag(ell_mfct); - addExplanationAndIncrementPos(pos, 2, "%02x%02x ell-mfct (%s)", + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x ell-mfct (%s)", ell_mfct_b[0], ell_mfct_b[1], man.c_str()); ell_id_found = true; @@ -951,14 +954,14 @@ bool Telegram::parseELL(vector::iterator &pos) string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); ids.push_back(id); idsc = idsc+","+id; - addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x ell-id", + addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x ell-id", ell_id_b[0], ell_id_b[1], ell_id_b[2], ell_id_b[3]); ell_version = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x ell-version", ell_version); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x ell-version", ell_version); ell_type = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x ell-type"); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x ell-type"); } if (has_session_number_pl_crc) @@ -975,7 +978,7 @@ bool Telegram::parseELL(vector::iterator &pos) ell_sn_sec = (ell_sn >> 29) & 0x7; // next 3 bits. ell_sec_mode = fromIntToELLSecurityMode(ell_sn_sec); string info = toString(ell_sec_mode); - addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x sn (%s)", + addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x sn (%s)", ell_sn_b[0], ell_sn_b[1], ell_sn_b[2], ell_sn_b[3], info.c_str()); if (ell_sec_mode == ELLSecurityMode::AES_CTR) @@ -996,7 +999,7 @@ bool Telegram::parseELL(vector::iterator &pos) int len = distance(pos+2, frame.end()); uint16_t check = crc16_EN13757(&(frame[dist]), len); - addExplanationAndIncrementPos(pos, 2, "%02x%02x payload crc (calculated %02x%02x %s)", + 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")); @@ -1037,7 +1040,7 @@ bool Telegram::parseNWL(vector::iterator &pos) debug("(wmbus) parseNWL @%d %d\n", distance(frame.begin(), pos), remaining); int ci_field = *pos; if (!isCiFieldOfType(ci_field, CI_TYPE::NWL)) return true; - addExplanationAndIncrementPos(pos, 1, "%02x nwl-ci-field (%s)", + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x nwl-ci-field (%s)", ci_field, ciType(ci_field).c_str()); nwl_ci = ci_field; // We have only seen 0x81 0x1d so far. @@ -1046,7 +1049,7 @@ bool Telegram::parseNWL(vector::iterator &pos) if (remaining < len+1) return expectedMore(__LINE__); uchar nwl = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x nwl?", nwl); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x nwl?", nwl); return true; } @@ -1063,12 +1066,12 @@ bool Telegram::parseAFL(vector::iterator &pos) int ci_field = *pos; if (!isCiFieldOfType(ci_field, CI_TYPE::AFL)) return true; - addExplanationAndIncrementPos(pos, 1, "%02x afl-ci-field (%s)", + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x afl-ci-field (%s)", ci_field, ciType(ci_field).c_str()); afl_ci = ci_field; afl_len = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x afl-len (%d)", + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x afl-len (%d)", afl_len, afl_len); int len = ciFieldLength(afl_ci); @@ -1078,7 +1081,7 @@ bool Telegram::parseAFL(vector::iterator &pos) afl_fc_b[1] = *(pos+1); afl_fc = afl_fc_b[1] << 8 | afl_fc_b[0]; string afl_fc_info = toStringFromAFLFC(afl_fc); - addExplanationAndIncrementPos(pos, 2, "%02x%02x afl-fc (%s)", + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x afl-fc (%s)", afl_fc_b[0], afl_fc_b[1], afl_fc_info.c_str()); bool has_key_info = afl_fc & 0x0200; @@ -1092,7 +1095,7 @@ bool Telegram::parseAFL(vector::iterator &pos) { afl_mcl = *pos; string afl_mcl_info = toStringFromAFLMC(afl_mcl); - addExplanationAndIncrementPos(pos, 1, "%02x afl-mcl (%s)", + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x afl-mcl (%s)", afl_mcl, afl_mcl_info.c_str()); } @@ -1102,7 +1105,7 @@ bool Telegram::parseAFL(vector::iterator &pos) afl_ki_b[1] = *(pos+1); afl_ki = afl_ki_b[1] << 8 | afl_ki_b[0]; string afl_ki_info = ""; - addExplanationAndIncrementPos(pos, 2, "%02x%02x afl-ki (%s)", + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x afl-ki (%s)", afl_ki_b[0], afl_ki_b[1], afl_ki_info.c_str()); } @@ -1117,7 +1120,7 @@ bool Telegram::parseAFL(vector::iterator &pos) afl_counter_b[1] << 8 | afl_counter_b[0]; - addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x afl-counter (%u)", + addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x afl-counter (%u)", afl_counter_b[0],afl_counter_b[1], afl_counter_b[2],afl_counter_b[3], afl_counter); @@ -1143,7 +1146,7 @@ bool Telegram::parseAFL(vector::iterator &pos) afl_mac_b.insert(afl_mac_b.end(), *(pos+i)); } string s = bin2hex(afl_mac_b); - addExplanationAndIncrementPos(pos, len, "%s afl-mac %d bytes", s.c_str(), len); + addExplanationAndIncrementPos(pos, len, KindOfData::PROTOCOL, Understanding::FULL, "%s afl-mac %d bytes", s.c_str(), len); must_check_mac = true; } @@ -1234,7 +1237,8 @@ bool Telegram::parseTPLConfig(std::vector::iterator &pos) tpl_num_encr_blocks = (tpl_cfg >> 4) & 0x0f; has_cfg_ext = true; } - addExplanationAndIncrementPos(pos, 2, "%02x%02x tpl-cfg %04x (%s)", cfg1, cfg2, tpl_cfg, info.c_str()); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x tpl-cfg %04x (%s)", cfg1, cfg2, tpl_cfg, info.c_str()); if (has_cfg_ext) { @@ -1242,7 +1246,8 @@ bool Telegram::parseTPLConfig(std::vector::iterator &pos) tpl_cfg_ext = *(pos+0); tpl_kdf_selection = (tpl_cfg_ext >> 4) & 3; - addExplanationAndIncrementPos(pos, 1, "%02x tpl-cfg-ext (KDFS=%d)", tpl_cfg_ext, tpl_kdf_selection); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02x tpl-cfg-ext (KDFS=%d)", tpl_cfg_ext, tpl_kdf_selection); if (tpl_kdf_selection == 1) { @@ -1309,11 +1314,13 @@ bool Telegram::parseShortTPL(std::vector::iterator &pos) CHECK(1); tpl_acc = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x tpl-acc-field", tpl_acc); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02x tpl-acc-field", tpl_acc); CHECK(1); tpl_sts = *pos; - addExplanationAndIncrementPos(pos, 1, "%02x tpl-sts-field (%s)", tpl_sts, decodeTPLStatusByte(tpl_sts, NULL).c_str()); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02x tpl-sts-field (%s)", tpl_sts, decodeTPLStatusByte(tpl_sts, NULL).c_str()); bool ok = parseTPLConfig(pos); if (!ok) return false; @@ -1340,7 +1347,9 @@ bool Telegram::parseLongTPL(std::vector::iterator &pos) string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); ids.push_back(id); idsc = idsc+","+id; - addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x tpl-id (%02x%02x%02x%02x)", tpl_id_b[0], tpl_id_b[1], tpl_id_b[2], tpl_id_b[3], + addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x%02x%02x tpl-id (%02x%02x%02x%02x)", + tpl_id_b[0], tpl_id_b[1], tpl_id_b[2], tpl_id_b[3], tpl_id_b[3], tpl_id_b[2], tpl_id_b[1], tpl_id_b[0]); CHECK(2); @@ -1348,18 +1357,18 @@ bool Telegram::parseLongTPL(std::vector::iterator &pos) tpl_mfct_b[1] = *(pos+1); tpl_mfct = tpl_mfct_b[1] << 8 | tpl_mfct_b[0]; string man = manufacturerFlag(tpl_mfct); - addExplanationAndIncrementPos(pos, 2, "%02x%02x tpl-mfct (%s)", tpl_mfct_b[0], tpl_mfct_b[1], man.c_str()); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x tpl-mfct (%s)", tpl_mfct_b[0], tpl_mfct_b[1], man.c_str()); CHECK(1); tpl_version = *(pos+0); tpl_a[4] = *(pos+0); - addExplanationAndIncrementPos(pos, 1, "%02x tpl-version", tpl_version); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x tpl-version", tpl_version); CHECK(1); tpl_type = *(pos+0); tpl_a[5] = *(pos+0); string info = mediaType(tpl_type, tpl_mfct); - addExplanationAndIncrementPos(pos, 1, "%02x tpl-type (%s)", tpl_type, info.c_str()); + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x tpl-type (%s)", tpl_type, info.c_str()); bool ok = parseShortTPL(pos); @@ -1407,7 +1416,7 @@ bool loadFormatBytesFromSignature(uint16_t format_signature, vector *form bool Telegram::alreadyDecryptedCBC(vector::iterator &pos) { if (*(pos+0) != 0x2f || *(pos+1) != 0x2f) return false; - addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1)); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1)); return true; } @@ -1463,7 +1472,8 @@ bool Telegram::potentiallyDecrypt(vector::iterator &pos) } return false; } - addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1)); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x decrypt check bytes", *(pos+0), *(pos+1)); } else if (tpl_sec_mode == TPLSecurityMode::AES_CBC_NO_IV) { @@ -1471,7 +1481,8 @@ bool Telegram::potentiallyDecrypt(vector::iterator &pos) if (meter_keys == NULL || (!meter_keys->hasConfidentialityKey() && isSimulated())) { CHECK(2); - addExplanationAndIncrementPos(pos, 2, "%02x%02x (already) decrypted check bytes", *(pos+0), *(pos+1)); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x (already) decrypted check bytes", *(pos+0), *(pos+1)); return true; } bool mac_ok = checkMAC(frame, tpl_start, frame.end(), afl_mac_b, tpl_generated_mac_key); @@ -1521,7 +1532,8 @@ bool Telegram::potentiallyDecrypt(vector::iterator &pos) } return false; } - addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1)); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x decrypt check bytes", *(pos+0), *(pos+1)); } else if (tpl_sec_mode == TPLSecurityMode::SPECIFIC_16_31) { @@ -1597,7 +1609,8 @@ bool Telegram::parse_TPL_79(vector::iterator &pos) CHECK(2); uchar ecrc0 = *(pos+0); uchar ecrc1 = *(pos+1); - addExplanationAndIncrementPos(pos, 2, "%02x%02x format signature", ecrc0, ecrc1); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x format signature", ecrc0, ecrc1); format_signature = ecrc1<<8 | ecrc0; vector format_bytes; @@ -1622,7 +1635,8 @@ bool Telegram::parse_TPL_79(vector::iterator &pos) CHECK(2); int ecrc2 = *(pos+0); int ecrc3 = *(pos+1); - addExplanationAndIncrementPos(pos, 2, "%02x%02x data crc", ecrc2, ecrc3); + addExplanationAndIncrementPos(pos, 2, KindOfData::PROTOCOL, Understanding::FULL, + "%02x%02x data crc", ecrc2, ecrc3); header_size = distance(frame.begin(), pos); int remaining = distance(pos, frame.end()); @@ -1671,7 +1685,8 @@ bool Telegram::parseTPL(vector::iterator &pos) tpl_ci = ci_field; tpl_start = pos; - addExplanationAndIncrementPos(pos, 1, "%02x tpl-ci-field (%s)", + addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, + "%02x tpl-ci-field (%s)", tpl_ci, ciType(tpl_ci).c_str()); int len = ciFieldLength(tpl_ci); @@ -1923,8 +1938,122 @@ bool Telegram::parseHAN(vector &input_frame, MeterKeys *mk, bool warn) void Telegram::explainParse(string intro, int from) { - for (auto& p : explanations) { - debug("%s %02x: %s\n", intro.c_str(), p.first, p.second.c_str()); + for (auto& p : explanations) + { + // Protocol or content? + const char *c = p.kind == KindOfData::PROTOCOL ? " " : "C"; + const char *u = "?"; + if (p.understanding == Understanding::FULL) u = "!"; + if (p.understanding == Understanding::PARTIAL) u = "p"; + + // Do not print ok for understood protocol, it is implicit. + // However if a protocol is not full understood then print p or ?. + if (p.kind == KindOfData::PROTOCOL && p.understanding == Understanding::FULL) u = " "; + + debug("%s %03d %s%s: %s\n", intro.c_str(), p.pos, c, u, p.info.c_str()); + } +} + +void printAnalysisAsText(vector &explanations, bool use_ansi) +{ + const char *green; + const char *yellow; + const char *red; + const char *reset; + + if (use_ansi) + { + green = "\033[0;97m\033[0;42m"; + yellow = "\033[0;97m\033[0;43m"; + red = "\033[0;97m\033[0;41m"; + reset = "\033[0m"; + } + else + { + green = ""; + yellow = ""; + red = ""; + reset = ""; + } + + for (auto& p : explanations) + { + // Protocol or content? + const char *c = p.kind == KindOfData::PROTOCOL ? " " : "C"; + const char *u = "?"; + if (p.understanding == Understanding::FULL) u = "!"; + if (p.understanding == Understanding::PARTIAL) u = "p"; + + // Do not print ok for understood protocol, it is implicit. + // However if a protocol is not full understood then print p or ?. + if (p.kind == KindOfData::PROTOCOL && p.understanding == Understanding::FULL) u = " "; + + const char *pre; + const char *post = reset; + + if (*u == '!') + { + pre = green; + } + else if (*u == 'p') + { + pre = yellow; + } + else if (*u == ' ') + { + pre = ""; + post = ""; + } + else + { + pre = red; + } + + printf("%03d %s%s: %s%s%s\n", p.pos, c, u, pre, p.info.c_str(), post); + } +} + +void printAnalysisAsJson(vector &explanations) +{ + printf("{ \"TODO\": true }\n"); +} + +void Telegram::analyzeParse(OutputFormat format, int *content_length, int *understood_content_length) +{ + int u = 0; + int l = 0; + + // Calculate how much is understood. + for (auto& e : explanations) + { + if (e.kind == KindOfData::CONTENT) + { + l += e.len; + if (e.understanding != Understanding::NONE) + { + // Its content and we have at least some understanding. + u += e.len; + } + } + } + *content_length = l; + *understood_content_length = u; + + switch(format) + { + case OutputFormat::PLAIN : + case OutputFormat::TERMINAL: + { + bool use_ansi = format == OutputFormat::TERMINAL; + printAnalysisAsText(explanations, use_ansi); + break; + } + case OutputFormat::JSON: + printAnalysisAsJson(explanations); + break; + case OutputFormat::NONE: + // Do nothing + break; } } diff --git a/src/wmbus.h b/src/wmbus.h index e5f61ea..e77e826 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -353,6 +353,32 @@ struct AboutTelegram AboutTelegram() {} }; +// Mark understood bytes as either PROTOCOL, ie dif vif, acc and other header bytes. +// Or CONTENT, ie the value fields found inside the transport layer. +enum class KindOfData +{ + PROTOCOL, CONTENT +}; + +// Content can be not understood at all NONE, partially understood PARTIAL when typically bitsets have +// been partially decoded, or FULL when the volume or energy field is by itself complete. +enum class Understanding +{ + NONE, PARTIAL, FULL +}; + +struct Explanation +{ + int pos {}; + int len {}; + string info; + KindOfData kind {}; + Understanding understanding {}; + + Explanation(int p, int l, const string &i, KindOfData k, Understanding u) : + pos(p), len(l), info(i), kind(k), understanding(u) {} +}; + struct Telegram { AboutTelegram about; @@ -482,12 +508,13 @@ struct Telegram // A vector of indentations and explanations, to be printed // below the raw data bytes to explain the telegram content. - vector> explanations; - void addExplanationAndIncrementPos(vector::iterator &pos, int len, const char* fmt, ...); + vector explanations; + void addExplanationAndIncrementPos(vector::iterator &pos, int len, KindOfData k, Understanding u, const char* fmt, ...); void addMoreExplanation(int pos, const char* fmt, ...); // Add an explanation of data inside manufacturer specific data. - void addSpecialExplanation(int offset, const char* fmt, ...); + void addSpecialExplanation(int offset, KindOfData k, Understanding u, const char* fmt, ...); void explainParse(string intro, int from); + void analyzeParse(OutputFormat o, int *content_length, int *understood_content_length); bool parserWarns() { return parser_warns_; } bool isSimulated() { return is_simulated_; } diff --git a/test.sh b/test.sh index 452a6e9..f2c5993 100755 --- a/test.sh +++ b/test.sh @@ -120,6 +120,9 @@ if [ "$?" != "0" ]; then RC="1"; fi ./tests/test_broken.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 (cd ..; ./additional_tests.sh $PROG) diff --git a/tests/test_analyze.sh b/tests/test_analyze.sh new file mode 100755 index 0000000..c2ef9c9 --- /dev/null +++ b/tests/test_analyze.sh @@ -0,0 +1,166 @@ +#!/bin/sh + +PROG="$1" +TEST=testoutput +mkdir -p $TEST + +TESTNAME="Test analyze compact telegram" +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 ERROR: $TESTNAME + echo "wmbusmeters returned error code: $?" + cat $TEST/test_output.txt +fi + +TESTNAME="Test analyze find best driver" +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 ERROR: $TESTNAME + echo "wmbusmeters returned error code: $?" + cat $TEST/test_output.txt +fi + +TESTNAME="Test analyze normal telegram" +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