From 8ee784c63504a684c7429f377979b088645e73aa Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Tue, 27 Feb 2024 17:37:34 +0100 Subject: [PATCH 1/7] all: Adapt ChirpStack to v4 --- cmd/chirpstack/chirpstack.go | 2 +- go.mod | 1 + go.sum | 2 + pkg/source/chirpstack/chirpstack.go | 6 +- pkg/source/chirpstack/config/config.go | 75 ++++++++++++--- pkg/source/chirpstack/config/errors.go | 1 + pkg/source/chirpstack/source.go | 122 ++++++++++--------------- pkg/source/chirpstack/util.go | 78 ++++++---------- 8 files changed, 146 insertions(+), 141 deletions(-) diff --git a/cmd/chirpstack/chirpstack.go b/cmd/chirpstack/chirpstack.go index 80e3805..3850f54 100644 --- a/cmd/chirpstack/chirpstack.go +++ b/cmd/chirpstack/chirpstack.go @@ -23,7 +23,7 @@ const sourceName = "chirpstack" // ChirpStackCmd represents the chirpstack source. var ChirpStackCmd = commands.Source(sourceName, - "Export devices from ChirpStack V3", + "Export devices from ChirpStack v4", commands.WithDevicesOptions( commands.WithShort("Export devices by DevEUI"), ), diff --git a/go.mod b/go.mod index e415497..7602da2 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/TheThingsNetwork/ttn/core/types v0.0.0-20190516112328-fcd38e2b9dc6 github.com/apex/log v1.9.0 github.com/brocaar/chirpstack-api/go/v3 v3.12.5 + github.com/chirpstack/chirpstack/api/go/v4 v4.6.0 github.com/mdempsky/unconvert v0.0.0-20230125054757-2661c2c99a9b github.com/mgechev/revive v1.3.7 github.com/smarty/assertions v1.15.1 diff --git a/go.sum b/go.sum index 76be0b9..3722326 100644 --- a/go.sum +++ b/go.sum @@ -177,6 +177,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/chirpstack/chirpstack/api/go/v4 v4.6.0 h1:l+nr/QhFab1y9E8LVOJq/lDG+o0+mShcZOCNBvFYXUA= +github.com/chirpstack/chirpstack/api/go/v4 v4.6.0/go.mod h1:6+68s1PGHq2QWZ216RTwXhp7h1vCiMc6kX3f4s74ZzQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= diff --git a/pkg/source/chirpstack/chirpstack.go b/pkg/source/chirpstack/chirpstack.go index 82e3575..f9dcf44 100644 --- a/pkg/source/chirpstack/chirpstack.go +++ b/pkg/source/chirpstack/chirpstack.go @@ -20,12 +20,12 @@ import ( ) func init() { - cfg, flags := config.New() + cfg := config.New() source.RegisterSource(source.Registration{ Name: "chirpstack", - Description: "Migrate from ChirpStack LoRaWAN Network Server", - FlagSet: flags, + Description: "Migrate from ChirpStack LoRaWAN Network Server v4", + FlagSet: cfg.Flags(), Create: createNewSource(cfg), }) } diff --git a/pkg/source/chirpstack/config/config.go b/pkg/source/chirpstack/config/config.go index 5d082c0..642439a 100644 --- a/pkg/source/chirpstack/config/config.go +++ b/pkg/source/chirpstack/config/config.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/pflag" "go.thethings.network/lorawan-stack-migrate/pkg/source" + "go.thethings.network/lorawan-stack/v3/pkg/frequencyplans" "go.thethings.network/lorawan-stack/v3/pkg/types" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -75,32 +76,77 @@ func New() (*Config, *pflag.FlagSet) { } type Config struct { - source.Config + src source.Config + + token, caCertPath, url, joinEUI string + flags *pflag.FlagSet + fpStore *frequencyplans.Store + insecure bool ClientConn *grpc.ClientConn - token, caPath, url, + ExportVars, + ExportSession bool FrequencyPlanID string + JoinEUI *types.EUI64 +} - joinEUI string - JoinEUI *types.EUI64 +func New() *Config { + config := &Config{ + flags: &pflag.FlagSet{}, + } - insecure, - ExportVars, - ExportSession bool + config.flags.StringVar(&config.url, + "api-url", + "", + "ChirpStack API URL") + config.flags.StringVar(&config.token, + "api-token", + "", + "ChirpStack API Token") + config.flags.StringVar(&config.caCertPath, + "ca-cert-path", + "", + "(optional) Path to the CA certificate file for ChirpStack API TLS connections") + config.flags.BoolVar(&config.insecure, + "insecure", + false, + "Do not connect to ChirpStack over TLS") + config.flags.BoolVar(&config.ExportVars, + "export-vars", + false, + "Export device variables from ChirpStack") + config.flags.BoolVar(&config.ExportSession, + "export-session", + false, + "Export device session keys from ChirpStack") + config.flags.StringVar(&config.joinEUI, + "join-eui", + "", + "JoinEUI of exported devices") + config.flags.StringVar(&config.FrequencyPlanID, + "frequency-plan-id", + "", + "Frequency Plan ID of exported devices") + + return config } -func (c *Config) Initialize() error { - if c.token == "" { +func (c *Config) Initialize(src source.Config) error { + c.src = src + + if c.token = os.Getenv("CHIRPSTACK_API_TOKEN"); c.token == "" { return errNoAPIToken.New() } - if c.url == "" { + if c.url = os.Getenv("CHIRPSTACK_API_URL"); c.url == "" { return errNoAPIURL.New() } - if c.FrequencyPlanID == "" { + if c.FrequencyPlanID = os.Getenv("FREQUENCY_PLAN_ID"); c.FrequencyPlanID == "" { return errNoFrequencyPlan.New() } - + if c.joinEUI = os.Getenv("JOIN_EUI"); c.joinEUI == "" { + return errNoJoinEUI.New() + } c.JoinEUI = &types.EUI64{} if err := c.JoinEUI.UnmarshalText([]byte(c.joinEUI)); err != nil { return errInvalidJoinEUI.WithAttributes("join_eui", c.joinEUI) @@ -117,6 +163,11 @@ func (c *Config) Initialize() error { return nil } +// Flags returns the flags for the configuration. +func (c *Config) Flags() *pflag.FlagSet { + return c.flags +} + func (c *Config) dialGRPC(opts ...grpc.DialOption) error { if c.insecure { opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/pkg/source/chirpstack/config/errors.go b/pkg/source/chirpstack/config/errors.go index 9373899..070d333 100644 --- a/pkg/source/chirpstack/config/errors.go +++ b/pkg/source/chirpstack/config/errors.go @@ -20,6 +20,7 @@ var ( errNoAPIToken = errors.DefineInvalidArgument("no_api_token", "no API token") errNoAPIURL = errors.DefineInvalidArgument("no_api_url", "no API URL") errNoFrequencyPlan = errors.DefineInvalidArgument("no_frequency_plan", "no Frequency Plan") + errNoJoinEUI = errors.DefineInvalidArgument("no_join_eui", "no join eui") errInvalidJoinEUI = errors.DefineInvalidArgument("invalid_join_eui", "invalid JoinEUI `{join_eui}`") ) diff --git a/pkg/source/chirpstack/source.go b/pkg/source/chirpstack/source.go index a7360ed..99bce06 100644 --- a/pkg/source/chirpstack/source.go +++ b/pkg/source/chirpstack/source.go @@ -22,12 +22,12 @@ import ( "strings" "time" - csapi "github.com/brocaar/chirpstack-api/go/v3/as/external/api" + csv4api "github.com/chirpstack/chirpstack/api/go/v4/api" + "github.com/chirpstack/chirpstack/api/go/v4/common" "go.thethings.network/lorawan-stack-migrate/pkg/iterator" "go.thethings.network/lorawan-stack-migrate/pkg/source" "go.thethings.network/lorawan-stack-migrate/pkg/source/chirpstack/config" "go.thethings.network/lorawan-stack-migrate/pkg/util" - "go.thethings.network/lorawan-stack/v3/pkg/log" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/types" "google.golang.org/protobuf/types/known/durationpb" @@ -53,28 +53,21 @@ type Source struct { ctx context.Context - applications map[int64]*csapi.Application - devProfiles map[string]*csapi.DeviceProfile - svcProfiles map[string]*csapi.ServiceProfile + applications map[string]*csv4api.Application + devProfiles map[string]*csv4api.DeviceProfile } func createNewSource(cfg *config.Config) source.CreateSource { - return func(ctx context.Context, _ source.Config) (source.Source, error) { - s := &Source{ - ctx: ctx, - Config: cfg, - } - - if err := cfg.Initialize(); err != nil { + return func(ctx context.Context, src source.Config) (source.Source, error) { + if err := cfg.Initialize(src); err != nil { return nil, err } - log.FromContext(s.ctx).WithFields(s.LogFields()).Info("Initialized ChirpStack source") - - s.applications = make(map[int64]*csapi.Application) - s.devProfiles = make(map[string]*csapi.DeviceProfile) - s.svcProfiles = make(map[string]*csapi.ServiceProfile) - - return s, nil + return &Source{ + ctx: ctx, + Config: cfg, + applications: make(map[string]*csv4api.Application), + devProfiles: make(map[string]*csv4api.DeviceProfile), + }, nil } } @@ -89,10 +82,10 @@ func (p *Source) RangeDevices(id string, f func(source.Source, string) error) er if err != nil { return err } - client := csapi.NewDeviceServiceClient(p.ClientConn) - offset := int64(0) + client := csv4api.NewDeviceServiceClient(p.ClientConn) + offset := uint32(0) for { - devices, err := client.List(p.ctx, &csapi.ListDeviceRequest{ + devices, err := client.List(p.ctx, &csv4api.ListDevicesRequest{ ApplicationId: app.Id, Limit: limit, Offset: offset, @@ -127,14 +120,6 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { if err != nil { return nil, err } - app, err := p.getApplicationByID(csdev.ApplicationId) - if err != nil { - return nil, err - } - svcProfile, err := p.getServiceProfile(app.ServiceProfileId) - if err != nil { - return nil, err - } devProfile, err := p.getDeviceProfile(csdev.DeviceProfileId) if err != nil { return nil, err @@ -165,11 +150,8 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { } } - // Service Profile - if svcProfile.DevStatusReqFreq > 0 { - // ChirpStack frequency is requests/day. TTS is time.Duration of interval - d := time.Duration(24) * time.Hour / time.Duration(svcProfile.DevStatusReqFreq) - dev.MacSettings.StatusTimePeriodicity = durationpb.New(d) + if d := devProfile.DeviceStatusReqInterval; d > 0 { + dev.MacSettings.StatusTimePeriodicity = durationpb.New(time.Duration(d)) } // Frequency Plan @@ -177,31 +159,31 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { // General switch devProfile.MacVersion { - case "1.0.0": + case common.MacVersion_LORAWAN_1_0_0: dev.LorawanVersion = ttnpb.MACVersion_MAC_V1_0 dev.LorawanPhyVersion = ttnpb.PHYVersion_TS001_V1_0 - case "1.0.1": + case common.MacVersion_LORAWAN_1_0_1: dev.LorawanVersion = ttnpb.MACVersion_MAC_V1_0_1 dev.LorawanPhyVersion = ttnpb.PHYVersion_TS001_V1_0_1 - case "1.0.2": + case common.MacVersion_LORAWAN_1_0_2: dev.LorawanVersion = ttnpb.MACVersion_MAC_V1_0_2 switch devProfile.RegParamsRevision { - case "A": + case common.RegParamsRevision_A: dev.LorawanPhyVersion = ttnpb.PHYVersion_RP001_V1_0_2 - case "B": + case common.RegParamsRevision_B: dev.LorawanPhyVersion = ttnpb.PHYVersion_RP001_V1_0_2_REV_B default: return nil, errInvalidPHYVersion.WithAttributes("phy_version", devProfile.RegParamsRevision) } - case "1.0.3": + case common.MacVersion_LORAWAN_1_0_3: dev.LorawanVersion = ttnpb.MACVersion_MAC_V1_0_3 dev.LorawanPhyVersion = ttnpb.PHYVersion_RP001_V1_0_3_REV_A - case "1.1.0": + case common.MacVersion_LORAWAN_1_1_0: dev.LorawanVersion = ttnpb.MACVersion_MAC_V1_1 switch devProfile.RegParamsRevision { - case "A": + case common.RegParamsRevision_A: dev.LorawanPhyVersion = ttnpb.PHYVersion_RP001_V1_1_REV_A - case "B": + case common.RegParamsRevision_B: dev.LorawanPhyVersion = ttnpb.PHYVersion_RP001_V1_1_REV_B default: return nil, errInvalidPHYVersion.WithAttributes("phy_version", devProfile.RegParamsRevision) @@ -211,31 +193,28 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { } // Join (OTAA/ABP) - dev.SupportsJoin = devProfile.SupportsJoin + dev.SupportsJoin = devProfile.SupportsOtaa if !dev.SupportsJoin { - if devProfile.RxFreq_2 > 0 { + if freq := devProfile.AbpRx2Freq; freq > 0 { dev.MacSettings.Rx2Frequency = &ttnpb.FrequencyValue{ - Value: uint64(devProfile.RxFreq_2), + Value: uint64(freq), } } - if devProfile.RxDelay_1 > 0 { + if delay := devProfile.AbpRx1Delay; delay > 0 { dev.MacSettings.Rx1Delay = &ttnpb.RxDelayValue{ - Value: ttnpb.RxDelay(devProfile.RxDelay_1), + Value: ttnpb.RxDelay(delay), } } - if devProfile.RxDrOffset_1 >= 0 { + if offset := devProfile.AbpRx1DrOffset; offset > 0 { dev.MacSettings.DesiredRx1DataRateOffset = &ttnpb.DataRateOffsetValue{ - Value: ttnpb.DataRateOffset(devProfile.RxDrOffset_1), + Value: ttnpb.DataRateOffset(offset), } } - if devProfile.RxDatarate_2 >= 0 { + if dataRate := devProfile.AbpRx2Dr; dataRate > 0 { dev.MacSettings.DesiredRx2DataRateIndex = &ttnpb.DataRateIndexValue{ - Value: ttnpb.DataRateIndex(devProfile.RxDatarate_2), + Value: ttnpb.DataRateIndex(dataRate), } } - for _, freq := range devProfile.FactoryPresetFreqs { - dev.MacSettings.FactoryPresetFrequencies = append(dev.MacSettings.FactoryPresetFrequencies, uint64(freq)) - } } // Class B @@ -247,17 +226,14 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { } // ChirpStack API returns 2^(seconds + 5) dev.MacSettings.PingSlotPeriodicity = &ttnpb.PingSlotPeriodValue{ - Value: ttnpb.PingSlotPeriod(math.Log2(float64(devProfile.PingSlotPeriod)) - 5), + Value: ttnpb.PingSlotPeriod(math.Log2(float64(devProfile.ClassBPingSlotNbK)) - 5), } - if devProfile.PingSlotFreq > 0 { + if devProfile.ClassBPingSlotNbK > 0 { dev.MacSettings.DesiredPingSlotFrequency = &ttnpb.ZeroableFrequencyValue{ - Value: uint64(devProfile.PingSlotFreq), + Value: uint64(devProfile.ClassBPingSlotNbK), } } - dev.MacSettings.DesiredPingSlotDataRateIndex = &ttnpb.DataRateIndexValue{ - Value: ttnpb.DataRateIndex(devProfile.PingSlotDr), - } } // Class C @@ -295,29 +271,27 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { } // Payload formatters - switch devProfile.PayloadCodec { - case "CAYENNE_LPP": + switch devProfile.PayloadCodecRuntime { + case csv4api.CodecRuntime_CAYENNE_LPP: dev.Formatters.UpFormatter = ttnpb.PayloadFormatter_FORMATTER_CAYENNELPP dev.Formatters.DownFormatter = ttnpb.PayloadFormatter_FORMATTER_CAYENNELPP - case "CUSTOM_JS": - if devProfile.PayloadEncoderScript != "" { + case csv4api.CodecRuntime_JS: + if s := devProfile.PayloadCodecScript; s != "" { dev.Formatters.UpFormatter = ttnpb.PayloadFormatter_FORMATTER_JAVASCRIPT - dev.Formatters.UpFormatterParameter = fmt.Sprintf(encoderFormat, devProfile.PayloadEncoderScript) - } - if devProfile.PayloadDecoderScript != "" { dev.Formatters.DownFormatter = ttnpb.PayloadFormatter_FORMATTER_JAVASCRIPT - dev.Formatters.DownFormatterParameter = fmt.Sprintf(decoderFormat, devProfile.PayloadDecoderScript) + dev.Formatters.UpFormatterParameter = fmt.Sprintf(encoderFormat, s) + dev.Formatters.DownFormatterParameter = fmt.Sprintf(encoderFormat, s) } } // Configuration - if csdev.SkipFCntCheck { + if csdev.SkipFcntCheck { dev.MacSettings.ResetsFCnt = &ttnpb.BoolValue{ - Value: csdev.SkipFCntCheck, + Value: csdev.SkipFcntCheck, } } - // Session + // Export session information. if p.ExportSession { activation, err := p.getActivation(devEui) if err == nil { @@ -357,7 +331,7 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { default: } - if devProfile.SupportsJoin { + if devProfile.SupportsOtaa { dev.Session.Keys.SessionKeyId = generateBytes(16) } dev.Session.LastAFCntDown = activation.AFCntDown diff --git a/pkg/source/chirpstack/util.go b/pkg/source/chirpstack/util.go index 33e62fd..a0a5faa 100644 --- a/pkg/source/chirpstack/util.go +++ b/pkg/source/chirpstack/util.go @@ -16,28 +16,27 @@ package chirpstack import ( "context" - "strconv" - csapi "github.com/brocaar/chirpstack-api/go/v3/as/external/api" + csv4api "github.com/chirpstack/chirpstack/api/go/v4/api" log "go.thethings.network/lorawan-stack/v3/pkg/log" "go.thethings.network/lorawan-stack/v3/pkg/random" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -const limit = int64(100) +const limit = uint32(100) func generateBytes(length int) []byte { return random.Bytes(length) } -func (p *Source) getDeviceProfile(id string) (*csapi.DeviceProfile, error) { +func (p *Source) getDeviceProfile(id string) (*csv4api.DeviceProfile, error) { if profile, ok := p.devProfiles[id]; ok { return profile, nil } - client := csapi.NewDeviceProfileServiceClient(p.ClientConn) - resp, err := client.Get(p.ctx, &csapi.GetDeviceProfileRequest{ + client := csv4api.NewDeviceProfileServiceClient(p.ClientConn) + resp, err := client.Get(p.ctx, &csv4api.GetDeviceProfileRequest{ Id: id, }) if err != nil { @@ -47,26 +46,10 @@ func (p *Source) getDeviceProfile(id string) (*csapi.DeviceProfile, error) { return resp.DeviceProfile, nil } -func (p *Source) getServiceProfile(id string) (*csapi.ServiceProfile, error) { - if profile, ok := p.svcProfiles[id]; ok { - return profile, nil - } +func (p *Source) getDevice(devEui string) (*csv4api.Device, error) { + client := csv4api.NewDeviceServiceClient(p.ClientConn) - client := csapi.NewServiceProfileServiceClient(p.ClientConn) - resp, err := client.Get(p.ctx, &csapi.GetServiceProfileRequest{ - Id: id, - }) - if err != nil { - return nil, errAPI.WithCause(err) - } - p.svcProfiles[id] = resp.ServiceProfile - return resp.ServiceProfile, nil -} - -func (p *Source) getDevice(devEui string) (*csapi.Device, error) { - client := csapi.NewDeviceServiceClient(p.ClientConn) - - resp, err := client.Get(p.ctx, &csapi.GetDeviceRequest{ + resp, err := client.Get(p.ctx, &csv4api.GetDeviceRequest{ DevEui: devEui, }) if err != nil { @@ -75,37 +58,30 @@ func (p *Source) getDevice(devEui string) (*csapi.Device, error) { return resp.Device, nil } -func (p *Source) getApplication(application string) (*csapi.Application, error) { - appID, err := strconv.ParseInt(application, 10, 64) - if err != nil { - appID, err = p.getApplicationIDByName(application) - if err != nil { - return nil, err - } - } - app, err := p.getApplicationByID(appID) +func (p *Source) getApplication(application string) (*csv4api.Application, error) { + app, err := p.getApplicationByID(application) if err != nil { switch status.Code(err) { case codes.NotFound: - appID, err = p.getApplicationIDByName(application) + appID, err := p.getApplicationIDByName(application) if err != nil { return nil, err } + return p.getApplicationByID(appID) default: return nil, err } - return p.getApplicationByID(appID) } return app, nil } -func (p *Source) getApplicationByID(id int64) (*csapi.Application, error) { +func (p *Source) getApplicationByID(id string) (*csv4api.Application, error) { if app, ok := p.applications[id]; ok { return app, nil } - client := csapi.NewApplicationServiceClient(p.ClientConn) - resp, err := client.Get(p.ctx, &csapi.GetApplicationRequest{ + client := csv4api.NewApplicationServiceClient(p.ClientConn) + resp, err := client.Get(p.ctx, &csv4api.GetApplicationRequest{ Id: id, }) if err != nil { @@ -116,17 +92,17 @@ func (p *Source) getApplicationByID(id int64) (*csapi.Application, error) { return resp.Application, nil } -func (p *Source) getApplicationIDByName(name string) (int64, error) { - client := csapi.NewApplicationServiceClient(p.ClientConn) - offset := int64(0) +func (p *Source) getApplicationIDByName(name string) (string, error) { + client := csv4api.NewApplicationServiceClient(p.ClientConn) + offset := uint32(0) for { - resp, err := client.List(p.ctx, &csapi.ListApplicationRequest{ + resp, err := client.List(p.ctx, &csv4api.ListApplicationsRequest{ Limit: limit, Offset: offset, Search: name, }) if err != nil { - return 0, err + return "", err } for _, appListItem := range resp.Result { if appListItem.Name == name { @@ -135,14 +111,14 @@ func (p *Source) getApplicationIDByName(name string) (int64, error) { } if offset += limit; offset > resp.TotalCount { - return 0, errAppNotFound.WithAttributes("app", name) + return "", errAppNotFound.WithAttributes("app", name) } } } -func (p *Source) getRootKeys(devEui string) (*csapi.DeviceKeys, error) { - client := csapi.NewDeviceServiceClient(p.ClientConn) - resp, err := client.GetKeys(context.Background(), &csapi.GetDeviceKeysRequest{ +func (p *Source) getRootKeys(devEui string) (*csv4api.DeviceKeys, error) { + client := csv4api.NewDeviceServiceClient(p.ClientConn) + resp, err := client.GetKeys(context.Background(), &csv4api.GetDeviceKeysRequest{ DevEui: devEui, }) if err != nil { @@ -152,9 +128,9 @@ func (p *Source) getRootKeys(devEui string) (*csapi.DeviceKeys, error) { return resp.DeviceKeys, err } -func (p *Source) getActivation(devEui string) (*csapi.DeviceActivation, error) { - client := csapi.NewDeviceServiceClient(p.ClientConn) - resp, err := client.GetActivation(context.Background(), &csapi.GetDeviceActivationRequest{ +func (p *Source) getActivation(devEui string) (*csv4api.DeviceActivation, error) { + client := csv4api.NewDeviceServiceClient(p.ClientConn) + resp, err := client.GetActivation(context.Background(), &csv4api.GetDeviceActivationRequest{ DevEui: devEui, }) if err != nil { From 0943e96459d78acc14ecf16412824fff6587b894 Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Tue, 27 Feb 2024 18:15:16 +0100 Subject: [PATCH 2/7] all: Make ChirpStack work --- pkg/source/chirpstack/config/config.go | 19 +++++++++---------- pkg/source/chirpstack/errors.go | 15 ++++++++------- pkg/source/chirpstack/source.go | 7 ++++++- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/source/chirpstack/config/config.go b/pkg/source/chirpstack/config/config.go index 642439a..c037c9f 100644 --- a/pkg/source/chirpstack/config/config.go +++ b/pkg/source/chirpstack/config/config.go @@ -78,10 +78,10 @@ func New() (*Config, *pflag.FlagSet) { type Config struct { src source.Config - token, caCertPath, url, joinEUI string - flags *pflag.FlagSet - fpStore *frequencyplans.Store - insecure bool + apiKey, caCertPath, url, joinEUI string + flags *pflag.FlagSet + fpStore *frequencyplans.Store + insecure bool ClientConn *grpc.ClientConn @@ -100,10 +100,10 @@ func New() *Config { "api-url", "", "ChirpStack API URL") - config.flags.StringVar(&config.token, - "api-token", + config.flags.StringVar(&config.apiKey, + "api-key", "", - "ChirpStack API Token") + "ChirpStack API key") config.flags.StringVar(&config.caCertPath, "ca-cert-path", "", @@ -135,7 +135,7 @@ func New() *Config { func (c *Config) Initialize(src source.Config) error { c.src = src - if c.token = os.Getenv("CHIRPSTACK_API_TOKEN"); c.token == "" { + if c.apiKey = os.Getenv("CHIRPSTACK_API_KEY"); c.apiKey == "" { return errNoAPIToken.New() } if c.url = os.Getenv("CHIRPSTACK_API_URL"); c.url == "" { @@ -154,12 +154,11 @@ func (c *Config) Initialize(src source.Config) error { err := c.dialGRPC( grpc.FailOnNonTempDialError(true), grpc.WithBlock(), - grpc.WithPerRPCCredentials(token(c.token)), + grpc.WithPerRPCCredentials(token(c.apiKey)), ) if err != nil { return err } - return nil } diff --git a/pkg/source/chirpstack/errors.go b/pkg/source/chirpstack/errors.go index 694f8ac..9b8592a 100644 --- a/pkg/source/chirpstack/errors.go +++ b/pkg/source/chirpstack/errors.go @@ -25,11 +25,12 @@ var ( errAPI = errors.Define("api", "API error") - errAppNotFound = errors.DefineNotFound("app_not_found", "app `{app}` not found") - errInvalidDevAddr = errors.DefineInvalidArgument("invalid_dev_addr", "invalid DevAddr `{dev_addr}`") - errInvalidDevEUI = errors.DefineInvalidArgument("invalid_dev_eui", "invalid DevEUI `{dev_eui}`") - errInvalidJoinEUI = errors.DefineInvalidArgument("invalid_join_eui", "invalid JoinEUI `{join_eui}`") - errInvalidPHYVersion = errors.DefineInvalidArgument("invalid_phy_version", "invalid PHY version `{phy_version}`") - errInvalidMACVersion = errors.DefineInvalidArgument("invalid_mac_version", "invalid MAC version `{mac_version}`") - errInvalidKey = errors.DefineInvalidArgument("invalid_key", "invalid key `{key}`") + errAppNotFound = errors.DefineNotFound("app_not_found", "app `{app}` not found") + errInvalidDevAddr = errors.DefineInvalidArgument("invalid_dev_addr", "invalid DevAddr `{dev_addr}`") + errInvalidDevEUI = errors.DefineInvalidArgument("invalid_dev_eui", "invalid DevEUI `{dev_eui}`") + errInvalidJoinEUI = errors.DefineInvalidArgument("invalid_join_eui", "invalid JoinEUI `{join_eui}`") + errInvalidPHYVersion = errors.DefineInvalidArgument("invalid_phy_version", "invalid PHY version `{phy_version}`") + errInvalidMACVersion = errors.DefineInvalidArgument("invalid_mac_version", "invalid MAC version `{mac_version}`") + errInvalidKey = errors.DefineInvalidArgument("invalid_key", "invalid key `{key}`") + errInvalidApplicationID = errors.DefineInvalidArgument("invalid_application_id", "invalid application ID `{application_id}`") ) diff --git a/pkg/source/chirpstack/source.go b/pkg/source/chirpstack/source.go index 99bce06..a4ac9a1 100644 --- a/pkg/source/chirpstack/source.go +++ b/pkg/source/chirpstack/source.go @@ -131,7 +131,12 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { return nil, errInvalidDevEUI.WithAttributes("dev_eui", devEui).WithCause(err) } dev.Ids.JoinEui = p.JoinEUI.Bytes() - dev.Ids.ApplicationIds.ApplicationId = fmt.Sprintf("chirpstack-%d", csdev.ApplicationId) + // Get last index of the application ID (UUID). + s := strings.Split(csdev.ApplicationId, "-") + if len(s) < 2 { + return nil, errInvalidApplicationID.WithAttributes("application_id", csdev.ApplicationId) + } + dev.Ids.ApplicationIds.ApplicationId = fmt.Sprintf("chirpstack-%s", s[len(s)-1]) dev.Ids.DeviceId = "eui-" + strings.ToLower(devEui) // Information From 633458c42059b0d260e25c71e3d0eefcda81c140 Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Tue, 27 Feb 2024 18:30:03 +0100 Subject: [PATCH 3/7] all: Fix wrong usage of confirmed downlink counter --- pkg/source/chirpstack/source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/source/chirpstack/source.go b/pkg/source/chirpstack/source.go index a4ac9a1..5016dbb 100644 --- a/pkg/source/chirpstack/source.go +++ b/pkg/source/chirpstack/source.go @@ -339,8 +339,8 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { if devProfile.SupportsOtaa { dev.Session.Keys.SessionKeyId = generateBytes(16) } - dev.Session.LastAFCntDown = activation.AFCntDown dev.Session.LastFCntUp = activation.FCntUp + dev.Session.LastAFCntDown = activation.AFCntDown dev.Session.LastNFCntDown = activation.NFCntDown } } From 02ad46ef8315c265eb9ce1b5c6a0a74ad8ff8217 Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Tue, 27 Feb 2024 18:41:55 +0100 Subject: [PATCH 4/7] all: Generate MAC state --- pkg/source/chirpstack/config/config.go | 8 ++++++- pkg/source/chirpstack/source.go | 30 +++++++++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pkg/source/chirpstack/config/config.go b/pkg/source/chirpstack/config/config.go index c037c9f..fe63d60 100644 --- a/pkg/source/chirpstack/config/config.go +++ b/pkg/source/chirpstack/config/config.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/pflag" "go.thethings.network/lorawan-stack-migrate/pkg/source" + "go.thethings.network/lorawan-stack/v3/pkg/fetch" "go.thethings.network/lorawan-stack/v3/pkg/frequencyplans" "go.thethings.network/lorawan-stack/v3/pkg/types" "google.golang.org/grpc" @@ -80,7 +81,7 @@ type Config struct { apiKey, caCertPath, url, joinEUI string flags *pflag.FlagSet - fpStore *frequencyplans.Store + FPStore *frequencyplans.Store insecure bool ClientConn *grpc.ClientConn @@ -159,6 +160,11 @@ func (c *Config) Initialize(src source.Config) error { if err != nil { return err } + fpFetcher, err := fetch.FromHTTP(http.DefaultClient, src.FrequencyPlansURL) + if err != nil { + return err + } + c.FPStore = frequencyplans.NewStore(fpFetcher) return nil } diff --git a/pkg/source/chirpstack/source.go b/pkg/source/chirpstack/source.go index 5016dbb..a24ebce 100644 --- a/pkg/source/chirpstack/source.go +++ b/pkg/source/chirpstack/source.go @@ -28,6 +28,7 @@ import ( "go.thethings.network/lorawan-stack-migrate/pkg/source" "go.thethings.network/lorawan-stack-migrate/pkg/source/chirpstack/config" "go.thethings.network/lorawan-stack-migrate/pkg/util" + "go.thethings.network/lorawan-stack/v3/pkg/networkserver/mac" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/types" "google.golang.org/protobuf/types/known/durationpb" @@ -199,7 +200,7 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { // Join (OTAA/ABP) dev.SupportsJoin = devProfile.SupportsOtaa - if !dev.SupportsJoin { + if !dev.SupportsJoin || p.ExportSession { if freq := devProfile.AbpRx2Freq; freq > 0 { dev.MacSettings.Rx2Frequency = &ttnpb.FrequencyValue{ Value: uint64(freq), @@ -209,6 +210,10 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { dev.MacSettings.Rx1Delay = &ttnpb.RxDelayValue{ Value: ttnpb.RxDelay(delay), } + } else { + dev.MacSettings.Rx1Delay = &ttnpb.RxDelayValue{ + Value: ttnpb.RxDelay_RX_DELAY_1, + } } if offset := devProfile.AbpRx1DrOffset; offset > 0 { dev.MacSettings.DesiredRx1DataRateOffset = &ttnpb.DataRateOffsetValue{ @@ -300,7 +305,13 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { if p.ExportSession { activation, err := p.getActivation(devEui) if err == nil { - dev.Session = &ttnpb.Session{Keys: &ttnpb.SessionKeys{}, StartedAt: timestamppb.Now()} + dev.Session = &ttnpb.Session{Keys: &ttnpb.SessionKeys{AppSKey: &ttnpb.KeyEnvelope{}, FNwkSIntKey: &ttnpb.KeyEnvelope{}}} + + // These fields cannot be empty. + if devProfile.SupportsOtaa { + dev.Session.Keys.SessionKeyId = generateBytes(16) + dev.Session.StartedAt = timestamppb.Now() + } devAddr := &types.DevAddr{} if err := devAddr.UnmarshalText([]byte(activation.DevAddr)); err != nil { @@ -308,9 +319,6 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { } dev.Session.DevAddr = devAddr.Bytes() - // This cannot be empty - dev.Session.StartedAt = timestamppb.Now() - dev.Session.Keys.AppSKey = &ttnpb.KeyEnvelope{} dev.Session.Keys.AppSKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.AppSKey) if err != nil { @@ -336,12 +344,18 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { default: } - if devProfile.SupportsOtaa { - dev.Session.Keys.SessionKeyId = generateBytes(16) - } + // Set the frame counters. dev.Session.LastFCntUp = activation.FCntUp dev.Session.LastAFCntDown = activation.AFCntDown dev.Session.LastNFCntDown = activation.NFCntDown + + // Create a MACState. + dev.MacState, err = mac.NewState(dev, p.FPStore, &ttnpb.MACSettings{}) + if err != nil { + return nil, err + } + dev.MacState.CurrentParameters = dev.MacState.DesiredParameters + dev.MacState.CurrentParameters.Rx1Delay = dev.MacSettings.Rx1Delay.Value } } From a5b51fefab19ae61cf5f1669236bf090b4ed1bf6 Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Tue, 27 Feb 2024 18:44:11 +0100 Subject: [PATCH 5/7] all: Add CS application ID to attributes --- pkg/source/chirpstack/config/config.go | 44 +------------------------- pkg/source/chirpstack/source.go | 1 + 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/pkg/source/chirpstack/config/config.go b/pkg/source/chirpstack/config/config.go index fe63d60..4b87674 100644 --- a/pkg/source/chirpstack/config/config.go +++ b/pkg/source/chirpstack/config/config.go @@ -34,48 +34,6 @@ import ( const dialTimeout = 10 * time.Second -func New() (*Config, *pflag.FlagSet) { - var ( - config = &Config{} - flags = &pflag.FlagSet{} - ) - - flags.StringVar(&config.url, - "api-url", - os.Getenv("CHIRPSTACK_API_URL"), - "ChirpStack API URL") - flags.StringVar(&config.token, - "api-token", - os.Getenv("CHIRPSTACK_API_TOKEN"), - "ChirpStack API Token") - flags.StringVar(&config.caPath, - "api-ca", - os.Getenv("CHIRPSTACK_API_CA"), - "(optional) CA for TLS") - flags.BoolVar(&config.insecure, - "api-insecure", - os.Getenv("CHIRPSTACK_API_INSECURE") == "1", - "Do not connect to ChirpStack over TLS") - flags.BoolVar(&config.ExportVars, - "export-vars", - false, - "Export device variables from ChirpStack") - flags.BoolVar(&config.ExportSession, - "export-session", - true, - "Export device session keys from ChirpStack") - flags.StringVar(&config.joinEUI, - "join-eui", - os.Getenv("JOIN_EUI"), - "JoinEUI of exported devices") - flags.StringVar(&config.FrequencyPlanID, - "frequency-plan-id", - os.Getenv("FREQUENCY_PLAN_ID"), - "Frequency Plan ID of exported devices") - - return config, flags -} - type Config struct { src source.Config @@ -177,7 +135,7 @@ func (c *Config) dialGRPC(opts ...grpc.DialOption) error { if c.insecure { opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) } else { - tlsConfig, err := generateTLSConfig(c.caPath) + tlsConfig, err := generateTLSConfig(c.caCertPath) if err != nil { return err } diff --git a/pkg/source/chirpstack/source.go b/pkg/source/chirpstack/source.go index a24ebce..aab63c6 100644 --- a/pkg/source/chirpstack/source.go +++ b/pkg/source/chirpstack/source.go @@ -144,6 +144,7 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { dev.Name = csdev.Name dev.Description = csdev.Description dev.Attributes["chirpstack-device-profile"] = csdev.DeviceProfileId + dev.Attributes["chirpstack-application-id"] = csdev.ApplicationId for key, value := range devProfile.Tags { dev.Attributes[key] = value } From 6a6c1f7161b71687932d355d8d113930434c190a Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Wed, 6 Mar 2024 15:14:45 +0100 Subject: [PATCH 6/7] dev: Update readme and changelog --- CHANGELOG.md | 10 +++++++ README.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5402a..1f36307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Support for ChirpStack v4. + ### Changed ### Deprecated ### Removed +- Support for ChirpStack v3. Use versions `0.11.x` for ChirpStack v3. + +### Fixed + +## [v0.11.2] (2024-03-04) + ### Fixed +- Exporting devices from ChirpStack v3. + ## [v0.11.1] (2024-01-20) ### Fixed diff --git a/README.md b/README.md index b58e9f7..18f21fe 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Binaries are available on [GitHub](https://github.com/TheThingsNetwork/lorawan-s ## Support - [x] The Things Network Stack V2 -- [x] [ChirpStack Network Server](https://www.chirpstack.io/) +- [x] [ChirpStack Network Server v4](https://www.chirpstack.io/) +- [x] [ChirpStack Network Server v3](https://www.chirpstack.io/docs/v3-documentation.html) (only versions `v0.11.x`). - [x] [The Things Stack](https://www.github.com/TheThingsNetwork/lorawan-stack/) - [x] [Firefly](https://fireflyiot.com/) - [ ] [LORIOT Network Server](https://www.loriot.io/) @@ -106,7 +107,9 @@ $ ttn-lw-migrate ttnv2 application 'my-app-id' --dry-run --verbose > devices.jso $ ttn-lw-migrate ttnv2 application 'my-app-id' > devices.json ``` -## ChirpStack +## ChirpStack v3 + +> Note: ChirpStack v3 support is removed from versions `v0.12.0` onwards. Use `v0.11.x` for ChirpStack v3. ### Configuration @@ -176,6 +179,81 @@ And export with: $ ttn-lw-migrate chirpstack application < application_names.txt > devices.json ``` +## ChirpStack v4 + +> Minimum supported version: `v0.12.0` + +### Configuration + +Configure with environment variables, or command-line arguments. See `--help` for more details: + +```bash +$ export CHIRPSTACK_API_URL="localhost:8080" # ChirpStack Application Server URL +$ export CHIRPSTACK_API_KEY="eyJ0eX........" # Generate from ChirpStack GUI +$ export JOIN_EUI="0101010102020203" # JoinEUI for exported devices +$ export FREQUENCY_PLAN_ID="EU_863_870" # Frequency Plan for exported devices +$ export CHIRPSTACK_EXPORT_SESSION="true" # Set to true for session migration. +``` + +See [Frequency Plans](https://thethingsstack.io/reference/frequency-plans/) for the list of frequency plans available on The Things Stack. For example, to use `United States 902-928 MHz, FSB 1`, you need to specify the `US_902_928_FSB_1` frequency plan ID. + +> _NOTE_: `JoinEUI` and `FrequencyPlanID` are required because ChirpStack does not store these fields. + +### Notes + +- ABP devices without an active session are successfully exported from ChirpStack, but cannot be imported into The Things Stack. +- MaxEIRP may not be always set properly. +- ChirpStack payload formatters also accept a `variables` parameter. This will always be `null` on The Things Stack. +- ChirpStack v4 uses UUIDs as application ID. The migration tool uses the appends the last index of the UUID to application ID. + - Ex: If the ChirpStack v4 application ID is `59459ffa-bfd3-4ef3-9cee-e1ca219397f2`, the tool generates `chirpstack-e1ca219397f2` as the application ID. + +### Export Devices + +To export a single device using its DevEUI (e.g. `0102030405060708`): + +``` +$ ttn-lw-migrate chirpstack device '0102030405060708' > devices.json +``` + +In order to export a large number of devices, create a file named `device_euis.txt` with one DevEUI per line: + +``` +0102030405060701 +0102030405060702 +0102030405060703 +0102030405060704 +0102030405060705 +0102030405060706 +``` + +And then export with: + +```bash +$ ttn-lw-migrate chirpstack device < device_euis.txt > devices.json +``` + +### Export Applications + +Similarly, to export all devices of application `chirpstack-app-1`: + +```bash +$ ttn-lw-migrate chirpstack application 'chirpstack-app-1' > devices.json +``` + +In order to export multiple applications, create a file named `application_names.txt` with one Application name per line: + +``` +chirpstack-app-1 +chirpstack-app-2 +chirpstack-app-3 +``` + +And export with: + +```bash +$ ttn-lw-migrate chirpstack application < application_names.txt > devices.json +``` + ## The Things Stack ### Configuration From d4f338a4dd3b094210a203dc198b4f48e4108661 Mon Sep 17 00:00:00 2001 From: Krishna Iyer Easwaran Date: Mon, 11 Mar 2024 14:30:58 +0100 Subject: [PATCH 7/7] dev: Fix ABP export --- pkg/source/chirpstack/config/config.go | 5 ++ pkg/source/chirpstack/source.go | 89 +++++++++++++------------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/pkg/source/chirpstack/config/config.go b/pkg/source/chirpstack/config/config.go index 4b87674..a38768f 100644 --- a/pkg/source/chirpstack/config/config.go +++ b/pkg/source/chirpstack/config/config.go @@ -110,6 +110,11 @@ func (c *Config) Initialize(src source.Config) error { if err := c.JoinEUI.UnmarshalText([]byte(c.joinEUI)); err != nil { return errInvalidJoinEUI.WithAttributes("join_eui", c.joinEUI) } + c.caCertPath = os.Getenv("CHIRPSTACK_CA_CERT_PATH") + c.insecure = os.Getenv("CHIRPSTACK_INSECURE") == "true" + c.ExportSession = os.Getenv("EXPORT_SESSION") == "true" + c.ExportVars = os.Getenv("EXPORT_VARS") == "true" + err := c.dialGRPC( grpc.FailOnNonTempDialError(true), grpc.WithBlock(), diff --git a/pkg/source/chirpstack/source.go b/pkg/source/chirpstack/source.go index aab63c6..0a3492b 100644 --- a/pkg/source/chirpstack/source.go +++ b/pkg/source/chirpstack/source.go @@ -303,61 +303,62 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) { } // Export session information. - if p.ExportSession { + if !dev.SupportsJoin || p.ExportSession { activation, err := p.getActivation(devEui) - if err == nil { - dev.Session = &ttnpb.Session{Keys: &ttnpb.SessionKeys{AppSKey: &ttnpb.KeyEnvelope{}, FNwkSIntKey: &ttnpb.KeyEnvelope{}}} + if err != nil { + return nil, err + } + dev.Session = &ttnpb.Session{Keys: &ttnpb.SessionKeys{AppSKey: &ttnpb.KeyEnvelope{}, FNwkSIntKey: &ttnpb.KeyEnvelope{}}} - // These fields cannot be empty. - if devProfile.SupportsOtaa { - dev.Session.Keys.SessionKeyId = generateBytes(16) - dev.Session.StartedAt = timestamppb.Now() - } + // These fields cannot be empty. + if devProfile.SupportsOtaa { + dev.Session.Keys.SessionKeyId = generateBytes(16) + dev.Session.StartedAt = timestamppb.Now() + } - devAddr := &types.DevAddr{} - if err := devAddr.UnmarshalText([]byte(activation.DevAddr)); err != nil { - return nil, errInvalidDevAddr.WithAttributes("dev_addr", activation.DevAddr).WithCause(err) - } - dev.Session.DevAddr = devAddr.Bytes() + devAddr := &types.DevAddr{} + if err := devAddr.UnmarshalText([]byte(activation.DevAddr)); err != nil { + return nil, errInvalidDevAddr.WithAttributes("dev_addr", activation.DevAddr).WithCause(err) + } + dev.Session.DevAddr = devAddr.Bytes() - dev.Session.Keys.AppSKey = &ttnpb.KeyEnvelope{} - dev.Session.Keys.AppSKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.AppSKey) + dev.Session.Keys.AppSKey = &ttnpb.KeyEnvelope{} + dev.Session.Keys.AppSKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.AppSKey) + if err != nil { + return nil, errInvalidKey.WithAttributes(activation.AppSKey).WithCause(err) + } + dev.Session.Keys.FNwkSIntKey = &ttnpb.KeyEnvelope{} + dev.Session.Keys.FNwkSIntKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.FNwkSIntKey) + if err != nil { + return nil, errInvalidKey.WithAttributes(activation.FNwkSIntKey).WithCause(err) + } + switch dev.LorawanVersion { + case ttnpb.MACVersion_MAC_V1_1: + dev.Session.Keys.NwkSEncKey = &ttnpb.KeyEnvelope{} + dev.Session.Keys.NwkSEncKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.NwkSEncKey) if err != nil { - return nil, errInvalidKey.WithAttributes(activation.AppSKey).WithCause(err) + return nil, errInvalidKey.WithAttributes(activation.NwkSEncKey).WithCause(err) } - dev.Session.Keys.FNwkSIntKey = &ttnpb.KeyEnvelope{} - dev.Session.Keys.FNwkSIntKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.FNwkSIntKey) + dev.Session.Keys.SNwkSIntKey = &ttnpb.KeyEnvelope{} + dev.Session.Keys.SNwkSIntKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.SNwkSIntKey) if err != nil { - return nil, errInvalidKey.WithAttributes(activation.FNwkSIntKey).WithCause(err) - } - switch dev.LorawanVersion { - case ttnpb.MACVersion_MAC_V1_1: - dev.Session.Keys.NwkSEncKey = &ttnpb.KeyEnvelope{} - dev.Session.Keys.NwkSEncKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.NwkSEncKey) - if err != nil { - return nil, errInvalidKey.WithAttributes(activation.NwkSEncKey).WithCause(err) - } - dev.Session.Keys.SNwkSIntKey = &ttnpb.KeyEnvelope{} - dev.Session.Keys.SNwkSIntKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.SNwkSIntKey) - if err != nil { - return nil, errInvalidKey.WithAttributes(activation.SNwkSIntKey).WithCause(err) - } - default: + return nil, errInvalidKey.WithAttributes(activation.SNwkSIntKey).WithCause(err) } + default: + } - // Set the frame counters. - dev.Session.LastFCntUp = activation.FCntUp - dev.Session.LastAFCntDown = activation.AFCntDown - dev.Session.LastNFCntDown = activation.NFCntDown + // Set the frame counters. + dev.Session.LastFCntUp = activation.FCntUp + dev.Session.LastAFCntDown = activation.AFCntDown + dev.Session.LastNFCntDown = activation.NFCntDown - // Create a MACState. - dev.MacState, err = mac.NewState(dev, p.FPStore, &ttnpb.MACSettings{}) - if err != nil { - return nil, err - } - dev.MacState.CurrentParameters = dev.MacState.DesiredParameters - dev.MacState.CurrentParameters.Rx1Delay = dev.MacSettings.Rx1Delay.Value + // Create a MACState. + dev.MacState, err = mac.NewState(dev, p.FPStore, &ttnpb.MACSettings{}) + if err != nil { + return nil, err } + dev.MacState.CurrentParameters = dev.MacState.DesiredParameters + dev.MacState.CurrentParameters.Rx1Delay = dev.MacSettings.Rx1Delay.Value } return dev, nil