From c7a26ee8b3a66674445ef812ccbb9fcbca5f6450 Mon Sep 17 00:00:00 2001 From: Eoin Motherway <25342760+YuKitsune@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:55:20 +1000 Subject: [PATCH] fix: Do not promote to CDA on receipt of DM63 --- ...ownlinkReceivedNotificationHandlerTests.cs | 69 +++++++++++++++++++ .../DownlinkReceivedNotificationHandler.cs | 6 +- source/CPDLCServer/Model/ControlMessages.cs | 5 ++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/source/CPDLCServer.Tests/Handlers/DownlinkReceivedNotificationHandlerTests.cs b/source/CPDLCServer.Tests/Handlers/DownlinkReceivedNotificationHandlerTests.cs index 2cb0f99..98e8ca8 100644 --- a/source/CPDLCServer.Tests/Handlers/DownlinkReceivedNotificationHandlerTests.cs +++ b/source/CPDLCServer.Tests/Handlers/DownlinkReceivedNotificationHandlerTests.cs @@ -224,6 +224,75 @@ public async Task Handle_PromotesAircraftToCurrentDataAuthorityOnFirstDownlink() Assert.Equal(Contracts.DataAuthorityState.CurrentDataAuthority, dto.DataAuthorityState); } + [Fact] + public async Task Handle_DoesNotPromoteToCurrentDataAuthority_WhenNotCurrentDataAuthorityMessageReceived() + { + // Arrange + var clock = new TestClock(); + var aircraftManager = new TestAircraftRepository(); + var aircraft = new AircraftConnection("UAL123", "hoppies-ybbb", "YBBB", DataAuthorityState.NextDataAuthority); + aircraft.RequestLogon(clock.UtcNow()); + aircraft.AcceptLogon(clock.UtcNow()); + await aircraftManager.Add(new(aircraft.Callsign, aircraft.AcarsClientId), aircraft, CancellationToken.None); + + var controllerManager = new TestControllerRepository(); + var controller = new ControllerInfo( + Guid.NewGuid(), + "ConnectionId-1", + "YBBB", + "BN-TSN_FSS", + "1234567"); + await controllerManager.Add(controller, CancellationToken.None); + + var mediator = Substitute.For(); + var hubContext = Substitute.For>(); + var clientProxy = Substitute.For(); + hubContext.Clients.Clients(Arg.Any>()).Returns(clientProxy); + + var dialogueRepository = new TestDialogueRepository(); + + var publisher = new TestPublisher(); + var handler = new DownlinkReceivedNotificationHandler( + aircraftManager, + mediator, + clock, + controllerManager, + dialogueRepository, + hubContext, + publisher, + Logger.None); + + var downlinkMessage = new DownlinkMessage( + 1, + null, + "UAL123", + CpdlcDownlinkResponseType.NoResponse, + AlertType.None, + "NOT CURRENT DATA AUTHORITY", + clock.UtcNow()); + + var notification = new DownlinkReceivedNotification( + "hoppies-ybbb", + "YBBB", + downlinkMessage); + + // Assert - aircraft starts as NextDataAuthority + Assert.Equal(DataAuthorityState.NextDataAuthority, aircraft.DataAuthorityState); + + // Act + await handler.Handle(notification, CancellationToken.None); + + // Assert - aircraft remains as NextDataAuthority + Assert.Equal(DataAuthorityState.NextDataAuthority, aircraft.DataAuthorityState); + + // Assert - AircraftConnectionUpdated event was NOT sent + var receivedCalls = clientProxy.ReceivedCalls() + .Where(call => call.GetMethodInfo().Name == "SendCoreAsync") + .ToList(); + + Assert.Empty(receivedCalls); + } + [Fact] public async Task Handle_UpdatesLastSeen() { diff --git a/source/CPDLCServer/Handlers/DownlinkReceivedNotificationHandler.cs b/source/CPDLCServer/Handlers/DownlinkReceivedNotificationHandler.cs index 371599c..b6b5563 100644 --- a/source/CPDLCServer/Handlers/DownlinkReceivedNotificationHandler.cs +++ b/source/CPDLCServer/Handlers/DownlinkReceivedNotificationHandler.cs @@ -74,8 +74,10 @@ await mediator.Send( // Allow these to flow through to the controller } - // Promote aircraft to CurrentDataAuthority on first downlink - if (aircraftConnection.DataAuthorityState == DataAuthorityState.NextDataAuthority) + // Promote aircraft to CurrentDataAuthority on first downlink, unless + // the aircraft explicitly indicates we are not the current data authority + if (aircraftConnection.DataAuthorityState == DataAuthorityState.NextDataAuthority && + !ControlMessages.IsNotCurrentDataAuthority(notification.Downlink)) { aircraftConnection.PromoteToCurrentDataAuthority(); logger.Information("{Callsign} promoted to CurrentDataAuthority", notification.Downlink.Sender); diff --git a/source/CPDLCServer/Model/ControlMessages.cs b/source/CPDLCServer/Model/ControlMessages.cs index f9468c4..69e8953 100644 --- a/source/CPDLCServer/Model/ControlMessages.cs +++ b/source/CPDLCServer/Model/ControlMessages.cs @@ -16,4 +16,9 @@ public static bool IsEndServiceUplink(UplinkMessage downlinkMessage) { return downlinkMessage.Content == "END SERVICE"; } + + public static bool IsNotCurrentDataAuthority(DownlinkMessage downlinkMessage) + { + return downlinkMessage.Content == "NOT CURRENT DATA AUTHORITY"; + } }