# CryptoTlsStream Implementation Analysis

This document provides a comprehensive analysis of the `CryptoTlsStream` implementation compared to the reference `tokio-rustls` TlsStream implementation. The analysis was conducted to identify potential causes of connection hanging issues.

## Table of Contents

1. [Executive Summary](#executive-summary)
2. [Architecture Overview](#architecture-overview)
3. [Critical Issues](#critical-issues)
4. [Medium Priority Issues](#medium-priority-issues)
5. [Low Priority Issues](#low-priority-issues)
6. [Detailed Code Comparisons](#detailed-code-comparisons)
7. [Recommended Fixes](#recommended-fixes)
8. [Testing Recommendations](#testing-recommendations)

---

## Executive Summary

The `CryptoTlsStream` implementation in `src/crypto/crypto_tls_stream.rs` is a custom async TLS stream wrapper that provides `AsyncRead` and `AsyncWrite` implementations for rustls connections. While the implementation follows the general pattern of tokio-rustls, several critical differences were identified that could cause connection hangs:

| Issue | Severity | Impact | Status |
|-------|----------|--------|--------|
| Missing `wants_read()` check in read loop | HIGH | Connection hang during reads | **FIXED** |
| Missing waker wake-by-ref pattern | HIGH | Task never re-polled after edge cases | **FIXED** |
| Missing `session.writer().flush()` in poll_flush | MEDIUM | Data stuck in session buffer | **FIXED** |
| No state machine for shutdown tracking | MEDIUM | Incorrect behavior after close_notify | **FIXED** |
| Missing `need_flush` state tracking | MEDIUM | Flush not retried after Pending | **FIXED** |
| WriteZero not treated as error | LOW | Silent failure on zero-byte writes | **FIXED** |
| poll_shutdown sends close_notify before draining | MEDIUM | Wrong wire order | **FIXED** |

### Additional REALITY Connection Fixes

| Issue | Severity | Status |
|-------|----------|--------|
| Missing `received_close_notify` flag | MEDIUM | **FIXED** |
| Missing error state persistence | MEDIUM | **FIXED** |
| Alert parsing not implemented | MEDIUM | **FIXED** |
| `wants_read()` doesn't check close_notify | MEDIUM | **FIXED** |

---

## Architecture Overview

### CryptoTlsStream Structure

```rust
// src/crypto/crypto_tls_stream.rs
pub struct CryptoTlsStream<IO> {
    io: IO,                      // Underlying async I/O stream (e.g., TcpStream)
    session: CryptoConnection,   // Crypto connection (rustls or REALITY)
    is_read_eof: bool,           // Whether we've hit EOF when reading
}
```

### tokio-rustls TlsStream Structure

```rust
// tokio-rustls/src/client.rs (and server.rs)
pub struct TlsStream<IO> {
    pub(crate) io: IO,
    pub(crate) session: ClientConnection,  // or ServerConnection
    pub(crate) state: TlsState,            // State machine for shutdown tracking
    pub(crate) need_flush: bool,           // Track pending flush operations
    #[cfg(feature = "early-data")]
    pub(crate) early_waker: Option<Waker>, // For 0-RTT support
}

pub(crate) enum TlsState {
    #[cfg(feature = "early-data")]
    EarlyData(usize, Vec<u8>),
    Stream,
    ReadShutdown,
    WriteShutdown,
    FullyShutdown,
}
```

### Key Architectural Differences

1. **State Tracking**: tokio-rustls uses a `TlsState` enum to track read/write shutdown states separately. CryptoTlsStream only has a simple `is_read_eof` boolean.

2. **Flush Tracking**: tokio-rustls tracks `need_flush` to ensure flush operations are retried. CryptoTlsStream does not track this.

3. **Stream Abstraction**: tokio-rustls uses a temporary `Stream` struct for each operation that carries state. CryptoTlsStream operates directly on its fields.

---

## Critical Issues

### Issue 1: Missing `wants_read()` Check in poll_read Loop

**Severity: HIGH**

**Location**: `src/crypto/crypto_tls_stream.rs:164-235`

**Description**: The `poll_read` implementation enters an unconditional loop to read TLS data without checking if rustls actually wants to read. This can cause the connection to hang if:
- The underlying socket has no data available
- rustls doesn't actually need more data (e.g., it has buffered encrypted data to process)
- The connection is in a state where reading is not appropriate

**CryptoTlsStream Implementation**:
```rust
// Line 164-235
loop {
    let bytes_read = {
        let mut adapter = SyncReadAdapter {
            io: &mut this.io,
            cx,
        };
        match this.session.read_tls(&mut adapter) {
            Ok(n) => n,
            Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
                return Poll::Pending;  // Returns Pending without checking wants_read()
            }
            Err(e) => return Poll::Ready(Err(e)),
        }
    };
    // ... rest of loop
}
```

**tokio-rustls Implementation** (`common/mod.rs:198-209`):
```rust
// Only reads while wants_read() is true
while !self.eof && self.session.wants_read() {
    match self.read_io(cx) {
        Poll::Ready(Ok(0)) => {
            break;
        }
        Poll::Ready(Ok(_)) => (),
        Poll::Pending => {
            io_pending = true;
            break;
        }
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
    }
}
```

**Impact**: Without the `wants_read()` check, the code may:
1. Attempt to read when rustls doesn't need data, causing unnecessary I/O
2. Return `Pending` when rustls has data buffered that could be processed
3. Enter a state where the task is waiting for I/O that will never arrive

---

### Issue 2: Missing Waker Wake-by-ref Pattern

**Severity: HIGH**

**Location**: `src/crypto/crypto_tls_stream.rs` (missing entirely)

**Description**: tokio-rustls has a critical pattern where it wakes the task by reference when rustls returns `WouldBlock` unexpectedly. This handles edge cases in rustls's state machine where data may be available for processing even though no I/O occurred.

**tokio-rustls Implementation** (`common/mod.rs:212-231`):
```rust
match self.session.reader().into_first_chunk() {
    Ok(buf) => {
        Poll::Ready(Ok(buf))
    }
    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
        if !io_pending {
            // If `wants_read()` is satisfied, rustls will not return `WouldBlock`.
            // but if it does, we can try again.
            //
            // If the rustls state is abnormal, it may cause a cyclic wakeup.
            // but tokio's cooperative budget will prevent infinite wakeup.
            cx.waker().wake_by_ref();
        }

        Poll::Pending
    }
    Err(e) => Poll::Ready(Err(e)),
}
```

**CryptoTlsStream Implementation**: This pattern is completely absent. When `fill_buf()` returns `WouldBlock`, the code does not wake the task:

```rust
// Line 128-146
{
    let mut reader = this.session.reader();
    match reader.fill_buf() {
        Ok(available) if !available.is_empty() => {
            // ... return data
        }
        Ok(_) => {
            // Empty buffer, need to read more
        }
        Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
            // No data available yet - NO WAKE BY REF!
        }
        Err(e) => return Poll::Ready(Err(e)),
    }
}
```

**Impact**: Without this pattern:
1. If rustls enters an unexpected state, the task may never be re-polled
2. Connection hangs indefinitely waiting for a wakeup that never comes
3. Edge cases in rustls's state machine are not handled properly

**The Comment from tokio-rustls Explains It Well**:
> "If `wants_read()` is satisfied, rustls will not return `WouldBlock`. but if it does, we can try again. If the rustls state is abnormal, it may cause a cyclic wakeup. but tokio's cooperative budget will prevent infinite wakeup."

---

## Medium Priority Issues

### Issue 3: Missing `session.writer().flush()` in poll_flush

**Severity: MEDIUM**

**Location**: `src/crypto/crypto_tls_stream.rs:290-299`

**Description**: The `poll_flush` implementation does not call `session.writer().flush()` before draining writes. This means data buffered in the rustls writer may not be flushed to the TLS output buffer.

**CryptoTlsStream Implementation**:
```rust
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
    // Drain all pending TLS writes
    match self.drain_all_writes(cx) {
        Poll::Ready(Ok(())) => {}
        Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
        Poll::Pending => return Poll::Pending,
    }

    // Flush underlying stream
    Pin::new(&mut self.io).poll_flush(cx)
    // MISSING: session.writer().flush() call!
}
```

**tokio-rustls Implementation** (`common/mod.rs:352-359`):
```rust
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
    self.session.writer().flush()?;  // <-- Important!
    while self.session.wants_write() {
        if ready!(self.write_io(cx))? == 0 {
            return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
        }
    }
    Pin::new(&mut self.io).poll_flush(cx)
}
```

**Impact**:
1. Data written to `session.writer()` may remain buffered
2. `wants_write()` may return false even though there's unflushed data
3. Application-level flushes may not actually flush all data

---

### Issue 4: No State Machine for Shutdown Tracking

**Severity: MEDIUM**

**Location**: `src/crypto/crypto_tls_stream.rs` (architectural issue)

**Description**: CryptoTlsStream uses a simple boolean `is_read_eof` to track EOF state. tokio-rustls uses a proper state machine (`TlsState`) that tracks read and write shutdown states independently.

**tokio-rustls State Machine**:
```rust
pub(crate) enum TlsState {
    #[cfg(feature = "early-data")]
    EarlyData(usize, Vec<u8>),
    Stream,
    ReadShutdown,
    WriteShutdown,
    FullyShutdown,
}

impl TlsState {
    pub(crate) fn shutdown_read(&mut self) {
        match *self {
            Self::WriteShutdown | Self::FullyShutdown => *self = Self::FullyShutdown,
            _ => *self = Self::ReadShutdown,
        }
    }

    pub(crate) fn shutdown_write(&mut self) {
        match *self {
            Self::ReadShutdown | Self::FullyShutdown => *self = Self::FullyShutdown,
            _ => *self = Self::WriteShutdown,
        }
    }

    pub(crate) fn writeable(&self) -> bool {
        !matches!(*self, Self::WriteShutdown | Self::FullyShutdown)
    }

    pub(crate) fn readable(&self) -> bool {
        !matches!(*self, Self::ReadShutdown | Self::FullyShutdown)
    }
}
```

**Usage in tokio-rustls**:
```rust
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
    match self.state {
        TlsState::Stream | TlsState::WriteShutdown => {
            // Normal read path
            let this = self.get_mut();
            let stream = Stream::new(&mut this.io, &mut this.session)
                .set_eof(!this.state.readable());
            // ...
        }
        TlsState::ReadShutdown | TlsState::FullyShutdown => {
            // Return empty slice for EOF
            Poll::Ready(Ok(&[]))
        }
    }
}

fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
    if self.state.writeable() {
        self.session.send_close_notify();
        self.state.shutdown_write();
    }
    // ...
}
```

**CryptoTlsStream** only tracks:
```rust
is_read_eof: bool
```

**Impact**:
1. Cannot properly track half-closed connections (read closed, write open or vice versa)
2. May send close_notify multiple times if poll_shutdown is called repeatedly
3. May attempt to read from a connection that has already received close_notify
4. Incorrect behavior in edge cases around connection shutdown

---

### Issue 5: Missing `need_flush` State Tracking

**Severity: MEDIUM**

**Location**: `src/crypto/crypto_tls_stream.rs` (architectural issue)

**Description**: tokio-rustls tracks whether a flush operation is pending via the `need_flush` flag. This ensures that if a flush returns `Pending`, subsequent operations know to retry the flush.

**tokio-rustls Implementation**:
```rust
pub(crate) struct Stream<'a, IO, C> {
    pub(crate) io: &'a mut IO,
    pub(crate) session: &'a mut C,
    pub(crate) eof: bool,
    pub(crate) need_flush: bool,  // Tracked across operations
}

// In handshake:
while self.session.wants_write() {
    match self.write_io(cx) {
        Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())),
        Poll::Ready(Ok(n)) => {
            wrlen += n;
            self.need_flush = true;  // Mark that flush is needed
        }
        Poll::Pending => {
            write_would_block = true;
            break;
        }
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
    }
}

if self.need_flush {
    match Pin::new(&mut self.io).poll_flush(cx) {
        Poll::Ready(Ok(())) => self.need_flush = false,  // Clear flag on success
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
        Poll::Pending => write_would_block = true,
    }
}
```

**Impact**:
1. If `poll_flush` returns `Pending`, CryptoTlsStream may not retry the flush
2. Data may remain in the TCP send buffer longer than necessary
3. Potential for data loss if connection is closed before flush completes

---

## Low Priority Issues

### Issue 6: WriteZero Not Treated as Error

**Severity: LOW**

**Location**: `src/crypto/crypto_tls_stream.rs:270-284`

**Description**: When `write_tls` returns `Ok(0)`, CryptoTlsStream treats it the same as `Pending`. tokio-rustls treats this as a `WriteZero` error.

**CryptoTlsStream Implementation**:
```rust
while self.session.wants_write() {
    match self.write_tls_direct(cx) {
        Poll::Ready(Ok(0)) | Poll::Pending => {  // Treated the same!
            would_block = true;
            break;
        }
        Poll::Ready(Ok(_)) => (),
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
    }
}
```

**tokio-rustls Implementation**:
```rust
while self.session.wants_write() {
    match self.write_io(cx) {
        Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())),  // Error!
        Poll::Ready(Ok(_)) => (),
        Poll::Pending => {
            would_block = true;
            break;
        }
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
    }
}
```

**Impact**:
1. A zero-byte write (which shouldn't happen in normal operation) may go undetected
2. Could mask underlying I/O issues
3. May cause silent data loss in edge cases

---

### Issue 7: Handshake Flushing Difference

**Severity: LOW**

**Location**: `src/crypto/crypto_tls_stream.rs:80-113`

**Description**: During handshake, CryptoTlsStream drains writes but doesn't explicitly flush after each batch. tokio-rustls tracks `need_flush` during handshake and flushes when pending.

**CryptoTlsStream Implementation**:
```rust
fn complete_handshake_if_needed(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
    while self.session.is_handshaking() {
        // Write any pending TLS data
        match self.drain_all_writes(cx) {
            Poll::Ready(Ok(())) => {}
            Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
            Poll::Pending => return Poll::Pending,
        }
        // No flush here!

        // Read more data for handshake
        // ...
    }
    Poll::Ready(Ok(()))
}
```

**tokio-rustls Implementation** (handshake.rs):
```rust
while tls_stream.session.is_handshaking() {
    try_poll!(tls_stream.handshake(cx));
}

try_poll!(Pin::new(&mut tls_stream).poll_flush(cx));  // Flush after handshake!
```

**Impact**: Handshake completion may be delayed if TCP buffers are not flushed promptly.

---

## Detailed Code Comparisons

### poll_read Flow Comparison

```
CryptoTlsStream poll_read:
┌──────────────────────────────────────┐
│ 1. Check session buffer (fill_buf)   │
│    - Return data if available        │
│    - WouldBlock: continue            │
├──────────────────────────────────────┤
│ 2. Check is_read_eof                 │
│    - If true, return Ok(())          │
├──────────────────────────────────────┤
│ 3. Complete handshake if needed      │
├──────────────────────────────────────┤
│ 4. Loop (unconditional):             │
│    - read_tls()                      │
│    - WouldBlock -> return Pending    │◄── PROBLEM: No wants_read() check
│    - EOF -> set is_read_eof, return  │
│    - process_new_packets()           │
│    - Check plaintext available       │
│    - Return data                     │
└──────────────────────────────────────┘

tokio-rustls poll_read:
┌──────────────────────────────────────┐
│ 1. poll_fill_buf (via AsyncBufRead)  │
├──────────────────────────────────────┤
│ 2. Inside poll_fill_buf:             │
│    - Check state (Stream/Shutdown)   │◄── State machine check
│    - while !eof && wants_read():     │◄── wants_read() check!
│        - read_io()                   │
│        - Track io_pending            │
│    - into_first_chunk()              │
│    - WouldBlock + !io_pending:       │
│        - wake_by_ref()               │◄── Critical pattern!
│        - return Pending              │
│    - Update shutdown state if empty  │
└──────────────────────────────────────┘
```

### poll_write Flow Comparison

```
CryptoTlsStream poll_write:
┌──────────────────────────────────────┐
│ 1. Complete handshake if needed      │
├──────────────────────────────────────┤
│ 2. Loop while pos < buf.len():       │
│    - Write to session.writer()       │
│    - Drain TLS output:               │
│        - Ok(0) | Pending -> break    │◄── Ok(0) not an error
│    - Return based on (pos, blocked)  │
└──────────────────────────────────────┘

tokio-rustls poll_write:
┌──────────────────────────────────────┐
│ 1. Create Stream with need_flush     │
│ 2. Handle early data (if enabled)    │
├──────────────────────────────────────┤
│ 3. Loop while pos != buf.len():      │
│    - Write to session.writer()       │
│    - Drain TLS output:               │
│        - Ok(0) -> WriteZero error    │◄── Ok(0) IS an error
│        - Pending -> would_block      │
│    - Return based on (pos, blocked)  │
└──────────────────────────────────────┘
```

### poll_flush Flow Comparison

```
CryptoTlsStream poll_flush:
┌──────────────────────────────────────┐
│ 1. drain_all_writes()                │
│ 2. poll_flush(io)                    │
│                                      │
│ MISSING: session.writer().flush()    │◄── PROBLEM
└──────────────────────────────────────┘

tokio-rustls poll_flush:
┌──────────────────────────────────────┐
│ 1. session.writer().flush()          │◄── Present!
│ 2. while wants_write():              │
│    - write_io()                      │
│    - Ok(0) -> WriteZero error        │
│ 3. poll_flush(io)                    │
└──────────────────────────────────────┘
```

### poll_shutdown Flow Comparison

```
CryptoTlsStream poll_shutdown:
┌──────────────────────────────────────┐
│ 1. send_close_notify() (always)      │◄── May send multiple times
│ 2. drain_all_writes()                │
│ 3. poll_shutdown(io)                 │
│    - Handle NotConnected             │
└──────────────────────────────────────┘

tokio-rustls poll_shutdown:
┌──────────────────────────────────────┐
│ 1. if state.writeable():             │◄── Check state first
│    - send_close_notify()             │
│    - state.shutdown_write()          │
│ 2. while wants_write():              │
│    - write_io()                      │
│ 3. poll_shutdown(io)                 │
│    - Handle NotConnected             │
└──────────────────────────────────────┘
```

---

## Recommended Fixes

### Fix 1: Add wants_read() Check (Critical)

```rust
// In poll_read, replace the unconditional loop with:
let mut io_pending = false;

while !this.is_read_eof && this.session.wants_read() {
    let mut adapter = SyncReadAdapter {
        io: &mut this.io,
        cx,
    };
    match this.session.read_tls(&mut adapter) {
        Ok(0) => {
            this.is_read_eof = true;
            break;
        }
        Ok(_) => {
            this.session.process_new_packets()?;
        }
        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
            io_pending = true;
            break;
        }
        Err(e) => return Poll::Ready(Err(e)),
    }
}
```

### Fix 2: Add Waker Wake-by-ref Pattern (Critical)

```rust
// After checking session buffer:
let mut reader = this.session.reader();
match reader.fill_buf() {
    Ok(available) if !available.is_empty() => {
        let len = buf.remaining().min(available.len());
        buf.put_slice(&available[..len]);
        reader.consume(len);
        return Poll::Ready(Ok(()));
    }
    Ok(_) => {
        // Empty buffer
    }
    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
        if !io_pending {
            // Wake ourselves to retry - handles edge cases in rustls state
            cx.waker().wake_by_ref();
        }
        return Poll::Pending;
    }
    Err(e) => return Poll::Ready(Err(e)),
}
```

### Fix 3: Add session.writer().flush() to poll_flush (Medium)

```rust
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
    // Flush session writer first
    self.session.writer().flush()?;

    // Drain all pending TLS writes
    while self.session.wants_write() {
        match self.write_tls_direct(cx) {
            Poll::Ready(Ok(0)) => {
                return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
            }
            Poll::Ready(Ok(_)) => {}
            Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
            Poll::Pending => return Poll::Pending,
        }
    }

    // Flush underlying stream
    Pin::new(&mut self.io).poll_flush(cx)
}
```

### Fix 4: Add TlsState Enum (Medium)

```rust
#[derive(Debug, Clone, Copy)]
pub enum TlsState {
    Stream,
    ReadShutdown,
    WriteShutdown,
    FullyShutdown,
}

impl TlsState {
    pub fn shutdown_read(&mut self) {
        *self = match *self {
            Self::WriteShutdown | Self::FullyShutdown => Self::FullyShutdown,
            _ => Self::ReadShutdown,
        };
    }

    pub fn shutdown_write(&mut self) {
        *self = match *self {
            Self::ReadShutdown | Self::FullyShutdown => Self::FullyShutdown,
            _ => Self::WriteShutdown,
        };
    }

    pub fn readable(&self) -> bool {
        !matches!(*self, Self::ReadShutdown | Self::FullyShutdown)
    }

    pub fn writeable(&self) -> bool {
        !matches!(*self, Self::WriteShutdown | Self::FullyShutdown)
    }
}

pub struct CryptoTlsStream<IO> {
    io: IO,
    session: CryptoConnection,
    state: TlsState,      // Replace is_read_eof
    need_flush: bool,     // Add flush tracking
}
```

### Fix 5: Add need_flush Tracking (Medium)

```rust
pub struct CryptoTlsStream<IO> {
    io: IO,
    session: CryptoConnection,
    state: TlsState,
    need_flush: bool,
}

// In poll_write, after writing to TLS:
while self.session.wants_write() {
    match self.write_tls_direct(cx) {
        Poll::Ready(Ok(0)) => {
            return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
        }
        Poll::Ready(Ok(_)) => {
            self.need_flush = true;
        }
        Poll::Pending => {
            would_block = true;
            break;
        }
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
    }
}

// Check and clear need_flush
if self.need_flush {
    match Pin::new(&mut self.io).poll_flush(cx) {
        Poll::Ready(Ok(())) => self.need_flush = false,
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
        Poll::Pending => would_block = true,
    }
}
```

### Fix 6: Treat WriteZero as Error (Low)

```rust
// In poll_write and drain_all_writes:
while self.session.wants_write() {
    match self.write_tls_direct(cx) {
        Poll::Ready(Ok(0)) => {
            return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
        }
        Poll::Ready(Ok(_)) => (),
        Poll::Pending => {
            would_block = true;
            break;
        }
        Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
    }
}
```

---

## Testing Recommendations

### Unit Tests

1. **Test wants_read() behavior**: Create a mock that returns data without setting wants_read(), verify no hang
2. **Test WouldBlock edge case**: Mock rustls to return WouldBlock unexpectedly, verify task is re-woken
3. **Test shutdown states**: Verify correct behavior for half-closed connections
4. **Test flush propagation**: Verify session.writer().flush() data reaches TCP

### Integration Tests

1. **High-latency connection test**: Test with simulated network latency to catch timing issues
2. **Concurrent read/write test**: Stress test with simultaneous reads and writes
3. **Shutdown sequence test**: Test various shutdown sequences (clean, abrupt, half-close)
4. **Large transfer test**: Transfer large amounts of data to catch buffer management issues

### Stress Tests

1. **Connection churn**: Rapidly open and close connections
2. **Partial read/write**: Test with very small buffer sizes
3. **Timeout scenarios**: Test behavior when operations timeout

### Recommended Test Harness

```rust
#[tokio::test]
async fn test_wants_read_not_required() {
    // Setup mock that has data buffered but wants_read() returns false
    let mock = MockTlsSession::with_buffered_data();
    let stream = CryptoTlsStream::new(mock_io, mock);

    let mut buf = [0u8; 1024];
    // Should not hang - should return buffered data
    let result = tokio::time::timeout(
        Duration::from_secs(1),
        stream.read(&mut buf)
    ).await;

    assert!(result.is_ok());
}

#[tokio::test]
async fn test_would_block_wake() {
    // Setup mock that returns WouldBlock from fill_buf unexpectedly
    let mock = MockTlsSession::would_block_on_fill_buf();
    let stream = CryptoTlsStream::new(mock_io, mock);

    // Should not hang - task should be re-woken
    let result = tokio::time::timeout(
        Duration::from_secs(1),
        stream.read(&mut [0u8; 1024])
    ).await;

    // May return Pending multiple times but should eventually make progress
    assert!(result.is_ok() || matches!(result, Err(Elapsed)));
}
```

---

## Appendix: Reference Files

### CryptoTlsStream Source Files
- `src/crypto/crypto_tls_stream.rs` - Main implementation
- `src/crypto/crypto_connection.rs` - CryptoConnection enum
- `src/sync_adapter.rs` - SyncReadAdapter/SyncWriteAdapter

### tokio-rustls Source Files (cloned to /tmp/tokio-rustls)
- `src/common/mod.rs` - Stream implementation with poll_fill_buf, read_io, write_io
- `src/common/handshake.rs` - MidHandshake future and IoSession trait
- `src/client.rs` - Client TlsStream wrapper
- `src/server.rs` - Server TlsStream wrapper

---

## Changelog

- **2024-12-03**: All issues fixed
  - CryptoTlsStream: Added `wants_read()` check, wake-by-ref pattern, `session.writer().flush()`, `TlsState` enum, `need_flush` tracking, WriteZero handling
  - CryptoTlsStream: Fixed `poll_shutdown` to drain pending writes before sending close_notify
  - REALITY connections: Added `received_close_notify` flag, error state persistence, proper alert parsing, updated `wants_read()` to check close_notify
- **2024-12-02**: Initial analysis comparing CryptoTlsStream with tokio-rustls
