Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Dark

Collapse
SHARUR
Community Wiki DLMS/COSEM Tools
Search Sign in

Why does setting DLMS execution_time (Class 22 / Class 20) with wildcards 0xFF or 0x00 trigger continuous 1Hz firing or 32-bit overflow bugs?

Scheduled Pinned Locked Moved General Discussion
dlmscosemclass-20class-22activity-calend
10 Posts 5 Posters 53 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
  • C Offline
    C Offline
    carlos_mendez
    wrote on last edited by
    #1

    [!WARNING]
    Technical Redaction & Safe Harbor Notice:
    All communication captures, system titles, and cryptographic parameters in this thread have been sanitized and neutralized. Real utility meter identifiers, production encryption keys (GUEK/GAK/MK), SIM IMSIs, and proprietary network envelopes have been substituted with standard IEC 62056 synthetic test vectors. Do not post unredacted production captures or live cryptographic keys.

    We are commissioning AMI firmware for multi-rate tariff switching and disconnect scheduling using Activity Calendar (Class 20, OBIS 0.0.13.0.0.255, Attribute 4 activate_passive_calendar_time) and Single Action Schedule (Class 22, OBIS 0.0.15.0.0.255, Attribute 2 execution_time).

    When utility operators attempt to disable or deactivate a pending schedule, they frequently transmit either:

    1. All-wildcards date/time: FF FF FF FF FF FF FF FF FF 80 00 00 (12 bytes),
    2. Zeroed date/time: 00 00 00 00 FF 00 00 00 00 80 00 00.

    However, on several batch meters running firmware v2.4.0, writing FF FF... causes the meter to execute the linked script action (e.g. Tariff Switch / Breaker Check) every single second in an infinite 1Hz loop, spiking MCU load to 100% and jamming the optical port! Writing 00 00... or an expired timestamp (e.g. 2020-01-01) immediately fires the script upon receiving the SET-Request!

    Here is the sanitized SET-Request transmitted over TCP 4059 wrapper for 0.0.13.0.0.255 Attribute 4:

    00 01 00 01 00 01 00 23 DB 08 53 48 41 00 00 00 00 01 30 00 00 00 12 A1 88 9F 33 22 10 44 88 99 C1 C3 01 C1 00 14 00 00 0D 00 00 FF 04 09 0C FF FF FF FF FF FF FF FF FF 80 00 00
    

    And the meter responds with SET-Response-Normal (0xC5 0x01 C1 00: success), immediately followed by the infinite execution loop.

    Our field operators assumed 0xFF was the universal DLMS way to disable scheduled execution. Why is this triggering 1Hz execution storms?

    1 Reply Last reply
    0
    • S Offline
      S Offline
      sarah_jenkins
      wrote on last edited by
      #2

      Carlos, your operators' assumption that 0xFF means 'disabled' in a COSEM date_time octet-string directly contradicts the standard.

      Per DLMS Blue Book Ed. 17 Section 4.1.6.1 (Date and Time Formats) and IEC 62056-62 Section 6.2.19 (Single Action Schedule), a 12-byte date_time octet-string is defined as:

      Byte 1-2: Year (High/Low) - 0xFFFF = not specified (wildcard: any year)
      Byte 3:   Month           - 0xFF   = not specified (wildcard: every month)
      Byte 4:   Day of month    - 0xFF   = not specified (wildcard: every day)
      Byte 5:   Day of week     - 0xFF   = not specified (wildcard: any weekday)
      Byte 6:   Hour            - 0xFF   = not specified (wildcard: every hour)
      Byte 7:   Minute          - 0xFF   = not specified (wildcard: every minute)
      Byte 8:   Second          - 0xFF   = not specified (wildcard: every second)
      Byte 9:   Hundredths      - 0xFF   = not specified
      Byte 10-11: Deviation     - 0x8000 = not specified
      Byte 12:  Clock Status    - 0x00
      

      In DLMS pattern matching, 0xFF is a Wildcard (meaning MATCH ALL). When every byte is set to 0xFF, the meter's cron/scheduler evaluates:
      Condition: (current_year == wildcard) AND (current_second == wildcard) => TRUE

      Because the condition evaluates to TRUE on every 1-second RTC tick, the scheduler fires the linked script action every second! Setting 0xFF across all fields creates an unconstrained periodic timer, not a disabled state.

      1 Reply Last reply
      0
      • E Offline
        E Offline
        elena_rostova
        wrote on last edited by
        #3

        Sarah's explanation of the 0xFF wildcard matching is 100% accurate. Now let's address why writing all zeros (0000-00-00) or past timestamps triggers immediate execution upon SET:

        In embedded RTOS firmware, the scheduler task evaluates task queues using comparative delta timestamps:

        // Typical flawed firmware scheduler check:
        if ((int32_t)(current_rtc_epoch - task->scheduled_epoch) >= 0 && !task->executed_flag) {
            execute_script(task->script_logical_name, task->script_selector);
            task->executed_flag = true;
        }
        

        When a past date (e.g. 2020-01-01 or zeroed date) is written, current_rtc_epoch - task->scheduled_epoch is a positive number (delta_t > 0). If the firmware clears executed_flag upon receiving the SET-Request without checking whether the newly written timestamp is already in the past, the scheduler evaluates delta_t >= 0 as an expired alarm that needs immediate catch-up execution!

        Furthermore, per DLMS Blue Book Ed. 17 Section 4.1.6.1 Table 6, Year = 0x0000 is illegal in Gregorian calendar conversions and causes standard C library functions (such as mktime()) to return (time_t)-1, triggering invalid epoch exceptions.

        1 Reply Last reply
        0
        • D Offline
          D Offline
          david_miller
          wrote on last edited by
          #4

          A common workaround that utilities attempt when they realize past dates fire immediately is to write an extreme far-future year, such as Year 9999 (0x270F-12-31-FF 23:59:59 00 80 00 00), hoping to push execution beyond the operational life of the meter.

          However, Year 9999 introduces a catastrophic 32-bit integer overflow bug:

          Most smart meter microcontrollers (such as ARM Cortex-M4 or Renesas RL78) implement standard POSIX time_t as a signed 32-bit integer:
          Max 32-bit Unix Epoch: 2^31 - 1 = 2,147,483,647 => 2038-01-19 03:14:07 UTC

          When Year 9999 (0x270F) is converted to Unix epoch seconds (~253,402,300,799), the 32-bit integer overflows into a negative value (~ -1,845,128,833). In two's complement, this represents a date in Year 1911!

          Because the negative epoch is less than current_rtc_epoch, the firmware immediately falls back into the 'past timestamp' trap that Elena described, firing the script instantaneously upon SET-Request!

          1 Reply Last reply
          0
          • A Offline
            A Offline
            alex_turner
            wrote on last edited by
            #5

            From the security and APDU framing perspective, there is also a critical validation detail in the date_time Octet-String format.

            Per DLMS Green Book 10th Ed Section 9.2.5.3 and IEC 62056-53 Section 8.4.2, when writing date_time objects over Security Suite 1 (0xDB General-Glo-Ciphering), the APDU contains an explicit 12-byte Octet-String (Tag 0x09 0x0C😞

            09 0C [YY YY] [MM] [DD] [WD] [hh] [mm] [ss] [hd] [TZ_H] [TZ_L] [Status]
            

            If the client sends invalid Daylight Saving Time status bits in Byte 12 (e.g. setting Bit 7 without configuring daylight_savings_begin in Class 8, OBIS 0.0.1.0.0.255), or invalid timezone deviation bytes (Bytes 10-11 != 0x8000 and outside [-720, +720]), a compliant COSEM parser must reject the SET-Request with:
            data-access-result: type-mismatch (0xC5 0x01 C1 03) or hardware-fault (0xC5 0x01 C1 01).

            Carlos, did the meters that threw temporary-failure have strict timezone validation enabled in their COSEM stack?

            1 Reply Last reply
            0
            • C Offline
              C Offline
              carlos_mendez
              wrote on last edited by
              #6

              Yes! Alex and David, that perfectly explains the firmware split we observed:

              1. Meter Batch A (running firmware v2.4.0) had the signed 32-bit time_t overflow. When we sent Year 9999, it overflowed to Year 1911 and triggered the disconnect breaker instantly.
              2. Meter Batch B had strict Blue Book A-XDR validation on Byte 10-12 and rejected FF FF... because Byte 10-11 was set to 0xFFFF instead of standard 0x8000 (not specified).
              3. When we sent FF FF FF FF FF FF FF FF FF 80 00 00, Batch A went into the 1Hz execution loop and clicked the internal load-shedding relay 60 times a minute until the power supply brownout occurred!

              Given these vendor traps, what is the exact, standard-compliant way to disable or safely park a schedule for both Class 20 (Activity Calendar) and Class 22 (Single Action Schedule)?

              1 Reply Last reply
              0
              • S Offline
                S Offline
                sarah_jenkins
                wrote on last edited by sarah_jenkins
                #7

                For Class 22 (Single Action Schedule, OBIS 0.0.15.0.0.255), the DLMS standard provides architectural mechanisms to disable execution, but there is a major cross-device firmware defect to watch out for:

                Per DLMS Blue Book Ed. 17 Section 4.4.4 and IEC 62056-62 Table 24, Interface Class 22 defines:

                • Attribute 2: execution_time (Array of date_time octet-strings)
                • Attribute 4: execution_type (Enum)

                Theoretical Disabling Methods vs Real-World Firmware Defects:

                1. Method A (Empty Array 01 00 per standard): Theoretically, writing an empty array (0x01 0x00) to Attribute 2 execution_time clears all scheduled triggers.
                2. The Real-World Device Defect: On many legacy firmware builds and strict schema validators (especially meters enforcing fixed-capacity static array descriptors min_entries >= 1), writing 0x01 0x00 is rejected with data-access-result: type-mismatch (0x03) or temporary-failure (0x02), making it impossible to clear via empty array on those specific devices!
                3. Method B (Execution Type Enum): Setting Attribute 4 to 0 (disabled) or 5 (inactive) also encounters firmware variance where Attribute 4 is read-only.

                Therefore, relying exclusively on 0x01 0x00 fails on non-standard hardware. What is the single universal method that succeeds across 100% of devices?

                1 Reply Last reply
                0
                • E Offline
                  E Offline
                  elena_rostova
                  wrote on last edited by elena_rostova
                  #8

                  Elena here. Sarah is completely right—in our meter interoperability lab, we found that ~15% of deployed meter models reject the empty array 0x01 0x00 on Class 22, and for Class 20 (Activity Calendar, OBIS 0.0.13.0.0.255), Attribute 4 (activate_passive_calendar_time) is a mandatory single 12-byte Octet-String that cannot be empty anyway!

                  The Universal Golden Standard: Writing a Confirmed Distant Future Timestamp (2099 / 2100)

                  Whether targeting Class 20 (Activity Calendar) or Class 22 (Single Action Schedule), the most robust, cross-vendor, fail-safe solution is setting a confirmed valid distant future date:

                  Recommended Timestamp: 2099-12-31-FF 23:59:59.00 (UTC not specified)

                  09 0C 08 33 0C 1F FF 17 3B 3B 00 80 00 00
                  

                  (Alternatively, 2100-01-01-FF 00:00:00 -> 09 0C 08 34 01 01 FF 00 00 00 00 80 00 00)

                  Why a Safe Distant Epoch (Year 2099/2100) is Universally Superior:

                  1. 100% Schema Acceptance: Conforms to standard 12-byte date_time Octet-String format, passing schema validators on all meters (even those rejecting empty arrays).
                  2. Safe from 32-bit Unix Overflow: Year 2099/2100 fits safely within 32-bit unsigned epoch ranges ($< 2^{32} - 1 = 4{,}294{,}967{,}295$), avoiding the Year 9999 negative wraparound.
                  3. Zero False Triggers: Pushes the trigger 70+ years beyond the physical operational lifespan of the meter.
                  4. No 1Hz Storms: All time components (Month, Day, Hour, Min, Sec) are explicit non-wildcard values, preventing 1-second periodic matching.
                  1 Reply Last reply
                  0
                  • A Offline
                    A Offline
                    alex_turner
                    wrote on last edited by
                    #9

                    Here is the verified, working SET-Request trace for setting 0.0.13.0.0.255 Attribute 4 (activate_passive_calendar_time) to 2099-12-31-FF 23:59:59 over TCP 4059 with Security Suite 1:

                    00 01 00 01 00 01 00 23 DB 08 53 48 41 00 00 00 00 01 30 00 00 00 45 B2 11 90 EE 44 19 82 77 1B C0 C3 01 C1 00 14 00 00 0D 00 00 FF 04 09 0C 08 33 0C 1F FF 17 3B 3B 00 80 00 00
                    

                    And here is the meter's successful SET-Response-Normal:

                    00 01 00 01 00 01 00 15 DB 08 53 48 41 00 00 00 00 01 30 00 00 00 4A C4 88 12 90 AA 34 11 02 C5 01 C1 00
                    

                    Dissecting the plain response:

                    • C5 01: SET-Response-Normal
                    • C1: Invoke-ID 1
                    • 00: data-access-result: success

                    With Year 2099 encoded as 0x0833 and deviation as 0x8000, the meter accepts the passive calendar time without scheduling false alarms or 1Hz execution storms.

                    1 Reply Last reply
                    0
                    • E Offline
                      E Offline
                      elena_rostova
                      wrote on last edited by elena_rostova
                      #10

                      To synthesize this for all AMI and meter firmware engineers, here is the production Python snippet using Gurux.DLMS demonstrating how to park both Class 20 and Class 22 schedules safely using the universal distant timestamp approach:

                      from gurux_dlms.objects import GXDLMSActivityCalendar, GXDLMSSingleActionSchedule
                      from gurux_dlms.GXDateTime import GXDateTime
                      from datetime import datetime
                      
                      # The Universal Safe Distant Epoch: 2099-12-31 23:59:59 (Year 2099 = 0x0833)
                      SAFE_DISTANT_EPOCH = GXDateTime(datetime(2099, 12, 31, 23, 59, 59))
                      
                      def park_single_action_schedule_universal(client, reader, obis="0.0.15.0.0.255"):
                          sas = GXDLMSSingleActionSchedule(obis)
                          # 1. Try standard empty array first
                          try:
                              sas.executionTime = []
                              reader.write(sas, 2)
                              print(f"[+] Class 22 {obis} cleared via empty array.")
                          except Exception as err:
                              # 2. Fallback to Universal Distant Epoch if meter rejects empty array
                              print(f"[!] Meter rejected empty array (err: {err}). Falling back to safe distant epoch...")
                              sas.executionTime = [SAFE_DISTANT_EPOCH]
                              reader.write(sas, 2)
                              print(f"[+] Class 22 {obis} successfully parked at 2099-12-31 23:59:59.")
                      
                      def park_activity_calendar_passive_time(client, reader, obis="0.0.13.0.0.255"):
                          calendar = GXDLMSActivityCalendar(obis)
                          calendar.time = SAFE_DISTANT_EPOCH
                          reader.write(calendar, 4) # Write Attribute 4 (activate_passive_calendar_time)
                          print(f"[+] Class 20 {obis} passive activation parked at 2099-12-31 23:59:59.")
                      

                      Summary Golden Rules for DLMS Schedule Management:

                      1. Never use 0xFF across all time bytes to disable: In DLMS Blue Book Section 4.1.6.1, 0xFF is a wildcard that triggers continuous 1Hz execution.
                      2. Never use past timestamps (2000-01-01 or all zeros): Flawed RTOS schedulers treat past timestamps as expired alarms and execute immediately upon SET.
                      3. Avoid Year 9999: Exceeds 32-bit signed Unix epoch ($2^{31}-1$) and overflows into negative timestamps (Year 1911).
                      4. Empty array 01 00 is valid on most devices, but rejected on ~15% of legacy models: Always support fallback to a valid distant future timestamp.
                      5. Universal Bulletproof Standard: Writing a confirmed valid distant future timestamp (e.g. 2099-12-31 23:59:59 / 0x0833 0x0C 0x1F FF 0x17 0x3B 0x3B 0x00 0x8000 0x00 or 2100-01-01 00:00:00) is 100% compliant and accepted across all vendor meters without side effects.
                      1 Reply Last reply
                      0

                      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                      With your input, this post could be even better 💗

                      Register Login
                      Reply
                      • Reply as topic
                      Log in to reply
                      • Oldest to Newest
                      • Newest to Oldest
                      • Most Votes


                      • Login

                      • Don't have an account? Register

                      • Login or register to search.
                      Powered by NodeBB Contributors
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • World
                      • Users
                      • Groups