Compare commits

..

No commits in common. "0e5f09b2bd05690f3d28f7076629031fcc2cc6e6" and "27f90bd85f2abf2ecdac69880801d657f125b6ce" have entirely different histories.

8 changed files with 0 additions and 345 deletions

View File

@ -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

View File

@ -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 => {

View File

@ -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(())
} }

View File

@ -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"

View File

@ -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) {}
}

View File

@ -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!(),
} }

View File

@ -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;

View File

@ -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())
}
}