diff --git a/cmd/skywire-cli/commands/rtfind/root.go b/cmd/skywire-cli/commands/rtfind/root.go index 719b76ef9..4395c09ac 100644 --- a/cmd/skywire-cli/commands/rtfind/root.go +++ b/cmd/skywire-cli/commands/rtfind/root.go @@ -1,6 +1,7 @@ package rtfind import ( + "context" "fmt" "time" @@ -9,6 +10,7 @@ import ( "github.com/skycoin/skywire/cmd/skywire-cli/internal" "github.com/skycoin/skywire/pkg/route-finder/client" + "github.com/skycoin/skywire/pkg/routing" ) var frAddr string @@ -33,11 +35,14 @@ var RootCmd = &cobra.Command{ var srcPK, dstPK cipher.PubKey internal.Catch(srcPK.Set(args[0])) internal.Catch(dstPK.Set(args[1])) - - forward, reverse, err := rfc.PairedRoutes(srcPK, dstPK, frMinHops, frMaxHops) + forward := [2]cipher.PubKey{srcPK, dstPK} + backward := [2]cipher.PubKey{dstPK, srcPK} + ctx := context.Background() + routes, err := rfc.FindRoutes(ctx, []routing.PathEdges{forward, backward}, + &client.RouteOptions{MinHops: frMinHops, MaxHops: frMaxHops}) internal.Catch(err) - fmt.Println("forward: ", forward) - fmt.Println("reverse: ", reverse) + fmt.Println("forward: ", routes[forward][0]) + fmt.Println("reverse: ", routes[backward][0]) }, } diff --git a/go.sum b/go.sum index 60f365b04..2beaa995c 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,7 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/pkg/route-finder/client/client.go b/pkg/route-finder/client/client.go index 6fa5b5335..72e5f0c92 100644 --- a/pkg/route-finder/client/client.go +++ b/pkg/route-finder/client/client.go @@ -11,7 +11,6 @@ import ( "strings" "time" - "github.com/skycoin/dmsg/cipher" "github.com/skycoin/skycoin/src/util/logging" "github.com/skycoin/skywire/pkg/routing" @@ -21,18 +20,16 @@ const defaultContextTimeout = 10 * time.Second var log = logging.MustGetLogger("route-finder") -// GetRoutesRequest parses json body for /routes endpoint request -type GetRoutesRequest struct { - SrcPK cipher.PubKey `json:"src_pk,omitempty"` - DstPK cipher.PubKey `json:"dst_pk,omitempty"` - MinHops uint16 `json:"min_hops,omitempty"` - MaxHops uint16 `json:"max_hops,omitempty"` +// RouteOptions for FindRoutesRequest. If nil MinHops and MaxHops will take default values +type RouteOptions struct { + MinHops uint16 + MaxHops uint16 } -// GetRoutesResponse encodes the json body of /routes response -type GetRoutesResponse struct { - Forward []routing.Route `json:"forward"` - Reverse []routing.Route `json:"response"` +// FindRoutesRequest parses json body for /routes endpoint request +type FindRoutesRequest struct { + Edges []routing.PathEdges + Opts *RouteOptions } // HTTPResponse represents http response struct @@ -49,7 +46,7 @@ type HTTPError struct { // Client implements route finding operations. type Client interface { - PairedRoutes(source, destiny cipher.PubKey, minHops, maxHops uint16) ([]routing.Route, []routing.Route, error) + FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][]routing.Path, error) } // APIClient implements Client interface @@ -72,26 +69,24 @@ func NewHTTP(addr string, apiTimeout time.Duration) Client { } } -// PairedRoutes returns routes from source skywire visor to destiny, that has at least the given minHops and as much -// the given maxHops as well as the reverse routes from destiny to source. -func (c *apiClient) PairedRoutes(source, destiny cipher.PubKey, minHops, maxHops uint16) ([]routing.Route, []routing.Route, error) { - requestBody := &GetRoutesRequest{ - SrcPK: source, - DstPK: destiny, - MinHops: minHops, - MaxHops: maxHops, +// FindRoutes returns routes from source skywire visor to destiny, that has at least the given minHops and as much +// the given maxHops. +func (c *apiClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][]routing.Path, error) { + requestBody := &FindRoutesRequest{ + Edges: rts, + Opts: opts, } marshaledBody, err := json.Marshal(requestBody) if err != nil { - return nil, nil, err + return nil, err } req, err := http.NewRequest(http.MethodGet, c.addr+"/routes", bytes.NewBuffer(marshaledBody)) if err != nil { - return nil, nil, err + return nil, err } req.Header.Set("Content-Type", "application/json") - ctx, cancel := context.WithTimeout(context.Background(), c.apiTimeout) + ctx, cancel := context.WithTimeout(ctx, c.apiTimeout) defer cancel() req = req.WithContext(ctx) @@ -104,7 +99,7 @@ func (c *apiClient) PairedRoutes(source, destiny cipher.PubKey, minHops, maxHops }() } if err != nil { - return nil, nil, err + return nil, err } if res.StatusCode != http.StatusOK { @@ -112,19 +107,19 @@ func (c *apiClient) PairedRoutes(source, destiny cipher.PubKey, minHops, maxHops err = json.NewDecoder(res.Body).Decode(&apiErr) if err != nil { - return nil, nil, err + return nil, err } - return nil, nil, errors.New(apiErr.Error.Message) + return nil, errors.New(apiErr.Error.Message) } - var routes GetRoutesResponse - err = json.NewDecoder(res.Body).Decode(&routes) + var paths map[routing.PathEdges][]routing.Path + err = json.NewDecoder(res.Body).Decode(&paths) if err != nil { - return nil, nil, err + return nil, err } - return routes.Forward, routes.Reverse, nil + return paths, nil } func sanitizedAddr(addr string) string { diff --git a/pkg/route-finder/client/mock.go b/pkg/route-finder/client/mock.go index 3184b2b91..359802a91 100644 --- a/pkg/route-finder/client/mock.go +++ b/pkg/route-finder/client/mock.go @@ -1,6 +1,9 @@ package client import ( + "context" + "fmt" + "github.com/skycoin/dmsg/cipher" "github.com/skycoin/skywire/pkg/routing" @@ -23,27 +26,24 @@ func (r *mockClient) SetError(err error) { r.err = err } -// PairedRoutes implements Client for MockClient -func (r *mockClient) PairedRoutes(src, dst cipher.PubKey, minHops, maxHops uint16) ([]routing.Route, []routing.Route, error) { +// FindRoutes implements Client for MockClient +func (r *mockClient) FindRoutes(ctx context.Context, rts []routing.PathEdges, opts *RouteOptions) (map[routing.PathEdges][]routing.Path, error) { if r.err != nil { - return nil, nil, r.err + return nil, r.err } - return []routing.Route{ - { - &routing.Hop{ - From: src, - To: dst, - Transport: transport.MakeTransportID(src, dst, ""), - }, - }, - }, []routing.Route{ + if len(rts) == 0 { + return nil, fmt.Errorf("no edges provided to returns routes from") + } + return map[routing.PathEdges][]routing.Path{ + [2]cipher.PubKey{rts[0][0], rts[0][1]}: { { - &routing.Hop{ - From: src, - To: dst, - Transport: transport.MakeTransportID(src, dst, ""), + routing.Hop{ + From: rts[0][0], + To: rts[0][1], + Transport: transport.MakeTransportID(rts[0][0], rts[0][1], ""), }, }, - }, nil + }, + }, nil } diff --git a/pkg/router/router.go b/pkg/router/router.go index 9b49339fe..93865e066 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -409,14 +409,18 @@ func (r *Router) destroyLoop(loop routing.Loop) error { return r.rm.RemoveLoopRule(loop) } -func (r *Router) fetchBestRoutes(source, destination cipher.PubKey) (fwd routing.Route, rev routing.Route, err error) { +func (r *Router) fetchBestRoutes(source, destination cipher.PubKey) (fwd routing.Path, rev routing.Path, err error) { r.Logger.Infof("Requesting new routes from %s to %s", source, destination) timer := time.NewTimer(time.Second * 10) defer timer.Stop() + forward := [2]cipher.PubKey{source, destination} + backward := [2]cipher.PubKey{destination, source} fetchRoutesAgain: - fwdRoutes, revRoutes, err := r.conf.RouteFinder.PairedRoutes(source, destination, minHops, maxHops) + ctx := context.Background() + paths, err := r.conf.RouteFinder.FindRoutes(ctx, []routing.PathEdges{forward, backward}, + &routeFinder.RouteOptions{MinHops: minHops, MaxHops: maxHops}) if err != nil { select { case <-timer.C: @@ -426,8 +430,8 @@ fetchRoutesAgain: } } - r.Logger.Infof("Found routes Forward: %s. Reverse %s", fwdRoutes, revRoutes) - return fwdRoutes[0], revRoutes[0], nil + r.Logger.Infof("Found routes Forward: %s. Reverse %s", paths[forward], paths[backward]) + return paths[forward][0], paths[backward][0], nil } // SetupIsTrusted checks if setup node is trusted. diff --git a/pkg/routing/loop.go b/pkg/routing/loop.go index 3206fd8aa..0be65091a 100644 --- a/pkg/routing/loop.go +++ b/pkg/routing/loop.go @@ -21,8 +21,8 @@ func (l Loop) String() string { // LoopDescriptor defines a loop over a pair of routes. type LoopDescriptor struct { Loop Loop - Forward Route - Reverse Route + Forward Path + Reverse Path KeepAlive time.Duration } diff --git a/pkg/routing/packet_test.go b/pkg/routing/packet_test.go index 2558da29c..3e0cbccda 100644 --- a/pkg/routing/packet_test.go +++ b/pkg/routing/packet_test.go @@ -1,9 +1,12 @@ package routing import ( + "encoding/json" "testing" + "github.com/skycoin/dmsg/cipher" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMakePacket(t *testing.T) { @@ -18,3 +21,22 @@ func TestMakePacket(t *testing.T) { assert.Equal(t, RouteID(2), packet.RouteID()) assert.Equal(t, []byte("foo"), packet.Payload()) } + +func TestEncoding(t *testing.T) { + pka, _ := cipher.GenerateKeyPair() + pkb, _ := cipher.GenerateKeyPair() + + edges1 := PathEdges{pka, pkb} + edges2 := PathEdges{pkb, pka} + + m := map[PathEdges]string{edges1: "a", edges2: "b"} + + b, err := json.Marshal(m) + require.NoError(t, err) + + m2 := make(map[PathEdges]string) + + err = json.Unmarshal(b, &m2) + require.NoError(t, err) + assert.Equal(t, m, m2) +} diff --git a/pkg/routing/route.go b/pkg/routing/route.go index 86a962748..021b56a5d 100644 --- a/pkg/routing/route.go +++ b/pkg/routing/route.go @@ -3,6 +3,7 @@ package routing import ( + "bytes" "fmt" "github.com/google/uuid" @@ -20,6 +21,41 @@ func (h Hop) String() string { return fmt.Sprintf("%s -> %s @ %s", h.From, h.To, h.Transport) } +// PathEdges are the edge nodes of a path +type PathEdges [2]cipher.PubKey + +// MarshalText implements encoding.TextMarshaler +func (p PathEdges) MarshalText() ([]byte, error) { + b1, err := p[0].MarshalText() + if err != nil { + return nil, err + } + b2, err := p[1].MarshalText() + if err != nil { + return nil, err + } + res := bytes.NewBuffer(b1) + res.WriteString(":") // nolint + res.Write(b2) // nolint + return res.Bytes(), nil +} + +// UnmarshalText implements json.Unmarshaler +func (p *PathEdges) UnmarshalText(b []byte) error { + err := p[0].UnmarshalText(b[:66]) + if err != nil { + return err + } + err = p[1].UnmarshalText(b[67:]) + if err != nil { + return err + } + return nil +} + +// Path is a list of hops between nodes (transports), and indicates a route between the edges +type Path []Hop + // Route is a succession of transport entries that denotes a path from source node to destination node type Route []*Hop diff --git a/pkg/setup/idreservoir.go b/pkg/setup/idreservoir.go index 467192c6c..8fff74bc2 100644 --- a/pkg/setup/idreservoir.go +++ b/pkg/setup/idreservoir.go @@ -19,7 +19,7 @@ type idReservoir struct { mx sync.Mutex } -func newIDReservoir(routes ...routing.Route) (*idReservoir, int) { +func newIDReservoir(routes ...routing.Path) (*idReservoir, int) { rec := make(map[cipher.PubKey]uint8) var total int @@ -130,7 +130,7 @@ func GenerateRules(idc *idReservoir, ld routing.LoopDescriptor) (rules RulesMap, // - firstRID: the first visor's route ID. // - lastRID: the last visor's route ID (note that there is no rule set for this ID yet). // - err: an error (if any). -func SaveForwardRules(rules RulesMap, idc *idReservoir, keepAlive time.Duration, route routing.Route) (firstRID, lastRID routing.RouteID, err error) { +func SaveForwardRules(rules RulesMap, idc *idReservoir, keepAlive time.Duration, route routing.Path) (firstRID, lastRID routing.RouteID, err error) { // 'firstRID' is the first visor's key routeID - this is to be returned. var ok bool diff --git a/pkg/setup/node.go b/pkg/setup/node.go index c522a7329..eef2a9f52 100644 --- a/pkg/setup/node.go +++ b/pkg/setup/node.go @@ -224,7 +224,7 @@ func (sn *Node) handleCreateLoop(ctx context.Context, ld routing.LoopDescriptor) return nil } -func (sn *Node) reserveRouteIDs(ctx context.Context, fwd, rev routing.Route) (*idReservoir, error) { +func (sn *Node) reserveRouteIDs(ctx context.Context, fwd, rev routing.Path) (*idReservoir, error) { idc, total := newIDReservoir(fwd, rev) sn.Logger.Infof("There are %d route IDs to reserve.", total)