Skip to content

Commit ab7079e

Browse files
committed
feat: contentrouter implements routing.ValueStore
1 parent e4eceac commit ab7079e

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

routing/http/contentrouter/contentrouter.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package contentrouter
33
import (
44
"context"
55
"reflect"
6+
"strings"
67

8+
"github.com/ipfs/boxo/ipns"
79
"github.com/ipfs/boxo/routing/http/types"
810
"github.com/ipfs/boxo/routing/http/types/iter"
911
"github.com/ipfs/go-cid"
@@ -20,6 +22,8 @@ var logger = logging.Logger("routing/http/contentrouter")
2022
type Client interface {
2123
GetProviders(ctx context.Context, key cid.Cid) (iter.ResultIter[types.Record], error)
2224
GetPeers(ctx context.Context, pid peer.ID) (peers iter.ResultIter[types.Record], err error)
25+
GetIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error)
26+
PutIPNSRecord(ctx context.Context, name ipns.Name, record *ipns.Record) error
2327
}
2428

2529
type contentRouter struct {
@@ -28,6 +32,7 @@ type contentRouter struct {
2832

2933
var _ routing.ContentRouting = (*contentRouter)(nil)
3034
var _ routing.PeerRouting = (*contentRouter)(nil)
35+
var _ routing.ValueStore = (*contentRouter)(nil)
3136
var _ routinghelpers.ProvideManyRouter = (*contentRouter)(nil)
3237
var _ routinghelpers.ReadyAbleRouter = (*contentRouter)(nil)
3338

@@ -143,3 +148,71 @@ func (c *contentRouter) FindPeer(ctx context.Context, pid peer.ID) (peer.AddrInf
143148

144149
return peer.AddrInfo{}, err
145150
}
151+
152+
func (c *contentRouter) PutValue(ctx context.Context, key string, data []byte, opts ...routing.Option) error {
153+
if !strings.HasPrefix(key, "/ipns/") {
154+
return routing.ErrNotSupported
155+
}
156+
157+
name, err := ipns.NameFromRoutingKey([]byte(key))
158+
if err != nil {
159+
return err
160+
}
161+
162+
record, err := ipns.UnmarshalRecord(data)
163+
if err != nil {
164+
return err
165+
}
166+
167+
return c.client.PutIPNSRecord(ctx, name, record)
168+
}
169+
170+
func (c *contentRouter) GetValue(ctx context.Context, key string, opts ...routing.Option) ([]byte, error) {
171+
if !strings.HasPrefix(key, "/ipns/") {
172+
return nil, routing.ErrNotSupported
173+
}
174+
175+
name, err := ipns.NameFromRoutingKey([]byte(key))
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
record, err := c.client.GetIPNSRecord(ctx, name)
181+
if err != nil {
182+
return nil, err
183+
}
184+
185+
return ipns.MarshalRecord(record)
186+
}
187+
188+
func (c *contentRouter) SearchValue(ctx context.Context, key string, opts ...routing.Option) (<-chan []byte, error) {
189+
if !strings.HasPrefix(key, "/ipns/") {
190+
return nil, routing.ErrNotSupported
191+
}
192+
193+
name, err := ipns.NameFromRoutingKey([]byte(key))
194+
if err != nil {
195+
return nil, err
196+
}
197+
198+
ch := make(chan []byte)
199+
200+
go func() {
201+
record, err := c.client.GetIPNSRecord(ctx, name)
202+
if err != nil {
203+
close(ch)
204+
return
205+
}
206+
207+
raw, err := ipns.MarshalRecord(record)
208+
if err != nil {
209+
close(ch)
210+
return
211+
}
212+
213+
ch <- raw
214+
close(ch)
215+
}()
216+
217+
return ch, nil
218+
}

routing/http/contentrouter/contentrouter_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ import (
44
"context"
55
"crypto/rand"
66
"testing"
7+
"time"
78

9+
"github.com/ipfs/boxo/coreiface/path"
10+
"github.com/ipfs/boxo/ipns"
11+
ipfspath "github.com/ipfs/boxo/path"
812
"github.com/ipfs/boxo/routing/http/types"
913
"github.com/ipfs/boxo/routing/http/types/iter"
1014
"github.com/ipfs/go-cid"
15+
"github.com/libp2p/go-libp2p/core/crypto"
1116
"github.com/libp2p/go-libp2p/core/peer"
17+
"github.com/libp2p/go-libp2p/core/routing"
1218
"github.com/multiformats/go-multihash"
1319
"github.com/stretchr/testify/mock"
1420
"github.com/stretchr/testify/require"
@@ -28,6 +34,15 @@ func (m *mockClient) Ready(ctx context.Context) (bool, error) {
2834
args := m.Called(ctx)
2935
return args.Bool(0), args.Error(1)
3036
}
37+
func (m *mockClient) GetIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
38+
args := m.Called(ctx, name)
39+
return args.Get(0).(*ipns.Record), args.Error(1)
40+
}
41+
func (m *mockClient) PutIPNSRecord(ctx context.Context, name ipns.Name, record *ipns.Record) error {
42+
args := m.Called(ctx, name, record)
43+
return args.Error(0)
44+
}
45+
3146
func makeCID() cid.Cid {
3247
buf := make([]byte, 63)
3348
_, err := rand.Read(buf)
@@ -108,3 +123,87 @@ func TestFindPeer(t *testing.T) {
108123
require.NoError(t, err)
109124
require.Equal(t, peer.ID, p1)
110125
}
126+
127+
func makeName(t *testing.T) (crypto.PrivKey, ipns.Name) {
128+
sk, _, err := crypto.GenerateEd25519Key(rand.Reader)
129+
require.NoError(t, err)
130+
131+
pid, err := peer.IDFromPrivateKey(sk)
132+
require.NoError(t, err)
133+
134+
return sk, ipns.NameFromPeer(pid)
135+
}
136+
137+
func makeIPNSRecord(t *testing.T, sk crypto.PrivKey, opts ...ipns.Option) (*ipns.Record, []byte) {
138+
cid, err := cid.Decode("bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4")
139+
require.NoError(t, err)
140+
141+
path := path.IpfsPath(cid)
142+
eol := time.Now().Add(time.Hour * 48)
143+
ttl := time.Second * 20
144+
145+
record, err := ipns.NewRecord(sk, ipfspath.FromString(path.String()), 1, eol, ttl, opts...)
146+
require.NoError(t, err)
147+
148+
rawRecord, err := ipns.MarshalRecord(record)
149+
require.NoError(t, err)
150+
151+
return record, rawRecord
152+
}
153+
154+
func TestGetValue(t *testing.T) {
155+
ctx := context.Background()
156+
client := &mockClient{}
157+
crc := NewContentRoutingClient(client)
158+
159+
t.Run("Fail On Unsupported Key", func(t *testing.T) {
160+
v, err := crc.GetValue(ctx, "/something/unsupported")
161+
require.Nil(t, v)
162+
require.ErrorIs(t, err, routing.ErrNotSupported)
163+
})
164+
165+
t.Run("Fail On Invalid IPNS Name", func(t *testing.T) {
166+
v, err := crc.GetValue(ctx, "/ipns/invalid")
167+
require.Nil(t, v)
168+
require.Error(t, err)
169+
})
170+
171+
t.Run("Succeeds On Valid IPNS Name", func(t *testing.T) {
172+
sk, name := makeName(t)
173+
rec, rawRec := makeIPNSRecord(t, sk)
174+
client.On("GetIPNSRecord", ctx, name).Return(rec, nil)
175+
v, err := crc.GetValue(ctx, string(name.RoutingKey()))
176+
require.NoError(t, err)
177+
require.Equal(t, rawRec, v)
178+
})
179+
}
180+
181+
func TestPutValue(t *testing.T) {
182+
ctx := context.Background()
183+
client := &mockClient{}
184+
crc := NewContentRoutingClient(client)
185+
186+
sk, name := makeName(t)
187+
_, rawRec := makeIPNSRecord(t, sk)
188+
189+
t.Run("Fail On Unsupported Key", func(t *testing.T) {
190+
err := crc.PutValue(ctx, "/something/unsupported", rawRec)
191+
require.ErrorIs(t, err, routing.ErrNotSupported)
192+
})
193+
194+
t.Run("Fail On Invalid IPNS Name", func(t *testing.T) {
195+
err := crc.PutValue(ctx, "/ipns/invalid", rawRec)
196+
require.Error(t, err)
197+
})
198+
199+
t.Run("Fail On Invalid IPNS Record", func(t *testing.T) {
200+
err := crc.PutValue(ctx, string(name.RoutingKey()), []byte("gibberish"))
201+
require.Error(t, err)
202+
})
203+
204+
t.Run("Succeeds On Valid IPNS Name & Record", func(t *testing.T) {
205+
client.On("PutIPNSRecord", ctx, name, mock.Anything).Return(nil)
206+
err := crc.PutValue(ctx, string(name.RoutingKey()), rawRec)
207+
require.NoError(t, err)
208+
})
209+
}

0 commit comments

Comments
 (0)