Milestone 1
This commit is contained in:
14
client_node/Cargo.toml
Normal file
14
client_node/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "client_node"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
core_protocol = { version = "0.1.0", path = "../core_protocol" }
|
||||
futures = "0.3.32"
|
||||
tokio = { version = "1.52.1", features = ["rt-multi-thread", "net", "macros"] }
|
||||
tokio-serde = { version = "0.9.0", features = ["bincode"] }
|
||||
tokio-util = { version = "0.7.18", features = ["codec"] }
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = "0.3.23"
|
||||
24
client_node/src/main.rs
Normal file
24
client_node/src/main.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
//! Client Node entry point.
|
||||
//!
|
||||
//! This module initializes the desktop client application, sets up the Tokio
|
||||
//! background thread for networking, and eventually binds to the UI framework.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::all, clippy::pedantic)]
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
mod network;
|
||||
|
||||
use tracing::{error, info};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
info!("Starting client node...");
|
||||
|
||||
if let Err(e) = network::control::connect_and_auth("TestUser").await {
|
||||
error!("Connection error: {:?}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
72
client_node/src/network/control.rs
Normal file
72
client_node/src/network/control.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
//! Reliable TCP connectivity logic.
|
||||
//!
|
||||
//! Implements the client-side TCP connection handling, including framing,
|
||||
//! serialization, and the initial authentication handshake.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use core_protocol::constants;
|
||||
use core_protocol::tcp_events::TcpEvent;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_serde::formats::Bincode;
|
||||
use tokio_serde::SymmetricallyFramed;
|
||||
use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// A type alias for the symmetrically framed TCP stream to simplify syntax.
|
||||
type FramedStream = SymmetricallyFramed<
|
||||
Framed<TcpStream, LengthDelimitedCodec>,
|
||||
TcpEvent,
|
||||
Bincode<TcpEvent, TcpEvent>,
|
||||
>;
|
||||
|
||||
/// Connects to the server and performs the initial authentication handshake.
|
||||
///
|
||||
/// This establishes a length-delimited TCP connection to prevent fragmentation,
|
||||
/// sends an `AuthRequest` with the given username, and awaits the `AuthResponse`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `username` - The requested display name for this client.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an `anyhow::Result` if the TCP socket cannot connect, if the serialization/
|
||||
/// deserialization fails, or if the server closes the connection prematurely.
|
||||
pub async fn connect_and_auth(username: &str) -> Result<()> {
|
||||
let addr = format!("127.0.0.1:{}", constants::TCP_PORT);
|
||||
info!("Connecting to server at {}...", addr);
|
||||
|
||||
let stream = TcpStream::connect(&addr).await.context("Failed to connect to server")?;
|
||||
info!("Connected!");
|
||||
|
||||
// Construct the codec pipeline exactly mirroring the server's configuration
|
||||
// to ensure reliable packet framing.
|
||||
let length_delimited = Framed::new(stream, LengthDelimitedCodec::new());
|
||||
let mut framed: FramedStream = SymmetricallyFramed::new(
|
||||
length_delimited,
|
||||
Bincode::<TcpEvent, TcpEvent>::default(),
|
||||
);
|
||||
|
||||
let auth_req = TcpEvent::AuthRequest {
|
||||
username: username.to_string(),
|
||||
};
|
||||
|
||||
framed.send(auth_req).await.context("Failed to send AuthRequest")?;
|
||||
info!("Sent AuthRequest for user: {}", username);
|
||||
|
||||
if let Some(response) = framed.next().await {
|
||||
let response = response.context("Failed to deserialize response")?;
|
||||
|
||||
match response {
|
||||
TcpEvent::AuthResponse { session_token } => {
|
||||
info!("Successfully authenticated! Session token: {}", session_token);
|
||||
}
|
||||
_ => {
|
||||
warn!("Received unexpected event instead of AuthResponse");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Connection closed before receiving AuthResponse");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
6
client_node/src/network/mod.rs
Normal file
6
client_node/src/network/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
//! Internet connectivity modules.
|
||||
//!
|
||||
//! This module isolates all remote networking logic from the UI and audio engines.
|
||||
//! It exposes the necessary interfaces to manage TCP control lanes and UDP streams.
|
||||
|
||||
pub mod control;
|
||||
Reference in New Issue
Block a user