websocket protocol

This commit is contained in:
2024-10-19 16:00:31 +02:00
parent 5e651b382d
commit b94b3cf44f
13 changed files with 440 additions and 217 deletions

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
avam-protocol = { path = "../avam-protocol" }
anyhow = { version = "1.0" }
base64 = { version = "0.22.1", default-features = false }
clap = { version = "4.5.20", features = ["derive"] }

View File

@@ -1,90 +1,180 @@
use std::{borrow::Cow, time::Duration};
use std::time::Duration;
use avam_protocol::{Packet, Packets, SimConnectPacket, SystemPacket};
use futures_util::{SinkExt, StreamExt};
use reqwest::StatusCode;
use tokio::{
sync::broadcast::{Receiver, Sender},
time::sleep,
sync::broadcast::{channel, Receiver, Sender},
task::JoinSet,
time::{sleep, timeout},
};
use tokio_tungstenite::{
connect_async,
tungstenite::{
self,
protocol::{frame::coding::CloseCode, CloseFrame},
ClientRequestBuilder,
ClientRequestBuilder, Message,
},
};
use crate::{state_machine::Event, BASE_URL};
use crate::{config::Config, state_machine::Event, BASE_URL};
pub async fn start(
config: Config,
simconnect_sender: Sender<SimConnectPacket>,
socket_receiver: Receiver<Packets>,
event_sender: Sender<Event>,
mut event_receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
let mut writer = None;
let uri: tungstenite::http::Uri = format!("{}/ws", BASE_URL.replace("https", "wss")).parse()?;
let mut futures = JoinSet::new();
let (ia_sender, ia_receiver) = channel(10);
loop {
if let Ok(event) = &event_receiver.try_recv() {
match event {
Event::TokenReceived { token } => {
Event::TokenReceived => {
let Some(token) = config.token() else {
let _ = event_sender.send(Event::Logout);
sleep(Duration::from_millis(100)).await;
continue;
};
let builder = ClientRequestBuilder::new(uri.clone())
.with_header("Authorization", format!("Bearer {}", token));
let (socket, response) = connect_async(builder).await?;
tracing::info!("Connecting");
let Ok(Ok((socket, response))) =
timeout(Duration::from_secs(10), connect_async(builder)).await
else {
tracing::error!("Unable to connect");
let _ = event_sender.send(Event::Disconnected);
sleep(Duration::from_millis(100)).await;
continue;
};
if response.status() != StatusCode::SWITCHING_PROTOCOLS {
tracing::error!("{:#?}", response);
tracing::error!("Unable to connect: {:#?}", response);
let _ = event_sender.send(Event::Disconnected);
sleep(Duration::from_millis(100)).await;
continue;
}
let (write, mut read) = socket.split();
writer = Some(write);
let (mut write, mut read) = socket.split();
tokio::spawn(async move {
let message = match read.next().await {
Some(data) => match data {
Ok(message) => message,
Err(e) => {
tracing::error!("{:?}", e);
return;
let mut ia_receiver: Receiver<Packets> = ia_receiver.resubscribe();
futures.spawn(async move {
loop {
if let Ok(d) = ia_receiver.try_recv() {
let message = match d {
Packets::System(SystemPacket::Close { reason }) => {
Message::Close(Some(CloseFrame {
code: CloseCode::Normal,
reason: reason.into(),
}))
}
Packets::System(SystemPacket::Pong) => {
let Ok(encoded_message) = d.encode() else {
tracing::error!(
"Unable to encode message for sending: {:#?}",
d
);
sleep(Duration::from_millis(100)).await;
continue;
};
Message::Binary(encoded_message)
}
d => {
tracing::info!("sending packet: {:?}", &d);
let Ok(encoded_message) = d.encode() else {
tracing::error!(
"Unable to encode message for sending: {:#?}",
d
);
sleep(Duration::from_millis(100)).await;
continue;
};
Message::Binary(encoded_message)
}
};
match write.send(message).await {
Err(tungstenite::Error::AlreadyClosed) => break,
Err(e) => {
tracing::error!("Error writing to socket: {:?}", e);
break;
}
Ok(()) => {}
};
}
sleep(Duration::from_millis(100)).await;
}
});
let ias = ia_sender.clone();
let es = event_sender.clone();
let scs = simconnect_sender.clone();
futures.spawn(async move {
loop {
let message = match read.next().await {
Some(data) => match data {
Ok(message) => message,
Err(e) => {
tracing::error!("{:?}", e);
let _ = es.send(Event::Disconnected);
sleep(Duration::from_millis(100)).await;
continue;
}
},
None => break,
};
if let Ok(data) = Packets::decode(&message.into_data()) {
if data == Packets::System(SystemPacket::Ping) {
let _ = ias.send(Packets::System(SystemPacket::Pong));
continue;
}
},
None => return,
};
let data = message.to_text();
tracing::debug!("{:?}", data);
// From Socket -> SimConnect
if let Packets::SimConnect(sim_connect_packet) = data {
tracing::info!("packet received: {:?}", &sim_connect_packet);
let _ = scs.send(sim_connect_packet);
}
}
sleep(Duration::from_millis(100)).await;
}
});
// Data from simconnect -> Socket
let mut socket_receiver = socket_receiver.resubscribe();
let ias = ia_sender.clone();
futures.spawn(async move {
loop {
if let Ok(message) = socket_receiver.try_recv() {
let _ = ias.send(message);
}
sleep(Duration::from_millis(100)).await;
}
});
tracing::info!("Connected");
let _ = event_sender.send(Event::Connected);
}
Event::Logout => {
if let Some(mut write) = writer {
write
.send(tungstenite::Message::Close(Some(CloseFrame {
code: CloseCode::Normal,
reason: Cow::from("User Logout"),
})))
.await?;
writer = None;
tracing::debug!("Disconnected");
event_sender.send(Event::Disconnected)?;
}
let _ = ia_sender.send(Packets::System(SystemPacket::Close {
reason: "User Logout".to_string(),
}));
sleep(Duration::from_millis(200)).await;
futures.abort_all();
}
Event::Quit => {
tracing::info!("Shutting down Client");
if let Some(mut write) = writer {
write
.send(tungstenite::Message::Close(Some(CloseFrame {
code: CloseCode::Normal,
reason: Cow::from("Application Shutdown"),
})))
.await?;
tracing::debug!("Disconnected");
}
let _ = ia_sender.send(Packets::System(SystemPacket::Close {
reason: "Quit".to_string(),
}));
sleep(Duration::from_millis(200)).await;
futures.abort_all();
break;
}
_ => {}

View File

@@ -18,7 +18,7 @@ use lock::Lock;
use oauth::{start_code_listener, start_code_to_token};
use pipe::Pipe;
use state_machine::Event;
use tokio::task::JoinSet;
use tokio::{sync::broadcast::channel, task::JoinSet};
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
@@ -44,8 +44,7 @@ async fn main() -> Result<(), anyhow::Error> {
init_logging()?;
// let (socket_sender, socket_receiver) = tokio::sync::broadcast::channel(1);
let (event_sender, event_receiver) = tokio::sync::broadcast::channel(10);
let (event_sender, event_receiver) = channel(10);
let args = Arguments::parse();
if handle_single_instance(&args).await? {
@@ -93,14 +92,21 @@ async fn main() -> Result<(), anyhow::Error> {
let receiver = event_receiver.resubscribe();
futures.spawn(start_code_to_token(c, pipe_receiver, sender, receiver));
// Prepare channels for socket <-> simconnect
let (simconnect_sender, __simconnect_receiver) = channel(10);
let (__socket_sender, socket_receiver) = channel(10);
// Start the websocket client
// The socket client will just sit there until TokenReceivedEvent comes in to authenticate with the socket server
// The server needs to not accept any messages until the authentication is verified
let c = config.clone();
let sender = event_sender.clone();
let receiver = event_receiver.resubscribe();
futures.spawn(client::start(sender, receiver));
// We need 2 way channels (2 channels, both with tx/rx) to send data from the socket to simconnect and back
futures.spawn(client::start(
c,
simconnect_sender,
socket_receiver,
sender,
receiver,
));
// Start the simconnect listener
// The simconnect sends data to the webscoket

View File

@@ -106,7 +106,7 @@ pub async fn start_code_to_token(
config.set_token(Some(token.clone()))?;
event_sender.send(Event::TokenReceived { token })?;
event_sender.send(Event::TokenReceived)?;
}
}
}

View File

@@ -1,8 +1,8 @@
pub struct Client {
pub struct SimConnect {
// whatever we need
}
impl Client {
impl SimConnect {
pub fn new() -> Self {
Self {
// websocket receiver
@@ -25,5 +25,5 @@ impl Client {
}
pub async fn start() -> Result<(), anyhow::Error> {
Client::new().run().await?
SimConnect::new().run().await?
}

View File

@@ -5,18 +5,15 @@ use tokio::{
time::sleep,
};
use crate::{
config::Config,
models::{CodeChallengeMethod, CodeVerifier},
oauth,
};
use crate::{config::Config, oauth};
#[derive(Debug, Clone, PartialEq)]
pub enum State {
Init,
AppStart,
Shutdown,
Authenticate,
Connect { token: String },
Connect,
WaitForSim,
InSim,
}
@@ -24,8 +21,8 @@ pub enum State {
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Ready,
StartAuthenticate, // should not be string
TokenReceived { token: String }, // AppStart and Authenticate can fire off TokenReceived to transition into Connect
StartAuthenticate, // should not be string
TokenReceived, // AppStart and Authenticate can fire off TokenReceived to transition into Connect
Connected, // Once connected to the socket, and properly authenticated, fire off Connected to transition to WaitForSim
Disconnected, // If for whatever reason we're disconnected from the backend, we need to transition back to Connect
SimConnected, // SimConnect is connected, we're in the world and ready to send data, transition to Running
@@ -42,16 +39,20 @@ impl State {
(_, Event::Logout) => State::AppStart,
(_, Event::StartAuthenticate) => Self::Authenticate, // Goto Authenticate
(_, Event::TokenReceived { token }) => State::Connect { token },
(_, Event::TokenReceived) => State::Connect,
(_, Event::Connected) => State::WaitForSim, // Goto WaitForSim
(_, Event::SimConnected) => todo!(), // Goto InSim
(_, Event::Disconnected) => {
sleep(Duration::from_secs(5)).await; // wait 5 seconds before reconnecting
tracing::info!("Attempting reconnect");
State::AppStart // Goto Connect
}
(_, Event::Disconnected) => State::AppStart, // Goto Connect
(_, Event::SimConnected) => State::InSim, // Goto InSim
(_, Event::SimDisconnected) => State::WaitForSim, // Goto WaitForSim
(_, Event::Quit) => todo!(), // All events can go into quit, to shutdown the application
(_, Event::Quit) => State::Shutdown, // All events can go into quit, to shutdown the application
}
}
@@ -59,10 +60,8 @@ impl State {
match self {
State::Init => Ok(()),
State::AppStart => {
if let Some(token) = config.token() {
signal.send(Event::TokenReceived {
token: token.to_string(),
})?;
if config.token().is_some() {
signal.send(Event::TokenReceived)?;
} else {
signal.send(Event::StartAuthenticate)?;
}
@@ -75,12 +74,10 @@ impl State {
Ok(())
}
State::Connect { .. } => Ok(()),
State::WaitForSim => {
tracing::info!("Waiting for sim!");
Ok(())
}
State::Connect => Ok(()),
State::WaitForSim => Ok(()),
State::InSim => Ok(()),
State::Shutdown => Ok(()),
}
}
}