@@ -1473,7 +1473,15 @@ type downlinkAttemptResult struct {
14731473 DownlinkTaskUpdateStrategy downlinkTaskUpdateStrategy
14741474}
14751475
1476- func (ns * NetworkServer ) attemptClassADataDownlink (ctx context.Context , dev * ttnpb.EndDevice , phy * band.Band , fp * frequencyplans.FrequencyPlan , slot * classADownlinkSlot , maxUpLength uint16 ) downlinkAttemptResult {
1476+ func (ns * NetworkServer ) attemptClassADataDownlink ( // nolint:gocyclo
1477+ ctx context.Context ,
1478+ dev * ttnpb.EndDevice ,
1479+ phy * band.Band ,
1480+ fp * frequencyplans.FrequencyPlan ,
1481+ slot * classADownlinkSlot ,
1482+ maxUpLength uint16 ,
1483+ profile * ttnpb.MACSettings ,
1484+ ) downlinkAttemptResult {
14771485 ctx = events .ContextWithCorrelationID (ctx , slot .Uplink .CorrelationIds ... )
14781486 if ! dev .MacState .RxWindowsAvailable {
14791487 log .FromContext (ctx ).Error ("RX windows not available, skip class A downlink slot" )
@@ -1661,6 +1669,16 @@ func (ns *NetworkServer) attemptClassADataDownlink(ctx context.Context, dev *ttn
16611669 dev .Session .QueuedApplicationDownlinks = dev .Session .QueuedApplicationDownlinks [:0 :0 ]
16621670 }
16631671 recordDataDownlink (dev , genState , genDown .NeedsMACAnswer , down , ns .defaultMACSettings )
1672+ if genState .ApplicationDownlink != nil &&
1673+ genState .ApplicationDownlink .Confirmed &&
1674+ dev .MacState .DeviceClass == ttnpb .Class_CLASS_C {
1675+ timeout := mac .DeviceClassCTimeout (dev , ns .defaultMACSettings , profile )
1676+ taskAt := time .Now ().UTC ().Add (timeout )
1677+ log .FromContext (ctx ).WithField ("start_at" , taskAt ).Debug ("Add pending downlink task" )
1678+ if err := ns .pendingDownlinkTasks .Add (ctx , dev .Ids , taskAt , true ); err != nil {
1679+ log .FromContext (ctx ).WithError (err ).Warn ("Failed to add pending downlink task" )
1680+ }
1681+ }
16641682 dev .MacState .PendingRelayDownlink = nil
16651683 return downlinkAttemptResult {
16661684 SetPaths : ttnpb .AddFields (sets ,
@@ -1679,7 +1697,15 @@ func (ns *NetworkServer) attemptClassADataDownlink(ctx context.Context, dev *ttn
16791697 }
16801698}
16811699
1682- func (ns * NetworkServer ) attemptNetworkInitiatedDataDownlink (ctx context.Context , dev * ttnpb.EndDevice , phy * band.Band , fp * frequencyplans.FrequencyPlan , slot * networkInitiatedDownlinkSlot , maxUpLength uint16 ) downlinkAttemptResult {
1700+ func (ns * NetworkServer ) attemptNetworkInitiatedDataDownlink ( // nolint:gocyclo
1701+ ctx context.Context ,
1702+ dev * ttnpb.EndDevice ,
1703+ phy * band.Band ,
1704+ fp * frequencyplans.FrequencyPlan ,
1705+ slot * networkInitiatedDownlinkSlot ,
1706+ maxUpLength uint16 ,
1707+ profile * ttnpb.MACSettings ,
1708+ ) downlinkAttemptResult {
16831709 var drIdx ttnpb.DataRateIndex
16841710 var freq uint64
16851711 switch slot .Class {
@@ -1890,6 +1916,16 @@ func (ns *NetworkServer) attemptNetworkInitiatedDataDownlink(ctx context.Context
18901916 }
18911917
18921918 recordDataDownlink (dev , genState , genDown .NeedsMACAnswer , down , ns .defaultMACSettings )
1919+ if genState .ApplicationDownlink != nil &&
1920+ genState .ApplicationDownlink .Confirmed &&
1921+ dev .MacState .DeviceClass == ttnpb .Class_CLASS_C {
1922+ timeout := mac .DeviceClassCTimeout (dev , ns .defaultMACSettings , profile )
1923+ taskAt := time .Now ().UTC ().Add (timeout )
1924+ log .FromContext (ctx ).WithField ("start_at" , taskAt ).Debug ("Add pending downlink task" )
1925+ if err := ns .pendingDownlinkTasks .Add (ctx , dev .Ids , taskAt , true ); err != nil {
1926+ log .FromContext (ctx ).WithError (err ).Warn ("Failed to add pending downlink task" )
1927+ }
1928+ }
18931929 if genState .ApplicationDownlink != nil || genState .EvictDownlinkQueueIfScheduled {
18941930 sets = ttnpb .AddFields (sets , "session.queued_application_downlinks" )
18951931 }
@@ -2198,7 +2234,7 @@ func (ns *NetworkServer) processDownlinkTask(ctx context.Context, consumerID str
21982234 }
21992235 switch slot := v .(type ) {
22002236 case * classADownlinkSlot :
2201- a := ns .attemptClassADataDownlink (ctx , dev , phy , fp , slot , maxUpLength )
2237+ a := ns .attemptClassADataDownlink (ctx , dev , phy , fp , slot , maxUpLength , profile . GetMacSettings () )
22022238 queuedEvents = append (queuedEvents , a .QueuedEvents ... )
22032239 queuedApplicationUplinks = append (queuedApplicationUplinks , a .QueuedApplicationUplinks ... )
22042240 taskUpdateStrategy = a .DownlinkTaskUpdateStrategy
@@ -2229,7 +2265,7 @@ func (ns *NetworkServer) processDownlinkTask(ctx context.Context, consumerID str
22292265 earliestAt = time .Now ().Add (absoluteTimeSchedulingDelay / 2 )
22302266 continue
22312267 }
2232- a := ns .attemptNetworkInitiatedDataDownlink (ctx , dev , phy , fp , slot , maxUpLength )
2268+ a := ns .attemptNetworkInitiatedDataDownlink (ctx , dev , phy , fp , slot , maxUpLength , profile . GetMacSettings () )
22332269 queuedEvents = append (queuedEvents , a .QueuedEvents ... )
22342270 queuedApplicationUplinks = append (queuedApplicationUplinks , a .QueuedApplicationUplinks ... )
22352271 taskUpdateStrategy = a .DownlinkTaskUpdateStrategy
@@ -2273,3 +2309,90 @@ func (ns *NetworkServer) processDownlinkTask(ctx context.Context, consumerID str
22732309 }
22742310 return err
22752311}
2312+
2313+ const maxConfirmedDownlinkRetries = 8
2314+
2315+ func (ns * NetworkServer ) createProcessPendingDownlinkTask (consumerID string ) func (context.Context ) error {
2316+ return func (ctx context.Context ) error {
2317+ return ns .processPendingDownlinkTask (ctx , consumerID )
2318+ }
2319+ }
2320+
2321+ // processPendingDownlinkTask processes the most recent pending downlink task ready for execution,
2322+ // if such is available or wait until it is before processing it.
2323+ // NOTE: ctx.Done() is not guaranteed to be respected by processPendingDownlinkTask.
2324+ // The processPendingDownlinkTask receives the consumerID that will be used for popping
2325+ // from the pending downlink task queue.
2326+ func (ns * NetworkServer ) processPendingDownlinkTask (ctx context.Context , consumerID string ) error {
2327+ var setErr bool
2328+ err := ns .pendingDownlinkTasks .Pop (
2329+ ctx ,
2330+ consumerID ,
2331+ func (ctx context.Context , devID * ttnpb.EndDeviceIdentifiers , t time.Time ,
2332+ ) (time.Time , error ) {
2333+ ctx = log .NewContextWithFields (ctx , log .Fields (
2334+ "device_uid" , unique .ID (ctx , devID ),
2335+ "started_at" , time .Now ().UTC (),
2336+ ))
2337+ logger := log .FromContext (ctx )
2338+ logger .WithField ("start_at" , t ).Debug ("Process pending downlink task" )
2339+
2340+ dev , ctx , err := ns .devices .SetByID (ctx , devID .ApplicationIds , devID .DeviceId ,
2341+ []string {
2342+ "mac_state" ,
2343+ "session" ,
2344+ },
2345+ func (_ context.Context , dev * ttnpb.EndDevice ) (* ttnpb.EndDevice , []string , error ) {
2346+ if dev == nil {
2347+ logger .Warn ("Device not found" )
2348+ return nil , nil , nil
2349+ }
2350+
2351+ pendingAppDown := dev .MacState .GetPendingApplicationDownlink ()
2352+ if pendingAppDown != nil {
2353+ pendingAppDown .FCnt = dev .Session .LastNFCntDown + 1
2354+ pendingAppDown .ConfirmedRetry .Attempt ++
2355+
2356+ if pendingAppDown .ConfirmedRetry .Attempt > maxConfirmedDownlinkRetries {
2357+ dev .MacState .PendingApplicationDownlink = nil
2358+ logger .Warn ("Max confirmed downlink retries reached, drop pending application downlink" )
2359+ return dev , []string {
2360+ "mac_state.pending_application_downlink" ,
2361+ }, nil
2362+ }
2363+
2364+ // Enqueue the pending application downlink at the front of the queue.
2365+ // This preserves the order of other queued application downlinks.
2366+ // The pending application downlink will be processed first in the next downlink task.
2367+ // This is important for confirmed downlinks, as we need to ensure that the ACK is received
2368+ // before processing other downlinks.
2369+ dev .Session .QueuedApplicationDownlinks = append (
2370+ []* ttnpb.ApplicationDownlink {pendingAppDown },
2371+ dev .Session .QueuedApplicationDownlinks ... ,
2372+ )
2373+ dev .MacState .PendingApplicationDownlink = nil
2374+ }
2375+ return dev , []string {
2376+ "mac_state" ,
2377+ "session" ,
2378+ }, nil
2379+ },
2380+ )
2381+ if err != nil {
2382+ setErr = true
2383+ logger .WithError (err ).Error ("Failed to update device in registry" )
2384+ return time.Time {}, err
2385+ }
2386+
2387+ if err := ns .updateDataDownlinkTask (ctx , dev , time.Time {}); err != nil {
2388+ log .FromContext (ctx ).WithError (err ).Error (
2389+ "Failed to update downlink task queue after processing pending downlink" )
2390+ }
2391+
2392+ return time.Time {}, nil
2393+ })
2394+ if err != nil && ! setErr {
2395+ log .FromContext (ctx ).WithError (err ).Error ("Failed to pop entry from pending downlink task queue" )
2396+ }
2397+ return err
2398+ }
0 commit comments