/* Copyright (C) 2018-2024 Fredrik Öhrström (gpl-3.0-or-later) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include"dvparser.h" #include"wmbus.h" #include"util.h" #include #include #include #include #include // The parser should not crash on invalid data, but yeah, when I // need to debug it because it crashes on invalid data, then // I enable the following define... //#define DEBUG_PARSER(...) fprintf(stdout, __VA_ARGS__) #define DEBUG_PARSER(...) using namespace std; union RealConversion { uint32_t i; float f; }; const char *toString(VIFRange v) { switch (v) { case VIFRange::None: return "None"; case VIFRange::Any: return "Any"; #define X(name,from,to,quantity,unit) case VIFRange::name: return #name; LIST_OF_VIF_RANGES #undef X } assert(0); } VIFRange toVIFRange(const char *s) { if (!strcmp(s, "None")) return VIFRange::None; if (!strcmp(s, "Any")) return VIFRange::Any; #define X(name,from,to,quantity,unit) if (!strcmp(s, #name)) return VIFRange::name; LIST_OF_VIF_RANGES #undef X return VIFRange::None; } VIFCombinable toVIFCombinable(const char *s) { if (!strcmp(s, "None")) return VIFCombinable::None; if (!strcmp(s, "Any")) return VIFCombinable::Any; #define X(name,from,to) if (!strcmp(s, #name)) return VIFCombinable::name; LIST_OF_VIF_COMBINABLES #undef X return VIFCombinable::None; } const char *toString(VIFCombinable v) { switch (v) { case VIFCombinable::None: return "None"; case VIFCombinable::Any: return "Any"; #define X(name,from,to) case VIFCombinable::name: return #name; LIST_OF_VIF_COMBINABLES #undef X } assert(0); } VIFCombinable toVIFCombinable(int i) { #define X(name,from,to) if (from <= i && i <= to) return VIFCombinable::name; LIST_OF_VIF_COMBINABLES #undef X return VIFCombinable::None; } Unit toDefaultUnit(Vif v) { #define X(name,from,to,quantity,unit) { if (from <= v.intValue() && v.intValue() <= to) return unit; } LIST_OF_VIF_RANGES #undef X return Unit::Unknown; } Unit toDefaultUnit(VIFRange v) { switch (v) { case VIFRange::Any: case VIFRange::None: assert(0); break; #define X(name,from,to,quantity,unit) case VIFRange::name: return unit; LIST_OF_VIF_RANGES #undef X } assert(0); } VIFRange toVIFRange(int i) { #define X(name,from,to,quantity,unit) if (from <= i && i <= to) return VIFRange::name; LIST_OF_VIF_RANGES #undef X return VIFRange::None; } bool isInsideVIFRange(Vif vif, VIFRange vif_range) { if (vif_range == VIFRange::AnyVolumeVIF) { // There are more volume units in the standard that will be added here. return isInsideVIFRange(vif, VIFRange::Volume); } if (vif_range == VIFRange::AnyEnergyVIF) { return isInsideVIFRange(vif, VIFRange::EnergyWh) || isInsideVIFRange(vif, VIFRange::EnergyMJ) || isInsideVIFRange(vif, VIFRange::EnergyMWh) || isInsideVIFRange(vif, VIFRange::EnergyGJ); } if (vif_range == VIFRange::AnyPowerVIF) { // There are more power units in the standard that will be added here. return isInsideVIFRange(vif, VIFRange::PowerW); } #define X(name,from,to,quantity,unit) if (VIFRange::name == vif_range) { return from <= vif.intValue() && vif.intValue() <= to; } LIST_OF_VIF_RANGES #undef X return false; } map hash_to_format_; bool loadFormatBytesFromSignature(uint16_t format_signature, vector *format_bytes) { if (hash_to_format_.count(format_signature) > 0) { debug("(dvparser) found remembered format for hash %x\n", format_signature); // Return the proper hash! hex2bin(hash_to_format_[format_signature], format_bytes); return true; } // Unknown format signature. return false; } bool parseDV(Telegram *t, vector &databytes, vector::iterator data, size_t data_len, map> *dv_entries, vector::iterator *format, size_t format_len, uint16_t *format_hash) { map dv_count; vector format_bytes; vector id_bytes; vector data_bytes; string dv, key; size_t start_parse_here = t->parsed.size(); vector::iterator data_start = data; vector::iterator data_end = data+data_len; vector::iterator format_end; bool data_has_difvifs = true; bool variable_length = false; int force_mfct_index = t->force_mfct_index; if (format == NULL) { // No format string was supplied, we therefore assume // that the difvifs necessary to parse the data is // part of the data! This is the default. format = &data; format_end = data_end; } else { // A format string has been supplied. The data is compressed, // and can only be decoded using the supplied difvifs. // Since the data does not have the difvifs. data_has_difvifs = false; format_end = *format+format_len; string s = bin2hex(*format, format_end, format_len); debug("(dvparser) using format \"%s\"\n", s.c_str()); } dv_entries->clear(); // Data format is: // DIF byte (defines how the binary data bits should be decoded and howy man data bytes there are) // Sometimes followed by one or more dife bytes, if the 0x80 high bit is set. // The last dife byte does not have the 0x80 bit set. // VIF byte (defines what the decoded value means, water,energy,power,etc.) // Sometimes followed by one or more vife bytes, if the 0x80 high bit is set. // The last vife byte does not have the 0x80 bit set. // Data bytes, the number of data bytes are defined by the dif format. // Or if the dif says variable length, then the first data byte specifies the number of data bytes. // DIF again... // A Dif(Difes)Vif(Vifes) identifier can be for example be the 02FF20 for the Multical21 // vendor specific status bits. The parser then uses this identifier as a key to store the // data bytes in a map. The same identifier could occur several times in a telegram, // even though it often don't. Since the first occurence is stored under 02FF20, // the second identical identifier stores its data under the key "02FF20_2" etc for 3 and forth... // A proper meter would use storagenr etc to differentiate between different measurements of // the same value. format_bytes.clear(); id_bytes.clear(); for (;;) { id_bytes.clear(); DEBUG_PARSER("(dvparser debug) Remaining format data %ju\n", std::distance(*format,format_end)); if (*format == format_end) break; if (force_mfct_index != -1) { // This is an old meter without a proper 0f or other hear start manufacturer data marker. int index = std::distance(data_start, data); if (index >= force_mfct_index) { DEBUG_PARSER("(dvparser) manufacturer specific data, parsing is done.\n", dif); size_t datalen = std::distance(data, data_end); string value = bin2hex(data, data_end, datalen); t->addExplanationAndIncrementPos(data, datalen, KindOfData::CONTENT, Understanding::NONE, "manufacturer specific data %s", value.c_str()); break; } } uchar dif = **format; MeasurementType mt = difMeasurementType(dif); int datalen = difLenBytes(dif); DEBUG_PARSER("(dvparser debug) dif=%02x datalen=%d \"%s\" type=%s\n", dif, datalen, difType(dif).c_str(), measurementTypeName(mt).c_str()); if (datalen == -2) { if (dif == 0x0f) { DEBUG_PARSER("(dvparser) reached dif %02x manufacturer specific data, parsing is done.\n", dif); datalen = std::distance(data,data_end); 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, KindOfData::CONTENT, Understanding::NONE, "%02X manufacturer specific data %s", dif, value.c_str()); break; } if (dif == 0x1f) { DEBUG_PARSER("(dvparser) reached dif %02x more records in next telegram.\n", dif); datalen = std::distance(data,data_end); 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, KindOfData::CONTENT, Understanding::FULL, "%02X more data in next telegram %s", dif, value.c_str()); break; } DEBUG_PARSER("(dvparser) reached unknown dif %02x treating remaining data as manufacturer specific, parsing is done.\n", dif); datalen = std::distance(data,data_end); 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, KindOfData::CONTENT, Understanding::NONE, "%02X unknown dif treating remaining data as mfct specific %s", dif, value.c_str()); break; } if (dif == 0x2f) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X skip", dif); DEBUG_PARSER("\n"); continue; } if (datalen == -1) { variable_length = true; } else { variable_length = false; } if (data_has_difvifs) { format_bytes.push_back(dif); id_bytes.push_back(dif); t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X dif (%s)", dif, difType(dif).c_str()); } else { id_bytes.push_back(**format); (*format)++; } int difenr = 0; int subunit = 0; int tariff = 0; int lsb_of_storage_nr = (dif & 0x40) >> 6; int storage_nr = lsb_of_storage_nr; bool has_another_dife = (dif & 0x80) == 0x80; int num_dife = 0; while (has_another_dife) { num_dife++; if (num_dife > 10) { debug("(dvparser) warning: too many dife found!\n"); break; } if (*format == format_end) { debug("(dvparser) warning: unexpected end of data (dife expected)\n"); break; } uchar dife = **format; int subunit_bit = (dife & 0x40) >> 6; subunit |= subunit_bit << difenr; int tariff_bits = (dife & 0x30) >> 4; tariff |= tariff_bits << (difenr*2); int storage_nr_bits = (dife & 0x0f); storage_nr |= storage_nr_bits << (1+difenr*4); DEBUG_PARSER("(dvparser debug) dife=%02x (subunit=%d tariff=%d storagenr=%d)\n", dife, subunit, tariff, storage_nr); if (data_has_difvifs) { format_bytes.push_back(dife); id_bytes.push_back(dife); 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)++; } has_another_dife = (dife & 0x80) == 0x80; difenr++; } if (*format == format_end) { debug("(dvparser) warning: unexpected end of data (vif expected)\n"); break; } uchar vif = **format; int full_vif = vif & 0x7f; bool extension_vif = false; int combinable_full_vif = 0; bool combinable_extension_vif = false; set found_combinable_vifs; set found_combinable_vifs_raw; DEBUG_PARSER("(dvparser debug) vif=%04x \"%s\"\n", vif, vifType(vif).c_str()); if (data_has_difvifs) { format_bytes.push_back(vif); id_bytes.push_back(vif); t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X vif (%s)", vif, vifType(vif).c_str()); } else { id_bytes.push_back(**format); (*format)++; } // Check if this is marker for one of the extended sets of vifs: first, second and third or manufacturer. if (vif == 0xfb || vif == 0xfd || vif == 0xef || vif == 0xff) { // Extension vifs. full_vif <<= 8; extension_vif = true; } // Grabbing a variable length vif. This does not currently work // with the compact format. if (vif == 0x7c) { 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; id_bytes.push_back(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, KindOfData::PROTOCOL, Understanding::FULL, "%02X vif (%c)", v, v); id_bytes.push_back(v); } } // Do we have another vife byte? We better have one, if extension_vif is true. bool has_another_vife = (vif & 0x80) == 0x80; int num_vife = 0; while (has_another_vife) { num_vife++; if (num_vife > 10) { debug("(dvparser) warning: too many vife found!\n"); break; } if (*format == format_end) { debug("(dvparser) warning: unexpected end of data (vife expected)\n"); break; } uchar vife = **format; DEBUG_PARSER("(dvparser debug) vife=%02x (%s)\n", vife, vifeType(dif, vif, vife).c_str()); if (data_has_difvifs) { // Collect the difvifs to create signature for future use. format_bytes.push_back(vife); id_bytes.push_back(vife); } else { // Reuse the existing id_bytes.push_back(**format); (*format)++; } has_another_vife = (vife & 0x80) == 0x80; if (extension_vif) { // First vife after the extension marker is the real vif. full_vif |= (vife & 0x7f); extension_vif = false; if (data_has_difvifs) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X vife (%s)", vife, vifeType(dif, vif, vife).c_str()); } } else { if (combinable_extension_vif) { // First vife after the combinable extension marker is the real vif. combinable_full_vif |= (vife & 0x7f); combinable_extension_vif = false; VIFCombinable vc = toVIFCombinable(combinable_full_vif); found_combinable_vifs.insert(vc); found_combinable_vifs_raw.insert(combinable_full_vif); if (data_has_difvifs) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X combinable extension vife (%s)", vife, toString(vc)); } } else { combinable_full_vif = vife & 0x7f; // Check if this is marker for one of the extended combinable vifs if (combinable_full_vif == 0x7c || combinable_full_vif == 0x7f) { combinable_full_vif <<= 8; combinable_extension_vif = true; VIFCombinable vc = toVIFCombinable(vife & 0x7f); if (data_has_difvifs) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X combinable vif (%s)", vife, toString(vc)); } } else { VIFCombinable vc = toVIFCombinable(combinable_full_vif); found_combinable_vifs.insert(vc); found_combinable_vifs_raw.insert(combinable_full_vif); if (data_has_difvifs) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X combinable vif (%s)", vife, toString(vc)); } } } } } dv = ""; for (uchar c : id_bytes) { char hex[3]; hex[2] = 0; snprintf(hex, 3, "%02X", c); dv.append(hex); } DEBUG_PARSER("(dvparser debug) key \"%s\"\n", dv.c_str()); int count = ++dv_count[dv]; if (count > 1) { strprintf(&key, "%s_%d", dv.c_str(), count); } else { strprintf(&key, "%s", dv.c_str()); } DEBUG_PARSER("(dvparser debug) DifVif key is %s\n", key.c_str()); int remaining = std::distance(data, data_end); if (remaining < 1) { debug("(dvparser) warning: unexpected end of data\n"); break; } if (variable_length) { DEBUG_PARSER("(dvparser debug) varlen %02x\n", *(data+0)); datalen = *(data); t->addExplanationAndIncrementPos(data, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X varlen=%d", *(data+0), datalen); remaining--; // Drop the length byte. } DEBUG_PARSER("(dvparser debug) remaining data %d len=%d\n", remaining, datalen); if (remaining < datalen) { debug("(dvparser) warning: unexpected end of data\n"); datalen = remaining-1; } string value = bin2hex(data, data_end, datalen); int offset = start_parse_here+data-data_start; (*dv_entries)[key] = { offset, DVEntry(offset, key, mt, Vif(full_vif), found_combinable_vifs, found_combinable_vifs_raw, StorageNr(storage_nr), TariffNr(tariff), SubUnitNr(subunit), value) }; DVEntry *dve = &(*dv_entries)[key].second; if (isTraceEnabled()) { trace("[DVPARSER] entry %s\n", dve->str().c_str()); } assert(key == dve->dif_vif_key.str()); if (value.length() > 0) { // This call increments data with datalen. 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()) { // We are done here! break; } } string format_string = bin2hex(format_bytes); uint16_t hash = crc16_EN13757(safeButUnsafeVectorPtr(format_bytes), format_bytes.size()); if (data_has_difvifs) { if (hash_to_format_.count(hash) == 0) { hash_to_format_[hash] = format_string; debug("(dvparser) found new format \"%s\" with hash %x, remembering!\n", format_string.c_str(), hash); } } return true; } bool hasKey(std::map> *dv_entries, std::string key) { return dv_entries->count(key) > 0; } bool findKey(MeasurementType mit, VIFRange vif_range, StorageNr storagenr, TariffNr tariffnr, std::string *key, std::map> *dv_entries) { return findKeyWithNr(mit, vif_range, storagenr, tariffnr, 1, key, dv_entries); } bool findKeyWithNr(MeasurementType mit, VIFRange vif_range, StorageNr storagenr, TariffNr tariffnr, int nr, std::string *key, std::map> *dv_entries) { /*debug("(dvparser) looking for type=%s vifrange=%s storagenr=%d tariffnr=%d\n", measurementTypeName(mit).c_str(), toString(vif_range), storagenr.intValue(), tariffnr.intValue());*/ for (auto& v : *dv_entries) { MeasurementType ty = v.second.second.measurement_type; Vif vi = v.second.second.vif; StorageNr sn = v.second.second.storage_nr; TariffNr tn = v.second.second.tariff_nr; /* debug("(dvparser) match? %s type=%s vife=%x (%s) and storagenr=%d\n", v.first.c_str(), measurementTypeName(ty).c_str(), vi.intValue(), storagenr, sn);*/ if (isInsideVIFRange(vi, vif_range) && (mit == MeasurementType::Instantaneous || mit == ty) && (storagenr == AnyStorageNr || storagenr == sn) && (tariffnr == AnyTariffNr || tariffnr == tn)) { *key = v.first; nr--; if (nr <= 0) return true; debug("(dvparser) found key %s for type=%s vif=%x storagenr=%d\n", v.first.c_str(), measurementTypeName(ty).c_str(), vi.intValue(), storagenr.intValue()); } } return false; } void extractDV(DifVifKey &dvk, uchar *dif, int *vif, bool *has_difes, bool *has_vifes) { string tmp = dvk.str(); extractDV(tmp, dif, vif, has_difes, has_vifes); } void extractDV(string &s, uchar *dif, int *vif, bool *has_difes, bool *has_vifes) { vector bytes; hex2bin(s, &bytes); size_t i = 0; *has_difes = false; *has_vifes = false; if (bytes.size() == 0) { *dif = 0; *vif = 0; return; } *dif = bytes[i]; while (i < bytes.size() && (bytes[i] & 0x80)) { i++; *has_difes = true; } i++; if (i >= bytes.size()) { *vif = 0; return; } *vif = bytes[i]; if (*vif == 0xfb || // first extension *vif == 0xfd || // second extensio *vif == 0xef || // third extension *vif == 0xff) // vendor extension { if (i+1 < bytes.size()) { // Create an extended vif, like 0xfd31 for example. *vif = bytes[i] << 8 | bytes[i+1]; i++; } } while (i < bytes.size() && (bytes[i] & 0x80)) { i++; *has_vifes = true; } } bool extractDVuint8(map> *dv_entries, string key, int *offset, uchar *value) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract uint8 from non-existant key \"%s\"\n", key.c_str()); *offset = -1; *value = 0; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; vector v; hex2bin(p.second.value, &v); *value = v[0]; return true; } bool extractDVuint16(map> *dv_entries, string key, int *offset, uint16_t *value) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract uint16 from non-existant key \"%s\"\n", key.c_str()); *offset = -1; *value = 0; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; vector v; hex2bin(p.second.value, &v); *value = v[1]<<8 | v[0]; return true; } bool extractDVuint24(map> *dv_entries, string key, int *offset, uint32_t *value) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract uint24 from non-existant key \"%s\"\n", key.c_str()); *offset = -1; *value = 0; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; vector v; hex2bin(p.second.value, &v); *value = v[2] << 16 | v[1]<<8 | v[0]; return true; } bool extractDVuint32(map> *dv_entries, string key, int *offset, uint32_t *value) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract uint32 from non-existant key \"%s\"\n", key.c_str()); *offset = -1; *value = 0; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; vector v; hex2bin(p.second.value, &v); *value = (uint32_t(v[3]) << 24) | (uint32_t(v[2]) << 16) | (uint32_t(v[1])<<8) | uint32_t(v[0]); return true; } bool extractDVdouble(map> *dv_entries, string key, int *offset, double *value, bool auto_scale, bool force_unsigned) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract double from non-existant key \"%s\"\n", key.c_str()); *offset = 0; *value = 0; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; if (p.second.value.length() == 0) { verbose("(dvparser) warning: key found but no data \"%s\"\n", key.c_str()); *offset = 0; *value = 0; return false; } return p.second.extractDouble(value, auto_scale, force_unsigned); } bool checkSizeHex(size_t expected_len, DifVifKey &dvk, string &v) { if (v.length() == expected_len) return true; warning("(dvparser) bad decode since difvif %s expected %d hex chars but got \"%s\"\n", dvk.str().c_str(), expected_len, v.c_str()); return false; } bool is_all_F(string &v) { for (size_t i = 0; i < v.length(); ++i) { if (v[i] != 'F') return false; } return true; } bool DVEntry::extractDouble(double *out, bool auto_scale, bool force_unsigned) { int t = dif_vif_key.dif() & 0xf; if (t == 0x0 || t == 0x8 || t == 0xd || t == 0xf) { // Cannot extract from nothing, selection for readout, variable length or special. // Variable length is used for compact varlen history. Should be added in the future. return false; } else if (t == 0x1 || // 8 Bit Integer/Binary t == 0x2 || // 16 Bit Integer/Binary t == 0x3 || // 24 Bit Integer/Binary t == 0x4 || // 32 Bit Integer/Binary t == 0x6 || // 48 Bit Integer/Binary t == 0x7) // 64 Bit Integer/Binary { vector v; hex2bin(value, &v); uint64_t raw = 0; bool negate = false; uint64_t negate_mask = 0; if (t == 0x1) { if (!checkSizeHex(2, dif_vif_key, value)) return false; assert(v.size() == 1); raw = v[0]; if (!force_unsigned && (raw & (uint64_t)0x80UL) != 0) { negate = true; negate_mask = ~((uint64_t)0)<<8; } } else if (t == 0x2) { if (!checkSizeHex(4, dif_vif_key, value)) return false; assert(v.size() == 2); raw = v[1]*256 + v[0]; if (!force_unsigned && (raw & (uint64_t)0x8000UL) != 0) { negate = true; negate_mask = ~((uint64_t)0)<<16; } } else if (t == 0x3) { if (!checkSizeHex(6, dif_vif_key, value)) return false; assert(v.size() == 3); raw = v[2]*256*256 + v[1]*256 + v[0]; if (!force_unsigned && (raw & (uint64_t)0x800000UL) != 0) { negate = true; negate_mask = ~((uint64_t)0)<<24; } } else if (t == 0x4) { if (!checkSizeHex(8, dif_vif_key, value)) return false; assert(v.size() == 4); raw = ((unsigned int)v[3])*256*256*256 + ((unsigned int)v[2])*256*256 + ((unsigned int)v[1])*256 + ((unsigned int)v[0]); if (!force_unsigned && (raw & (uint64_t)0x80000000UL) != 0) { negate = true; negate_mask = ~((uint64_t)0)<<32; } } else if (t == 0x6) { if (!checkSizeHex(12, dif_vif_key, value)) return false; assert(v.size() == 6); raw = ((uint64_t)v[5])*256*256*256*256*256 + ((uint64_t)v[4])*256*256*256*256 + ((uint64_t)v[3])*256*256*256 + ((uint64_t)v[2])*256*256 + ((uint64_t)v[1])*256 + ((uint64_t)v[0]); if (!force_unsigned && (raw & (uint64_t)0x800000000000UL) != 0) { negate = true; negate_mask = ~((uint64_t)0)<<48; } } else if (t == 0x7) { if (!checkSizeHex(16, dif_vif_key, value)) return false; assert(v.size() == 8); raw = ((uint64_t)v[7])*256*256*256*256*256*256*256 + ((uint64_t)v[6])*256*256*256*256*256*256 + ((uint64_t)v[5])*256*256*256*256*256 + ((uint64_t)v[4])*256*256*256*256 + ((uint64_t)v[3])*256*256*256 + ((uint64_t)v[2])*256*256 + ((uint64_t)v[1])*256 + ((uint64_t)v[0]); if (!force_unsigned && (raw & (uint64_t)0x8000000000000000UL) != 0) { negate = true; negate_mask = 0; } } double scale = 1.0; double draw = (double)raw; if (negate) { draw = (double)((int64_t)(negate_mask | raw)); } if (auto_scale) scale = vifScale(dif_vif_key.vif()); *out = (draw) / scale; } else if (t == 0x9 || // 2 digit BCD t == 0xA || // 4 digit BCD t == 0xB || // 6 digit BCD t == 0xC || // 8 digit BCD t == 0xE) // 12 digit BCD { // Negative BCD values are always visible in bcd. I.e. they are always signed. // Ignore assumption on signedness. // 74140000 -> 00001474 string& v = value; uint64_t raw = 0; bool negate = false; if (is_all_F(v)) { *out = std::nan(""); return false; } if (t == 0x9) { if (!checkSizeHex(2, dif_vif_key, v)) return false; if (v[0] == 'F') { negate = true; v[0] = '0'; } raw = (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xA) { if (!checkSizeHex(4, dif_vif_key, v)) return false; if (v[2] == 'F') { negate = true; v[2] = '0'; } raw = (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xB) { if (!checkSizeHex(6, dif_vif_key, v)) return false; if (v[4] == 'F') { negate = true; v[4] = '0'; } raw = (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10 + (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xC) { if (!checkSizeHex(8, dif_vif_key, v)) return false; if (v[6] == 'F') { negate = true; v[6] = '0'; } raw = (v[6]-'0')*10*10*10*10*10*10*10 + (v[7]-'0')*10*10*10*10*10*10 + (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10 + (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xE) { if (!checkSizeHex(12, dif_vif_key, v)) return false; if (v[10] == 'F') { negate = true; v[10] = '0'; } raw =(v[10]-'0')*10*10*10*10*10*10*10*10*10*10*10 + (v[11]-'0')*10*10*10*10*10*10*10*10*10*10 + (v[8]-'0')*10*10*10*10*10*10*10*10*10 + (v[9]-'0')*10*10*10*10*10*10*10*10 + (v[6]-'0')*10*10*10*10*10*10*10 + (v[7]-'0')*10*10*10*10*10*10 + (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10 + (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } double scale = 1.0; double draw = (double)raw; if (negate) { draw = (double)draw * -1; } if (auto_scale) scale = vifScale(dif_vif_key.vif()); *out = (draw) / scale; } else if (t == 0x5) // 32 Bit Real { vector v; hex2bin(value, &v); if (!checkSizeHex(8, dif_vif_key, value)) return false; assert(v.size() == 4); RealConversion rc; rc.i = v[3]<<24 | v[2]<<16 | v[1]<<8 | v[0]; // Assumes float uses the standard IEEE 754 bit set. // 1 bit sign, 8 bit exp, 23 bit mantissa // RealConversion is tested on an amd64 platform. How about // other platsforms with different byte ordering? double draw = rc.f; double scale = 1.0; if (auto_scale) scale = vifScale(dif_vif_key.vif()); *out = (draw) / scale; } else { warning("(dvparser) Unsupported dif format for extraction to double! dif=%02x\n", dif_vif_key.dif()); return false; } return true; } bool extractDVlong(map> *dv_entries, string key, int *offset, uint64_t *out) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract long from non-existant key \"%s\"\n", key.c_str()); *offset = 0; *out = 0; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; if (p.second.value.length() == 0) { verbose("(dvparser) warning: key found but no data \"%s\"\n", key.c_str()); *offset = 0; *out = 0; return false; } return p.second.extractLong(out); } bool DVEntry::extractLong(uint64_t *out) { int t = dif_vif_key.dif() & 0xf; if (t == 0x1 || // 8 Bit Integer/Binary t == 0x2 || // 16 Bit Integer/Binary t == 0x3 || // 24 Bit Integer/Binary t == 0x4 || // 32 Bit Integer/Binary t == 0x6 || // 48 Bit Integer/Binary t == 0x7) // 64 Bit Integer/Binary { vector v; hex2bin(value, &v); uint64_t raw = 0; if (t == 0x1) { if (!checkSizeHex(2, dif_vif_key, value)) return false; assert(v.size() == 1); raw = v[0]; } else if (t == 0x2) { if (!checkSizeHex(4, dif_vif_key, value)) return false; assert(v.size() == 2); raw = v[1]*256 + v[0]; } else if (t == 0x3) { if (!checkSizeHex(6, dif_vif_key, value)) return false; assert(v.size() == 3); raw = v[2]*256*256 + v[1]*256 + v[0]; } else if (t == 0x4) { if (!checkSizeHex(8, dif_vif_key, value)) return false; assert(v.size() == 4); raw = ((unsigned int)v[3])*256*256*256 + ((unsigned int)v[2])*256*256 + ((unsigned int)v[1])*256 + ((unsigned int)v[0]); } else if (t == 0x6) { if (!checkSizeHex(12, dif_vif_key, value)) return false; assert(v.size() == 6); raw = ((uint64_t)v[5])*256*256*256*256*256 + ((uint64_t)v[4])*256*256*256*256 + ((uint64_t)v[3])*256*256*256 + ((uint64_t)v[2])*256*256 + ((uint64_t)v[1])*256 + ((uint64_t)v[0]); } else if (t == 0x7) { if (!checkSizeHex(16, dif_vif_key, value)) return false; assert(v.size() == 8); raw = ((uint64_t)v[7])*256*256*256*256*256*256*256 + ((uint64_t)v[6])*256*256*256*256*256*256 + ((uint64_t)v[5])*256*256*256*256*256 + ((uint64_t)v[4])*256*256*256*256 + ((uint64_t)v[3])*256*256*256 + ((uint64_t)v[2])*256*256 + ((uint64_t)v[1])*256 + ((uint64_t)v[0]); } *out = raw; } else if (t == 0x9 || // 2 digit BCD t == 0xA || // 4 digit BCD t == 0xB || // 6 digit BCD t == 0xC || // 8 digit BCD t == 0xE) // 12 digit BCD { // 74140000 -> 00001474 string& v = value; if (is_all_F(v)) { return false; } uint64_t raw = 0; bool negate = false; if (t == 0x9) { if (!checkSizeHex(2, dif_vif_key, value)) return false; if (v[0] == 'F') { negate = true; v[0] = '0'; } assert(v.size() == 2); raw = (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xA) { if (!checkSizeHex(4, dif_vif_key, value)) return false; if (v[2] == 'F') { negate = true; v[2] = '0'; } assert(v.size() == 4); raw = (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xB) { if (!checkSizeHex(6, dif_vif_key, value)) return false; if (v[4] == 'F') { negate = true; v[4] = '0'; } assert(v.size() == 6); raw = (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10 + (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xC) { if (!checkSizeHex(8, dif_vif_key, value)) return false; if (v[6] == 'F') { negate = true; v[6] = '0'; } assert(v.size() == 8); raw = (v[6]-'0')*10*10*10*10*10*10*10 + (v[7]-'0')*10*10*10*10*10*10 + (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10 + (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } else if (t == 0xE) { if (!checkSizeHex(12, dif_vif_key, value)) return false; if (v[10] == 'F') { negate = true; v[10] = '0'; } assert(v.size() == 12); raw =(v[10]-'0')*10*10*10*10*10*10*10*10*10*10*10 + (v[11]-'0')*10*10*10*10*10*10*10*10*10*10 + (v[8]-'0')*10*10*10*10*10*10*10*10*10 + (v[9]-'0')*10*10*10*10*10*10*10*10 + (v[6]-'0')*10*10*10*10*10*10*10 + (v[7]-'0')*10*10*10*10*10*10 + (v[4]-'0')*10*10*10*10*10 + (v[5]-'0')*10*10*10*10 + (v[2]-'0')*10*10*10 + (v[3]-'0')*10*10 + (v[0]-'0')*10 + (v[1]-'0'); } if (negate) { raw = (uint64_t)(((int64_t)raw)*-1); } *out = raw; } else { error("Unsupported dif format for extraction to long! dif=%02x\n", dif_vif_key.dif()); } return true; } bool extractDVHexString(map> *dv_entries, string key, int *offset, string *value) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract string from non-existant key \"%s\"\n", key.c_str()); *offset = -1; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; *value = p.second.value; return true; } bool extractDVReadableString(map> *dv_entries, string key, int *offset, string *out) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract string from non-existant key \"%s\"\n", key.c_str()); *offset = -1; return false; } pair& p = (*dv_entries)[key]; *offset = p.first; return p.second.extractReadableString(out); } bool DVEntry::extractReadableString(string *out) { int t = dif_vif_key.dif() & 0xf; string v = value; if (t == 0x1 || // 8 Bit Integer/Binary t == 0x2 || // 16 Bit Integer/Binary t == 0x3 || // 24 Bit Integer/Binary t == 0x4 || // 32 Bit Integer/Binary t == 0x6 || // 48 Bit Integer/Binary t == 0x7 || // 64 Bit Integer/Binary t == 0xD) // Variable length { if (isLikelyAscii(v)) { // For example an enhanced id 32 bits binary looks like: // 44434241 and will be reversed to: 41424344 and translated using ascii // to ABCD v = reverseBinaryAsciiSafeToString(v); } else { v = reverseBCD(v); } } if (t == 0x9 || // 2 digit BCD t == 0xA || // 4 digit BCD t == 0xB || // 6 digit BCD t == 0xC || // 8 digit BCD t == 0xE) // 12 digit BCD { // For example an enhanced id 12 digit bcd looks like: // 618171183100 and will be reversed to: 003118718161 v = reverseBCD(v); } *out = v; return true; } double DVEntry::getCounter(DVEntryCounterType ct) { switch (ct) { case DVEntryCounterType::STORAGE_COUNTER: return storage_nr.intValue(); case DVEntryCounterType::TARIFF_COUNTER: return tariff_nr.intValue(); case DVEntryCounterType::SUBUNIT_COUNTER: return subunit_nr.intValue(); case DVEntryCounterType::UNKNOWN: break; } return std::numeric_limits::quiet_NaN(); } string DVEntry::str() { string s = tostrprintf("%d: %s %s vif=%x %s%s st=%d ta=%d su=%d", offset, dif_vif_key.str().c_str(), toString(measurement_type), vif.intValue(), combinable_vifs.size() > 0 ? "HASCOMB ":"", combinable_vifs_raw.size() > 0 ? "HASCOMBRAW ":"", storage_nr.intValue(), tariff_nr.intValue(), subunit_nr.intValue() ); return s; } bool extractDate(uchar hi, uchar lo, struct tm *date) { // | hi | lo | // | YYYY MMMM | YYY DDDDD | int day = (0x1f) & lo; int year1 = ((0xe0) & lo) >> 5; int month = (0x0f) & hi; int year2 = ((0xf0) & hi) >> 1; int year = (2000 + year1 + year2); date->tm_mday = day; /* Day of the month (1-31) */ date->tm_mon = month - 1; /* Month (0-11) */ date->tm_year = year - 1900; /* Year - 1900 */ if (month > 12) return false; return true; } bool extractTime(uchar hi, uchar lo, struct tm *date) { // | hi | lo | // | ...hhhhh | ..mmmmmm | int min = (0x3f) & lo; int hour = (0x1f) & hi; date->tm_min = min; date->tm_hour = hour; if (min > 59) return false; if (hour > 23) return false; return true; } bool extractDVdate(map> *dv_entries, string key, int *offset, struct tm *out) { if ((*dv_entries).count(key) == 0) { verbose("(dvparser) warning: cannot extract date from non-existant key \"%s\"\n", key.c_str()); *offset = -1; memset(out, 0, sizeof(struct tm)); return false; } pair& p = (*dv_entries)[key]; *offset = p.first; return p.second.extractDate(out); } bool DVEntry::extractDate(struct tm *out) { memset(out, 0, sizeof(*out)); out->tm_isdst = -1; // Figure out the dst automatically! vector v; hex2bin(value, &v); bool ok = true; if (v.size() == 2) { ok &= ::extractDate(v[1], v[0], out); } else if (v.size() == 4) { ok &= ::extractDate(v[3], v[2], out); ok &= ::extractTime(v[1], v[0], out); } else if (v.size() == 6) { ok &= ::extractDate(v[4], v[3], out); ok &= ::extractTime(v[2], v[1], out); // ..ss ssss int sec = (0x3f) & v[0]; out->tm_sec = sec; // There are also bits for day of week, week of year. // A bit for if daylight saving is in use or not and its offset. // A bit if it is a leap year. // I am unsure how to deal with this here..... TODO } return ok; } bool FieldMatcher::matches(DVEntry &dv_entry) { if (!active) return false; // Test an explicit dif vif key. if (match_dif_vif_key) { bool b = dv_entry.dif_vif_key == dif_vif_key; return b; } // Test ranges and types. bool range = (!match_vif_range || isInsideVIFRange(dv_entry.vif, vif_range)); bool raw = (!match_vif_raw || dv_entry.vif == vif_raw); bool type = (!match_measurement_type || dv_entry.measurement_type == measurement_type); bool storage = (!match_storage_nr || (dv_entry.storage_nr >= storage_nr_from && dv_entry.storage_nr <= storage_nr_to)); bool tariff = (!match_tariff_nr || (dv_entry.tariff_nr >= tariff_nr_from && dv_entry.tariff_nr <= tariff_nr_to)); bool subunit = (!match_subunit_nr || (dv_entry.subunit_nr >= subunit_nr_from && dv_entry.subunit_nr <= subunit_nr_to)); //printf("Match? range=%d raw=%d type=%d storage=%d tariff=%d subunit=%d \n", range, raw, type, storage, tariff, subunit); bool b = range & raw & type & storage & tariff & subunit; if (!b) return false; // So far so good, now test the combinables. // If field matcher has no combinables, then do NOT match any dventry with a combinable! if (vif_combinables.size()== 0 && vif_combinables_raw.size() == 0) { // If there is a combinable vif, then there is a raw combinable vif. So comparing both not strictly necessary. if (dv_entry.combinable_vifs.size() == 0 && dv_entry.combinable_vifs_raw.size() == 0) return true; // Oups, field matcher does not expect any combinables, but the dv_entry has combinables. // This means no match for us since combinables must be handled explicitly. return false; } // Lets check that the dv_entry vif combinables raw contains the field matcher requested combinable raws. // The raws are used for meters using reserved and manufacturer specific vif combinables. for (uint16_t vcr : vif_combinables_raw) { if (dv_entry.combinable_vifs_raw.count(vcr) == 0) { // Ouch, one of the requested vif combinables raw did not exist in the dv_entry. No match! return false; } } // Lets check that the dv_entry combinables contains the field matcher requested combinables. // The named vif combinables are used by well behaved meters. for (VIFCombinable vc : vif_combinables) { if (vc != VIFCombinable::Any && dv_entry.combinable_vifs.count(vc) == 0) { // Ouch, one of the requested combinables did not exist in the dv_entry. No match! return false; } } // Now if we have not selected the Any combinable match pattern, // then we need to check if there are unmatched combinables in the telegram, if so fail the match. if (vif_combinables.count(VIFCombinable::Any) == 0) { if (vif_combinables.size() > 0) { for (VIFCombinable vc : dv_entry.combinable_vifs) { if (vif_combinables.count(vc) == 0) { // Oups, the telegram entry had a vif combinable that we had no matcher for. return false; } } } else { for (uint16_t vcr : dv_entry.combinable_vifs_raw) { if (vif_combinables_raw.count(vcr) == 0) { // Oups, the telegram entry had a vif combinable raw that we had no matcher for. return false; } } } } // Yay, they were all found. return true; } const char *toString(MeasurementType mt) { switch (mt) { case MeasurementType::Any: return "Any"; case MeasurementType::Instantaneous: return "Instantaneous"; case MeasurementType::Minimum: return "Minimum"; case MeasurementType::Maximum: return "Maximum"; case MeasurementType::AtError: return "AtError"; case MeasurementType::Unknown: return "Unknown"; } return "?"; } MeasurementType toMeasurementType(const char *s) { if (!strcmp(s, "Any")) return MeasurementType::Any; if (!strcmp(s, "Instantaneous")) return MeasurementType::Instantaneous; if (!strcmp(s, "Minimum")) return MeasurementType::Minimum; if (!strcmp(s, "Maximum")) return MeasurementType::Maximum; if (!strcmp(s, "AtError")) return MeasurementType::AtError; if (!strcmp(s, "Unknown")) return MeasurementType::Unknown; return MeasurementType::Unknown; } string FieldMatcher::str() { string s = ""; if (match_dif_vif_key) { s = s+"DVK("+dif_vif_key.str()+") "; } if (match_measurement_type) { s = s+"MT("+toString(measurement_type)+") "; } if (match_vif_range) { s = s+"VR("+toString(vif_range)+") "; } if (match_vif_raw) { s = s+"VRR("+to_string(vif_raw)+") "; } if (vif_combinables.size() > 0) { s += "Comb("; for (auto vc : vif_combinables) { s = s+toString(vc)+" "; } s.pop_back(); s += ")"; } if (match_storage_nr) { s = s+"S("+to_string(storage_nr_from.intValue())+"-"+to_string(storage_nr_to.intValue())+") "; } if (match_tariff_nr) { s = s+"T("+to_string(tariff_nr_from.intValue())+"-"+to_string(tariff_nr_to.intValue())+") "; } if (match_subunit_nr) { s += "U("+to_string(subunit_nr_from.intValue())+"-"+to_string(subunit_nr_to.intValue())+") "; } if (index_nr.intValue() != 1) { s += "I("+to_string(index_nr.intValue())+")"; } if (s.size() > 0) { s.pop_back(); } return s; } DVEntryCounterType toDVEntryCounterType(const std::string &s) { if (s == "storage_counter") return DVEntryCounterType::STORAGE_COUNTER; if (s == "tariff_counter") return DVEntryCounterType::TARIFF_COUNTER; if (s == "subunit_counter") return DVEntryCounterType::SUBUNIT_COUNTER; return DVEntryCounterType::UNKNOWN; } const char *toString(DVEntryCounterType ct) { switch (ct) { case DVEntryCounterType::UNKNOWN: return "unknown"; case DVEntryCounterType::STORAGE_COUNTER: return "storage_counter"; case DVEntryCounterType::TARIFF_COUNTER: return "tariff_counter"; case DVEntryCounterType::SUBUNIT_COUNTER: return "subunit_counter"; } return "unknown"; } string available_vif_ranges_; const string &availableVIFRanges() { if (available_vif_ranges_ != "") return available_vif_ranges_; #define X(n,from,to,q,u) available_vif_ranges_ += string(#n) + "\n"; LIST_OF_VIF_RANGES #undef X // Remove last newline available_vif_ranges_.pop_back(); return available_vif_ranges_; } string available_vif_combinables_; const string &availableVIFCombinables() { if (available_vif_combinables_ != "") return available_vif_combinables_; #define X(n,from,to) available_vif_combinables_ += string(#n) + "\n"; LIST_OF_VIF_COMBINABLES #undef X // Remove last newline available_vif_combinables_.pop_back(); return available_vif_combinables_; }