start, reverse guard, cli-frontend for server and client

This commit is contained in:
2026-01-01 18:54:36 +03:00
commit 5fbb32d243
30 changed files with 4700 additions and 0 deletions

17
ostp/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
name = "ostp"
version.workspace = true
edition.workspace = true
[dependencies]
tokio.workspace = true
chacha20poly1305.workspace = true
x25519-dalek.workspace = true
bytes.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
hmac.workspace = true
sha2.workspace = true
rand.workspace = true
uuid.workspace = true

159
ostp/src/client.rs Normal file
View File

@@ -0,0 +1,159 @@
//! OSTP Client - Stealth tunnel initiator
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::crypto::{AeadCipher, KeyExchange, PskValidator};
use crate::mimicry::{MimicryEngine, TlsHelloBuilder};
use crate::uot::{encode_frame, decode_frame, FrameFlags};
use bytes::BytesMut;
/// Client configuration
#[derive(Clone)]
pub struct ClientConfig {
pub server_addr: SocketAddr,
pub psk: [u8; 32],
pub country_code: String,
}
impl ClientConfig {
pub fn new(server: SocketAddr, psk: [u8; 32], country: impl Into<String>) -> Self {
Self {
server_addr: server,
psk,
country_code: country.into(),
}
}
}
/// OSTP Client with TLS mimicry
pub struct OstpClient {
config: ClientConfig,
psk_validator: PskValidator,
mimicry: MimicryEngine,
cipher: Option<AeadCipher>,
}
impl OstpClient {
pub fn new(config: ClientConfig) -> Self {
let psk_validator = PskValidator::new(config.psk);
Self {
config,
psk_validator,
mimicry: MimicryEngine::new(),
cipher: None,
}
}
/// Connect to OSTP server with silent handshake
pub async fn connect(&mut self) -> anyhow::Result<TcpStream> {
let mut stream = TcpStream::connect(self.config.server_addr).await?;
tracing::info!("Connected to {}", self.config.server_addr);
// Phase 1: Generate keypair and send PSK-signed handshake
let kex = KeyExchange::new();
let client_pubkey = *kex.public_key();
// Sign the public key with PSK
let signature = self.psk_validator.sign(client_pubkey.as_bytes());
// Send: [32-byte signature][32-byte pubkey]
stream.write_all(&signature).await?;
stream.write_all(client_pubkey.as_bytes()).await?;
// Phase 2: Receive server's response
let mut buf = [0u8; 64];
let n = stream.read_exact(&mut buf).await?;
if n < 64 {
anyhow::bail!("Invalid server response");
}
let server_sig: [u8; 32] = buf[..32].try_into()?;
let server_pubkey_bytes: [u8; 32] = buf[32..64].try_into()?;
// Verify server's signature
if !self.psk_validator.verify(&server_pubkey_bytes, &server_sig) {
anyhow::bail!("Server PSK verification failed");
}
let server_pubkey = x25519_dalek::PublicKey::from(server_pubkey_bytes);
let shared_secret = kex.derive_shared(&server_pubkey);
// Derive session key
let session_key = crate::crypto::derive_session_key(&shared_secret, b"ostp-session-v1");
self.cipher = Some(AeadCipher::new(&session_key));
tracing::info!("Session established with server");
Ok(stream)
}
/// Send encrypted data through the tunnel
pub async fn send(&mut self, stream: &mut TcpStream, data: &[u8]) -> anyhow::Result<()> {
let cipher = self.cipher.as_mut().ok_or_else(|| anyhow::anyhow!("Not connected"))?;
let encrypted = cipher.encrypt(data).map_err(|_| anyhow::anyhow!("Encryption failed"))?;
let padding = rand::random::<usize>() % 64; // Random padding 0-63 bytes
let frame = encode_frame(&encrypted, FrameFlags::DATA, padding)?;
stream.write_all(&frame).await?;
Ok(())
}
/// Receive and decrypt data from the tunnel
pub async fn recv(&mut self, stream: &mut TcpStream) -> anyhow::Result<Vec<u8>> {
let cipher = self.cipher.as_ref().ok_or_else(|| anyhow::anyhow!("Not connected"))?;
let mut buf = [0u8; 4096];
let n = stream.read(&mut buf).await?;
if n == 0 {
anyhow::bail!("Connection closed");
}
let mut read_buf = BytesMut::from(&buf[..n]);
if let Some((data, _flags)) = decode_frame(&mut read_buf)? {
let plaintext = cipher.decrypt(&data).map_err(|_| anyhow::anyhow!("Decryption failed"))?;
Ok(plaintext)
} else {
anyhow::bail!("Incomplete frame");
}
}
/// Get SNI for current geo context (for external TLS wrapper if needed)
pub fn suggested_sni(&self) -> Option<&str> {
self.mimicry.random_sni(&self.config.country_code)
}
/// Build TLS ClientHello for the selected SNI
pub fn build_tls_hello(&self) -> Option<Vec<u8>> {
let sni = self.suggested_sni()?;
Some(TlsHelloBuilder::new(sni).build())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_config() {
let addr: SocketAddr = "127.0.0.1:8443".parse().unwrap();
let psk = [0x42u8; 32];
let config = ClientConfig::new(addr, psk, "RU");
assert_eq!(config.country_code, "RU");
}
#[test]
fn test_sni_selection() {
let addr: SocketAddr = "127.0.0.1:8443".parse().unwrap();
let config = ClientConfig::new(addr, [0u8; 32], "RU");
let client = OstpClient::new(config);
let sni = client.suggested_sni();
assert!(sni.is_some());
// Should be one of the Russian domains
let sni = sni.unwrap();
assert!(sni.ends_with(".ru"));
}
}

149
ostp/src/crypto.rs Normal file
View File

@@ -0,0 +1,149 @@
//! OSTP Crypto Module - PSK, X25519, ChaCha20-Poly1305
use chacha20poly1305::{
aead::{Aead, KeyInit, OsRng},
ChaCha20Poly1305, Nonce,
};
use hmac::{digest::KeyInit as HmacKeyInit, Hmac, Mac};
use sha2::Sha256;
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret};
/// AEAD error type re-export
pub type AeadError = chacha20poly1305::aead::Error;
type HmacSha256 = Hmac<Sha256>;
/// PSK-based silent handshake validator
pub struct PskValidator {
psk: [u8; 32],
}
impl PskValidator {
pub fn new(psk: [u8; 32]) -> Self {
Self { psk }
}
/// Generate HMAC signature for packet authentication
pub fn sign(&self, data: &[u8]) -> [u8; 32] {
let mut mac: HmacSha256 = HmacKeyInit::new_from_slice(&self.psk).expect("HMAC key size");
mac.update(data);
mac.finalize().into_bytes().into()
}
/// Verify packet has valid PSK-derived signature (silent drop if invalid)
pub fn verify(&self, data: &[u8], signature: &[u8; 32]) -> bool {
let mut mac: HmacSha256 = HmacKeyInit::new_from_slice(&self.psk).expect("HMAC key size");
mac.update(data);
mac.verify_slice(signature).is_ok()
}
}
/// X25519 key exchange
pub struct KeyExchange {
secret: EphemeralSecret,
public: PublicKey,
}
impl KeyExchange {
pub fn new() -> Self {
let secret = EphemeralSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret);
Self { secret, public }
}
pub fn public_key(&self) -> &PublicKey {
&self.public
}
pub fn derive_shared(self, peer_public: &PublicKey) -> SharedSecret {
self.secret.diffie_hellman(peer_public)
}
}
impl Default for KeyExchange {
fn default() -> Self {
Self::new()
}
}
/// AEAD cipher for data encryption
pub struct AeadCipher {
cipher: ChaCha20Poly1305,
nonce_counter: u64,
}
impl AeadCipher {
pub fn new(key: &[u8; 32]) -> Self {
let cipher = ChaCha20Poly1305::new_from_slice(key).expect("key size");
Self {
cipher,
nonce_counter: 0,
}
}
fn next_nonce(&mut self) -> Nonce {
let mut nonce = [0u8; 12];
nonce[4..12].copy_from_slice(&self.nonce_counter.to_le_bytes());
self.nonce_counter = self.nonce_counter.wrapping_add(1);
Nonce::from(nonce)
}
pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>, AeadError> {
let nonce = self.next_nonce();
let mut ciphertext = self.cipher.encrypt(&nonce, plaintext)?;
// Prepend nonce for decryption
let mut output = nonce.to_vec();
output.append(&mut ciphertext);
Ok(output)
}
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, AeadError> {
if ciphertext.len() < 12 {
return Err(chacha20poly1305::aead::Error);
}
let nonce = Nonce::from_slice(&ciphertext[..12]);
self.cipher.decrypt(nonce, &ciphertext[12..])
}
}
/// Derive session key from shared secret using HKDF-like expansion
pub fn derive_session_key(shared: &SharedSecret, salt: &[u8]) -> [u8; 32] {
let mut mac: HmacSha256 = HmacKeyInit::new_from_slice(salt).expect("HMAC key size");
mac.update(shared.as_bytes());
mac.finalize().into_bytes().into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_psk_sign_verify() {
let psk = [0x42u8; 32];
let validator = PskValidator::new(psk);
let data = b"hello";
let sig = validator.sign(data);
assert!(validator.verify(data, &sig));
}
#[test]
fn test_key_exchange() {
let alice = KeyExchange::new();
let bob = KeyExchange::new();
let alice_pub = *alice.public_key();
let bob_pub = *bob.public_key();
let alice_shared = alice.derive_shared(&bob_pub);
let bob_shared = bob.derive_shared(&alice_pub);
assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
}
#[test]
fn test_aead_encrypt_decrypt() {
let key = [0x11u8; 32];
let mut cipher = AeadCipher::new(&key);
let plaintext = b"secret message";
let ciphertext = cipher.encrypt(plaintext).unwrap();
let decrypted = cipher.decrypt(&ciphertext).unwrap();
assert_eq!(plaintext.as_slice(), decrypted.as_slice());
}
}

11
ostp/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod client;
pub mod crypto;
pub mod mimicry;
pub mod server;
pub mod uot;
pub use client::{ClientConfig, OstpClient};
pub use crypto::{AeadCipher, KeyExchange, PskValidator};
pub use mimicry::{MimicryEngine, TlsHelloBuilder};
pub use server::{OstpServer, ServerConfig};
pub use uot::{decode_frame, encode_frame, FrameFlags, Fragmenter, Reassembler};

174
ostp/src/mimicry.rs Normal file
View File

@@ -0,0 +1,174 @@
//! Dynamic SNI & TLS Mimicry Engine
use std::collections::HashMap;
/// Geo-based SNI target selection
pub struct MimicryEngine {
geo_sni_map: HashMap<String, Vec<String>>,
}
impl MimicryEngine {
pub fn new() -> Self {
let mut geo_sni_map = HashMap::new();
// Default geo-SNI mappings for contextual mimicry
geo_sni_map.insert(
"RU".into(),
vec![
"gosuslugi.ru".into(),
"sberbank.ru".into(),
"yandex.ru".into(),
],
);
geo_sni_map.insert(
"NO".into(),
vec!["bankid.no".into(), "vipps.no".into(), "altinn.no".into()],
);
geo_sni_map.insert(
"DE".into(),
vec![
"sparkasse.de".into(),
"deutsche-bank.de".into(),
"bund.de".into(),
],
);
geo_sni_map.insert(
"US".into(),
vec![
"apple.com".into(),
"microsoft.com".into(),
"amazon.com".into(),
],
);
geo_sni_map.insert(
"CN".into(),
vec!["qq.com".into(), "baidu.com".into(), "taobao.com".into()],
);
Self { geo_sni_map }
}
/// Select SNI based on geo-location
pub fn select_sni(&self, country_code: &str) -> Option<&str> {
self.geo_sni_map
.get(country_code)
.and_then(|list| list.first().map(|s| s.as_str()))
}
/// Get random SNI from geo list for anti-fingerprinting
pub fn random_sni(&self, country_code: &str) -> Option<&str> {
self.geo_sni_map.get(country_code).and_then(|list| {
if list.is_empty() {
None
} else {
let idx = rand::random::<usize>() % list.len();
Some(list[idx].as_str())
}
})
}
/// Add custom geo-SNI mapping
pub fn add_mapping(&mut self, country: String, domains: Vec<String>) {
self.geo_sni_map.insert(country, domains);
}
}
impl Default for MimicryEngine {
fn default() -> Self {
Self::new()
}
}
/// TLS ClientHello builder for REALITY-like mimicry
pub struct TlsHelloBuilder {
sni: String,
random_session_id: bool,
}
impl TlsHelloBuilder {
pub fn new(sni: impl Into<String>) -> Self {
Self {
sni: sni.into(),
random_session_id: true,
}
}
/// Build minimal TLS 1.3 ClientHello-like header
pub fn build(&self) -> Vec<u8> {
let mut hello = Vec::with_capacity(256);
// TLS record header
hello.push(0x16); // Handshake
hello.extend_from_slice(&[0x03, 0x01]); // TLS 1.0 for compat
// Placeholder for length (will update)
let len_pos = hello.len();
hello.extend_from_slice(&[0x00, 0x00]);
// Handshake header
hello.push(0x01); // ClientHello
let hs_len_pos = hello.len();
hello.extend_from_slice(&[0x00, 0x00, 0x00]); // length placeholder
// Client version (TLS 1.2 presented, 1.3 in extensions)
hello.extend_from_slice(&[0x03, 0x03]);
// Random (32 bytes)
let random: [u8; 32] = rand::random();
hello.extend_from_slice(&random);
// Session ID (32 bytes if random)
if self.random_session_id {
hello.push(32);
let session_id: [u8; 32] = rand::random();
hello.extend_from_slice(&session_id);
} else {
hello.push(0);
}
// Cipher suites (TLS 1.3 suites)
hello.extend_from_slice(&[0x00, 0x06]); // 3 suites
hello.extend_from_slice(&[0x13, 0x01]); // TLS_AES_128_GCM_SHA256
hello.extend_from_slice(&[0x13, 0x02]); // TLS_AES_256_GCM_SHA384
hello.extend_from_slice(&[0x13, 0x03]); // TLS_CHACHA20_POLY1305_SHA256
// Compression (null)
hello.extend_from_slice(&[0x01, 0x00]);
// Extensions with SNI
let ext_start = hello.len();
hello.extend_from_slice(&[0x00, 0x00]); // ext length placeholder
// SNI extension
hello.extend_from_slice(&[0x00, 0x00]); // type: server_name
let sni_bytes = self.sni.as_bytes();
let sni_ext_len = sni_bytes.len() + 5;
hello.extend_from_slice(&(sni_ext_len as u16).to_be_bytes());
hello.extend_from_slice(&((sni_bytes.len() + 3) as u16).to_be_bytes());
hello.push(0x00); // host_name type
hello.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
hello.extend_from_slice(sni_bytes);
// supported_versions extension
hello.extend_from_slice(&[0x00, 0x2b]); // type
hello.extend_from_slice(&[0x00, 0x03]); // length
hello.push(0x02); // versions length
hello.extend_from_slice(&[0x03, 0x04]); // TLS 1.3
// Update lengths
let ext_len = hello.len() - ext_start - 2;
hello[ext_start] = (ext_len >> 8) as u8;
hello[ext_start + 1] = ext_len as u8;
let total_len = hello.len() - 5;
hello[len_pos] = (total_len >> 8) as u8;
hello[len_pos + 1] = total_len as u8;
let hs_len = hello.len() - hs_len_pos - 3;
hello[hs_len_pos] = 0;
hello[hs_len_pos + 1] = (hs_len >> 8) as u8;
hello[hs_len_pos + 2] = hs_len as u8;
hello
}
}

192
ostp/src/server.rs Normal file
View File

@@ -0,0 +1,192 @@
//! OSTP Server - Stealth relay endpoint
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::RwLock;
use crate::crypto::{AeadCipher, KeyExchange, PskValidator};
use crate::uot::decode_frame;
use bytes::BytesMut;
/// Server configuration
#[derive(Clone)]
pub struct ServerConfig {
pub listen_addr: SocketAddr,
pub psk: [u8; 32],
pub max_connections: usize,
}
impl ServerConfig {
pub fn new(addr: SocketAddr, psk: [u8; 32]) -> Self {
Self {
listen_addr: addr,
psk,
max_connections: 1024,
}
}
}
/// Active connection state
#[allow(dead_code)]
struct Connection {
cipher: AeadCipher,
user_id: Option<uuid::Uuid>,
bytes_rx: u64,
bytes_tx: u64,
}
/// OSTP Server with silent handshake
pub struct OstpServer {
config: ServerConfig,
#[allow(dead_code)]
psk_validator: PskValidator,
connections: Arc<RwLock<HashMap<SocketAddr, Connection>>>,
}
impl OstpServer {
pub fn new(config: ServerConfig) -> Self {
let psk_validator = PskValidator::new(config.psk);
Self {
config,
psk_validator,
connections: Arc::new(RwLock::new(HashMap::new())),
}
}
/// Start the server (main async loop)
pub async fn run(&self) -> anyhow::Result<()> {
let listener = TcpListener::bind(self.config.listen_addr).await?;
tracing::info!("OSTP server listening on {}", self.config.listen_addr);
loop {
match listener.accept().await {
Ok((stream, addr)) => {
let psk_validator = PskValidator::new(self.config.psk);
let connections = Arc::clone(&self.connections);
tokio::spawn(async move {
if let Err(e) = Self::handle_connection(stream, addr, psk_validator, connections).await {
tracing::debug!("Connection {} closed: {}", addr, e);
}
});
}
Err(e) => {
tracing::warn!("Accept error: {}", e);
}
}
}
}
async fn handle_connection(
mut stream: TcpStream,
addr: SocketAddr,
psk_validator: PskValidator,
connections: Arc<RwLock<HashMap<SocketAddr, Connection>>>,
) -> anyhow::Result<()> {
let mut buf = [0u8; 4096];
// Phase 1: Silent PSK handshake (32-byte signature must be first)
let n = stream.read(&mut buf).await?;
if n < 64 {
// Silent drop - don't reveal port is open
return Ok(());
}
let signature: [u8; 32] = buf[..32].try_into()?;
let payload = &buf[32..n];
if !psk_validator.verify(payload, &signature) {
// Silent drop on invalid PSK - core anti-probing defense
tracing::trace!("Silent drop for {}: invalid PSK", addr);
return Ok(());
}
// Phase 2: X25519 key exchange
// Payload contains client's public key (32 bytes)
if payload.len() < 32 {
return Ok(());
}
let client_pubkey_bytes: [u8; 32] = payload[..32].try_into()?;
let client_pubkey = x25519_dalek::PublicKey::from(client_pubkey_bytes);
let server_kex = KeyExchange::new();
let server_pubkey = *server_kex.public_key();
let shared_secret = server_kex.derive_shared(&client_pubkey);
// Derive session key
let session_key = crate::crypto::derive_session_key(&shared_secret, b"ostp-session-v1");
let cipher = AeadCipher::new(&session_key);
// Send server's public key (signed with PSK)
let server_response = server_pubkey.as_bytes();
let response_sig = psk_validator.sign(server_response);
stream.write_all(&response_sig).await?;
stream.write_all(server_response).await?;
// Store connection
{
let mut conns = connections.write().await;
conns.insert(addr, Connection {
cipher,
user_id: None,
bytes_rx: 0,
bytes_tx: 0,
});
}
tracing::info!("Session established with {}", addr);
// Phase 3: Encrypted relay loop
let mut read_buf = BytesMut::with_capacity(65536);
loop {
let n = stream.read(&mut buf).await?;
if n == 0 {
break;
}
read_buf.extend_from_slice(&buf[..n]);
// Process frames
while let Some((data, flags)) = decode_frame(&mut read_buf)? {
let conns = connections.read().await;
if let Some(conn) = conns.get(&addr) {
match conn.cipher.decrypt(&data) {
Ok(plaintext) => {
if flags.is_data() {
// TODO: Route decrypted data to destination
tracing::trace!("Received {} bytes from {}", plaintext.len(), addr);
}
}
Err(_) => {
tracing::warn!("Decrypt failed for {}", addr);
break;
}
}
}
}
}
// Cleanup
connections.write().await.remove(&addr);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_config() {
let addr: SocketAddr = "127.0.0.1:8443".parse().unwrap();
let psk = [0x42u8; 32];
let config = ServerConfig::new(addr, psk);
assert_eq!(config.listen_addr, addr);
assert_eq!(config.max_connections, 1024);
}
}

132
ostp/src/uot.rs Normal file
View File

@@ -0,0 +1,132 @@
//! UoT (UDP-over-TCP) framing layer
use bytes::{Buf, BufMut, Bytes, BytesMut};
use thiserror::Error;
/// Maximum UDP datagram size
pub const MAX_UDP_SIZE: usize = 65535;
/// Frame header: 2 bytes length + 1 byte flags
const HEADER_SIZE: usize = 3;
#[derive(Error, Debug)]
pub enum FrameError {
#[error("frame too large: {0} bytes")]
TooLarge(usize),
#[error("incomplete frame")]
Incomplete,
#[error("invalid frame")]
Invalid,
}
#[derive(Clone, Copy, Debug)]
pub struct FrameFlags(u8);
impl FrameFlags {
pub const DATA: Self = Self(0x00);
pub const CONTROL: Self = Self(0x01);
pub const PADDING: Self = Self(0x02);
pub fn is_data(self) -> bool {
self.0 == 0x00
}
}
/// Encapsulate UDP datagram into framed format with random padding
pub fn encode_frame(data: &[u8], flags: FrameFlags, padding: usize) -> Result<Bytes, FrameError> {
let total_len = data.len() + padding;
if total_len > MAX_UDP_SIZE {
return Err(FrameError::TooLarge(total_len));
}
let mut buf = BytesMut::with_capacity(HEADER_SIZE + total_len);
buf.put_u16(total_len as u16);
buf.put_u8(flags.0);
buf.put_slice(data);
// Random padding to avoid fingerprinting
if padding > 0 {
let pad: Vec<u8> = (0..padding).map(|_| rand::random()).collect();
buf.put_slice(&pad);
}
Ok(buf.freeze())
}
/// Decode frame from buffer, returns (payload, data_length, flags)
pub fn decode_frame(buf: &mut BytesMut) -> Result<Option<(Bytes, FrameFlags)>, FrameError> {
if buf.len() < HEADER_SIZE {
return Ok(None);
}
let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
let flags = FrameFlags(buf[2]);
if buf.len() < HEADER_SIZE + len {
return Ok(None);
}
buf.advance(HEADER_SIZE);
let data = buf.split_to(len).freeze();
Ok(Some((data, flags)))
}
/// MTU-aware fragmentation
pub struct Fragmenter {
mtu: usize,
}
impl Fragmenter {
pub fn new(mtu: usize) -> Self {
Self { mtu }
}
pub fn fragment(&self, data: &[u8]) -> Vec<Bytes> {
data.chunks(self.mtu)
.map(|chunk| Bytes::copy_from_slice(chunk))
.collect()
}
}
/// Reassembly buffer for fragmented packets
pub struct Reassembler {
buffer: BytesMut,
expected_len: Option<usize>,
}
impl Reassembler {
pub fn new() -> Self {
Self {
buffer: BytesMut::new(),
expected_len: None,
}
}
pub fn push(&mut self, fragment: &[u8], total_len: usize) {
if self.expected_len.is_none() {
self.expected_len = Some(total_len);
}
self.buffer.extend_from_slice(fragment);
}
pub fn is_complete(&self) -> bool {
self.expected_len
.map(|len| self.buffer.len() >= len)
.unwrap_or(false)
}
pub fn take(&mut self) -> Option<Bytes> {
if self.is_complete() {
self.expected_len = None;
Some(self.buffer.split().freeze())
} else {
None
}
}
}
impl Default for Reassembler {
fn default() -> Self {
Self::new()
}
}