use crate::client::Client;
use crate::types::events::Event;
use crate::types::message::MessageInfo;
use log::{debug, warn};
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use wacore::libsignal::crypto::DecryptionError;
use wacore::libsignal::protocol::SenderKeyStore;
use wacore::libsignal::protocol::group_decrypt;
use wacore::libsignal::protocol::{
CiphertextMessage, DecryptionResult, IdentityChange, OwnedCiphertextMessage,
PreKeySignalMessage, SignalMessage, SignalProtocolError, UsePQRatchet, message_decrypt,
message_decrypt_owned,
};
use wacore::message_processing::EncType;
use wacore::protocol::nack::NackReason;
use wacore::types::jid::{JidExt, make_sender_key_name};
use wacore_binary::Jid;
use wacore_binary::JidExt as _;
use wacore_binary::node::ValueRef;
use wacore_binary::{NodeRef, OwnedNodeRef};
use waproto::whatsapp::{self as wa};
use wacore::protocol::retry::MAX_RETRY_COUNT as MAX_DECRYPT_RETRIES;
#[inline]
fn sender_retry_count(enc_node: &NodeRef<'_>) -> u8 {
enc_node
.get_attr("count")
.and_then(|value| match value {
ValueRef::String(value) => value.parse::<u64>().ok(),
ValueRef::Jid(_) => None,
})
.map(|count| count.min(MAX_DECRYPT_RETRIES as u64) as u8)
.unwrap_or(0)
}
#[inline]
fn attr_matches_jid(value: &ValueRef<'_>, jid: &Jid) -> bool {
match value {
ValueRef::String(value) => wacore_binary::jid::parse_jid_ref(value)
.map(|parsed| jid == &parsed)
.unwrap_or_else(|| value.parse::<Jid>().is_ok_and(|parsed| jid == &parsed)),
ValueRef::Jid(value) => jid == value,
}
}
fn message_enc_nodes_for_device<'node, 'data: 'node>(
node: &'node NodeRef<'data>,
own_jid: Option<&'node Jid>,
) -> impl Iterator<Item = &'node NodeRef<'data>> + 'node {
let per_device = node
.get_optional_child("participants")
.into_iter()
.flat_map(|participants| participants.get_children_by_tag("to"))
.filter(move |to_node| {
own_jid.is_some_and(|ours| {
to_node
.get_attr("jid")
.is_some_and(|value| attr_matches_jid(value, ours))
})
})
.flat_map(|to_node| to_node.get_children_by_tag("enc"));
node.get_children_by_tag("enc").chain(per_device)
}
pub(crate) struct EncPayload {
pub ciphertext: bytes::Bytes,
pub enc_type: EncType,
pub padding_version: u8,
}
impl EncPayload {
fn from_parts(ciphertext: bytes::Bytes, enc_node: &NodeRef<'_>) -> Option<Self> {
let enc_type = EncType::from_wire(enc_node.attrs().optional_string("type")?.as_ref())?;
let padding_version = enc_node.attrs().optional_u64("v").unwrap_or(2) as u8;
Some(Self {
ciphertext,
enc_type,
padding_version,
})
}
pub(crate) fn from_owned_node(owner: &OwnedNodeRef, enc_node: &NodeRef<'_>) -> Option<Self> {
Self::from_parts(owner.slice_bytes(enc_node.content_bytes()?), enc_node)
}
#[cfg(test)]
pub(crate) fn from_node_ref(node: &NodeRef<'_>) -> Option<Self> {
Self::from_parts(bytes::Bytes::copy_from_slice(node.content_bytes()?), node)
}
}
pub(crate) struct ClassifiedMessage {
pub info: Arc<MessageInfo>,
pub sender_encryption_jid: Jid,
pub session_payloads: Vec<EncPayload>,
pub group_payloads: Vec<EncPayload>,
pub bot_payloads: Vec<EncPayload>,
pub max_sender_retry_count: u8,
pub decrypt_fail_mode: crate::types::events::DecryptFailMode,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct SessionBatchOutcome {
decrypted: bool,
duplicate: bool,
undecryptable: bool,
dispatched: bool,
skdm_only: bool,
plaintext_failed: bool,
had_failure: bool,
}
#[derive(Clone, Copy, Debug)]
enum MigrationDecryptResult {
Decrypted,
Duplicate,
NotDecrypted,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct PlaintextHandleOutcome {
dispatched: bool,
skdm_only: bool,
}
const INBOUND_COMMIT_PENDING: u8 = 0;
const INBOUND_COMMIT_DURABLE: u8 = 1;
const INBOUND_COMMIT_DROPPED: u8 = 2;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum InboundCommitTicketState {
Pending,
Durable,
Dropped,
}
#[derive(Clone)]
pub(crate) struct InboundCommitTicket(Arc<AtomicU8>);
impl InboundCommitTicket {
fn new() -> Self {
Self(Arc::new(AtomicU8::new(INBOUND_COMMIT_PENDING)))
}
fn state(&self) -> InboundCommitTicketState {
match self.0.load(Ordering::Acquire) {
INBOUND_COMMIT_DURABLE => InboundCommitTicketState::Durable,
INBOUND_COMMIT_DROPPED => InboundCommitTicketState::Dropped,
_ => InboundCommitTicketState::Pending,
}
}
fn resolve(&self, state: u8) {
let _ = self.0.compare_exchange(
INBOUND_COMMIT_PENDING,
state,
Ordering::AcqRel,
Ordering::Acquire,
);
}
fn mark_durable(&self) {
self.resolve(INBOUND_COMMIT_DURABLE);
}
fn mark_dropped(&self) {
self.resolve(INBOUND_COMMIT_DROPPED);
}
}
pub(crate) enum InboundCommitState {
Durable,
Deferred(Option<InboundCommitTicket>),
Failed,
}
struct DeferredPlaintext {
enc_type: &'static str,
plaintext: Vec<u8>,
padding_version: u8,
}
fn should_process_skmsg_after_session(
session_payloads_empty: bool,
session_outcome: SessionBatchOutcome,
) -> bool {
session_payloads_empty
|| (!session_outcome.had_failure
&& (session_outcome.decrypted || session_outcome.duplicate))
}
fn should_ack_skdm_only_session_fallback(
session_outcome: SessionBatchOutcome,
bot_payloads_empty: bool,
) -> bool {
session_outcome.decrypted
&& session_outcome.skdm_only
&& !session_outcome.dispatched
&& !session_outcome.had_failure
&& !session_outcome.plaintext_failed
&& !session_outcome.undecryptable
&& bot_payloads_empty
}
const HIGH_RETRY_COUNT_THRESHOLD: u8 = 3;
fn decrypt_fail_log_level(mode: crate::types::events::DecryptFailMode) -> log::Level {
match mode {
crate::types::events::DecryptFailMode::Hide => log::Level::Debug,
crate::types::events::DecryptFailMode::Show => log::Level::Warn,
}
}
fn group_decrypt_retry_reason(e: &SignalProtocolError) -> Option<RetryReason> {
match e {
SignalProtocolError::SignatureValidationFailed => Some(RetryReason::InvalidSignature),
SignalProtocolError::InvalidSenderKeySession => Some(RetryReason::InvalidSession),
SignalProtocolError::UnrecognizedMessageVersion(_) => Some(RetryReason::InvalidMessage),
SignalProtocolError::InvalidMessage(_, _) => Some(RetryReason::InvalidMessage),
_ => None,
}
}
pub(crate) use wacore::protocol::retry::RetryReason;
pub(crate) mod commit_batch;
mod dispatch;
mod durability;
mod msg_secret;
mod receive;
mod retry;
mod special;
#[cfg(test)]
fn unwrap_device_sent(msg: wa::Message) -> wa::Message {
wacore::messages::unwrap_device_sent(msg)
}
#[cfg(test)]
fn is_sender_key_distribution_only(msg: &mut wa::Message) -> bool {
wacore::messages::is_sender_key_distribution_only(msg)
}
#[cfg(test)]
mod tests;