Skip to content

[.NET] Fix issues around handling errors during certificate signing operations - #390

Closed
Tim Taylor (timtay-microsoft) wants to merge 13 commits into
mainfrom
timtay/certError
Closed

Tim Taylor (timtay-microsoft) wants to merge 13 commits into
mainfrom
timtay/certError

Conversation

@timtay-microsoft

Copy link
Copy Markdown
Member

Some error payload shapes weren't deserialized correctly so the certificate operation call would appear to hang

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A failed resubmission can cancel the original operation and leave its eventual certificate response untracked.

2 open findings
What changed in this PR

Improves .NET certificate-signing response handling so malformed or service-error payloads fail operations instead of leaving them pending.

Changes:

  • Adds resilient error parsing and richer failure details.
  • Ensures responses are acknowledged and operations terminate deterministically.
  • Adds extensive malformed-response and callback-failure tests.
File Description
ConnectionClientUnitTests.cs Expands certificate response coverage.
ConnectionClient.cs Reworks response routing and failure handling.
CertificateSigningRequestFailedException.cs Adds structured failure metadata.
CertificateSigningRequestErrorResponse.cs Adds tolerant payload parsing.
CertificateSigningOperation.cs Updates operation documentation.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dotnet/src/Microsoft.Azure.Iot.Device/Unified/Connection/ConnectionClient.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Incompatible payloads can still leave operations pending, and the changes introduce breaking API renames and invalid documentation examples.

3 open findings
1 resolved since last review

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 9, 2026 22:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Deserialization and callback exceptions can still leave certificate-signing tasks pending indefinitely.

3 open findings
1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Add regression tests for error payloads and callback failures

dotnet/​src/​Microsoft.Azure.Iot.Device/​Models/​CertificateManagement/​CertificateSigningRequestErrorResponse.cs:15

Please add unit coverage for each supported error payload shape and assert that both Accepted and Completed settle. The existing certificate integration test covers only success, while the unit-test MQTT mock can inject responses; without these regression cases, omitted fields, incompatible field types, malformed payloads, and callback failures can reintroduce the indefinite wait this change targets.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 9, 2026 22:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

JSON null responses can still leave completion pending, produce invalid exceptions, and retain terminal operations.

4 open findings
Previously missed (3)

In code that hasn't changed since last review

Medium severity Handle null error payloads without creating invalid exceptions

dotnet/​src/​Microsoft.Azure.Iot.Device/​Unified/​Connection/​ConnectionClient.cs:196

A 400 payload of JSON null deserializes without throwing, so this constructs a CertificateSigningRequestFailedException whose required, non-null Error property is actually null. The new test only checks the exception type, but callers such as the integration path dereference e.Error.Message and will then get a NullReferenceException. Treat a null deserialization result as an unreadable response and synthesize the fallback error.

Medium severity Reject null deserialization results before accepting responses

dotnet/​src/​Microsoft.Azure.Iot.Device/​Unified/​Connection/​ConnectionClient.cs:207

Valid JSON null does not throw JsonException. For a 202 response, deserialization therefore passes null to SetAccepted, so Accepted succeeds with a null result while Completed remains pending indefinitely—the hang this change is intended to prevent. Explicitly reject null results from each response deserialization before settling the operation.

Low severity Correct task failure semantics after acceptance

dotnet/​src/​Microsoft.Azure.Iot.Device/​Models/​CertificateManagement/​CertificateSigningOperation.cs:17

This statement conflicts with the operation's actual task semantics and the new CertificateSigningErrorAfterAcceptanceFailsOnlyCompletedTask test: once Accepted has succeeded, a later issuance failure cannot make it throw, and a completion callback exception is propagated as its original type. Distinguish failures before acceptance from failures during completion so consumers do not expect both tasks to fault in all cases.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 9, 2026 22:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

JSON null responses can still leave operations hanging, and exception paths retain pending operations.

3 open findings
1 resolved since last review
Previously missed (4)

In code that hasn't changed since last review

Medium severity Make the info property nullable or normalize null values

dotnet/​src/​Microsoft.Azure.Iot.Device/​Models/​CertificateManagement/​CertificateSigningRequestErrorResponse.cs:48

System.Text.Json can assign null when the service sends "info": null, overwriting this initializer while the public property still promises a non-null value. Since the new remarks state every field is optional, make this property nullable (or normalize null in a setter) so its annotation matches the values callers can actually receive.

Medium severity Reject null error payloads before creating failure exceptions

dotnet/​src/​Microsoft.Azure.Iot.Device/​Unified/​Connection/​ConnectionClient.cs:198

For an error payload containing JSON null, deserialization succeeds with a null result and creates a CertificateSigningRequestFailedException whose required Error property is null. Callers such as the sample and integration tests dereference ex.Error.Message, so handling this failure causes another NullReferenceException. Route null through the malformed-response handling instead.

This issue also appears on line 202 of the same file.

Low severity Document behavior before and after acceptance completes

dotnet/​src/​Microsoft.Azure.Iot.Device/​Models/​CertificateManagement/​CertificateSigningOperation.cs:17

This still overstates the task behavior: once Accepted has completed successfully, TrySetException cannot rewrite it, and the new post-acceptance error test explicitly verifies that only Completed faults. Describe the before- versus after-acceptance behavior so consumers do not wait for an exception from an already-completed task.

Low severity Test null payloads for successful response statuses

dotnet/​tests/​Microsoft.Azure.Iot.Device.UnitTests/​Unified/​ConnectionClientUnitTests.cs:130

The new malformed-response matrix covers JSON null only for an error status, but the 202 branch is the case where null currently completes Accepted with null and leaves Completed hanging. Add null cases for the successful response statuses so this regression is exercised.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 9, 2026 23:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Null payloads, callback JSON exceptions, operation cleanup, and public API compatibility remain incorrectly handled.

5 open findings
Previously missed (2)

In code that hasn't changed since last review

Medium severity Null JSON responses are not rejected

dotnet/​src/​Microsoft.Azure.Iot.Device/​Unified/​Connection/​ConnectionClient.cs:200

JsonSerializer.Deserialize returns null for the valid JSON token null; it does not throw JsonException. Consequently a 202 payload of null calls SetAccepted(null) and leaves Completed pending, a 200 payload can complete with a null result, and an error payload produces an exception whose required Error is null. Explicitly reject null after all three deserializations (for example, ?? throw new JsonException(...)) so these malformed responses take this failure path.

This issue also appears on line 197 of the same file.

Medium severity Failed sends leak pending operations

dotnet/​tests/​Microsoft.Azure.Iot.Device.UnitTests/​Unified/​ConnectionClientUnitTests.cs:252

This test misses the pending-operation leak: SendCertificateSigningRequestAsync inserts the operation before subscribing/publishing and never removes it when either await throws. Thus each failed send leaves an operation that the caller cannot access in _pendingCertificateSigningOperations. Assert that the count returns to zero here and clean up the dictionary on send failure.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 9, 2026 23:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

JSON null responses and malformed 202 responses can still produce hangs, invalid results, or leaked pending operations.

5 open findings
1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Low severity Align task failure behavior with the documented contract

dotnet/​src/​Microsoft.Azure.Iot.Device/​Models/​CertificateManagement/​CertificateSigningOperation.cs:17

This contract says both tasks throw whenever the operation fails, but a failure after acceptance leaves Accepted successful (as the new test verifies), and a completion callback can propagate an exception other than CertificateSigningRequestFailedException. Distinguish failures before and after acceptance and callback failures so consumers can rely on the public documentation.

🧠 Review effort: Balanced

Comment thread dotnet/src/Microsoft.Azure.Iot.Device/Unified/Connection/ConnectionClient.cs Outdated
Copilot AI balanced review requested due to automatic review settings October 10, 2026 00:06
Throw JsonException if response is null after deserialization.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Malformed acceptance and JSON-null responses can still leak operations, leave tasks pending, or return invalid null results.

4 open findings
2 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Low severity Document phase-dependent task failure behavior

dotnet/​src/​Microsoft.Azure.Iot.Device/​Models/​CertificateManagement/​CertificateSigningOperation.cs:17

This public contract still says both tasks fail for an error at any point, but the new callback-failure behavior (and the added test) leaves Accepted successfully completed and faults only Completed. The same is true for any hub failure after a successful 202. Document the phase-dependent behavior so callers do not incorrectly expect Accepted to be rewritten.

🧠 Review effort: Balanced

Comment thread dotnet/src/Microsoft.Azure.Iot.Device/Unified/Connection/ConnectionClient.cs Outdated
Copilot AI balanced review requested due to automatic review settings October 10, 2026 00:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Failed publishes and malformed 202 responses can leave stale operations that cause same-ID retries to hang.

4 open findings
2 resolved since last review

🧠 Review effort: Balanced

/// <returns>A set of tasks. One that completes when IoT hub accepts the request (and starts signing), one that completes when IoT hub completes the signing, and one that completes if any step in the process fails.</returns>
public async Task<CertificateSigningOperation> SendCertificateSigningRequestAsync(IotHubCertificateSigningRequest request, CancellationToken cancellationToken = default)
{
//TODO how does hub respond if device loses connection at any point during this process?
Co-authored-by: timtay-microsoft <28789848+timtay-microsoft@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A malformed 202 leaves its request ID registered, causing a supported same-ID retry to remain untracked and hang.

1 open finding
3 resolved since last review

🧠 Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants