// Package offline implements IpfsRouting with a client which // is only able to perform offline operations. package offline import ( "bytes" "context" "errors" "time" proto "github.com/gogo/protobuf/proto" cid "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dshelp "github.com/ipfs/go-ipfs-ds-help" "github.com/libp2p/go-libp2p-peer" pstore "github.com/libp2p/go-libp2p-peerstore" record "github.com/libp2p/go-libp2p-record" pb "github.com/libp2p/go-libp2p-record/pb" routing "github.com/libp2p/go-libp2p-routing" ropts "github.com/libp2p/go-libp2p-routing/options" ) // ErrOffline is returned when trying to perform operations that // require connectivity. var ErrOffline = errors.New("routing system in offline mode") // NewOfflineRouter returns an IpfsRouting implementation which only performs // offline operations. It allows to Put and Get signed dht // records to and from the local datastore. func NewOfflineRouter(dstore ds.Datastore, validator record.Validator) routing.IpfsRouting { return &offlineRouting{ datastore: dstore, validator: validator, } } // offlineRouting implements the IpfsRouting interface, // but only provides the capability to Put and Get signed dht // records to and from the local datastore. type offlineRouting struct { datastore ds.Datastore validator record.Validator } func (c *offlineRouting) PutValue(ctx context.Context, key string, val []byte, _ ...ropts.Option) error { if err := c.validator.Validate(key, val); err != nil { return err } if old, err := c.GetValue(ctx, key); err == nil { // be idempotent to be nice. if bytes.Equal(old, val) { return nil } // check to see if the older record is better i, err := c.validator.Select(key, [][]byte{val, old}) if err != nil { // this shouldn't happen for validated records. return err } if i != 0 { return errors.New("can't replace a newer record with an older one") } } rec := record.MakePutRecord(key, val) data, err := proto.Marshal(rec) if err != nil { return err } return c.datastore.Put(dshelp.NewKeyFromBinary([]byte(key)), data) } func (c *offlineRouting) GetValue(ctx context.Context, key string, _ ...ropts.Option) ([]byte, error) { buf, err := c.datastore.Get(dshelp.NewKeyFromBinary([]byte(key))) if err != nil { return nil, err } rec := new(pb.Record) err = proto.Unmarshal(buf, rec) if err != nil { return nil, err } val := rec.GetValue() err = c.validator.Validate(key, val) if err != nil { return nil, err } return val, nil } func (c *offlineRouting) SearchValue(ctx context.Context, key string, _ ...ropts.Option) (<-chan []byte, error) { out := make(chan []byte, 1) go func() { defer close(out) v, err := c.GetValue(ctx, key) if err == nil { out <- v } }() return out, nil } func (c *offlineRouting) FindPeer(ctx context.Context, pid peer.ID) (pstore.PeerInfo, error) { return pstore.PeerInfo{}, ErrOffline } func (c *offlineRouting) FindProvidersAsync(ctx context.Context, k cid.Cid, max int) <-chan pstore.PeerInfo { out := make(chan pstore.PeerInfo) close(out) return out } func (c *offlineRouting) Provide(_ context.Context, k cid.Cid, _ bool) error { return ErrOffline } func (c *offlineRouting) Ping(ctx context.Context, p peer.ID) (time.Duration, error) { return 0, ErrOffline } func (c *offlineRouting) Bootstrap(context.Context) error { return nil } // ensure offlineRouting matches the IpfsRouting interface var _ routing.IpfsRouting = &offlineRouting{}