use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_uint};
use std::ptr::NonNull;
use std::{fmt, ops, ptr, slice};
use crate::error::IntoResult;
use crate::{Error, ErrorType, Result, ToTextError, ALLOC_CONTEXT, FFI};
mod internals;
#[derive(Debug, Eq)]
pub struct Stanza {
inner: NonNull<sys::xmpp_stanza_t>,
owned: bool,
}
impl Stanza {
#[inline]
pub fn new() -> Self {
unsafe { Stanza::from_owned(sys::xmpp_stanza_new(ALLOC_CONTEXT.as_ptr())) }
}
#[inline]
pub fn new_presence() -> Self {
unsafe { Stanza::from_owned(sys::xmpp_presence_new(ALLOC_CONTEXT.as_ptr())) }
}
#[inline]
pub fn new_iq(typ: Option<&str>, id: Option<&str>) -> Self {
let typ = FFI(typ).send();
let id = FFI(id).send();
unsafe { Stanza::from_owned(sys::xmpp_iq_new(ALLOC_CONTEXT.as_ptr(), typ.as_ptr(), id.as_ptr())) }
}
#[inline]
pub fn new_message(typ: Option<&str>, id: Option<&str>, to: Option<&str>) -> Self {
let typ = FFI(typ).send();
let to = FFI(to).send();
let id = FFI(id).send();
unsafe {
Stanza::from_owned(sys::xmpp_message_new(
ALLOC_CONTEXT.as_ptr(),
typ.as_ptr(),
to.as_ptr(),
id.as_ptr(),
))
}
}
#[inline]
#[cfg(feature = "libstrophe-0_9_3")]
pub fn new_error(typ: ErrorType, text: Option<&str>) -> Self {
let text = FFI(text).send();
unsafe { Stanza::from_owned(sys::xmpp_error_new(ALLOC_CONTEXT.as_ptr(), typ, text.as_ptr())) }
}
#[inline]
#[cfg(feature = "libstrophe-0_10_0")]
pub fn from_str(s: impl AsRef<str>) -> Self {
#![allow(clippy::should_implement_trait)]
let s = FFI(s.as_ref()).send();
unsafe { Stanza::from_owned(sys::xmpp_stanza_new_from_string(ALLOC_CONTEXT.as_ptr(), s.as_ptr())) }
}
#[inline]
unsafe fn with_inner(inner: *mut sys::xmpp_stanza_t, owned: bool) -> Self {
let mut out = Stanza {
inner: NonNull::new(inner).expect("Cannot allocate memory for Stanza"),
owned,
};
if owned {
out.set_alloc_context();
}
out
}
#[inline]
pub unsafe fn from_owned(inner: *mut sys::xmpp_stanza_t) -> Self {
Stanza::with_inner(inner, true)
}
#[inline]
pub unsafe fn from_ref<'st>(inner: *const sys::xmpp_stanza_t) -> StanzaRef<'st> {
Stanza::with_inner(inner as _, false).into()
}
#[inline]
pub unsafe fn from_ref_mut<'st>(inner: *mut sys::xmpp_stanza_t) -> StanzaMutRef<'st> {
Stanza::with_inner(inner, false).into()
}
pub(crate) fn as_ptr(&self) -> *mut sys::xmpp_stanza_t {
self.inner.as_ptr()
}
fn set_alloc_context(&mut self) {
#[allow(non_camel_case_types)]
#[repr(C)]
struct xmpp_stanza_t {
rf: c_int,
ctx: *mut sys::xmpp_ctx_t,
typ: c_int,
prev: *mut sys::xmpp_stanza_t,
next: *mut sys::xmpp_stanza_t,
children: *mut sys::xmpp_stanza_t,
parent: *mut sys::xmpp_stanza_t,
data: *mut c_char,
attributes: *mut hash_t,
}
#[allow(non_camel_case_types)]
#[repr(C)]
struct hash_t {
rf: c_uint,
ctx: *mut sys::xmpp_ctx_t,
}
let mut inner = unsafe { (self.inner.as_ptr() as *mut xmpp_stanza_t).as_mut() }.expect("Null pointer for Stanza context");
let alloc_ctx = ALLOC_CONTEXT.as_ptr();
inner.ctx = alloc_ctx;
if let Some(attrs) = unsafe { inner.attributes.as_mut() } {
attrs.ctx = alloc_ctx;
}
for mut child in self.children_mut() {
child.set_alloc_context();
}
}
#[inline]
pub fn is_text(&self) -> bool {
FFI(unsafe { sys::xmpp_stanza_is_text(self.inner.as_ptr()) }).receive_bool()
}
#[inline]
pub fn is_tag(&self) -> bool {
FFI(unsafe { sys::xmpp_stanza_is_tag(self.inner.as_ptr()) }).receive_bool()
}
#[inline]
pub fn to_text(&self) -> Result<String, ToTextError> {
stanza_to_text(self.inner.as_ptr(), |buf| Ok(buf.to_str()?.to_owned()))
}
#[inline]
pub fn set_name(&mut self, name: impl AsRef<str>) -> Result<()> {
let name = FFI(name.as_ref()).send();
unsafe { sys::xmpp_stanza_set_name(self.inner.as_mut(), name.as_ptr()) }.into_result()
}
#[inline]
pub fn name(&self) -> Option<&str> {
unsafe { FFI(sys::xmpp_stanza_get_name(self.inner.as_ptr())).receive() }
}
#[inline]
pub fn attribute_count(&self) -> i32 {
unsafe { sys::xmpp_stanza_get_attribute_count(self.inner.as_ptr()) }
}
#[inline]
pub fn set_attribute(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
let name = FFI(name.as_ref()).send();
let value = FFI(value.as_ref()).send();
unsafe { sys::xmpp_stanza_set_attribute(self.inner.as_mut(), name.as_ptr(), value.as_ptr()) }.into_result()
}
#[inline]
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<&str> {
let name = FFI(name.as_ref()).send();
unsafe { FFI(sys::xmpp_stanza_get_attribute(self.inner.as_ptr(), name.as_ptr())).receive() }
}
pub fn attributes(&self) -> HashMap<&str, &str> {
let count = self.attribute_count();
let mut out = HashMap::with_capacity(count as _);
let mut arr = vec![ptr::null() as _; count as usize * 2];
unsafe {
sys::xmpp_stanza_get_attributes(self.inner.as_ptr(), arr.as_mut_ptr(), count * 2);
}
let mut iter = arr.into_iter();
while let (Some(key), Some(val)) = (iter.next(), iter.next()) {
out.insert(
unsafe { FFI(key).receive() }.expect("Null pointer received for key in attributes() call"),
unsafe { FFI(val).receive() }.expect("Null pointer received for value in attributes() call"),
);
}
out
}
#[inline]
pub fn del_attribute(&mut self, name: impl AsRef<str>) -> Result<()> {
let name = FFI(name.as_ref()).send();
unsafe { sys::xmpp_stanza_del_attribute(self.inner.as_mut(), name.as_ptr()) }.into_result()
}
#[inline]
pub fn set_text(&mut self, text: impl AsRef<str>) -> Result<()> {
let text = text.as_ref();
unsafe { sys::xmpp_stanza_set_text_with_size(self.inner.as_mut(), text.as_ptr() as _, text.len()) }.into_result()
}
#[inline]
pub fn text(&self) -> Option<String> {
unsafe { FFI(sys::xmpp_stanza_get_text(self.inner.as_ptr())).receive_with_free(|x| ALLOC_CONTEXT.free(x)) }
}
#[inline]
pub fn set_id(&mut self, id: impl AsRef<str>) -> Result<()> {
let id = FFI(id.as_ref()).send();
unsafe { sys::xmpp_stanza_set_id(self.inner.as_mut(), id.as_ptr()) }.into_result()
}
#[inline]
pub fn id(&self) -> Option<&str> {
unsafe { FFI(sys::xmpp_stanza_get_id(self.inner.as_ptr())).receive() }
}
#[inline]
pub fn set_ns(&mut self, ns: impl AsRef<str>) -> Result<()> {
let ns = FFI(ns.as_ref()).send();
unsafe { sys::xmpp_stanza_set_ns(self.inner.as_mut(), ns.as_ptr()) }.into_result()
}
#[inline]
pub fn ns(&self) -> Option<&str> {
unsafe { FFI(sys::xmpp_stanza_get_ns(self.inner.as_ptr())).receive() }
}
#[inline]
pub fn set_stanza_type(&mut self, typ: impl AsRef<str>) -> Result<()> {
let typ = FFI(typ.as_ref()).send();
unsafe { sys::xmpp_stanza_set_type(self.inner.as_mut(), typ.as_ptr()) }.into_result()
}
#[inline]
pub fn stanza_type(&self) -> Option<&str> {
unsafe { FFI(sys::xmpp_stanza_get_type(self.inner.as_ptr())).receive() }
}
#[inline]
pub fn set_to(&mut self, to: impl AsRef<str>) -> Result<()> {
let to = FFI(to.as_ref()).send();
unsafe { sys::xmpp_stanza_set_to(self.inner.as_mut(), to.as_ptr()) }.into_result()
}
#[inline]
pub fn to(&self) -> Option<&str> {
unsafe { FFI(sys::xmpp_stanza_get_to(self.inner.as_ptr())).receive() }
}
#[inline]
pub fn set_from(&mut self, from: impl AsRef<str>) -> Result<()> {
let from = FFI(from.as_ref()).send();
unsafe { sys::xmpp_stanza_set_from(self.inner.as_mut(), from.as_ptr()) }.into_result()
}
#[inline]
pub fn from(&self) -> Option<&str> {
unsafe { FFI(sys::xmpp_stanza_get_from(self.inner.as_ptr())).receive() }
}
#[inline]
pub fn get_first_child(&self) -> Option<StanzaRef> {
unsafe { sys::xmpp_stanza_get_children(self.inner.as_ptr()).as_ref() }.map(|x| unsafe { Self::from_ref(x) })
}
#[inline]
pub fn get_first_child_mut(&mut self) -> Option<StanzaMutRef> {
unsafe { sys::xmpp_stanza_get_children(self.inner.as_mut()).as_mut() }.map(|x| unsafe { Self::from_ref_mut(x) })
}
#[inline]
pub fn get_child_by_ns(&self, ns: impl AsRef<str>) -> Option<StanzaRef> {
let ns = FFI(ns.as_ref()).send();
unsafe { sys::xmpp_stanza_get_child_by_ns(self.inner.as_ptr(), ns.as_ptr()).as_ref() }.map(|x| unsafe { Self::from_ref(x) })
}
#[inline]
pub fn get_child_by_ns_mut(&mut self, ns: impl AsRef<str>) -> Option<StanzaMutRef> {
let ns = FFI(ns.as_ref()).send();
unsafe { sys::xmpp_stanza_get_child_by_ns(self.inner.as_mut(), ns.as_ptr()).as_mut() }
.map(|x| unsafe { Self::from_ref_mut(x) })
}
#[inline]
pub fn get_child_by_name(&self, name: impl AsRef<str>) -> Option<StanzaRef> {
let name = FFI(name.as_ref()).send();
unsafe { sys::xmpp_stanza_get_child_by_name(self.inner.as_ptr(), name.as_ptr()).as_ref() }
.map(|x| unsafe { Self::from_ref(x) })
}
#[inline]
pub fn get_child_by_name_mut(&mut self, name: impl AsRef<str>) -> Option<StanzaMutRef> {
let name = FFI(name.as_ref()).send();
unsafe { sys::xmpp_stanza_get_child_by_name(self.inner.as_mut(), name.as_ptr()).as_mut() }
.map(|x| unsafe { Self::from_ref_mut(x) })
}
#[inline]
#[cfg(feature = "libstrophe-0_10_0")]
pub fn get_child_by_name_and_ns(&self, name: impl AsRef<str>, ns: impl AsRef<str>) -> Option<StanzaRef> {
let name = FFI(name.as_ref()).send();
let ns = FFI(ns.as_ref()).send();
unsafe { sys::xmpp_stanza_get_child_by_name_and_ns(self.inner.as_ptr(), name.as_ptr(), ns.as_ptr()).as_ref() }
.map(|x| unsafe { Self::from_ref(x) })
}
#[inline]
#[cfg(feature = "libstrophe-0_10_0")]
pub fn get_child_by_name_and_ns_mut(&mut self, name: impl AsRef<str>, ns: impl AsRef<str>) -> Option<StanzaMutRef> {
let name = FFI(name.as_ref()).send();
let ns = FFI(ns.as_ref()).send();
unsafe { sys::xmpp_stanza_get_child_by_name_and_ns(self.inner.as_mut(), name.as_ptr(), ns.as_ptr()).as_mut() }
.map(|x| unsafe { Self::from_ref_mut(x) })
}
#[cfg(feature = "libstrophe-0_12_0")]
pub fn get_child_by_path(&self, path: &[&str]) -> Option<StanzaRef> {
let res = internals::stanza_get_child_by_path(self.inner.as_ptr(), path);
unsafe { res.as_ref() }.map(|x| unsafe { Self::from_ref(x) })
}
#[inline]
#[cfg(feature = "libstrophe-0_12_0")]
pub fn get_child_by_path_mut(&mut self, path: &[&str]) -> Option<StanzaMutRef> {
let res = internals::stanza_get_child_by_path(unsafe { self.inner.as_mut() }, path);
unsafe { res.as_mut() }.map(|x| unsafe { Self::from_ref_mut(x) })
}
#[inline]
pub fn children(&self) -> impl Iterator<Item = StanzaRef> {
ChildIterator {
cur: self.get_first_child().map(StanzaChildRef),
}
}
#[inline]
pub fn children_mut(&mut self) -> impl Iterator<Item = StanzaMutRef> {
ChildIteratorMut {
cur: self.get_first_child_mut().map(StanzaChildMutRef),
}
}
#[inline]
pub fn get_next(&self) -> Option<StanzaRef> {
unsafe { sys::xmpp_stanza_get_next(self.inner.as_ptr()).as_ref() }.map(|x| unsafe { Self::from_ref(x) })
}
#[inline]
pub fn get_next_mut(&mut self) -> Option<StanzaMutRef> {
unsafe { sys::xmpp_stanza_get_next(self.inner.as_mut()).as_mut() }.map(|x| unsafe { Self::from_ref_mut(x) })
}
#[inline]
pub fn add_child(&mut self, child: Stanza) -> Result<()> {
let mut child = child;
unsafe { sys::xmpp_stanza_add_child(self.inner.as_mut(), child.inner.as_mut()) }.into_result()
}
#[inline]
pub fn reply(&self) -> Self {
unsafe { Self::from_owned(sys::xmpp_stanza_reply(self.inner.as_ptr())) }
}
#[inline]
#[cfg(feature = "libstrophe-0_10_0")]
pub fn reply_error(&self, error_type: impl AsRef<str>, condition: impl AsRef<str>, text: impl AsRef<str>) -> Self {
let error_type = FFI(error_type.as_ref()).send();
let condition = FFI(condition.as_ref()).send();
let text = FFI(text.as_ref()).send();
unsafe {
Self::from_owned(sys::xmpp_stanza_reply_error(
self.inner.as_ptr(),
error_type.as_ptr(),
condition.as_ptr(),
text.as_ptr(),
))
}
}
#[inline]
pub fn set_body(&mut self, body: impl AsRef<str>) -> Result<()> {
let body = FFI(body.as_ref()).send();
unsafe { sys::xmpp_message_set_body(self.inner.as_mut(), body.as_ptr()) }.into_result()
}
#[inline]
pub fn body(&self) -> Option<String> {
unsafe { FFI(sys::xmpp_message_get_body(self.inner.as_ptr())).receive_with_free(|x| ALLOC_CONTEXT.free(x)) }
}
}
#[inline]
#[allow(non_snake_case)]
pub fn XMPP_STANZA_NAME_IN_NS(name: &str, ns: &str) -> String {
format!("{}[@ns='{}']", name, ns)
}
#[cfg(feature = "libstrophe-0_10_0")]
impl std::str::FromStr for Stanza {
type Err = ();
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from_str(s))
}
}
impl Display for Stanza {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
stanza_to_text(self.inner.as_ptr(), |buf| f.write_str(buf.to_str().map_err(|_| fmt::Error)?))
}
}
impl Clone for Stanza {
#[inline]
fn clone(&self) -> Self {
unsafe { Stanza::from_owned(sys::xmpp_stanza_copy(self.inner.as_ptr())) }
}
}
impl PartialEq for Stanza {
#[inline]
fn eq(&self, other: &Stanza) -> bool {
self.inner == other.inner
}
}
impl Hash for Stanza {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
impl Drop for Stanza {
#[inline]
fn drop(&mut self) {
if self.owned {
unsafe {
sys::xmpp_stanza_release(self.inner.as_mut());
}
}
}
}
impl Default for Stanza {
#[inline]
fn default() -> Self {
Self::new()
}
}
unsafe impl Send for Stanza {}
impl<'st> From<Stanza> for StanzaRef<'st> {
#[inline]
fn from(s: Stanza) -> Self {
StanzaRef(s, PhantomData)
}
}
impl<'st> From<Stanza> for StanzaMutRef<'st> {
#[inline]
fn from(s: Stanza) -> Self {
StanzaMutRef(s, PhantomData)
}
}
#[derive(Debug, Eq)]
pub struct StanzaRef<'st>(Stanza, PhantomData<&'st Stanza>);
impl ops::Deref for StanzaRef<'_> {
type Target = Stanza;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq for StanzaRef<'_> {
#[inline]
fn eq(&self, other: &StanzaRef) -> bool {
self.inner == other.inner
}
}
impl Display for StanzaRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug)]
struct StanzaChildRef<'parent>(StanzaRef<'parent>);
impl<'parent> StanzaChildRef<'parent> {
#[inline]
pub fn get_next(&self) -> Option<StanzaChildRef<'parent>> {
unsafe { sys::xmpp_stanza_get_next(self.0.inner.as_ptr()).as_ref() }.map(|x| StanzaChildRef(unsafe { Stanza::from_ref(x) }))
}
}
#[derive(Debug)]
pub struct StanzaMutRef<'st>(Stanza, PhantomData<&'st mut Stanza>);
impl ops::Deref for StanzaMutRef<'_> {
type Target = Stanza;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ops::DerefMut for StanzaMutRef<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Display for StanzaMutRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug)]
pub struct StanzaChildMutRef<'parent>(StanzaMutRef<'parent>);
impl<'parent> StanzaChildMutRef<'parent> {
#[inline]
pub fn get_next_mut(&mut self) -> Option<StanzaChildMutRef<'parent>> {
unsafe { sys::xmpp_stanza_get_next(self.0.inner.as_ptr()).as_mut() }
.map(|x| StanzaChildMutRef(unsafe { Stanza::from_ref_mut(x) }))
}
}
struct ChildIterator<'st> {
cur: Option<StanzaChildRef<'st>>,
}
impl<'st> Iterator for ChildIterator<'st> {
type Item = StanzaRef<'st>;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
self.cur.take().map(|cur| {
self.cur = cur.get_next();
cur.0
})
}
}
struct ChildIteratorMut<'st> {
cur: Option<StanzaChildMutRef<'st>>,
}
impl<'st> Iterator for ChildIteratorMut<'st> {
type Item = StanzaMutRef<'st>;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
self.cur.take().map(|mut cur| {
self.cur = cur.get_next_mut();
cur.0
})
}
}
fn stanza_to_text<T, E>(stanza: *mut sys::xmpp_stanza_t, cb: impl FnOnce(&CStr) -> Result<T, E>) -> Result<T, E>
where
E: From<Error>,
{
let mut buf: *mut c_char = ptr::null_mut();
let mut buflen: usize = 0;
let res = unsafe { sys::xmpp_stanza_to_text(stanza, &mut buf, &mut buflen) };
let _free_buf = scopeguard::guard(buf, |buf| {
if !buf.is_null() {
unsafe {
ALLOC_CONTEXT.free(buf);
}
}
});
res.into_result().map_err(E::from).and_then(|_| {
let text = unsafe { CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(buf as *const _, buflen + 1)) };
cb(text)
})
}