Compare commits
No commits in common. "0e5f09b2bd05690f3d28f7076629031fcc2cc6e6" and "27f90bd85f2abf2ecdac69880801d657f125b6ce" have entirely different histories.
0e5f09b2bd
...
27f90bd85f
|
@ -20,7 +20,6 @@
|
||||||
- [x] rfc 7590: tls
|
- [x] rfc 7590: tls
|
||||||
- [x] xep-0368: srv records for xmpp over tls
|
- [x] xep-0368: srv records for xmpp over tls
|
||||||
- [ ] server side downgrade protection for sasl
|
- [ ] server side downgrade protection for sasl
|
||||||
- [x] xep-0199: xmpp ping
|
|
||||||
- [ ] xep-0030: service discovery
|
- [ ] xep-0030: service discovery
|
||||||
- [ ] xep-0115: entity capabilities
|
- [ ] xep-0115: entity capabilities
|
||||||
- [ ] xep-0163: pep
|
- [ ] xep-0163: pep
|
||||||
|
|
|
@ -26,7 +26,6 @@ use crate::{
|
||||||
pub struct JabberClient {
|
pub struct JabberClient {
|
||||||
connection: ConnectionState,
|
connection: ConnectionState,
|
||||||
jid: JID,
|
jid: JID,
|
||||||
// TODO: have reconnection be handled by another part, so creds don't need to be stored in object
|
|
||||||
password: Arc<SASLConfig>,
|
password: Arc<SASLConfig>,
|
||||||
server: String,
|
server: String,
|
||||||
}
|
}
|
||||||
|
@ -50,10 +49,6 @@ impl JabberClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jid(&self) -> JID {
|
|
||||||
self.jid.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect(&mut self) -> Result<()> {
|
pub async fn connect(&mut self) -> Result<()> {
|
||||||
match &self.connection {
|
match &self.connection {
|
||||||
ConnectionState::Disconnected => {
|
ConnectionState::Disconnected => {
|
||||||
|
|
|
@ -63,7 +63,6 @@ where
|
||||||
if let Some(_write_handle) = this.write_handle {
|
if let Some(_write_handle) = this.write_handle {
|
||||||
panic!("start_send called without poll_ready")
|
panic!("start_send called without poll_ready")
|
||||||
} else {
|
} else {
|
||||||
// TODO: switch to buffer of one rather than thread spawning and joining
|
|
||||||
*this.write_handle = Some(tokio::spawn(write(this.writer.clone(), item)));
|
*this.write_handle = Some(tokio::spawn(write(this.writer.clone(), item)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,3 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3.31"
|
|
||||||
jabber = { version = "0.1.0", path = "../jabber" }
|
|
||||||
stanza = { version = "0.1.0", path = "../stanza" }
|
|
||||||
tokio = "1.42.0"
|
|
||||||
tokio-stream = "0.1.17"
|
|
||||||
tokio-util = "0.7.13"
|
|
||||||
|
|
171
luz/src/lib.rs
171
luz/src/lib.rs
|
@ -1,171 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
|
||||||
pin::pin,
|
|
||||||
task::{ready, Poll},
|
|
||||||
thread::JoinHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures::{
|
|
||||||
stream::{SplitSink, SplitStream},
|
|
||||||
Sink, SinkExt, Stream, StreamExt,
|
|
||||||
};
|
|
||||||
use jabber::{client::JabberClient, JID};
|
|
||||||
use stanza::{
|
|
||||||
client::{
|
|
||||||
iq::{Iq, IqType, Query},
|
|
||||||
Stanza,
|
|
||||||
},
|
|
||||||
roster,
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
|
||||||
use tokio_util::sync::{PollSendError, PollSender};
|
|
||||||
|
|
||||||
pub struct Client {
|
|
||||||
client: JabberClient,
|
|
||||||
pending_iqs: HashMap<String, mpsc::Sender<Iq>>,
|
|
||||||
// database connection (sqlite)
|
|
||||||
receiver: ReceiverStream<UpdateMessage>,
|
|
||||||
sender: PollSender<CommandMessage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub async fn new(jid: String, password: &str) -> Result<Self, Error> {
|
|
||||||
let (read_sender, read_receiver) = mpsc::channel::<UpdateMessage>(20);
|
|
||||||
let (write_sender, write_receiver) = mpsc::channel::<CommandMessage>(20);
|
|
||||||
let mut jabber_client = JabberClient::new(jid, password)?;
|
|
||||||
jabber_client.connect().await?;
|
|
||||||
let (write, read) = jabber_client.split();
|
|
||||||
let client = Self {
|
|
||||||
client: jabber_client,
|
|
||||||
receiver: ReceiverStream::new(read_receiver),
|
|
||||||
sender: PollSender::new(write_sender),
|
|
||||||
pending_iqs: HashMap::new(),
|
|
||||||
};
|
|
||||||
tokio::spawn(client.process_read(read, read_sender));
|
|
||||||
tokio::spawn(client.process_write(write, write_receiver));
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn process_read(
|
|
||||||
&self,
|
|
||||||
mut stream: SplitStream<JabberClient>,
|
|
||||||
sender: mpsc::Sender<UpdateMessage>,
|
|
||||||
) {
|
|
||||||
for stanza in stream.next().await {
|
|
||||||
tokio::spawn(self.process_stanza(stanza, sender.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn process_write(
|
|
||||||
&self,
|
|
||||||
mut sink: SplitSink<JabberClient, Stanza>,
|
|
||||||
receiver: mpsc::Receiver<CommandMessage>,
|
|
||||||
) {
|
|
||||||
for message in receiver.recv_many(, )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Error {
|
|
||||||
PollSend(PollSendError<CommandMessage>),
|
|
||||||
Jabber(jabber::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<jabber::Error> for Error {
|
|
||||||
fn from(e: jabber::Error) -> Self {
|
|
||||||
Self::Jabber(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stream for Client {
|
|
||||||
type Item = UpdateMessage;
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Option<Self::Item>> {
|
|
||||||
pin!(self).receiver.poll_next_unpin(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sink<CommandMessage> for Client {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn poll_ready(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(ready!(pin!(self).sender.poll_ready_unpin(cx)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_send(self: std::pin::Pin<&mut Self>, item: CommandMessage) -> Result<(), Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> Poll<Result<(), Self::Error>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_close(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> Poll<Result<(), Self::Error>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PollSendError<CommandMessage>> for Error {
|
|
||||||
fn from(e: PollSendError<CommandMessage>) -> Self {
|
|
||||||
Self::PollSend(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum CommandMessage {
|
|
||||||
Connect,
|
|
||||||
GetRoster,
|
|
||||||
SendMessage(JID, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum UpdateMessage {
|
|
||||||
Roster(Vec<roster::Item>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub async fn process_stanza(
|
|
||||||
&mut self,
|
|
||||||
stanza: Result<Stanza, jabber::Error>,
|
|
||||||
sender: mpsc::Sender<UpdateMessage>,
|
|
||||||
) {
|
|
||||||
match stanza {
|
|
||||||
Ok(stanza) => todo!(),
|
|
||||||
Err(e) => self.process_error(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn iq(
|
|
||||||
&mut self,
|
|
||||||
to: Option<JID>,
|
|
||||||
r#type: IqType,
|
|
||||||
query: Option<Query>,
|
|
||||||
) -> Result<IqResponse, Error> {
|
|
||||||
self.client
|
|
||||||
.send(Stanza::Iq(Iq {
|
|
||||||
from: Some(self.client.jid()),
|
|
||||||
// TODO: generate id
|
|
||||||
id: "test".to_string(),
|
|
||||||
to,
|
|
||||||
r#type,
|
|
||||||
// TODO: lang
|
|
||||||
lang: None,
|
|
||||||
query,
|
|
||||||
errors: Vec::new(),
|
|
||||||
}))
|
|
||||||
.await?;
|
|
||||||
Ok(todo!())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn iq_process(&mut self, iq: Iq) {}
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ use peanuts::{
|
||||||
use crate::{
|
use crate::{
|
||||||
bind::{self, Bind},
|
bind::{self, Bind},
|
||||||
client::error::Error,
|
client::error::Error,
|
||||||
roster,
|
|
||||||
xep_0199::{self, Ping},
|
xep_0199::{self, Ping},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,7 +31,6 @@ pub struct Iq {
|
||||||
pub enum Query {
|
pub enum Query {
|
||||||
Bind(Bind),
|
Bind(Bind),
|
||||||
Ping(Ping),
|
Ping(Ping),
|
||||||
Roster(roster::Query),
|
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,9 +39,6 @@ impl FromElement for Query {
|
||||||
match element.identify() {
|
match element.identify() {
|
||||||
(Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)),
|
(Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)),
|
||||||
(Some(xep_0199::XMLNS), "ping") => Ok(Query::Ping(Ping::from_element(element)?)),
|
(Some(xep_0199::XMLNS), "ping") => Ok(Query::Ping(Ping::from_element(element)?)),
|
||||||
(Some(roster::XMLNS), "query") => {
|
|
||||||
Ok(Query::Roster(roster::Query::from_element(element)?))
|
|
||||||
}
|
|
||||||
_ => Ok(Query::Unsupported),
|
_ => Ok(Query::Unsupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +49,6 @@ impl IntoElement for Query {
|
||||||
match self {
|
match self {
|
||||||
Query::Bind(bind) => bind.builder(),
|
Query::Bind(bind) => bind.builder(),
|
||||||
Query::Ping(ping) => ping.builder(),
|
Query::Ping(ping) => ping.builder(),
|
||||||
Query::Roster(query) => query.builder(),
|
|
||||||
// TODO: consider what to do if attempt to serialize unsupported
|
// TODO: consider what to do if attempt to serialize unsupported
|
||||||
Query::Unsupported => todo!(),
|
Query::Unsupported => todo!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ use peanuts::declaration::VersionInfo;
|
||||||
|
|
||||||
pub mod bind;
|
pub mod bind;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod roster;
|
|
||||||
pub mod sasl;
|
pub mod sasl;
|
||||||
pub mod stanza_error;
|
pub mod stanza_error;
|
||||||
pub mod starttls;
|
pub mod starttls;
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use jid::JID;
|
|
||||||
use peanuts::{
|
|
||||||
element::{FromElement, IntoElement},
|
|
||||||
DeserializeError, Element,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const XMLNS: &str = "jabber:iq:roster";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Query {
|
|
||||||
ver: Option<String>,
|
|
||||||
items: Vec<Item>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromElement for Query {
|
|
||||||
fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
|
|
||||||
element.check_name("query")?;
|
|
||||||
element.check_namespace(XMLNS)?;
|
|
||||||
|
|
||||||
let ver = element.attribute_opt("ver")?;
|
|
||||||
let items = element.pop_children()?;
|
|
||||||
|
|
||||||
Ok(Self { ver, items })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoElement for Query {
|
|
||||||
fn builder(&self) -> peanuts::element::ElementBuilder {
|
|
||||||
Element::builder("query", Some(XMLNS))
|
|
||||||
.push_attribute_opt("ver", self.ver.clone())
|
|
||||||
.push_children(self.items.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Item {
|
|
||||||
approved: Option<bool>,
|
|
||||||
ask: bool,
|
|
||||||
jid: JID,
|
|
||||||
name: Option<String>,
|
|
||||||
subscription: Subscription,
|
|
||||||
groups: Vec<Group>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromElement for Item {
|
|
||||||
fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
|
|
||||||
element.check_name("item")?;
|
|
||||||
element.check_namespace(XMLNS)?;
|
|
||||||
|
|
||||||
let approved = element.attribute_opt("approved")?;
|
|
||||||
let ask = if let Some(result) = element.attribute_opt("ask")?.map(|v| {
|
|
||||||
if v == "subscribe" {
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Err(DeserializeError::FromStr(v))
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
result?
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
let jid = element.attribute("jid")?;
|
|
||||||
let name = element.attribute_opt("name")?;
|
|
||||||
let subscription = element.attribute_opt("subscription")?.unwrap_or_default();
|
|
||||||
let groups = element.pop_children()?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
approved,
|
|
||||||
ask,
|
|
||||||
jid,
|
|
||||||
name,
|
|
||||||
subscription,
|
|
||||||
groups,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoElement for Item {
|
|
||||||
fn builder(&self) -> peanuts::element::ElementBuilder {
|
|
||||||
Element::builder("item", Some(XMLNS))
|
|
||||||
.push_attribute_opt("approved", self.approved)
|
|
||||||
.push_attribute_opt(
|
|
||||||
"ask",
|
|
||||||
if self.ask {
|
|
||||||
Some("subscribe".to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.push_attribute("jid", self.jid.clone())
|
|
||||||
.push_attribute_opt("name", self.name.clone())
|
|
||||||
.push_attribute("subscription", self.subscription)
|
|
||||||
.push_children(self.groups.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
|
||||||
pub enum Subscription {
|
|
||||||
Both,
|
|
||||||
From,
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Remove,
|
|
||||||
To,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Subscription {
|
|
||||||
type Err = DeserializeError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"both" => Ok(Self::Both),
|
|
||||||
"from" => Ok(Self::From),
|
|
||||||
"none" => Ok(Self::None),
|
|
||||||
"remove" => Ok(Self::Remove),
|
|
||||||
"to" => Ok(Self::To),
|
|
||||||
s => Err(DeserializeError::FromStr(s.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for Subscription {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Subscription::Both => "both".to_string(),
|
|
||||||
Subscription::From => "from".to_string(),
|
|
||||||
Subscription::None => "none".to_string(),
|
|
||||||
Subscription::Remove => "remove".to_string(),
|
|
||||||
Subscription::To => "to".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Group(Option<String>);
|
|
||||||
|
|
||||||
impl FromElement for Group {
|
|
||||||
fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
|
|
||||||
element.check_name("group")?;
|
|
||||||
element.check_namespace(XMLNS)?;
|
|
||||||
|
|
||||||
let group = element.pop_value_opt()?;
|
|
||||||
|
|
||||||
Ok(Self(group))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoElement for Group {
|
|
||||||
fn builder(&self) -> peanuts::element::ElementBuilder {
|
|
||||||
Element::builder("group", Some(XMLNS)).push_text_opt(self.0.clone())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue