@@ -25,6 +25,7 @@ import (
2525 "github.com/libp2p/go-libp2p/core/crypto"
2626 "github.com/libp2p/go-libp2p/core/peer"
2727 "github.com/multiformats/go-multiaddr"
28+ "github.com/multiformats/go-multibase"
2829)
2930
3031var (
@@ -51,22 +52,20 @@ type Client struct {
5152 clock clock.Clock
5253 accepts string
5354
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
5759
58- // Called immediately after signing a provide request. It is used
60+ // Called immediately after signing a provide (peer) request. It is used
5961 // 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 )
6263}
6364
6465// defaultUserAgent is used as a fallback to inform HTTP server which library
6566// version sent a request
6667var defaultUserAgent = moduleVersion ()
6768
68- var _ contentrouter.Client = & Client {}
69-
7069type httpClient interface {
7170 Do (req * http.Request ) (* http.Response , error )
7271}
@@ -102,9 +101,10 @@ func WithUserAgent(ua string) Option {
102101 }
103102}
104103
105- func WithProviderInfo (peerID peer.ID , addrs []multiaddr.Multiaddr ) Option {
104+ func WithProviderInfo (peerID peer.ID , addrs []multiaddr.Multiaddr , protocols [] string ) Option {
106105 return func (c * Client ) {
107106 c .peerID = peerID
107+ c .protocols = protocols
108108 for _ , a := range addrs {
109109 c .addrs = append (c .addrs , types.Multiaddr {Multiaddr : a })
110110 }
@@ -236,102 +236,121 @@ func (c *Client) FindProviders(ctx context.Context, key cid.Cid) (providers iter
236236 return & measuringIter [iter.Result [types.Record ]]{Iter : it , ctx : ctx , m : m }, nil
237237}
238238
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
253242 }
254243
255244 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+ }
256260
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+ }
272268
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+ if c .afterSignCallback != nil {
275+ c .afterSignCallback (record )
276+ }
277+
278+ records [i ] = record
275279 }
276280
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 ,
280285 }
281286
282- return advisoryTTL , err
287+ return c . provide ( ctx , url , req )
283288}
284289
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 ) {
294291 b , err := drjson .MarshalJSONBytes (req )
295292 if err != nil {
296- return 0 , err
293+ return nil , err
297294 }
298295
299296 httpReq , err := http .NewRequestWithContext (ctx , http .MethodPut , url , bytes .NewBuffer (b ))
300297 if err != nil {
301- return 0 , err
298+ return nil , err
302299 }
303300
304301 resp , err := c .httpClient .Do (httpReq )
305302 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 )
307304 }
308- defer resp .Body .Close ()
309305
310306 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 )
312309 }
313310
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 )
317313 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 )
322316 }
323317
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+ }()
329324
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" )
332341 }
333342
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
335354}
336355
337356// 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
395414 case mediaTypeJSON :
396415 parsedResp := & jsontypes.PeersResponse {}
397416 err = json .NewDecoder (resp .Body ).Decode (parsedResp )
417+ if err != nil {
418+ return nil , err
419+ }
398420 var sliceIt iter.Iter [* types.PeerRecord ] = iter .FromSlice (parsedResp .Peers )
399421 it = iter .ToResultIter (sliceIt )
400422 case mediaTypeNDJSON :
@@ -408,6 +430,50 @@ func (c *Client) FindPeers(ctx context.Context, pid peer.ID) (peers iter.ResultI
408430 return & measuringIter [iter.Result [* types.PeerRecord ]]{Iter : it , ctx : ctx , m : m }, nil
409431}
410432
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+
411477// GetIPNS tries to retrieve the [ipns.Record] for the given [ipns.Name]. The record is
412478// validated against the given name. If validation fails, an error is returned, but no
413479// record.
0 commit comments