public void TestInitialise()
        {
            _Compressor = Factory.Singleton.Resolve<IBaseStationMessageCompressor>();

            _Message = new BaseStationMessage();
            _Message.MessageType = BaseStationMessageType.Transmission;
            _Message.TransmissionType = BaseStationTransmissionType.SurveillanceAlt;
            _Message.Icao24 = "405012";
        }
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public BaseStationMessage Translate(string text)
        {
            var result = new BaseStationMessage();

            if(!String.IsNullOrEmpty(text)) {
                string[] parts = text.Split(',');
                for(int c = 0;c < parts.Length;c++) {
                    string chunk = parts[c];
                    try {
                        if(!String.IsNullOrEmpty(chunk)) {
                            switch(c) {
                                case 0:     result.MessageType = BaseStationMessageHelper.ConvertToBaseStationMessageType(chunk); break;
                                case 1:     result.TransmissionType = result.MessageType == BaseStationMessageType.Transmission ? BaseStationMessageHelper.ConvertToBaseStationTransmissionType(chunk) : BaseStationTransmissionType.None; break;
                                case 2:     result.SessionId = ParseInt(chunk); break;
                                case 3:     result.AircraftId = ParseInt(chunk); break;
                                case 4:     result.Icao24 = chunk; break;
                                case 5:     result.FlightId = ParseInt(chunk); break;
                                case 6:     result.MessageGenerated = ParseDate(chunk); break;
                                case 7:     result.MessageGenerated = ParseTime(result.MessageGenerated, chunk); break;
                                case 8:     result.MessageLogged = ParseDate(chunk); break;
                                case 9:     result.MessageLogged = ParseTime(result.MessageLogged, chunk); break;
                                case 10:    if(result.MessageType == BaseStationMessageType.StatusChange) result.StatusCode = BaseStationMessageHelper.ConvertToBaseStationStatusCode(chunk); else result.Callsign = chunk.Replace("@", "").Trim(); break;
                                case 11:    result.Altitude = ParseInt(chunk); break;
                                case 12:    result.GroundSpeed = ParseFloat(chunk); break;
                                case 13:    result.Track = ParseFloat(chunk); break;
                                case 14:    result.Latitude = ParseDouble(chunk); break;
                                case 15:    result.Longitude = ParseDouble(chunk); break;
                                case 16:    result.VerticalRate = ParseInt(chunk); break;
                                case 17:    result.Squawk = ParseInt(chunk); if(result.Squawk == 0) result.Squawk = null; break;
                                case 18:    result.SquawkHasChanged = ParseBool(chunk); break;
                                case 19:    result.Emergency = ParseBool(chunk); break;
                                case 20:    result.IdentActive = ParseBool(chunk); break;
                                case 21:    result.OnGround = ParseBool(chunk); break;
                            }
                        }
                    } catch(Exception ex) {
                        Debug.WriteLine(String.Format("BaseStationMessageTranslator.Translate caught exception: {0}", ex.ToString()));

                        // I would prefer to pass ex as the inner exception to this. However Microsoft's Application.ThreadException unhandled exception handler
                        // strips off all outer exceptions and only shows the bottom-most exception - i.e., in our case, the exception from a Parse method. This
                        // is not useful in isolation, we need to know what was being translated, the context in which the exception was thrown. So I have ended
                        // up with this, which is not very nice but shows enough information in the unhandled exception handler to allow diagnosis of the problem.
                        throw new BaseStationTranslatorException(String.Format("{0} while translating \"{1}\" (chunk {2}) in \"{3}\"", ex.Message, chunk, c, text));
                    }
                }
            }

            return result;
        }
        public void TestInitialise()
        {
            _Server = Factory.Singleton.Resolve<IRebroadcastServer>();
            _Listener = new Mock<IListener>(MockBehavior.Default) { DefaultValue = DefaultValue.Mock }.SetupAllProperties();

            _BroadcastProvider = new Mock<IBroadcastProvider>(MockBehavior.Default) { DefaultValue = DefaultValue.Mock }.SetupAllProperties();
            _SentBytes = new List<byte[]>();
            _BroadcastProvider.Setup(r => r.Send(It.IsAny<byte[]>())).Callback((byte[] bytes) => _SentBytes.Add(bytes));

            _Server.Listener = _Listener.Object;
            _Server.BroadcastProvider = _BroadcastProvider.Object;

            _ExceptionCaughtEvent = new EventRecorder<EventArgs<Exception>>();
            _ExceptionCaughtEvent.EventRaised += DefaultExceptionCaughtHandler;
            _OnlineChangedEvent = new EventRecorder<EventArgs>();
            _Server.ExceptionCaught += _ExceptionCaughtEvent.Handler;
            _Server.OnlineChanged += _OnlineChangedEvent.Handler;

            _Port30003Message = new BaseStationMessage() { Icao24 = "313233" };
        }
 /// <summary>
 /// Returns false if the message doesn't carry useful information and can be discarded.
 /// </summary>
 /// <param name="message"></param>
 /// <returns></returns>
 private static bool IsMeaningfulMessage(BaseStationMessage message)
 {
     return message.MessageType == BaseStationMessageType.Transmission &&
            message.TransmissionType != BaseStationTransmissionType.AllCallReply &&
            message.TransmissionType != BaseStationTransmissionType.None &&
            message.Icao24 != null && message.Icao24.Length == 6;
 }
 /// <summary>
 /// Creates a new object.
 /// </summary>
 /// <param name="message"></param>
 public BaseStationMessageEventArgs(BaseStationMessage message)
 {
     Message = message;
 }
        public void TestInitialise()
        {
            _Statistics = Factory.Singleton.Resolve<IStatistics>().Singleton;
            _Statistics.Initialise();
            _Statistics.ResetConnectionStatistics();
            _Statistics.ResetMessageCounters();

            _OriginalClassFactory = Factory.TakeSnapshot();

            _RuntimeEnvironment = TestUtilities.CreateMockSingleton<IRuntimeEnvironment>();
            _RuntimeEnvironment.Setup(r => r.IsTest).Returns(true);

            _Port30003Translator = TestUtilities.CreateMockImplementation<IBaseStationMessageTranslator>();
            _ModeSTranslator = TestUtilities.CreateMockImplementation<IModeSTranslator>();
            _AdsbTranslator = TestUtilities.CreateMockImplementation<IAdsbTranslator>();
            _RawMessageTranslator = new Mock<IRawMessageTranslator>(MockBehavior.Default) { DefaultValue = DefaultValue.Mock };
            _ModeSParity = TestUtilities.CreateMockImplementation<IModeSParity>();
            _ModeSMessage = new ModeSMessage();
            _AdsbMessage = new AdsbMessage(_ModeSMessage);
            _Port30003Message = new BaseStationMessage();
            _Port30003Translator.Setup(r => r.Translate(It.IsAny<string>())).Returns(_Port30003Message);
            _AdsbTranslator.Setup(r => r.Translate(It.IsAny<ModeSMessage>())).Returns(_AdsbMessage);
            _ModeSTranslator.Setup(r => r.Translate(It.IsAny<byte[]>())).Returns(_ModeSMessage);
            _ModeSTranslator.Setup(r => r.Translate(It.IsAny<byte[]>(), It.IsAny<int>())).Returns(_ModeSMessage);
            _RawMessageTranslator.Setup(r => r.Translate(It.IsAny<DateTime>(), It.IsAny<ModeSMessage>(), It.IsAny<AdsbMessage>())).Returns(_Port30003Message);

            _Listener = Factory.Singleton.Resolve<IListener>();
            _Provider = new MockListenerProvider();
            _BytesExtractor = new MockMessageBytesExtractor();

            _ExceptionCaughtEvent = new EventRecorder<EventArgs<Exception>>();
            _ConnectionStateChangedEvent = new EventRecorder<EventArgs>();
            _ModeSMessageReceivedEvent = new EventRecorder<ModeSMessageEventArgs>();
            _Port30003MessageReceivedEvent = new EventRecorder<BaseStationMessageEventArgs>();
            _SourceChangedEvent = new EventRecorder<EventArgs>();

            _Listener.ConnectionStateChanged += _ConnectionStateChangedEvent.Handler;
            _Listener.ExceptionCaught += _ExceptionCaughtEvent.Handler;
            _Listener.ModeSMessageReceived += _ModeSMessageReceivedEvent.Handler;
            _Listener.Port30003MessageReceived += _Port30003MessageReceivedEvent.Handler;
            _Listener.SourceChanged += _SourceChangedEvent.Handler;

            _ExceptionCaughtEvent.EventRaised += DefaultExceptionCaughtHandler;
        }
        public void Plugin_Flight_Does_Record_Latitude_Of_NonZero_Longitude_Of_Zero()
        {
            SetEnabledOption(true);
            var startTime = SetProviderTimes(new DateTime(2012, 3, 4, 5, 6, 7));
            _Plugin.Startup(_StartupParameters);

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = "ABC123", Altitude = 31, Emergency = true,
                GroundSpeed = 289, IdentActive = true, Latitude = 0.1F, Longitude = 0F, MessageGenerated = DateTime.Now, MessageLogged = DateTime.Now, MessageNumber = 12389, MessageType = BaseStationMessageType.Transmission,
                OnGround = true, Squawk = 1293, SquawkHasChanged = true, StatusCode = BaseStationStatusCode.None, Track = 12.4F, TransmissionType = BaseStationTransmissionType.AirToAir, VerticalRate = 18 };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            SetProviderTimes(startTime.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(h => h.SlowTick += null, EventArgs.Empty);

            Assert.AreEqual(0.1F, flight.FirstLat);
            Assert.AreEqual(0F, flight.FirstLon);
            Assert.AreEqual(0.1F, flight.LastLat);
            Assert.AreEqual(0F, flight.LastLon);
        }
        public void Plugin_Flight_Updates_Message_Counters_Correctly()
        {
            // These are the nullable integers which start with "Num"
            var worksheet = new ExcelWorksheetData(TestContext);

            SetEnabledOption(true);

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() {
                Icao24 = "Z",
                MessageType = worksheet.ParseEnum<BaseStationMessageType>("MessageType"),
                TransmissionType = worksheet.ParseEnum<BaseStationTransmissionType>("TransmissionType"),
                StatusCode = worksheet.ParseEnum<BaseStationStatusCode>("StatusCode"),
                GroundSpeed = worksheet.NFloat("GroundSpeed"),
                Track = worksheet.NFloat("Track"),
                Latitude = worksheet.NFloat("Latitude"),
                Longitude = worksheet.NFloat("Longitude"),
                VerticalRate = worksheet.NInt("VerticalRate"),
            };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            SetProviderTimes(_Provider.Object.LocalNow.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);

            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());

            // Note that the totals are not null after the final update even if no messages were received in a particular category
            Assert.AreEqual(worksheet.Int("NumPosMsgRec"), flight.NumPosMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumADSBMsgRec"), flight.NumADSBMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumModeSMsgRec"), flight.NumModeSMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumIDMsgRec"), flight.NumIDMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumSurPosMsgRec"), flight.NumSurPosMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumAirPosMsgRec"), flight.NumAirPosMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumAirVelMsgRec"), flight.NumAirVelMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumSurAltMsgRec"), flight.NumSurAltMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumSurIDMsgRec"), flight.NumSurIDMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumAirToAirMsgRec"), flight.NumAirToAirMsgRec.Value);
            Assert.AreEqual(worksheet.Int("NumAirCallRepMsgRec"), flight.NumAirCallRepMsgRec.Value);
        }
        public void Plugin_Flight_EndTime_Reflects_Time_Of_Last_Message_From_Aircraft()
        {
            SetEnabledOption(true);

            _Plugin.Startup(_StartupParameters);

            var startTime = SetProviderTimes(new DateTime(2012, 3, 4, 5, 6, 7));
            var message = new BaseStationMessage() { Icao24 = "123456", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            SetProviderTimes(startTime.AddMinutes(1));
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            var endTime = SetProviderTimes(startTime.AddMinutes(7));
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight flight) => {
                Assert.AreEqual(startTime, flight.StartTime);
                Assert.AreEqual(endTime, flight.EndTime);
            });

            SetProviderTimes(startTime.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Never());

            SetProviderTimes(endTime.AddMinutes(MinutesBeforeFlightClosed).AddMilliseconds(-1));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Never());

            SetProviderTimes(endTime.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());
        }
        public void Plugin_Reports_Exceptions_During_Flight_Update()
        {
            var exception = new InvalidOperationException();
            SetEnabledOption(true);
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback(() => { throw exception; });

            var startTime = SetProviderTimes(DateTime.Now);
            _Plugin.Startup(_StartupParameters);

            _Plugin.StatusChanged += _StatusChangedEvent.Handler;
            _StatusChangedEvent.EventRaised += (s, a) => {
                Assert.AreEqual(String.Format("Exception caught: {0}", exception.Message), _Plugin.StatusDescription);
            };

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            var endTime = SetProviderTimes(startTime.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);

            Assert.AreEqual(1, _StatusChangedEvent.CallCount);
            Assert.AreSame(_Plugin, _StatusChangedEvent.Sender);
            _Log.Verify(g => g.WriteLine(It.IsAny<string>(), exception.ToString()), Times.Once());
        }
        public void Plugin_Aircraft_Database_Not_Repeatedly_Polled_For_Same_Aircraft()
        {
            SetEnabledOption(true);

            BaseStationAircraft aircraft = new BaseStationAircraft();
            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("X")).Returns(aircraft);

            _Plugin.Startup(_StartupParameters);

            var messageX = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            var messageY = new BaseStationMessage() { AircraftId = 42, Icao24 = "Y", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(messageX));
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(messageX));
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(messageY));

            _BaseStationDatabase.Verify(d => d.GetAircraftByCode("X"), Times.Once());
            _BaseStationDatabase.Verify(d => d.GetAircraftByCode("Y"), Times.Once());
        }
        public void Plugin_Flight_Record_Still_Inserted_If_Aircraft_Record_Already_Exists_For_Transmitting_Aircraft()
        {
            SetEnabledOption(true);

            BaseStationFlight flight = null;
            BaseStationAircraft aircraft = new BaseStationAircraft() { AircraftID = 5832, ModeS = "X" };
            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("X")).Returns(aircraft);
            _BaseStationDatabase.Setup(d => d.InsertFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Once());
            Assert.AreEqual(5832, flight.AircraftID);
        }
        public void Plugin_Aircraft_Record_Not_Touched_If_Aircraft_Record_Already_Exists_For_Transmitting_Aircraft()
        {
            SetEnabledOption(true);

            BaseStationAircraft aircraft = new BaseStationAircraft();
            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("X")).Returns(aircraft);

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X" };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertAircraft(It.IsAny<BaseStationAircraft>()), Times.Never());
            _BaseStationDatabase.Verify(d => d.UpdateAircraft(It.IsAny<BaseStationAircraft>()), Times.Never());
        }
        public void Plugin_Only_Takes_Notice_Of_Transmission_Messages()
        {
            SetEnabledOption(true);

            _Plugin.Startup(_StartupParameters);

            int expectedInserts = 0;
            int counter = 0;

            var messageTypes =
                from        m in Enum.GetValues(typeof(BaseStationMessageType)).OfType<BaseStationMessageType>()
                from        t in Enum.GetValues(typeof(BaseStationTransmissionType)).OfType<BaseStationTransmissionType>()
                from        s in Enum.GetValues(typeof(BaseStationStatusCode)).OfType<BaseStationStatusCode>()
                select      new { MessageType = m, TransmissionType = t, StatusCode = s };
            foreach(var messageType in messageTypes) {
                var message = new BaseStationMessage() {
                    MessageType = messageType.MessageType,
                    TransmissionType = messageType.TransmissionType,
                    StatusCode = messageType.StatusCode,
                    Icao24 = counter++.ToString(),
                };

                if(message.MessageType == BaseStationMessageType.Transmission && message.TransmissionType != BaseStationTransmissionType.None) ++expectedInserts;

                _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

                _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Exactly(expectedInserts), messageType.ToString());
            }
        }
        public void Plugin_Flight_Record_Written_When_First_Message_From_Aircraft_Received()
        {
            SetEnabledOption(true);

            _BaseStationDatabase.Setup(d => d.InsertFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight flight) => {
                Assert.AreEqual(0, flight.FlightID);
                Assert.AreEqual(5483, flight.AircraftID);
                Assert.AreEqual(42, flight.SessionID);
                Assert.AreEqual(null, flight.Aircraft);
                Assert.AreEqual("ABC123", flight.Callsign);
                Assert.AreEqual(_Provider.Object.LocalNow, flight.StartTime);
                Assert.AreEqual(null, flight.EndTime);

                Assert.AreEqual(null, flight.FirstAltitude);
                Assert.AreEqual(null, flight.FirstGroundSpeed);
                Assert.AreEqual(false, flight.FirstIsOnGround);
                Assert.AreEqual(null, flight.FirstLat);
                Assert.AreEqual(null, flight.FirstLon);
                Assert.AreEqual(null, flight.FirstSquawk);
                Assert.AreEqual(null, flight.FirstTrack);
                Assert.AreEqual(null, flight.FirstVerticalRate);
                Assert.AreEqual(false, flight.HadAlert);
                Assert.AreEqual(false, flight.HadEmergency);
                Assert.AreEqual(false, flight.HadSpi);
                Assert.AreEqual(null, flight.LastAltitude);
                Assert.AreEqual(null, flight.LastGroundSpeed);
                Assert.AreEqual(false, flight.LastIsOnGround);
                Assert.AreEqual(null, flight.LastLat);
                Assert.AreEqual(null, flight.LastLon);
                Assert.AreEqual(null, flight.LastSquawk);
                Assert.AreEqual(null, flight.LastTrack);
                Assert.AreEqual(null, flight.LastVerticalRate);
                Assert.AreEqual(null, flight.NumADSBMsgRec);
                Assert.AreEqual(null, flight.NumModeSMsgRec);
                Assert.AreEqual(null, flight.NumSurPosMsgRec);
                Assert.AreEqual(null, flight.NumAirPosMsgRec);
                Assert.AreEqual(null, flight.NumAirVelMsgRec);
                Assert.AreEqual(null, flight.NumSurAltMsgRec);
                Assert.AreEqual(null, flight.NumSurIDMsgRec);
                Assert.AreEqual(null, flight.NumAirToAirMsgRec);
                Assert.AreEqual(null, flight.NumAirCallRepMsgRec);
                Assert.AreEqual(null, flight.NumPosMsgRec);
            });
            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("X")).Returns((BaseStationAircraft)null);
            _BaseStationDatabase.Setup(d => d.InsertSession(It.IsAny<BaseStationSession>())).Callback((BaseStationSession s) => { s.SessionID = 42; });
            _BaseStationDatabase.Setup(d => d.InsertAircraft(It.IsAny<BaseStationAircraft>())).Callback((BaseStationAircraft a) => { a.AircraftID = 5483; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = "ABC123", Altitude = 31, Emergency = true,
                GroundSpeed = 289, IdentActive = true, Latitude = 31.1F, Longitude = 123.1F, MessageGenerated = DateTime.Now, MessageLogged = DateTime.Now, MessageNumber = 12389, MessageType = BaseStationMessageType.Transmission,
                OnGround = true, Squawk = 1293, SquawkHasChanged = true, StatusCode = BaseStationStatusCode.None, Track = 12.4F, TransmissionType = BaseStationTransmissionType.AirToAir, VerticalRate = 18 };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Once());
        }
 /// <summary>
 /// Creates a new object.
 /// </summary>
 /// <param name="message"></param>
 /// <param name="isOutOfBand"></param>
 /// <param name="isSatcomFeed"></param>
 public BaseStationMessageEventArgs(BaseStationMessage message, bool isOutOfBand, bool isSatcomFeed) : this(message)
 {
     IsOutOfBand  = isOutOfBand;
     IsSatcomFeed = isSatcomFeed;
 }
        public void Plugin_Flight_Updated_Correctly_When_Transmissions_Received_After_Clocks_Go_Forwards_During_Flight()
        {
            SetEnabledOption(true);

            // First message is received when both local and UTC are the same value
            var startTime = SetProviderTimes(new DateTime(2012, 3, 4, 5, 6, 7));

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = "ABC123", Altitude = 31, Emergency = true,
                GroundSpeed = 289, IdentActive = true, Latitude = 31.1F, Longitude = 123.1F, MessageGenerated = DateTime.Now, MessageLogged = DateTime.Now, MessageNumber = 12389, MessageType = BaseStationMessageType.Transmission,
                OnGround = true, Squawk = 1293, SquawkHasChanged = true, StatusCode = BaseStationStatusCode.None, Track = 12.4F, TransmissionType = BaseStationTransmissionType.AirToAir, VerticalRate = 18 };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            // Next two messages come in after one minute each, but after local time is one hour ahead of UTC.
            var localEndTime = startTime.AddMinutes(61);
            var utcEndTime = startTime.AddMinutes(1);
            SetProviderTimes(localEndTime, utcEndTime);
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            localEndTime = localEndTime.AddMinutes(1);
            utcEndTime = utcEndTime.AddMinutes(1);
            SetProviderTimes(localEndTime, utcEndTime);
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            // No more messages - no update should be performed 25 minutes after start time because 2 messages came in after that...
            localEndTime = startTime.AddMinutes(60 + MinutesBeforeFlightClosed);
            utcEndTime = startTime.AddMinutes(MinutesBeforeFlightClosed);
            SetProviderTimes(localEndTime, utcEndTime);
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Never());

            // But 25 minutes after the last message it should close the flight off
            localEndTime = startTime.AddMinutes(60 + 2 + MinutesBeforeFlightClosed);
            utcEndTime = startTime.AddMinutes(2 + MinutesBeforeFlightClosed);
            SetProviderTimes(localEndTime, utcEndTime);
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());

            Assert.AreEqual(startTime, flight.StartTime);
            Assert.AreEqual(startTime.AddMinutes(62), flight.EndTime);
        }
        public void Plugin_Flight_Record_Not_Repeatedly_Inserted_For_Same_Aircraft()
        {
            SetEnabledOption(true);

            BaseStationAircraft aircraft = new BaseStationAircraft() { AircraftID = 5832, ModeS = "X" };
            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("X")).Returns(aircraft);

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Once());
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Never());
        }
        public void Plugin_Flight_Update_Keeps_Callsign_If_Originally_Missing()
        {
            SetEnabledOption(true);

            var startTime = SetProviderTimes(DateTime.Now);

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = null, Altitude = 31, Emergency = true,
                GroundSpeed = 289, IdentActive = true, Latitude = 31.1F, Longitude = 123.1F, MessageGenerated = DateTime.Now, MessageLogged = DateTime.Now, MessageNumber = 12389, MessageType = BaseStationMessageType.Transmission,
                OnGround = true, Squawk = 1293, SquawkHasChanged = true, StatusCode = BaseStationStatusCode.None, Track = 12.4F, TransmissionType = BaseStationTransmissionType.AirToAir, VerticalRate = 18 };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            message.Callsign = "ABC123";
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight flight) => {
                Assert.AreEqual("ABC123", flight.Callsign);
            });

            var endTime = SetProviderTimes(startTime.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);

            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Exactly(2));
        }
        public void Plugin_Database_Not_Touched_If_Writes_Disallowed()
        {
            SetEnabledOption(true);
            SetDBHistory(false);

            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("X")).Returns((BaseStationAircraft)null);

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X" };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertAircraft(It.IsAny<BaseStationAircraft>()), Times.Never());
            _BaseStationDatabase.Verify(d => d.UpdateAircraft(It.IsAny<BaseStationAircraft>()), Times.Never());

            _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Never());
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Never());
        }
        public void Plugin_Flight_Updates_Status_Bool_Values_Correctly()
        {
            // These are the Flight record fields that start with "Had".
            var worksheet = new ExcelWorksheetData(TestContext);

            SetEnabledOption(true);

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var messageProperty = typeof(BaseStationMessage).GetProperty(worksheet.String("MsgProperty"));
            for(int i = 0;i < worksheet.Int("Count");++i) {
                var message = new BaseStationMessage() { Icao24 = "Z", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };

                var valueText = worksheet.EString(String.Format("MsgValue{0}", i + 1));
                messageProperty.SetValue(message, TestUtilities.ChangeType(valueText, messageProperty.PropertyType, CultureInfo.InvariantCulture), null);

                _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));
            }

            SetProviderTimes(_Provider.Object.LocalNow.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);

            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());

            var propertyName = worksheet.String("FlightProperty");
            var flightProperty = typeof(BaseStationFlight).GetProperty(propertyName);
            var flightValue = worksheet.EString("FlightValue");

            var expectedValue = TestUtilities.ChangeType(flightValue, flightProperty.PropertyType, CultureInfo.InvariantCulture);
            var actualValue = flightProperty.GetValue(flight, null);

            Assert.AreEqual(expectedValue, actualValue, propertyName);
        }
        public void Plugin_Copes_If_Aircraft_Icao24_Is_Missing_From_Message()
        {
            // This will never happen from a compliant feed of messages but we just need to know that if it ever
            // did arise we would handle it gracefully
            SetEnabledOption(true);

            _Plugin.Startup(_StartupParameters);

            var messageX = new BaseStationMessage() { AircraftId = 99, Icao24 = null };
            var messageY = new BaseStationMessage() { AircraftId = 42, Icao24 = "" };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(messageX));
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(messageY));

            _BaseStationDatabase.Verify(d => d.GetAircraftByCode(null), Times.Never());
            _BaseStationDatabase.Verify(d => d.GetAircraftByCode(""), Times.Never());
        }
        public void Plugin_Writes_New_Flight_Record_For_Aircraft_If_Message_Received_After_Final_Update_Of_Previous_Flight()
        {
            SetEnabledOption(true);

            _BaseStationDatabase.Setup(d => d.GetAircraftByCode("Z")).Returns(new BaseStationAircraft() { AircraftID = 4722 });
            _BaseStationDatabase.Setup(d => d.InsertSession(It.IsAny<BaseStationSession>())).Callback((BaseStationSession session) => { session.SessionID = 318; });

            var message = new BaseStationMessage() { MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir, AircraftId = 2372211, SessionId = -1, Icao24 = "Z" };

            _Plugin.Startup(_StartupParameters);

            var time = SetProviderTimes(DateTime.Now);
            _Listener.Raise(m => m.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            time = SetProviderTimes(time.AddMinutes(MinutesBeforeFlightClosed));
            _HeartbeatService.Raise(h => h.SlowTick += null, EventArgs.Empty);

            _BaseStationDatabase.Setup(d => d.InsertFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight flight) => {
                Assert.AreEqual(0, flight.FlightID);
                Assert.AreEqual(4722, flight.AircraftID);
                Assert.AreEqual(318, flight.SessionID);
                Assert.AreEqual(null, flight.Aircraft);
                Assert.AreEqual("", flight.Callsign);
                Assert.AreEqual(time, flight.StartTime);
                Assert.AreEqual(null, flight.EndTime);

                Assert.AreEqual(null, flight.FirstAltitude);
                Assert.AreEqual(null, flight.FirstGroundSpeed);
                Assert.AreEqual(false, flight.FirstIsOnGround);
                Assert.AreEqual(null, flight.FirstLat);
                Assert.AreEqual(null, flight.FirstLon);
                Assert.AreEqual(null, flight.FirstSquawk);
                Assert.AreEqual(null, flight.FirstTrack);
                Assert.AreEqual(null, flight.FirstVerticalRate);
                Assert.AreEqual(false, flight.HadAlert);
                Assert.AreEqual(false, flight.HadEmergency);
                Assert.AreEqual(false, flight.HadSpi);
                Assert.AreEqual(null, flight.LastAltitude);
                Assert.AreEqual(null, flight.LastGroundSpeed);
                Assert.AreEqual(false, flight.LastIsOnGround);
                Assert.AreEqual(null, flight.LastLat);
                Assert.AreEqual(null, flight.LastLon);
                Assert.AreEqual(null, flight.LastSquawk);
                Assert.AreEqual(null, flight.LastTrack);
                Assert.AreEqual(null, flight.LastVerticalRate);
                Assert.AreEqual(null, flight.NumADSBMsgRec);
                Assert.AreEqual(null, flight.NumModeSMsgRec);
                Assert.AreEqual(null, flight.NumSurPosMsgRec);
                Assert.AreEqual(null, flight.NumAirPosMsgRec);
                Assert.AreEqual(null, flight.NumAirVelMsgRec);
                Assert.AreEqual(null, flight.NumSurAltMsgRec);
                Assert.AreEqual(null, flight.NumSurIDMsgRec);
                Assert.AreEqual(null, flight.NumAirToAirMsgRec);
                Assert.AreEqual(null, flight.NumAirCallRepMsgRec);
                Assert.AreEqual(null, flight.NumPosMsgRec);
            });

            time = SetProviderTimes(time.AddMilliseconds(1));
            _Listener.Raise(m => m.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Exactly(2));
        }
        public void Plugin_Reports_Exceptions_During_Database_EndTransaction()
        {
            var exception = new InvalidOperationException();
            SetEnabledOption(true);
            _BaseStationDatabase.Setup(d => d.EndTransaction()).Callback(() => { throw exception; });

            _Plugin.Startup(_StartupParameters);

            _Plugin.StatusChanged += _StatusChangedEvent.Handler;
            _StatusChangedEvent.EventRaised += (s, a) => {
                Assert.AreEqual(String.Format("Exception caught: {0}", exception.Message), _Plugin.StatusDescription);
            };

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            Assert.AreEqual(1, _StatusChangedEvent.CallCount);
            Assert.AreSame(_Plugin, _StatusChangedEvent.Sender);
            _Log.Verify(g => g.WriteLine(It.IsAny<string>(), exception.ToString()), Times.Once());
        }
        public void Plugin_DatabaseFileNameChanging_Flushes_All_Tracked_Flights_To_Disk()
        {
            SetEnabledOption(true);
            _OptionsView.Setup(v => v.DisplayView()).Returns(true);
            _OptionsView.Setup(v => v.PluginEnabled).Returns(false);

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir, Icao24 = "Y" };
            _Listener.Raise(m => m.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight flight) => {
                Assert.AreEqual(_Provider.Object.LocalNow, flight.EndTime);
                Assert.AreEqual(1, flight.NumModeSMsgRec);
            });

            _BaseStationDatabase.Raise(d => d.FileNameChanging += null, EventArgs.Empty);

            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());
        }
        public void Plugin_Flight_Leaves_Callsign_Empty_If_Missing_From_Initial_Message()
        {
            SetEnabledOption(true);

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.InsertFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = null, MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.InsertFlight(It.IsAny<BaseStationFlight>()), Times.Once());
            Assert.AreEqual("", flight.Callsign);
        }
 /// <summary>
 /// Creates a new object.
 /// </summary>
 /// <param name="message"></param>
 public BaseStationMessageEventArgs(BaseStationMessage message)
 {
     Message = message;
 }
        public void Plugin_Flight_Updated_If_Subsequent_Message_Adds_Callsign()
        {
            SetEnabledOption(true);

            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight flight) => {
                // The update must ONLY update the callsign, it isn't allowed to fill in the values that are set when the flight is closed off
                Assert.AreEqual("ABC123", flight.Callsign);
                Assert.AreEqual(null, flight.EndTime);
                Assert.AreEqual(null, flight.FirstAltitude);
                Assert.AreEqual(null, flight.FirstGroundSpeed);
                Assert.AreEqual(false, flight.FirstIsOnGround);
                Assert.AreEqual(null, flight.FirstLat);
                Assert.AreEqual(null, flight.FirstLon);
                Assert.AreEqual(null, flight.FirstSquawk);
                Assert.AreEqual(null, flight.FirstTrack);
                Assert.AreEqual(null, flight.FirstVerticalRate);
                Assert.AreEqual(false, flight.HadAlert);
                Assert.AreEqual(false, flight.HadEmergency);
                Assert.AreEqual(false, flight.HadSpi);
                Assert.AreEqual(null, flight.LastAltitude);
                Assert.AreEqual(null, flight.LastGroundSpeed);
                Assert.AreEqual(false, flight.LastIsOnGround);
                Assert.AreEqual(null, flight.LastLat);
                Assert.AreEqual(null, flight.LastLon);
                Assert.AreEqual(null, flight.LastSquawk);
                Assert.AreEqual(null, flight.LastTrack);
                Assert.AreEqual(null, flight.LastVerticalRate);
                Assert.AreEqual(null, flight.NumADSBMsgRec);
                Assert.AreEqual(null, flight.NumModeSMsgRec);
                Assert.AreEqual(null, flight.NumIDMsgRec);
                Assert.AreEqual(null, flight.NumSurPosMsgRec);
                Assert.AreEqual(null, flight.NumAirPosMsgRec);
                Assert.AreEqual(null, flight.NumAirVelMsgRec);
                Assert.AreEqual(null, flight.NumSurAltMsgRec);
                Assert.AreEqual(null, flight.NumSurIDMsgRec);
                Assert.AreEqual(null, flight.NumAirToAirMsgRec);
                Assert.AreEqual(null, flight.NumAirCallRepMsgRec);
                Assert.AreEqual(null, flight.NumPosMsgRec);
            });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = null, Altitude = 31, Emergency = true,
                GroundSpeed = 289, IdentActive = true, Latitude = 31.1F, Longitude = 123.1F, MessageGenerated = DateTime.Now, MessageLogged = DateTime.Now, MessageNumber = 12389, MessageType = BaseStationMessageType.Transmission,
                OnGround = true, Squawk = 1293, SquawkHasChanged = true, StatusCode = BaseStationStatusCode.None, Track = 12.4F, TransmissionType = BaseStationTransmissionType.AirToAir, VerticalRate = 18 };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            message.Callsign = "ABC123";
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());
        }
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public BaseStationMessage Decompress(byte[] buffer)
        {
            BaseStationMessage result = null;

            if(buffer.Length > 0 && (int)buffer[0] == buffer.Length) {
                using(var stream = new MemoryStream(buffer)) {
                    using(var reader = new BinaryReader(stream)) {
                        result = new BaseStationMessage();
                        stream.ReadByte(); // <-- length of buffer, we can skip this

                        ushort checksum = reader.ReadUInt16();  // the checksum is skipped as well - caller should ensure it's a valid message before decompression

                        result.MessageType = BaseStationMessageType.Transmission;
                        result.StatusCode = BaseStationStatusCode.None;
                        result.TransmissionType = (BaseStationTransmissionType)stream.ReadByte();
                        result.Icao24 = DecodeIcao(stream);
                        result.MessageGenerated = result.MessageLogged = DateTime.Now;

                        OptionalFields optionalFields = (OptionalFields)DecodeShort(reader);

                        if((optionalFields & OptionalFields.CallSign) != 0)     result.Callsign = DecodeString(stream);
                        if((optionalFields & OptionalFields.Altitude) != 0)     result.Altitude = (int)DecodeFloatInt(stream);
                        if((optionalFields & OptionalFields.GroundSpeed) != 0)  result.GroundSpeed = (int)DecodeFloatShort(reader);
                        if((optionalFields & OptionalFields.Track) != 0)        result.Track = DecodeFloatShort(reader) / 10f;
                        if((optionalFields & OptionalFields.Latitude) != 0)     result.Latitude = DecodeFloat(reader);
                        if((optionalFields & OptionalFields.Longitude) != 0)    result.Longitude = DecodeFloat(reader);
                        if((optionalFields & OptionalFields.VerticalRate) != 0) result.VerticalRate = (int)DecodeFloatShort(reader);
                        if((optionalFields & OptionalFields.Squawk) != 0)       result.Squawk = DecodeShort(reader);

                        bool hasSquawkHasChanged =  (optionalFields & OptionalFields.SquawkHasChanged) != 0;
                        bool hasEmergency =         (optionalFields & OptionalFields.Emergency) != 0;
                        bool hasIdentActive =       (optionalFields & OptionalFields.IdentActive) != 0;
                        bool hasOnGround =          (optionalFields & OptionalFields.OnGround) != 0;

                        if(hasSquawkHasChanged || hasEmergency || hasIdentActive || hasOnGround) {
                            CompressedFlags flags = (CompressedFlags)stream.ReadByte();
                            if(hasSquawkHasChanged)     result.SquawkHasChanged = (flags & CompressedFlags.SquawkHasChanged) != 0;
                            if(hasEmergency)            result.Emergency = (flags & CompressedFlags.Emergency) != 0;
                            if(hasIdentActive)          result.IdentActive = (flags & CompressedFlags.IdentActive) != 0;
                            if(hasOnGround)             result.OnGround = (flags & CompressedFlags.OnGround) != 0;
                        }
                    }
                }
            }

            return result;
        }
        public void Plugin_Flight_Not_Updated_After_Callsign_Set()
        {
            SetEnabledOption(true);

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir, AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = null };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            message = new BaseStationMessage() { MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir,AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = "ABC123" };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            message = new BaseStationMessage() { MessageType = BaseStationMessageType.Transmission, TransmissionType = BaseStationTransmissionType.AirToAir,AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = "XYZ987" };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());
            Assert.AreEqual("ABC123", flight.Callsign);
        }
        /// <summary>
        /// See interface docs.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public byte[] Compress(BaseStationMessage message)
        {
            byte[] result = null;

            if(IsMeaningfulMessage(message)) {
                using(var stream = new MemoryStream()) {
                    using(var writer = new BinaryWriter(stream)) {
                        stream.WriteByte(0); // <-- reserve a byte for the buffer length
                        writer.Write((ushort)0); // <-- reserve a word for the checksum
                        stream.WriteByte((byte)(int)message.TransmissionType);
                        EncodeIcao(stream, message.Icao24);
                        long optionalFlagsOffset = stream.Position;
                        stream.WriteByte(0); // <-- reserve two bytes for the optional fields flags
                        stream.WriteByte(0); // <--    "
                        OptionalFields optionalFields = OptionalFields.None;
                        if(!String.IsNullOrEmpty(message.Callsign)) { optionalFields |= OptionalFields.CallSign; EncodeString(stream, message.Callsign); }
                        if(message.Altitude != null)                { optionalFields |= OptionalFields.Altitude; EncodeFloatInt(stream, message.Altitude.Value); }
                        if(message.GroundSpeed != null)             { optionalFields |= OptionalFields.GroundSpeed; EncodeFloatShort(writer, message.GroundSpeed.Value); }
                        if(message.Track != null)                   { optionalFields |= OptionalFields.Track; EncodeFloatShort(writer, message.Track.Value * 10f); }
                        if(message.Latitude != null)                { optionalFields |= OptionalFields.Latitude; EncodeFloat(writer, (float)message.Latitude.Value); }
                        if(message.Longitude != null)               { optionalFields |= OptionalFields.Longitude; EncodeFloat(writer, (float)message.Longitude.Value); }
                        if(message.VerticalRate != null)            { optionalFields |= OptionalFields.VerticalRate; EncodeFloatShort(writer, message.VerticalRate.Value); }
                        if(message.Squawk != null)                  { optionalFields |= OptionalFields.Squawk; EncodeShort(writer, (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, message.Squawk.Value))); }

                        CompressedFlags flags = CompressedFlags.None;
                        bool hasFlag = false;
                        if(message.SquawkHasChanged != null)        { optionalFields |= OptionalFields.SquawkHasChanged; hasFlag = true; if(message.SquawkHasChanged.Value) flags |= CompressedFlags.SquawkHasChanged; }
                        if(message.Emergency != null)               { optionalFields |= OptionalFields.Emergency; hasFlag = true; if(message.Emergency.Value) flags |= CompressedFlags.Emergency; }
                        if(message.IdentActive != null)             { optionalFields |= OptionalFields.IdentActive; hasFlag = true; if(message.IdentActive.Value) flags |= CompressedFlags.IdentActive; }
                        if(message.OnGround != null)                { optionalFields |= OptionalFields.OnGround; hasFlag = true; if(message.OnGround.Value) flags |= CompressedFlags.OnGround; }
                        if(hasFlag) stream.WriteByte((byte)flags);

                        stream.Seek(optionalFlagsOffset, SeekOrigin.Begin);
                        EncodeShort(writer, (short)optionalFields);
                    }

                    result = stream.ToArray();
                    if(result.Length != 0) {
                        result[0] = (byte)result.Length;
                        var checksum = CalculateChecksum(result);
                        result[1] = (byte)(checksum & 0x00ff);
                        result[2] = (byte)((checksum & 0xff00) >> 8);
                    }
                }
            }

            return result ?? new byte[0];
        }
        public void Plugin_Flight_Updated_Correctly_When_Clocks_Go_Backwards_During_Flight()
        {
            SetEnabledOption(true);

            // First message is received when both local and UTC are the same value
            var startTime = SetProviderTimes(new DateTime(2012, 3, 4, 5, 6, 7));

            BaseStationFlight flight = null;
            _BaseStationDatabase.Setup(d => d.UpdateFlight(It.IsAny<BaseStationFlight>())).Callback((BaseStationFlight f) => { flight = f; });

            _Plugin.Startup(_StartupParameters);

            var message = new BaseStationMessage() { AircraftId = 99, Icao24 = "X", FlightId = 429, SessionId = 1239, Callsign = "ABC123", Altitude = 31, Emergency = true,
                GroundSpeed = 289, IdentActive = true, Latitude = 31.1F, Longitude = 123.1F, MessageGenerated = DateTime.Now, MessageLogged = DateTime.Now, MessageNumber = 12389, MessageType = BaseStationMessageType.Transmission,
                OnGround = true, Squawk = 1293, SquawkHasChanged = true, StatusCode = BaseStationStatusCode.None, Track = 12.4F, TransmissionType = BaseStationTransmissionType.AirToAir, VerticalRate = 18 };
            _Listener.Raise(r => r.Port30003MessageReceived += null, new BaseStationMessageEventArgs(message));

            // Local is localNow one hour behind UTC, so local is 59 minutes behind original start time.
            // The plugin must recognise that only one minute has elapsed and NOT update the flight record on the database.
            var localEndTime = startTime.AddMinutes(-59);
            var utcEndTime = startTime.AddMinutes(1);
            SetProviderTimes(localEndTime, utcEndTime);
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Never());

            // Time moves on and the wait period has elapsed in both local and UTC - the database should localNow be updated
            localEndTime = localEndTime.AddMinutes(MinutesBeforeFlightClosed);
            utcEndTime = utcEndTime.AddMinutes(MinutesBeforeFlightClosed);
            SetProviderTimes(localEndTime, utcEndTime);
            _HeartbeatService.Raise(s => s.SlowTick += null, EventArgs.Empty);
            _BaseStationDatabase.Verify(d => d.UpdateFlight(It.IsAny<BaseStationFlight>()), Times.Once());

            Assert.AreEqual(startTime, flight.StartTime);
            Assert.AreEqual(startTime, flight.EndTime);
        }