Skip to content

Extract shared call protocol, crypto, and key package helpers#486

Merged
justinmoon merged 4 commits intomasterfrom
openclaw
Mar 8, 2026
Merged

Extract shared call protocol, crypto, and key package helpers#486
justinmoon merged 4 commits intomasterfrom
openclaw

Conversation

@justinmoon
Copy link
Copy Markdown
Collaborator

@justinmoon justinmoon commented Mar 7, 2026

Summary

  • pikaci: staged run plans — adds structured run plan model and staged executor for CI
  • Extract shared key package interop helpers — moves key package fetch/upload logic into pika-marmot-runtime so both the sidecar daemon and core can reuse it
  • Route remaining key package fetches through shared helper — completes the migration so all key package interop goes through the shared crate
  • Extract shared call protocol and crypto helpers — pulls call control and crypto routines into pika-marmot-runtime, shrinking call_control.rs and daemon.rs significantly
  • Add pika core extraction plan — documents the next steps for the pika-core extraction in todos/pika-core-final.md

Net effect: ~1750 lines added, ~875 removed. The bulk of the diff is moving duplicated MLS/call logic into the shared pika-marmot-runtime crate.

Test plan

  • CI passes (cargo check, cargo test, clippy)

🤖 Generated with Claude Code


Open with Devin

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced call signaling with improved invite, accept, reject, and end message handling
    • Improved key package retrieval with automatic format compatibility adjustments
    • Added relay authentication token validation for secure communication
  • Refactor

    • Consolidated call cryptography and signal handling into shared runtime module
    • Centralized key package processing logic across the codebase
  • Chores

    • Added comprehensive architecture planning documentation for future development

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 7, 2026

📝 Walkthrough

Walkthrough

This PR extracts and consolidates call signaling and key package management logic into a shared pika-marmot-runtime library, replacing duplicated implementations across the CLI, daemon, and Rust core codebases with centralized, re-exported utilities and new modules for signal parsing, cryptography, and key package normalization.

Changes

Cohort / File(s) Summary
CLI key package refactoring
cli/src/session.rs, cli/src/harness.rs, cli/src/main.rs, cli/src/relay_util.rs
Renamed fetch_latest_key_package to fetch_latest_key_package_for_mdk across the CLI, updated all call sites, and changed the re-export in relay_util to use the new function name from pika_marmot_runtime::relay.
Runtime library expansion
crates/pika-marmot-runtime/Cargo.toml, crates/pika-marmot-runtime/src/lib.rs
Added base64 and pika-media dependencies. Exported two new public modules: call and key_package.
Call signaling and crypto module
crates/pika-marmot-runtime/src/call.rs
New comprehensive module implementing call envelope structures (signal types: invite, accept, reject, end), signal parsing/building utilities, media cryptography context derivation, relay authentication token generation and validation, and supporting utilities (hashing, timestamp, key IDs).
Key package utilities module
crates/pika-marmot-runtime/src/key_package.rs
New module providing relay extraction from key package events, legacy key package normalization (hex↔base64 conversion, version/ciphersuite compatibility), and MLS tag handling with comprehensive test coverage.
Relay module enhancement
crates/pika-marmot-runtime/src/relay.rs
Added fetch_latest_key_package_for_mdk function that wraps the existing key package fetch with automatic normalization for MDK compatibility.
Daemon consolidation
crates/pikachat-sidecar/src/daemon.rs
Removed local call signaling types and helpers; imported and reused CallSessionParams, CallTrackSpec, CallMediaCryptoContext, signal parsing, and crypto derivation functions from pika_marmot_runtime::call. Updated key package fetching to use fetch_latest_key_package_for_mdk.
Rust core call control refactoring
rust/src/core/call_control.rs
Removed local implementations of call types and signal logic; imported CallCryptoDeriveContext, CallMediaCryptoContext, CallSessionParams, CallTrackSpec, ParsedCallSignal from pika_marmot_runtime::call. Delegated media crypto and relay auth derivation to shared runtime functions.
Rust core call runtime
rust/src/core/call_runtime.rs
Removed local CallMediaCryptoContext declaration; imported from call_control which now sources it from shared runtime.
Rust core interop module
rust/src/core/interop.rs
Removed local key package extraction and normalization functions; re-exported normalize_peer_key_package_event_for_mdk and relay extraction utilities from pika_marmot_runtime::key_package.
Rust workspace dependency
rust/Cargo.toml
Added pika-marmot-runtime as a workspace path dependency.
Architecture planning
todos/pika-core-final.md
New comprehensive document detailing incremental extraction strategy, phased migration plan, ownership boundaries, interface model, and governance discipline for pika_core refactoring.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Signals hop through shared chambers clean,
Crypto keys dance where threads convene,
Logic unified from scattered places,
Relay tokens flow through trusted spaces,
MDK's journey, normalized and bright! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main objective: extracting shared call protocol, crypto, and key package helpers into a reusable crate for both sidecar daemon and core modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch openclaw

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

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/pikachat-sidecar/src/daemon.rs (1)

751-762: ⚠️ Potential issue | 🟠 Major

Redact call payloads before logging parse failures.

These warnings can dump relay_auth from invite/accept bodies into logs. A malformed signal should not turn a bearer capability into log data.

Also applies to: 805-810

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pikachat-sidecar/src/daemon.rs` around lines 751 - 762, The
parse-failure warning for CallSignalEnvelopeCompat logs the raw content which
can leak sensitive fields like relay_auth; update the error handling in the
serde_json::from_str::<CallSignalEnvelopeCompat>(content) Err branch (and the
analogous block handling call signals later) to redact the call payload before
logging: detect when content contains "pika.call", "call.invite", or
"call.accept" and replace or mask the body/details (e.g., remove JSON fields
that may contain credentials or replace with a fixed "[REDACTED_CALL_PAYLOAD]")
and then log the sanitized string in the warn! call instead of the raw content
so parse errors are still surfaced without leaking bearer capabilities.
rust/src/core/call_control.rs (1)

823-833: ⚠️ Potential issue | 🟠 Major

Core dropped the compatibility parsing that the sidecar still keeps.

crates/pikachat-sidecar/src/daemon.rs still unwraps double-encoded payloads and nested content / rumor.content envelopes before calling the shared parser. Calling the strict parser directly here means those same call signals will be silently ignored in core.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/core/call_control.rs` around lines 823 - 833,
maybe_parse_call_signal currently calls the strict parse_call_signal directly
and thus drops the sidecar's compatibility handling for double-encoded payloads
and nested content/rumor.content envelopes; update maybe_parse_call_signal to
first perform the same compatibility unwrapping the sidecar uses (try decoding
double-encoded JSON, unwrap nested "content" or "rumor.content" fields) and only
then pass the resulting string to parse_call_signal (or create a small helper
like compat_parse_call_signal that encapsulates the unwrap logic and calls
parse_call_signal). Ensure you still keep the early-return check against
self.session.pubkey (the existing my_pubkey check) and use the same
error-tolerant behavior as the sidecar unwrap logic so legacy call signals are
not silently ignored.
🧹 Nitpick comments (4)
crates/pika-marmot-runtime/src/relay.rs (1)

66-68: Consider removing the trivial wrapper.

normalize_fetched_key_package_for_mdk is a pass-through to normalize_peer_key_package_event_for_mdk. You could call the imported function directly in fetch_latest_key_package_for_mdk unless you anticipate adding relay-specific normalization logic here.

♻️ Proposed simplification
 pub async fn fetch_latest_key_package_for_mdk(
     client: &Client,
     author: &PublicKey,
     relay_urls: &[RelayUrl],
     timeout: Duration,
 ) -> Result<Event> {
     let event = fetch_latest_key_package(client, author, relay_urls, timeout).await?;
-    Ok(normalize_fetched_key_package_for_mdk(&event))
+    Ok(normalize_peer_key_package_event_for_mdk(&event))
 }
-
-fn normalize_fetched_key_package_for_mdk(event: &Event) -> Event {
-    normalize_peer_key_package_event_for_mdk(event)
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pika-marmot-runtime/src/relay.rs` around lines 66 - 68, The function
normalize_fetched_key_package_for_mdk is a trivial passthrough to
normalize_peer_key_package_event_for_mdk; remove the wrapper and update callers
(notably fetch_latest_key_package_for_mdk) to call
normalize_peer_key_package_event_for_mdk directly, or if you want to keep a
relay-specific hook, keep the wrapper but add relay-specific logic—ensure no
remaining references to normalize_fetched_key_package_for_mdk remain after the
change.
crates/pika-marmot-runtime/src/call.rs (1)

119-125: Consider documenting the timestamp overflow behavior.

The cast as_millis() as i64 could theoretically overflow for dates beyond ~292 million years from Unix epoch. While practically safe, a brief comment noting this is intentionally ignored for call signaling purposes would improve clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pika-marmot-runtime/src/call.rs` around lines 119 - 125, In function
now_millis(), the conversion `.as_millis() as i64` can theoretically overflow
for timestamps far beyond practical ranges; add a short inline comment or doc
comment above now_millis() stating that potential overflow is intentionally
ignored (dates ~292 million years out) because the value is only used for call
signaling and the cast is safe for all expected use cases. Reference the
now_millis() function and its use of SystemTime::now(), UNIX_EPOCH, and
as_millis() when adding the explanatory note.
cli/src/harness.rs (1)

1609-1621: Prefer the shared CLI relay helper over a second local wrapper.

The rest of the CLI already goes through relay_util::fetch_latest_key_package_for_mdk; keeping another wrapper here duplicates the timeout/context policy and makes future drift easier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/harness.rs` around lines 1609 - 1621, The local wrapper async fn
fetch_latest_key_package_for_mdk duplicates timeout/context behavior; replace
its usage with the shared helper relay_util::fetch_latest_key_package_for_mdk
instead of calling pika_marmot_runtime::relay::fetch_latest_key_package_for_mdk
here, remove this local wrapper if no other callers remain, and update
imports/usages to import relay_util::fetch_latest_key_package_for_mdk (or fully
qualify it) so the CLI consistently uses the shared relay helper.
cli/src/main.rs (1)

1330-1358: Extract the peer key-package fallback into one helper.

These two blocks now duplicate the same primary fetch, default-relay fallback, timeout, and error text. Pulling that into a shared helper will keep send and send-hypernote from drifting the next time the relay policy changes.

Also applies to: 1531-1558

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/main.rs` around lines 1330 - 1358, Extract the duplicated
fetch-and-fallback logic into a single helper (e.g.
get_peer_key_package_with_fallback) that encapsulates calling
relay_util::fetch_latest_key_package_for_mdk with the timeout, checking
cli.kp_relay.is_empty() and comparing kp_relays != relays to decide the fallback
to relays, and producing the same contextualized errors currently built with
primary_err; replace the duplicated blocks that set peer_kp (including the
occurrence around send and the other occurrence at the send-hypernote site) with
a call to this helper, passing c, peer_pubkey, kp_relays, relays, cli.kp_relay,
and Duration::from_secs(10). Ensure the helper returns the key package or an
appropriate anyhow::Error with the existing context strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/harness.rs`:
- Around line 339-342: The current call to fetch_latest_key_package_for_mdk may
return a normalized copy which makes the subsequent wire-ID assertion (comparing
fetched_b_kp.id to b_kp.id) flaky; replace the call to
fetch_latest_key_package_for_mdk with the raw fetch path (e.g., the
non-normalizing fetch function used elsewhere such as fetch_latest_key_package)
so fetched_b_kp is the original relay event, or alternatively apply the same
MDK-normalization to b_kp before doing the comparison; update the invocation
that creates fetched_b_kp and ensure the comparison uses matching forms.

In `@crates/pika-marmot-runtime/src/key_package.rs`:
- Around line 48-51: The code uses s.len().is_multiple_of(2) (inside the
content_is_hex block) which requires Rust 1.87+; update the crate to declare
rust-version = "1.87" in Cargo.toml or replace the call with a compatible check
such as s.len() % 2 == 0 to support the current edition; modify either the
Cargo.toml rust-version or the expression in the content_is_hex computation
accordingly so the crate compiles on Rust 1.85/1.86.

In `@crates/pikachat-sidecar/src/daemon.rs`:
- Around line 872-884: The code derives media keys from session.tracks.first()
via call_primary_track_name(session) which can pick a non-audio track; instead
determine the audio track using call_audio_track_spec(session) (or the helper
that returns the chosen audio track) and pass that result into
derive_shared_call_media_crypto_context. Replace the
call_primary_track_name(session) usage with the actual audio track obtained from
call_audio_track_spec(session) (handle its Result/Option the same way as
call_primary_track_name currently is handled) and propagate errors with
.map_err(anyhow::Error::msg) so tx_keys/rx_keys are derived from the real audio
track.

In `@rust/src/core/call_control.rs`:
- Around line 149-165: handle_incoming_call_signal currently hardcodes
"audio0"/"video0" when building CallCryptoDeriveContext and calling
derive_shared_call_media_crypto_context, which allows mismatched negotiated
track names to pass signaling but fail at media runtime; update the handler to
inspect session.tracks to (a) find the actual negotiated audio and video track
names (e.g., by searching session.tracks for the audio/video types or configured
role) and pass those names into derive_shared_call_media_crypto_context instead
of the literal "audio0"/"video0", or (b) if your protocol requires fixed names,
validate session.tracks up front and return an error/reject the call when the
expected names are not present; adjust CallCryptoDeriveContext/derive call sites
to use the discovered/validated track name variables rather than hardcoded
strings.

In `@todos/pika-core-final.md`:
- Line 345: Replace the typo "revertable" with the correct spelling "revertible"
in the sentence "keep PRs independently reviewable and revertable" so it reads
"keep PRs independently reviewable and revertible".

---

Outside diff comments:
In `@crates/pikachat-sidecar/src/daemon.rs`:
- Around line 751-762: The parse-failure warning for CallSignalEnvelopeCompat
logs the raw content which can leak sensitive fields like relay_auth; update the
error handling in the serde_json::from_str::<CallSignalEnvelopeCompat>(content)
Err branch (and the analogous block handling call signals later) to redact the
call payload before logging: detect when content contains "pika.call",
"call.invite", or "call.accept" and replace or mask the body/details (e.g.,
remove JSON fields that may contain credentials or replace with a fixed
"[REDACTED_CALL_PAYLOAD]") and then log the sanitized string in the warn! call
instead of the raw content so parse errors are still surfaced without leaking
bearer capabilities.

In `@rust/src/core/call_control.rs`:
- Around line 823-833: maybe_parse_call_signal currently calls the strict
parse_call_signal directly and thus drops the sidecar's compatibility handling
for double-encoded payloads and nested content/rumor.content envelopes; update
maybe_parse_call_signal to first perform the same compatibility unwrapping the
sidecar uses (try decoding double-encoded JSON, unwrap nested "content" or
"rumor.content" fields) and only then pass the resulting string to
parse_call_signal (or create a small helper like compat_parse_call_signal that
encapsulates the unwrap logic and calls parse_call_signal). Ensure you still
keep the early-return check against self.session.pubkey (the existing my_pubkey
check) and use the same error-tolerant behavior as the sidecar unwrap logic so
legacy call signals are not silently ignored.

---

Nitpick comments:
In `@cli/src/harness.rs`:
- Around line 1609-1621: The local wrapper async fn
fetch_latest_key_package_for_mdk duplicates timeout/context behavior; replace
its usage with the shared helper relay_util::fetch_latest_key_package_for_mdk
instead of calling pika_marmot_runtime::relay::fetch_latest_key_package_for_mdk
here, remove this local wrapper if no other callers remain, and update
imports/usages to import relay_util::fetch_latest_key_package_for_mdk (or fully
qualify it) so the CLI consistently uses the shared relay helper.

In `@cli/src/main.rs`:
- Around line 1330-1358: Extract the duplicated fetch-and-fallback logic into a
single helper (e.g. get_peer_key_package_with_fallback) that encapsulates
calling relay_util::fetch_latest_key_package_for_mdk with the timeout, checking
cli.kp_relay.is_empty() and comparing kp_relays != relays to decide the fallback
to relays, and producing the same contextualized errors currently built with
primary_err; replace the duplicated blocks that set peer_kp (including the
occurrence around send and the other occurrence at the send-hypernote site) with
a call to this helper, passing c, peer_pubkey, kp_relays, relays, cli.kp_relay,
and Duration::from_secs(10). Ensure the helper returns the key package or an
appropriate anyhow::Error with the existing context strings.

In `@crates/pika-marmot-runtime/src/call.rs`:
- Around line 119-125: In function now_millis(), the conversion `.as_millis() as
i64` can theoretically overflow for timestamps far beyond practical ranges; add
a short inline comment or doc comment above now_millis() stating that potential
overflow is intentionally ignored (dates ~292 million years out) because the
value is only used for call signaling and the cast is safe for all expected use
cases. Reference the now_millis() function and its use of SystemTime::now(),
UNIX_EPOCH, and as_millis() when adding the explanatory note.

In `@crates/pika-marmot-runtime/src/relay.rs`:
- Around line 66-68: The function normalize_fetched_key_package_for_mdk is a
trivial passthrough to normalize_peer_key_package_event_for_mdk; remove the
wrapper and update callers (notably fetch_latest_key_package_for_mdk) to call
normalize_peer_key_package_event_for_mdk directly, or if you want to keep a
relay-specific hook, keep the wrapper but add relay-specific logic—ensure no
remaining references to normalize_fetched_key_package_for_mdk remain after the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94a11666-676a-4a7a-83ce-b37c39d5dda0

📥 Commits

Reviewing files that changed from the base of the PR and between 93933e3 and 98d5dcd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • cli/src/agent/session.rs
  • cli/src/harness.rs
  • cli/src/main.rs
  • cli/src/relay_util.rs
  • crates/pika-marmot-runtime/Cargo.toml
  • crates/pika-marmot-runtime/src/call.rs
  • crates/pika-marmot-runtime/src/key_package.rs
  • crates/pika-marmot-runtime/src/lib.rs
  • crates/pika-marmot-runtime/src/relay.rs
  • crates/pikachat-sidecar/src/daemon.rs
  • rust/Cargo.toml
  • rust/src/core/call_control.rs
  • rust/src/core/call_runtime.rs
  • rust/src/core/interop.rs
  • todos/pika-core-final.md

Comment on lines +339 to +342
let fetched_b_kp =
fetch_latest_key_package_for_mdk(&a_client, &b_keys.public_key(), relay_url.clone())
.await
.context("fetch B keypackage")?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't use the MDK-normalizing fetcher for the wire-ID assertion.

fetch_latest_key_package_for_mdk can return a normalized copy of the relay event. The fetched_b_kp.id != b_kp.id check on Line 343 then becomes flaky and can fail for a valid legacy/interop key package. Keep the raw fetch path here, or normalize b_kp before comparing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/harness.rs` around lines 339 - 342, The current call to
fetch_latest_key_package_for_mdk may return a normalized copy which makes the
subsequent wire-ID assertion (comparing fetched_b_kp.id to b_kp.id) flaky;
replace the call to fetch_latest_key_package_for_mdk with the raw fetch path
(e.g., the non-normalizing fetch function used elsewhere such as
fetch_latest_key_package) so fetched_b_kp is the original relay event, or
alternatively apply the same MDK-normalization to b_kp before doing the
comparison; update the invocation that creates fetched_b_kp and ensure the
comparison uses matching forms.

Comment on lines +48 to +51
let content_is_hex = {
let s = out.content.trim();
!s.is_empty() && s.len().is_multiple_of(2) && s.bytes().all(|b| b.is_ascii_hexdigit())
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

When was is_multiple_of stabilized in Rust?

💡 Result:

is_multiple_of (for the primitive unsigned integer types, behind #![feature(unsigned_is_multiple_of)]) was stabilized in Rust 1.87.0, released May 15, 2025. [1] [2]


🏁 Script executed:

# Find and examine Cargo.toml for the crate
find . -name "Cargo.toml" | grep -E "(pika-marmot|root)" | head -5

Repository: sledtools/pika

Length of output: 97


🏁 Script executed:

# Check for rust-toolchain or similar files
find . -name "rust-toolchain*" -o -name ".rust-version" | head -10

Repository: sledtools/pika

Length of output: 40


🏁 Script executed:

# Look at the Cargo.toml for pika-marmot-runtime
cat crates/pika-marmot-runtime/Cargo.toml

Repository: sledtools/pika

Length of output: 643


🏁 Script executed:

# Check root Cargo.toml for workspace rust-version
head -50 Cargo.toml

Repository: sledtools/pika

Length of output: 1742


🏁 Script executed:

# View the actual file to verify the code and context
cat -n crates/pika-marmot-runtime/src/key_package.rs | sed -n '40,60p'

Repository: sledtools/pika

Length of output: 1092


🌐 Web query:

Rust 2024 edition minimum Rust version requirement

💡 Result:

Rust 2024 Edition requires Rust 1.85.0 or newer (stable), since the edition was stabilized in Rust 1.85.0 (2025-02-20). [1][2]

If you set it in Cargo.toml, use:

  • edition = "2024"
  • rust-version = "1.85" (minimum toolchain for the edition) [2]

Sources:
[1] Rust Release Notes (Rust 1.85.0: “The 2024 Edition is now stable.”)
[2] Rust Blog: “Rust 2024 in beta channel” (recommends rust-version = "1.85")


Set explicit rust-version or use compatible alternative.

The is_multiple_of method requires Rust 1.87.0+ (stabilized May 15, 2025), but the crate's edition = "2024" only guarantees Rust 1.85.0+. This will cause compilation failures on Rust 1.85.x and 1.86.x. Either add rust-version = "1.87" to Cargo.toml or use s.len() % 2 == 0 for compatibility with the stated edition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pika-marmot-runtime/src/key_package.rs` around lines 48 - 51, The code
uses s.len().is_multiple_of(2) (inside the content_is_hex block) which requires
Rust 1.87+; update the crate to declare rust-version = "1.87" in Cargo.toml or
replace the call with a compatible check such as s.len() % 2 == 0 to support the
current edition; modify either the Cargo.toml rust-version or the expression in
the content_is_hex computation accordingly so the crate compiles on Rust
1.85/1.86.

Comment on lines +872 to +884
let primary_track = call_primary_track_name(session)?;
let derive_ctx = CallCryptoDeriveContext {
mdk,
&mls_group_id,
DEFAULT_SCHEME_VERSION,
&root_hash,
"application/pika-call",
&root_filename,
)
.map_err(|e| anyhow!("derive media group root failed: {e}"))?;

Ok(CallMediaCryptoContext {
tx_keys: FrameKeyMaterial::from_base_key(
tx_base,
key_id_for_sender(local_pubkey_hex.as_bytes()),
group.epoch,
generation,
track,
group_root,
),
rx_keys: FrameKeyMaterial::from_base_key(
rx_base,
key_id_for_sender(peer_pubkey_hex.as_bytes()),
group.epoch,
generation,
track,
group_root,
),
local_participant_label: opaque_participant_label(&group_root, local_pubkey_hex.as_bytes()),
peer_participant_label: opaque_participant_label(&group_root, peer_pubkey_hex.as_bytes()),
})
mls_group_id: &mls_group_id,
group_epoch: group.epoch,
call_id,
session,
local_pubkey_hex,
peer_pubkey_hex,
};

derive_shared_call_media_crypto_context(&derive_ctx, primary_track, None)
.map_err(anyhow::Error::msg)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Derive keys from the actual audio track, not the first track.

Audio mode is chosen via call_audio_track_spec(session), but this path derives tx_keys/rx_keys from session.tracks.first(). A session with a non-audio track first will accept and then fail every audio encrypt/decrypt.

Suggested fix
-    let primary_track = call_primary_track_name(session)?;
+    let primary_track = if let Some(track) = call_audio_track_spec(session) {
+        track.name.as_str()
+    } else {
+        call_primary_track_name(session)?
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pikachat-sidecar/src/daemon.rs` around lines 872 - 884, The code
derives media keys from session.tracks.first() via
call_primary_track_name(session) which can pick a non-audio track; instead
determine the audio track using call_audio_track_spec(session) (or the helper
that returns the chosen audio track) and pass that result into
derive_shared_call_media_crypto_context. Replace the
call_primary_track_name(session) usage with the actual audio track obtained from
call_audio_track_spec(session) (handle its Result/Option the same way as
call_primary_track_name currently is handled) and propagate errors with
.map_err(anyhow::Error::msg) so tx_keys/rx_keys are derived from the real audio
track.

Comment on lines +149 to 165
let video_track = session
.tracks
.iter()
.any(|t| t.name == "video0")
.then_some("video0");
let derive_ctx = CallCryptoDeriveContext {
mdk: &sess.mdk,
mls_group_id: &group_entry.mls_group_id,
group_epoch: group.epoch,
call_id,
session,
local_pubkey_hex,
peer_pubkey_hex,
"audio0",
)?;

let has_video = session.tracks.iter().any(|t| t.name == "video0");
let (video_tx_keys, video_rx_keys) = if has_video {
let (vtx, vrx, _) = self.derive_track_keys(
chat_id,
call_id,
session,
local_pubkey_hex,
peer_pubkey_hex,
"video0",
)?;
(Some(vtx), Some(vrx))
} else {
(None, None)
};

Ok(super::call_runtime::CallMediaCryptoContext {
tx_keys,
rx_keys,
video_tx_keys,
video_rx_keys,
local_participant_label: opaque_participant_label(
&group_root,
local_pubkey_hex.as_bytes(),
),
peer_participant_label: opaque_participant_label(
&group_root,
peer_pubkey_hex.as_bytes(),
),
})
derive_shared_call_media_crypto_context(&derive_ctx, "audio0", video_track)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Either honor negotiated track names or reject them up front.

The shared helper now takes explicit track names, but this still hardcodes "audio0" / "video0". handle_incoming_call_signal accepts arbitrary session.tracks, and the media runtime still publishes/subscribes fixed names, so a peer using different names can clear signaling/auth and only fail once media starts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/core/call_control.rs` around lines 149 - 165,
handle_incoming_call_signal currently hardcodes "audio0"/"video0" when building
CallCryptoDeriveContext and calling derive_shared_call_media_crypto_context,
which allows mismatched negotiated track names to pass signaling but fail at
media runtime; update the handler to inspect session.tracks to (a) find the
actual negotiated audio and video track names (e.g., by searching session.tracks
for the audio/video types or configured role) and pass those names into
derive_shared_call_media_crypto_context instead of the literal
"audio0"/"video0", or (b) if your protocol requires fixed names, validate
session.tracks up front and return an error/reject the call when the expected
names are not present; adjust CallCryptoDeriveContext/derive call sites to use
the discovered/validated track name variables rather than hardcoded strings.

- one subsystem per PR
- app consumes the extracted code first when practical
- daemon follows onto it after the pattern is proven
- keep PRs independently reviewable and revertable
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor typo: "revertable" → "revertible".

The standard spelling is "revertible" (or alternatively "reversible").

📝 Proposed fix
-- keep PRs independently reviewable and revertable
+- keep PRs independently reviewable and revertible
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- keep PRs independently reviewable and revertable
- keep PRs independently reviewable and revertible
🧰 Tools
🪛 LanguageTool

[grammar] ~345-~345: Ensure spelling is correct
Context: ...- keep PRs independently reviewable and revertable ## Success Criteria We are succeeding if: ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@todos/pika-core-final.md` at line 345, Replace the typo "revertable" with the
correct spelling "revertible" in the sentence "keep PRs independently reviewable
and revertable" so it reads "keep PRs independently reviewable and revertible".

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