11package blockstore
22
33import (
4+ "bytes"
45 "context"
56 "errors"
67 "fmt"
78 "io"
89 "net/http"
910 "net/url"
1011
12+ "github.com/fil-forge/libforge/blobindex"
13+
1114 indexclient "github.com/fil-forge/indexing-service/pkg/client"
1215 contentcmds "github.com/fil-forge/libforge/commands/content"
1316 "github.com/fil-forge/libforge/ucan/retrieval"
@@ -55,8 +58,9 @@ type Forge struct {
5558}
5659
5760var (
58- _ BlockReader = (* Forge )(nil )
59- _ BlobReader = (* Forge )(nil )
61+ _ BlockReader = (* Forge )(nil )
62+ _ BlobReader = (* Forge )(nil )
63+ _ BlobRangeReader = (* Forge )(nil )
6064)
6165
6266// ForgeConfig wires a read-only Forge block reader.
@@ -141,16 +145,19 @@ func NewForge(cfg ForgeConfig) (*Forge, error) {
141145// empty 200 when it can't resolve a did:plc principal in the proof chain)
142146// otherwise surfaces only as a bare client-side EOF mid-body, with nothing
143147// attributable in ingot's own logs.
144- func (f * Forge ) retrieve (ctx context.Context , space did.DID , c cid.Cid ) (io.ReadCloser , int64 , error ) {
145- rc , n , err := f .doRetrieve (ctx , space , c )
148+ // off/n select a sub-range of the located bytes: stored bytes [off, off+n)
149+ // of the blob. n < 0 retrieves the locator's whole range (the entire blob,
150+ // for a body blob).
151+ func (f * Forge ) retrieve (ctx context.Context , space did.DID , c cid.Cid , off , n int64 ) (io.ReadCloser , int64 , error ) {
152+ rc , got , err := f .doRetrieve (ctx , space , c , off , n )
146153 if err != nil && ! errors .Is (err , ErrNotFound ) {
147154 f .logger .Warn ("forge: retrieve failed" ,
148155 zap .Stringer ("space" , space ), zap .Stringer ("cid" , c ), zap .Error (err ))
149156 }
150- return rc , n , err
157+ return rc , got , err
151158}
152159
153- func (f * Forge ) doRetrieve (ctx context.Context , space did.DID , c cid.Cid ) (io.ReadCloser , int64 , error ) {
160+ func (f * Forge ) doRetrieve (ctx context.Context , space did.DID , c cid.Cid , off , n int64 ) (io.ReadCloser , int64 , error ) {
154161 locations , err := f .locator .Locate (ctx , []did.DID {space }, c .Hash ())
155162 if err != nil {
156163 var nf locator.NotFoundError
@@ -170,6 +177,23 @@ func (f *Forge) doRetrieve(ctx context.Context, space did.DID, c cid.Cid) (io.Re
170177 }
171178 target := cm .Location [0 ]
172179
180+ // Narrow the located range to the caller's sub-range. loc.Range is
181+ // inclusive and, for a whole blob, starts at 0 — so the sub-range is
182+ // [Start+off, Start+off+n-1], clamped to the located end. The decrypting
183+ // read path uses this to fetch only the ciphertext chunks it needs.
184+ if n >= 0 {
185+ start := loc .Range .Start + off
186+ if start > loc .Range .End || n == 0 {
187+ // Nothing to fetch; an empty body keeps the contract simple.
188+ return io .NopCloser (bytes .NewReader (nil )), 0 , nil
189+ }
190+ end := start + n - 1
191+ if end > loc .Range .End {
192+ end = loc .Range .End
193+ }
194+ loc .Range = blobindex.Range {Start : start , End : end }
195+ }
196+
173197 // The commitment's space scopes the retrieve capability; fall back to
174198 // the caller's space if the commitment lacks one (the local locator
175199 // always stamps it).
@@ -236,7 +260,7 @@ func (f *Forge) doRetrieve(ctx context.Context, space did.DID, c cid.Cid) (io.Re
236260// via a UCAN-authorized /content/retrieve. It buffers the whole block, so it is
237261// for small catalog blocks; object-body blobs use the streaming OpenBlob.
238262func (f * Forge ) GetBlock (ctx context.Context , space did.DID , c cid.Cid ) (block.Block , error ) {
239- rc , wantLen , err := f .retrieve (ctx , space , c )
263+ rc , wantLen , err := f .retrieve (ctx , space , c , 0 , - 1 )
240264 if err != nil {
241265 return nil , err
242266 }
@@ -257,7 +281,15 @@ func (f *Forge) GetBlock(ctx context.Context, space did.DID, c cid.Cid) (block.B
257281// straight off the /content/retrieve response; the caller owns the reader and
258282// must Close it.
259283func (f * Forge ) OpenBlob (ctx context.Context , space did.DID , digest mh.Multihash ) (io.ReadCloser , error ) {
260- rc , _ , err := f .retrieve (ctx , space , cid .NewCidV1 (cid .Raw , digest ))
284+ rc , _ , err := f .retrieve (ctx , space , cid .NewCidV1 (cid .Raw , digest ), 0 , - 1 )
285+ return rc , err
286+ }
287+
288+ // OpenBlobRange streams stored bytes [off, off+n) of an object-body blob from
289+ // piri — a ranged /content/retrieve, so the decrypting read path fetches only
290+ // the ciphertext chunks a plaintext range needs rather than the whole blob.
291+ func (f * Forge ) OpenBlobRange (ctx context.Context , space did.DID , digest mh.Multihash , off , n int64 ) (io.ReadCloser , error ) {
292+ rc , _ , err := f .retrieve (ctx , space , cid .NewCidV1 (cid .Raw , digest ), off , n )
261293 return rc , err
262294}
263295
0 commit comments