sftpx 0.1.0

QUIC-based file transfer tool with auto-resume capability
Documentation
# Client/Server Wait Issue - Fix Documentation

## Problem Identified

Both the client and server were waiting/blocking indefinitely when trying to establish a QUIC connection.

### Root Causes

1. **Server Handshake Duplication**: The server's `run()` method was processing the initial packet, but then `complete_handshake()` was blocking on `recv_from()` waiting for another packet that wouldn't come until after the handshake was complete.

2. **Blocking Socket Mode**: The server socket remained in blocking mode during handshake, causing infinite waits when no packets were available.

3. **No Timeout on Client Handshake**: The client handshake loop had no timeout, waiting forever if the server didn't respond.

4. **Missing Error Handling**: Socket receive errors during handshake weren't handled properly.

## Fixes Applied

### 1. Server Module (`src/server/mod.rs`)

**Changes:**
- Added debug logging to track packet flow
- Server now properly sends handshake response immediately after receiving initial packet
- Clarified the flow: initial packet → response → complete handshake → application data

**Before:**
```rust
let (len, from) = self.socket.recv_from(&mut buf)?;
// Parse header, create connection, process packet, send response
self.handle_session(&mut server_conn, &mut buf, &mut out)?;
```

**After:**
```rust
println!("Server: waiting for initial packet...");
let (len, from) = self.socket.recv_from(&mut buf)?;
println!("Server: received initial packet ({} bytes) from {}", len, from);
// Parse header, create connection
println!("Server: connection accepted");
// Process packet
println!("Server: initial packet processed");
// Send response
println!("Server: sent handshake response");
// Handle session (completes handshake + app data)
```

### 2. Server Session (`src/server/session.rs`)

**Changes:**
- `complete_handshake()` now uses **non-blocking mode** with timeout
- Added 5-second deadline for handshake completion
- Proper handling of `WouldBlock` errors
- Debug logging for handshake progress

**Before:**
```rust
fn complete_handshake(...) {
    while !self.connection.is_established() {
        if let Ok((len, from)) = socket.recv_from(buf) {
            // Process packet
        }
    }
}
```

**After:**
```rust
fn complete_handshake(...) {
    socket.set_nonblocking(true)?;
    let deadline = Instant::now() + Duration::from_secs(5);
    
    while !self.connection.is_established() && Instant::now() < deadline {
        match socket.recv_from(buf) {
            Ok((len, from)) => { /* process */ }
            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                // Send pending packets, sleep, continue
            }
            Err(e) => return Err(e.into()),
        }
    }
    
    if !self.connection.is_established() {
        return Err("Handshake timeout".into());
    }
}
```

### 3. Client Test (`examples/test_client.rs`)

**Changes:**
- Added 5-second timeout for handshake
- Set read timeout on socket
- Proper handling of timeout errors during handshake
- Added deadline check to prevent infinite loops

**Before:**
```rust
loop {
    if conn.is_established() { break; }
    
    match socket.recv_from(&mut buf) {
        Ok((len, from)) => { /* process */ }
        Err(e) => {
            eprintln!("Socket recv error: {:?}", e);
            break;  // Exit on any error
        }
    }
    // Send response
}
```

**After:**
```rust
socket.set_read_timeout(Some(Duration::from_secs(5)))?;
let handshake_deadline = Instant::now() + Duration::from_secs(5);

while !conn.is_established() && Instant::now() < handshake_deadline {
    match socket.recv_from(&mut buf) {
        Ok((len, from)) => { /* process */ }
        Err(e) if e.kind() == ErrorKind::WouldBlock || 
                  e.kind() == ErrorKind::TimedOut => {
            // Normal timeout, continue
        }
        Err(e) => {
            eprintln!("Socket recv error: {:?}", e);
            break;
        }
    }
    // Send response
    if conn.is_established() { break; }
    sleep(Duration::from_millis(10));
}

if !conn.is_established() {
    return Err("Handshake timeout".into());
}
```

## Testing

### Running the Test

Two methods to test the fix:

#### Method 1: Using Test Script
```bash
./test.sh
```

This script:
1. Builds both examples
2. Starts the server in background
3. Runs the client
4. Reports success/failure
5. Cleans up server process

#### Method 2: Manual Testing

Terminal 1 (Server):
```bash
cargo run --example test_server
```

Terminal 2 (Client):
```bash
cargo run --example test_client
```

### Expected Output

**Server Output:**
```
=== QUIC Server Test ===

Server Configuration:
  - Address: 127.0.0.1:4443
  ...

✓ Server initialized successfully
✓ Listening for connections...

Server: waiting for initial packet...
Server: received initial packet (1200 bytes) from 127.0.0.1:xxxxx
Server: connection accepted
Server: initial packet processed
Server: sent handshake response
Connection established, initializing streams...
Server: completing handshake...
Server: handshake complete!
Server received on stream 0: Hello from QUIC client!
Message sent, closing server.

✓ Server completed successfully
```

**Client Output:**
```
=== QUIC Client Test ===

Client Configuration:
  - Local Address: 127.0.0.1:xxxxx
  - Server Address: 127.0.0.1:4443
  ...

✓ QUIC connection initialized
✓ Sent initial packet (1200 bytes)

Completing handshake...
  - Received 1200 bytes from 127.0.0.1:4443
✓ Connection established!

Sending application data...
✓ Sent 23 bytes on stream 0

Waiting for server response...
✓ Stream 0 is readable
✓ Received from server: "Hello from QUIC server!"
✓ Stream 0 finished

✓ Test PASSED: Communication successful!
✓ Connection closed
```

## Configuration Alignment

Both client and server now use matching configurations:

| Parameter | Client | Server | Status |
|-----------|--------|--------|--------|
| Port | 4443 | 4443 | ✓ Matching |
| Protocol | `hq-29` | `hq-29` | ✓ Matching |
| Max Idle Timeout | 5000ms | 5000ms | ✓ Matching |
| Max Data | 10MB | 10MB | ✓ Matching |
| Max Stream Data | 1MB | 1MB | ✓ Matching |
| Max Streams | 100 | 100 | ✓ Matching |
| Datagram Size | 1350 | 1350 | ✓ Matching |

## Key Lessons

1. **Always use non-blocking sockets with timeouts** during handshake to prevent infinite waits
2. **Don't process packets twice** - if you receive a packet in one function, don't try to receive it again in a subsequent function
3. **Add debug logging** to trace packet flow during development
4. **Handle WouldBlock errors properly** - they're expected in non-blocking mode
5. **Set deadlines for operations** that could potentially block forever
6. **Flush packets after processing** to ensure responses are sent

## Files Modified

1. `src/server/mod.rs` - Fixed initial packet handling and added logging
2. `src/server/session.rs` - Fixed handshake completion with non-blocking mode
3. `examples/test_client.rs` - Added timeout and proper error handling
4. `test.sh` - Created automated test script (new file)
5. `FIXES.md` - This documentation (new file)

## Status

✅ **RESOLVED** - Both client and server now successfully establish connection and exchange data without hanging.