@@ -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" ) {
0 commit comments