1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
//! Origin backends — the source of truth a node falls back to on a cache miss.
//!
//! [`Origin`] is the seam. Three concrete implementations ship today:
//! [`HttpOrigin`] (generic HTTP/S), [`FilesystemOrigin`] (local disk), and
//! [`S3Origin`] (S3-compatible object stores: AWS S3, R2, B2, `MinIO`).
pub
pub
use Future;
use Pin;
use Bytes;
use Stream;
use Hash;
pub use FilesystemOrigin;
pub use HttpOrigin;
pub use ;
// `OriginKind`, `OriginUrl`, `DecompressMode`, `parse_origin_url`, and
// `DEFAULT_USER_AGENT` moved to the `decdn-config-types` leaf crate
// (#578); re-export from there so `decdn_cache::origin::*` paths and the
// `Origin` trait's `kind()` return type stay identical to the
// top-level `decdn_cache::*` re-exports.
pub use ;
use crateOriginPullError;
/// Marker error packed into the `io::Error` that the cache's
/// stream wrappers write to the engine's side channel when an
/// origin delivers more than `max_blob_bytes`. The engine
/// downcasts via `io::Error::get_ref` to surface
/// `CacheError::BlobTooLarge` (matching the pre-streaming typed
/// variant) instead of generic `OriginError`. Adapter-internal
/// caps (HTTP's encoded chunk cap) and the engine's running cap
/// on streamed bytes both produce this marker so the operator-
/// visible error is consistent regardless of which layer first
/// detected the overrun.
pub
/// Boxed byte stream returned by [`Origin::fetch`]. Each [`Bytes`] item is
/// one chunk of the blob's payload; the stream completes when the origin
/// signals end-of-data.
///
/// The bound matches [`iroh_blobs::api::blobs::Blobs::add_stream`]: `Send +
/// Sync + 'static` so the engine can hand the stream straight through to
/// the store layer without an intermediate buffer. `'static` means impls
/// move whatever per-fetch state they hold (file handle, HTTP response,
/// S3 `ByteStream`) into the stream by `move`-into-async-block; nothing
/// borrows from `&self` on the origin.
///
/// Errors flow as `io::Error`. They are routed through retry
/// classification by one of two paths (#519):
///
/// - **Buffer-then-commit (small blobs):** when the adapter's
/// `size_hint` is at or below
/// `cache.origin_retry.buffered_max_bytes` (default 4 MiB), the
/// engine drains the stream into a bounded `BytesMut` before
/// handing to iroh-blobs. Drain errors are classified via
/// `crate::retry::classify_io_error` and re-feed the retry loop.
/// No on-disk amplification — the partial bytes never reach the
/// store.
/// - **Abort + restart (large blobs / unknown `size_hint`):** the
/// engine streams directly into `iroh_blobs::Blobs::add_stream`
/// and captures any mid-stream `io::Error` via the side channel.
/// After `temp_tag().await` completes, the captured error is
/// classified; on `Transient` the partial `TempTag` is dropped
/// (iroh-blobs GC reclaims the bytes at `cache.gc_interval_sec`
/// cadence, #518) and the retry loop restarts from the headers
/// phase. Worst-case orphaned bytes per fetch are
/// `(1 + max_retries) * max_blob_bytes` until GC.
///
/// Typed `Permanent` markers short-circuit retry regardless of which
/// path applies: the internal `BlobTooLargeMarker` surfaces as
/// [`crate::CacheError::BlobTooLarge`] (size-cap breach is
/// deterministic — retry won't fit a bigger blob into a smaller cap),
/// and [`crate::OriginError::DecompressionFailed`] surfaces as
/// `Permanent` (corrupt body — retry sees the same bytes again).
pub type OriginByteStream =
;
// `OriginKind` (the backend tag surfaced through the admin
// eviction-preview, #439) moved to the `decdn-config-types` leaf crate
// (#578) and is re-exported above. Its serde/`as_str`/`Display`
// wire-form contract and the `origin_kind_*` tests live there.
/// Result of an [`Origin::fetch`] call.
///
/// `Found` carries a streaming primitive so the cache engine pipes
/// origin bytes straight into
/// [`iroh_blobs::api::blobs::Blobs::add_stream`] without ever holding
/// the full blob in memory (issue #271). `size_hint` carries the
/// adapter's best-effort length advertisement (HTTP `Content-Length`,
/// filesystem `metadata().len()`, S3 `content_length()`); the engine
/// uses it for a short-circuit `BlobTooLarge` check before reading
/// the first byte and falls back to a running-total check via
/// `count_and_cap_stream` for adapters that can't know the length
/// upfront or origins that lie. The hint is **advisory** — adapters
/// that don't know set `None`, and even when set the actual stream
/// length may differ (e.g., HTTP origins lying in `Content-Length`).
/// Result of an [`Origin::fetch_range_data`] call ([ADR 037 §Origin-tier
/// pull-through](https://github.com/decdn/decdn/blob/main/adr/037-regional-proxy-warming.md), #823).
///
/// A range-scoped origin pull fetches the small sibling `{H}.obao4` outboard
/// once ([`Origin::fetch_outboard`]), then fetches the requested byte span in
/// windows of at most [`crate::RANGE_PULL_WINDOW_BYTES`], verifying them
/// against the root `H` before any byte is imported or served — avoiding
/// whole-blob origin egress to serve a byte range on a cache miss, with memory
/// bounded by the window rather than the span (#2065). The verifier is
/// the engine: one [`bao_tree::io::fsm::encode_ranges_validated`] pass on the
/// [`crate::CacheEngine::origin_range_wire`] path.
///
/// The optimization is **best-effort**: when the origin does not honor
/// `Range`, or the object is absent or shorter than the span, the adapter
/// returns [`Self::Unsupported`] and the engine degrades to the existing
/// whole-blob [`Origin::fetch`] pull. That fallback is never a
/// correctness or availability failure — it only forgoes the cost reduction
/// (ADR 037 §"Fallback is always correct").
/// The chunk-group-aligned span a [`Origin::fetch_range_data`] call must
/// fetch, passed from the engine to the adapter. This is the
/// produced-by-engine half of the range pull; the verify-against-root half
/// lives in [`crate::range_pull`] and [`crate::origin_range`].
/// Result of an [`Origin::fetch_outboard`] call — a standalone fetch of just
/// the sibling `{H}.obao4` outboard, with no accompanying blob data. Feeds a
/// later "stream-while-store" serve path (#1130): a node that already holds
/// the outboard but not the full blob can start verifying/serving a
/// requester's byte range before the origin pull-through of the data
/// completes, rather than waiting on a whole-blob fetch first.
///
/// Like [`OriginRangeFetch`], the origin is a dumb byte store: the returned
/// outboard is **untrusted** until verified against the root `H`.
/// An origin backend. Implementors fetch a blob identified by its BLAKE3 hash.
///
/// The origin is **not** responsible for verifying the hash — the cache engine
/// does that after the bytes come back. Returning the wrong bytes is a
/// detectable protocol violation, not a security breach: no data is trusted
/// until it matches the content address.
///
/// ## Error classification
///
/// Adapters return [`OriginPullError::Transient`] for failures that can be
/// retried (HTTP 5xx/408/429, transient I/O), and
/// [`OriginPullError::Permanent`] for failures that won't be cured by
/// retrying (HTTP 4xx other than 404, encoding/cap breaches, permission
/// denied). The cache engine's retry loop keys off this distinction.
/// `NotFound` is *not* an error — adapters return [`OriginFetch::NotFound`].
// tests