Skip to content

Commit 99f546b

Browse files
committed
Update 0.9.X
1 parent 0ba7052 commit 99f546b

File tree

10 files changed

+448
-66
lines changed

10 files changed

+448
-66
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added `#[tracing::instrument]` attributes to key async functions for enhanced contextual logging
1313
- Created logging configuration module with flexible log level control via environment variables
1414
- Implemented concurrent-safe logging infrastructure for better debugging and observability
15+
- Added configurable connection timeouts for all network operations
16+
- Implemented heartbeat mechanism with keep-alive ping/pong messages
17+
- Added automatic detection and cleanup of dead connections
18+
- Implemented client-side timeout handling with automatic reconnection capability
1519

1620
### Changed
1721
- Replaced all `println!` and `eprintln!` calls with appropriate structured logging macros (`debug!`, `info!`, `warn!`, `error!`)
1822
- Enhanced logging detail with structured fields for better filtering and analysis
1923
- Improved error logging with contextual information across all modules
2024
- Updated documentation examples to use structured logging
25+
- Modified connection handling to use timeout wrappers for all I/O operations
26+
- Enhanced client and server implementations to support configurable timeouts
27+
- Updated network transport layer to detect and report connection timeouts
28+
- Refactored message processing loops to handle keep-alive messages transparently
2129

2230
### Fixed
2331
- Removed deprecated legacy handshake functions (`derive_shared_key`, `verify_server_ack`, `server_handshake_response`)
2432
- Removed deprecated message types (`HandshakeInit`, `HandshakeAck`)
2533
- Removed references to deprecated code from dispatcher, client, and daemon
2634
- Updated API documentation to reflect removal of legacy handshake functionality
35+
- Fixed double error unwrapping in timeout handlers for client and server code
36+
- Corrected handshake state management in parallel test executions
37+
- Fixed client send_and_wait functionality to properly handle timeout errors
38+
- Added proper cleanup of connection resources when timeout or keep-alive failures occur
2739

2840
### Security
2941
- Enhanced security by removing insecure legacy handshake implementation

Cargo.lock

Lines changed: 99 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,7 @@ rustls = { version = "0.21", features = ["dangerous_configuration"] }
6767
tokio-rustls = "0.24"
6868
rcgen = "0.11"
6969
rustls-pemfile = "1.0"
70-
rustls-native-certs = "0.6"
70+
rustls-native-certs = "0.6"
71+
72+
[dev-dependencies]
73+
serial_test = "2.0"

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ pub enum ProtocolError {
101101

102102
#[error("Timeout occurred")]
103103
Timeout,
104+
105+
#[error("Connection timed out (no activity)")]
106+
ConnectionTimeout,
104107

105108
#[error("Custom error: {0}")]
106109
Custom(String),

src/protocol/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub mod message;
3434
pub mod handshake;
3535
pub mod heartbeat;
3636
pub mod dispatcher;
37+
pub mod keepalive;
3738

3839
#[cfg(test)]
3940
mod tests;

src/service/client.rs

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
1-
//use tokio_util::codec::Framed;
2-
//use tokio::net::TcpStream;
31
use futures::{SinkExt, StreamExt};
2+
use tokio::time;
3+
use tracing::{debug, info, warn, instrument};
44

55
use crate::core::packet::Packet;
6-
//use crate::core::codec::PacketCodec;
76
use crate::protocol::message::Message;
87
// Import secure handshake functions
98
use crate::protocol::handshake::{client_secure_handshake_init, client_secure_handshake_verify, client_derive_session_key};
9+
use crate::protocol::heartbeat::{build_ping, is_pong};
10+
use crate::protocol::keepalive::KeepAliveManager;
1011
use crate::service::secure::SecureConnection;
1112
use crate::transport::remote;
1213
use crate::error::{Result, ProtocolError};
14+
use crate::utils::timeout::{with_timeout_error, DEFAULT_TIMEOUT, HANDSHAKE_TIMEOUT};
1315

1416
/// High-level protocol client with post-handshake encryption
1517
pub struct Client {
1618
conn: SecureConnection,
19+
keep_alive: KeepAliveManager,
1720
}
1821

1922
impl Client {
20-
/// Connect and perform secure handshake
23+
/// Connect and perform secure handshake with timeout
24+
#[instrument(skip(addr), fields(address = %addr))]
2125
pub async fn connect(addr: &str) -> Result<Self> {
22-
let mut framed = remote::connect(addr).await?;
26+
// Connect with timeout
27+
let mut framed = with_timeout_error(
28+
async {
29+
remote::connect(addr).await
30+
},
31+
DEFAULT_TIMEOUT
32+
).await?;
2333

2434
// --- Legacy Handshake Support ---
2535
// Commented out legacy code for reference
@@ -37,9 +47,17 @@ impl Client {
3747
payload: init_bytes,
3848
}).await?;
3949

40-
// Step 2: Receive server response
41-
let packet = framed.next().await.ok_or(ProtocolError::Timeout)??;
42-
let response: Message = bincode::deserialize(&packet.payload)?;
50+
// Step 2: Receive server response with timeout
51+
let response = with_timeout_error(
52+
async {
53+
let packet = framed.next().await
54+
.ok_or(ProtocolError::ConnectionClosed)?
55+
.map_err(|e| ProtocolError::TransportError(e.to_string()))?;
56+
bincode::deserialize::<Message>(&packet.payload)
57+
.map_err(|e| ProtocolError::DeserializeError(e.to_string()))
58+
},
59+
HANDSHAKE_TIMEOUT
60+
).await?;
4361

4462
// Step 3: Verify server response and send confirmation
4563
// Extract data from server response
@@ -62,23 +80,99 @@ impl Client {
6280
// Step 4: Derive shared session key
6381
let key = client_derive_session_key()?;
6482
let conn = SecureConnection::new(framed, key);
83+
let keep_alive = KeepAliveManager::new();
6584

66-
Ok(Self { conn })
85+
info!("Connection established successfully");
86+
Ok(Self { conn, keep_alive })
6787
}
6888

6989
/// Securely send a message
90+
#[instrument(skip(self, msg))]
7091
pub async fn send(&mut self, msg: Message) -> Result<()> {
71-
self.conn.secure_send(msg).await
92+
let result = self.conn.secure_send(msg).await;
93+
if result.is_ok() {
94+
self.keep_alive.update_send();
95+
}
96+
result
7297
}
7398

7499
/// Securely receive a message
100+
#[instrument(skip(self))]
75101
pub async fn recv(&mut self) -> Result<Message> {
76-
self.conn.secure_recv().await
102+
let result = self.conn.secure_recv().await;
103+
if result.is_ok() {
104+
self.keep_alive.update_recv();
105+
}
106+
result
107+
}
108+
109+
/// Send a keep-alive ping to the server
110+
#[instrument(skip(self))]
111+
pub async fn send_keepalive(&mut self) -> Result<()> {
112+
debug!("Sending keep-alive ping");
113+
let ping = build_ping();
114+
self.send(ping).await
115+
}
116+
117+
/// Wait for messages with keep-alive handling
118+
#[instrument(skip(self))]
119+
pub async fn recv_with_keepalive(&mut self, timeout_duration: std::time::Duration) -> Result<Message> {
120+
let mut ping_interval = time::interval(self.keep_alive.ping_interval());
121+
122+
let timeout = time::sleep(timeout_duration);
123+
tokio::pin!(timeout);
124+
125+
loop {
126+
tokio::select! {
127+
// Check if we need to send a ping
128+
_ = ping_interval.tick() => {
129+
if self.keep_alive.should_ping() {
130+
self.send_keepalive().await?;
131+
}
132+
133+
// Check if connection is dead
134+
if self.keep_alive.is_connection_dead() {
135+
warn!(dead_seconds = ?self.keep_alive.time_since_last_recv().as_secs(),
136+
"Connection appears dead");
137+
return Err(ProtocolError::ConnectionTimeout);
138+
}
139+
}
140+
141+
// Try to receive a message
142+
recv_result = self.conn.secure_recv::<Message>() => {
143+
match recv_result {
144+
Ok(msg) => {
145+
self.keep_alive.update_recv();
146+
147+
// Filter out pong messages, return everything else
148+
if !is_pong(&msg) {
149+
return Ok(msg);
150+
} else {
151+
debug!("Received pong response");
152+
// Continue waiting for non-pong messages
153+
}
154+
}
155+
Err(ProtocolError::Timeout) => {
156+
// Timeout is expected, just continue the loop
157+
continue;
158+
}
159+
Err(e) => return Err(e),
160+
}
161+
}
162+
163+
// User-provided timeout
164+
_ = &mut timeout => {
165+
return Err(ProtocolError::Timeout);
166+
}
167+
}
168+
}
77169
}
78170

79-
/// Send a message and wait for a response
171+
/// Send a message and wait for a response with keep-alive handling
172+
#[instrument(skip(self, msg))]
80173
pub async fn send_and_wait(&mut self, msg: Message) -> Result<Message> {
81174
self.send(msg).await?;
82-
self.recv().await
175+
// Use a reasonably long timeout for waiting for a response
176+
self.recv_with_keepalive(std::time::Duration::from_secs(30)).await
83177
}
84178
}

0 commit comments

Comments
 (0)