diff --git a/reverse-proxy/src/handler/mod.rs b/reverse-proxy/src/handler/mod.rs index 27c8859..215edfa 100644 --- a/reverse-proxy/src/handler/mod.rs +++ b/reverse-proxy/src/handler/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::str::FromStr; use std::sync::{Mutex, MutexGuard}; use log::debug; use ntor::common::{InitSessionMessage, NTorParty}; @@ -126,10 +127,16 @@ impl ReverseHandler { Err(res) => return res, }; + let compression = ctx + .get_request_header() + .get("x-compression") + .map(|val| utils::compression::CompressorVariant::from_str(val).unwrap()); + let wrapped_request = match ProxyHandler::decrypt_request_body( request_body, self.config.ntor_server_id.clone(), shared_secret.clone(), + compression, ) { Ok(req) => req, Err(res) => return res, diff --git a/reverse-proxy/src/handler/proxy/handler.rs b/reverse-proxy/src/handler/proxy/handler.rs index 6db8357..7dda126 100644 --- a/reverse-proxy/src/handler/proxy/handler.rs +++ b/reverse-proxy/src/handler/proxy/handler.rs @@ -79,32 +79,50 @@ impl ProxyHandler { request_body: EncryptedMessage, ntor_server_id: String, shared_secret: Vec, - ) -> Result - { + compression: Option, + ) -> Result { let mut ntor_server = NTorServer::new(ntor_server_id); ntor_server.set_shared_secret(shared_secret.clone()); // Decrypt the request body using nTor shared secret - let decrypted_data = ntor_server.decrypt(ntor::common::EncryptedMessage { - nonce: <[u8; 12]>::try_from(request_body.nonce).unwrap(), - data: request_body.data, - }).map_err(|err| { - return APIHandlerResponse { - status: StatusCode::BAD_REQUEST, - body: Some(format!("Decryption failed: {}", err).as_bytes().to_vec()), - }; - })?; - // let decrypted_data = request_body.data; - - // parse decrypted data into WrappedUserRequest - let wrapped_request: L8RequestObject = bytes_to_json(decrypted_data) + let mut decrypted_data = ntor_server + .decrypt(ntor::common::EncryptedMessage { + nonce: <[u8; 12]>::try_from(request_body.nonce).unwrap(), + data: request_body.data, + }) .map_err(|err| { return APIHandlerResponse { status: StatusCode::BAD_REQUEST, - body: Some(format!("Failed to parse request body: {}", err).as_bytes().to_vec()), + body: Some(format!("Decryption failed: {}", err).as_bytes().to_vec()), }; })?; + // decompress the data if compression is specified + if let Some(variant) = compression { + log::warn!( + "Size of decrypted data before decompression: {}", + decrypted_data.len() + ); + decrypted_data = utils::compression::decompress_data(&variant, &decrypted_data); + log::warn!( + "Size of decrypted data after decompression: {}", + decrypted_data.len() + ); + } + + // parse decrypted data into WrappedUserRequest + let wrapped_request: L8RequestObject = bytes_to_json(decrypted_data).map_err(|err| { + log::error!("Failed to parse request body: {}", err); + return APIHandlerResponse { + status: StatusCode::BAD_REQUEST, + body: Some( + format!("Failed to parse request body: {}", err) + .as_bytes() + .to_vec(), + ), + }; + })?; + Ok(wrapped_request) } diff --git a/spa/frontend/src/main.ts b/spa/frontend/src/main.ts index b90ca9d..0e4450e 100644 --- a/spa/frontend/src/main.ts +++ b/spa/frontend/src/main.ts @@ -3,14 +3,17 @@ import './assets/main.css' import { createApp } from 'vue' import App from './App.vue' import router from './router' -import { initEncryptedTunnel, ServiceProvider } from "interceptor-wasm" +import { initEncryptedTunnel, ServiceProvider, ServiceProviderOptions } from "interceptor-wasm" let forward_proxy_url = import.meta.env.VITE_FORWARD_PROXY_URL || 'http://localhost:6191'; let backend_url = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3000'; const layer8_ = async () => { try { - let providers = [ServiceProvider.new(backend_url)]; + let options = new ServiceProviderOptions(); + options.compression = "gzip"; + + let providers = [ServiceProvider.new(backend_url, options)]; await initEncryptedTunnel(forward_proxy_url, providers).finally(() => { console.log('Encrypted tunnel initialized successfully'); }); diff --git a/utils/Cargo.toml b/utils/Cargo.toml index b8b799d..f54d27a 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -10,3 +10,4 @@ serde_json = "1.0.140" serde = { version = "1.0.219", features = ["derive"] } base64 = "0.21.7" log = "0.4.27" +flate2 = "1.1.2" diff --git a/utils/src/compression.rs b/utils/src/compression.rs new file mode 100644 index 0000000..dd6f8af --- /dev/null +++ b/utils/src/compression.rs @@ -0,0 +1,116 @@ +use std::io::prelude::*; +use std::str::FromStr; + +use flate2::Compression; +use flate2::write::{GzEncoder, ZlibEncoder}; + +#[derive(Debug)] +pub enum CompressorVariant { + Zlib, + Gzip, + // Deflate, // To be used when experimenting with with chunked data compression: +} + +impl CompressorVariant { + pub fn as_str(&self) -> &str { + match self { + CompressorVariant::Zlib => "zlib", + CompressorVariant::Gzip => "gzip", + } + } +} + +impl Default for CompressorVariant { + fn default() -> Self { + CompressorVariant::Zlib + } +} + +impl FromStr for CompressorVariant { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "zlib" => Ok(CompressorVariant::Zlib), + "gzip" => Ok(CompressorVariant::Gzip), + _ => Ok(CompressorVariant::default()), + } + } +} + +pub fn compress_data(variant: &CompressorVariant, data: &[u8]) -> Vec { + match variant { + CompressorVariant::Zlib => { + // Compress using Zlib + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(data) + .expect("Failed to write data to Zlib encoder"); + encoder.finish().expect("Failed to finish Zlib encoding") + } + CompressorVariant::Gzip => { + // Compress using Gzip + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(data) + .expect("Failed to write data to Gzip encoder"); + encoder.finish().expect("Failed to finish Gzip encoding") + } + } +} + +pub fn decompress_data(variant: &CompressorVariant, data: &[u8]) -> Vec { + match variant { + CompressorVariant::Zlib => { + // Decompress using Zlib + let mut decoder = flate2::read::ZlibDecoder::new(data); + let mut decoded_data = Vec::new(); + decoder + .read_to_end(&mut decoded_data) + .expect("Failed to read data from Zlib decoder"); + decoded_data + } + CompressorVariant::Gzip => { + // Decompress using Gzip + let mut decoder = flate2::read::GzDecoder::new(data); + let mut decoded_data = Vec::new(); + decoder + .read_to_end(&mut decoded_data) + .expect("Failed to read data from Gzip decoder"); + decoded_data + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_zlib_compression() { + let data = b"Hello, world!"; + let compressed = compress_data(&CompressorVariant::Zlib, data); + let decompressed = decompress_data(&CompressorVariant::Zlib, &compressed); + assert_eq!(data.to_vec(), decompressed); + } + + #[test] + fn test_gzip_compression() { + let data = b"Hello, world!"; + let compressed = compress_data(&CompressorVariant::Gzip, data); + let decompressed = decompress_data(&CompressorVariant::Gzip, &compressed); + assert_eq!(data.to_vec(), decompressed); + } + + #[test] + fn test_compression_consistency() { + let data = b"Hello, world! This is a test of the compression and decompression functions."; + let compressed_zlib = compress_data(&CompressorVariant::Zlib, data); + let decompressed_zlib = decompress_data(&CompressorVariant::Zlib, &compressed_zlib); + assert_eq!(data.to_vec(), decompressed_zlib); + let compressed_gzip = compress_data(&CompressorVariant::Gzip, data); + let decompressed_gzip = decompress_data(&CompressorVariant::Gzip, &compressed_gzip); + assert_eq!(data.to_vec(), decompressed_gzip); + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index bfeaf42..a51d350 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -7,6 +7,8 @@ use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use serde::{Deserialize, Serialize}; use log::error; +pub mod compression; + pub fn to_reqwest_header(map: HashMap) -> HeaderMap { let mut header_map = HeaderMap::new(); for (k, v) in map {