1package discovery
2
3import (
4	"context"
5	"github.com/libp2p/go-libp2p-core/discovery"
6	"time"
7
8	cid "github.com/ipfs/go-cid"
9
10	"github.com/libp2p/go-libp2p-core/peer"
11	"github.com/libp2p/go-libp2p-core/routing"
12
13	mh "github.com/multiformats/go-multihash"
14)
15
16// RoutingDiscovery is an implementation of discovery using ContentRouting.
17// Namespaces are translated to Cids using the SHA256 hash.
18type RoutingDiscovery struct {
19	routing.ContentRouting
20}
21
22func NewRoutingDiscovery(router routing.ContentRouting) *RoutingDiscovery {
23	return &RoutingDiscovery{router}
24}
25
26func (d *RoutingDiscovery) Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error) {
27	var options Options
28	err := options.Apply(opts...)
29	if err != nil {
30		return 0, err
31	}
32
33	ttl := options.Ttl
34	if ttl == 0 || ttl > 3*time.Hour {
35		// the DHT provider record validity is 24hrs, but it is recommnded to republish at least every 6hrs
36		// we go one step further and republish every 3hrs
37		ttl = 3 * time.Hour
38	}
39
40	cid, err := nsToCid(ns)
41	if err != nil {
42		return 0, err
43	}
44
45	// this context requires a timeout; it determines how long the DHT looks for
46	// closest peers to the key/CID before it goes on to provide the record to them.
47	// Not setting a timeout here will make the DHT wander forever.
48	pctx, cancel := context.WithTimeout(ctx, 60*time.Second)
49	defer cancel()
50
51	err = d.Provide(pctx, cid, true)
52	if err != nil {
53		return 0, err
54	}
55
56	return ttl, nil
57}
58
59func (d *RoutingDiscovery) FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan peer.AddrInfo, error) {
60	var options Options
61	err := options.Apply(opts...)
62	if err != nil {
63		return nil, err
64	}
65
66	limit := options.Limit
67	if limit == 0 {
68		limit = 100 // that's just arbitrary, but FindProvidersAsync needs a count
69	}
70
71	cid, err := nsToCid(ns)
72	if err != nil {
73		return nil, err
74	}
75
76	return d.FindProvidersAsync(ctx, cid, limit), nil
77}
78
79func nsToCid(ns string) (cid.Cid, error) {
80	h, err := mh.Sum([]byte(ns), mh.SHA2_256, -1)
81	if err != nil {
82		return cid.Undef, err
83	}
84
85	return cid.NewCidV1(cid.Raw, h), nil
86}
87
88func NewDiscoveryRouting(disc discovery.Discovery, opts ...discovery.Option) *DiscoveryRouting {
89	return &DiscoveryRouting{disc, opts}
90}
91
92type DiscoveryRouting struct {
93	discovery.Discovery
94	opts []discovery.Option
95}
96
97func (r *DiscoveryRouting) Provide(ctx context.Context, c cid.Cid, bcast bool) error {
98	if !bcast {
99		return nil
100	}
101
102	_, err := r.Advertise(ctx, cidToNs(c), r.opts...)
103	return err
104}
105
106func (r *DiscoveryRouting) FindProvidersAsync(ctx context.Context, c cid.Cid, limit int) <-chan peer.AddrInfo {
107	ch, _ := r.FindPeers(ctx, cidToNs(c), append([]discovery.Option{discovery.Limit(limit)}, r.opts...)...)
108	return ch
109}
110
111func cidToNs(c cid.Cid) string {
112	return "/provider/" + c.String()
113}
114