Skip to content

Commit e5a4330

Browse files
Tomer27czPolarGooselatonitaCopilot
authored
Self describing push (#12)
Co-authored-by: PolarGoose <35307286+PolarGoose@users.noreply.github.com> Co-authored-by: Anton Viktorov <anton.viktorov@live.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent dc3becd commit e5a4330

10 files changed

Lines changed: 384 additions & 49 deletions

README.md

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,18 @@ parser.load_default_patterns();
2626

2727
**Built-in patterns (available after calling `parser.load_default_patterns()`):**
2828

29-
| Name | Pattern | Priority | Typical use |
30-
|------------------------------------------|------------------|---------:|-------------------------------------------|
31-
| `classId-taggedObis-scaler-value` | `TC,TO,TS,TV` | 10 | class ID, tagged OBIS, scaler, value |
32-
| `taggedObis-value-scalerUnit` | `TO,TV,TSU` | 20 | tagged OBIS, value, scaler-unit structure |
33-
| `value-classId-scalerUnit-taggedObis` | `TV,TC,TSU,TO` | 30 | value first, class ID, scaler-unit, OBIS |
34-
| `zpaAidon-untaggedLayout` | `ADV` | 40 | untagged ZPA/Aidon-style layouts |
35-
| `structuredObis-value-scalerUnit` | `S(TO, TV, TSU)` | 50 | OBIS, value, scaler-unit structure |
36-
| `structuredObis-value` | `S(TO, TV)` | 60 | OBIS and value structure |
37-
| `flatObis-valuePair` | `TO, TV` | 70 | flat OBIS + value pairs |
38-
| `firstElement-dateTime` | `F, S(TO, TDTM)` | 80 | first-element date-time structure |
39-
| `swappedTagObis-value-scalerUnit` | `TOW, TV, TSU` | 90 | swapped-tag OBIS, value, scaler-unit |
29+
| Name | Pattern | Priority |
30+
|------------------------------------------|------------------|---------:|
31+
| `SelfDescribing` | `SelfDesc` | 10 |
32+
| `classId-taggedObis-scaler-value` | `TC,TO,TS,TV` | 20 |
33+
| `taggedObis-value-scalerUnit` | `TO,TV,TSU` | 30 |
34+
| `value-classId-scalerUnit-taggedObis` | `TV,TC,TSU,TO` | 40 |
35+
| `zpaAidon-untaggedLayout` | `ADV` | 50 |
36+
| `structuredObis-value-scalerUnit` | `S(TO, TV, TSU)` | 60 |
37+
| `structuredObis-value` | `S(TO, TV)` | 70 |
38+
| `flatObis-valuePair` | `TO, TV` | 80 |
39+
| `firstElement-dateTime` | `F, S(TO, TDTM)` | 90 |
40+
| `swappedTagObis-value-scalerUnit` | `TOW, TV, TSU` | 100 |
4041

4142
**Registering Custom Patterns:**
4243

@@ -71,26 +72,27 @@ parser.register_pattern("TOW, TV, TSU"); // Landis+Gyr swapped OBIS
7172
```
7273

7374
### Token reference
74-
| Token | Meaning | Hex example |
75-
|----------------|--------------------------------------------|---------------------------------|
76-
| `F` | first element guard | position check only |
77-
| `L` | last element guard | position check only |
78-
| `C` | class ID, 2-byte uint16 without tag | `00 03` |
79-
| `TC` | tagged class ID | `12 00 03` |
80-
| `O` | OBIS code, 6-byte octet string without tag | `01 00 01 08 00 FF` |
81-
| `TO` | tagged OBIS code | `09 06 01 00 01 08 00 FF` |
82-
| `TOW` | tagged OBIS with swapped tag bytes | `06 09 01 00 1F 07 00 FF` |
83-
| `A` | attribute index, 1-byte uint8 without tag | `02` |
84-
| `TA` | tagged attribute | `11 02` or `0F 02` |
85-
| `V` / `TV` | generic value | `06 00 00 07 A4` |
86-
| `TSTR` | tagged string-like value | `09 08 38 34 38 39 35 31 32 36` |
87-
| `TDTM` | tagged 12-byte date-time value | `19 ...` or `09 0C ...` |
88-
| `TS` | tagged scaler | `0F FF` |
89-
| `TU` | tagged unit enum | `16 23` |
90-
| `TSU` | tagged scaler-unit pair | `02 02 0F FF 16 23` |
91-
| `S(x, y, ...)` | inline sub-structure | `02 03` |
92-
| `DN` | descend into nested structure | control token |
93-
| `UP` | return from nested structure | control token |
75+
| Token | Meaning | Hex example |
76+
|----------------|------------------------------------------------|---------------------------------|
77+
| `SelfDesc` | array of value descriptions followed by values | definitions array + values |
78+
| `F` | first element guard | position check only |
79+
| `L` | last element guard | position check only |
80+
| `C` | class ID, 2-byte uint16 without tag | `00 03` |
81+
| `TC` | tagged class ID | `12 00 03` |
82+
| `O` | OBIS code, 6-byte octet string without tag | `01 00 01 08 00 FF` |
83+
| `TO` | tagged OBIS code | `09 06 01 00 01 08 00 FF` |
84+
| `TOW` | tagged OBIS with swapped tag bytes | `06 09 01 00 1F 07 00 FF` |
85+
| `A` | attribute index, 1-byte uint8 without tag | `02` |
86+
| `TA` | tagged attribute | `11 02` or `0F 02` |
87+
| `V` / `TV` | generic value | `06 00 00 07 A4` |
88+
| `TSTR` | tagged string-like value | `09 08 38 34 38 39 35 31 32 36` |
89+
| `TDTM` | tagged 12-byte date-time value | `19 ...` or `09 0C ...` |
90+
| `TS` | tagged scaler | `0F FF` |
91+
| `TU` | tagged unit enum | `16 23` |
92+
| `TSU` | tagged scaler-unit pair | `02 02 0F FF 16 23` |
93+
| `S(x, y, ...)` | inline sub-structure | `02 03` |
94+
| `DN` | descend into nested structure | control token |
95+
| `UP` | return from nested structure | control token |
9496

9597
## API Reference
9698

src/dlms_parser/axdr_parser.cpp

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ bool AxdrParser::parse_sequence_(const uint8_t type, const uint8_t depth) {
142142
while (elements_consumed < elements_count) {
143143
const size_t original_position = this->pos_;
144144

145-
if (this->try_match_patterns_(elements_consumed, elements_count)) {
145+
if (this->try_match_patterns_(type, elements_consumed, elements_count)) {
146146
elements_consumed = static_cast<uint8_t>(
147147
elements_consumed + (this->last_pattern_elements_consumed_ ? this->last_pattern_elements_consumed_ : 1));
148148
this->last_pattern_elements_consumed_ = 0;
@@ -225,11 +225,12 @@ bool AxdrParser::capture_generic_value_(AxdrCaptures& c) {
225225
return true;
226226
}
227227

228-
bool AxdrParser::try_match_patterns_(const uint8_t elem_idx, const uint8_t elem_count) {
228+
bool AxdrParser::try_match_patterns_(const uint8_t container_type, const uint8_t elem_idx,
229+
const uint8_t elem_count) {
229230
for (size_t i = 0; i < this->patterns_count_; ++i) {
230231
const auto& p = this->patterns_[i];
231232
const size_t saved_position = this->pos_;
232-
if (uint8_t consumed = 0; this->match_pattern_(elem_idx, elem_count, p, consumed)) {
233+
if (uint8_t consumed = 0; this->match_pattern_(container_type, elem_idx, elem_count, p, consumed)) {
233234
this->last_pattern_elements_consumed_ = consumed;
234235
return true;
235236
}
@@ -238,8 +239,84 @@ bool AxdrParser::try_match_patterns_(const uint8_t elem_idx, const uint8_t elem_
238239
return false;
239240
}
240241

241-
bool AxdrParser::match_pattern_(const uint8_t elem_idx, const uint8_t elem_count, const AxdrDescriptorPattern& pat,
242-
uint8_t& consumed) {
242+
bool AxdrParser::parse_self_describing_(const uint8_t container_type, const uint8_t elem_idx,
243+
const uint8_t elem_count, const AxdrDescriptorPattern& pat,
244+
uint8_t& consumed) {
245+
// SelfDesc is a whole-container matcher; mixing it with other tokens would ignore them.
246+
if (pat.steps[0].type != AxdrTokenType::SELF_DESC || pat.steps[1].type != AxdrTokenType::END_OF_PATTERN) return false;
247+
if (container_type != DLMS_DATA_TYPE_STRUCTURE || elem_idx != 0 || elem_count == 0) return false;
248+
if (this->read_byte_() != DLMS_DATA_TYPE_ARRAY) return false;
249+
// Short-form length only (<=127 descriptors); BER long-form not handled - capped below anyway.
250+
const size_t array_elements = this->read_byte_();
251+
if (array_elements == 0xFF) return false;
252+
253+
// The first outer element is the definitions array. All remaining elements are values.
254+
// MAX_SELF_DESC_OBJECTS bounds both the guard and the descriptor buffers - keep them tied.
255+
constexpr size_t MAX_SELF_DESC_OBJECTS = 64;
256+
const size_t num_values = elem_count - 1;
257+
if (num_values > array_elements) return false;
258+
if (array_elements > MAX_SELF_DESC_OBJECTS) {
259+
Logger::log(LogLevel::WARNING, "SelfDesc: %zu descriptors exceeds limit %zu - skipping",
260+
array_elements, MAX_SELF_DESC_OBJECTS);
261+
return false;
262+
}
263+
const size_t offset = array_elements - num_values;
264+
265+
std::array<std::array<uint8_t, 6>, MAX_SELF_DESC_OBJECTS> obis_list{};
266+
std::array<uint16_t, MAX_SELF_DESC_OBJECTS> class_id_list{};
267+
268+
for (size_t i = 0; i < array_elements; i++) {
269+
if (this->pos_ + 18 > this->buffer_.size()) return false;
270+
if (this->read_byte_() != DLMS_DATA_TYPE_STRUCTURE || this->read_byte_() != 4) return false;
271+
272+
if (this->read_byte_() != DLMS_DATA_TYPE_UINT16) return false;
273+
class_id_list[i] = this->read_u16_();
274+
275+
if (this->read_byte_() != DLMS_DATA_TYPE_OCTET_STRING || this->read_byte_() != 6) return false;
276+
for (size_t j = 0; j < 6; j++) {
277+
obis_list[i][j] = this->read_byte_();
278+
}
279+
280+
const auto attr_type = this->read_byte_();
281+
if (attr_type != DLMS_DATA_TYPE_INT8 && attr_type != DLMS_DATA_TYPE_UINT8) return false;
282+
const uint8_t attr = this->read_byte_();
283+
if (attr == 0) return false;
284+
285+
if (this->read_byte_() != DLMS_DATA_TYPE_UINT16) return false;
286+
this->read_u16_();
287+
}
288+
289+
// The push_object_list starts with the Push-setup object (IC 40), which has no value -
290+
// hence more descriptors than values. Verify the surplus leading descriptors are really
291+
// Push-setup objects; otherwise bail rather than mis-align every OBIS with the wrong value.
292+
constexpr uint16_t IC_PUSH_SETUP = 40;
293+
for (size_t i = 0; i < offset; i++) {
294+
if (class_id_list[i] != IC_PUSH_SETUP) {
295+
Logger::log(LogLevel::WARNING,
296+
"SelfDesc: %zu surplus descriptor(s), but def[%zu] is class %u (not Push-setup 40) - skipping",
297+
offset, i, class_id_list[i]);
298+
return false;
299+
}
300+
}
301+
302+
AxdrCaptures cap{};
303+
for (size_t i = 0; i < num_values; i++) {
304+
const size_t definition_idx = i + offset;
305+
cap = {};
306+
cap.elem_idx = static_cast<uint32_t>(this->pos_);
307+
cap.class_id = class_id_list[definition_idx];
308+
cap.obis = std::span<const uint8_t>(obis_list[definition_idx]);
309+
310+
if (!this->capture_generic_value_(cap)) return false;
311+
this->emit_object_(pat, cap);
312+
}
313+
314+
consumed = elem_count;
315+
return true;
316+
}
317+
318+
bool AxdrParser::match_pattern_(const uint8_t container_type, const uint8_t elem_idx, const uint8_t elem_count,
319+
const AxdrDescriptorPattern& pat, uint8_t& consumed) {
243320
AxdrCaptures cap{};
244321
consumed = 0;
245322
uint8_t level = 0;
@@ -345,6 +422,9 @@ bool AxdrParser::match_pattern_(const uint8_t elem_idx, const uint8_t elem_count
345422
cap.has_scaler_unit = true;
346423
consume_one();
347424
break;
425+
case AxdrTokenType::SELF_DESC: {
426+
return this->parse_self_describing_(container_type, elem_idx, elem_count, pat, consumed);
427+
}
348428
case AxdrTokenType::GOING_DOWN: level++; break;
349429
case AxdrTokenType::GOING_UP: level--; break;
350430
case AxdrTokenType::END_OF_PATTERN: break;
@@ -483,7 +563,8 @@ AxdrDescriptorPattern& AxdrParser::register_pattern_dsl_(const char* name, const
483563
};
484564

485565
auto process_simple_token = [&](const std::string_view tok) {
486-
if (tok == "F") add_step(AxdrTokenType::EXPECT_TO_BE_FIRST);
566+
if (tok == "SelfDesc") add_step(AxdrTokenType::SELF_DESC);
567+
else if (tok == "F") add_step(AxdrTokenType::EXPECT_TO_BE_FIRST);
487568
else if (tok == "L") add_step(AxdrTokenType::EXPECT_TO_BE_LAST);
488569
else if (tok == "C") add_step(AxdrTokenType::EXPECT_CLASS_ID_UNTAGGED);
489570
else if (tok == "TC") {

src/dlms_parser/axdr_parser.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ enum class AxdrTokenType : uint8_t {
2525
EXPECT_STRUCTURE_N,
2626
EXPECT_SCALER_TAGGED,
2727
EXPECT_UNIT_ENUM_TAGGED,
28+
SELF_DESC,
2829
GOING_DOWN,
2930
GOING_UP,
3031
END_OF_PATTERN = 0xFF
@@ -111,8 +112,11 @@ class AxdrParser final : NonCopyableAndNonMovable {
111112
// Pattern matching
112113
bool test_if_date_time_12b_(std::span<const uint8_t> buf = {}) const;
113114
bool capture_generic_value_(AxdrCaptures& c);
114-
bool try_match_patterns_(uint8_t elem_idx, uint8_t elem_count);
115-
bool match_pattern_(uint8_t elem_idx, uint8_t elem_count, const AxdrDescriptorPattern& pat, uint8_t& consumed);
115+
bool try_match_patterns_(uint8_t container_type, uint8_t elem_idx, uint8_t elem_count);
116+
bool parse_self_describing_(uint8_t container_type, uint8_t elem_idx, uint8_t elem_count,
117+
const AxdrDescriptorPattern& pat, uint8_t& consumed);
118+
bool match_pattern_(uint8_t container_type, uint8_t elem_idx, uint8_t elem_count,
119+
const AxdrDescriptorPattern& pat, uint8_t& consumed);
116120
static float apply_scaler(float value, int8_t scaler);
117121
void emit_object_(const AxdrDescriptorPattern& pat, const AxdrCaptures& c);
118122
};

src/dlms_parser/dlms_parser.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ void DlmsParser::set_authentication_key(const Aes128GcmAuthenticationKey& key) c
3838

3939
void DlmsParser::load_default_patterns() {
4040
axdr_parser_.clear_patterns();
41-
axdr_parser_.register_pattern("classId-taggedObis-scaler-value", "TC,TO,TS,TV", 10);
42-
axdr_parser_.register_pattern("taggedObis-value-scalerUnit", "TO,TV,TSU", 20);
43-
axdr_parser_.register_pattern("value-classId-scalerUnit-taggedObis", "TV,TC,TSU,TO", 30);
44-
axdr_parser_.register_pattern("zpaAidon-untaggedLayout", "ADV", 40);
45-
axdr_parser_.register_pattern("structuredObis-value-scalerUnit", "S(TO, TV, TSU)", 50);
46-
axdr_parser_.register_pattern("structuredObis-value", "S(TO, TV)", 60);
47-
axdr_parser_.register_pattern("flatObis-valuePair", "TO, TV", 70);
48-
axdr_parser_.register_pattern("firstElement-dateTime", "F, S(TO, TDTM)", 80);
49-
axdr_parser_.register_pattern("swappedTagObis-value-scalerUnit", "TOW, TV, TSU", 90);
41+
axdr_parser_.register_pattern("SelfDescribing", "SelfDesc", 10);
42+
axdr_parser_.register_pattern("classId-taggedObis-scaler-value", "TC,TO,TS,TV", 20);
43+
axdr_parser_.register_pattern("taggedObis-value-scalerUnit", "TO,TV,TSU", 30);
44+
axdr_parser_.register_pattern("value-classId-scalerUnit-taggedObis", "TV,TC,TSU,TO", 40);
45+
axdr_parser_.register_pattern("zpaAidon-untaggedLayout", "ADV", 50);
46+
axdr_parser_.register_pattern("structuredObis-value-scalerUnit", "S(TO, TV, TSU)", 60);
47+
axdr_parser_.register_pattern("structuredObis-value", "S(TO, TV)", 70);
48+
axdr_parser_.register_pattern("flatObis-valuePair", "TO, TV", 80);
49+
axdr_parser_.register_pattern("firstElement-dateTime", "F, S(TO, TDTM)", 90);
50+
axdr_parser_.register_pattern("swappedTagObis-value-scalerUnit", "TOW, TV, TSU", 100);
5051
}
5152

5253
void DlmsParser::register_pattern(const char* dsl) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7E A0 84 CE FF 03 13 12 8B E6 E7 00 E0 40 00 01 00 00 70 0F 00 08 B6 6E 0C 07 EA 06 08 01 12 2E 02 FF 80 00 00 02 15 01 15 02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 02 12 00 00 02 04 12 00 08 09 06 00 00 01 00 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 15 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 29 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 3D 07 00 FF 0F 02 12 00 00 7A 83 7E 7E A0 7D CE FF 03 13 D0 45 E0 40 00 02 00 00 6C 02 04 12 00 03 09 06 01 00 16 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 2A 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 3E 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 01 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 02 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 03 07 00 FF 0F 02 12 00 00 6D EA 7E 7E A0 7D CE FF 03 13 D0 45 E0 40 00 03 00 00 6C 02 04 12 00 03 09 06 01 00 04 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 0D 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 1F 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 33 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 47 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 20 07 00 FF 0F 02 12 00 00 45 91 7E 7E A0 85 CE FF 03 13 56 80 E0 40 00 04 00 00 74 02 04 12 00 03 09 06 01 00 34 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 48 07 00 FF 0F 02 12 00 00 02 04 12 00 03 09 06 01 00 0E 07 00 FF 0F 02 12 00 00 02 04 12 00 01 09 06 00 00 60 03 00 FF 0F 02 12 00 00 09 0C 07 EA 06 08 01 12 2E 04 FF 80 00 80 06 00 00 00 49 06 00 00 00 22 06 00 00 00 50 06 00 00 00 00 06 00 00 00 00 06 00 00 00 00 57 92 7E 7E A0 42 CE FF 03 13 FD FB E0 C0 00 05 00 00 31 06 00 00 00 BB 06 00 00 00 00 06 00 00 00 00 06 00 00 02 6E 12 01 1E 12 00 82 12 00 4A 12 00 59 12 00 EE 12 00 EF 12 00 EE 12 00 32 06 00 00 00 0B 74 F7 7E
32.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)