Expand description
§Hayate Engine
Hayate is a completion-based transfer engine for encrypted file and directory movement across local networks. It is the library behind the Hayate CLI, but it is designed to be embedded directly in Rust applications.
The engine combines:
- QUIC transport through
compio-quic. - Completion-based async I/O through
compio. - Ephemeral X25519 key agreement.
- HKDF-SHA256 key derivation.
- Blake3 or SHA256 payload integrity.
- ChaCha20-Poly1305 or AES-256-GCM frame encryption.
- Optional zstd compression.
- Safe tar streaming for directory payloads (symlinks rejected; hard links supported by replay after the target file is extracted).
Most applications should start with HayateSender and HayateReceiver.
Lower-level modules remain public for custom transports, protocol testing,
or UI integrations that need direct control over handshakes and payload
streams.
§Direct Transfer
Direct mode connects to a known receiver address.
use std::net::SocketAddr;
use hayate::HayateSender;
let target: SocketAddr = "192.168.1.50:50001".parse().unwrap();
let sender = HayateSender::new()
.target(target)
.compress(true);
let checksum = sender.send("photos", |bytes_sent| {
println!("sent {bytes_sent} bytes");
Ok(())
}).await?;
println!("checksum {checksum}");use std::net::SocketAddr;
use hayate::HayateReceiver;
let bind_addr: SocketAddr = "0.0.0.0:50001".parse().unwrap();
let receiver = HayateReceiver::new()
.bind(bind_addr);
let (checksum, path) = receiver.receive(
"./downloads",
|meta| {
println!("incoming {} ({} bytes)", meta.filename, meta.total_size);
true
},
|bytes_received| {
println!("received {bytes_received} bytes");
Ok(())
},
).await?;
println!("saved to {}", path.display());
println!("checksum {checksum}");§Pairing-Code Transfer
Pairing mode lets a sender and receiver find each other through LAN broadcast using a shared phrase. The same phrase is also used during key derivation, so peers that do not know it cannot derive the application-layer metadata and payload key.
use hayate::HayateSender;
let sender = HayateSender::new()
.code("apple-bravo-charlie".to_owned());
sender.send("report.pdf", |_| Ok(())).await?;use hayate::HayateReceiver;
let receiver = HayateReceiver::new()
.code("apple-bravo-charlie".to_owned())
.auto_accept(true);
let (_checksum, _path) = receiver.receive("./downloads", |_| true, |_| Ok(())).await?;§Module Guide
runnerprovides the builder-style high-level API, including staged transfers (TransferStage,HayateSender::send_with,HayateReceiver::receive_with,ListeningReceiver).transferimplements handshake, consent, payload send, and payload receive.protocoldefines wire constants, frame flags, andprotocol::Metadata.cryptocontains key agreement, HKDF, AEAD sealing, and AEAD opening.networkbinds QUIC endpoints and builds ephemeral TLS/transport config.discoveryhandles pairing-code broadcast and discovery.tarpackages directories and safely extracts directory payloads.local_addrexposes local IPv4 helpers for UI and discovery.errordefinesEngineError.
§Protocol Shape
A transfer establishes QUIC, opens a bidirectional stream, negotiates protocol version and cipher capability, performs X25519 key agreement, derives a 32-byte AEAD key, encrypts metadata (including chosen hash algorithm), sends receiver consent, and then streams length-prefixed encrypted payload frames. File transfers must finish with the exact announced byte count; directory transfers are tar streams with containment checks during extraction.
§Safety Guarantees
Hayate treats peer input as untrusted:
- Metadata is encrypted and authenticated before use.
- Unknown transfer types are rejected before receive routing.
- Payload frames are length-capped and AEAD-authenticated before writes.
- Directory extraction rejects absolute paths,
.., and symlinks; hard links are collected and replayed after their targets are extracted. - Each transfer uses a fresh random HKDF salt and a transcript-bound key derivation. When a passphrase is used, it is mixed into the IKM; this authenticates the session against an eavesdropper but is not a PAKE on its own — offline brute-force resistance depends on the word-list entropy (see the CLI word-list hardening in the matching Tier 2 change).
- Receive task failures are returned as
EngineErrorinstead of panicking.
§Runtime
Public async APIs are intended to run inside a compio runtime, commonly
via #[compio::main]. Low-level callers must respect compio buffer
ownership: buffers are moved into I/O operations and returned through
compio::BufResult.
Re-exports§
pub use discovery::BroadcasterGuard;pub use discovery::DiscoveredPeer;pub use error::EngineError;pub use protocol::Metadata;pub use protocol::TransferKind;pub use runner::HayateReceiver;pub use runner::HayateSender;pub use runner::ListeningReceiver;pub use runner::ReceiveOutcome;pub use runner::SendOutcome;pub use runner::TransferStage;pub use runner::is_benign_peer_close;
Modules§
- crypto
- Cryptographic primitives: X25519 ECDH key exchange, HKDF-SHA256 key derivation, and AES-GCM / ChaCha20-Poly1305 AEAD.
- discovery
- LAN peer discovery with mDNS (RFC 6762) primary + UDP broadcast fallback.
- error
- Error types for the Hayate engine.
- local_
addr - Utilities for resolving and querying local network interface addresses and subnets.
- network
- QUIC network layer built on compio-quic (quinn-proto sans tokio).
- pool
- Lock-free fixed-size buffer pool for the hot I/O path.
- protocol
- Binary wire protocol constants, framing, and metadata codec.
- runner
- High-level developer-friendly API runners for sending and receiving files.
- tar
- Streaming tar archive reader for directory transfers.
- transfer
- Transfer pipeline: handshake, send, receive.