use crate::proxy::types::{ChunkOffset, RequestId};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct BufferedData {
chunks: Vec<(ChunkOffset, Vec<u8>)>,
total_size: usize,
}
impl BufferedData {
fn new() -> Self {
Self {
chunks: Vec::new(),
total_size: 0,
}
}
pub fn add_chunk(&mut self, offset: ChunkOffset, data: Vec<u8>) {
self.total_size = (*offset.as_ref() + data.len()).max(self.total_size);
self.chunks.push((offset, data));
}
pub fn set_total_size(&mut self, size: usize) {
self.total_size = size;
}
pub fn is_complete(&self) -> bool {
if self.chunks.is_empty() {
return false;
}
if self.total_size == 0 {
return false;
}
let mut sorted_chunks = self.chunks.clone();
sorted_chunks.sort_by_key(|(offset, _)| *offset.as_ref());
let mut current_pos = 0;
for (offset, data) in &sorted_chunks {
if *offset.as_ref() != current_pos {
return false; }
current_pos += data.len();
}
current_pos == self.total_size
}
pub fn reconstruct(&self) -> Option<Vec<u8>> {
if !self.is_complete() {
return None;
}
let mut sorted_chunks = self.chunks.clone();
sorted_chunks.sort_by_key(|(offset, _)| *offset.as_ref());
let mut result = Vec::with_capacity(self.total_size);
for (_, data) in sorted_chunks {
result.extend_from_slice(&data);
}
Some(result)
}
}
pub struct AuditBufferManager {
request_buffers: HashMap<RequestId, BufferedData>,
response_buffers: HashMap<RequestId, BufferedData>,
}
impl AuditBufferManager {
pub fn new() -> Self {
Self {
request_buffers: HashMap::new(),
response_buffers: HashMap::new(),
}
}
pub fn add_request_chunk(&mut self, request_id: RequestId, offset: ChunkOffset, data: Vec<u8>) {
self.request_buffers
.entry(request_id)
.or_insert_with(BufferedData::new)
.add_chunk(offset, data);
}
pub fn add_response_chunk(
&mut self,
request_id: RequestId,
offset: ChunkOffset,
data: Vec<u8>,
) {
self.response_buffers
.entry(request_id)
.or_insert_with(BufferedData::new)
.add_chunk(offset, data);
}
pub fn get_complete_request_body(&mut self, request_id: &RequestId) -> Option<Vec<u8>> {
if let Some(buffer) = self.request_buffers.get(request_id) {
if let Some(data) = buffer.reconstruct() {
self.request_buffers.remove(request_id);
return Some(data);
}
}
None
}
pub fn get_complete_response_body(&mut self, request_id: &RequestId) -> Option<Vec<u8>> {
if let Some(buffer) = self.response_buffers.get(request_id) {
if let Some(data) = buffer.reconstruct() {
self.response_buffers.remove(request_id);
return Some(data);
}
}
None
}
pub fn cleanup_request(&mut self, request_id: &RequestId) {
self.request_buffers.remove(request_id);
self.response_buffers.remove(request_id);
}
}
impl Default for AuditBufferManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffered_data_complete() {
let mut buffer = BufferedData::new();
buffer.add_chunk(ChunkOffset::from(0), vec![1, 2, 3]);
buffer.add_chunk(ChunkOffset::from(3), vec![4, 5, 6]);
assert!(buffer.is_complete());
assert_eq!(buffer.reconstruct(), Some(vec![1, 2, 3, 4, 5, 6]));
}
#[test]
fn test_buffered_data_with_gap() {
let mut buffer = BufferedData::new();
buffer.add_chunk(ChunkOffset::from(0), vec![1, 2, 3]);
buffer.add_chunk(ChunkOffset::from(6), vec![7, 8, 9]);
assert!(!buffer.is_complete());
assert_eq!(buffer.reconstruct(), None);
}
#[test]
fn test_buffered_data_out_of_order() {
let mut buffer = BufferedData::new();
buffer.add_chunk(ChunkOffset::from(3), vec![4, 5, 6]);
buffer.add_chunk(ChunkOffset::from(0), vec![1, 2, 3]);
assert!(buffer.is_complete());
assert_eq!(buffer.reconstruct(), Some(vec![1, 2, 3, 4, 5, 6]));
}
#[test]
fn test_audit_buffer_manager() {
let mut manager = AuditBufferManager::new();
let request_id = RequestId::new();
manager.add_request_chunk(request_id, ChunkOffset::from(0), vec![1, 2, 3]);
manager.add_response_chunk(request_id, ChunkOffset::from(0), vec![4, 5, 6]);
manager.cleanup_request(&request_id);
assert!(manager.get_complete_request_body(&request_id).is_none());
assert!(manager.get_complete_response_body(&request_id).is_none());
}
}