Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
737adf0
feat(voip): add group calls and call links
jlucaso1 Jul 26, 2026
a509322
fix(voip): address follow-up group call review
jlucaso1 Jul 26, 2026
dc17263
fix(voip): harden group call review paths
jlucaso1 Jul 27, 2026
478382d
fix(voip): authenticate group snapshots
jlucaso1 Jul 27, 2026
6ac1e26
fix(voip): harden group media lifecycle
jlucaso1 Jul 27, 2026
875dac7
fix(ci): gate voip test import
jlucaso1 Jul 27, 2026
f6636d0
fix(voip): harden group generation commits
jlucaso1 Jul 27, 2026
7af9a9b
refactor(voip): unify queued media purging
jlucaso1 Jul 27, 2026
3aba2d9
fix(voip): harden group call state invariants
jlucaso1 Jul 27, 2026
1bbccc3
fix(voip): close remaining group call races
jlucaso1 Jul 27, 2026
0693481
perf(voip): reduce group signaling code size
jlucaso1 Jul 27, 2026
8006df1
fix(voip): preserve call-link admission state
jlucaso1 Jul 27, 2026
55fd6c6
fix(voip): harden group media transitions
jlucaso1 Jul 27, 2026
d804c7d
fix(voip): harden group call ordering
jlucaso1 Jul 27, 2026
8c8ff24
fix(voip): close group call review gaps
jlucaso1 Jul 27, 2026
1776f2e
fix(voip): preserve ordered group transitions
jlucaso1 Jul 27, 2026
2a9ffb0
fix(voip): harden group signaling edges
jlucaso1 Jul 27, 2026
463ef96
fix(voip): serialize group control transitions
jlucaso1 Jul 27, 2026
4da90e1
fix(voip): close remaining group call races
jlucaso1 Jul 27, 2026
5f27308
fix(ci): gate voip group offer test helper
jlucaso1 Jul 27, 2026
4e0a5c3
fix(voip): close group signaling races
jlucaso1 Jul 27, 2026
2bc946f
fix(voip): harden group signaling authority
jlucaso1 Jul 27, 2026
e6b7d1d
fix(voip): harden group call state validation
jlucaso1 Jul 27, 2026
47cd2bb
fix(voip): harden asynchronous group transitions
jlucaso1 Jul 28, 2026
5991b3c
fix(voip): harden pending group call state
jlucaso1 Jul 28, 2026
8e6ffa5
fix(voip): bound concurrent group transitions
jlucaso1 Jul 28, 2026
1beb442
fix(voip): preserve authoritative group transitions
jlucaso1 Jul 28, 2026
bb0e76f
fix(voip): validate group lifecycle state
jlucaso1 Jul 28, 2026
37415b5
fix(voip): guard saturated joins and video upgrades
jlucaso1 Jul 28, 2026
25cb581
fix(voip): isolate group admission state
jlucaso1 Jul 28, 2026
073eebd
fix(voip): close terminal group transition races
jlucaso1 Jul 28, 2026
9915dde
fix(voip): scope call-link buffer saturation
jlucaso1 Jul 28, 2026
08ddcbe
fix(voip): scope accepted group state
jlucaso1 Jul 28, 2026
339fe56
fix(voip): preserve staged group control integrity
jlucaso1 Jul 28, 2026
4586085
fix(voip): harden group call admission state
jlucaso1 Jul 28, 2026
44169dc
fix(voip): bind call-link ACKs before dispatch
jlucaso1 Jul 28, 2026
78f4a38
fix(voip): isolate pre-offer control retention
jlucaso1 Jul 28, 2026
c963ae8
fix(voip): harden pre-offer and invite validation
jlucaso1 Jul 28, 2026
c891aa4
fix(voip): address call-link review races
jlucaso1 Jul 28, 2026
126dfdf
fix(voip): harden group call identity and media state
jlucaso1 Jul 28, 2026
18b171d
fix(voip): close epoch and admission races
jlucaso1 Jul 28, 2026
c2d9fd7
fix(voip): recover ambiguous call-link admission
jlucaso1 Jul 28, 2026
7220ca5
fix(protocol): use generated group action tags
jlucaso1 Jul 28, 2026
ce3a36e
fix(voip): gate stale group media controls
jlucaso1 Jul 28, 2026
9863f74
fix(voip): prevalidate relay tag refreshes
jlucaso1 Jul 28, 2026
e14fb43
fix(voip): close group admission races
jlucaso1 Jul 28, 2026
44df6cd
fix(voip): preserve group invite delivery state
jlucaso1 Jul 28, 2026
75faed6
fix(voip): bound group media transitions
jlucaso1 Jul 28, 2026
689d099
fix(voip): defer relay allocation timeout
jlucaso1 Jul 28, 2026
836d157
fix(voip): preserve oversized startup epochs
jlucaso1 Jul 28, 2026
84b7284
fix(voip): close group control identity races
jlucaso1 Jul 28, 2026
214a708
fix(voip): bind group media session state
jlucaso1 Jul 28, 2026
2c870fe
fix(voip): harden group call state transitions
jlucaso1 Jul 28, 2026
fac5738
fix(voip): close remaining group call review gaps
jlucaso1 Jul 28, 2026
0d64844
fix(voip): retain group epochs across attach retries
jlucaso1 Jul 28, 2026
bb066e2
fix(voip): exclude local aliases from epoch fanout
jlucaso1 Jul 28, 2026
8209b20
fix(voip): close routed group-call cleanup gaps
jlucaso1 Jul 29, 2026
f733e53
fix(voip): align group media device identity
jlucaso1 Jul 29, 2026
f770703
fix(voip): guard live group promotion
jlucaso1 Jul 29, 2026
e274d4b
fix(voip): close call-link review gaps
jlucaso1 Jul 29, 2026
27a4077
fix(voip): reuse raced call-link epochs
jlucaso1 Jul 29, 2026
5dea6bd
fix(voip): match group invite aliases
jlucaso1 Jul 29, 2026
1968ade
fix(voip): bind group preaccept generation
jlucaso1 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ thiserror = "2.0.19"
tokio = { version = "1.53.1", default-features = false }
tracing = { version = "0.1", default-features = false, features = ["attributes"] }
uuid = { version = "1", default-features = false }
zeroize = { version = "1.8", default-features = false, features = ["alloc"] }

# Internal workspace crates
wacore = { path = "./wacore", default-features = false, version = "0.6.0" }
Expand Down Expand Up @@ -230,6 +231,7 @@ tracing = { workspace = true, optional = true }
wacore = { workspace = true }
wacore-binary = { workspace = true }
waproto = { workspace = true }
zeroize = { workspace = true }
webrtc-data = { version = "0.17", optional = true }
webrtc-dtls = { version = "0.12", optional = true }
webrtc-sctp = { version = "0.17", optional = true }
Expand Down
27 changes: 27 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ pub struct MemoryReport {
pub signal_sessions: CollectionStats,
pub signal_identities: CollectionStats,
pub signal_sender_keys: CollectionStats,
/// Admission snapshots retained while a call-link join ACK is in flight.
#[cfg(feature = "voip-runtime")]
pub pending_call_link_updates: CollectionStats,
/// Active/ringing calls and bounded pre-offer group controls, including their snapshots/queues.
#[cfg(feature = "voip-runtime")]
pub active_calls: CollectionStats,
#[cfg(feature = "plugins")]
pub plugins: u64,
#[cfg(feature = "plugins")]
Expand Down Expand Up @@ -348,6 +354,10 @@ impl MemoryReport {
/// Sum of every estimated byte figure in the report.
pub fn total_estimated_bytes(&self) -> u64 {
let total: u64 = self.collections().iter().map(|(_, c)| c.bytes).sum();
#[cfg(feature = "voip-runtime")]
let total = total
.saturating_add(self.pending_call_link_updates.bytes)
.saturating_add(self.active_calls.bytes);
#[cfg(feature = "plugins")]
let total = total.saturating_add(self.plugin_event_queue.bytes);
total
Expand Down Expand Up @@ -423,6 +433,12 @@ impl std::fmt::Display for MemoryReport {
for (name, c) in &collections[TTL_BOUNDED..TTL_BOUNDED + SIGNAL_CACHES] {
line(f, name, c)?;
}
#[cfg(feature = "voip-runtime")]
{
writeln!(f, "--- VoIP state ---")?;
line(f, "pending_link_updates:", &self.pending_call_link_updates)?;
line(f, "active_calls:", &self.active_calls)?;
}
writeln!(f, "--- In-flight history sync ---")?;
line(
f,
Expand Down Expand Up @@ -1334,6 +1350,17 @@ pub struct Client {
#[cfg(feature = "voip-runtime")]
pub(crate) call_registry: Arc<wacore::voip::CallRegistry>,

/// Admission snapshots that can race a call-link join ACK before its call id is registered.
/// Kept beside the client-side join lifecycle so `wacore` does not authorize unknown calls.
#[cfg(feature = "voip-runtime")]
pending_call_link_joins: Arc<std::sync::Mutex<voip::PendingCallLinkJoins>>,

/// Serializes call-link joins until the ACK reveals which call id owns any admission state
/// buffered during the request. This keeps a bounded overflow tied to one join instead of
/// letting it reject an unrelated concurrent join.
#[cfg(feature = "voip-runtime")]
pending_call_link_join_lane: Arc<Mutex<()>>,

/// Serializes incoming-answer registration with generation-aware teardown. A failed answer holds
/// its call-id lane until `<terminate>` has been written, so a same-call-id re-offer cannot become
/// current in the removal-before-send window. Stripes bound storage while allowing independent
Expand Down
12 changes: 12 additions & 0 deletions src/client/accessors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ impl Client {
history_sync_activity.tasks as u64,
history_sync_activity.payload_bytes as u64,
);
#[cfg(feature = "voip-runtime")]
let pending_call_link_updates = self
.pending_call_link_joins
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.memory_stats();
Comment thread
jlucaso1 marked this conversation as resolved.
#[cfg(feature = "voip-runtime")]
let active_calls = self.call_registry.memory_stats();
#[cfg(feature = "plugins")]
let plugin_stats = self.plugin_stats();
#[cfg(feature = "plugins")]
Expand Down Expand Up @@ -268,6 +276,10 @@ impl Client {
signal_sessions,
signal_identities,
signal_sender_keys,
#[cfg(feature = "voip-runtime")]
pending_call_link_updates,
#[cfg(feature = "voip-runtime")]
active_calls,
#[cfg(feature = "plugins")]
plugins,
#[cfg(feature = "plugins")]
Expand Down
6 changes: 6 additions & 0 deletions src/client/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@ impl Client {
#[cfg(feature = "voip-runtime")]
call_registry: Arc::new(wacore::voip::CallRegistry::new()),
#[cfg(feature = "voip-runtime")]
pending_call_link_joins: Arc::new(std::sync::Mutex::new(
voip::PendingCallLinkJoins::default(),
)),
#[cfg(feature = "voip-runtime")]
pending_call_link_join_lane: Arc::new(Mutex::new(())),
#[cfg(feature = "voip-runtime")]
answer_transition_locks: std::array::from_fn(|_| Arc::new(Mutex::new(()))),
#[cfg(feature = "voip-runtime")]
pending_outgoing_calls: Arc::new(std::sync::Mutex::new(HashMap::new())),
Expand Down
6 changes: 6 additions & 0 deletions src/client/node_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@ impl Client {
// message ids), so a mismatch here means the id space collided.
match waiter {
ResponseWaiter::Iq(sender) => {
#[cfg(feature = "voip-runtime")]
self.bind_pending_call_link_join_ack(nr);
if sender.send(Arc::clone(&node)).is_err() {
warn!(target: "Client/IQ", "Failed to send IQ response to waiter. Receiver was likely dropped.");
}
Expand Down Expand Up @@ -1434,6 +1436,8 @@ impl Client {
};
match waiter {
ResponseWaiter::Iq(sender) => {
#[cfg(feature = "voip-runtime")]
self.bind_pending_call_link_join_ack(node.get());
if let Err(rejected) = sender.send(Arc::clone(node)) {
Self::warn_ack_waiter_dropped(&rejected);
}
Expand All @@ -1455,6 +1459,8 @@ impl Client {
};
match waiter {
ResponseWaiter::Iq(sender) => {
#[cfg(feature = "voip-runtime")]
self.bind_pending_call_link_join_ack(node.get());
if let Err(rejected) = sender.send(Arc::new(node)) {
Self::warn_ack_waiter_dropped(&rejected);
}
Expand Down
11 changes: 6 additions & 5 deletions src/client/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4296,13 +4296,14 @@ async fn resource_report_composes_client_and_out_of_client_components() {
mem.total_estimated_bytes()
);

// The SQLite backend reports its page-cache estimate — proving the storage
// report (workstream A) composes through `dyn Backend`.
assert!(
// The SQLite backend is intentionally best-effort and uses a non-blocking pool checkout.
// A concurrently held single connection may therefore report no storage sample; when a sample
// is available, its two SQLite-derived fields must remain coherent. The backend's dedicated
// resource-report test deterministically verifies the concrete page-cache calculation.
assert_eq!(
report.storage.memory_bytes.is_some(),
"SQLite backend reports storage memory"
report.storage.pages.is_some()
);
assert!(report.storage.pages.is_some());

// No transport is connected and the mock HTTP client reports nothing.
assert!(report.transport.is_none());
Expand Down
Loading
Loading