Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support migration from the Firefly LNS #97

Merged
merged 32 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a595f8e
dev: Add `firefly` source
happyRip Sep 9, 2022
edef8e7
dev: Implement a barebones `source.Source` interface
happyRip Sep 9, 2022
c82d280
dev: Add basic config
happyRip Sep 9, 2022
0a1f175
dev: Improve config
happyRip Sep 14, 2022
26f2109
dev: Create `devices` package
happyRip Sep 14, 2022
326f404
dev: Add `api` package
happyRip Sep 14, 2022
cbed7ee
dev: Extend `source` functionalities
happyRip Sep 14, 2022
93ffebf
dev: Improve `devices` package
happyRip Sep 16, 2022
cc72f7c
dev: Improve `api` package
happyRip Sep 16, 2022
6db8516
dev: Work on `firefly` package
happyRip Sep 16, 2022
d4e690f
dev: Move to `zap` logger
happyRip Sep 16, 2022
8b1f3c7
dev: Add `Location` object to `devices` package
happyRip Sep 19, 2022
2fab5a2
dev: Small updates
happyRip Sep 19, 2022
dd978f8
doc: Add documentation for `firefly` source
happyRip Sep 20, 2022
763c7da
cli: Fix firefly source
KrishnaIyer Oct 31, 2023
7498fa0
all: Complete firefly functionality
KrishnaIyer Oct 31, 2023
938f8cd
all: Make firefly device bytes optional
KrishnaIyer Oct 31, 2023
8d3ecd8
all: Fix fetching packets
KrishnaIyer Oct 31, 2023
92c815f
all: Update session key ID
KrishnaIyer Oct 31, 2023
61a08e8
all: Improve code reuse
KrishnaIyer Nov 1, 2023
6683a5f
all: Add option to invalidate keys
KrishnaIyer Nov 1, 2023
a4a3ad8
doc: Update readme
KrishnaIyer Nov 1, 2023
99603f6
dev: Add changelog
KrishnaIyer Nov 1, 2023
46f8fb7
dev: Define iterator per source
KrishnaIyer Nov 6, 2023
bec38d9
all: Harmonize logger usage
KrishnaIyer Nov 6, 2023
0070c4c
all: Propagate context to sub commands
KrishnaIyer Nov 6, 2023
609f8de
all: Fix device ID export
KrishnaIyer Nov 6, 2023
dabeba5
dev: Create MAC state for Firefly devices
KrishnaIyer Nov 6, 2023
9720ffa
all: Init flags without exposing values
KrishnaIyer Nov 6, 2023
1e5e089
all: Make iterator use stdin for devices
KrishnaIyer Nov 7, 2023
1b8e814
dev: Update readme
KrishnaIyer Nov 7, 2023
012bb90
dev: Update test package name
KrishnaIyer Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
# Build files
/build/
/dist/

main
KrishnaIyer marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- Firefly source.

### Changed

### Deprecated
Expand Down
71 changes: 70 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Binaries are available on [GitHub](https://github.com/TheThingsNetwork/lorawan-s
- [x] The Things Network Stack V2
- [x] [ChirpStack Network Server](https://www.chirpstack.io/)
- [x] [The Things Stack](https://www.github.com/TheThingsNetwork/lorawan-stack/)
- [ ] [Firefly](https://fireflyiot.com/)
- [x] [Firefly](https://fireflyiot.com/)
- [ ] [LORIOT Network Server](https://www.loriot.io/)

Support for different sources is done by creating Source plugins. List available sources with:
Expand Down Expand Up @@ -238,6 +238,75 @@ $ ttn-lw-migrate tts application 'my-app-id' --dry-run --verbose > devices.json
$ ttn-lw-migrate tts application 'my-app-id' > devices.json
```

## Firefly

### Configuration

Configure with environment variables, or command-line arguments.

See `ttn-lw-migrate firefly {device|application} --help` for more details.

The following example shows how to set options via environment variables.

```bash
$ export FIREFLY_HOST=example.com # Host of the Firefly API
$ export FIREFLY_API_KEY=abcdefgh # Firefly API Key
$ export APP_ID=my-test-app # Application ID for the exported devices
$ export JOIN_EUI=1111111111111111 # JoinEUI for the exported devices
$ export FREQUENCY_PLAN_ID=EU_863_870 # Frequency Plan ID for the exported devices
$ export MAC_VERSION=1.0.2b # LoRaWAN MAC version for the exported devices
```

### Notes

- The export process will halt if any error occurs.
- Use the `--invalidate-keys` option to invalidate the root and/or session keys of the devices on the Firefly server. This is necessary to prevent both networks from communicating with the same device. The last byte of the keys will be incremented by 0x01. This enables an easy rollback if necessary. Setting this flag to false (default) would result in a "dry run", where the devices are exported but they will still be able to communicate with the Firefly server.

### Export Devices

To export a single device using its Device EUI (e.g. `1111111111111112`):

```bash
# dry run first, verify that no errors occur
$ ttn-lw-migrate firefly device 1111111111111112 --verbose > devices.json
# export device
$ ttn-lw-migrate firefly device 1111111111111112 --invalidate-keys > devices.json
```

In order to export a large number of devices, create a file named `device_euis.txt` with one device EUI per line:

```txt
1111111111111112
FF11111111111134
ABCD111111111100
```

And then export with:

```bash
# dry run first, verify that no errors occur
$ ttn-lw-migrate firefly device --verbose < device_ids.txt > devices.json
# export devices
$ ttn-lw-migrate firefly device --invalidate-keys < device_ids.txt > devices.json
```

### Export All Devices

The Firefly LNS does not strictly enforce device to application relationships.

In order to preserve the semantics of the migration tool, the `firefly` source supports the `application` command but in this case, **all devices that are accessible by the API key** are exported.

> Note: Please be cautious while using this command as this might invalidate all the keys of all the devices.

Similarly, to export all devices of application `my-app-id`:

```bash
# dry run first, verify that no errors occur
$ ttn-lw-migrate firefly application all --verbose > devices.json
KrishnaIyer marked this conversation as resolved.
Show resolved Hide resolved
# export devices
$ ttn-lw-migrate firefly application all --invalidate-keys > devices.json
```

## Development Environment

Requires Go version 1.16 or higher. [Download Go](https://golang.org/dl/).
Expand Down
27 changes: 27 additions & 0 deletions cmd/firefly/firefly.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firefly

import (
"go.thethings.network/lorawan-stack-migrate/pkg/commands"
_ "go.thethings.network/lorawan-stack-migrate/pkg/source/firefly"
)

const sourceName = "firefly"

// FireflyCmd represents the firefly source.
var FireflyCmd = commands.Source(sourceName,
"Export devices from Digimondo's Firefly",
)
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/spf13/cobra"
"go.thethings.network/lorawan-stack-migrate/cmd/chirpstack"
"go.thethings.network/lorawan-stack-migrate/cmd/firefly"
"go.thethings.network/lorawan-stack-migrate/cmd/ttnv2"
"go.thethings.network/lorawan-stack-migrate/cmd/tts"
"go.thethings.network/lorawan-stack-migrate/pkg/export"
Expand Down Expand Up @@ -95,4 +96,5 @@ func init() {
rootCmd.AddCommand(ttnv2.TTNv2Cmd)
rootCmd.AddCommand(tts.TTSCmd)
rootCmd.AddCommand(chirpstack.ChirpStackCmd)
rootCmd.AddCommand(firefly.FireflyCmd)
}
17 changes: 9 additions & 8 deletions pkg/source/chirpstack/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
csapi "github.com/brocaar/chirpstack-api/go/v3/as/external/api"
"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"
Expand Down Expand Up @@ -135,7 +136,7 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) {
}

// Identifiers
dev.Ids.DevEui, err = unmarshalTextToBytes(&types.EUI64{}, devEui)
dev.Ids.DevEui, err = util.UnmarshalTextToBytes(&types.EUI64{}, devEui)
if err != nil {
return nil, errInvalidDevEUI.WithAttributes("dev_eui", devEui).WithCause(err)
}
Expand Down Expand Up @@ -269,19 +270,19 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) {
switch dev.LorawanVersion {
case ttnpb.MACVersion_MAC_V1_1:
dev.RootKeys.AppKey = &ttnpb.KeyEnvelope{}
dev.RootKeys.AppKey.Key, err = unmarshalTextToBytes(&types.AES128Key{}, rootKeys.AppKey)
dev.RootKeys.AppKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, rootKeys.AppKey)
if err != nil {
return nil, errInvalidKey.WithAttributes(rootKeys.AppKey).WithCause(err)
}
dev.RootKeys.NwkKey = &ttnpb.KeyEnvelope{}
dev.RootKeys.NwkKey.Key, err = unmarshalTextToBytes(&types.AES128Key{}, rootKeys.NwkKey)
dev.RootKeys.NwkKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, rootKeys.NwkKey)
if err != nil {
return nil, errInvalidKey.WithAttributes(rootKeys.NwkKey).WithCause(err)
}
case ttnpb.MACVersion_MAC_V1_0, ttnpb.MACVersion_MAC_V1_0_1, ttnpb.MACVersion_MAC_V1_0_2, ttnpb.MACVersion_MAC_V1_0_3, ttnpb.MACVersion_MAC_V1_0_4:
// For LoRaWAN v1.0.x, ChirpStack stores AppKey as NwkKey
dev.RootKeys.AppKey = &ttnpb.KeyEnvelope{}
dev.RootKeys.AppKey.Key, err = unmarshalTextToBytes(&types.AES128Key{}, rootKeys.NwkKey)
dev.RootKeys.AppKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, rootKeys.NwkKey)
if err != nil {
return nil, errInvalidKey.WithAttributes(rootKeys.NwkKey).WithCause(err)
}
Expand Down Expand Up @@ -327,24 +328,24 @@ func (p *Source) ExportDevice(devEui string) (*ttnpb.EndDevice, error) {
dev.Session.StartedAt = timestamppb.Now()

dev.Session.Keys.AppSKey = &ttnpb.KeyEnvelope{}
dev.Session.Keys.AppSKey.Key, err = unmarshalTextToBytes(&types.AES128Key{}, activation.AppSKey)
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 = unmarshalTextToBytes(&types.AES128Key{}, activation.FNwkSIntKey)
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 = unmarshalTextToBytes(&types.AES128Key{}, activation.NwkSEncKey)
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 = unmarshalTextToBytes(&types.AES128Key{}, activation.SNwkSIntKey)
dev.Session.Keys.SNwkSIntKey.Key, err = util.UnmarshalTextToBytes(&types.AES128Key{}, activation.SNwkSIntKey)
if err != nil {
return nil, errInvalidKey.WithAttributes(activation.SNwkSIntKey).WithCause(err)
}
Expand Down
11 changes: 0 additions & 11 deletions pkg/source/chirpstack/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,3 @@ func (p *Source) getActivation(devEui string) (*csapi.DeviceActivation, error) {
}
return resp.DeviceActivation, err
}

func unmarshalTextToBytes(
unmarshaller interface {
UnmarshalText([]byte) error
Bytes() []byte
},
source string,
) ([]byte, error) {
err := unmarshaller.UnmarshalText([]byte(source))
return unmarshaller.Bytes(), err
}
Loading
Loading