use crate::WireEnum;
use rand::Rng;
use sha2::{Digest, Sha256};
use std::time::Duration;
use thiserror::Error;
use wacore_binary::builder::NodeBuilder;
use wacore_binary::{Jid, JidExt, LEGACY_USER_SERVER};
use wacore_binary::{Node, NodeContent, NodeRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq, WireEnum)]
pub enum InfoQueryType {
#[wire = "set"]
Set,
#[wire = "get"]
Get,
}
#[derive(Debug, Clone)]
pub struct InfoQuery<'a> {
pub namespace: &'a str,
pub query_type: InfoQueryType,
pub to: Jid,
pub target: Option<Jid>,
pub id: Option<String>,
pub content: Option<NodeContent>,
pub timeout: Option<Duration>,
}
impl<'a> InfoQuery<'a> {
pub fn get(namespace: &'a str, to: Jid, content: Option<NodeContent>) -> Self {
Self {
namespace,
query_type: InfoQueryType::Get,
to,
target: None,
id: None,
content,
timeout: None,
}
}
pub fn set(namespace: &'a str, to: Jid, content: Option<NodeContent>) -> Self {
Self {
namespace,
query_type: InfoQueryType::Set,
to,
target: None,
id: None,
content,
timeout: None,
}
}
pub fn with_target(mut self, target: Jid) -> Self {
self.target = Some(target);
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn get_ref(namespace: &'a str, to: &Jid, content: Option<NodeContent>) -> Self {
Self::get(namespace, to.clone(), content)
}
pub fn set_ref(namespace: &'a str, to: &Jid, content: Option<NodeContent>) -> Self {
Self::set(namespace, to.clone(), content)
}
pub fn with_target_ref(self, target: &Jid) -> Self {
self.with_target(target.clone())
}
}
#[derive(Debug, Error)]
pub enum IqError {
#[error("IQ request timed out")]
Timeout,
#[error("client is not connected")]
NotConnected,
#[error("received disconnect node during IQ wait: {0:?}")]
Disconnected(Node),
#[error("received a server error response: code={code}, text='{text}'")]
ServerError { code: u16, text: String },
#[error("internal channel closed unexpectedly")]
InternalChannelClosed,
}
#[derive(Debug, Clone, Error)]
#[error("server error: code={code}, text='{text}'")]
pub struct ServerErrorCode {
pub code: u16,
pub text: String,
}
impl ServerErrorCode {
pub fn from_anyhow(err: &anyhow::Error) -> Option<&Self> {
err.downcast_ref::<Self>()
}
}
pub struct RequestUtils {
unique_id: String,
id_counter: std::sync::Arc<portable_atomic::AtomicU64>,
}
impl RequestUtils {
pub fn new(unique_id: String) -> Self {
Self {
unique_id,
id_counter: std::sync::Arc::new(portable_atomic::AtomicU64::new(0)),
}
}
pub fn with_counter(
unique_id: String,
id_counter: std::sync::Arc<portable_atomic::AtomicU64>,
) -> Self {
Self {
unique_id,
id_counter,
}
}
pub fn generate_request_id(&self) -> String {
let count = self
.id_counter
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
format!(
"{unique_id}-{count}",
unique_id = self.unique_id,
count = count
)
}
pub fn generate_message_id(&self, user_jid: Option<&Jid>) -> String {
let mut data = Vec::with_capacity(8 + 20 + 16);
let timestamp = crate::time::now_secs() as u64;
data.extend_from_slice(×tamp.to_be_bytes());
if let Some(jid) = user_jid {
data.extend_from_slice(jid.user.as_bytes());
data.extend_from_slice(b"@");
data.extend_from_slice(LEGACY_USER_SERVER.as_bytes());
}
let mut random_bytes = [0u8; 16];
rand::make_rng::<rand::rngs::StdRng>().fill_bytes(&mut random_bytes);
data.extend_from_slice(&random_bytes);
const HEX_UPPER: &[u8; 16] = b"0123456789ABCDEF";
let hash = Sha256::digest(&data);
let truncated = &hash[..9];
let mut id = String::with_capacity(22);
id.push_str("3EB0");
for &b in truncated {
id.push(HEX_UPPER[(b >> 4) as usize] as char);
id.push(HEX_UPPER[(b & 0x0F) as usize] as char);
}
id
}
pub fn build_iq_node(&self, query: InfoQuery<'_>, req_id: Option<String>) -> Node {
let id = req_id.unwrap_or_else(|| self.generate_request_id());
let mut builder = NodeBuilder::new("iq")
.attr("id", id)
.attr("xmlns", query.namespace)
.attr("type", query.query_type.as_str())
.attr("to", query.to);
if let Some(target) = query.target
&& !target.is_empty()
{
builder = builder.attr("target", target);
}
builder.apply_content(query.content).build()
}
pub fn parse_iq_response(&self, response_node: &NodeRef<'_>) -> Result<(), IqError> {
if response_node.tag == "stream:error" || response_node.tag == "xmlstreamend" {
return Err(IqError::Disconnected(response_node.to_owned()));
}
if let Some(res_type) = response_node.get_attr("type")
&& res_type.as_str() == "error"
{
let error_child = response_node.get_optional_child_by_tag(&["error"]);
if let Some(error_node) = error_child {
let mut parser = error_node.attrs();
let code = parser.optional_u64("code").unwrap_or(0) as u16;
let text = parser
.optional_string("text")
.as_deref()
.unwrap_or("")
.to_string();
return Err(IqError::ServerError { code, text });
}
return Err(IqError::ServerError {
code: 0,
text: "Malformed error response".to_string(),
});
}
Ok(())
}
}