docs: add JPEG validation implementation plan for M5Max48
This commit is contained in:
187
docs_v1.0/DESIGN/Thumbnail_JPEG_Validation_Impl.md
Normal file
187
docs_v1.0/DESIGN/Thumbnail_JPEG_Validation_Impl.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
title: Thumbnail JPEG Validation Implementation
|
||||||
|
version: 1.0.0
|
||||||
|
date: 2026-05-27
|
||||||
|
author: M5Max128
|
||||||
|
status: ready_for_implementation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Thumbnail JPEG Validation Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add JPEG quality validation to all ffmpeg image extraction endpoints to prevent:
|
||||||
|
- Empty images (0 bytes)
|
||||||
|
- Corrupted JPEG (missing header/footer)
|
||||||
|
- Incomplete JPEG (truncated output)
|
||||||
|
|
||||||
|
## Files to Create/Modify
|
||||||
|
|
||||||
|
### 1. Create: `src/core/thumbnail/validator.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
|
pub const JPEG_MIN_SIZE: usize = 100;
|
||||||
|
pub const JPEG_SOI_MARKER: [u8; 3] = [0xFF, 0xD8, 0xFF];
|
||||||
|
pub const JPEG_EOI_MARKER: [u8; 2] = [0xFF, 0xD9];
|
||||||
|
|
||||||
|
pub fn validate_jpeg(data: &[u8]) -> Result<()> {
|
||||||
|
if data.len() < JPEG_MIN_SIZE {
|
||||||
|
bail!("JPEG too small: {} bytes (minimum {})", data.len(), JPEG_MIN_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[0..3] != JPEG_SOI_MARKER {
|
||||||
|
bail!("Invalid JPEG header: expected {:02X?}, got {:02X?}", JPEG_SOI_MARKER, &data[0..3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[data.len() - 2..] != JPEG_EOI_MARKER {
|
||||||
|
bail!("Incomplete JPEG: missing EOI marker, got {:02X?}", &data[data.len() - 2..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid_jpeg(data: &[u8]) -> bool {
|
||||||
|
validate_jpeg(data).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jpeg_size_ok(data: &[u8]) -> bool {
|
||||||
|
data.len() >= JPEG_MIN_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jpeg_header_ok(data: &[u8]) -> bool {
|
||||||
|
data.len() >= 3 && data[0..3] == JPEG_SOI_MARKER
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jpeg_footer_ok(data: &[u8]) -> bool {
|
||||||
|
data.len() >= 2 && data[data.len() - 2..] == JPEG_EOI_MARKER
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Modify: `src/core/thumbnail/mod.rs`
|
||||||
|
|
||||||
|
Add module declaration at line 1:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub mod validator;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
// ... rest of file
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Modify: `src/api/media_api.rs`
|
||||||
|
|
||||||
|
Location: `face_thumbnail()` function, after ffmpeg output check (around line 754)
|
||||||
|
|
||||||
|
Add validation:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD THIS LINE:
|
||||||
|
crate::core::thumbnail::validator::validate_jpeg(&output.stdout)
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
// ... rest of response
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Modify: `src/api/trace_agent_api.rs`
|
||||||
|
|
||||||
|
Location: `get_trace_thumbnail()` function, after reading bytes (around line 544)
|
||||||
|
|
||||||
|
Add validation:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let bytes = tokio::fs::read(&tmp).await.map_err(|e| {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()})))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let _ = tokio::fs::remove_file(&tmp).await;
|
||||||
|
|
||||||
|
// ADD THIS LINE:
|
||||||
|
crate::core::thumbnail::validator::validate_jpeg(&bytes)
|
||||||
|
.map_err(|e| {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()})))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
// ... rest of response
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Modify: `src/core/frame_cache.rs`
|
||||||
|
|
||||||
|
Location: `FrameManager::extract()`, when iterating extracted frames (around line 73)
|
||||||
|
|
||||||
|
Replace the frame collection logic:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
for entry in &entries {
|
||||||
|
let fname = entry.file_name();
|
||||||
|
let fname_str = fname.to_string_lossy();
|
||||||
|
if let Some(num_str) = fname_str
|
||||||
|
.strip_prefix("frame_")
|
||||||
|
.and_then(|s| s.strip_suffix(".jpg"))
|
||||||
|
{
|
||||||
|
if let Ok(frame_num) = num_str.parse::<u64>() {
|
||||||
|
let frame_path = entry.path();
|
||||||
|
// ADD VALIDATION:
|
||||||
|
if let Ok(data) = std::fs::read(&frame_path) {
|
||||||
|
if crate::core::thumbnail::validator::is_valid_jpeg(&data) {
|
||||||
|
let timestamp = frame_num as f64 / fps;
|
||||||
|
frames.push(CachedFrame {
|
||||||
|
path: frame_path,
|
||||||
|
frame_number: frame_num,
|
||||||
|
timestamp_secs: timestamp,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
info!("[FrameCache] Skipping invalid JPEG: {:?}", frame_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Logic
|
||||||
|
|
||||||
|
| Check | Condition | Error if failed |
|
||||||
|
|-------|-----------|-----------------|
|
||||||
|
| Minimum size | `len() >= 100` | "JPEG too small" |
|
||||||
|
| SOI marker | `[0..3] == [0xFF,0xD8,0xFF]` | "Invalid JPEG header" |
|
||||||
|
| EOI marker | `[-2..] == [0xFF,0xD9]` | "Incomplete JPEG" |
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
After implementation, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ~/.cargo/env
|
||||||
|
export MOMENTRY_PYTHON_PATH="/Users/accusys/momentry_core/venv/bin/python"
|
||||||
|
cargo clippy --lib
|
||||||
|
cargo test --lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 220 passed, 0 failed
|
||||||
|
|
||||||
|
## Commit Message
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add JPEG validation to thumbnail endpoints
|
||||||
|
|
||||||
|
- Create validator module with JPEG header/footer/size checks
|
||||||
|
- Add validation to face_thumbnail endpoint
|
||||||
|
- Add validation to get_trace_thumbnail endpoint
|
||||||
|
- Filter invalid JPEGs in FrameManager::extract
|
||||||
|
|
||||||
|
Prevents serving corrupted/incomplete JPEG images to frontend.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
| Version | Date | Author | Changes |
|
||||||
|
|---------|------|--------|---------|
|
||||||
|
| 1.0.0 | 2026-05-27 | M5Max128 | Implementation plan ready |
|
||||||
Reference in New Issue
Block a user