@@ -25,6 +25,7 @@ import (
25
25
"github.com/libp2p/go-libp2p/core/crypto"
26
26
"github.com/libp2p/go-libp2p/core/peer"
27
27
"github.com/multiformats/go-multiaddr"
28
+ "github.com/multiformats/go-multibase"
28
29
)
29
30
30
31
var (
@@ -51,22 +52,20 @@ type Client struct {
51
52
clock clock.Clock
52
53
accepts string
53
54
54
- peerID peer.ID
55
- addrs []types.Multiaddr
56
- identity crypto.PrivKey
55
+ identity crypto.PrivKey
56
+ peerID peer.ID
57
+ addrs []types.Multiaddr
58
+ protocols []string
57
59
58
- // Called immediately after signing a provide request. It is used
60
+ // Called immediately after signing a provide (peer) request. It is used
59
61
// for testing, e.g., testing the server with a mangled signature.
60
- //lint:ignore SA1019 // ignore staticcheck
61
- afterSignCallback func (req * types.WriteBitswapRecord )
62
+ afterSignCallback func (req * types.AnnouncementRecord )
62
63
}
63
64
64
65
// defaultUserAgent is used as a fallback to inform HTTP server which library
65
66
// version sent a request
66
67
var defaultUserAgent = moduleVersion ()
67
68
68
- var _ contentrouter.Client = & Client {}
69
-
70
69
type httpClient interface {
71
70
Do (req * http.Request ) (* http.Response , error )
72
71
}
@@ -102,9 +101,10 @@ func WithUserAgent(ua string) Option {
102
101
}
103
102
}
104
103
105
- func WithProviderInfo (peerID peer.ID , addrs []multiaddr.Multiaddr ) Option {
104
+ func WithProviderInfo (peerID peer.ID , addrs []multiaddr.Multiaddr , protocols [] string ) Option {
106
105
return func (c * Client ) {
107
106
c .peerID = peerID
107
+ c .protocols = protocols
108
108
for _ , a := range addrs {
109
109
c .addrs = append (c .addrs , types.Multiaddr {Multiaddr : a })
110
110
}
@@ -236,102 +236,121 @@ func (c *Client) FindProviders(ctx context.Context, key cid.Cid) (providers iter
236
236
return & measuringIter [iter.Result [types.Record ]]{Iter : it , ctx : ctx , m : m }, nil
237
237
}
238
238
239
- // Deprecated: protocol-agnostic provide is being worked on in [IPIP-378]:
240
- //
241
- // [IPIP-378]: https://github.com/ipfs/specs/pull/378
242
- func (c * Client ) ProvideBitswap (ctx context.Context , keys []cid.Cid , ttl time.Duration ) (time.Duration , error ) {
243
- if c .identity == nil {
244
- return 0 , errors .New ("cannot provide Bitswap records without an identity" )
245
- }
246
- if c .peerID .Size () == 0 {
247
- return 0 , errors .New ("cannot provide Bitswap records without a peer ID" )
248
- }
249
-
250
- ks := make ([]types.CID , len (keys ))
251
- for i , c := range keys {
252
- ks [i ] = types.CID {Cid : c }
239
+ func (c * Client ) Provide (ctx context.Context , announcements ... types.AnnouncementRequest ) (iter.ResultIter [* types.AnnouncementRecord ], error ) {
240
+ if err := c .canProvide (); err != nil {
241
+ return nil , err
253
242
}
254
243
255
244
now := c .clock .Now ()
245
+ records := make ([]types.Record , len (announcements ))
246
+
247
+ for i , announcement := range announcements {
248
+ record := & types.AnnouncementRecord {
249
+ Schema : types .SchemaAnnouncement ,
250
+ Payload : types.AnnouncementPayload {
251
+ CID : announcement .CID ,
252
+ Scope : announcement .Scope ,
253
+ Timestamp : now ,
254
+ TTL : announcement .TTL ,
255
+ ID : & c .peerID ,
256
+ Addrs : c .addrs ,
257
+ Protocols : c .protocols ,
258
+ },
259
+ }
256
260
257
- req := types.WriteBitswapRecord {
258
- Protocol : "transport-bitswap" ,
259
- Schema : types .SchemaBitswap ,
260
- Payload : types.BitswapPayload {
261
- Keys : ks ,
262
- AdvisoryTTL : & types.Duration {Duration : ttl },
263
- Timestamp : & types.Time {Time : now },
264
- ID : & c .peerID ,
265
- Addrs : c .addrs ,
266
- },
267
- }
268
- err := req .Sign (c .peerID , c .identity )
269
- if err != nil {
270
- return 0 , err
271
- }
261
+ if len (announcement .Metadata ) != 0 {
262
+ var err error
263
+ record .Payload .Metadata , err = multibase .Encode (multibase .Base64 , announcement .Metadata )
264
+ if err != nil {
265
+ return nil , fmt .Errorf ("multibase-encoding metadata: %w" , err )
266
+ }
267
+ }
272
268
273
- if c .afterSignCallback != nil {
274
- c .afterSignCallback (& req )
269
+ err := record .Sign (c .peerID , c .identity )
270
+ if err != nil {
271
+ return nil , err
272
+ }
273
+
274
+ records [i ] = record
275
+
276
+ if c .afterSignCallback != nil {
277
+ c .afterSignCallback (record )
278
+ }
275
279
}
276
280
277
- advisoryTTL , err := c .provideSignedBitswapRecord (ctx , & req )
278
- if err != nil {
279
- return 0 , err
281
+ // TODO: trailing slash?
282
+ url := c .baseURL + "/routing/v1/providers"
283
+ req := jsontypes.AnnounceProvidersRequest {
284
+ Providers : records ,
280
285
}
281
286
282
- return advisoryTTL , err
287
+ return c . provide ( ctx , url , req )
283
288
}
284
289
285
- // ProvideAsync makes a provide request to a delegated router
286
- //
287
- //lint:ignore SA1019 // ignore staticcheck
288
- func (c * Client ) provideSignedBitswapRecord (ctx context.Context , bswp * types.WriteBitswapRecord ) (time.Duration , error ) {
289
- //lint:ignore SA1019 // ignore staticcheck
290
- req := jsontypes.WriteProvidersRequest {Providers : []types.Record {bswp }}
291
-
292
- url := c .baseURL + "/routing/v1/providers/"
293
-
290
+ func (c * Client ) provide (ctx context.Context , url string , req interface {}) (iter.ResultIter [* types.AnnouncementRecord ], error ) {
294
291
b , err := drjson .MarshalJSONBytes (req )
295
292
if err != nil {
296
- return 0 , err
293
+ return nil , err
297
294
}
298
295
299
296
httpReq , err := http .NewRequestWithContext (ctx , http .MethodPut , url , bytes .NewBuffer (b ))
300
297
if err != nil {
301
- return 0 , err
298
+ return nil , err
302
299
}
303
300
304
301
resp , err := c .httpClient .Do (httpReq )
305
302
if err != nil {
306
- return 0 , fmt .Errorf ("making HTTP req to provide a signed record: %w" , err )
303
+ return nil , fmt .Errorf ("making HTTP req to provide a signed peer record: %w" , err )
307
304
}
308
- defer resp .Body .Close ()
309
305
310
306
if resp .StatusCode != http .StatusOK {
311
- return 0 , httpError (resp .StatusCode , resp .Body )
307
+ resp .Body .Close ()
308
+ return nil , httpError (resp .StatusCode , resp .Body )
312
309
}
313
310
314
- //lint:ignore SA1019 // ignore staticcheck
315
- var provideResult jsontypes.WriteProvidersResponse
316
- err = json .NewDecoder (resp .Body ).Decode (& provideResult )
311
+ respContentType := resp .Header .Get ("Content-Type" )
312
+ mediaType , _ , err := mime .ParseMediaType (respContentType )
317
313
if err != nil {
318
- return 0 , err
319
- }
320
- if len (provideResult .ProvideResults ) != 1 {
321
- return 0 , fmt .Errorf ("expected 1 result but got %d" , len (provideResult .ProvideResults ))
314
+ resp .Body .Close ()
315
+ return nil , fmt .Errorf ("parsing Content-Type: %w" , err )
322
316
}
323
317
324
- //lint:ignore SA1019 // ignore staticcheck
325
- v , ok := provideResult .ProvideResults [0 ].(* types.WriteBitswapRecordResponse )
326
- if ! ok {
327
- return 0 , errors .New ("expected AdvisoryTTL field" )
328
- }
318
+ var skipBodyClose bool
319
+ defer func () {
320
+ if ! skipBodyClose {
321
+ resp .Body .Close ()
322
+ }
323
+ }()
329
324
330
- if v .AdvisoryTTL != nil {
331
- return v .AdvisoryTTL .Duration , nil
325
+ var it iter.ResultIter [* types.AnnouncementRecord ]
326
+ switch mediaType {
327
+ case mediaTypeJSON :
328
+ parsedResp := & jsontypes.AnnouncePeersResponse {}
329
+ err = json .NewDecoder (resp .Body ).Decode (parsedResp )
330
+ if err != nil {
331
+ return nil , err
332
+ }
333
+ var sliceIt iter.Iter [* types.AnnouncementRecord ] = iter .FromSlice (parsedResp .ProvideResults )
334
+ it = iter .ToResultIter (sliceIt )
335
+ case mediaTypeNDJSON :
336
+ skipBodyClose = true
337
+ it = ndjson .NewAnnouncementRecordsIter (resp .Body )
338
+ default :
339
+ logger .Errorw ("unknown media type" , "MediaType" , mediaType , "ContentType" , respContentType )
340
+ return nil , errors .New ("unknown content type" )
332
341
}
333
342
334
- return 0 , nil
343
+ return it , nil
344
+ }
345
+
346
+ func (c * Client ) canProvide () error {
347
+ if c .identity == nil {
348
+ return errors .New ("cannot provide without identity" )
349
+ }
350
+ if c .peerID .Size () == 0 {
351
+ return errors .New ("cannot provide without peer ID" )
352
+ }
353
+ return nil
335
354
}
336
355
337
356
// FindPeers searches for information for the given [peer.ID].
@@ -395,6 +414,9 @@ func (c *Client) FindPeers(ctx context.Context, pid peer.ID) (peers iter.ResultI
395
414
case mediaTypeJSON :
396
415
parsedResp := & jsontypes.PeersResponse {}
397
416
err = json .NewDecoder (resp .Body ).Decode (parsedResp )
417
+ if err != nil {
418
+ return nil , err
419
+ }
398
420
var sliceIt iter.Iter [* types.PeerRecord ] = iter .FromSlice (parsedResp .Peers )
399
421
it = iter .ToResultIter (sliceIt )
400
422
case mediaTypeNDJSON :
@@ -408,6 +430,50 @@ func (c *Client) FindPeers(ctx context.Context, pid peer.ID) (peers iter.ResultI
408
430
return & measuringIter [iter.Result [* types.PeerRecord ]]{Iter : it , ctx : ctx , m : m }, nil
409
431
}
410
432
433
+ // ProvidePeer provides information regarding your own peer, setup with [WithProviderInfo].
434
+ func (c * Client ) ProvidePeer (ctx context.Context , ttl time.Duration , metadata []byte ) (iter.ResultIter [* types.AnnouncementRecord ], error ) {
435
+ if err := c .canProvide (); err != nil {
436
+ return nil , err
437
+ }
438
+
439
+ record := & types.AnnouncementRecord {
440
+ Schema : types .SchemaAnnouncement ,
441
+ Payload : types.AnnouncementPayload {
442
+ // TODO: CID, Scope not present for /routing/v1/peers, right?
443
+ Timestamp : time .Now (),
444
+ TTL : ttl ,
445
+ ID : & c .peerID ,
446
+ Addrs : c .addrs ,
447
+ Protocols : c .protocols ,
448
+ },
449
+ }
450
+
451
+ if len (metadata ) != 0 {
452
+ var err error
453
+ record .Payload .Metadata , err = multibase .Encode (multibase .Base64 , metadata )
454
+ if err != nil {
455
+ return nil , fmt .Errorf ("multibase-encoding metadata: %w" , err )
456
+ }
457
+ }
458
+
459
+ err := record .Sign (c .peerID , c .identity )
460
+ if err != nil {
461
+ return nil , err
462
+ }
463
+
464
+ if c .afterSignCallback != nil {
465
+ c .afterSignCallback (record )
466
+ }
467
+
468
+ // TODO: trailing slash?
469
+ url := c .baseURL + "/routing/v1/peers"
470
+ req := jsontypes.AnnouncePeersRequest {
471
+ Providers : []types.Record {record },
472
+ }
473
+
474
+ return c .provide (ctx , url , req )
475
+ }
476
+
411
477
// GetIPNS tries to retrieve the [ipns.Record] for the given [ipns.Name]. The record is
412
478
// validated against the given name. If validation fails, an error is returned, but no
413
479
// record.
0 commit comments