Skip to main content

Module index

Module index 

Source
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_etagsource_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_indexsource_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§

FrameIndex
FrameIndexEntry
RangePlan
lookup_range の結果。byte_start..byte_end_exclusive を S3 から fetch、 該当 frames を decompress し、結果バイト列を [slice_start_in_combined, slice_end_in_combined) で slice すれば最終結果。

Enums§

IndexError

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-length etag bytes to the fixed header. v1 readers are kept as a back-compat path (see decode_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 を生成。