use bytes::{Bytes, BytesMut};
use std::io;
const TLS_RECORD_HEADER_SIZE: usize = 5;
const MAX_TLS_CIPHERTEXT_LEN: usize = 16384 + 2048;
pub const TLS_MAX_RECORD_SIZE: usize = MAX_TLS_CIPHERTEXT_LEN + TLS_RECORD_HEADER_SIZE;
const TLS_PROTOCOL_VERSION_MAJOR: u8 = 0x03;
const TLS_PROTOCOL_VERSION_MINOR_MIN: u8 = 0x01;
const TLS_PROTOCOL_VERSION_MINOR_MAX: u8 = 0x03;
#[derive(Debug, PartialEq)]
pub struct TlsDeframer {
buffer: BytesMut,
state: DeframerState,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum DeframerState {
ReadingHeader,
ReadingPayload { payload_len: usize },
}
impl TlsDeframer {
pub fn new() -> Self {
Self {
buffer: BytesMut::with_capacity(TLS_MAX_RECORD_SIZE),
state: DeframerState::ReadingHeader,
}
}
pub fn feed(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
pub fn next_record(&mut self) -> io::Result<Option<Bytes>> {
loop {
match self.state {
DeframerState::ReadingHeader => {
if self.buffer.len() < TLS_RECORD_HEADER_SIZE {
return Ok(None);
}
let content_type = self.buffer[0];
let version_major = self.buffer[1];
let version_minor = self.buffer[2];
let payload_len = u16::from_be_bytes([self.buffer[3], self.buffer[4]]) as usize;
if version_major != TLS_PROTOCOL_VERSION_MAJOR
|| !(TLS_PROTOCOL_VERSION_MINOR_MIN..=TLS_PROTOCOL_VERSION_MINOR_MAX)
.contains(&version_minor)
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Invalid TLS protocol version: 0x{:02x}{:02x} (expected 0x0301 to 0x0303)",
version_major, version_minor
),
));
}
if payload_len > MAX_TLS_CIPHERTEXT_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"TLS record ciphertext length {} exceeds maximum {} (TLS 1.2 limit)",
payload_len, MAX_TLS_CIPHERTEXT_LEN
),
));
}
if !(0x14..=0x18).contains(&content_type) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid TLS content type: 0x{:02x}", content_type),
));
}
self.state = DeframerState::ReadingPayload { payload_len };
}
DeframerState::ReadingPayload { payload_len } => {
let total_len = TLS_RECORD_HEADER_SIZE + payload_len;
if self.buffer.len() < total_len {
return Ok(None);
}
let record = self.buffer.split_to(total_len).freeze();
self.state = DeframerState::ReadingHeader;
return Ok(Some(record));
}
}
}
}
pub fn pending_bytes(&self) -> usize {
self.buffer.len()
}
pub fn next_records(&mut self) -> io::Result<Vec<Bytes>> {
let mut records = Vec::new();
while let Some(record) = self.next_record()? {
records.push(record);
}
Ok(records)
}
pub fn into_remaining_data(self) -> Bytes {
self.buffer.freeze()
}
pub fn remaining_data(&self) -> &[u8] {
&self.buffer
}
pub fn clear(&mut self) {
self.buffer.clear();
self.state = DeframerState::ReadingHeader;
}
pub fn deallocate(&mut self) {
drop(std::mem::take(&mut self.buffer));
}
}
impl Default for TlsDeframer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_tls_record(content_type: u8, payload: &[u8]) -> Vec<u8> {
let mut record = Vec::new();
record.push(content_type);
record.push(0x03); record.push(0x03); let len = payload.len() as u16;
record.extend_from_slice(&len.to_be_bytes());
record.extend_from_slice(payload);
record
}
#[test]
fn test_single_complete_packet() {
let mut deframer = TlsDeframer::new();
let payload = b"Hello, TLS!";
let record = make_tls_record(0x17, payload);
deframer.feed(&record);
let read_record = deframer.next_record().unwrap();
assert!(read_record.is_some());
let read_record = read_record.unwrap();
assert_eq!(read_record, &record[..]);
assert!(deframer.next_record().unwrap().is_none());
}
#[test]
fn test_partial_packet_split_header() {
let mut deframer = TlsDeframer::new();
let payload = b"Partial header test";
let record = make_tls_record(0x16, payload);
deframer.feed(&record[..3]);
assert!(deframer.next_record().unwrap().is_none());
deframer.feed(&record[3..5]);
assert!(deframer.next_record().unwrap().is_none());
deframer.feed(&record[5..]);
let extracted_record = deframer.next_record().unwrap();
assert!(extracted_record.is_some());
assert_eq!(extracted_record.unwrap(), &record[..]);
}
#[test]
fn test_partial_packet_split_payload() {
let mut deframer = TlsDeframer::new();
let payload = vec![0x42; 100]; let record = make_tls_record(0x17, &payload);
let split_point = 5 + 50;
deframer.feed(&record[..split_point]);
assert!(deframer.next_record().unwrap().is_none());
deframer.feed(&record[split_point..]);
let extracted_record = deframer.next_record().unwrap();
assert!(extracted_record.is_some());
assert_eq!(extracted_record.unwrap(), &record[..]);
}
#[test]
fn test_multiple_packets_at_once() {
let mut deframer = TlsDeframer::new();
let record1 = make_tls_record(0x16, b"First");
let record2 = make_tls_record(0x17, b"Second");
let record3 = make_tls_record(0x16, b"Third");
let mut combined = Vec::new();
combined.extend_from_slice(&record1);
combined.extend_from_slice(&record2);
combined.extend_from_slice(&record3);
deframer.feed(&combined);
let rec1 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec1, &record1[..]);
let rec2 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec2, &record2[..]);
let rec3 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec3, &record3[..]);
assert!(deframer.next_record().unwrap().is_none());
}
#[test]
fn test_multiple_packets_partial_boundary() {
let mut deframer = TlsDeframer::new();
let record1 = make_tls_record(0x16, b"Record One");
let record2 = make_tls_record(0x17, b"Record Two");
let mut combined = Vec::new();
combined.extend_from_slice(&record1);
combined.extend_from_slice(&record2);
let split = record1.len() + 8;
deframer.feed(&combined[..split]);
let rec1 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec1, &record1[..]);
assert!(deframer.next_record().unwrap().is_none());
deframer.feed(&combined[split..]);
let rec2 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec2, &record2[..]);
}
#[test]
fn test_tls10_accepted() {
let mut deframer = TlsDeframer::new();
let mut record = vec![0x16, 0x03, 0x01, 0x00, 0x05]; record.extend_from_slice(b"Hello");
deframer.feed(&record);
let result = deframer.next_record();
assert!(result.is_ok());
assert_eq!(result.unwrap().unwrap(), &record[..]);
}
#[test]
fn test_invalid_protocol_version() {
let mut deframer = TlsDeframer::new();
let mut record = vec![0x16, 0x02, 0x00, 0x00, 0x05]; record.extend_from_slice(b"Hello");
deframer.feed(&record);
let result = deframer.next_record();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Invalid TLS protocol version")
);
}
#[test]
fn test_invalid_content_type() {
let mut deframer = TlsDeframer::new();
let mut record = vec![0xFF, 0x03, 0x03, 0x00, 0x05]; record.extend_from_slice(b"Hello");
deframer.feed(&record);
let result = deframer.next_record();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Invalid TLS content type")
);
}
#[test]
fn test_excessive_payload_length() {
let mut deframer = TlsDeframer::new();
let record = vec![0x17, 0x03, 0x03, 0xFF, 0xFF];
deframer.feed(&record);
let result = deframer.next_record();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("exceeds maximum"));
}
#[test]
fn test_byte_by_byte_feed() {
let mut deframer = TlsDeframer::new();
let record = make_tls_record(0x16, b"Byte by byte");
for byte in &record[..record.len() - 1] {
deframer.feed(&[*byte]);
assert!(deframer.next_record().unwrap().is_none());
}
deframer.feed(&[record[record.len() - 1]]);
let extracted_record = deframer.next_record().unwrap().unwrap();
assert_eq!(extracted_record, &record[..]);
}
#[test]
fn test_clear() {
let mut deframer = TlsDeframer::new();
let record = make_tls_record(0x16, b"Test");
deframer.feed(&record[..3]);
assert_eq!(deframer.pending_bytes(), 3);
deframer.clear();
assert_eq!(deframer.pending_bytes(), 0);
assert!(deframer.next_record().unwrap().is_none());
}
#[test]
fn test_empty_payload() {
let mut deframer = TlsDeframer::new();
let record = make_tls_record(0x16, b"");
deframer.feed(&record);
let extracted_record = deframer.next_record().unwrap().unwrap();
assert_eq!(extracted_record.len(), 5); assert_eq!(extracted_record, &record[..]);
}
#[test]
fn test_max_size_payload() {
let mut deframer = TlsDeframer::new();
let payload = vec![0xAA; 18432];
let record = make_tls_record(0x17, &payload);
deframer.feed(&record);
let extracted_record = deframer.next_record().unwrap().unwrap();
assert_eq!(extracted_record, &record[..]);
}
#[test]
fn test_reuse_after_consume() {
let mut deframer = TlsDeframer::new();
let record1 = make_tls_record(0x16, b"First");
let record2 = make_tls_record(0x17, b"Second");
deframer.feed(&record1);
let rec1 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec1, &record1[..]);
deframer.feed(&record2);
let rec2 = deframer.next_record().unwrap().unwrap();
assert_eq!(rec2, &record2[..]);
assert_eq!(deframer.pending_bytes(), 0);
}
#[test]
fn test_next_records_empty() {
let mut deframer = TlsDeframer::new();
let records = deframer.next_records().unwrap();
assert!(records.is_empty());
}
#[test]
fn test_next_records_single() {
let mut deframer = TlsDeframer::new();
let record = make_tls_record(0x16, b"Single");
deframer.feed(&record);
let records = deframer.next_records().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0], &record[..]);
}
#[test]
fn test_next_records_multiple() {
let mut deframer = TlsDeframer::new();
let record1 = make_tls_record(0x16, b"First");
let record2 = make_tls_record(0x17, b"Second");
let record3 = make_tls_record(0x16, b"Third");
let mut combined = Vec::new();
combined.extend_from_slice(&record1);
combined.extend_from_slice(&record2);
combined.extend_from_slice(&record3);
deframer.feed(&combined);
let records = deframer.next_records().unwrap();
assert_eq!(records.len(), 3);
assert_eq!(records[0], &record1[..]);
assert_eq!(records[1], &record2[..]);
assert_eq!(records[2], &record3[..]);
assert_eq!(deframer.pending_bytes(), 0);
}
#[test]
fn test_next_records_partial_remaining() {
let mut deframer = TlsDeframer::new();
let record1 = make_tls_record(0x16, b"Complete");
let record2 = make_tls_record(0x17, b"Incomplete");
let mut combined = Vec::new();
combined.extend_from_slice(&record1);
combined.extend_from_slice(&record2[..10]);
deframer.feed(&combined);
let records = deframer.next_records().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0], &record1[..]);
assert_eq!(deframer.pending_bytes(), 10);
}
#[test]
fn test_next_records_error_propagation() {
let mut deframer = TlsDeframer::new();
let mut bad_record = vec![0xFF, 0x03, 0x03, 0x00, 0x05];
bad_record.extend_from_slice(b"Hello");
deframer.feed(&bad_record);
let result = deframer.next_records();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Invalid TLS content type")
);
}
#[test]
fn test_next_records_mixed_complete_and_partial() {
let mut deframer = TlsDeframer::new();
let record1 = make_tls_record(0x16, b"First");
let record2 = make_tls_record(0x17, b"Second");
deframer.feed(&record1);
deframer.feed(&record2[..8]);
let records = deframer.next_records().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0], &record1[..]);
deframer.feed(&record2[8..]);
let records = deframer.next_records().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0], &record2[..]);
}
}