OXYGENHUB

Images: a large image's missing-chunks question fits 750579ecc721

POST /flow/images/missing had axum's 2 MB body limit, though the hub
accepts up to 200,000 chunk names per question (about 13 MB), so pushing
an 11 GB image failed with 413. The route's limit now fits MAX_ASK.

closes #147
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-12 19:32 UTC
Signature
✔ signed by Andy McCall
Hash
750579ecc7217759272630f8d070c7965c6ddaf939f1a42371162c96297e0b2e
Depends on
42fa95540468 92a797b20912 a46d15e72101 b6b7e4cf6b24 d23464c0ac60
Depended on by
951aed50c2f5 8501fd5815b9 e9b9619cd857
In bookmarks
api-unknown-fields cancel-landed-runs close-proposal-140 diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
4 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/hub/src/api/images.rs +3 −0
…
13 unchanged lines
14 14
15 15
/// Most chunk names one `missing` question may ask about.
16 16
const MAX_ASK: usize = 200_000;
17 +
/// The body that many names make: 64 hex digits, quotes and a comma each,
18 +
/// with room to spare (#147: the 2 MB default refused an 11 GB image's).
19 +
pub const MAX_ASK_BYTES: usize = MAX_ASK * 80;
17 20
18 21
/// Who sends images: an account whose token may write repositories.
19 22
async fn sender(app: &Shared, headers: &HeaderMap) -> Result<String, ApiError> {
…
102 unchanged lines
modified crates/hub/src/api/mod.rs +1 −1
…
43 unchanged lines
44 44
        .fallback(no_route)
45 45
        .route("/flow/claim", post(crate::flow::claim))
46 46
        .route("/flow/runners/register", post(crate::flow::register))
47 -
        .route("/flow/images/missing", post(images::missing))
47 +
        .route("/flow/images/missing", post(images::missing).layer(axum::extract::DefaultBodyLimit::max(images::MAX_ASK_BYTES)))
48 48
        .route("/flow/swarm/announce", post(crate::swarm::announce))
49 49
        .route("/flow/images/chunks/{hash}", axum::routing::put(images::put_chunk).get(images::get_chunk))
50 50
        .route(
…
2357 unchanged lines
modified crates/hub/tests/flow.rs +6 −0
…
2642 unchanged lines
2643 2643
    }
2644 2644
    let (_, body) = call(&h.app, "POST", "/api/v1/flow/images/missing", Some("site"), Some(ask)).await;
2645 2645
    assert!(body.contains("\"missing\":[]"), "{body}");
2646 +
    // A large image's question fits (#147): 60,000 names, ~4 MB of JSON.
2647 +
    let many: Vec<String> = (0..60_000u32).map(|i| blake3::hash(&i.to_le_bytes()).to_hex().to_string()).collect();
2648 +
    let (st, body) =
2649 +
        call(&h.app, "POST", "/api/v1/flow/images/missing", Some("site"), Some(serde_json::json!({ "chunks": many }).to_string())).await;
2650 +
    assert_eq!(st, StatusCode::OK, "{}", &body[..body.len().min(200)]);
2651 +
    assert_eq!(serde_json::from_str::<serde_json::Value>(&body).unwrap()["missing"].as_array().unwrap().len(), 60_000);
2646 2652
    // A manifest naming another hash is refused; the right one is kept.
2647 2653
    let mut wrong = m.clone();
2648 2654
    wrong.hash = "ab".repeat(32);
…
377 unchanged lines