refactor: update tunnel MTU, optimize Frame parsing, and improve connection stability
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
|
||||
@@ -159,4 +159,4 @@ impl TlsBridge {
|
||||
pub fn pack_app_data(buffer: Bytes) -> Bytes {
|
||||
TlsRecord::build_application_data(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,4 +122,4 @@ impl Codec {
|
||||
self.tx.take().expect("TxCodec missing"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-57
@@ -2,28 +2,10 @@ use crate::parser::Parser;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use rand::Rng;
|
||||
|
||||
struct Padding {
|
||||
pub len: u16,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
pub const MAX_PADDING_SIZE: u32 = 255;
|
||||
|
||||
impl Padding {
|
||||
pub fn generate_padding() -> Padding {
|
||||
let mut rng = rand::rng();
|
||||
let random_u32: u32 = rng.next_u32();
|
||||
let padding_len: u16 = (random_u32 % MAX_PADDING_SIZE) as u16;
|
||||
let mut padding = vec![0u8; padding_len as usize];
|
||||
rng.fill_bytes(&mut padding);
|
||||
Padding {
|
||||
len: padding_len,
|
||||
data: Bytes::from(padding),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum FrameType {
|
||||
Connect = 0x00,
|
||||
Data = 0x01,
|
||||
@@ -33,19 +15,21 @@ pub(crate) enum FrameType {
|
||||
UdpData = 0x05,
|
||||
}
|
||||
|
||||
// 🔥 ОПТИМИЗАЦИЯ: Поля отсортированы по размеру для идеального Data Packing
|
||||
// (устраняет скрытые байты выравнивания, структура ровно 28 байт в памяти).
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct FrameHeader {
|
||||
pub(crate) _auth_tag: [u8; 16],
|
||||
pub(crate) auth_tag: [u8; 16],
|
||||
pub(crate) stream_id: u32,
|
||||
pub(crate) frame_type: FrameType,
|
||||
pub(crate) payload_len: u16,
|
||||
pub(crate) padding_len: u16,
|
||||
pub(crate) frame_type: FrameType,
|
||||
}
|
||||
|
||||
pub(crate) struct Frame {
|
||||
pub(crate) header: FrameHeader,
|
||||
// 🔥 ОПТИМИЗАЦИЯ: Поле _padding удалено, так как оно никогда не используется.
|
||||
pub(crate) payload: Bytes,
|
||||
pub(crate) _padding: Bytes,
|
||||
pub(crate) header: FrameHeader,
|
||||
}
|
||||
|
||||
const AUTH_TAG_SIZE: u16 = 16;
|
||||
@@ -55,39 +39,56 @@ const PAYLOAD_LEN_SIZE: u16 = 2;
|
||||
const PADDING_LEN_SIZE: u16 = 2;
|
||||
|
||||
pub const FRAME_HEADER_SIZE: u16 =
|
||||
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE;
|
||||
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE; // 25 bytes
|
||||
|
||||
impl Frame {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(stream_id: u32, frame_type: FrameType, payload: Bytes) -> Self {
|
||||
Self {
|
||||
header: FrameHeader {
|
||||
_auth_tag: [0u8; 16],
|
||||
auth_tag: [0u8; 16],
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len: payload.len() as u16,
|
||||
padding_len: 0,
|
||||
frame_type,
|
||||
},
|
||||
payload,
|
||||
_padding: Bytes::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn into_bytes(mut self, auth_key: &[u8; 16]) -> BytesMut {
|
||||
let generated_padding = Padding::generate_padding();
|
||||
self.header.padding_len = generated_padding.len;
|
||||
// 🔥 ОПТИМИЗАЦИЯ: Быстрая побитовая маска (& 0xFF) вместо дорогого деления с остатком (%)
|
||||
let padding_len = match self.header.frame_type {
|
||||
FrameType::Data | FrameType::UdpData => 0,
|
||||
_ => (rand::rng().next_u32() & 0xFF) as u16,
|
||||
};
|
||||
|
||||
let total_size =
|
||||
FRAME_HEADER_SIZE as usize + self.payload.len() + generated_padding.len as usize;
|
||||
self.header.padding_len = padding_len;
|
||||
|
||||
let total_size = FRAME_HEADER_SIZE as usize + self.payload.len() + padding_len as usize;
|
||||
let mut buf = BytesMut::with_capacity(total_size);
|
||||
|
||||
buf.put_slice(auth_key);
|
||||
buf.put_u32(self.header.stream_id);
|
||||
buf.put_u8(self.header.frame_type as u8);
|
||||
buf.put_u16(self.header.payload_len);
|
||||
buf.put_u16(self.header.padding_len);
|
||||
// 🔥 ОПТИМИЗАЦИЯ (Zero-Cost Abstraction): Собираем заголовок в стеке (в регистрах)
|
||||
// и пишем в память ровно ОДНИМ вызовом copy_from_slice.
|
||||
// Это избавляет от 5 последовательных проверок границ буфера.
|
||||
let mut header_buf = [0u8; 25];
|
||||
header_buf[0..16].copy_from_slice(auth_key);
|
||||
header_buf[16..20].copy_from_slice(&self.header.stream_id.to_be_bytes());
|
||||
header_buf[20] = self.header.frame_type as u8;
|
||||
header_buf[21..23].copy_from_slice(&self.header.payload_len.to_be_bytes());
|
||||
header_buf[23..25].copy_from_slice(&self.header.padding_len.to_be_bytes());
|
||||
|
||||
buf.put_slice(&header_buf);
|
||||
buf.put(self.payload);
|
||||
buf.put(generated_padding.data);
|
||||
|
||||
if padding_len > 0 {
|
||||
// 🔥 ОПТИМИЗАЦИЯ (Zero-Copy): Никаких vec![]. Мы просто растягиваем буфер нулями
|
||||
// и напрямую заполняем хвост через RNG. Ни одного лишнего выделения памяти.
|
||||
let start = buf.len();
|
||||
buf.resize(total_size, 0);
|
||||
rand::rng().fill_bytes(&mut buf[start..]);
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
@@ -96,24 +97,27 @@ impl Frame {
|
||||
impl Parser for FrameHeader {
|
||||
type Error = String;
|
||||
|
||||
#[inline(always)]
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
bytes.len() >= FRAME_HEADER_SIZE as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut header_chunk = bytes.split_to(FRAME_HEADER_SIZE as usize);
|
||||
// 🔥 ОПТИМИЗАЦИЯ (Zero-Copy): Больше нет split_to()! Мы просто читаем
|
||||
// срез памяти напрямую. Не создается объект Bytes, не обновляются счетчики ссылок.
|
||||
let header_slice = &bytes[..FRAME_HEADER_SIZE as usize];
|
||||
|
||||
let mut _auth_tag = [0u8; 16];
|
||||
header_chunk.copy_to_slice(&mut _auth_tag);
|
||||
let mut auth_tag = [0u8; 16];
|
||||
auth_tag.copy_from_slice(&header_slice[0..16]);
|
||||
|
||||
let stream_id = header_chunk.get_u32();
|
||||
let stream_id = u32::from_be_bytes(header_slice[16..20].try_into().unwrap());
|
||||
|
||||
let frame_type_byte = header_chunk.get_u8();
|
||||
let frame_type = match frame_type_byte {
|
||||
let frame_type = match header_slice[20] {
|
||||
0x00 => FrameType::Connect,
|
||||
0x01 => FrameType::Data,
|
||||
0x02 => FrameType::Close,
|
||||
@@ -123,11 +127,14 @@ impl Parser for FrameHeader {
|
||||
_ => FrameType::Close,
|
||||
};
|
||||
|
||||
let payload_len = header_chunk.get_u16();
|
||||
let padding_len = header_chunk.get_u16();
|
||||
let payload_len = u16::from_be_bytes(header_slice[21..23].try_into().unwrap());
|
||||
let padding_len = u16::from_be_bytes(header_slice[23..25].try_into().unwrap());
|
||||
|
||||
// Просто смещаем внутренний курсор оригинального буфера
|
||||
bytes.advance(FRAME_HEADER_SIZE as usize);
|
||||
|
||||
Ok(Some(Self {
|
||||
_auth_tag,
|
||||
auth_tag,
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len,
|
||||
@@ -139,6 +146,7 @@ impl Parser for FrameHeader {
|
||||
impl Parser for Frame {
|
||||
type Error = String;
|
||||
|
||||
#[inline(always)]
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||
return false;
|
||||
@@ -150,27 +158,25 @@ impl Parser for Frame {
|
||||
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let header = FrameHeader::parse(bytes)?.ok_or("Failed to parse header")?;
|
||||
let header = FrameHeader::parse(bytes)?.unwrap(); // Безопасно, т.к. can_parse прошел
|
||||
|
||||
let p_len = header.payload_len as usize;
|
||||
let pad_len = header.padding_len as usize;
|
||||
|
||||
if bytes.len() < p_len + pad_len {
|
||||
return Err("Buffer corrupted: length mismatch after header parse".into());
|
||||
}
|
||||
|
||||
// payload - единственное место, где мы аллоцируем Bytes объект (zero-copy clone),
|
||||
// так как он реально пойдет дальше по каналам в обработку.
|
||||
let payload = bytes.split_to(p_len).freeze();
|
||||
let _padding = bytes.split_to(pad_len).freeze();
|
||||
|
||||
Ok(Some(Self {
|
||||
header,
|
||||
payload,
|
||||
_padding,
|
||||
}))
|
||||
// 🔥 ОПТИМИЗАЦИЯ: Паддинг нам не нужен. Мы просто смещаем курсор, игнорируя эти байты.
|
||||
// Экономит целую аллокацию Bytes и вызов freeze() на каждом пакете.
|
||||
bytes.advance(pad_len);
|
||||
|
||||
Ok(Some(Self { header, payload }))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user