`Grpc.JsonTranscoding` `GetDurationText` unreachable branch / incorrect validation via `IsNormalized` due `toTimestamp` validator reuse
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 69/100
Research direction
Start with src/Grpc/JsonTranscoding/src/Shared/Legacy.cs and compare GetDurationText validation with the duration sign rules described in the issue. Check the existing tests for duration JSON formatting, then add coverage for a negative subsecond duration such as seconds = 0, nanos = -500000000. Done means valid negative durations serialize correctly and invalid duration values are still rejected.
Written by the indexing model from the issue text.
Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Describe the bug
In src/Grpc/JsonTranscoding/src/Shared/Legacy.cs, the GetDurationText(int nanos, long seconds) method erroneously reuses the IsNormalized method to validate a Duration.
However, IsNormalized in this file is a direct copy of the Timestamp validation logic from Google's TimestampPartial.cs, which strictly enforces nanoseconds >= 0. Reusing it inside GetDurationText breaks the serialization of valid negative durations and makes the subsequent nanos < 0 check inside GetDurationText completely unreachable (dead code).
Root Cause Analysis
In the official Google Protobuf C# runtime (protocolbuffers/protobuf), these two types are implemented using partial classes split across generated and manual files:
TimestampPartial.csenforcesnanoseconds >= 0because timestamps are absolute points in time.DurationPartial.cs(andJsonFormatter.cs) allows negative nanoseconds because durations can be negative (e.g.,-0.5sis represented asseconds = 0, nanos = -500,000,000).
When porting this logic into ASP.NET Core's internal Legacy.cs utility class, the Timestamp version of IsNormalized was accidentally applied to GetDurationText.
As a result:
// Inside Legacy.cs
public static string GetDurationText(int nanos, long seconds)
{
if (IsNormalized(seconds, nanos)) // <-- Rejects any negative nanos, returning false
{
// ...
if (seconds < 0 || nanos < 0) // <-- This branch is now dead code
{
builder.Append('-');
}
}
}
Expected Behavior
GetDurationText should either:
- Not use
IsNormalizedand instead implement the correct sign-matching validation rule forDuration(similar to how Google'sDuration.ToJsonworks). - Or use a separate
IsNormalizedForDurationvalidator that allows negative nanoseconds whenseconds <= 0.
Steps To Reproduce
Attempting to format a negative duration under 1 second (e.g., -0.5s -> seconds = 0, nanos = -500000000) will fail validation or format incorrectly because IsNormalized will evaluate -500000000 >= 0 to false.
Exceptions (if any)
No response
.NET Version
No response
Anything else?
No response
- Dominant language
- C#
- Stars
- 38.5k
- Forks
- 11.6k
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 289
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/aspnetcore
-
area-networking
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
dotnet/aspnetcore#70545 · 1 comment ·
Maintainers usually reply within 1 day
-
area-infrastructure
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
dotnet/aspnetcore#70524 · 1 comment ·
Maintainers usually reply within 1 day
-
[OpenTelemetry] Hosting replaces an Activity.DisplayName that the app set, when a route matchesOpenarea-hosting Regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
dotnet/aspnetcore#70516 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
[SSE] ServerSentEventsResult.WrapEvents is missing [EnumeratorCancellation], so RequestAborted never reaches the app's generator on the IAsyncEnumerable<T> overloadPossibly taken @bharatprajapati-coder claimed this 1 day ago. Openarea-minimal
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
dotnet/aspnetcore#70419 · 2 comments ·
Maintainers usually reply within 1 day
-
[Blazor] DefaultInMemoryCircuitPersistenceProvider never disposes the retention CancellationTokenSource when a persist is rejected at capacityPossibly taken @Vinoth2562000 claimed this 1 day ago. Openarea-blazor
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
dotnet/aspnetcore#70418 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
All issues in dotnet/aspnetcore
Similar issues
-
bug emitter:client:csharp
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
microsoft/typespec#12572 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
needs-area-label
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 2 days
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
Maintainers usually reply within 1 day
-
priority:P2
Difficulty 2/5 1-3 hours Newbie friendliness 80/100