Summary
On AU915 devices declared with LoRaWAN MAC version 1.0.4 (mapped to Regional Parameters RP002-1.0.3), setting mac_settings.uplink_dwell_time = false on a device does not prevent the Network Server from treating the device's uplink dwell time as enabled. As a result:
- The network server sends a
TxParamSetupReq MAC command with uplink_dwell_time: true, contradicting the explicit device setting.
- After the device answers with
TxParamSetupAns (although locally it might have not set uplink dwell time value to true), the network server internal mac_state.current_parameters.uplink_dwell_time is unconditionally overwritten to true.
- Because AU915's
RP002-1.0.3 band definition makes DR0 and DR1's maximum MAC payload size 0 bytes when dwell time is true any subsequent real-world uplink sent at DR0 or DR1 is silently dropped by the Network Server before MIC validation with only a debug-level log line and no visible trace in TTN Console Live Data or any application facing error.
This makes it impossible, using currently exposed device settings to reliably operate an AU915 device at DR0/DR1 on The Things Stack Cloud when the device is registered under a LoRaWAN version that maps to RP002-1.0.3. The only practical workaround found is to register the device using LoRaWAN version 1.0.3 (which maps to the legacy RP001-1.0.3-RevA band definition), which sidesteps the bug, not because dwell time is enforced differently, but because that legacy band definition never made DR0–DR6 payload size dwell-time-dependent to begin with (it uses hardcoded constant payload sizes, unlike the spec-correct dwell-time-dependent table in RP002-1.0.3).
Steps to Reproduce
The following was independently reproduced and verified via ttn-lw-cli against a live device (eu1.cloud.thethings.network, TTS v3.36.0):
mac_settings.uplink_dwell_time explicitly set to false; confirmed via end-devices get --mac-settings.
get-default-mac-settings for AU_915_928_FSB_2 / RP002_V1_0_3 shows no default forcing uplink_dwell_time, ruling out a frequency-plan or PHY-version default override.
- Explicit
end-devices reset --mac-state performed (isolating from join-timing effects).
mac_state.desired_parameters.uplink_dwell_time reads true immediately after reset
TxParamSetupReq observed in Live Data with uplink_dwell_time: true
- After the device's
TxParamSetupAns, mac_state.current_parameters.uplink_dwell_time which briefly read false immediately post-reset was observed to flip to true
- AU915 device uplinks at DR0/DR1 fail to appear in Live Data at all after step 6
- Re-registering the same device under LoRaWAN version 1.0.3 (mapping to
RP001-1.0.3-RevA) restores DR0/DR1 uplink delivery
Current Result and Issue Cause
The issue spans four functions in pkg/networkserver, plus one clarifying data point in pkg/band.
1. DeviceDesiredUplinkDwellTime never checks mac_settings
File: pkg/networkserver/mac/mac.go
// Used to build CurrentParameters - correctly checks mac_settings
func DeviceUplinkDwellTime(
dev *ttnpb.EndDevice,
phy *band.Band,
defaults *ttnpb.MACSettings,
profile *ttnpb.MACSettings,
) *ttnpb.BoolValue {
switch {
case !phy.TxParamSetupReqSupport:
return nil
case profile.GetUplinkDwellTime() != nil:
return &ttnpb.BoolValue{Value: profile.UplinkDwellTime.Value}
case dev.GetMacSettings().GetUplinkDwellTime() != nil:
return &ttnpb.BoolValue{Value: dev.MacSettings.UplinkDwellTime.Value}
case defaults.GetUplinkDwellTime() != nil:
return &ttnpb.BoolValue{Value: defaults.UplinkDwellTime.Value}
default:
return nil
}
}
// Used to build DesiredParameters - CANNOT check mac_settings; wrong signature
func DeviceDesiredUplinkDwellTime(phy *band.Band, fp *frequencyplans.FrequencyPlan) *ttnpb.BoolValue {
switch {
case !phy.TxParamSetupReqSupport:
return nil
case fp.DwellTime.Uplinks != nil:
return &ttnpb.BoolValue{Value: *fp.DwellTime.Uplinks}
default:
return &ttnpb.BoolValue{Value: true}
}
}
DeviceDesiredUplinkDwellTime does not accept dev, defaults, or profile as parameters at all, so it is structurally unable to consult a device's mac_settings.uplink_dwell_time override. It only ever looks at the frequency plan's DwellTime.Uplinks field, falling back to a hardcoded true if that field is unset.
The AU_915_928_FSB_2 frequency plan (from TheThingsNetwork/lorawan-frequency-plans) only sets dwell-time.downlinks: false and leaves uplinks unset, so this function always falls through to the hardcoded default of true, regardless of any device-level setting.
The exact same defect exists in the sibling function DeviceDesiredDownlinkDwellTime.
2. NewState uses the broken function for DesiredParameters
File: pkg/networkserver/mac/mac.go, function NewState (called on every OTAA join and on end-devices reset --mac-state):
current := &ttnpb.MACParameters{
...
UplinkDwellTime: DeviceUplinkDwellTime(dev, phy, defaults, profile), // correct - respects mac_settings
...
}
desired := &ttnpb.MACParameters{
...
UplinkDwellTime: DeviceDesiredUplinkDwellTime(phy, fp), // broken - ignores mac_settings
...
}
This produces CurrentParameters.UplinkDwellTime = false (correctly reflecting the device's mac_settings override) alongside DesiredParameters.UplinkDwellTime = true (always, regardless of mac settings). Because current != desired, DeviceNeedsTxParamSetupReq (in pkg/networkserver/mac/tx_param_setup.go) returns true, and the NS enqueues a TxParamSetupReq MAC command with uplink_dwell_time: true in its payload even though the device explicitly asked for false.
3. HandleTxParamSetupAns blindly trusts the Ans and corrupts CurrentParameters
File: pkg/networkserver/mac/tx_param_setup.go
func HandleTxParamSetupAns(ctx context.Context, dev *ttnpb.EndDevice) (events.Builders, error) {
...
func(cmd *ttnpb.MACCommand) error {
req := cmd.GetTxParamSetupReq()
dev.MacState.CurrentParameters.MaxEirp = lorawan.DeviceEIRPToFloat32(req.MaxEirpIndex)
dev.MacState.CurrentParameters.DownlinkDwellTime = &ttnpb.BoolValue{Value: req.DownlinkDwellTime}
dev.MacState.CurrentParameters.UplinkDwellTime = &ttnpb.BoolValue{Value: req.UplinkDwellTime}
if lorawan.Float32ToDeviceEIRP(dev.MacState.DesiredParameters.MaxEirp) == req.MaxEirpIndex {
dev.MacState.DesiredParameters.MaxEirp = dev.MacState.CurrentParameters.MaxEirp
}
return nil
},
...
}
TxParamSetupAns, per the LoRaWAN specification, is a bare acknowledgment with no payload it carries no information about whether the device actually applied the requested dwell-time setting. This handler unconditionally copies the requested value (req.UplinkDwellTime) into CurrentParameters.UplinkDwellTime assuming full compliance because there is not acknowledge mechanism defined per the LoRaWAN specification.
This means that even a device that ignores the network's dwell-time request will have the Network Servers own state overwritten to falsely reflect the network's request rather than the device's actual behavior.
The net effect: even though NewState() initially sets CurrentParameters.UplinkDwellTime = false correctly this correct value is overwritten to true as soon as the device answers the very first TxParamSetupReq which happens within seconds of every join since it is generated as a mandatory MAC command.
4. The corrupted CurrentParameters.UplinkDwellTime silently drops uplinks
File: pkg/networkserver/grpc_gsrpc.go, function matchAndHandleDataUplink:
// NOTE: We assume no dwell time if current value unknown.
if macspec.IgnoreUplinksExceedingLengthLimit(dev.MacState.LorawanVersion) &&
len(up.RawPayload)-5 > int(dr.MaxMACPayloadSize(dev.MacState.CurrentParameters.UplinkDwellTime.GetValue())) {
log.FromContext(ctx).Debug("Uplink length exceeds maximum")
return nil, false, nil
}
dr.MaxMACPayloadSize(dwellTime) for AU915 DR0/DR1 (band AU_915_928_RP2_v1_0_3, pkg/band/au_915_928.go) is defined as:
ttnpb.DataRateIndex_DATA_RATE_0: makeLoRaDataRate(12, 125000, Cr4_5, makeDwellTimeMaxMACPayloadSizeFunc(59, 0)),
ttnpb.DataRateIndex_DATA_RATE_1: makeLoRaDataRate(11, 125000, Cr4_5, makeDwellTimeMaxMACPayloadSizeFunc(59, 0)),
With dwellTime = true, MaxMACPayloadSize returns 0 for both DR0 and DR1. This causes any real uplink to be dropped with no event in Live Data only a debug-level server log line which is not accessible by the user.
5. The RP002 Regional Parameters 1.0.3 revision A workaround, that is not really a fix
File: pkg/band/au_915_928.go, comparing the two band definitions:
// RP002-1.0.3 (spec-correct, dwell-time-dependent - exposed to the bug)
ttnpb.DataRateIndex_DATA_RATE_0: makeLoRaDataRate(12, 125000, Cr4_5, makeDwellTimeMaxMACPayloadSizeFunc(59, 0)),
// RP001-1.0.3-RevA (legacy, dwell-time-independent - immune to the bug)
ttnpb.DataRateIndex_DATA_RATE_0: makeLoRaDataRate(12, 125000, Cr4_5, makeConstMaxMACPayloadSizeFunc(59)),
makeConstMaxMACPayloadSizeFunc(59) ignores its dwellTime argument entirely and always returns 59. Registering the device as LoRaWAN Specification 1.0.3 (which maps to RP001-1.0.3-RevA) does not actually correct the dwell-time handling bug it simply uses an older band definition that never made DR0–DR6 payload size dwell-time-dependent in the first place and is therefore structurally immune to the corrupted CurrentParameters.UplinkDwellTime value. RP002-1.0.3 is the spec-correct implementation and is precisely why it is the version affected.
Expected Result
Uplink dwell time set in MAC settings to false should be honored throughout the device's MAC state, so DR0/DR1 uplinks are accepted rather than silently dropped regardless of the declared LoRaWAN version or associated Regional Parameters revision.
URL
https://eu1.cloud.thethings.network/console
Deployment
The Things Stack Cloud
The Things Stack Version
3.3.6
Client Name and Version
The Things Network Command-line Interface: ttn-lw-cli
Version: 3.36.0
Build date: 2026-04-03T08:54:04Z
Git commit: cba826ea5
Go version: go1.24.13
OS/Arch: linux/amd64
Other Information
No response
Proposed Fix
Change the function signatures to match their Device*DwellTime (current-parameters) counterparts, and apply the same precedence order (profile → dev.MacSettings → defaults → frequency plan → hardcoded default):
func DeviceDesiredUplinkDwellTime(
dev *ttnpb.EndDevice,
phy *band.Band,
fp *frequencyplans.FrequencyPlan,
defaults *ttnpb.MACSettings,
profile *ttnpb.MACSettings,
) *ttnpb.BoolValue {
switch {
case !phy.TxParamSetupReqSupport:
return nil
case profile.GetUplinkDwellTime() != nil:
return &ttnpb.BoolValue{Value: profile.UplinkDwellTime.Value}
case dev.GetMacSettings().GetUplinkDwellTime() != nil:
return &ttnpb.BoolValue{Value: dev.MacSettings.UplinkDwellTime.Value}
case defaults.GetUplinkDwellTime() != nil:
return &ttnpb.BoolValue{Value: defaults.UplinkDwellTime.Value}
case fp.DwellTime.Uplinks != nil:
return &ttnpb.BoolValue{Value: *fp.DwellTime.Uplinks}
default:
return &ttnpb.BoolValue{Value: true}
}
}
Update the call site in NewState() accordingly:
UplinkDwellTime: DeviceDesiredUplinkDwellTime(dev, phy, fp, defaults, profile),
Apply the identical change to DeviceDesiredDownlinkDwellTime.
4.1 alone resolves the reported symptom (uplinks being dropped) because it removes the root cause forcing an unnecessary and incorrect TxParamSetupReq in the first place. 4.2 addresses the deeper design gap around trusting an Ans that cannot carry compliance information, which affects any scenario where a device's actual dwell-time state differs from what the network most recently requested.
Contributing
Validation
Code of Conduct
Summary
On AU915 devices declared with LoRaWAN MAC version 1.0.4 (mapped to Regional Parameters
RP002-1.0.3), settingmac_settings.uplink_dwell_time = falseon a device does not prevent the Network Server from treating the device's uplink dwell time as enabled. As a result:TxParamSetupReqMAC command withuplink_dwell_time: true, contradicting the explicit device setting.TxParamSetupAns(although locally it might have not set uplink dwell time value to true), the network server internalmac_state.current_parameters.uplink_dwell_timeis unconditionally overwritten totrue.RP002-1.0.3band definition makes DR0 and DR1's maximum MAC payload size 0 bytes when dwell time istrueany subsequent real-world uplink sent at DR0 or DR1 is silently dropped by the Network Server before MIC validation with only a debug-level log line and no visible trace in TTN Console Live Data or any application facing error.This makes it impossible, using currently exposed device settings to reliably operate an AU915 device at DR0/DR1 on The Things Stack Cloud when the device is registered under a LoRaWAN version that maps to
RP002-1.0.3. The only practical workaround found is to register the device using LoRaWAN version 1.0.3 (which maps to the legacyRP001-1.0.3-RevAband definition), which sidesteps the bug, not because dwell time is enforced differently, but because that legacy band definition never made DR0–DR6 payload size dwell-time-dependent to begin with (it uses hardcoded constant payload sizes, unlike the spec-correct dwell-time-dependent table inRP002-1.0.3).Steps to Reproduce
The following was independently reproduced and verified via
ttn-lw-cliagainst a live device (eu1.cloud.thethings.network, TTS v3.36.0):mac_settings.uplink_dwell_timeexplicitly set tofalse; confirmed viaend-devices get --mac-settings.get-default-mac-settingsforAU_915_928_FSB_2/RP002_V1_0_3shows no default forcinguplink_dwell_time, ruling out a frequency-plan or PHY-version default override.end-devices reset --mac-stateperformed (isolating from join-timing effects).mac_state.desired_parameters.uplink_dwell_timereadstrueimmediately after resetTxParamSetupReqobserved in Live Data withuplink_dwell_time: trueTxParamSetupAns,mac_state.current_parameters.uplink_dwell_timewhich briefly readfalseimmediately post-reset was observed to flip totrueRP001-1.0.3-RevA) restores DR0/DR1 uplink deliveryCurrent Result and Issue Cause
The issue spans four functions in
pkg/networkserver, plus one clarifying data point inpkg/band.1.
DeviceDesiredUplinkDwellTimenever checksmac_settingsFile:
pkg/networkserver/mac/mac.goDeviceDesiredUplinkDwellTimedoes not acceptdev,defaults, orprofileas parameters at all, so it is structurally unable to consult a device'smac_settings.uplink_dwell_timeoverride. It only ever looks at the frequency plan'sDwellTime.Uplinksfield, falling back to a hardcodedtrueif that field is unset.The
AU_915_928_FSB_2frequency plan (fromTheThingsNetwork/lorawan-frequency-plans) only setsdwell-time.downlinks: falseand leavesuplinksunset, so this function always falls through to the hardcoded default oftrue, regardless of any device-level setting.The exact same defect exists in the sibling function
DeviceDesiredDownlinkDwellTime.2.
NewStateuses the broken function forDesiredParametersFile:
pkg/networkserver/mac/mac.go, functionNewState(called on every OTAA join and onend-devices reset --mac-state):This produces
CurrentParameters.UplinkDwellTime = false(correctly reflecting the device'smac_settingsoverride) alongsideDesiredParameters.UplinkDwellTime = true(always, regardless of mac settings). Becausecurrent != desired,DeviceNeedsTxParamSetupReq(inpkg/networkserver/mac/tx_param_setup.go) returnstrue, and the NS enqueues aTxParamSetupReqMAC command withuplink_dwell_time: truein its payload even though the device explicitly asked forfalse.3.
HandleTxParamSetupAnsblindly trusts the Ans and corruptsCurrentParametersFile:
pkg/networkserver/mac/tx_param_setup.goTxParamSetupAns, per the LoRaWAN specification, is a bare acknowledgment with no payload it carries no information about whether the device actually applied the requested dwell-time setting. This handler unconditionally copies the requested value (req.UplinkDwellTime) intoCurrentParameters.UplinkDwellTimeassuming full compliance because there is not acknowledge mechanism defined per the LoRaWAN specification.This means that even a device that ignores the network's dwell-time request will have the Network Servers own state overwritten to falsely reflect the network's request rather than the device's actual behavior.
The net effect: even though
NewState()initially setsCurrentParameters.UplinkDwellTime = falsecorrectly this correct value is overwritten totrueas soon as the device answers the very firstTxParamSetupReqwhich happens within seconds of every join since it is generated as a mandatory MAC command.4. The corrupted
CurrentParameters.UplinkDwellTimesilently drops uplinksFile:
pkg/networkserver/grpc_gsrpc.go, functionmatchAndHandleDataUplink:dr.MaxMACPayloadSize(dwellTime)for AU915 DR0/DR1 (bandAU_915_928_RP2_v1_0_3,pkg/band/au_915_928.go) is defined as:With
dwellTime = true,MaxMACPayloadSizereturns 0 for both DR0 and DR1. This causes any real uplink to be dropped with no event in Live Data only a debug-level server log line which is not accessible by the user.5. The RP002 Regional Parameters 1.0.3 revision A workaround, that is not really a fix
File:
pkg/band/au_915_928.go, comparing the two band definitions:makeConstMaxMACPayloadSizeFunc(59)ignores itsdwellTimeargument entirely and always returns59. Registering the device as LoRaWAN Specification 1.0.3 (which maps toRP001-1.0.3-RevA) does not actually correct the dwell-time handling bug it simply uses an older band definition that never made DR0–DR6 payload size dwell-time-dependent in the first place and is therefore structurally immune to the corruptedCurrentParameters.UplinkDwellTimevalue.RP002-1.0.3is the spec-correct implementation and is precisely why it is the version affected.Expected Result
Uplink dwell time set in MAC settings to
falseshould be honored throughout the device's MAC state, so DR0/DR1 uplinks are accepted rather than silently dropped regardless of the declared LoRaWAN version or associated Regional Parameters revision.URL
https://eu1.cloud.thethings.network/console
Deployment
The Things Stack Cloud
The Things Stack Version
3.3.6
Client Name and Version
Other Information
No response
Proposed Fix
Change the function signatures to match their
Device*DwellTime(current-parameters) counterparts, and apply the same precedence order (profile→dev.MacSettings→defaults→ frequency plan → hardcoded default):Update the call site in
NewState()accordingly:Apply the identical change to
DeviceDesiredDownlinkDwellTime.4.1 alone resolves the reported symptom (uplinks being dropped) because it removes the root cause forcing an unnecessary and incorrect
TxParamSetupReqin the first place. 4.2 addresses the deeper design gap around trusting an Ans that cannot carry compliance information, which affects any scenario where a device's actual dwell-time state differs from what the network most recently requested.Contributing
Validation
Code of Conduct