//! Example for usage of the Chat Server Protocol state machine, doing a real handshake with the //! chat server and an exemplary payload flow loop. #![expect(unused_crate_dependencies, reason = "Example triggered false positive")] #![expect(
clippy::integer_division_remainder_used,
reason = "Some internal of tokio::select triggers this"
)]
use core::time::Duration; use std::io;
use anyhow::bail; use clap::Parser; use libthreema::{
cli::{FullIdentityConfig, FullIdentityConfigOptions},
csp::{
CspProtocol, CspProtocolContext, CspStateUpdate,
payload::{EchoPayload, IncomingPayload, OutgoingFrame, OutgoingPayload},
},
https::cli::https_client_builder,
utils::logging::init_stderr_logging,
}; use tokio::{
io::{AsyncReadExt as _, AsyncWriteExt as _},
net::TcpStream,
signal,
sync::mpsc,
time::{self, Instant},
}; use tracing::{Level, debug, error, info, trace, warn};
/// Payload queues for the main process struct PayloadQueuesForCspPingPong {
incoming: mpsc::Receiver<IncomingPayload>,
outgoing: mpsc::Sender<OutgoingPayload>,
}
/// Payload queues for the protocol flow runner struct PayloadQueuesForCsp {
incoming: mpsc::Sender<IncomingPayload>,
outgoing: mpsc::Receiver<OutgoingPayload>,
}
struct CspProtocolRunner { /// The TCP stream
stream: TcpStream,
/// An instance of the [`CspProtocol`] state machine
protocol: CspProtocol,
} impl CspProtocolRunner { /// Initiate a CSP protocol connection and hand out the initial `client_hello` message #[tracing::instrument(skip_all)] asyncfn new(
server_address: Vec<(String, u16)>,
context: CspProtocolContext,
) -> anyhow::Result<(Self, OutgoingFrame)> { // Connect via TCP
debug!(?server_address, "Establishing TCP connection to chat server",); let tcp_stream = TcpStream::connect(
server_address
.first()
.expect("CSP config should have at least one address"),
)
.await?;
// Create the protocol let (csp_protocol, client_hello) = CspProtocol::new(context);
Ok(( Self {
stream: tcp_stream,
protocol: csp_protocol,
},
client_hello,
))
}
/// Do the handshake with the chat server by exchanging the following messages: /// /// ```txt /// C -- client-hello -> S /// C <- server-hello -- S /// C ---- login ---- -> S /// C <-- login-ack ---- S /// ``` #[tracing::instrument(skip_all)] asyncfn run_handshake_flow(&mutself, client_hello: OutgoingFrame) -> anyhow::Result<()> { // Send the client hello
debug!(length = client_hello.0.len(), "Sending client hello"); self.send(&client_hello.0).await?;
// Handshake by polling the CSP state for iteration in1_usize.. {
trace!("Iteration #{iteration}");
// Receive required bytes and add them let bytes = self.receive_required().await?; self.protocol.add_chunks(&[&bytes])?;
// We do not expect an incoming payload at this stage iflet Some(incoming_payload) = instruction.incoming_payload { let message = "Unexpected incoming payload during handshake";
error!(?incoming_payload, message);
bail!(message)
}
// Check if we've completed the handshake iflet Some(CspStateUpdate::PostHandshake(login_ack_data)) = instruction.state_update {
info!(?login_ack_data, "Handshake complete"); break;
}
}
Ok(())
}
/// Run the payload exchange flow until stopped. #[tracing::instrument(skip_all)] asyncfn run_payload_flow(&mutself, mut queues: PayloadQueuesForCsp) -> anyhow::Result<()> { letmut read_buffer = [0_u8; 8192];
for iteration in1_usize.. {
trace!("Payload flow iteration #{iteration}");
// Poll for any pending instruction letmut instruction = self.protocol.poll()?; if instruction.is_none() { // No pending instruction left, wait for more input
instruction = tokio::select! { // Forward any incoming chunks from the TCP stream
_ = self.stream.readable() => { let length = self.try_receive(&mut read_buffer)?;
// Add chunks (poll in the next iteration) self.protocol
.add_chunks(&[read_buffer.get(..length)
.expect("Amount of read bytes should be available")])?;
None
},
// We do not expect any state updates at this stage iflet Some(state_update) = instruction.state_update { let message = "Unexpected state update after handshake";
error!(?state_update, message);
bail!(message)
}
/// Shut down the TCP connection #[tracing::instrument(skip_all)] asyncfn shutdown(&mutself) -> anyhow::Result<()> {
info!("Shutting down TCP connection");
Ok(self.stream.shutdown().await?)
}
/// Send bytes to the server over the TCP connection #[tracing::instrument(skip_all, fields(bytes_length = bytes.len()))] asyncfn send(&mutself, bytes: &[u8]) -> anyhow::Result<()> {
trace!(length = bytes.len(), "Sending bytes"); self.stream.write_all(bytes).await?;
Ok(())
}
#[tracing::instrument(skip_all)] asyncfn receive_required(&mutself) -> anyhow::Result<Vec<u8>> { // Get the minimum amount of bytes we'll need to receive let length = self.protocol.next_required_length()?; letmut buffer = vec![0; length];
trace!(?length, "Reading bytes");
// If there is nothing to read, return immediately if length == 0 { return Ok(buffer);
}
// Read the exact number of bytes required let _ = self.stream.read_exact(&mut buffer).await?;
// Read more if available matchself.stream.try_read_buf(&mut buffer) {
Ok(0) => { // Remote shut down our reading end. But we still need to process the previously // read bytes.
warn!("TCP reading end closed");
},
Ok(length) => {
trace!(length, "Got additional bytes");
},
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
trace!("No additional bytes available");
},
Err(error) => { return Err(error.into());
},
}
debug!(length = buffer.len(), "Received bytes");
Ok(buffer)
}
#[tracing::instrument(skip_all)] fn try_receive(&mutself, buffer: &mut [u8]) -> anyhow::Result<usize> { matchself.stream.try_read(buffer) {
Ok(0) => { // Remote shut down our reading end gracefully. // // IMPORTANT: An implementation needs to ensure that it stops gracefully by processing any // remaining payloads prior to stopping the protocol. This example implementation ensures this // by handling all pending instructions prior to polling for more data. The only case we bail // is therefore when our instruction queue is already dry.
bail!("TCP reading end closed")
},
Ok(length) => {
debug!(length, "Received bytes");
Ok(length)
},
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
trace!("No bytes to receive");
Ok(0)
},
Err(error) => Err(error.into()),
}
}
}
#[tracing::instrument(skip_all)] asyncfn run_ping_pong_flow(mut queues: PayloadQueuesForCspPingPong) -> anyhow::Result<()> { // Create the echo timer that will trigger an outgoing payload every 10s letmut echo_timer = time::interval_at(
Instant::now()
.checked_add(Duration::from_secs(10))
.expect("Oops, apocalypse in 10s"),
Duration::from_secs(10),
);
// Enter ping-pong flow loop loop {
tokio::select! { // Send echo-request when the timer fires
_ = echo_timer.tick() => { let echo_request = OutgoingPayload::EchoRequest(
EchoPayload("ping".as_bytes().to_owned()));
info!(?echo_request, "Sending echo request");
queues.outgoing.send(echo_request).await?;
},
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.