Skip to content

Commit

Permalink
Merge pull request #6321 from TheThingsNetwork/feature/refactor-edcs
Browse files Browse the repository at this point in the history
Refactor EDCS
  • Loading branch information
KrishnaIyer committed Jun 13, 2023
2 parents 9805a4c + 6b07c17 commit 268fe9e
Show file tree
Hide file tree
Showing 10 changed files with 477 additions and 402 deletions.
18 changes: 9 additions & 9 deletions config/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3644,27 +3644,27 @@
"file": "ttjs.go"
}
},
"error:pkg/deviceclaimingserver/enddevices:claiming_not_supported": {
"error:pkg/deviceclaimingserver:claiming_not_supported": {
"translations": {
"en": "claiming not supported for JoinEUI `{eui}`"
},
"description": {
"package": "pkg/deviceclaimingserver/enddevices",
"file": "enddevices.go"
"package": "pkg/deviceclaimingserver",
"file": "grpc_end_devices.go"
}
},
"error:pkg/deviceclaimingserver/enddevices:no_eui": {
"error:pkg/deviceclaimingserver:method_unavailable": {
"translations": {
"en": "DevEUI/JoinEUI not found in request"
"en": "method unavailable"
},
"description": {
"package": "pkg/deviceclaimingserver/enddevices",
"file": "enddevices.go"
"package": "pkg/deviceclaimingserver",
"file": "grpc_end_devices.go"
}
},
"error:pkg/deviceclaimingserver:method_unavailable": {
"error:pkg/deviceclaimingserver:no_eui": {
"translations": {
"en": "method unavailable"
"en": "DevEUI/JoinEUI not set for device"
},
"description": {
"package": "pkg/deviceclaimingserver",
Expand Down
22 changes: 14 additions & 8 deletions pkg/deviceclaimingserver/deviceclaimingserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,18 @@ func New(c *component.Component, conf *Config, opts ...Option) (*DeviceClaimingS
opt(dcs)
}

dcs.gatewayClaimingServerUpstream = noopGCLS{}

upstream, err := enddevices.NewUpstream(ctx, c, conf.EndDeviceClaimingServerConfig)
if err != nil {
return nil, err
if dcs.endDeviceClaimingUpstream == nil {
upstream, err := enddevices.NewUpstream(ctx, c, conf.EndDeviceClaimingServerConfig)
if err != nil {
return nil, err
}
dcs.endDeviceClaimingUpstream = upstream
}

dcs.endDeviceClaimingUpstream = upstream

dcs.grpc.endDeviceClaimingServer = &endDeviceClaimingServer{
DCS: dcs,
}

dcs.gatewayClaimingServerUpstream = noopGCLS{}
dcs.grpc.gatewayClaimingServer = &gatewayClaimingServer{
DCS: dcs,
}
Expand All @@ -80,6 +79,13 @@ func New(c *component.Component, conf *Config, opts ...Option) (*DeviceClaimingS
// Option configures GatewayClaimingServer.
type Option func(*DeviceClaimingServer)

// WithEndDeviceClaimingUpstream configures the upstream for end device claiming.
func WithEndDeviceClaimingUpstream(upstream *enddevices.Upstream) Option {
return func(dcs *DeviceClaimingServer) {
dcs.endDeviceClaimingUpstream = upstream
}
}

// Context returns the context of the Device Claiming Server.
func (dcs *DeviceClaimingServer) Context() context.Context {
return dcs.ctx
Expand Down
145 changes: 14 additions & 131 deletions pkg/deviceclaimingserver/enddevices/enddevices.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,16 @@ import (
"path/filepath"
"strings"

"go.thethings.network/lorawan-stack/v3/pkg/auth/rights"
"go.thethings.network/lorawan-stack/v3/pkg/cluster"
"go.thethings.network/lorawan-stack/v3/pkg/config"
"go.thethings.network/lorawan-stack/v3/pkg/crypto"
"go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/enddevices/ttjsv2"
"go.thethings.network/lorawan-stack/v3/pkg/errors"
"go.thethings.network/lorawan-stack/v3/pkg/fetch"
"go.thethings.network/lorawan-stack/v3/pkg/httpclient"
"go.thethings.network/lorawan-stack/v3/pkg/log"
"go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/types"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -64,16 +60,16 @@ const (

// Upstream abstracts EndDeviceClaimingServer.
type Upstream struct {
Component
deviceRegistry ttnpb.EndDeviceRegistryClient
servers map[string]EndDeviceClaimer
claimers map[string]EndDeviceClaimer
}

// NewUpstream returns a new Upstream.
func NewUpstream(ctx context.Context, c Component, conf Config, opts ...Option) (*Upstream, error) {
upstream := &Upstream{
Component: c,
servers: make(map[string]EndDeviceClaimer),
claimers: make(map[string]EndDeviceClaimer),
}
for _, opt := range opts {
opt(upstream)
}

fetcher, err := conf.Fetcher(ctx, c.GetBaseConfig(ctx).Blob, c)
Expand Down Expand Up @@ -130,11 +126,7 @@ func NewUpstream(ctx context.Context, c Component, conf Config, opts ...Option)

// The file for each client will be unique.
clientName := strings.Trim(fileName, filepath.Ext(fileName))
upstream.servers[clientName] = claimer
}

for _, opt := range opts {
opt(upstream)
upstream.claimers[clientName] = claimer
}

return upstream, nil
Expand All @@ -143,128 +135,19 @@ func NewUpstream(ctx context.Context, c Component, conf Config, opts ...Option)
// Option configures Upstream.
type Option func(*Upstream)

// WithDeviceRegistry overrides the device registry of the Upstream.
func WithDeviceRegistry(reg ttnpb.EndDeviceRegistryClient) Option {
// WithClaimer adds a claimer to Upstream.
func WithClaimer(name string, claimer EndDeviceClaimer) Option {
return func(upstream *Upstream) {
upstream.deviceRegistry = reg
upstream.claimers[name] = claimer
}
}

var (
errNoEUI = errors.DefineInvalidArgument("no_eui", "DevEUI/JoinEUI not found in request")
errClaimingNotSupported = errors.DefineAborted("claiming_not_supported", "claiming not supported for JoinEUI `{eui}`")
)

func (upstream *Upstream) joinEUIClaimer(_ context.Context, joinEUI types.EUI64) EndDeviceClaimer {
for _, srv := range upstream.servers {
if srv.SupportsJoinEUI(joinEUI) {
return srv
// JoinEUIClaimer returns the EndDeviceClaimer for the given JoinEUI.
func (upstream *Upstream) JoinEUIClaimer(_ context.Context, joinEUI types.EUI64) EndDeviceClaimer {
for _, claimer := range upstream.claimers {
if claimer.SupportsJoinEUI(joinEUI) {
return claimer
}
}
return nil
}

// Claim implements EndDeviceClaimingServer.
func (upstream *Upstream) Claim(
ctx context.Context, joinEUI, devEUI types.EUI64, claimAuthenticationCode string,
) error {
claimer := upstream.joinEUIClaimer(ctx, joinEUI)
if claimer == nil {
return errClaimingNotSupported.WithAttributes("eui", joinEUI)
}
return claimer.Claim(ctx, joinEUI, devEUI, claimAuthenticationCode)
}

// Unclaim implements EndDeviceClaimingServer.
func (upstream *Upstream) Unclaim(ctx context.Context, in *ttnpb.EndDeviceIdentifiers) (*emptypb.Empty, error) {
if in.DevEui == nil || in.JoinEui == nil {
return nil, errNoEUI.New()
}
err := upstream.requireRights(ctx, in, &ttnpb.Rights{
Rights: []ttnpb.Right{
ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE,
},
})
if err != nil {
return nil, err
}
claimer := upstream.joinEUIClaimer(ctx, types.MustEUI64(in.JoinEui).OrZero())
if claimer == nil {
return nil, errClaimingNotSupported.WithAttributes("eui", in.JoinEui)
}
err = claimer.Unclaim(ctx, in)
if err != nil {
return nil, err
}
return ttnpb.Empty, nil
}

// GetInfoByJoinEUI implements EndDeviceClaimingServer.
func (upstream *Upstream) GetInfoByJoinEUI(
ctx context.Context, in *ttnpb.GetInfoByJoinEUIRequest,
) (*ttnpb.GetInfoByJoinEUIResponse, error) {
joinEUI := types.MustEUI64(in.JoinEui).OrZero()
claimer := upstream.joinEUIClaimer(ctx, joinEUI)
return &ttnpb.GetInfoByJoinEUIResponse{
JoinEui: joinEUI.Bytes(),
SupportsClaiming: (claimer != nil),
}, nil
}

// GetClaimStatus implements EndDeviceClaimingServer.
func (upstream *Upstream) GetClaimStatus(
ctx context.Context, in *ttnpb.EndDeviceIdentifiers,
) (*ttnpb.GetClaimStatusResponse, error) {
if in.DevEui == nil || in.JoinEui == nil {
return nil, errNoEUI.New()
}
err := upstream.requireRights(ctx, in, &ttnpb.Rights{
Rights: []ttnpb.Right{
ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ,
},
})
if err != nil {
return nil, err
}
claimer := upstream.joinEUIClaimer(ctx, types.MustEUI64(in.JoinEui).OrZero())
if claimer == nil {
return nil, errClaimingNotSupported.WithAttributes("eui", in.JoinEui)
}
return claimer.GetClaimStatus(ctx, in)
}

func (upstream *Upstream) requireRights(
ctx context.Context, in *ttnpb.EndDeviceIdentifiers, appRights *ttnpb.Rights,
) error {
// Collaborator must have the required rights on the application.
if err := rights.RequireApplication(ctx, in.ApplicationIds,
appRights.Rights...,
); err != nil {
return err
}
// Check that the device actually exists in the application.
// If the EUIs are set in the request, the IS also checks that they match the stored device.
callOpt, err := rpcmetadata.WithForwardedAuth(ctx, upstream.Component.AllowInsecureForCredentials())
if err != nil {
return err
}
er, err := upstream.getDeviceRegistry(ctx)
if err != nil {
return err
}
_, err = er.Get(ctx, &ttnpb.GetEndDeviceRequest{
EndDeviceIds: in,
}, callOpt)
return err
}

func (upstream *Upstream) getDeviceRegistry(ctx context.Context) (ttnpb.EndDeviceRegistryClient, error) {
if upstream.deviceRegistry != nil {
return upstream.deviceRegistry, nil
}
conn, err := upstream.Component.GetPeerConn(ctx, ttnpb.ClusterRole_ENTITY_REGISTRY, nil)
if err != nil {
return nil, err
}
return ttnpb.NewEndDeviceRegistryClient(conn), nil
}
74 changes: 9 additions & 65 deletions pkg/deviceclaimingserver/enddevices/enddevices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,14 @@ package enddevices
import (
"testing"

"go.thethings.network/lorawan-stack/v3/pkg/auth/rights"
"go.thethings.network/lorawan-stack/v3/pkg/component"
componenttest "go.thethings.network/lorawan-stack/v3/pkg/component/test"
"go.thethings.network/lorawan-stack/v3/pkg/errors"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/types"
"go.thethings.network/lorawan-stack/v3/pkg/unique"
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
)

var (
supportedJoinEUI = &types.EUI64{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}
unsupportedJoinEUI = &types.EUI64{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D}
)

func TestUpstream(t *testing.T) {
t.Parallel()
a, ctx := test.New(t)
Expand All @@ -47,68 +39,20 @@ func TestUpstream(t *testing.T) {
_, err := NewUpstream(ctx, c, Config{
Source: "directory",
})
a.So(err, should.NotBeNil)
a.So(errors.IsNotFound(err), should.BeTrue)

// Upstream test
// Test Upstream.
upstream := test.Must(NewUpstream(ctx, c, Config{
NetID: test.DefaultNetID,
Source: "directory",
Directory: "testdata",
}, WithDeviceRegistry(&mockDeviceRegistry{})))

ctx = rights.NewContext(ctx, &rights.Rights{
ApplicationRights: *rights.NewMap(map[string]*ttnpb.Rights{
unique.ID(test.Context(), &ttnpb.ApplicationIdentifiers{ApplicationId: "test-app"}): ttnpb.RightsFrom(
ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ,
ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE,
),
}),
})

// Invalid JoinEUI.
err = upstream.Claim(ctx, *unsupportedJoinEUI,
types.EUI64{0x00, 0x04, 0xA3, 0x0B, 0x00, 0x1C, 0x05, 0x30},
"secret",
)
a.So(errors.IsAborted(err), should.BeTrue)

_, err = upstream.Unclaim(ctx, &ttnpb.EndDeviceIdentifiers{
DeviceId: "test-dev",
ApplicationIds: &ttnpb.ApplicationIdentifiers{
ApplicationId: "test-app",
},
JoinEui: unsupportedJoinEUI.Bytes(),
DevEui: types.EUI64{0x00, 0x04, 0xA3, 0x0B, 0x00, 0x1C, 0x05, 0x30}.Bytes(),
})
a.So(errors.IsUnauthenticated(err), should.BeTrue)

resp, err := upstream.GetInfoByJoinEUI(ctx, &ttnpb.GetInfoByJoinEUIRequest{
JoinEui: unsupportedJoinEUI.Bytes(),
})
a.So(err, should.BeNil)
a.So(resp.SupportsClaiming, should.BeFalse)
}))

// Valid JoinEUI.
inf, err := upstream.GetInfoByJoinEUI(ctx, &ttnpb.GetInfoByJoinEUIRequest{
JoinEui: supportedJoinEUI.Bytes(),
})
a.So(err, should.BeNil)
a.So(inf.JoinEui, should.Resemble, supportedJoinEUI.Bytes())
a.So(inf.SupportsClaiming, should.BeTrue)

err = upstream.Claim(ctx, *supportedJoinEUI,
types.EUI64{0x00, 0x04, 0xA3, 0x0B, 0x00, 0x1C, 0x05, 0x30},
"secret",
)
a.So(!errors.IsUnimplemented(err), should.BeTrue)
unsupportedJoinEUI := types.EUI64{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D}
claimer := upstream.JoinEUIClaimer(ctx, unsupportedJoinEUI)
a.So(claimer, should.BeNil)

_, err = upstream.Unclaim(ctx, &ttnpb.EndDeviceIdentifiers{
DeviceId: "test-dev",
ApplicationIds: &ttnpb.ApplicationIdentifiers{
ApplicationId: "test-app",
},
JoinEui: supportedJoinEUI.Bytes(),
DevEui: types.EUI64{0x00, 0x04, 0xA3, 0x0B, 0x00, 0x1C, 0x05, 0x30}.Bytes(),
})
a.So(!errors.IsUnavailable(err), should.BeTrue)
supportedJoinEUI := types.EUI64{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}
claimer = upstream.JoinEUIClaimer(ctx, supportedJoinEUI)
a.So(claimer, should.NotBeNil)
}
Loading

0 comments on commit 268fe9e

Please sign in to comment.