use wacore_binary::{Node, NodeContent};
pub const MAX_RETRY_COUNT: u8 = 5;
pub const MIN_RETRY_COUNT_FOR_KEYS: u8 = 2;
pub const MIN_RETRY_FOR_BASE_KEY_CHECK: u8 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
#[allow(dead_code)] pub enum RetryReason {
UnknownError = 0,
NoSession = 1,
InvalidKey = 2,
InvalidKeyId = 3,
InvalidMessage = 4,
InvalidSignature = 5,
FutureMessage = 6,
BadMac = 7,
InvalidSession = 8,
InvalidMsgKey = 9,
BadBroadcastEphemeralSetting = 10,
UnknownCompanionNoPrekey = 11,
AdvFailure = 12,
StatusRevokeDelay = 13,
}
pub fn get_bytes_content(node: &Node) -> Option<&[u8]> {
match &node.content {
Some(NodeContent::Bytes(b)) => Some(b.as_slice()),
_ => None,
}
}
pub fn extract_registration_id_from_node(node: &Node) -> Option<u32> {
let registration_node = node.get_optional_child("registration")?;
let bytes = get_bytes_content(registration_node)?;
if bytes.len() == 4 {
Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
} else if bytes.len() > 4 {
None
} else if !bytes.is_empty() {
let mut arr = [0u8; 4];
let start = 4 - bytes.len();
arr[start..].copy_from_slice(bytes);
Some(u32::from_be_bytes(arr))
} else {
None
}
}
pub fn should_include_keys(retry_count: u8, reason: RetryReason) -> bool {
let include_keys_early =
reason == RetryReason::NoSession || reason == RetryReason::UnknownCompanionNoPrekey;
retry_count >= MIN_RETRY_COUNT_FOR_KEYS || include_keys_early
}
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Cow;
use wacore_binary::Attrs;
#[test]
fn get_bytes_content_extracts_bytes() {
let node = Node {
tag: Cow::Borrowed("test"),
attrs: Attrs::new(),
content: Some(NodeContent::Bytes(vec![1, 2, 3, 4])),
};
assert_eq!(get_bytes_content(&node), Some(&[1, 2, 3, 4][..]));
}
#[test]
fn get_bytes_content_returns_none_for_string() {
let node = Node {
tag: Cow::Borrowed("test"),
attrs: Attrs::new(),
content: Some(NodeContent::String("hello".into())),
};
assert_eq!(get_bytes_content(&node), None);
}
#[test]
fn get_bytes_content_returns_none_for_empty() {
let node = Node {
tag: Cow::Borrowed("test"),
attrs: Attrs::new(),
content: None,
};
assert_eq!(get_bytes_content(&node), None);
}
#[test]
fn extract_registration_id_4_bytes() {
let reg_node = Node {
tag: Cow::Borrowed("registration"),
attrs: Attrs::new(),
content: Some(NodeContent::Bytes(vec![0x00, 0x01, 0x02, 0x03])),
};
let parent = Node {
tag: Cow::Borrowed("receipt"),
attrs: Attrs::new(),
content: Some(NodeContent::Nodes(vec![reg_node])),
};
assert_eq!(extract_registration_id_from_node(&parent), Some(0x00010203));
}
#[test]
fn extract_registration_id_3_bytes() {
let reg_node = Node {
tag: Cow::Borrowed("registration"),
attrs: Attrs::new(),
content: Some(NodeContent::Bytes(vec![0x01, 0x02, 0x03])),
};
let parent = Node {
tag: Cow::Borrowed("receipt"),
attrs: Attrs::new(),
content: Some(NodeContent::Nodes(vec![reg_node])),
};
assert_eq!(extract_registration_id_from_node(&parent), Some(0x00010203));
}
#[test]
fn extract_registration_id_missing() {
let parent = Node {
tag: Cow::Borrowed("receipt"),
attrs: Attrs::new(),
content: Some(NodeContent::Nodes(vec![])),
};
assert_eq!(extract_registration_id_from_node(&parent), None);
}
#[test]
fn extract_registration_id_empty_bytes() {
let reg_node = Node {
tag: Cow::Borrowed("registration"),
attrs: Attrs::new(),
content: Some(NodeContent::Bytes(vec![])),
};
let parent = Node {
tag: Cow::Borrowed("receipt"),
attrs: Attrs::new(),
content: Some(NodeContent::Nodes(vec![reg_node])),
};
assert_eq!(extract_registration_id_from_node(&parent), None);
}
#[test]
fn should_include_keys_no_session_retry_1() {
assert!(
should_include_keys(1, RetryReason::NoSession),
"NoSession at retry#1 should include keys (optimization)"
);
}
#[test]
fn should_include_keys_unknown_companion_retry_1() {
assert!(
should_include_keys(1, RetryReason::UnknownCompanionNoPrekey),
"UnknownCompanionNoPrekey at retry#1 should include keys"
);
}
#[test]
fn should_include_keys_invalid_message_retry_1() {
assert!(
!should_include_keys(1, RetryReason::InvalidMessage),
"InvalidMessage at retry#1 should NOT include keys"
);
}
#[test]
fn should_include_keys_retry_2_any_reason() {
assert!(should_include_keys(2, RetryReason::InvalidMessage));
assert!(should_include_keys(2, RetryReason::UnknownError));
assert!(should_include_keys(2, RetryReason::BadMac));
assert!(should_include_keys(2, RetryReason::NoSession));
}
#[test]
fn should_include_keys_retry_3_any_reason() {
assert!(should_include_keys(3, RetryReason::InvalidMessage));
assert!(should_include_keys(3, RetryReason::UnknownError));
}
#[test]
fn constants_match_wa_web() {
assert_eq!(MAX_RETRY_COUNT, 5);
assert_eq!(MIN_RETRY_COUNT_FOR_KEYS, 2);
assert_eq!(MIN_RETRY_FOR_BASE_KEY_CHECK, 2);
}
}