use crate::shadow_tls::parse_server_hello;
const TLS_CONTENT_TYPE_HANDSHAKE: u8 = 0x16;
const TLS_HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
const TLS_HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
const TLS13_CIPHER_AES_128_CCM_8_SHA256: u16 = 0x1305;
#[derive(Debug)]
pub struct VisionFilter {
record_filter_count: usize,
is_tls: bool,
is_tls12_or_above: bool,
supports_xtls: bool,
}
impl VisionFilter {
pub fn new() -> Self {
Self::new_with_record_count(8)
}
fn new_with_record_count(record_count: usize) -> Self {
Self {
record_filter_count: record_count,
supports_xtls: false,
is_tls: false,
is_tls12_or_above: false,
}
}
pub fn is_filtering(&self) -> bool {
self.record_filter_count > 0
}
pub fn decrement_filter_count(&mut self) {
self.record_filter_count = self.record_filter_count.saturating_sub(1);
}
pub fn stop_filtering(&mut self, reason: String) {
log::debug!("VISION: Stopping filtering - {}", reason);
self.record_filter_count = 0;
}
pub fn is_tls(&self) -> bool {
self.is_tls
}
pub fn is_tls12_or_above(&self) -> bool {
self.is_tls12_or_above
}
pub fn supports_xtls(&self) -> bool {
self.supports_xtls
}
pub fn filter_record(&mut self, data: &[u8]) {
if self.record_filter_count == 0 {
return;
}
self.record_filter_count = self.record_filter_count.saturating_sub(1);
if data.len() < 5 {
self.stop_filtering(format!("invalid record length: {}", data.len()));
return;
}
if !self.is_tls
&& data.len() >= 6
&& data[0] == TLS_CONTENT_TYPE_HANDSHAKE
&& data[1] == 0x03 && data[5] == TLS_HANDSHAKE_TYPE_CLIENT_HELLO
{
self.is_tls = true;
log::debug!("VISION: Detected TLS ClientHello");
}
if !self.is_tls12_or_above
&& data.len() >= 6
&& data[0] == TLS_CONTENT_TYPE_HANDSHAKE
&& data[1] == 0x03 && data[2] == 0x03 && data[5] == TLS_HANDSHAKE_TYPE_SERVER_HELLO
{
self.is_tls12_or_above = true;
self.is_tls = true;
match parse_server_hello(data) {
Ok(parsed) => {
log::debug!(
"VISION: Detected TLS cipher suite: 0x{:04x}",
parsed.cipher_suite
);
if parsed.is_tls13 {
log::debug!("VISION: Detected TLS 1.3 via supported_versions extension");
if parsed.cipher_suite == TLS13_CIPHER_AES_128_CCM_8_SHA256 {
log::warn!(
"VISION: TLS 1.3 detected but cipher 0x{:04x} (TLS_AES_128_CCM_8_SHA256) not supported for XTLS",
parsed.cipher_suite
);
} else {
self.supports_xtls = true;
log::debug!(
"VISION: TLS 1.3 with supported cipher 0x{:04x} - XTLS enabled",
parsed.cipher_suite
);
}
self.stop_filtering("TLS 1.3 handshake detected".to_string());
}
}
Err(e) => {
self.stop_filtering(format!("invalid ServerHello: {}", e));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vision_filter_initialization() {
let filter = VisionFilter::new();
assert!(filter.is_filtering());
assert!(!filter.supports_xtls());
assert!(!filter.is_tls());
assert!(!filter.is_tls12_or_above());
}
#[test]
fn test_vision_filter_with_custom_record_count() {
let filter = VisionFilter::new_with_record_count(5);
assert!(filter.is_filtering());
assert!(!filter.supports_xtls());
assert!(!filter.is_tls());
}
#[test]
fn test_stop_filtering() {
let mut filter = VisionFilter::new();
assert!(filter.is_filtering());
filter.stop_filtering("test".to_string());
assert!(!filter.is_filtering());
}
#[test]
fn test_detect_client_hello() {
let mut filter = VisionFilter::new();
let client_hello = vec![
0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, ];
filter.filter_record(&client_hello);
assert!(filter.is_tls());
assert!(!filter.supports_xtls()); }
#[test]
fn test_detect_server_hello_tls12() {
let mut filter = VisionFilter::new();
let server_hello = vec![
0x16, 0x03, 0x03, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, ];
filter.filter_record(&server_hello);
assert!(filter.is_tls());
assert!(filter.is_tls12_or_above());
}
#[test]
fn test_detect_application_data() {
let mut filter = VisionFilter::new();
let client_hello = vec![
0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00,
];
filter.filter_record(&client_hello);
let app_data = vec![
0x17, 0x03, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, ];
filter.filter_record(&app_data);
assert!(filter.is_tls());
assert!(filter.is_filtering());
}
#[test]
fn test_record_count_decrements() {
let mut filter = VisionFilter::new_with_record_count(3);
let dummy_record = vec![0x16, 0x03, 0x01, 0x00, 0x05, 0x00];
filter.filter_record(&dummy_record);
assert!(filter.is_filtering());
filter.filter_record(&dummy_record);
assert!(filter.is_filtering());
filter.filter_record(&dummy_record);
assert!(!filter.is_filtering());
filter.filter_record(&dummy_record);
assert!(!filter.is_filtering());
}
#[test]
fn test_filter_record_no_processing_when_complete() {
let mut filter = VisionFilter::new();
filter.stop_filtering("test".to_string());
let data = vec![0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00];
filter.filter_record(&data);
assert!(!filter.is_tls()); assert!(!filter.is_filtering());
}
#[test]
fn test_short_record_ignored() {
let mut filter = VisionFilter::new();
let short_record = vec![0x16, 0x03, 0x01];
filter.filter_record(&short_record);
assert!(!filter.is_tls()); assert!(!filter.is_filtering()); }
#[test]
fn test_non_tls_record_ignored() {
let mut filter = VisionFilter::new();
let non_tls_record = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
filter.filter_record(&non_tls_record);
assert!(!filter.is_tls());
assert!(!filter.supports_xtls());
assert!(filter.is_filtering());
}
#[test]
fn test_filter_record_client_hello() {
let mut filter = VisionFilter::new();
let client_hello = vec![0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00];
filter.filter_record(&client_hello);
assert!(filter.is_tls());
assert!(!filter.supports_xtls()); }
#[test]
fn test_filter_record_server_hello() {
let mut filter = VisionFilter::new();
let server_hello = vec![0x16, 0x03, 0x03, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00];
filter.filter_record(&server_hello);
assert!(filter.is_tls());
assert!(filter.is_tls12_or_above());
}
#[test]
fn test_client_hello_only_detected_once() {
let mut filter = VisionFilter::new();
let client_hello = vec![0x16, 0x03, 0x01, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00];
filter.filter_record(&client_hello);
assert!(filter.is_tls());
assert!(filter.is_filtering());
filter.filter_record(&client_hello);
assert!(filter.is_tls());
assert!(filter.is_filtering());
}
#[test]
fn test_server_hello_only_detected_once() {
let mut filter = VisionFilter::new();
let server_hello = vec![0x16, 0x03, 0x03, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00];
filter.filter_record(&server_hello);
assert!(filter.is_tls12_or_above());
assert!(!filter.is_filtering());
}
}