Skip to content
Merged
2 changes: 1 addition & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
disallowed-methods = [
{ path = "chrono::Utc::now", reason = "use wacore::time::now_utc() to respect the pluggable TimeProvider (WASM + deterministic tests)" },
{ path = "chrono::Local::now", reason = "use wacore::time::now_utc() (Local depends on SystemTime which panics on WASM)" },
{ path = "chrono::Local::now", reason = "use wacore::time::now_utc() to respect the pluggable TimeProvider and avoid direct system-clock access", allow-invalid = true },
{ path = "std::time::SystemTime::now", reason = "use wacore::time::now_millis() / now_utc()" },
{ path = "std::time::Instant::now", reason = "use wacore::time::Instant::now()" },
# buffa's Message codec methods are generic over the buffer type, so every
Expand Down
36 changes: 35 additions & 1 deletion src/client/adapters.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Signal/sender-key store adapters, per-session locks and noise socket access.

use super::*;
use anyhow::Context as _;

impl Client {
/// Build a [`SignalProtocolStoreAdapter`] from the current device state and signal cache.
Expand Down Expand Up @@ -107,7 +108,7 @@ impl Client {
self.signal_cache
.flush(&*backend)
.await
.map_err(|e| anyhow::anyhow!("Failed to flush signal cache: {e}"))
.context("Failed to flush signal cache")
}

/// Signal-cache flush that is safe while the offline drain is active.
Expand Down Expand Up @@ -212,3 +213,36 @@ fn log_signal_flush_error(context: &str, id: Option<&str>, e: &anyhow::Error) {
log::error!("Failed to flush signal cache ({context}): {e:?}");
}
}

#[cfg(test)]
mod tests {
use super::*;
use wacore::store::in_memory::InMemoryBackend;
use wacore_binary::{Jid, Server};

#[tokio::test]
async fn signal_flush_context_preserves_the_backend_error_chain() {
let backend = Arc::new(InMemoryBackend::new());
let client = crate::test_utils::create_test_client_with_backend(backend.clone()).await;
let peer = Jid::new("12025550111", Server::Pn).with_device(1);
crate::test_utils::seed_peer_session(&client, &peer).await;
backend.set_fail_session_writes(true);

let error = client
.flush_signal_cache()
.await
.expect_err("injected backend failure must propagate");
let chain: Vec<String> = error.chain().map(ToString::to_string).collect();

assert_eq!(
chain.first().map(String::as_str),
Some("Failed to flush signal cache")
);
assert!(
chain
.iter()
.any(|cause| cause.contains("put_sessions_batch failing (test hook)")),
"typed backend cause missing from {chain:?}"
);
}
}
42 changes: 41 additions & 1 deletion src/handlers/ib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::traits::StanzaHandler;
use crate::client::Client;
use crate::types::events::{Event, OfflineSyncPreview};
use crate::types::events::{DirtyState, Event, OfflineSyncPreview};
use async_trait::async_trait;
use futures::FutureExt;
use log::{debug, info, warn};
Expand Down Expand Up @@ -68,6 +68,13 @@ async fn handle_ib_impl(client: Arc<Client>, node: &wacore_binary::NodeRef<'_>)
);
let needs_resync = bit.dirty_type == DirtyType::SyncdAppState;

client.core.event_bus.dispatch(Event::DirtyState(
DirtyState::builder()
.dirty_type(bit.dirty_type.clone())
.maybe_timestamp(bit.timestamp)
.build(),
));

Comment thread
coderabbitai[bot] marked this conversation as resolved.
debug!(
"Received dirty state notification for type: '{dirty_type_str}'. Sending clean IQ."
);
Expand Down Expand Up @@ -216,3 +223,36 @@ async fn handle_ib_impl(client: Arc<Client>, node: &wacore_binary::NodeRef<'_>)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{TestEventCollector, create_test_client};
use wacore_binary::builder::NodeBuilder;

#[tokio::test]
async fn valid_dirty_marker_dispatches_typed_event() {
let client = create_test_client().await;
let collector = Arc::new(TestEventCollector::default());
client.register_handler(collector.clone());
let node = NodeBuilder::new("ib")
.children([NodeBuilder::new("dirty")
.attr("type", "account_sync")
.attr("timestamp", "1725000000")
.build()])
.build();

handle_ib_impl(client, &node.as_node_ref()).await;

assert!(collector.events().iter().any(|event| {
matches!(
&**event,
Event::DirtyState(DirtyState {
dirty_type: DirtyType::AccountSync,
timestamp: Some(1_725_000_000),
..
})
)
}));
}
}
9 changes: 7 additions & 2 deletions wacore/libsignal/src/core/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ pub struct PublicKey {
}

impl PublicKey {
/// Length of a raw Curve25519 public key, without its type prefix.
pub const RAW_KEY_LEN: usize = curve25519::PUBLIC_KEY_LENGTH;
/// Length of the canonical serialized form, including its type prefix.
pub const SERIALIZED_KEY_LEN: usize = Self::RAW_KEY_LEN + 1;

fn new(key: PublicKeyData) -> Self {
Self { key }
}
Expand Down Expand Up @@ -107,8 +112,8 @@ impl PublicKey {
}

/// Serialize the public key to a fixed-size array (1 type byte + 32 key bytes).
pub fn serialize(&self) -> [u8; 33] {
let mut result = [0u8; 33];
pub fn serialize(&self) -> [u8; Self::SERIALIZED_KEY_LEN] {
let mut result = [0u8; Self::SERIALIZED_KEY_LEN];
result[0] = self.key_type().value();
match &self.key {
PublicKeyData::DjbPublicKey(v) => result[1..].copy_from_slice(v),
Expand Down
8 changes: 8 additions & 0 deletions wacore/libsignal/src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod local_field;
#[allow(clippy::module_inception)]
mod protocol;
mod ratchet;
mod record_components;
mod sender_keys;
pub mod session;
mod session_cipher;
Expand Down Expand Up @@ -56,6 +57,13 @@ pub use ratchet::{
RootKey, UsePQRatchet, derive_keys, initialize_alice_session_record, initialize_bob_session,
initialize_bob_session_record,
};
pub use record_components::{
PendingKeyExchangeComponents, PendingPreKeyComponents, SenderChainKeyComponents,
SenderKeyRecordComponents, SenderKeyStateComponents, SenderMessageKeyComponents,
SenderSigningKeyComponents, SessionChainComponents, SessionChainKeyComponents,
SessionComponents, SessionMessageKeyComponents, SessionMessageKeyMaterial,
SessionRecordComponents,
};
pub use sender_keys::{SenderKeyRecord, SenderKeyState};
pub use session::{process_prekey, process_prekey_bundle};
pub use session_cipher::{
Expand Down
2 changes: 1 addition & 1 deletion wacore/libsignal/src/protocol/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn decode_message_version(version_byte: u8) -> u8 {
}

// Signal's original implementation uses version 4, but WhatsApp Web,
// Baileys (libsignal-node), and whatsmeow all use version 3.
// Interoperable Signal implementations use version 3.
pub const CIPHERTEXT_MESSAGE_CURRENT_VERSION: u8 = 3;
pub const SENDERKEY_MESSAGE_CURRENT_VERSION: u8 = 3;

Expand Down
Loading
Loading