Skip to content

Commit 3053cbe

Browse files
committed
Ignore Mbus short frames when parsing HDLC transmission
1 parent fccbd17 commit 3053cbe

6 files changed

Lines changed: 58 additions & 9 deletions

File tree

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dlms_parser",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "A C++20 library for parsing DLMS/COSEM push telegrams from electricity meters. It is designed for embedded and integration-heavy environments such as ESPHome",
55
"keywords": [
66
"dlms",

src/dlms_parser/dlms_parser.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ static void log_span_as_hex(const LogLevel level, const std::span<const uint8_t>
2222
}
2323
}
2424

25-
static bool is_mbus_short_frame(const std::span<const uint8_t> data) {
26-
if (data.size() < 5 || data[0] != 0x10 || data[4] != 0x16) return false;
27-
return static_cast<uint8_t>(data[1] + data[2]) == data[3];
28-
}
29-
3025
DlmsParser::DlmsParser(DlmsDataCallback dlmsDataCallback, Aes128GcmDecryptor* decryptor) : decryptor_(decryptor), axdr_parser_(std::move(dlmsDataCallback)) {}
3126

3227
void DlmsParser::set_skip_crc_check(const bool skip) {

src/dlms_parser/hdlc_decoder.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "hdlc_decoder.h"
2+
#include "utils.h"
23
#include "log.h"
34
#include <algorithm>
45

@@ -62,13 +63,20 @@ static size_t address_length(const std::span<const uint8_t> p) {
6263
static constexpr uint8_t HDLC_FLAG = 0x7E;
6364
static constexpr uint8_t HDLC_SEG_BIT = 0x08; // bit 3 of frame-type byte: "more frames follow"
6465

65-
std::span<uint8_t> decode_hdlc_frames_in_place(std::span<uint8_t> buf, bool skip_crc_check) {
66+
std::span<uint8_t> decode_hdlc_frames_in_place(std::span<uint8_t> buf, const bool skip_crc_check) {
6667
size_t read_offset = 0;
6768
size_t write_offset = 0;
6869
bool is_first = true;
6970

7071
do {
7172
const auto remaining = buf.subspan(read_offset);
73+
74+
if (is_mbus_short_frame(remaining)) {
75+
Logger::log(LogLevel::VERBOSE, "HDLC: skipping M-Bus short frame at offset %zu", read_offset);
76+
read_offset += 5;
77+
continue;
78+
}
79+
7280
if (remaining.size() < 4 || remaining[0] != HDLC_FLAG) {
7381
Logger::log(LogLevel::WARNING, "HDLC: invalid frame at offset %zu", read_offset);
7482
return {};

src/dlms_parser/hdlc_decoder.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ namespace dlms_parser {
1616
// - LLC header stripped if present: {0xE6, 0xE7, 0x00} or {0xE6, 0xE6, 0x00}
1717
// - FCS: CRC16/IBM-SDLC over frame[1..before_FCS]
1818
//
19-
// In-place decode: extracts and concatenates payloads from all HDLC frames
20-
// in buf, writing them sequentially to buf[0..]. Returns a subspan of buf, empty on error.
19+
// In-place decode: extracts and concatenates payloads from all HDLC frames.
20+
// Ignores valid M-Bus short frames if they appear before, between, or after HDLC frames.
21+
// Returns a subspan of buf, empty on error.
2122
std::span<uint8_t> decode_hdlc_frames_in_place(std::span<uint8_t> buf, bool skip_crc_check = false);
2223

2324
}

src/dlms_parser/utils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,9 @@ uint32_t read_ber_length(std::span<const uint8_t> buf, size_t& pos);
7979
int get_data_type_size(DlmsDataType type);
8080
bool is_value_data_type(DlmsDataType type);
8181

82+
inline bool is_mbus_short_frame(const std::span<const uint8_t> data) {
83+
if (data.size() < 5 || data[0] != 0x10 || data[4] != 0x16) return false;
84+
return static_cast<uint8_t>(data[1] + data[2]) == data[3];
85+
}
86+
8287
}

tests/test_hdlc_decoder.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,39 @@ TEST_CASE_FIXTURE(LogFixture, "HDLC Decoder - Payload Decoding (decode)") {
114114
CHECK(multi_frame[4] == 0xEE);
115115
}
116116

117+
SUBCASE("M-Bus short frames are ignored before, between, and after HDLC frames") {
118+
constexpr uint8_t mbus_short_frame[] = {0x10, 0x40, 0x01, 0x41, 0x16};
119+
std::vector<uint8_t> transmission(std::begin(mbus_short_frame), std::end(mbus_short_frame));
120+
121+
auto frame1 = BASE_FRAME;
122+
frame1.erase(frame1.begin() + 13);
123+
update_frame_length(frame1, true);
124+
125+
std::vector<uint8_t> frame2 = {
126+
0x7E, 0xA0, 0x00,
127+
0x03, 0x21, 0x93,
128+
0x11, 0x22,
129+
0xCC, 0xDD, 0xEE,
130+
0x33, 0x44,
131+
0x7E
132+
};
133+
update_frame_length(frame2);
134+
135+
transmission.insert(transmission.end(), frame1.begin(), frame1.end());
136+
transmission.insert(transmission.end(), std::begin(mbus_short_frame), std::end(mbus_short_frame));
137+
transmission.insert(transmission.end(), std::begin(mbus_short_frame), std::end(mbus_short_frame));
138+
transmission.insert(transmission.end(), frame2.begin(), frame2.end());
139+
transmission.insert(transmission.end(), std::begin(mbus_short_frame), std::end(mbus_short_frame));
140+
141+
const auto result = decode_hdlc_frames_in_place(transmission, true);
142+
REQUIRE(result.size() == 5);
143+
CHECK(result[0] == 0xAA);
144+
CHECK(result[1] == 0xBB);
145+
CHECK(result[2] == 0xCC);
146+
CHECK(result[3] == 0xDD);
147+
CHECK(result[4] == 0xEE);
148+
}
149+
117150
SUBCASE("LLC stripping ONLY occurs on first frame of segmented payload") {
118151
std::vector<uint8_t> multi_frame;
119152
auto frame1 = BASE_FRAME;
@@ -225,6 +258,13 @@ TEST_CASE_FIXTURE(LogFixture, "HDLC Decoder - Address Length Decoding") {
225258

226259
TEST_CASE_FIXTURE(LogFixture, "HDLC Decoder - Malformed Frame Handling") {
227260

261+
SUBCASE("M-Bus short frame with invalid checksum is not ignored") {
262+
std::vector<uint8_t> transmission = {0x10, 0x40, 0x01, 0x42, 0x16};
263+
transmission.insert(transmission.end(), BASE_FRAME.begin(), BASE_FRAME.end());
264+
265+
CHECK(decode_hdlc_frames_in_place(transmission, true).empty());
266+
}
267+
228268
SUBCASE("Length field mismatch vs buffer boundaries") {
229269
auto frame = BASE_FRAME;
230270
frame[2] += 2;

0 commit comments

Comments
 (0)