Skip to content

Replace qmicli IMS probing with native QMI detection - #17

Merged
frankwei98 merged 10 commits into
mainfrom
codex/native-ims-detection
Aug 5, 2026
Merged

frankwei98 merged 10 commits into
mainfrom
codex/native-ims-detection

Conversation

@frankwei98

@frankwei98 frankwei98 commented Aug 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Replace the qmicli subprocess probe with native QMI/QMUX encoding over the system qmi-proxy transport.
  • Report VoLTE, VoWiFi, and SMS-over-IMS capability, configuration, registration, service, and access technology as separate evidence.
  • Keep active VoLTE/VoWiFi classification conservative: registration, available voice service, and a known access technology are all required.
  • Update the modem WebUI and all supported locales, and remove obsolete private qmicli installation support.
  • Keep this phase read-only; it does not write modem profiles, PDC, or NV settings.

Review follow-up

  • Preserve valid service responses when CID cleanup fails, while completing release cleanup asynchronously with a timeout.
  • De-duplicate rendered diagnostics and expose unknown diagnostic codes.
  • Explain unavailable or failed NAS voice-capability queries.
  • Add native-probe failure coverage and panic-safe test socket cleanup.
  • Enforce exhaustive IMS enum translation mappings.

Testing

  • cargo fmt --check
  • cargo test --locked — 304 passed
  • cargo clippy --all-targets --locked -- -D warnings -A clippy::large-enum-variant -A clippy::assertions-on-constants
  • pnpm check
  • pnpm test — 131 passed
  • pnpm build

@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The IMS implementation now uses native QMI through qmi-proxy. It reports separate VoLTE, VoWiFi, and SMS-over-IMS data, updates localized frontend rendering and tests, and removes qmicli-based operational requirements.

Changes

Native IMS diagnostics

Layer / File(s) Summary
QMI proxy transport
src/modem/ims/qmi.rs
Adds Unix-socket QMI requests, framing, client management, timeouts, response validation, and typed TLV access.
IMS model and native probe
src/modem/ims.rs, src/modem.rs, frontend/src/lib/modem-api.ts
Replaces qmicli probing with NativeImsProbe, adds voice and SMS service models, classifies VoLTE and VoWiFi states, and wires the native probe into ModemService.
Frontend IMS status rendering
frontend/src/components/modem/modem-status-panel.tsx, frontend/src/locales/*, frontend/src/modem-status-panel.test.tsx
Renders the expanded IMS data with translated fields, evidence, diagnostics, voice badges, and native-QMI fixtures.
Documentation and environment cleanup
README.md, docs/operations/sms-over-ims.md, install.sh, scripts/install-private-qmicli-debian.sh, tests/install-private-qmicli.sh
Documents the read-only native QMI probe, removes qmicli installation guidance and tests, and limits environment warnings to missing mmcli.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModemService
  participant NativeImsProbe
  participant QmiProxy
  participant ModemPage
  ModemService->>NativeImsProbe: probe modem IMS data
  NativeImsProbe->>QmiProxy: send native QMI requests
  QmiProxy-->>NativeImsProbe: return capability and runtime TLVs
  NativeImsProbe-->>ModemService: return classified voice and SMS status
  ModemPage->>ModemService: request authenticated modem status
  ModemService-->>ModemPage: return translated IMS diagnostics data
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change from qmicli-based IMS probing to native QMI detection.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/native-ims-detection

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.

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

Actionable comments posted: 2

🧹 Nitpick comments (7)
src/modem/ims.rs (4)

673-694: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the probe error paths.

The new tests cover successful responses only. Three error paths carry user-visible diagnostics and have no test:

  • NativeQmiError::Qmi(_) from a response whose result TLV reports a failure. That path drives native_qmi_probe_failed.
  • NativeQmiError::PermissionDenied and NativeQmiError::ProxyUnavailable from map_proxy_io_error. Those drive ims_probe_permission_denied and qmi_proxy_unavailable.
  • The early return at lines 357-360 when the version query fails, which must leave probe.available false.

FakeNativeQmiClient already makes the first case cheap: return a frame with a non-zero failed field in the result TLV.

🤖 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 `@src/modem/ims.rs` around lines 673 - 694, The NativeImsProbe tests cover only
successful responses and omit user-visible error paths. Extend the test coverage
around NativeImsProbe and FakeNativeQmiClient for a non-zero result-TLV failure
producing NativeQmiError::Qmi(_) and native_qmi_probe_failed, map_proxy_io_error
cases producing PermissionDenied and ProxyUnavailable with their corresponding
diagnostics, and a failed version query that returns early while leaving
probe.available false.

886-890: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the test socket cleanup panic-safe.

Line 890 creates the socket file. Line 952 removes it. If any assertion between those lines fails, or if server.await.unwrap() panics because a server-side assertion failed, remove_file never runs and the socket file stays in /tmp.

Bind the socket inside a temporary directory that removes itself on drop, or wrap the path in a guard type with a Drop impl.

Also applies to: 951-952

🤖 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 `@src/modem/ims.rs` around lines 886 - 890, Make the test socket lifecycle
around the UnixListener binding and cleanup panic-safe by placing the socket
path in a temporary directory with automatic removal, or by introducing a
Drop-based cleanup guard. Ensure cleanup still occurs when assertions or
server.await.unwrap() panic, and remove the reliance on the explicit remove_file
call near the listener teardown.

374-387: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Record a diagnostic when the NAS query is absent or fails.

Line 378 discards a NAS error with if let Ok(response) = nas. Line 374 also skips the query silently when versions.nas is false. In both cases lte_voice_support and ims_voice_support stay None, and the panel renders them as "Unknown" with no diagnostic line.

Every other query in this function records a blocker or a warning: IMSA at lines 415-418 and 443-444, IMS at lines 483-490. Add the same treatment for NAS so the two voice-capability fields have an explanation when they are unknown.

New codes also need entries in imsDiagnosticMessage in frontend/src/components/modem/modem-status-panel.tsx and in all six locale files.

♻️ Proposed change
         if versions.nas {
             let nas = self
                 .request_response(&device, QMI_SERVICE_NAS, QMI_NAS_GET_SYSTEM_INFO, deadline)
                 .await;
-            if let Ok(response) = nas {
-                result.lte_voice_support = response.bool(0x21);
-                result.ims_voice_support = response.bool(0x29);
-                if result.ims_voice_support == Some(true) {
-                    result
-                        .evidence
-                        .push("qmi_nas_ims_voice_support".to_string());
+            match nas {
+                Ok(response) => {
+                    result.lte_voice_support = response.bool(0x21);
+                    result.ims_voice_support = response.bool(0x29);
+                    if result.ims_voice_support == Some(true) {
+                        result
+                            .evidence
+                            .push("qmi_nas_ims_voice_support".to_string());
+                    }
                 }
+                Err(error) => result.warnings.push(native_query_error_code(
+                    error,
+                    "ims_voice_support_query_failed",
+                )),
             }
+        } else {
+            result
+                .warnings
+                .push("ims_voice_support_query_unavailable".to_string());
         }
🤖 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 `@src/modem/ims.rs` around lines 374 - 387, Update the NAS handling in the
function containing the versions.nas query to record a diagnostic when the NAS
service is unavailable and when request_response fails, while preserving the
existing capability parsing on success. Add distinct diagnostic codes/messages
using the established blocker or warning conventions, map both codes in
imsDiagnosticMessage, and provide translations for each in all six locale files.

290-293: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a Default impl for NativeImsProbe.

NativeImsProbe::new() is public and takes no arguments, which triggers clippy::new_without_default. Add impl Default for NativeImsProbe { fn default() -> Self { Self::new() } } so the public constructor remains usable without a warning in CI configurations that treat clippy warnings as errors.

🤖 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 `@src/modem/ims.rs` around lines 290 - 293, Add an implementation of Default
for NativeImsProbe that returns Self::new(), while preserving the existing
public new constructor and its behavior.
frontend/src/components/modem/modem-status-panel.tsx (2)

524-526: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Include the unmapped code in the fallback text.

The fallback returns a generic message and discards code. If the backend adds a diagnostic code before the frontend map is updated, the user and support see no identifier for the condition.

Append the raw code so an unmapped diagnostic stays traceable.

♻️ Proposed change
 	return code in keys
 		? t(keys[code as keyof typeof keys])
-		: t("modem.imsDiagnostics.fallback");
+		: `${t("modem.imsDiagnostics.fallback")} (${code})`;
🤖 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 `@frontend/src/components/modem/modem-status-panel.tsx` around lines 524 - 526,
Update the fallback branch in the diagnostic translation logic to append the raw
code to the existing fallback message, while leaving mapped-code translation
unchanged. Use the `code` value in the `t("modem.imsDiagnostics.fallback")` path
so unmapped diagnostics remain identifiable.

464-478: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the formatImsEnum parameter type so missing translations fail at compile time.

The parameter is value: string and the fallback at line 477 returns the raw value. If a future backend enum variant reaches this function without a key, the panel renders a raw snake_case code such as not_registered to the user, and TypeScript reports nothing.

Type the parameter as the union of the enums that are actually passed. Then a new variant without a key becomes a compile error.

Also note that not_available at line 472 is unreachable. No backend enum serializes to that value; ImsServiceStatus in src/modem/ims.rs uses unavailable. The matching enum.notAvailable key is dead in all six locale files.

♻️ Proposed change
-function formatImsEnum(value: string, t: TFunction) {
+type ImsEnumValue =
+	| SmsOverIms["status"]
+	| SmsOverIms["voice_over_ims"]
+	| SmsOverIms["registration"]
+	| SmsOverIms["voice_service"]
+	| SmsOverIms["configured"];
+
+function formatImsEnum(value: ImsEnumValue, t: TFunction) {
 	const keys = {
🤖 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 `@frontend/src/components/modem/modem-status-panel.tsx` around lines 464 - 478,
Update formatImsEnum to accept the concrete union type of backend IMS enum
values passed by its callers instead of string, so adding an unmapped variant
produces a compile-time error. Remove the unreachable not_available key and its
corresponding unused enum.notAvailable translations, while retaining unavailable
and the existing mapped translation behavior.
src/modem/ims/qmi.rs (1)

137-142: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not let the CID release response mask a valid service response.

Line 137 uses ? on the release response. If the release read fails or the proxy returns an error for QMI_CTL_RELEASE_CID, the function returns Protocol and discards the service response that was already read at line 124. The release is best-effort cleanup: the stream is per-request and is closed when it drops.

Resolve the service response first, then treat the release result as advisory.

♻️ Proposed change to prioritize the service response
-        let release_response = read_qmi_frame(&mut stream).await?;
-        QmiResponse::parse(&release_response, QMI_SERVICE_CTL, QMI_CTL_RELEASE_CID)?;
-
-        let response = service_response?;
+        let release_outcome = read_qmi_frame(&mut stream)
+            .await
+            .and_then(|raw| {
+                QmiResponse::parse(&raw, QMI_SERVICE_CTL, QMI_CTL_RELEASE_CID).map(|_| ())
+            });
+
+        let response = service_response?;
+        release_outcome?;
         QmiResponse::parse(&response, service, message)?;
         Ok(response)
🤖 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 `@src/modem/ims/qmi.rs` around lines 137 - 142, Update the cleanup flow around
the service_response resolution and QMI_CTL_RELEASE_CID parsing so the service
response is resolved and returned first; perform CID release only as best-effort
advisory cleanup without allowing release read or parse errors to override a
valid service response. Preserve the existing service-response error
propagation.
🤖 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.

Inline comments:
In `@frontend/src/components/modem/modem-status-panel.tsx`:
- Around line 362-368: De-duplicate the combined diagnostics before rendering in
the diagnostics panel, since identical codes from reasons and warnings can
produce duplicate React keys. Update the diagnostics rendering around the
diagnostics.map call to iterate over unique codes while preserving the existing
imsDiagnosticMessage translation and display behavior.

In `@frontend/src/locales/es.ts`:
- Around line 72-73: The IMS card descriptions still use the outdated generic
route wording. Update the description entries in frontend/src/locales/es.ts
lines 72-73, frontend/src/locales/fr.ts lines 72-73, frontend/src/locales/ja.ts
lines 72-73, and frontend/src/locales/ko.ts lines 72-73 with localized text
explaining that VoLTE or VoWiFi is active only when the modem reports IMS
registration, voice service, and access technology, matching the updated English
and Chinese meaning.

---

Nitpick comments:
In `@frontend/src/components/modem/modem-status-panel.tsx`:
- Around line 524-526: Update the fallback branch in the diagnostic translation
logic to append the raw code to the existing fallback message, while leaving
mapped-code translation unchanged. Use the `code` value in the
`t("modem.imsDiagnostics.fallback")` path so unmapped diagnostics remain
identifiable.
- Around line 464-478: Update formatImsEnum to accept the concrete union type of
backend IMS enum values passed by its callers instead of string, so adding an
unmapped variant produces a compile-time error. Remove the unreachable
not_available key and its corresponding unused enum.notAvailable translations,
while retaining unavailable and the existing mapped translation behavior.

In `@src/modem/ims.rs`:
- Around line 673-694: The NativeImsProbe tests cover only successful responses
and omit user-visible error paths. Extend the test coverage around
NativeImsProbe and FakeNativeQmiClient for a non-zero result-TLV failure
producing NativeQmiError::Qmi(_) and native_qmi_probe_failed, map_proxy_io_error
cases producing PermissionDenied and ProxyUnavailable with their corresponding
diagnostics, and a failed version query that returns early while leaving
probe.available false.
- Around line 886-890: Make the test socket lifecycle around the UnixListener
binding and cleanup panic-safe by placing the socket path in a temporary
directory with automatic removal, or by introducing a Drop-based cleanup guard.
Ensure cleanup still occurs when assertions or server.await.unwrap() panic, and
remove the reliance on the explicit remove_file call near the listener teardown.
- Around line 374-387: Update the NAS handling in the function containing the
versions.nas query to record a diagnostic when the NAS service is unavailable
and when request_response fails, while preserving the existing capability
parsing on success. Add distinct diagnostic codes/messages using the established
blocker or warning conventions, map both codes in imsDiagnosticMessage, and
provide translations for each in all six locale files.
- Around line 290-293: Add an implementation of Default for NativeImsProbe that
returns Self::new(), while preserving the existing public new constructor and
its behavior.

In `@src/modem/ims/qmi.rs`:
- Around line 137-142: Update the cleanup flow around the service_response
resolution and QMI_CTL_RELEASE_CID parsing so the service response is resolved
and returned first; perform CID release only as best-effort advisory cleanup
without allowing release read or parse errors to override a valid service
response. Preserve the existing service-response error propagation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c811d376-5608-4549-b74a-5564a2d5904e

📥 Commits

Reviewing files that changed from the base of the PR and between f75764a and 78481d3.

📒 Files selected for processing (17)
  • README.md
  • docs/operations/sms-over-ims.md
  • frontend/src/components/modem/modem-status-panel.tsx
  • frontend/src/lib/modem-api.ts
  • frontend/src/locales/en.ts
  • frontend/src/locales/es.ts
  • frontend/src/locales/fr.ts
  • frontend/src/locales/ja.ts
  • frontend/src/locales/ko.ts
  • frontend/src/locales/zh-CN.ts
  • frontend/src/modem-status-panel.test.tsx
  • install.sh
  • scripts/install-private-qmicli-debian.sh
  • src/modem.rs
  • src/modem/ims.rs
  • src/modem/ims/qmi.rs
  • tests/install-private-qmicli.sh
💤 Files with no reviewable changes (3)
  • tests/install-private-qmicli.sh
  • install.sh
  • scripts/install-private-qmicli-debian.sh

Comment thread frontend/src/components/modem/modem-status-panel.tsx
Comment thread frontend/src/locales/es.ts Outdated
@frankwei98 frankwei98 changed the title Add native IMS detection and private qmicli installation support Replace qmicli IMS probing with native QMI detection Aug 5, 2026
@frankwei98
frankwei98 merged commit 922fcca into main Aug 5, 2026
6 checks passed
@frankwei98
frankwei98 deleted the codex/native-ims-detection branch August 5, 2026 17:18
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.

1 participant