Skip to content

Commit 1c54929

Browse files
committed
refactor: consistent range
1 parent 1347dde commit 1c54929

18 files changed

Lines changed: 153 additions & 154 deletions

DESIGN_NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ but unwired. The full trace is the
102102

103103
**Encrypted blobs** (the FilOne encryption design's read side) decrypt inside
104104
the per-blob open, leaving every other read-path value plaintext: the
105-
manifest's `BlobRef.Offset/Length`, `Body.Size`, ETag and Content-Length are
105+
manifest's `BlobRef.Start/End`, `Body.Size`, ETag and Content-Length are
106106
plaintext coordinates, while `BlobRef.Digest` names the stored FEE envelope.
107107
A `blob_encryption_params` row marks a blob encrypted and carries what its
108108
decryptor needs; the read unwraps the region-wrapped CEK through

blockstore/cache.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ func (c *Cached) OpenBlob(ctx context.Context, space did.DID, digest mh.Multihas
7070
return nil, ErrNotFound
7171
}
7272

73-
// OpenBlobRange streams stored bytes [off, off+n) of a body blob straight
73+
// OpenBlobRange streams stored bytes [start, end] (inclusive) of a body blob straight
7474
// from the base reader, bypassing the LRU exactly like OpenBlob.
75-
func (c *Cached) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, error) {
75+
func (c *Cached) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, error) {
7676
if br, ok := c.base.(BlobReader); ok {
77-
return OpenBlobRangeOf(ctx, br, space, digest, off, n)
77+
return OpenBlobRangeOf(ctx, br, space, digest, start, end)
7878
}
7979
return nil, ErrNotFound
8080
}

blockstore/forge.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package blockstore
22

33
import (
4-
"bytes"
54
"context"
65
"errors"
76
"fmt"
@@ -146,19 +145,19 @@ func NewForge(cfg ForgeConfig) (*Forge, error) {
146145
// empty 200 when it can't resolve a did:plc principal in the proof chain)
147146
// otherwise surfaces only as a bare client-side EOF mid-body, with nothing
148147
// attributable in ingot's own logs.
149-
// off/n select a sub-range of the located bytes: stored bytes [off, off+n)
150-
// of the blob. n < 0 retrieves the locator's whole range (the entire blob,
148+
// start/end select an inclusive sub-range of the located bytes (HTTP Range
149+
// semantics). end < 0 retrieves the locator's whole range (the entire blob,
151150
// for a body blob).
152-
func (f *Forge) retrieve(ctx context.Context, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, int64, error) {
153-
rc, got, err := f.doRetrieve(ctx, space, digest, off, n)
151+
func (f *Forge) retrieve(ctx context.Context, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, int64, error) {
152+
rc, got, err := f.doRetrieve(ctx, space, digest, start, end)
154153
if err != nil && !errors.Is(err, ErrNotFound) {
155154
f.logger.Warn("forge: retrieve failed",
156155
zap.Stringer("space", space), zap.String("digest", digestutil.Format(digest)), zap.Error(err))
157156
}
158157
return rc, got, err
159158
}
160159

161-
func (f *Forge) doRetrieve(ctx context.Context, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, int64, error) {
160+
func (f *Forge) doRetrieve(ctx context.Context, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, int64, error) {
162161
locations, err := f.locator.Locate(ctx, []did.DID{space}, digest)
163162
if err != nil {
164163
var nf locator.NotFoundError
@@ -178,21 +177,21 @@ func (f *Forge) doRetrieve(ctx context.Context, space did.DID, digest mh.Multiha
178177
}
179178
target := cm.Location[0]
180179

181-
// Narrow the located range to the caller's sub-range. loc.Range is
182-
// inclusive and, for a whole blob, starts at 0 — so the sub-range is
183-
// [Start+off, Start+off+n-1], clamped to the located end. The decrypting
184-
// read path uses this to fetch only the ciphertext chunks it needs.
185-
if n >= 0 {
186-
start := loc.Range.Start + off
187-
if start > loc.Range.End || n == 0 {
188-
// Nothing to fetch; an empty body keeps the contract simple.
189-
return io.NopCloser(bytes.NewReader(nil)), 0, nil
180+
// Narrow the located range to the caller's inclusive sub-range. loc.Range
181+
// is inclusive too and, for a whole blob, starts at 0 — so the sub-range
182+
// maps to [Range.Start+start, Range.Start+end], with the end clamped to
183+
// the located end (HTTP Range semantics). The decrypting read path uses
184+
// this to fetch only the ciphertext chunks it needs.
185+
if end >= 0 {
186+
first := loc.Range.Start + start
187+
if start < 0 || end < start || first > loc.Range.End {
188+
return nil, 0, fmt.Errorf("forge: range [%d,%d] of %s is unsatisfiable", start, end, digestutil.Format(digest))
190189
}
191-
end := start + n - 1
192-
if end > loc.Range.End {
193-
end = loc.Range.End
190+
last := loc.Range.Start + end
191+
if last > loc.Range.End {
192+
last = loc.Range.End
194193
}
195-
loc.Range = blobindex.Range{Start: start, End: end}
194+
loc.Range = blobindex.Range{Start: first, End: last}
196195
}
197196

198197
// The commitment's space scopes the retrieve capability; fall back to
@@ -286,11 +285,12 @@ func (f *Forge) OpenBlob(ctx context.Context, space did.DID, digest mh.Multihash
286285
return rc, err
287286
}
288287

289-
// OpenBlobRange streams stored bytes [off, off+n) of an object-body blob from
290-
// piri — a ranged /content/retrieve, so the decrypting read path fetches only
291-
// the ciphertext chunks a plaintext range needs rather than the whole blob.
292-
func (f *Forge) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, error) {
293-
rc, _, err := f.retrieve(ctx, space, digest, off, n)
288+
// OpenBlobRange streams stored bytes [start, end] (inclusive) of an
289+
// object-body blob from piri — a ranged /content/retrieve, so the decrypting
290+
// read path fetches only the ciphertext chunks a plaintext range needs
291+
// rather than the whole blob.
292+
func (f *Forge) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, error) {
293+
rc, _, err := f.retrieve(ctx, space, digest, start, end)
294294
return rc, err
295295
}
296296

blockstore/layered.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@ func (l *Layered) OpenBlob(ctx context.Context, space did.DID, digest mh.Multiha
9494
return nil, ErrNotFound
9595
}
9696

97-
// OpenBlobRange streams stored bytes [off, off+n) of a body blob with the
97+
// OpenBlobRange streams stored bytes [start, end] (inclusive) of a body blob with the
9898
// same tiering as OpenBlob: spool first, then the network base. A tier that
9999
// implements only BlobReader is served through OpenBlob with the prefix
100100
// discarded (OpenBlobRangeOf).
101-
func (l *Layered) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, error) {
101+
func (l *Layered) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, error) {
102102
if br, ok := l.spool.(BlobReader); ok {
103-
rc, err := OpenBlobRangeOf(ctx, br, space, digest, off, n)
103+
rc, err := OpenBlobRangeOf(ctx, br, space, digest, start, end)
104104
if err == nil {
105105
return rc, nil
106106
}
@@ -109,7 +109,7 @@ func (l *Layered) OpenBlobRange(ctx context.Context, space did.DID, digest mh.Mu
109109
}
110110
}
111111
if br, ok := l.base.(BlobReader); ok {
112-
return OpenBlobRangeOf(ctx, br, space, digest, off, n)
112+
return OpenBlobRangeOf(ctx, br, space, digest, start, end)
113113
}
114114
return nil, ErrNotFound
115115
}

blockstore/openblob_test.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ func TestCached_OpenBlob_BypassesCache(t *testing.T) {
8080
}
8181
}
8282

83-
// TestSpool_OpenBlobRange: the section reader serves exactly [off, off+n) of
84-
// the spooled file, and a range past the end yields a shorter stream.
83+
// TestSpool_OpenBlobRange: the section reader serves exactly the inclusive
84+
// [start, end] of the spooled file, and an end past the file's end yields a
85+
// shorter stream.
8586
func TestSpool_OpenBlobRange(t *testing.T) {
8687
ctx := context.Background()
8788
sp, err := NewSpool(t.TempDir())
@@ -95,18 +96,18 @@ func TestSpool_OpenBlobRange(t *testing.T) {
9596
}
9697

9798
cases := []struct {
98-
name string
99-
off, n int64
100-
want string
99+
name string
100+
start, end int64
101+
want string
101102
}{
102-
{"interior", 4, 6, "456789"},
103-
{"prefix", 0, 3, "012"},
104-
{"suffix past end clamps", 12, 100, "cdef"},
105-
{"empty", 5, 0, ""},
103+
{"interior", 4, 9, "456789"},
104+
{"prefix", 0, 2, "012"},
105+
{"single byte", 15, 15, "f"},
106+
{"end past file clamps", 12, 100, "cdef"},
106107
}
107108
for _, c := range cases {
108109
t.Run(c.name, func(t *testing.T) {
109-
rc, err := sp.OpenBlobRange(ctx, did.Undef, digest, c.off, c.n)
110+
rc, err := sp.OpenBlobRange(ctx, did.Undef, digest, c.start, c.end)
110111
if err != nil {
111112
t.Fatalf("OpenBlobRange: %v", err)
112113
}
@@ -116,7 +117,7 @@ func TestSpool_OpenBlobRange(t *testing.T) {
116117
t.Fatalf("read: %v", err)
117118
}
118119
if string(got) != c.want {
119-
t.Fatalf("range [%d,+%d) = %q, want %q", c.off, c.n, got, c.want)
120+
t.Fatalf("range [%d,%d] = %q, want %q", c.start, c.end, got, c.want)
120121
}
121122
})
122123
}
@@ -132,7 +133,7 @@ func TestOpenBlobRangeOf_Fallback(t *testing.T) {
132133
ctx := context.Background()
133134
tier := fakeBlobTier{data: []byte("0123456789abcdef")}
134135

135-
rc, err := OpenBlobRangeOf(ctx, tier, did.Undef, nil, 4, 6)
136+
rc, err := OpenBlobRangeOf(ctx, tier, did.Undef, nil, 4, 9)
136137
if err != nil {
137138
t.Fatalf("OpenBlobRangeOf: %v", err)
138139
}

blockstore/spool.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,19 @@ func (s *Spool) OpenBlob(_ context.Context, _ did.DID, digest mh.Multihash) (io.
116116
return f, nil
117117
}
118118

119-
// OpenBlobRange returns a reader over stored bytes [off, off+n) of the
120-
// spooled blob, or ErrNotFound — OpenBlob restricted to a section, for the
121-
// decrypting read path, which fetches only the ciphertext span a plaintext
122-
// range needs. A range past the file's end yields a shorter stream.
123-
func (s *Spool) OpenBlobRange(_ context.Context, _ did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, error) {
124-
if off < 0 || n < 0 {
125-
return nil, fmt.Errorf("blockstore: invalid blob range [%d, +%d)", off, n)
126-
}
119+
// OpenBlobRange returns a reader over stored bytes [start, end] (inclusive)
120+
// of the spooled blob, or ErrNotFound — OpenBlob restricted to a section,
121+
// for the decrypting read path, which fetches only the ciphertext span a
122+
// plaintext range needs. An end past the file's end yields a shorter stream.
123+
func (s *Spool) OpenBlobRange(_ context.Context, _ did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, error) {
127124
f, err := os.Open(s.Path(digest))
128125
if errors.Is(err, os.ErrNotExist) {
129126
return nil, ErrNotFound
130127
}
131128
if err != nil {
132129
return nil, fmt.Errorf("blockstore: spool open %s: %w", digest.B58String(), err)
133130
}
134-
return readerCloser{Reader: io.NewSectionReader(f, off, n), Closer: f}, nil
131+
return readerCloser{Reader: io.NewSectionReader(f, start, end-start+1), Closer: f}, nil
135132
}
136133

137134
// GetBlock returns the blob stored under c's multihash, or ErrNotFound. A miss

blockstore/store.go

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,43 +106,42 @@ type BlobReader interface {
106106
}
107107

108108
// BlobRangeReader is the optional ranged counterpart of BlobReader: it
109-
// streams only the stored bytes [off, off+n) of a blob. The decrypting read
110-
// path uses it to fetch exactly the ciphertext chunks a plaintext range
111-
// needs instead of the whole blob. Returns ErrNotFound if the blob is not in
112-
// this tier; a range past the stored bytes yields a shorter stream, which
113-
// the caller detects. A tier without this capability is served through
114-
// OpenBlob with the prefix discarded (see OpenBlobRangeOf).
109+
// streams only the stored bytes [start, end] (inclusive, HTTP Range
110+
// semantics) of a blob. The decrypting read path uses it to fetch exactly
111+
// the ciphertext chunks a plaintext range needs instead of the whole blob.
112+
// Returns ErrNotFound if the blob is not in this tier; an end past the
113+
// stored bytes yields a shorter stream, which the caller detects. A tier
114+
// without this capability is served through OpenBlob with the prefix
115+
// discarded (see OpenBlobRangeOf).
115116
type BlobRangeReader interface {
116-
OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, error)
117+
OpenBlobRange(ctx context.Context, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, error)
117118
}
118119

119-
// OpenBlobRangeOf opens stored bytes [off, off+n) of a blob through br,
120-
// using its BlobRangeReader capability when present and falling back to
121-
// OpenBlob with the prefix read-and-discarded (or Seek'd, for a spool file)
122-
// when not. It is how range-consuming callers stay correct over any tier.
123-
func OpenBlobRangeOf(ctx context.Context, br BlobReader, space did.DID, digest mh.Multihash, off, n int64) (io.ReadCloser, error) {
124-
if off < 0 || n < 0 {
125-
return nil, fmt.Errorf("blockstore: invalid blob range [%d, +%d)", off, n)
126-
}
120+
// OpenBlobRangeOf opens stored bytes [start, end] (inclusive) of a blob
121+
// through br, using its BlobRangeReader capability when present and falling
122+
// back to OpenBlob with the prefix read-and-discarded (or Seek'd, for a
123+
// spool file) when not. It is how range-consuming callers stay correct over
124+
// any tier.
125+
func OpenBlobRangeOf(ctx context.Context, br BlobReader, space did.DID, digest mh.Multihash, start, end int64) (io.ReadCloser, error) {
127126
if rr, ok := br.(BlobRangeReader); ok {
128-
return rr.OpenBlobRange(ctx, space, digest, off, n)
127+
return rr.OpenBlobRange(ctx, space, digest, start, end)
129128
}
130129
rc, err := br.OpenBlob(ctx, space, digest)
131130
if err != nil {
132131
return nil, err
133132
}
134-
if off > 0 {
133+
if start > 0 {
135134
if seeker, ok := rc.(io.Seeker); ok {
136-
if _, err := seeker.Seek(off, io.SeekStart); err != nil {
135+
if _, err := seeker.Seek(start, io.SeekStart); err != nil {
137136
_ = rc.Close()
138-
return nil, fmt.Errorf("blockstore: seek blob to %d: %w", off, err)
137+
return nil, fmt.Errorf("blockstore: seek blob to %d: %w", start, err)
139138
}
140-
} else if _, err := io.CopyN(io.Discard, rc, off); err != nil {
139+
} else if _, err := io.CopyN(io.Discard, rc, start); err != nil {
141140
_ = rc.Close()
142-
return nil, fmt.Errorf("blockstore: skip into blob to %d: %w", off, err)
141+
return nil, fmt.Errorf("blockstore: skip into blob to %d: %w", start, err)
143142
}
144143
}
145-
return readerCloser{Reader: io.LimitReader(rc, n), Closer: rc}, nil
144+
return readerCloser{Reader: io.LimitReader(rc, end-start+1), Closer: rc}, nil
146145
}
147146

148147
// readerCloser pairs a wrapped reader with the closer releasing its

bucket/cbor_gen.go

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)