Skip to content

fix(firmware): decode manifests independent of content type - #6400

Merged
jamesarich merged 1 commit into
meshtastic:mainfrom
jeremiah-k:bugfix/firmware-manifest-content-type
Jul 24, 2026
Merged

fix(firmware): decode manifests independent of content type#6400
jamesarich merged 1 commit into
meshtastic:mainfrom
jeremiah-k:bugfix/firmware-manifest-content-type

Conversation

@jeremiah-k

@jeremiah-k jeremiah-k commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Overview

This makes firmware release manifest decoding independent of the HTTP response content type.

GitHub release assets can return valid JSON as application/octet-stream. Passing those responses through Ktor content negotiation can produce NoTransformationFoundException even though the request succeeded and the response body contains a valid firmware manifest.

The repository now reads the response body as text and decodes it explicitly with the firmware JSON configuration.

Key Changes

  • Read release manifests as text rather than relying on Ktor response transformation.
  • Decode release and nightly metadata with one JSON configuration.
  • Ignore unknown fields for forward compatibility.
  • Preserve serialization failures for malformed payloads.
  • Leave request URLs, transport behavior, and manifest models unchanged.

Testing

  • Added coverage for a release-style manifest containing unknown fields.
  • Added coverage proving malformed JSON still fails decoding.
  • Verified manifest decoding independently of the response content type.

Migration Notes

  • No user data migration is required.
  • Firmware manifest models and repository interfaces are unchanged.
  • This only changes how a downloaded manifest body is decoded.

Summary by CodeRabbit

  • Bug Fixes
    • Improved firmware release manifest fetching when delivered as plain text, with more resilient JSON decoding.
    • Enhanced decoding robustness by ignoring unknown JSON fields and coercing explicit null values to safe defaults.
    • Kept existing behavior for missing nightly firmware (HTTP 404) while ensuring unexpected statuses still surface errors.
  • Tests
    • Added coverage for manifest retrieval/decoding (including octet-stream responses) and for malformed or non-JSON inputs.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fad3a203-43a1-4f1a-9fc9-6df2bcc8812a

📥 Commits

Reviewing files that changed from the base of the PR and between 3bdcfb5 and 1ce27ef.

📒 Files selected for processing (4)
  • core/network/build.gradle.kts
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/service/ApiService.kt
  • core/network/src/commonTest/kotlin/org/meshtastic/core/network/service/ApiServiceTest.kt
  • gradle/libs.versions.toml
🚧 Files skipped from review as they are similar to previous changes (4)
  • gradle/libs.versions.toml
  • core/network/build.gradle.kts
  • core/network/src/commonTest/kotlin/org/meshtastic/core/network/service/ApiServiceTest.kt
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/service/ApiService.kt

📝 Walkthrough

Walkthrough

ApiService now uses shared lenient JSON configuration for firmware payloads, decodes release manifests from raw response text, and applies the configuration to nightly firmware responses. Common tests cover response content types and decoder edge cases.

Changes

Firmware JSON decoding

Layer / File(s) Summary
Release manifest decoder
core/network/src/commonMain/.../ApiService.kt
Adds shared firmware JSON settings and decodes release manifests from raw response strings.
Nightly response integration
core/network/src/commonMain/.../ApiService.kt
Uses the shared decoder for successful nightly index responses while retaining 404-to-null behavior.
Manifest decoder validation
core/network/src/commonTest/.../ApiServiceTest.kt, core/network/build.gradle.kts, gradle/libs.versions.toml
Adds mock-client-backed tests for octet-stream responses, unknown fields, null coercion, and malformed JSON, plus common-test dependency wiring.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ApiServiceImpl
  participant FirmwareEndpoint
  participant firmwareJson
  ApiServiceImpl->>FirmwareEndpoint: Request firmware manifest
  FirmwareEndpoint-->>ApiServiceImpl: Return response body as text
  ApiServiceImpl->>firmwareJson: Decode FirmwareReleaseManifest
  firmwareJson-->>ApiServiceImpl: Return decoded manifest
Loading

Possibly related PRs

Suggested labels: refactor, build

Suggested reviewers: jamesarich

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: firmware manifest decoding now works independently of HTTP content type.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bugfix PR tag label Jul 24, 2026
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 02:29
@jamesarich

Copy link
Copy Markdown
Collaborator

Nice fix — reading bodyAsText() and decoding explicitly is the right way to dodge the NoTransformationFoundException from application/octet-stream release assets, and the two tests cover the important cases.

One non-blocking note on the JSON config. The new firmwareJson = Json { ignoreUnknownKeys = true } is stricter than the JSON the old .body() path used. That path went through ContentNegotiation, which is wired to the DI provideJson() (core/network/.../di/CoreNetworkModule.kt), configured with:

isLenient = true
ignoreUnknownKeys = true
coerceInputValues = true

Since every field in FirmwareReleaseManifest/FirmwareTarget is a non-nullable type with a default, coerceInputValues = true was previously coercing an explicit null (e.g. "platform": null) to the default "". Under the new config that same payload now throws SerializationException — a small behavioral narrowing. So decodeFirmwareReleaseManifest isn't quite "the same JSON configuration" the rest of the app uses; it's a new, narrower one.

Suggestion (low real-world likelihood, but a free robustness win): either inject the DI Json into ApiServiceImpl and reuse it, or just add isLenient = true and coerceInputValues = true to firmwareJson to preserve the prior tolerance and stay consistent with the surrounding code.

@jeremiah-k

Copy link
Copy Markdown
Contributor Author

@jamesarich Good catch. You’re right that the explicit decoder currently narrows the tolerance provided by the shared Json configuration.

I’ll align firmwareJson with the existing settings by adding isLenient and coerceInputValues, and add coverage for an explicit null falling back to the model default.

@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 11:41
@jeremiah-k
jeremiah-k force-pushed the bugfix/firmware-manifest-content-type branch 2 times, most recently from dcd689a to 72cce7a Compare July 24, 2026 12:59
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 13:01

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
core/network/src/commonTest/kotlin/org/meshtastic/core/network/service/ApiServiceTest.kt (1)

26-44: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the HTTP content-type regression path.

These tests call decodeFirmwareReleaseManifest directly, so they would still pass if getFirmwareReleaseManifest() regressed to content-negotiated decoding. Add a service-level test returning valid JSON as application/octet-stream and exercise the release endpoint; also cover the nightly endpoint if it is part of this change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/network/src/commonTest/kotlin/org/meshtastic/core/network/service/ApiServiceTest.kt`
around lines 26 - 44, Add service-level tests alongside `ApiServiceTest` that
configure the release endpoint to return valid firmware manifest JSON with
`application/octet-stream`, then invoke `getFirmwareReleaseManifest()` and
assert the decoded result. If the nightly endpoint is included in the
implementation, add the equivalent test for
`getNightlyFirmwareReleaseManifest()`; retain the direct decoder test for
unknown-field handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@core/network/src/commonTest/kotlin/org/meshtastic/core/network/service/ApiServiceTest.kt`:
- Around line 26-44: Add service-level tests alongside `ApiServiceTest` that
configure the release endpoint to return valid firmware manifest JSON with
`application/octet-stream`, then invoke `getFirmwareReleaseManifest()` and
assert the decoded result. If the nightly endpoint is included in the
implementation, add the equivalent test for
`getNightlyFirmwareReleaseManifest()`; retain the direct decoder test for
unknown-field handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de756f5-a577-4baa-854b-a374f42d15ca

📥 Commits

Reviewing files that changed from the base of the PR and between a82d0e2 and 72cce7a.

📒 Files selected for processing (2)
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/service/ApiService.kt
  • core/network/src/commonTest/kotlin/org/meshtastic/core/network/service/ApiServiceTest.kt

@jamesarich
jamesarich enabled auto-merge July 24, 2026 13:08
auto-merge was automatically disabled July 24, 2026 13:09

Head branch was pushed to by a user without write access

@jeremiah-k
jeremiah-k force-pushed the bugfix/firmware-manifest-content-type branch from 72cce7a to 3bdcfb5 Compare July 24, 2026 13:09
@jeremiah-k

jeremiah-k commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

I just added that last coderabbit nit test too.

@jamesarich
jamesarich enabled auto-merge July 24, 2026 13:31
@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 14:24
auto-merge was automatically disabled July 24, 2026 14:24

Pull request was converted to draft

GitHub release assets can serve valid firmware manifest JSON as application/octet-stream. Passing those responses through Ktor content negotiation raises NoTransformationFoundException even though the payload and HTTP status are valid.

Read release manifests as text and decode them explicitly. Preserve the former shared JSON tolerance for lenient syntax, unknown fields, and explicit nulls on defaulted properties while continuing to propagate transport, HTTP, and malformed-payload failures.

Cover forward-compatible fields, explicit-null coercion against the actual manifest model, malformed input, and the complete ApiService path with an octet-stream MockEngine response so content-negotiated decoding cannot regress unnoticed.
@jeremiah-k
jeremiah-k force-pushed the bugfix/firmware-manifest-content-type branch from 3bdcfb5 to 1ce27ef Compare July 24, 2026 15:45
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 16:09
@jamesarich
jamesarich added this pull request to the merge queue Jul 24, 2026
Merged via the queue into meshtastic:main with commit 500ab7a Jul 24, 2026
15 checks passed
@jeremiah-k
jeremiah-k deleted the bugfix/firmware-manifest-content-type branch July 24, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants