From d2618aca02f79bbc4d101f7f4e3bd443ed6c9f3c Mon Sep 17 00:00:00 2001 From: Richa Bansal Date: Thu, 22 Jan 2026 15:35:05 -0800 Subject: [PATCH] Handle ArgumentOutOfRangeException for datetime --- .../SearchValueExpressionBuilderHelper.cs | 42 +++++++++++++++++-- .../Rest/Search/DateSearchTests.cs | 30 +++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs index 401a105214..dcfed0e49e 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs @@ -100,10 +100,44 @@ void ISearchValueVisitor.Visit(DateTimeSearchValue dateTime) var startTicks = dateTime.Start.UtcTicks; var endTicks = dateTime.End.UtcTicks; - var differenceTicks = (long)((Clock.UtcNow.Ticks - Math.Max(startTicks, endTicks)) * ApproximateMultiplier); - - var approximateStart = dateTime.Start.AddTicks(-differenceTicks); - var approximateEnd = dateTime.End.AddTicks(differenceTicks); + // Calculate the difference safely to avoid overflow + long nowTicks = Clock.UtcNow.Ticks; + long maxTicks = Math.Max(startTicks, endTicks); + + // Use checked arithmetic to detect overflow, but handle it gracefully + long differenceTicks; + try + { + checked + { + long rawDifference = nowTicks - maxTicks; + differenceTicks = (long)(rawDifference * ApproximateMultiplier); + } + } + catch (OverflowException) + { + // If overflow occurs in calculation, use a safe maximum difference + // This represents approximately 10% of the maximum safe tick range + differenceTicks = long.MaxValue / 20; + } + + // Ensure we don't overflow when adding/subtracting ticks + long ticksToSubtract = differenceTicks; + long ticksToAdd = differenceTicks; + + // Clamp to prevent overflow before calling AddTicks + if (startTicks - ticksToSubtract < DateTimeOffset.MinValue.Ticks) + { + ticksToSubtract = startTicks - DateTimeOffset.MinValue.Ticks; + } + + if (endTicks + ticksToAdd > DateTimeOffset.MaxValue.Ticks) + { + ticksToAdd = DateTimeOffset.MaxValue.Ticks - endTicks; + } + + DateTimeOffset approximateStart = dateTime.Start.AddTicks(-ticksToSubtract); + DateTimeOffset approximateEnd = dateTime.End.AddTicks(ticksToAdd); _outputExpression = Expression.And( Expression.GreaterThanOrEqual(FieldName.DateTimeStart, _componentIndex, approximateStart), diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.E2E/Rest/Search/DateSearchTests.cs b/test/Microsoft.Health.Fhir.Shared.Tests.E2E/Rest/Search/DateSearchTests.cs index 39ac148ad8..5c2a2d0a50 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.E2E/Rest/Search/DateSearchTests.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.E2E/Rest/Search/DateSearchTests.cs @@ -236,5 +236,35 @@ public async Task GivenADateTimeSearchParam_WhenSearchedAgainstAPeriod_ThenCorre Assert.Fail($"A non-expected '{e.GetType()}' was raised. Url: {Client.HttpClient.BaseAddress}. No Activity Id present. Error: {e.Message}"); } } + + [Theory] + [InlineData("ap9500-01-01")] // Approximate search with future date that causes overflow + [InlineData("ap0001-01-01")] // Approximate search with very old date + [InlineData("ap9999-12-31")] // Approximate search near max date + public async Task GivenAnApproximateDateSearchParamWithExtremeDate_WhenSearched_ThenServerShouldNotCrash(string queryValue) + { + try + { + // The server should handle extreme dates gracefully and return a successful response + // even if no results are found (empty bundle is OK) + Bundle bundle = await Client.SearchAsync(ResourceType.Patient, $"birthdate={queryValue}"); + + // If we get here, the server handled it correctly - either with results or an empty bundle + Assert.NotNull(bundle); + Assert.Equal(Bundle.BundleType.Searchset, bundle.Type); + } + catch (FhirClientException fce) + { + // The server should NOT return 500 Internal Server Error for valid approximate date searches + // It's acceptable to return BadRequest if the server decides the date is out of reasonable bounds + Assert.True( + fce.StatusCode != HttpStatusCode.InternalServerError, + $"Server returned '{HttpStatusCode.InternalServerError}' for a valid approximate date search. This indicates an unhandled exception (overflow) in the date calculation logic. Url: {Client.HttpClient.BaseAddress}. Activity Id: {fce.Response.GetRequestId()}. Error: {fce.Message}"); + } + catch (Exception e) + { + Assert.Fail($"An unexpected '{e.GetType()}' was raised. The server should handle extreme approximate dates gracefully. Url: {Client.HttpClient.BaseAddress}. Error: {e.Message}"); + } + } } }