use crate::config::MimicryMode;
const TLS_APP_DATA: u8 = 0x17;
const TLS_VERSION: [u8; 2] = [0x03, 0x03];
const TLS_HEADER_LEN: usize = 5;
const H2_DATA: u8 = 0x00;
const H2_HEADER_LEN: usize = 9;
const H2_STREAM_ID: u32 = 1;
const DTLS_APP_DATA: u8 = 0x17;
const DTLS_VERSION: [u8; 2] = [0xFE, 0xFD];
const DTLS_HEADER_LEN: usize = 13;
pub struct ProtocolMimicry {
mode: MimicryMode,
}
impl ProtocolMimicry {
pub fn new(mode: MimicryMode) -> Self {
Self { mode }
}
pub fn wrap(&self, raw: &[u8]) -> Vec<u8> {
match self.mode {
MimicryMode::None => raw.to_vec(),
MimicryMode::Https => wrap_tls(raw),
MimicryMode::Grpc => wrap_h2_data(raw),
MimicryMode::WebRtc => wrap_dtls(raw),
}
}
pub fn unwrap(&self, envelope: &[u8]) -> Option<Vec<u8>> {
match self.mode {
MimicryMode::None => Some(envelope.to_vec()),
MimicryMode::Https => unwrap_tls(envelope),
MimicryMode::Grpc => unwrap_h2_data(envelope),
MimicryMode::WebRtc => unwrap_dtls(envelope),
}
}
}
fn wrap_tls(raw: &[u8]) -> Vec<u8> {
let len = raw.len() as u16;
let mut out = Vec::with_capacity(TLS_HEADER_LEN + raw.len());
out.push(TLS_APP_DATA);
out.extend_from_slice(&TLS_VERSION);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(raw);
out
}
fn unwrap_tls(envelope: &[u8]) -> Option<Vec<u8>> {
if envelope.len() < TLS_HEADER_LEN {
return None;
}
if envelope[0] != TLS_APP_DATA || envelope[1..3] != TLS_VERSION {
return None;
}
let payload_len = u16::from_be_bytes([envelope[3], envelope[4]]) as usize;
if envelope.len() != TLS_HEADER_LEN + payload_len {
return None;
}
Some(envelope[TLS_HEADER_LEN..].to_vec())
}
fn wrap_h2_data(raw: &[u8]) -> Vec<u8> {
let len = raw.len() as u32;
let mut out = Vec::with_capacity(H2_HEADER_LEN + raw.len());
out.push((len >> 16) as u8);
out.push((len >> 8) as u8);
out.push(len as u8);
out.push(H2_DATA);
out.push(0x00); out.extend_from_slice(&H2_STREAM_ID.to_be_bytes());
out.extend_from_slice(raw);
out
}
fn unwrap_h2_data(envelope: &[u8]) -> Option<Vec<u8>> {
if envelope.len() < H2_HEADER_LEN {
return None;
}
if envelope[3] != H2_DATA {
return None;
}
let payload_len =
((envelope[0] as usize) << 16) | ((envelope[1] as usize) << 8) | (envelope[2] as usize);
if envelope.len() != H2_HEADER_LEN + payload_len {
return None;
}
Some(envelope[H2_HEADER_LEN..].to_vec())
}
fn wrap_dtls(raw: &[u8]) -> Vec<u8> {
let len = raw.len() as u16;
let mut out = Vec::with_capacity(DTLS_HEADER_LEN + raw.len());
out.push(DTLS_APP_DATA);
out.extend_from_slice(&DTLS_VERSION);
out.extend_from_slice(&[0x00, 0x01]); out.extend_from_slice(&[0x00; 6]); out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(raw);
out
}
fn unwrap_dtls(envelope: &[u8]) -> Option<Vec<u8>> {
if envelope.len() < DTLS_HEADER_LEN {
return None;
}
if envelope[0] != DTLS_APP_DATA || envelope[1..3] != DTLS_VERSION {
return None;
}
let payload_len = u16::from_be_bytes([envelope[11], envelope[12]]) as usize;
if envelope.len() != DTLS_HEADER_LEN + payload_len {
return None;
}
Some(envelope[DTLS_HEADER_LEN..].to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn none_passthrough() {
let m = ProtocolMimicry::new(MimicryMode::None);
let raw = b"transparent";
let w = m.wrap(raw);
assert_eq!(w, raw);
assert_eq!(m.unwrap(&w).unwrap(), raw);
}
#[test]
fn tls_roundtrip() {
let m = ProtocolMimicry::new(MimicryMode::Https);
let raw = b"encrypted-payload";
let w = m.wrap(raw);
assert_eq!(w[0], TLS_APP_DATA);
assert_eq!(&w[1..3], &TLS_VERSION);
let len = u16::from_be_bytes([w[3], w[4]]) as usize;
assert_eq!(len, raw.len());
assert_eq!(w.len(), TLS_HEADER_LEN + raw.len());
assert_eq!(m.unwrap(&w).unwrap(), raw);
}
#[test]
fn tls_rejects_wrong_type() {
let m = ProtocolMimicry::new(MimicryMode::Https);
let mut bad = m.wrap(b"data");
bad[0] = 0x16; assert!(m.unwrap(&bad).is_none());
}
#[test]
fn grpc_roundtrip() {
let m = ProtocolMimicry::new(MimicryMode::Grpc);
let raw = b"grpc-stream-data";
let w = m.wrap(raw);
assert_eq!(w[3], H2_DATA);
let len = ((w[0] as usize) << 16) | ((w[1] as usize) << 8) | (w[2] as usize);
assert_eq!(len, raw.len());
assert_eq!(w.len(), H2_HEADER_LEN + raw.len());
let sid = u32::from_be_bytes([w[5], w[6], w[7], w[8]]);
assert_eq!(sid, H2_STREAM_ID);
assert_eq!(m.unwrap(&w).unwrap(), raw);
}
#[test]
fn grpc_rejects_wrong_frame_type() {
let m = ProtocolMimicry::new(MimicryMode::Grpc);
let mut bad = m.wrap(b"data");
bad[3] = 0x01; assert!(m.unwrap(&bad).is_none());
}
#[test]
fn dtls_roundtrip() {
let m = ProtocolMimicry::new(MimicryMode::WebRtc);
let raw = b"webrtc-datachannel";
let w = m.wrap(raw);
assert_eq!(w[0], DTLS_APP_DATA);
assert_eq!(&w[1..3], &DTLS_VERSION);
assert_eq!(w.len(), DTLS_HEADER_LEN + raw.len());
assert_eq!(m.unwrap(&w).unwrap(), raw);
}
#[test]
fn dtls_rejects_wrong_version() {
let m = ProtocolMimicry::new(MimicryMode::WebRtc);
let mut bad = m.wrap(b"data");
bad[1] = 0x03; assert!(m.unwrap(&bad).is_none());
}
#[test]
fn empty_payload_roundtrip() {
for mode in [
MimicryMode::None,
MimicryMode::Https,
MimicryMode::Grpc,
MimicryMode::WebRtc,
] {
let m = ProtocolMimicry::new(mode);
let w = m.wrap(b"");
assert_eq!(m.unwrap(&w).unwrap(), b"");
}
}
#[test]
fn truncated_envelope_rejected() {
let m = ProtocolMimicry::new(MimicryMode::Https);
assert!(m.unwrap(&[0x17, 0x03]).is_none()); assert!(m.unwrap(&[0x17, 0x03, 0x03, 0x00, 0x05, 0x01]).is_none());
}
}