Expand description
Frame index — Range GET の partial fetch を可能にするための sidecar object 形式。
§課題
S4-multipart object は [S4F2 frame]([S4P1 padding][S4F2 frame])* のシーケンス。
Range GET (e.g. bytes=N-M) を効率的に処理するには、(a) どの frame が
decompressed offset N..M に対応しているか、(b) その frame は object body の
どこ (compressed_offset) から始まるか、を知る必要がある。
§解決策
<key>.s4index という sidecar object に下記の binary index を書く:
┌──── v1 32 byte header ─┐
│ S4IX magic (4) │
│ version u32 (4) │
│ total_frames u64 (8) │
│ total_original u64 (8) │
│ total_padded u64 (8) │ ← S3 上の object サイズ (padding 含む)
└────────────────────────┘
各 frame について 32 byte:
original_offset u64 LE
original_size u64 LE
compressed_offset u64 LE ← S3 object body における frame header の開始位置
compressed_size u64 LE ← header (28 byte) + payload の合計1000 frame で 32 KB、10000 frame で 320 KB。10 万 frame でも 3.2 MB に収まる。
§使い方
- PUT: 1 frame の単純 index、PUT 完了後に sidecar 書込
- CompleteMultipartUpload: object 全体を一度 fetch + scan して index を構築
- Range GET: sidecar fetch →
lookup_range(start, end)で frame 範囲 + S3 byte 範囲を取得 → backend に partial Range GET → frame parse → decompress → slice
§v0.8.4 #73 H-2: source object version binding (v2 header)
v1 では sidecar に source object の identity が無いため、object overwrite 後に sidecar が stale のままだと Range GET が 間違った frame を返す危険があった (古い byte offset で新 object を partial GET する hazard)。攻撃者が backend を 直接触れる脅威モデルでは、偽 sidecar を仕込めば任意 frame を露呈させ得る。
対策として v2 header に source_etag と source_compressed_size を追加。GET
側は HEAD で current etag を取って一致確認 → 不一致なら sidecar を信用せず full
GET path に fall back する。
┌──── v2 header (variable) ┐
│ S4IX magic (4) │
│ version u32 (4) = 2 │
│ total_frames u64 (8) │
│ total_original u64 (8) │
│ total_padded u64 (8) │
│ source_compressed_size u64 (8) ← v2 で追加
│ etag_len u32 (4) ← v2 で追加 (UTF-8 byte length, 0 = absent)
│ etag bytes (etag_len) ← v2 で追加 (RFC 7232 entity-tag, quotes 含む)
└──────────────────────────┘- back-compat: v1 sidecar が backend に既存していれば read-only で
decode_indexがsource_etag = None,source_compressed_size = Noneで復元する。GET 側はNoneを見たら “legacy sidecar — verify skip, full GET にも fallback できる” と扱う (= 既存挙動保持)。 - 新規 PUT: 常に v2 を書く。
source_etagは backend response の e_tag、source_compressed_sizeは put body 長 (=total_padded_size) が原則。
Structs§
- Frame
Index - Frame
Index Entry - Range
Plan lookup_rangeの結果。byte_start..byte_end_exclusiveを S3 から fetch、 該当 frames を decompress し、結果バイト列を[slice_start_in_combined, slice_end_in_combined)で slice すれば最終結果。
Enums§
Constants§
- ENTRY_
BYTES - INDEX_
HEADER_ BYTES - INDEX_
MAGIC - INDEX_
VERSION - v0.8.4 #73 H-2: bumped 1 → 2. v2 appends
source_compressed_size(u64) +etag_len(u32) + variable-lengthetagbytes to the fixed header. v1 readers are kept as a back-compat path (seedecode_index). - INDEX_
VERSION_ V1 - Legacy v1 fixed header — kept for tests / back-compat readers.
Functions§
- build_
index_ from_ body - Object body の bytes 全体を scan して FrameIndex を構築する。
multipart_e2e.rs等で full-scan path として使用。 - decode_
index - encode_
index - v0.8.4 #73 H-2: emit the v2 layout (with
source_etag/source_compressed_size). Pre-v0.8.4 deployments that PUT under v1 are still readable (decode_index dispatches on the version field) — only the writer path is bumped here. - sidecar_
key <key>から sidecar key を生成。