WIP: client
This commit is contained in:
parent
89351e9563
commit
0e5f09b2bd
|
@ -20,6 +20,7 @@
|
||||||
- [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,6 +26,7 @@ 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,
|
||||||
}
|
}
|
||||||
|
@ -49,6 +50,10 @@ 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,6 +63,7 @@ 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,3 +4,9 @@ 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"
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
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,6 +9,7 @@ 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},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ pub struct Iq {
|
||||||
pub enum Query {
|
pub enum Query {
|
||||||
Bind(Bind),
|
Bind(Bind),
|
||||||
Ping(Ping),
|
Ping(Ping),
|
||||||
|
Roster(roster::Query),
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +41,9 @@ 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +54,7 @@ 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!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use peanuts::{
|
||||||
|
|
||||||
pub const XMLNS: &str = "jabber:iq:roster";
|
pub const XMLNS: &str = "jabber:iq:roster";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Query {
|
pub struct Query {
|
||||||
ver: Option<String>,
|
ver: Option<String>,
|
||||||
items: Vec<Item>,
|
items: Vec<Item>,
|
||||||
|
@ -33,7 +34,7 @@ impl IntoElement for Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
approved: Option<bool>,
|
approved: Option<bool>,
|
||||||
ask: bool,
|
ask: bool,
|
||||||
|
@ -95,7 +96,7 @@ impl IntoElement for Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub enum Subscription {
|
pub enum Subscription {
|
||||||
Both,
|
Both,
|
||||||
From,
|
From,
|
||||||
|
@ -132,7 +133,7 @@ impl ToString for Subscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Group(Option<String>);
|
pub struct Group(Option<String>);
|
||||||
|
|
||||||
impl FromElement for Group {
|
impl FromElement for Group {
|
||||||
|
|
Loading…
Reference in New Issue