diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index 5a232f26..ec0e415d 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -521 +522 diff --git a/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs b/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs index 98db1f89..72f20da5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/MaintenanceService.cs @@ -267,7 +267,7 @@ namespace AutopilotMonitor.Functions.Services { try { - var sessions = await _storageService.GetExcessiveDataSendersAsync(tenantId, windowCutoff); + var sessions = await _storageService.GetExcessiveDataSendersAsync(tenantId, windowCutoff, adminConfig.MaxSessionWindowHours); if (sessions.Count == 0) { diff --git a/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Maintenance.cs b/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Maintenance.cs index 59f0b9af..29dbdbf1 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Maintenance.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Maintenance.cs @@ -220,14 +220,18 @@ namespace AutopilotMonitor.Functions.Services } /// - /// Gets sessions where the device has sent data within the last window period - /// AND the session started before that window cutoff – indicating a device that has been - /// actively sending data for longer than the allowed maximum. + /// Gets sessions where the device has been actively sending data for longer than + /// . /// Status-independent: detects excessive data senders regardless of session status. /// Uses LastEventAt (written on every event batch) for the "still active" check. /// Sessions without LastEventAt (predating this field) are not returned. + /// + /// The OData pre-filter narrows candidates to sessions that straddle the cutoff boundary, + /// then a post-filter verifies the actual session duration (LastEventAt − StartedAt) + /// exceeds the allowed window. This prevents false positives from short sessions that + /// merely happen to straddle the cutoff time. /// - public async Task> GetExcessiveDataSendersAsync(string tenantId, DateTime windowCutoff) + public async Task> GetExcessiveDataSendersAsync(string tenantId, DateTime windowCutoff, int maxSessionWindowHours) { SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId)); @@ -235,8 +239,7 @@ namespace AutopilotMonitor.Functions.Services { var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions); - // LastEventAt gt cutoff → device sent data recently (still active) - // StartedAt lt cutoff → session has been running longer than the allowed window + // OData pre-filter: narrow to sessions that straddle the cutoff boundary. // IsPreProvisioned ne true → exclude WhiteGlove sessions: a pre-provisioned device // that resumes after weeks in storage looks like an excessive sender (StartedAt old, // LastEventAt recent) but is a legitimate resumption, not abuse. @@ -247,10 +250,22 @@ namespace AutopilotMonitor.Functions.Services $"and IsPreProvisioned ne true"; var query = tableClient.QueryAsync(filter: filter); + var maxDuration = TimeSpan.FromHours(maxSessionWindowHours); var sessions = new List(); await foreach (var entity in query) { + // Post-filter: verify actual session duration exceeds the window. + // OData cannot compute date differences, so we check in code. + var startedAt = entity.GetDateTimeOffset("StartedAt")?.UtcDateTime; + var lastEventAt = entity.GetDateTimeOffset("LastEventAt")?.UtcDateTime; + + if (startedAt.HasValue && lastEventAt.HasValue + && (lastEventAt.Value - startedAt.Value) < maxDuration) + { + continue; + } + sessions.Add(MapToSessionSummary(entity)); }