1use std::io::{self, Read, Write};
32use thiserror::Error;
33
34const MAGIC: &[u8; 8] = b"OXICACHE";
37const FORMAT_VERSION: u16 = 1;
38
39#[derive(Debug, Error)]
43pub enum SerializeError {
44 #[error("I/O error: {0}")]
46 Io(#[from] io::Error),
47
48 #[error("invalid magic header: expected 'OXICACHE', got {0:?}")]
50 InvalidMagic([u8; 8]),
51
52 #[error("unsupported format version {0}; expected {FORMAT_VERSION}")]
54 UnsupportedVersion(u16),
55
56 #[error("key at entry {0} is not valid UTF-8: {1}")]
58 InvalidKeyUtf8(usize, std::string::FromUtf8Error),
59
60 #[error("entry {index} value length {actual} exceeds safety limit {limit}")]
62 ValueTooLarge {
63 index: usize,
65 actual: u32,
67 limit: u32,
69 },
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct CacheRecord {
80 pub key: String,
82 pub value: Vec<u8>,
84 pub ttl_secs: u64,
86 pub priority: u32,
88}
89
90impl CacheRecord {
91 pub fn new(key: impl Into<String>, value: Vec<u8>) -> Self {
94 Self {
95 key: key.into(),
96 value,
97 ttl_secs: 0,
98 priority: 0,
99 }
100 }
101
102 pub fn with_ttl(mut self, ttl_secs: u64) -> Self {
104 self.ttl_secs = ttl_secs;
105 self
106 }
107
108 pub fn with_priority(mut self, priority: u32) -> Self {
110 self.priority = priority;
111 self
112 }
113}
114
115pub fn serialize<W: Write>(writer: &mut W, records: &[CacheRecord]) -> Result<(), SerializeError> {
124 writer.write_all(MAGIC)?;
126 writer.write_all(&FORMAT_VERSION.to_le_bytes())?;
127 let flags: u16 = 0;
128 writer.write_all(&flags.to_le_bytes())?;
129
130 let n = records.len() as u32;
132 writer.write_all(&n.to_le_bytes())?;
133
134 for rec in records {
135 let key_bytes = rec.key.as_bytes();
136 writer.write_all(&(key_bytes.len() as u32).to_le_bytes())?;
137 writer.write_all(key_bytes)?;
138
139 writer.write_all(&(rec.value.len() as u32).to_le_bytes())?;
140 writer.write_all(&rec.value)?;
141
142 writer.write_all(&rec.priority.to_le_bytes())?;
143 writer.write_all(&rec.ttl_secs.to_le_bytes())?;
144 }
145
146 Ok(())
147}
148
149#[derive(Debug, Clone)]
153pub struct DeserializeConfig {
154 pub max_value_bytes: u32,
159 pub max_records: u32,
164}
165
166impl Default for DeserializeConfig {
167 fn default() -> Self {
168 Self {
169 max_value_bytes: 512 * 1024 * 1024, max_records: u32::MAX,
171 }
172 }
173}
174
175pub fn deserialize<R: Read>(reader: &mut R) -> Result<Vec<CacheRecord>, SerializeError> {
179 deserialize_with_config(reader, &DeserializeConfig::default())
180}
181
182pub fn deserialize_with_config<R: Read>(
192 reader: &mut R,
193 config: &DeserializeConfig,
194) -> Result<Vec<CacheRecord>, SerializeError> {
195 let mut magic = [0u8; 8];
197 reader.read_exact(&mut magic)?;
198 if &magic != MAGIC {
199 return Err(SerializeError::InvalidMagic(magic));
200 }
201
202 let mut ver_buf = [0u8; 2];
204 reader.read_exact(&mut ver_buf)?;
205 let version = u16::from_le_bytes(ver_buf);
206 if version != FORMAT_VERSION {
207 return Err(SerializeError::UnsupportedVersion(version));
208 }
209
210 let mut flags_buf = [0u8; 2];
212 reader.read_exact(&mut flags_buf)?;
213 let mut n_buf = [0u8; 4];
217 reader.read_exact(&mut n_buf)?;
218 let n = u32::from_le_bytes(n_buf);
219
220 let to_read = n.min(config.max_records);
221 let mut records = Vec::with_capacity(to_read as usize);
222
223 for idx in 0..n as usize {
224 let mut klen_buf = [0u8; 4];
226 reader.read_exact(&mut klen_buf)?;
227 let key_len = u32::from_le_bytes(klen_buf) as usize;
228 let mut key_bytes = vec![0u8; key_len];
229 reader.read_exact(&mut key_bytes)?;
230 let key =
231 String::from_utf8(key_bytes).map_err(|e| SerializeError::InvalidKeyUtf8(idx, e))?;
232
233 let mut vlen_buf = [0u8; 4];
235 reader.read_exact(&mut vlen_buf)?;
236 let val_len = u32::from_le_bytes(vlen_buf);
237 if val_len > config.max_value_bytes {
238 return Err(SerializeError::ValueTooLarge {
239 index: idx,
240 actual: val_len,
241 limit: config.max_value_bytes,
242 });
243 }
244 let mut value = vec![0u8; val_len as usize];
245 reader.read_exact(&mut value)?;
246
247 let mut prio_buf = [0u8; 4];
249 reader.read_exact(&mut prio_buf)?;
250 let priority = u32::from_le_bytes(prio_buf);
251
252 let mut ttl_buf = [0u8; 8];
254 reader.read_exact(&mut ttl_buf)?;
255 let ttl_secs = u64::from_le_bytes(ttl_buf);
256
257 if (idx as u32) < config.max_records {
258 records.push(CacheRecord {
259 key,
260 value,
261 ttl_secs,
262 priority,
263 });
264 }
265 }
268
269 Ok(records)
270}
271
272pub fn save_to_file(path: &std::path::Path, records: &[CacheRecord]) -> Result<(), SerializeError> {
278 let mut file = std::fs::File::create(path)?;
279 serialize(&mut file, records)
280}
281
282pub fn load_from_file(path: &std::path::Path) -> Result<Vec<CacheRecord>, SerializeError> {
286 let mut file = std::fs::File::open(path)?;
287 deserialize(&mut file)
288}
289
290pub fn load_from_file_with_config(
292 path: &std::path::Path,
293 config: &DeserializeConfig,
294) -> Result<Vec<CacheRecord>, SerializeError> {
295 let mut file = std::fs::File::open(path)?;
296 deserialize_with_config(&mut file, config)
297}
298
299#[cfg(test)]
302mod tests {
303 use super::*;
304 use std::io::Cursor;
305
306 fn roundtrip(records: &[CacheRecord]) -> Vec<CacheRecord> {
307 let mut buf = Vec::new();
308 serialize(&mut buf, records).expect("serialize should succeed");
309 let mut cursor = Cursor::new(&buf);
310 deserialize(&mut cursor).expect("deserialize should succeed")
311 }
312
313 #[test]
315 fn test_empty_roundtrip() {
316 let records: Vec<CacheRecord> = Vec::new();
317 let restored = roundtrip(&records);
318 assert!(restored.is_empty());
319 }
320
321 #[test]
323 fn test_single_record_roundtrip() {
324 let records = vec![CacheRecord::new("key-001", b"hello world".to_vec())];
325 let restored = roundtrip(&records);
326 assert_eq!(restored.len(), 1);
327 assert_eq!(restored[0].key, "key-001");
328 assert_eq!(restored[0].value, b"hello world");
329 assert_eq!(restored[0].ttl_secs, 0);
330 assert_eq!(restored[0].priority, 0);
331 }
332
333 #[test]
335 fn test_multiple_records_roundtrip() {
336 let records: Vec<CacheRecord> = (0..20u32)
337 .map(|i| {
338 CacheRecord::new(format!("seg-{i:04}"), vec![i as u8; 128])
339 .with_ttl(300)
340 .with_priority(i % 5)
341 })
342 .collect();
343 let restored = roundtrip(&records);
344 assert_eq!(restored.len(), records.len());
345 for (orig, rest) in records.iter().zip(restored.iter()) {
346 assert_eq!(orig, rest);
347 }
348 }
349
350 #[test]
352 fn test_ttl_and_priority_roundtrip() {
353 let rec = CacheRecord::new("manifest.m3u8", b"#EXTM3U".to_vec())
354 .with_ttl(30)
355 .with_priority(10);
356 let restored = roundtrip(std::slice::from_ref(&rec));
357 assert_eq!(restored[0].ttl_secs, 30);
358 assert_eq!(restored[0].priority, 10);
359 }
360
361 #[test]
363 fn test_binary_value_roundtrip() {
364 let value: Vec<u8> = (0u8..=255).collect();
365 let records = vec![CacheRecord::new("binary", value.clone())];
366 let restored = roundtrip(&records);
367 assert_eq!(restored[0].value, value);
368 }
369
370 #[test]
372 fn test_unicode_key_roundtrip() {
373 let records = vec![CacheRecord::new("媒体-segment-001", vec![1, 2, 3])];
374 let restored = roundtrip(&records);
375 assert_eq!(restored[0].key, "媒体-segment-001");
376 }
377
378 #[test]
380 fn test_invalid_magic() {
381 let garbage = b"GARBAGE_HEADER_DATA";
382 let mut cursor = Cursor::new(garbage);
383 let result = deserialize(&mut cursor);
384 assert!(
385 matches!(result, Err(SerializeError::InvalidMagic(_))),
386 "expected InvalidMagic, got {result:?}"
387 );
388 }
389
390 #[test]
392 fn test_wrong_version() {
393 let mut buf = Vec::new();
394 buf.extend_from_slice(MAGIC);
395 buf.extend_from_slice(&9999u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); let mut cursor = Cursor::new(&buf);
399 let result = deserialize(&mut cursor);
400 assert!(
401 matches!(result, Err(SerializeError::UnsupportedVersion(9999))),
402 "expected UnsupportedVersion"
403 );
404 }
405
406 #[test]
408 fn test_max_records_limit() {
409 let records: Vec<CacheRecord> = (0..10u32)
410 .map(|i| CacheRecord::new(format!("k{i}"), vec![i as u8]))
411 .collect();
412 let mut buf = Vec::new();
413 serialize(&mut buf, &records).expect("ok");
414 let config = DeserializeConfig {
415 max_records: 3,
416 ..Default::default()
417 };
418 let mut cursor = Cursor::new(&buf);
419 let restored = deserialize_with_config(&mut cursor, &config).expect("ok");
420 assert_eq!(restored.len(), 3, "only 3 records should be restored");
421 }
422
423 #[test]
425 fn test_max_value_bytes_rejected() {
426 let records = vec![CacheRecord::new("big", vec![0u8; 1024])];
427 let mut buf = Vec::new();
428 serialize(&mut buf, &records).expect("ok");
429 let config = DeserializeConfig {
430 max_value_bytes: 128, ..Default::default()
432 };
433 let mut cursor = Cursor::new(&buf);
434 let result = deserialize_with_config(&mut cursor, &config);
435 assert!(
436 matches!(result, Err(SerializeError::ValueTooLarge { .. })),
437 "expected ValueTooLarge"
438 );
439 }
440
441 #[test]
443 fn test_file_save_load_roundtrip() {
444 let dir = std::env::temp_dir();
445 let path = dir.join("oximedia_cache_test_serialization.bin");
446 let records = vec![
447 CacheRecord::new("segment-1", b"data1".to_vec()).with_ttl(60),
448 CacheRecord::new("segment-2", b"data2".to_vec()).with_priority(5),
449 ];
450 save_to_file(&path, &records).expect("save should succeed");
451 let restored = load_from_file(&path).expect("load should succeed");
452 assert_eq!(restored.len(), 2);
453 assert_eq!(restored[0].key, "segment-1");
454 assert_eq!(restored[1].priority, 5);
455 let _ = std::fs::remove_file(&path);
457 }
458
459 #[test]
461 fn test_empty_key_roundtrip() {
462 let records = vec![CacheRecord::new("", b"value".to_vec())];
463 let restored = roundtrip(&records);
464 assert_eq!(restored[0].key, "");
465 }
466
467 #[test]
469 fn test_empty_value_roundtrip() {
470 let records = vec![CacheRecord::new("empty-val", Vec::new())];
471 let restored = roundtrip(&records);
472 assert!(restored[0].value.is_empty());
473 }
474
475 #[test]
477 fn test_serialized_magic_prefix() {
478 let mut buf = Vec::new();
479 serialize(&mut buf, &[]).expect("ok");
480 assert_eq!(&buf[..8], MAGIC);
481 }
482
483 #[test]
485 fn test_cache_record_builder() {
486 let rec = CacheRecord::new("k", vec![1, 2])
487 .with_ttl(120)
488 .with_priority(7);
489 assert_eq!(rec.key, "k");
490 assert_eq!(rec.ttl_secs, 120);
491 assert_eq!(rec.priority, 7);
492 }
493}