Skip to content

Commit 9e69f65

Browse files
doublegateclaude
andcommitted
feat(discovery): implement Transport trait abstraction (Sprint 5.1)
Phase 5 Sprint 5.1: Transport Trait Abstraction (21 SP) Core Changes: - Add Transport trait with async send/receive operations - TransportError with comprehensive error types - TransportStats for metrics tracking (bytes, packets, errors) - Graceful shutdown with close() and is_closed() - Implement AsyncUdpTransport using Tokio - Full Transport trait implementation - 2MB socket buffers for high throughput - Atomic statistics tracking (thread-safe) - 6 comprehensive tests (all passing) - Add QuicTransport placeholder for Phase 6 - Proper error messages (not-yet-implemented) - 4 placeholder tests for structure - Implement TransportFactory pattern - Configuration-based transport creation - TransportType enum (UDP, QUIC) - TransportFactoryConfig with builder pattern - Helper methods: create_udp(), create_quic() - 8 comprehensive tests (all passing) Dependencies: - Add async-trait = "0.1" for async trait support Testing: - 8 new tests, 74 total transport tests - All quality gates passing (fmt, clippy, test) Progress: - Sprint 5.1 Complete: 21/123 SP (17% of Phase 5) - Next: Sprint 5.2 - DHT Core Implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cb114cd commit 9e69f65

File tree

7 files changed

+1104
-3
lines changed

7 files changed

+1104
-3
lines changed

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
**Phase 5 Sprint 5.1: Transport Trait Abstraction (2025-11-30):**
13+
- Implemented `Transport` trait for multi-backend transport abstraction
14+
- Async send/receive operations with proper error handling
15+
- Transport statistics tracking (bytes, packets, errors)
16+
- Graceful shutdown support with `close()` and `is_closed()`
17+
- Added `AsyncUdpTransport` - Tokio-based async UDP implementation
18+
- Implements `Transport` trait with full statistics
19+
- Optimized 2MB socket buffers for high-throughput
20+
- Comprehensive test coverage (6 tests, all passing)
21+
- Added `QuicTransport` placeholder for future QUIC support
22+
- Proper error messages indicating not-yet-implemented status
23+
- Placeholder tests for future implementation
24+
- Implemented `TransportFactory` pattern
25+
- Configuration-based transport creation
26+
- Support for UDP (implemented) and QUIC (placeholder)
27+
- Helper methods: `create_udp()`, `create_quic()`
28+
- Transport availability checking
29+
- Dependencies: Added `async-trait = "0.1"` for async trait support
30+
- **Test Results:** 8 new tests, all passing (74 total transport tests)
31+
- **Quality Gates:** All passing (fmt, clippy, test)
32+
- **Progress:** Phase 5 Sprint 5.1 Complete (21/123 SP, 17% of Phase 5)
33+
1034
---
1135

1236
## [0.4.8] - 2025-11-30

crates/wraith-transport/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ tokio = { workspace = true }
1515
socket2 = { workspace = true }
1616
thiserror = { workspace = true }
1717
tracing = { workspace = true }
18+
async-trait = "0.1"
1819
crossbeam-channel = "0.5"
1920
num_cpus = "1.16"
2021
libc = "0.2"
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
//! Transport factory for creating transport instances.
2+
//!
3+
//! This module provides a factory pattern for creating different transport
4+
//! implementations based on configuration.
5+
6+
use crate::quic::QuicTransport;
7+
use crate::transport::{Transport, TransportResult};
8+
use crate::udp_async::AsyncUdpTransport;
9+
use std::net::SocketAddr;
10+
use std::sync::Arc;
11+
12+
/// Transport type selection.
13+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14+
pub enum TransportType {
15+
/// UDP transport (always available)
16+
#[default]
17+
Udp,
18+
/// QUIC transport (placeholder, not yet implemented)
19+
Quic,
20+
}
21+
22+
/// Configuration for creating a transport.
23+
#[derive(Debug, Clone)]
24+
pub struct TransportFactoryConfig {
25+
/// Type of transport to create
26+
pub transport_type: TransportType,
27+
/// Local address to bind to
28+
pub bind_addr: SocketAddr,
29+
/// Receive buffer size (bytes)
30+
pub recv_buffer_size: Option<usize>,
31+
/// Send buffer size (bytes)
32+
pub send_buffer_size: Option<usize>,
33+
}
34+
35+
impl TransportFactoryConfig {
36+
/// Create a new transport configuration.
37+
///
38+
/// # Arguments
39+
/// * `transport_type` - The type of transport to create
40+
/// * `bind_addr` - The local address to bind to
41+
///
42+
/// # Examples
43+
/// ```no_run
44+
/// use wraith_transport::factory::{TransportFactoryConfig, TransportType};
45+
/// use std::net::SocketAddr;
46+
///
47+
/// let addr: SocketAddr = "127.0.0.1:40000".parse().unwrap();
48+
/// let config = TransportFactoryConfig::new(TransportType::Udp, addr);
49+
/// ```
50+
#[must_use]
51+
pub fn new(transport_type: TransportType, bind_addr: SocketAddr) -> Self {
52+
Self {
53+
transport_type,
54+
bind_addr,
55+
recv_buffer_size: None,
56+
send_buffer_size: None,
57+
}
58+
}
59+
60+
/// Create a UDP transport configuration.
61+
///
62+
/// # Arguments
63+
/// * `bind_addr` - The local address to bind to
64+
///
65+
/// # Examples
66+
/// ```no_run
67+
/// use wraith_transport::factory::TransportFactoryConfig;
68+
/// use std::net::SocketAddr;
69+
///
70+
/// let addr: SocketAddr = "127.0.0.1:40000".parse().unwrap();
71+
/// let config = TransportFactoryConfig::udp(addr);
72+
/// ```
73+
#[must_use]
74+
pub fn udp(bind_addr: SocketAddr) -> Self {
75+
Self::new(TransportType::Udp, bind_addr)
76+
}
77+
78+
/// Create a QUIC transport configuration.
79+
///
80+
/// # Arguments
81+
/// * `bind_addr` - The local address to bind to
82+
///
83+
/// # Examples
84+
/// ```no_run
85+
/// use wraith_transport::factory::TransportFactoryConfig;
86+
/// use std::net::SocketAddr;
87+
///
88+
/// let addr: SocketAddr = "127.0.0.1:40000".parse().unwrap();
89+
/// let config = TransportFactoryConfig::quic(addr);
90+
/// ```
91+
#[must_use]
92+
pub fn quic(bind_addr: SocketAddr) -> Self {
93+
Self::new(TransportType::Quic, bind_addr)
94+
}
95+
96+
/// Set custom buffer sizes.
97+
///
98+
/// # Arguments
99+
/// * `recv_size` - Receive buffer size in bytes
100+
/// * `send_size` - Send buffer size in bytes
101+
#[must_use]
102+
pub fn with_buffer_sizes(mut self, recv_size: usize, send_size: usize) -> Self {
103+
self.recv_buffer_size = Some(recv_size);
104+
self.send_buffer_size = Some(send_size);
105+
self
106+
}
107+
}
108+
109+
impl Default for TransportFactoryConfig {
110+
fn default() -> Self {
111+
Self {
112+
transport_type: TransportType::Udp,
113+
bind_addr: "0.0.0.0:0".parse().unwrap(),
114+
recv_buffer_size: None,
115+
send_buffer_size: None,
116+
}
117+
}
118+
}
119+
120+
/// Factory for creating transport instances.
121+
///
122+
/// This factory provides a unified interface for creating different transport
123+
/// implementations based on configuration.
124+
///
125+
/// # Examples
126+
///
127+
/// ```no_run
128+
/// use wraith_transport::factory::{TransportFactory, TransportFactoryConfig, TransportType};
129+
/// use std::net::SocketAddr;
130+
///
131+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
132+
/// let addr: SocketAddr = "127.0.0.1:40000".parse()?;
133+
/// let config = TransportFactoryConfig::udp(addr);
134+
///
135+
/// let transport = TransportFactory::create(config).await?;
136+
/// println!("Created transport on {}", transport.local_addr()?);
137+
/// # Ok(())
138+
/// # }
139+
/// ```
140+
pub struct TransportFactory;
141+
142+
impl TransportFactory {
143+
/// Create a transport based on the provided configuration.
144+
///
145+
/// # Arguments
146+
/// * `config` - Transport configuration
147+
///
148+
/// # Returns
149+
/// A boxed transport implementing the `Transport` trait
150+
///
151+
/// # Errors
152+
/// Returns `TransportError` if transport creation fails
153+
///
154+
/// # Examples
155+
/// ```no_run
156+
/// use wraith_transport::factory::{TransportFactory, TransportFactoryConfig};
157+
/// use std::net::SocketAddr;
158+
///
159+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
160+
/// let addr: SocketAddr = "127.0.0.1:0".parse()?;
161+
/// let config = TransportFactoryConfig::udp(addr);
162+
/// let transport = TransportFactory::create(config).await?;
163+
/// # Ok(())
164+
/// # }
165+
/// ```
166+
pub async fn create(config: TransportFactoryConfig) -> TransportResult<Arc<dyn Transport>> {
167+
match config.transport_type {
168+
TransportType::Udp => {
169+
let transport = AsyncUdpTransport::bind(config.bind_addr).await?;
170+
Ok(Arc::new(transport))
171+
}
172+
TransportType::Quic => {
173+
// QUIC not yet implemented - will return error
174+
let transport = QuicTransport::bind(config.bind_addr).await?;
175+
Ok(Arc::new(transport))
176+
}
177+
}
178+
}
179+
180+
/// Create a UDP transport with default settings.
181+
///
182+
/// # Arguments
183+
/// * `bind_addr` - The local address to bind to
184+
///
185+
/// # Errors
186+
/// Returns `TransportError` if binding fails
187+
///
188+
/// # Examples
189+
/// ```no_run
190+
/// use wraith_transport::factory::TransportFactory;
191+
/// use std::net::SocketAddr;
192+
///
193+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
194+
/// let addr: SocketAddr = "127.0.0.1:0".parse()?;
195+
/// let transport = TransportFactory::create_udp(addr).await?;
196+
/// # Ok(())
197+
/// # }
198+
/// ```
199+
pub async fn create_udp(bind_addr: SocketAddr) -> TransportResult<Arc<dyn Transport>> {
200+
let config = TransportFactoryConfig::udp(bind_addr);
201+
Self::create(config).await
202+
}
203+
204+
/// Create a QUIC transport with default settings (not yet implemented).
205+
///
206+
/// # Arguments
207+
/// * `bind_addr` - The local address to bind to
208+
///
209+
/// # Errors
210+
/// Currently always returns `TransportError::Other` as QUIC is not implemented
211+
///
212+
/// # Examples
213+
/// ```no_run
214+
/// use wraith_transport::factory::TransportFactory;
215+
/// use std::net::SocketAddr;
216+
///
217+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
218+
/// let addr: SocketAddr = "127.0.0.1:0".parse()?;
219+
/// let result = TransportFactory::create_quic(addr).await;
220+
/// assert!(result.is_err()); // QUIC not yet implemented
221+
/// # Ok(())
222+
/// # }
223+
/// ```
224+
pub async fn create_quic(bind_addr: SocketAddr) -> TransportResult<Arc<dyn Transport>> {
225+
let config = TransportFactoryConfig::quic(bind_addr);
226+
Self::create(config).await
227+
}
228+
229+
/// Get list of available transport types.
230+
///
231+
/// # Returns
232+
/// Vector of transport types that can be created
233+
#[must_use]
234+
pub fn available_transports() -> Vec<TransportType> {
235+
vec![
236+
TransportType::Udp,
237+
// QUIC is technically "available" but will return error when created
238+
TransportType::Quic,
239+
]
240+
}
241+
242+
/// Check if a transport type is fully implemented.
243+
///
244+
/// # Arguments
245+
/// * `transport_type` - The transport type to check
246+
///
247+
/// # Returns
248+
/// `true` if the transport is fully implemented and functional
249+
#[must_use]
250+
pub fn is_implemented(transport_type: TransportType) -> bool {
251+
matches!(transport_type, TransportType::Udp)
252+
}
253+
}
254+
255+
#[cfg(test)]
256+
mod tests {
257+
use super::*;
258+
259+
#[tokio::test]
260+
async fn test_factory_create_udp() {
261+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
262+
let config = TransportFactoryConfig::udp(addr);
263+
264+
let transport = TransportFactory::create(config).await.unwrap();
265+
let bound_addr = transport.local_addr().unwrap();
266+
assert!(bound_addr.is_ipv4());
267+
assert_ne!(bound_addr.port(), 0);
268+
}
269+
270+
#[tokio::test]
271+
async fn test_factory_create_udp_shorthand() {
272+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
273+
let transport = TransportFactory::create_udp(addr).await.unwrap();
274+
assert!(transport.local_addr().unwrap().is_ipv4());
275+
}
276+
277+
#[tokio::test]
278+
async fn test_factory_create_quic_not_implemented() {
279+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
280+
let result = TransportFactory::create_quic(addr).await;
281+
assert!(result.is_err());
282+
}
283+
284+
#[tokio::test]
285+
async fn test_factory_available_transports() {
286+
let transports = TransportFactory::available_transports();
287+
assert!(transports.contains(&TransportType::Udp));
288+
assert!(transports.contains(&TransportType::Quic));
289+
}
290+
291+
#[tokio::test]
292+
async fn test_factory_is_implemented() {
293+
assert!(TransportFactory::is_implemented(TransportType::Udp));
294+
assert!(!TransportFactory::is_implemented(TransportType::Quic));
295+
}
296+
297+
#[tokio::test]
298+
async fn test_config_default() {
299+
let config = TransportFactoryConfig::default();
300+
assert_eq!(config.transport_type, TransportType::Udp);
301+
assert!(config.recv_buffer_size.is_none());
302+
assert!(config.send_buffer_size.is_none());
303+
}
304+
305+
#[tokio::test]
306+
async fn test_config_with_buffer_sizes() {
307+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
308+
let config = TransportFactoryConfig::udp(addr).with_buffer_sizes(1024 * 1024, 512 * 1024);
309+
310+
assert_eq!(config.recv_buffer_size, Some(1024 * 1024));
311+
assert_eq!(config.send_buffer_size, Some(512 * 1024));
312+
}
313+
314+
#[tokio::test]
315+
async fn test_factory_udp_send_recv() {
316+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
317+
let server = TransportFactory::create_udp(addr).await.unwrap();
318+
let server_addr = server.local_addr().unwrap();
319+
320+
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
321+
let client = TransportFactory::create_udp(addr).await.unwrap();
322+
323+
// Send data
324+
client.send_to(b"Factory test", server_addr).await.unwrap();
325+
326+
// Receive data
327+
let mut buf = vec![0u8; 1500];
328+
let (size, from) = tokio::time::timeout(
329+
std::time::Duration::from_secs(1),
330+
server.recv_from(&mut buf),
331+
)
332+
.await
333+
.expect("Timeout")
334+
.unwrap();
335+
336+
assert_eq!(size, 12);
337+
assert_eq!(&buf[..size], b"Factory test");
338+
assert_eq!(from, client.local_addr().unwrap());
339+
}
340+
}

0 commit comments

Comments
 (0)