Skip to content

Commit 0f55d85

Browse files
doublegateclaude
andcommitted
fix(tech-debt): remediate v1.6.1 technical debt items
Tech Debt Remediation: - TD-001: Add DNS-based STUN resolution with hickory-resolver - New dns.rs module with StunDnsResolver, caching (5-min TTL) - Fallback to hardcoded IPs when DNS fails - TD-006: Fix iOS UniFFI unwrap() calls with proper error handling - Replaced all unwrap() calls with proper Result handling - Added FFI-safe error types for Swift interop - Android client: Enhanced error handling and logging Documentation Updates: - README.md: Updated metrics (1,617 Rust tests, 60,300 LOC) - CHANGELOG.md: Added v1.6.1 release notes with tech debt items - README_Protocol-DEV.md: Updated test counts and metrics - README_Clients-DEV.md: Updated client status with TD-006 fix Quality: - All 1,617 Rust tests pass (16 ignored) - Zero clippy warnings - Code formatted with cargo fmt Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 364fa86 commit 0f55d85

File tree

20 files changed

+656
-129
lines changed

20 files changed

+656
-129
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,34 @@ No unreleased changes yet.
1919
- 62 tests covering all UI components (TransferList, NewTransferDialog, SessionPanel, SettingsPanel)
2020
- Testing Library integration for React component testing
2121
- 100% pass rate on frontend tests
22+
- **DNS-based STUN Resolution (TD-001):**
23+
- New `dns.rs` module with `StunDnsResolver` in wraith-discovery
24+
- DNS caching with 5-minute TTL for efficient resolution
25+
- Fallback to hardcoded IP addresses when DNS fails
26+
- Integration with hickory-resolver for async DNS queries
2227

2328
### Changed
2429
- **Dependencies:**
2530
- Updated rusqlite from 0.32 to 0.38 (SQLCipher 4.10.0, SQLite 3.51.1, wasm32 support)
2631
- Updated markdownlint-cli2-action from 21 to 22 (markdownlint v0.40.0)
32+
- Added hickory-resolver for DNS-based STUN server resolution
2733
- **CI/CD GitHub Actions:**
2834
- Updated actions/upload-artifact from 5 to 6 (PR #41)
2935
- Updated actions/download-artifact from 6 to 7 (PR #40)
3036
- Updated actions/cache from 4 to 5 (PR #39)
3137

38+
### Fixed
39+
- **Tech Debt Remediation:**
40+
- TD-001: DNS-based STUN resolution with hickory-resolver (caching, fallback)
41+
- TD-006: iOS UniFFI unwrap() calls replaced with proper error handling
42+
- Verified TD-002 through TD-005, TD-012, TD-013 already fixed in previous releases
43+
3244
### Documentation
3345
- **Technical Debt Documentation:**
3446
- Comprehensive technical debt analysis for AF_XDP implementation
3547
- NAT signaling protocol enhancement documentation
3648
- Audit and documentation of all 23 ignored tests with justifications
37-
- Updated project metrics with accurate test counts (1,303 Rust + 62 frontend)
49+
- Updated project metrics with accurate test counts (1,617 Rust + 62 frontend)
3850

3951
---
4052

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ A decentralized secure file transfer protocol optimized for high-throughput, low
2121

2222
WRAITH Protocol is production-ready with desktop, mobile, and messaging applications. Phase 16 delivers Android and iOS mobile clients with native UIs (Kotlin/Jetpack Compose, Swift/SwiftUI), plus WRAITH-Chat, a secure E2EE messaging application with Signal Protocol Double Ratchet encryption, SQLCipher encrypted storage, and React 18 frontend. v1.6.0 adds mobile platform support, end-to-end encrypted messaging, and comprehensive client ecosystem expansion.
2323

24-
**Project Metrics (2025-01-20):**
25-
- **Code Volume:** ~57,400 lines of Rust code across protocol crates + ~4,000 lines in client applications (Kotlin/Swift/TypeScript)
26-
- **Test Coverage:** 1,303+ Rust tests + 62 frontend tests = 1,365+ total tests - 100% pass rate
24+
**Project Metrics (2026-01-20):**
25+
- **Code Volume:** ~60,300 lines of Rust code across protocol crates + ~4,000 lines in client applications (Kotlin/Swift/TypeScript)
26+
- **Test Coverage:** 1,617+ Rust tests + 62 frontend tests = 1,679+ total tests - 100% pass rate
2727
- **Documentation:** 111 markdown files, ~63,000+ lines of comprehensive documentation
2828
- **Dependencies:** 286 audited packages (zero vulnerabilities via cargo-audit)
2929
- **Security:** Grade A+ (EXCELLENT), zero vulnerabilities, comprehensive DPI evasion validation
@@ -287,7 +287,7 @@ WRAITH-Protocol/
287287
| **Integration Tests** | 323 | Cross-crate protocol integration and end-to-end scenarios |
288288
| **Benchmarks** | - | Performance validation (frame parsing, AEAD, hashing, file operations) |
289289

290-
**Project Total:** 1,365+ tests (1,303 Rust tests + 62 frontend tests) - 100% pass rate
290+
**Project Total:** 1,679+ tests (1,617 Rust tests + 62 frontend tests) - 100% pass rate
291291

292292
## Documentation
293293

@@ -622,7 +622,7 @@ WRAITH Protocol is designed with security as a core principle:
622622
- **Unsafe Code Audit:** 100% documentation coverage with SAFETY comments
623623

624624
**Validation:**
625-
- **Test Coverage:** 1,365+ tests (1,303 Rust + 62 frontend) covering all protocol layers
625+
- **Test Coverage:** 1,679+ tests (1,617 Rust + 62 frontend) covering all protocol layers
626626
- **DPI Evasion:** Comprehensive validation against Wireshark, Zeek, Suricata, nDPI (see [DPI Evasion Report](docs/security/DPI_EVASION_REPORT.md))
627627
- **Fuzzing:** 5 libFuzzer targets continuously testing robustness
628628
- **Property-Based Tests:** QuickCheck-style invariant validation
@@ -714,4 +714,4 @@ WRAITH Protocol builds on the work of many excellent projects and technologies:
714714

715715
**WRAITH Protocol** - *Secure. Fast. Invisible.*
716716

717-
**Status:** v1.6.1 Testing Infrastructure & Documentation | **License:** MIT | **Language:** Rust 2024 (MSRV 1.85) | **Tests:** 1,365+ (1,303 Rust + 62 frontend, 100% pass rate) | **Quality:** Production-ready, 0 vulnerabilities, zero warnings, 98/100 quality grade | **Clients:** 4 Tier 1 applications complete (Desktop, Android, iOS, E2EE Chat)
717+
**Status:** v1.6.1 Testing Infrastructure & Documentation | **License:** MIT | **Language:** Rust 2024 (MSRV 1.85) | **Tests:** 1,679+ (1,617 Rust + 62 frontend, 100% pass rate) | **Quality:** Production-ready, 0 vulnerabilities, zero warnings, 98/100 quality grade | **Clients:** 4 Tier 1 applications complete (Desktop, Android, iOS, E2EE Chat)

clients/wraith-android/app/src/main/rust/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ version = "1.0.0"
44
edition = "2024"
55
rust-version = "1.85"
66

7+
# This is an independent package, not part of the main workspace
8+
[workspace]
9+
710
[lib]
811
crate-type = ["cdylib"]
912
name = "wraith_android"
@@ -18,6 +21,7 @@ tokio = { version = "1", features = ["full"] }
1821
# Serialization
1922
serde = { version = "1.0", features = ["derive"] }
2023
serde_json = "1.0"
24+
hex = "0.4"
2125

2226
# Logging
2327
log = "0.4"

clients/wraith-android/app/src/main/rust/src/lib.rs

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use jni::objects::{JClass, JObject, JString};
77
use jni::sys::{jbyteArray, jint, jlong, jstring};
88
use jni::JNIEnv;
9+
use std::sync::atomic::{AtomicUsize, Ordering};
910
use std::sync::{Arc, Mutex};
1011
use tokio::runtime::Runtime;
1112
use wraith_core::node::{Node, NodeConfig};
@@ -22,6 +23,24 @@ static RUNTIME: Mutex<Option<Runtime>> = Mutex::new(None);
2223
/// Global node instance
2324
static NODE: Mutex<Option<Arc<Node>>> = Mutex::new(None);
2425

26+
/// Global counter for active transfers
27+
static ACTIVE_TRANSFERS: AtomicUsize = AtomicUsize::new(0);
28+
29+
/// Increment active transfer count
30+
fn increment_transfers() {
31+
ACTIVE_TRANSFERS.fetch_add(1, Ordering::SeqCst);
32+
}
33+
34+
/// Decrement active transfer count
35+
fn decrement_transfers() {
36+
ACTIVE_TRANSFERS.fetch_sub(1, Ordering::SeqCst);
37+
}
38+
39+
/// Get current active transfer count
40+
fn get_active_transfers() -> usize {
41+
ACTIVE_TRANSFERS.load(Ordering::SeqCst)
42+
}
43+
2544
/// Initialize the WRAITH node
2645
///
2746
/// # Safety
@@ -45,7 +64,13 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_initNode(
4564

4665
// Initialize runtime if not already done
4766
{
48-
let mut rt_lock = RUNTIME.lock().unwrap();
67+
let mut rt_lock = match RUNTIME.lock() {
68+
Ok(guard) => guard,
69+
Err(e) => {
70+
log::error!("Failed to acquire runtime lock: {}", e);
71+
return -1;
72+
}
73+
};
4974
if rt_lock.is_none() {
5075
match Runtime::new() {
5176
Ok(rt) => *rt_lock = Some(rt),
@@ -83,8 +108,20 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_initNode(
83108
};
84109

85110
// Create node
86-
let rt = RUNTIME.lock().unwrap();
87-
let rt = rt.as_ref().unwrap();
111+
let rt = match RUNTIME.lock() {
112+
Ok(guard) => guard,
113+
Err(e) => {
114+
log::error!("Failed to acquire runtime lock: {}", e);
115+
return -1;
116+
}
117+
};
118+
let rt = match rt.as_ref() {
119+
Some(rt) => rt,
120+
None => {
121+
log::error!("Runtime not initialized");
122+
return -1;
123+
}
124+
};
88125

89126
let node = match rt.block_on(async { Node::new(config).await }) {
90127
Ok(n) => Arc::new(n),
@@ -110,7 +147,13 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_initNode(
110147

111148
// Store node globally
112149
{
113-
let mut node_lock = NODE.lock().unwrap();
150+
let mut node_lock = match NODE.lock() {
151+
Ok(guard) => guard,
152+
Err(e) => {
153+
log::error!("Failed to acquire node lock: {}", e);
154+
return -1;
155+
}
156+
};
114157
*node_lock = Some(node.clone());
115158
}
116159

@@ -135,18 +178,18 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_shutdownNode(
135178

136179
let node = Arc::from_raw(handle as *const Node);
137180

138-
let rt = RUNTIME.lock().unwrap();
139-
if let Some(rt) = rt.as_ref() {
140-
rt.block_on(async {
141-
if let Err(e) = node.shutdown().await {
142-
log::error!("Error during node shutdown: {}", e);
143-
}
144-
});
181+
if let Ok(rt) = RUNTIME.lock() {
182+
if let Some(rt) = rt.as_ref() {
183+
rt.block_on(async {
184+
if let Err(e) = node.stop().await {
185+
log::error!("Error during node shutdown: {}", e);
186+
}
187+
});
188+
}
145189
}
146190

147191
// Clear global node
148-
{
149-
let mut node_lock = NODE.lock().unwrap();
192+
if let Ok(mut node_lock) = NODE.lock() {
150193
*node_lock = None;
151194
}
152195
}
@@ -180,8 +223,20 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_establishSession(
180223
}
181224
};
182225

183-
let rt = RUNTIME.lock().unwrap();
184-
let rt = rt.as_ref().unwrap();
226+
let rt = match RUNTIME.lock() {
227+
Ok(guard) => guard,
228+
Err(e) => {
229+
log::error!("Failed to acquire runtime lock: {}", e);
230+
return std::ptr::null_mut();
231+
}
232+
};
233+
let rt = match rt.as_ref() {
234+
Some(rt) => rt,
235+
None => {
236+
log::error!("Runtime not initialized");
237+
return std::ptr::null_mut();
238+
}
239+
};
185240

186241
let session_info = match rt.block_on(async {
187242
// Convert peer_id string to PeerId type
@@ -251,8 +306,23 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_sendFile(
251306
Err(_) => return std::ptr::null_mut(),
252307
};
253308

254-
let rt = RUNTIME.lock().unwrap();
255-
let rt = rt.as_ref().unwrap();
309+
let rt = match RUNTIME.lock() {
310+
Ok(guard) => guard,
311+
Err(e) => {
312+
log::error!("Failed to acquire runtime lock: {}", e);
313+
return std::ptr::null_mut();
314+
}
315+
};
316+
let rt = match rt.as_ref() {
317+
Some(rt) => rt,
318+
None => {
319+
log::error!("Runtime not initialized");
320+
return std::ptr::null_mut();
321+
}
322+
};
323+
324+
// Track transfer start
325+
increment_transfers();
256326

257327
let transfer_info = match rt.block_on(async {
258328
use std::path::Path;
@@ -283,6 +353,7 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_sendFile(
283353
Ok(info) => info,
284354
Err(e) => {
285355
log::error!("Failed to send file: {}", e);
356+
decrement_transfers(); // Decrement on failure
286357
return std::ptr::null_mut();
287358
}
288359
};
@@ -313,9 +384,9 @@ pub unsafe extern "C" fn Java_com_wraith_android_WraithNative_getNodeStatus(
313384

314385
let status = serde_json::json!({
315386
"running": node.is_running(),
316-
"localPeerId": hex::encode(node.local_peer_id()),
317-
"sessionCount": node.session_count(),
318-
"activeTransfers": 0, // TODO: Track transfers
387+
"localPeerId": hex::encode(node.node_id()),
388+
"sessionCount": node.active_route_count(),
389+
"activeTransfers": get_active_transfers(),
319390
});
320391

321392
match env.new_string(status.to_string()) {

clients/wraith-chat/src-tauri/src/commands.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,19 @@ pub async fn start_node(
270270
pub async fn get_node_status(state: State<'_, Arc<AppState>>) -> Result<NodeStatus, String> {
271271
let peer_id = state.local_peer_id.lock().await;
272272

273+
// Get session count from active ratchets (cryptographic sessions)
274+
let ratchets = state.ratchets.lock().await;
275+
let session_count = ratchets.len();
276+
277+
// Get conversation count from database
278+
let db = state.db.lock().await;
279+
let active_conversations = db.count_conversations().unwrap_or(0);
280+
273281
Ok(NodeStatus {
274282
running: !peer_id.is_empty(),
275283
local_peer_id: peer_id.clone(),
276-
session_count: 0, // TODO: Get from node
277-
active_conversations: 0, // TODO: Get from database
284+
session_count,
285+
active_conversations,
278286
})
279287
}
280288

clients/wraith-chat/src-tauri/src/crypto.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,18 @@ impl DoubleRatchet {
185185
// Update receiving public key
186186
self.dh_receiving_public = Some(remote_public.to_vec());
187187

188-
// Perform DH
189-
let remote_pub = PublicKey::from(<[u8; 32]>::try_from(remote_public).unwrap());
190-
let our_secret =
191-
StaticSecret::from(<[u8; 32]>::try_from(&self.dh_sending_secret[..]).unwrap());
188+
// Perform DH - convert slices to fixed-size arrays
189+
let remote_pub_array: [u8; 32] = remote_public
190+
.try_into()
191+
.map_err(|_| CryptoError::InvalidKeyLength)?;
192+
let remote_pub = PublicKey::from(remote_pub_array);
193+
194+
let our_secret_array: [u8; 32] = self
195+
.dh_sending_secret
196+
.as_slice()
197+
.try_into()
198+
.map_err(|_| CryptoError::InvalidKeyLength)?;
199+
let our_secret = StaticSecret::from(our_secret_array);
192200
let dh_output = our_secret.diffie_hellman(&remote_pub);
193201

194202
// KDF to derive new root key and receiving chain key
@@ -267,6 +275,12 @@ impl DoubleRatchet {
267275

268276
/// Skip message keys for out-of-order handling
269277
fn skip_message_keys(&mut self, until: u32) -> Result<(), CryptoError> {
278+
// Get the receiving DH public key (required for key ID generation)
279+
let dh_recv_pub = self
280+
.dh_receiving_public
281+
.as_ref()
282+
.ok_or(CryptoError::NoReceivingChain)?;
283+
270284
if let Some(receiving_chain_key) = &self.receiving_chain_key {
271285
let mut temp_chain_key = receiving_chain_key.clone();
272286

@@ -281,10 +295,7 @@ impl DoubleRatchet {
281295
let message_key = okm[32..].to_vec();
282296

283297
// Store skipped key
284-
let key_id = self.key_id(
285-
self.dh_receiving_public.as_ref().unwrap(),
286-
self.receiving_chain_index,
287-
);
298+
let key_id = self.key_id(dh_recv_pub, self.receiving_chain_index);
288299
self.skipped_keys.insert(key_id, message_key);
289300

290301
self.receiving_chain_index += 1;

clients/wraith-chat/src-tauri/src/database.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,16 @@ impl Database {
289289
Ok(conversations)
290290
}
291291

292+
/// Count active (non-archived) conversations
293+
pub fn count_conversations(&self) -> Result<usize> {
294+
let count: i64 = self.conn.query_row(
295+
"SELECT COUNT(*) FROM conversations WHERE archived = 0",
296+
[],
297+
|row| row.get(0),
298+
)?;
299+
Ok(count as usize)
300+
}
301+
292302
// MARK: - Message Operations
293303

294304
pub fn insert_message(&self, msg: &Message) -> Result<i64> {

clients/wraith-ios/wraith-swift-ffi/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ version = "1.0.0"
44
edition = "2024"
55
rust-version = "1.85"
66

7+
# This is an independent package, not part of the main workspace
8+
[workspace]
9+
710
[lib]
811
crate-type = ["staticlib", "cdylib"]
912
name = "wraith_swift"
@@ -26,6 +29,9 @@ anyhow = "1.0"
2629
# Logging
2730
log = "0.4"
2831

32+
# Hex encoding
33+
hex = "0.4"
34+
2935
# WRAITH Protocol
3036
wraith-core = { path = "../../../crates/wraith-core" }
3137
wraith-crypto = { path = "../../../crates/wraith-crypto" }

0 commit comments

Comments
 (0)