diff --git a/session.go b/session.go index 35281e98e..a6d296999 100644 --- a/session.go +++ b/session.go @@ -62,7 +62,8 @@ type session struct { transportDataDictionary *datadictionary.DataDictionary appDataDictionary *datadictionary.DataDictionary - timestampPrecision TimestampPrecision + timestampPrecision TimestampPrecision + lastCheckedResetSeqTime time.Time } func (s *session) logError(err error) { diff --git a/session_state.go b/session_state.go index d97d464e1..6fe4dded7 100644 --- a/session_state.go +++ b/session_state.go @@ -156,13 +156,27 @@ func (sm *stateMachine) CheckSessionTime(session *session, now time.Time) { } func (sm *stateMachine) CheckResetTime(session *session, now time.Time) { - if session.EnableResetSeqTime { - if session.ResetSeqTime.Hour() == now.Hour() && - session.ResetSeqTime.Minute() == now.Minute() && - session.ResetSeqTime.Second() == now.Second() { - session.sendLogonInReplyTo(true, nil) - } + // If the reset time is not enabled, we do nothing. + if !session.EnableResetSeqTime { + return + } + // If the last checked reset seq time is not set or we are not connected, we do nothing. + if session.lastCheckedResetSeqTime.IsZero() || !session.stateMachine.State.IsConnected() { + session.lastCheckedResetSeqTime = now + return } + + // Get the reset time for today + nowInTimeZone := now.In(session.ResetSeqTime.Location()) + resetSeqTimeToday := time.Date(nowInTimeZone.Year(), nowInTimeZone.Month(), nowInTimeZone.Day(), session.ResetSeqTime.Hour(), session.ResetSeqTime.Minute(), session.ResetSeqTime.Second(), session.ResetSeqTime.Nanosecond(), session.ResetSeqTime.Location()) + + // If we have crossed the reset time boundary in between checks or we are at the reset time, we send the reset + if session.lastCheckedResetSeqTime.Before(resetSeqTimeToday) && !now.Before(resetSeqTimeToday) { + session.sendLogonInReplyTo(true, nil) + } + + // Update the last checked reset seq time to now + session.lastCheckedResetSeqTime = now } func (sm *stateMachine) setState(session *session, nextState sessionState) { diff --git a/session_test.go b/session_test.go index 99c9dfbe0..2ea5c3508 100644 --- a/session_test.go +++ b/session_test.go @@ -1010,11 +1010,15 @@ func (s *SessionSuite) TestSeqNumResetTime() { s.session.ResetSeqTime = now s.session.EnableResetSeqTime = true + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() s.MockApp.On("ToAdmin") + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) s.IncrNextSenderMsgSeqNum() s.IncrNextTargetMsgSeqNum() @@ -1022,8 +1026,8 @@ func (s *SessionSuite) TestSeqNumResetTime() { s.session.CheckResetTime(s.session, now) - s.NextSenderMsgSeqNum(2) - s.NextSenderMsgSeqNum(2) + s.NextSenderMsgSeqNum(3) + s.NextSenderMsgSeqNum(3) } @@ -1052,3 +1056,273 @@ func (s *SessionSuite) TestSeqNumResetTimeDisconnected() { s.NextSenderMsgSeqNum(2) s.NextTargetMsgSeqNum(2) } + +func (s *SessionSuite) TestSeqNumResetTimeAfterElapse() { + s.session.State = logonState{} + before := time.Now().UTC() + resetTime := time.Now().UTC().Add(time.Second * 2) + after := resetTime.Add(time.Second * 1) + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(1) +} + +func (s *SessionSuite) TestSeqNumResetTimeNotAfterDisconnect() { + s.session.State = logonState{} + before := time.Now().UTC() + resetTime := before.Add(time.Second * 2) + after := resetTime.Add(time.Second * 1) + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.onAdmin(stopReq{}) + s.Disconnected() + s.Stopped() + + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) +} + +func (s *SessionSuite) TestSeqNumResetTimeDisconnected_LocalTZ() { + s.session.State = logonState{} + tz, err := time.LoadLocation("America/Chicago") + if err != nil { + s.T().Fatal(err) + } + s.session.TimeZone = tz + s.session.ResetSeqTime = time.Now().In(tz).Add(time.Second * 2) + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.onAdmin(stopReq{}) + s.Disconnected() + s.Stopped() + + // Wait for reset time to pass. + time.Sleep(time.Second * 3) + + s.MockApp.On("ToAdmin") + // Disconnected so the seq numbers should not be reset. + s.session.CheckResetTime(s.session, time.Now().UTC()) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) +} + +func (s *SessionSuite) TestSeqNumResetTimeAfterElapse_LocalTZ() { + s.session.State = logonState{} + tz, err := time.LoadLocation("America/Chicago") + if err != nil { + s.T().Fatal(err) + } + s.session.TimeZone = tz + before := time.Now().In(tz) + resetTime := before.Add(time.Second * 2) + after := resetTime.Add(time.Second * 1) + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(1) +} + +func (s *SessionSuite) TestSeqNumResetTimeNotAfterDisconnect_LocalTZ() { + s.session.State = logonState{} + tz, err := time.LoadLocation("America/Chicago") + if err != nil { + s.T().Fatal(err) + } + s.session.TimeZone = tz + before := time.Now().In(tz) + resetTime := before.Add(time.Second * 2) + after := resetTime.Add(time.Second * 1) + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.onAdmin(stopReq{}) + s.Disconnected() + s.Stopped() + + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) +} + +func (s *SessionSuite) TestSeqNumResetTimeAfterElapse_MultipleDaysLater() { + s.session.State = logonState{} + + // Example Dates: + // ResetTime = 2025-06-08 10:15:30 + // Before = 2025-07-10 10:15:29 + // After = 2025-07-10 10:15:32 + resetTime := time.Now() + before := resetTime.AddDate(0, 1, 2).Add(time.Second * -1) + after := resetTime.AddDate(0, 1, 2).Add(time.Second * 2) + + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(1) +} + +func (s *SessionSuite) TestSeqNumResetTimeAtExactTime() { + s.session.State = logonState{} + resetTime := time.Now() + before := resetTime.Add(time.Second * -1) + + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + // We are before the reset time, so we should not reset the seq numbers + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + // We check the reset time exactly at the configured time, so we reset + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, resetTime) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(1) +} + +func (s *SessionSuite) TestSeqNumResetTimePreviousCheck() { + s.session.State = logonState{} + resetTime := time.Now() + before := resetTime.Add(time.Second * -1) + after := resetTime.Add(time.Second * 1) + + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + // We are before the reset time, so we should not reset the seq numbers + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + // We just checked the reset time at the configured time, so we do not reset the seq num for the next check + s.session.lastCheckedResetSeqTime = resetTime + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) +} + +func (s *SessionSuite) TestSeqNumResetTime_NanoSeconds() { + s.session.State = logonState{} + resetTime := time.Now() + // Nanoseconds are not used in the resetSeqTime, but are referenced in the comparison of before/after + before := resetTime.Add(time.Nanosecond * -1) + after := resetTime.Add(time.Nanosecond * 1) + + s.session.ResetSeqTime = resetTime + s.session.EnableResetSeqTime = true + + s.NextSenderMsgSeqNum(1) + s.NextTargetMsgSeqNum(1) + s.IncrNextTargetMsgSeqNum() + s.IncrNextSenderMsgSeqNum() + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.MockApp.On("ToAdmin") + s.session.CheckResetTime(s.session, before) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(2) + + s.session.lastCheckedResetSeqTime = before + s.session.CheckResetTime(s.session, after) + s.NextSenderMsgSeqNum(2) + s.NextTargetMsgSeqNum(1) +}