Skip to content

Multicast Class B/C sessions not stopped when fragmentation session completes #155

@aelbretonactility

Description

@aelbretonactility

Summary

When a fragmentation (FUOTA) session completes — either successfully or with failure — the
multicast sessions (Class B or Class C) that were serving it are not stopped. They continue
running until their server-assigned timeout expires, wasting battery and RF resources.

Affected components

  • lbm_lib/smtc_modem_core/lorawan_packages/fragmented_data_block_transport/v2.0.0/lorawan_fragmentation_package_v2.0.0.c
  • lbm_lib/smtc_modem_core/lorawan_packages/fragmented_data_block_transport/v1.0.0/lorawan_fragmentation_package_v1.0.0.c

Root cause

The fragmentation package fires SMTC_MODEM_EVENT_LORAWAN_FUOTA_DONE when all fragments are
received (or when decoding fails), but it makes no attempt to stop the multicast sessions that
delivered those fragments.

The Remote Multicast Setup package manages multicast session lifetime using a timer-only approach:
sessions are stopped when session_time + time_out expires
(lorawan_remote_multicast_setup_package_service_on_update, line 644 in v2.0.0). There is no
mechanism for the fragmentation package to trigger an early stop.

The fragmentation session data structure does contain mc_group_bit_mask (set during
FragSessionSetup, line 647 in v2.0.0), which identifies which multicast groups (0–3) were used.
This information is available at completion time but is never used for cleanup — it is only used
to validate incoming fragment frames (lines 795–822 in v2.0.0).

The problem exists symmetrically for both Class B and Class C multicast sessions, and in both
package versions (v1.0.0 and v2.0.0).

Impact

  • Class B: beacon search and ping slots remain active after FUOTA completes, consuming battery
    until the server-programmed session_time + time_out expires (can be minutes to hours).
  • Class C: the device stays in continuous receive mode after FUOTA completes instead of returning
    to Class A, increasing power consumption.
  • The SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_B /
    SMTC_MODEM_EVENT_NO_MORE_MULTICAST_SESSION_CLASS_C events are not fired at FUOTA completion,
    only at timer expiry, so the application cannot rely on them as a FUOTA-done signal.

Steps to reproduce

  1. Configure a multicast Class B or Class C session via Remote Multicast Setup package commands.
  2. Start a fragmentation session over that multicast session.
  3. Successfully receive all fragments (or exhaust the session with failures).
  4. Observe: SMTC_MODEM_EVENT_LORAWAN_FUOTA_DONE fires, but the multicast session and the
    associated radio activity (ping slots / Class C window) continue until the timer expires.

Fix

Design

Two new public functions are added to the Remote Multicast Setup package:

void lorawan_remote_multicast_setup_force_stop_class_b( uint8_t mc_group_id, uint8_t stack_id );
void lorawan_remote_multicast_setup_force_stop_class_c( uint8_t mc_group_id, uint8_t stack_id );

Each function:

  1. Guards on whether the given group actually has an active session of the respective class
    (stop_class_b[] or launch_class_b[] flag set); if not, returns immediately.
  2. Calls lorawan_api_multicast_b/c_stop_session() to stop the radio session.
  3. Clears the internal stop_class_b/c[] and launch_class_b/c[] flags to prevent a redundant
    timer-based stop later.
  4. Clears the corresponding STOP_CLASS_B/C_TASK and LAUNCH_CLASS_B/C_TASK bits from
    task_ctx_mask to neutralise any already-scheduled supervisor task for that group.
    The task will still fire at the original timer expiry but will find nothing to do — a harmless
    single extra wakeup (there is no task cancellation API in the supervisor).
  5. When all groups of that class are idle (tmp == 0), disables the transport mode
    (lorawan_class_b_management_enable(false) / lorawan_api_class_c_enabled(false)) and fires
    the NO_MORE_MULTICAST_SESSION_CLASS_B/C event.

The guard on step 1 is essential: the fragmentation package does not track which class was used
for delivery, so it calls both force_stop_class_b and force_stop_class_c for every group bit
set in mc_group_bit_mask. The guard ensures only the actually-active class takes effect.

Changes

lorawan_remote_multicast_setup_package.h — declarations added:

void lorawan_remote_multicast_setup_force_stop_class_b( uint8_t mc_group_id, uint8_t stack_id );
void lorawan_remote_multicast_setup_force_stop_class_c( uint8_t mc_group_id, uint8_t stack_id );

lorawan_remote_multicast_setup_package_v2.0.0.c and
lorawan_remote_multicast_setup_package_v1.0.0.c — implementations added at end of file.

lorawan_fragmentation_package_v2.0.0.c and
lorawan_fragmentation_package_v1.0.0.c — two changes each:

  1. Added #include "lorawan_remote_multicast_setup_package.h".

  2. At the fragmentation completion block (both FRAG_SESSION_FINISHED_SUCCESSFULLY and
    FRAG_SESSION_FAILED paths), before increment_asynchronous_msgnumber(FUOTA_DONE):

// Stop multicast sessions that were serving this fragmentation session.
// Both Class B and Class C are attempted for each group bit: the fragmentation
// package does not track which class was used, so each force_stop function
// silently skips groups that were not active for that class.
for( uint8_t i = 0; i < 4; i++ )
{
    if( ( frag_session_data[frag_index].frag_group_data.frag_session.mc_group_bit_mask &
          ( 1 << i ) ) != 0 )
    {
        lorawan_remote_multicast_setup_force_stop_class_b( i, stack_id );
        lorawan_remote_multicast_setup_force_stop_class_c( i, stack_id );
    }
}

FUOTA_DONE is fired after the teardown, so the application receives it with sessions already
stopped.

Notes

  • The new force_stop functions are also useful independently (e.g., application-initiated
    abort) and are intentionally exposed in the public header.
  • This fix is independent of this issue (transport mode not disabled on timer-based session stop),
    though both should be applied together for complete correctness.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions