use std::{
collections,
ffi,
fmt::{
Display,
Error as FmtError,
Formatter,
Result as FmtResult,
},
hash::{Hash, Hasher},
marker::PhantomData,
ops,
os::raw,
ptr::{self, NonNull},
slice,
};
use crate::{
ALLOC_CONTEXT,
Error,
error::IntoResult,
FFI,
Result,
ToTextError,
};
#[derive(Debug)]
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_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: raw::c_int,
ctx: *mut sys::xmpp_ctx_t,
typ: raw::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 raw::c_char,
attributes: *mut hash_t,
}
#[allow(non_camel_case_types)]
#[repr(C)]
struct hash_t {
rf: raw::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) -> collections::HashMap<&str, &str> {
let count = self.attribute_count();
let mut out = collections::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) })
}
#[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)
})
}
}
}
#[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 Formatter) -> FmtResult {
stanza_to_text(self.inner.as_ptr(), |buf| {
f.write_str(buf.to_str().map_err(|_| FmtError)?)
})
}
}
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 Eq for Stanza {}
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)]
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 Display for StanzaRef<'_> {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
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 Formatter) -> FmtResult {
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(&ffi::CStr) -> Result<T, E>) -> Result<T, E>
where
E: From<Error>
{
let mut buf: *mut raw::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((), |_| if !buf.is_null() {
unsafe { ALLOC_CONTEXT.free(buf); }
});
res.into_result()
.map_err(E::from)
.and_then(|_| {
let text = unsafe {
ffi::CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(buf as *const _, buflen + 1))
};
cb(text)
})
}