Error Reference
Comprehensive reference for all error types in QuicD, including error codes, causes, and solutions.
Error Categories
Section titled “Error Categories”QuicD errors fall into several categories:
- Transport Errors: QUIC protocol-level errors
- Application Errors: Application-defined error codes
- I/O Errors: System-level I/O failures
- Configuration Errors: Invalid configuration
- Resource Errors: System resource limitations
QuicError
Section titled “QuicError”Main error type for QUIC operations defined in quicd-x.
StreamStopped
Section titled “StreamStopped”Stream was stopped by peer with STOP_SENDING frame.
QuicError::StreamStopped { error_code: u64 }Cause: Peer sent STOP_SENDING frame for this stream
When: During write_all() or other send operations
Solution: Stop sending on this stream, close gracefully
Example:
match send.write_all(&data).await { Err(QuicError::StreamStopped { error_code }) => { log::info!("Peer stopped stream with code {}", error_code); // Clean up and exit } Ok(_) => { /* Success */ } Err(e) => { /* Other error */ }}StreamReset
Section titled “StreamReset”Stream was reset by peer with RESET_STREAM frame.
QuicError::StreamReset { error_code: u64 }Cause: Peer sent RESET_STREAM frame
When: During read() or other receive operations
Solution: Stop processing this stream
Example:
match recv.read(&mut buf).await { Err(QuicError::StreamReset { error_code }) => { log::warn!("Stream reset by peer: code {}", error_code); return Ok(()); // Exit handler } Ok(n) => { /* Process n bytes */ } Err(e) => { /* Other error */ }}ConnectionClosed
Section titled “ConnectionClosed”Connection was closed (gracefully or due to error).
QuicError::ConnectionClosed { error_code: u64, reason: Vec<u8>}Cause:
- Peer called
close() - Idle timeout
- Transport error
- Application error
When: Any connection operation after closure
Solution: Clean up resources, connection cannot be recovered
Example:
match handle.open_bidirectional_stream().await { Err(QuicError::ConnectionClosed { error_code, reason }) => { let reason_str = String::from_utf8_lossy(&reason); log::info!("Connection closed: {} (code {})", reason_str, error_code); return; } Ok(stream) => { /* Use stream */ } Err(e) => { /* Other error */ }}InvalidStreamId
Section titled “InvalidStreamId”Invalid stream ID provided.
QuicError::InvalidStreamId(u64)Cause:
- Stream ID doesn’t exist
- Stream was already closed
- Wrong stream type (bidi vs unidi)
Solution: Check stream IDs, ensure stream is open
StreamLimitReached
Section titled “StreamLimitReached”Maximum concurrent streams limit reached.
QuicError::StreamLimitReachedCause: Attempted to open more streams than allowed by peer
When: open_bidirectional_stream() or open_unidirectional_stream()
Solution:
- Wait for existing streams to complete
- Increase
max_streams_bidi/max_streams_uniin config - Use fewer concurrent streams
Example:
match handle.open_bidirectional_stream().await { Err(QuicError::StreamLimitReached) => { log::warn!("Stream limit reached, waiting..."); tokio::time::sleep(Duration::from_millis(100)).await; // Retry or queue request } Ok(stream) => { /* Use stream */ } Err(e) => { /* Other error */ }}FlowControl
Section titled “FlowControl”Flow control limit violated.
QuicError::FlowControlCause:
- Attempted to send more data than flow control window allows
- Peer hasn’t increased window with MAX_DATA/MAX_STREAM_DATA
Solution:
- Wait for peer to increase window
- Send smaller chunks
- Increase initial flow control windows in config
Transport
Section titled “Transport”Generic transport protocol error.
QuicError::Transport(String)Cause: Various QUIC transport errors
Common causes:
- Invalid packet format
- Protocol violation
- Crypto error
- Version negotiation failure
Example messages:
"InvalidPacket""CryptoFail""FlowControl""StreamState"
System I/O error.
QuicError::Io(std::io::Error)Cause: Underlying I/O operation failed
Common causes:
- Socket errors
- File descriptor limits
- Network unreachable
- Permission denied
Example:
match handle.open_bidirectional_stream().await { Err(QuicError::Io(e)) if e.kind() == std::io::ErrorKind::PermissionDenied => { log::error!("Permission denied: {}", e); } Err(QuicError::Io(e)) => { log::error!("I/O error: {}", e); } Ok(stream) => { /* Success */ } Err(e) => { /* Other QuicError */ }}H3Error
Section titled “H3Error”HTTP/3-specific errors from quicd-h3 crate.
InvalidFrame
Section titled “InvalidFrame”Invalid HTTP/3 frame received.
H3Error::InvalidFrameCause:
- Malformed frame
- Unknown frame type on wrong stream
- Frame ordering violation
Example: DATA frame on control stream
InvalidHeader
Section titled “InvalidHeader”Invalid HTTP header.
H3Error::InvalidHeaderCause:
- Malformed header block
- Invalid pseudo-headers
- Forbidden headers
Example: Missing :method pseudo-header
QPACK encoding/decoding error.
H3Error::Qpack(QpackError)Cause:
- Invalid QPACK instruction
- Encoder/decoder stream error
- Table synchronization error
Solution: Check QPACK configuration, may indicate peer bug
StreamClosed
Section titled “StreamClosed”HTTP/3 stream closed unexpectedly.
H3Error::StreamClosedCause: Stream closed before complete request/response
Solution: Check for clean stream closure in your handler
Underlying QUIC error.
H3Error::Quic(QuicError)Cause: QUIC transport error during HTTP/3 operation
Solution: See QuicError section above
QUIC Transport Error Codes
Section titled “QUIC Transport Error Codes”Standard QUIC error codes from RFC 9000.
NO_ERROR (0x0)
Section titled “NO_ERROR (0x0)”No error, normal closure.
handle.close(0x0, b"Normal closure");INTERNAL_ERROR (0x1)
Section titled “INTERNAL_ERROR (0x1)”Implementation error.
Cause: Bug in QuicD or application
Action: Report bug with logs
CONNECTION_REFUSED (0x2)
Section titled “CONNECTION_REFUSED (0x2)”Server refusing connection.
Cause:
- No application registered for ALPN
- Server overloaded
- Connection limit reached
FLOW_CONTROL_ERROR (0x3)
Section titled “FLOW_CONTROL_ERROR (0x3)”Flow control limit violated.
Cause: Peer sent more data than allowed
Action: May indicate peer bug
STREAM_LIMIT_ERROR (0x4)
Section titled “STREAM_LIMIT_ERROR (0x4)”Stream limit violated.
Cause: Peer opened too many streams
STREAM_STATE_ERROR (0x5)
Section titled “STREAM_STATE_ERROR (0x5)”Stream in wrong state for operation.
Cause: Protocol violation (e.g., sending on receive-only stream)
FINAL_SIZE_ERROR (0x6)
Section titled “FINAL_SIZE_ERROR (0x6)”Final stream size inconsistent.
Cause: Peer changed stream final size
FRAME_ENCODING_ERROR (0x7)
Section titled “FRAME_ENCODING_ERROR (0x7)”Malformed frame.
Cause: Invalid frame format
TRANSPORT_PARAMETER_ERROR (0x8)
Section titled “TRANSPORT_PARAMETER_ERROR (0x8)”Invalid transport parameters.
Cause: Invalid parameters in handshake
CONNECTION_ID_LIMIT_ERROR (0x9)
Section titled “CONNECTION_ID_LIMIT_ERROR (0x9)”Too many connection IDs.
PROTOCOL_VIOLATION (0xA)
Section titled “PROTOCOL_VIOLATION (0xA)”Generic protocol violation.
INVALID_TOKEN (0xB)
Section titled “INVALID_TOKEN (0xB)”Invalid stateless reset token.
APPLICATION_ERROR (0xC)
Section titled “APPLICATION_ERROR (0xC)”Application-specific error.
Use: Application-defined error codes
CRYPTO_BUFFER_EXCEEDED (0xD)
Section titled “CRYPTO_BUFFER_EXCEEDED (0xD)”Crypto handshake buffer overflow.
KEY_UPDATE_ERROR (0xE)
Section titled “KEY_UPDATE_ERROR (0xE)”Key update error.
AEAD_LIMIT_REACHED (0xF)
Section titled “AEAD_LIMIT_REACHED (0xF)”AEAD confidentiality/integrity limit reached.
NO_VIABLE_PATH (0x10)
Section titled “NO_VIABLE_PATH (0x10)”No viable network path.
Cause: All paths failed, connection migration not possible
Application Error Codes
Section titled “Application Error Codes”Application-specific error codes (user-defined).
HTTP/3 Error Codes
Section titled “HTTP/3 Error Codes”Standard HTTP/3 application errors:
- H3_NO_ERROR (0x0100): No error
- H3_GENERAL_PROTOCOL_ERROR (0x0101): General protocol error
- H3_INTERNAL_ERROR (0x0102): Internal error
- H3_STREAM_CREATION_ERROR (0x0103): Stream creation error
- H3_CLOSED_CRITICAL_STREAM (0x0104): Critical stream closed
- H3_FRAME_UNEXPECTED (0x0105): Unexpected frame
- H3_FRAME_ERROR (0x0106): Frame error
- H3_EXCESSIVE_LOAD (0x0107): Excessive load
- H3_ID_ERROR (0x0108): ID error
- H3_SETTINGS_ERROR (0x0109): Settings error
- H3_MISSING_SETTINGS (0x010A): Missing settings
- H3_REQUEST_REJECTED (0x010B): Request rejected
- H3_REQUEST_CANCELLED (0x010C): Request cancelled
- H3_REQUEST_INCOMPLETE (0x010D): Request incomplete
- H3_MESSAGE_ERROR (0x010E): Message error
- H3_CONNECT_ERROR (0x010F): Connect error
- H3_VERSION_FALLBACK (0x0110): Version fallback
Custom Error Codes
Section titled “Custom Error Codes”Define your own for custom protocols:
// Define application error codesconst APP_ERROR_INVALID_REQUEST: u64 = 0x1000;const APP_ERROR_UNAUTHORIZED: u64 = 0x1001;const APP_ERROR_RATE_LIMITED: u64 = 0x1002;
// Use in your applicationif !authorized { handle.close(APP_ERROR_UNAUTHORIZED, b"Unauthorized"); return;}Convention: Use high values (0x1000+) to avoid conflicts
Common Error Scenarios
Section titled “Common Error Scenarios”Handshake Failures
Section titled “Handshake Failures”Symptom: Connection fails to establish
Errors:
CRYPTO_ERRORTRANSPORT_PARAMETER_ERRORCONNECTION_REFUSED
Causes:
- TLS certificate issues
- ALPN mismatch
- Version mismatch
- Invalid transport parameters
Solutions:
# Check certificateopenssl x509 -in cert.pem -text -noout
# Verify ALPN configuration# Client and server must have matching ALPN
# Check QuicD logstail -f /var/log/quicd.logStream Errors
Section titled “Stream Errors”Symptom: Stream operations fail
Errors:
StreamResetStreamStoppedInvalidStreamId
Causes:
- Peer closing stream early
- Application error in handler
- Stream ID mismatch
Solutions:
// Graceful error handlingmatch recv.read(&mut buf).await { Ok(0) => { // Normal end of stream log::info!("Stream finished normally"); } Ok(n) => { // Process n bytes } Err(QuicError::StreamReset { error_code }) => { // Peer reset - not necessarily an error log::info!("Stream reset by peer: {}", error_code); } Err(e) => { // Actual error log::error!("Stream error: {}", e); }}Flow Control Errors
Section titled “Flow Control Errors”Symptom: Sends block or fail
Errors:
FlowControlFLOW_CONTROL_ERROR
Causes:
- Sending faster than peer can receive
- Flow control windows too small
- Peer not reading data
Solutions:
# Increase flow control windows[quic]initial_max_data = 10485760 # 10MBinitial_max_stream_data_bidi = 1048576 # 1MB// Implement backpressurelet stats = handle.stats();if stats.bytes_sent - stats.bytes_acked > THRESHOLD { // Wait for peer to read tokio::time::sleep(Duration::from_millis(10)).await;}Connection Timeouts
Section titled “Connection Timeouts”Symptom: Connection drops after idle period
Errors:
ConnectionClosedwithNO_ERRORorIDLE_TIMEOUT
Causes:
- No activity for
max_idle_timeout - Network path failure
- Peer crashed
Solutions:
# Increase idle timeout[quic]max_idle_timeout = 60000 # 60 seconds// Send keep-alive pingstokio::spawn(async move { loop { tokio::time::sleep(Duration::from_secs(30)).await; if let Err(e) = handle.ping().await { log::error!("Ping failed: {}", e); break; } }});Resource Exhaustion
Section titled “Resource Exhaustion”Symptom: Operations fail with resource errors
Errors:
StreamLimitReachedIo(ErrorKind::OutOfMemory)Io(ErrorKind::TooManyFiles)
Causes:
- Too many concurrent streams/connections
- System resource limits
- Memory exhaustion
Solutions:
# Increase file descriptor limitulimit -n 100000
# Increase system limitssudo sysctl -w fs.file-max=2097152# Limit concurrent streams[quic]max_streams_bidi = 100max_streams_uni = 100max_connections = 10000// Implement connection limitingif active_connections >= MAX_CONNECTIONS { log::warn!("Connection limit reached"); // Reject new connections or queue}Debugging Errors
Section titled “Debugging Errors”Enable Debug Logging
Section titled “Enable Debug Logging”RUST_LOG=debug quicd --config config.toml# In config.toml[telemetry]enabled = truelog_level = "debug"Error Context
Section titled “Error Context”Add context to errors:
use anyhow::{Context, Result};
async fn handle_request(handle: ConnectionHandle) -> Result<()> { let (send, recv) = handle .open_bidirectional_stream() .await .context("Failed to open stream")?;
let data = recv .read_to_end() .await .context("Failed to read request")?;
Ok(())}Error Recovery
Section titled “Error Recovery”Implement retry logic:
async fn send_with_retry( handle: &ConnectionHandle, data: &[u8], max_retries: u32,) -> Result<(), QuicError> { for attempt in 0..max_retries { match handle.open_unidirectional_stream().await { Ok(mut send) => { return send.write_all(data).await; } Err(QuicError::StreamLimitReached) if attempt < max_retries - 1 => { log::warn!("Stream limit reached, retrying..."); tokio::time::sleep(Duration::from_millis(100)).await; continue; } Err(e) => return Err(e), } } Err(QuicError::StreamLimitReached)}Metrics
Section titled “Metrics”Monitor error rates:
metrics::counter!("quicd.errors.stream_reset").increment(1);metrics::counter!("quicd.errors.connection_closed").increment(1);Related Documentation
Section titled “Related Documentation”- API Reference - Complete API documentation
- Configuration - Configuration options
- Troubleshooting FAQ - Common issues
- Architecture - System design
Error Handling Best Practices
Section titled “Error Handling Best Practices”- Always handle errors: Don’t use
.unwrap()in production - Add context: Use
anyhoworthiserrorfor error context - Log errors: Include relevant details for debugging
- Graceful degradation: Handle errors without crashing
- Monitor error rates: Track errors in metrics/telemetry
- Retry transient errors: Implement backoff for retryable errors
- Clean up resources: Use RAII patterns,
Droptrait - Document error codes: Define and document your application errors
// Good error handling exampleasync fn handle_connection(handle: ConnectionHandle) { if let Err(e) = process_connection(handle).await { match e { QuicError::ConnectionClosed { .. } => { // Expected, clean shutdown log::info!("Connection closed"); } QuicError::StreamReset { error_code } => { // Possibly expected log::warn!("Stream reset: {}", error_code); metrics::counter!("stream_resets").increment(1); } _ => { // Unexpected error log::error!("Connection error: {}", e); metrics::counter!("connection_errors").increment(1); } } }}