diff --git a/README.md b/README.md index b8e7e2e..15b5fe0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Docker images are available on [Docker Hub](https://hub.docker.com/r/TheThingsNe ## Support +- [X] The Things Network Stack V2 - [X] [ChirpStack Network Server](https://www.chirpstack.io/) - [ ] [LORIOT Network Server](https://www.loriot.io/) @@ -33,6 +34,63 @@ Navigate to your application, click **Import End Devices**, select **The Things $ ttn-lw-cli end-devices create --application-id test-app < devices.json ``` +## The Things Network Stack V2 + +### Configuration + +Configure with environment variables, or command-line arguments. See `--help` for more details: + +```bash +$ export TTNV2_APP_ID="my-ttn-app" # TTN App ID +$ export TTNV2_APP_ACCESS_KEY="ttn-account-v2.a..." # TTN App Access Key (needs `devices` permissions) +$ export FREQUENCY_PLAN_ID="EU_863_870_TTN" # Frequency Plan for exported devices +``` + +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. + +Private The Things Network Stack V2 deployments are also supported, and require extra configuration. See `ttn-lw-migrate device --help` for more details. For example, to override the discovery server address: + +```bash +$ export TTNV2_DISCOVERY_SERVER_ADDRESS="discovery.thethings.network:1900" +``` + +### Export Devices + +To export a single device using its Device ID (e.g. `mydevice`): + +```bash +$ ttn-lw-migrate device --source ttnv2 "mydevice" > devices.json +``` + +In order to export a large number of devices, create a file named `device_ids.txt` with one device ID per line: + +``` +mydevice +otherdevice +device3 +device4 +device5 +``` + +And then export with: + +```bash +$ ttn-lw-migrate device --source ttnv2 < device_ids.txt > devices.json +``` + +### Export Applications + +Similarly, to export all devices of application `my-app-id`: + +```bash +$ ttn-lw-migrate application --source ttnv2 "my-app-id" > devices.json +``` + +### Notes + +- Payload formatters are not exported. See [Payload Formatters](https://thethingsstack.io/integrations/payload-formatters/). +- Active device sessions are exported by default. You can disable this by using the `--ttnv2.with-session=false` flag. It is recommended that you do not export session keys for devices that can instead re-join on The Things Stack. + ## ChirpStack ### Configuration @@ -46,6 +104,8 @@ $ export JOIN_EUI="0101010102020203" # JoinEUI for exported devices $ export FREQUENCY_PLAN_ID="EU_863_870" # Frequency Plan for exported devices ``` +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. ### Export Devices diff --git a/cmd/ttn-lw-migrate/main.go b/cmd/ttn-lw-migrate/main.go index 9c1130b..033538e 100644 --- a/cmd/ttn-lw-migrate/main.go +++ b/cmd/ttn-lw-migrate/main.go @@ -18,6 +18,7 @@ import ( "os" _ "go.thethings.network/lorawan-stack-migrate/pkg/source/chirpstack" // ChirpStack source + _ "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv2" // TTNv2 source "go.thethings.network/lorawan-stack-migrate/cmd" ) diff --git a/go.mod b/go.mod index 7a05455..317e80a 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,9 @@ require ( github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/azure-storage-blob-go v0.10.0 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.2 // indirect + github.com/TheThingsNetwork/go-app-sdk v0.0.0-20191121100818-5bae20ae2b27 + github.com/TheThingsNetwork/go-utils v0.0.0-20190516083235-bdd4967fab4e + github.com/TheThingsNetwork/ttn/utils/errors v0.0.0-20190516081709-034d40b328bd github.com/aws/aws-sdk-go v1.34.9 // indirect github.com/brocaar/chirpstack-api/go/v3 v3.7.5 github.com/envoyproxy/protoc-gen-validate v0.4.1 // indirect diff --git a/go.sum b/go.sum index 2ef6df4..c81b952 100644 --- a/go.sum +++ b/go.sum @@ -111,7 +111,31 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/TheThingsIndustries/mystique v0.0.0-20200127144137-4aa959111fe7/go.mod h1:SdYf+XGL8wHUUJHd6LedTIWQRGfJqBqXEYFxwlCdLc8= +github.com/TheThingsNetwork/api v0.0.0-20190330165854-3fb363b63d07/go.mod h1:iGcCXFi0My4IJbLSWWlRONaYiMtSjLbdkfFE5xBrgRc= +github.com/TheThingsNetwork/api v0.0.0-20190516111443-a3523f89e84f h1:socqGNa6e5LZ4mQFZmwKHrejRHX3geh6kZzHUhVCSqI= +github.com/TheThingsNetwork/api v0.0.0-20190516111443-a3523f89e84f/go.mod h1:MYWz0xDXK+4LWQMnAcm//UPhLoamf67LgFjMwUdhKbE= +github.com/TheThingsNetwork/go-account-lib v0.0.0-20190516094738-77d15a3f8875 h1:Q6gqExgAk2mQlZPur7dwbGsy9Uxs9Hs2kurMTd+o2Rc= +github.com/TheThingsNetwork/go-account-lib v0.0.0-20190516094738-77d15a3f8875/go.mod h1:VZeXL6kkGnZouPnLESpVSGSRQNlz8zAOKHzr0P6m4pc= +github.com/TheThingsNetwork/go-app-sdk v0.0.0-20191121100818-5bae20ae2b27 h1:Hy6OG6EOE9zing2nQ0XKIEijdrGzsoxwMNAkB82olUs= +github.com/TheThingsNetwork/go-app-sdk v0.0.0-20191121100818-5bae20ae2b27/go.mod h1:4CxXsBHspKfPQJnpXH/6nTKC9KCkx0mvPzWMcQvA0Yk= github.com/TheThingsNetwork/go-cayenne-lib v1.0.0/go.mod h1:Lkg0oDuFTF6WlZvyPV35WFHov5U9zPv2lLoAQ15NjrI= +github.com/TheThingsNetwork/go-utils v0.0.0-20190516083235-bdd4967fab4e h1:JEt3G2ONKGfW1YDp2A8Q/+kZIuIB117nkit0+GDJN04= +github.com/TheThingsNetwork/go-utils v0.0.0-20190516083235-bdd4967fab4e/go.mod h1:9uzg7Jk8ywYqL+xUEhTNrJcs68Nafj4qTaz/zB+STwg= +github.com/TheThingsNetwork/ttn/api v0.0.0-20190516081709-034d40b328bd h1:vCjDYImJDdW+39EXwij00yzDi1pd3TmP6XtCteDJBd0= +github.com/TheThingsNetwork/ttn/api v0.0.0-20190516081709-034d40b328bd/go.mod h1:UCRXmEaShvS/wHOf2RcoY2vKUGJnrYrotBA6LZzYdFM= +github.com/TheThingsNetwork/ttn/core/types v0.0.0-20190516081709-034d40b328bd/go.mod h1:VVWTaeAJHezuE+c0Vk0AJ4R6KSLg50H1y3RB7vGhGOA= +github.com/TheThingsNetwork/ttn/core/types v0.0.0-20190516092602-86414c703ee1/go.mod h1:q3r/3g5fixboWUOCounAWo+Y8OlkKdhVnGosLMEQ09Q= +github.com/TheThingsNetwork/ttn/core/types v0.0.0-20190516112328-fcd38e2b9dc6 h1:XUWO3mw11jfff7qjDR2Z9VxMofsFDyrxUhPY/82lrqg= +github.com/TheThingsNetwork/ttn/core/types v0.0.0-20190516112328-fcd38e2b9dc6/go.mod h1:q3r/3g5fixboWUOCounAWo+Y8OlkKdhVnGosLMEQ09Q= +github.com/TheThingsNetwork/ttn/mqtt v0.0.0-20190516112328-fcd38e2b9dc6 h1:2T7NTSemIJ9uWv0VcfxS7ANkFvRHqQ7+yXS0OQZCiy8= +github.com/TheThingsNetwork/ttn/mqtt v0.0.0-20190516112328-fcd38e2b9dc6/go.mod h1:reZ1DGzwREWfUapXIyMWU4Ybj8yxB3DD6zBW5Onm2Z0= +github.com/TheThingsNetwork/ttn/utils/errors v0.0.0-20190516081709-034d40b328bd h1:ITXOJpmUR4Jhp3Xb/xNUIJH4WR0h2/NsxZkSDzFIFiU= +github.com/TheThingsNetwork/ttn/utils/errors v0.0.0-20190516081709-034d40b328bd/go.mod h1:e8FjzgvhAVx9+iqPloB4v7QM0rmv+r5ysRn9kWFamG4= +github.com/TheThingsNetwork/ttn/utils/random v0.0.0-20190516081709-034d40b328bd/go.mod h1:ktVq1/rYkTlgilBixqCtltTh29rOUnZET4g50xoKlpE= +github.com/TheThingsNetwork/ttn/utils/random v0.0.0-20190516092602-86414c703ee1 h1:Y0jKI253ear5Kz8XJf3PIv2+rtHB2b1UoWDIpm8bdV0= +github.com/TheThingsNetwork/ttn/utils/random v0.0.0-20190516092602-86414c703ee1/go.mod h1:ktVq1/rYkTlgilBixqCtltTh29rOUnZET4g50xoKlpE= +github.com/TheThingsNetwork/ttn/utils/security v0.0.0-20190516081709-034d40b328bd/go.mod h1:aaYF12LufW5Xs4Z2C6UOrCzpkyoBjw+rmHCcNYgb1JU= +github.com/TheThingsNetwork/ttn/utils/testing v0.0.0-20190516092602-86414c703ee1/go.mod h1:KE2xKKLXyXmH9KBkUWJD5XPI0K10T2tj7E1kBLHq8kQ= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= @@ -126,12 +150,15 @@ github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQY github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.0 h1:J5rld6WVFi6NxA6m8GJ1LJqu3+GiTFIt3mYv27gdQWI= github.com/apex/log v1.1.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -151,11 +178,14 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bluele/gcache v0.0.0-20190301044115-79ae3b2d8680/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk= github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833 h1:yCfXxYaelOyqnia8F/Yng47qhmfC9nKTRIbYRrRueq4= github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk= github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/brocaar/chirpstack-api/go/v3 v3.7.5 h1:lcnzNehXwC2jg76melt10aWejMHByNXDh8UY65+nf8U= github.com/brocaar/chirpstack-api/go/v3 v3.7.5/go.mod h1:ex/wqXQaClwDMa2zDN6crp9ZiMGc1GMVQhjxiB+OJcg= +github.com/brocaar/lorawan v0.0.0-20170626123636-a64aca28516d h1:yIe4YHMG4WUoBCWlcDsfFD1IgnysKNLDKCe+yY0sknc= +github.com/brocaar/lorawan v0.0.0-20170626123636-a64aca28516d/go.mod h1:kwUChfPyeHBQumTUYBvOkO4prdwMM55wby9Zw9lhzlA= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -210,6 +240,7 @@ github.com/eaigner/dkim v0.0.0-20150301120808-6fe4a7ee9cfb/go.mod h1:FSCIHbrqk7D github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= @@ -303,6 +334,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -371,6 +403,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -385,6 +418,7 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 h1:b70jEaX2iaJSPZULSUxKtm73LBfsCrMsIlYCUgNGSIs= github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976/go.mod h1:ZGQeOwybjD8lkCjIyJfqR5LD2wMVHJ31d6GdPxoTsWY= +github.com/gotnospirit/messageformat v0.0.0-20180622080451-0eab1176a3fb/go.mod h1:NO9UUa4C4cSmRsYSfZMAKhI5ifCRzOjSGe/pi7TKRvs= github.com/gotnospirit/messageformat v0.0.0-20190719172517-c1d0bdacdea2 h1:yUr520KXfjzq/QTGZ2h+DvEydkyBfvifw6ksyDW3Lpg= github.com/gotnospirit/messageformat v0.0.0-20190719172517-c1d0bdacdea2/go.mod h1:NO9UUa4C4cSmRsYSfZMAKhI5ifCRzOjSGe/pi7TKRvs= github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -447,6 +481,7 @@ github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/C github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= @@ -595,6 +630,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-grpc-middleware v1.0.0 h1:XraEe8LhUuB33YeV4NWfLh2KUZicskSZ2lMhVRnDvTQ= +github.com/mwitkow/go-grpc-middleware v1.0.0/go.mod h1:wqm8af53+/cILryTaG+dCJS6CsDMVZDxlKh6lSkF19U= github.com/nats-io/jwt v0.2.6/go.mod h1:mQxQ0uHQ9FhEVPIcTSKwx2lqZEpXWWcCgA7R6NrWvvY= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -624,6 +661,7 @@ github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= @@ -631,6 +669,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= @@ -738,6 +777,7 @@ github.com/sendgrid/sendgrid-go v3.5.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -748,8 +788,11 @@ github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cL github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v0.0.0-20190426220047-d9c9211acd48/go.mod h1:oqKsUQaUkJ2EU1ZzLQFJt1WUp9DDuj1CnZbp4DwPwL4= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -798,6 +841,7 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -997,6 +1041,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190516014833-cab07311ab81/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190516110030-61b9204099cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1147,6 +1193,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190515210553-995ef27e003f/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -1235,6 +1282,8 @@ gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g= gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw= +gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= diff --git a/pkg/source/ttnv2/config.go b/pkg/source/ttnv2/config.go new file mode 100644 index 0000000..df58e50 --- /dev/null +++ b/pkg/source/ttnv2/config.go @@ -0,0 +1,135 @@ +// Copyright © 2020 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 ttnv2 + +import ( + "context" + "crypto/tls" + "crypto/x509" + "io/ioutil" + "os" + + ttnsdk "github.com/TheThingsNetwork/go-app-sdk" + ttnlog "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/spf13/pflag" +) + +const ( + clientName = "ttn-lw-migrate" +) + +type config struct { + sdkConfig ttnsdk.ClientConfig + + appAccessKey string + appID string + + frequencyPlanID string + + withSession bool +} + +func flagSet() *pflag.FlagSet { + flags := &pflag.FlagSet{} + flags.String("ttnv2.frequency-plan-id", os.Getenv("FREQUENCY_PLAN_ID"), "Frequency Plan ID of exported devices") + flags.String("ttnv2.app-id", os.Getenv("TTNV2_APP_ID"), "TTN Application ID") + flags.String("ttnv2.app-access-key", os.Getenv("TTNV2_APP_ACCESS_KEY"), "TTN Application Access Key (with 'devices' permissions)") + flags.String("ttnv2.ca-cert", os.Getenv("TTNV2_CA_CERT"), "(only for private networks) CA for TLS") + flags.String("ttnv2.handler-address", os.Getenv("TTNV2_HANDLER_ADDRESS"), "(only for private networks) Address for the Handler") + flags.String("ttnv2.account-server-address", os.Getenv("TTNV2_ACCOUNT_SERVER_ADDRESS"), "(only for private networks) Address for the Account Server") + flags.String("ttnv2.account-server-client-id", os.Getenv("TTNV2_ACCOUNT_SERVER_CLIENT_ID"), "(only for private networks) Client ID for the Account Server") + flags.String("ttnv2.account-server-client-secret", os.Getenv("TTNV2_ACCOUNT_SERVER_CLIENT_SECRET"), "(only for private networks) Client secret for the Account Server") + flags.String("ttnv2.discovery-server-address", os.Getenv("TTNV2_DISCOVERY_SERVER_ADDRESS"), "(only for private networks) Address for the Discovery Server") + flags.Bool("ttnv2.discovery-server-insecure", false, "(only for private networks) Not recommended") + flags.Bool("ttnv2.with-session", true, "Export device session keys and frame counters") + return flags +} + +func getConfig(ctx context.Context, flags *pflag.FlagSet) (config, error) { + stringFlag := func(f string) string { + s, _ := flags.GetString(f) + return s + } + boolFlag := func(f string) bool { + s, _ := flags.GetBool(f) + return s + } + + cfg := ttnsdk.NewCommunityConfig(clientName) + if f := stringFlag("ttnv2.account-server-address"); f != "" { + cfg.AccountServerAddress = f + } + if f := stringFlag("ttnv2.account-server-client-id"); f != "" { + cfg.AccountServerClientID = f + } + if f := stringFlag("ttnv2.account-server-client-secret"); f != "" { + cfg.AccountServerClientSecret = f + } + if f := stringFlag("ttnv2.handler-address"); f != "" { + cfg.HandlerAddress = f + } + if f := stringFlag("ttnv2.discovery-server-address"); f != "" { + cfg.DiscoveryServerAddress = f + } + cfg.DiscoveryServerInsecure = boolFlag("ttnv2.discovery-server-insecure") + + if ca := stringFlag("ttnv2.ca-cert"); ca != "" { + if cfg.TLSConfig == nil { + cfg.TLSConfig = &tls.Config{} + } + rootCAs := cfg.TLSConfig.RootCAs + if rootCAs == nil { + var err error + if rootCAs, err = x509.SystemCertPool(); err != nil { + rootCAs = x509.NewCertPool() + } + } + pemBytes, err := ioutil.ReadFile(ca) + if err != nil { + return config{}, errRead.WithAttributes("file", ca) + } + rootCAs.AppendCertsFromPEM(pemBytes) + } + + appAccessKey := stringFlag("ttnv2.app-access-key") + if appAccessKey == "" { + return config{}, errNoAppAccessKey.New() + } + appID := stringFlag("ttnv2.app-id") + if appID == "" { + return config{}, errNoAppID.New() + } + frequencyPlanID := stringFlag("ttnv2.frequency-plan-id") + if frequencyPlanID == "" { + return config{}, errNoFrequencyPlanID.New() + } + + logger := apex.Stdout() + if boolFlag("verbose") { + logger.MustParseLevel("debug") + } + ttnlog.Set(logger) + + return config{ + sdkConfig: cfg, + + appID: appID, + appAccessKey: appAccessKey, + frequencyPlanID: frequencyPlanID, + + withSession: boolFlag("ttnv2.with-session"), + }, nil +} diff --git a/pkg/source/ttnv2/errors.go b/pkg/source/ttnv2/errors.go new file mode 100644 index 0000000..1aa62be --- /dev/null +++ b/pkg/source/ttnv2/errors.go @@ -0,0 +1,25 @@ +// Copyright © 2020 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 ttnv2 + +import "go.thethings.network/lorawan-stack/v3/pkg/errors" + +var ( + errRead = errors.DefinePermissionDenied("read", "failed to read `{file}`") + + errNoAppID = errors.DefineInvalidArgument("no_app_id", "no app id") + errNoAppAccessKey = errors.DefineInvalidArgument("no_app_access_key", "no app access key") + errNoFrequencyPlanID = errors.DefineInvalidArgument("no_frequency_plan_id", "no frequency plan id") +) diff --git a/pkg/source/ttnv2/source.go b/pkg/source/ttnv2/source.go new file mode 100644 index 0000000..a7e8bb7 --- /dev/null +++ b/pkg/source/ttnv2/source.go @@ -0,0 +1,198 @@ +// Copyright © 2020 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 ttnv2 + +import ( + "context" + "time" + + ttnsdk "github.com/TheThingsNetwork/go-app-sdk" + "github.com/TheThingsNetwork/ttn/utils/errors" + pbtypes "github.com/gogo/protobuf/types" + "github.com/spf13/pflag" + "go.thethings.network/lorawan-stack-migrate/pkg/source" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "go.thethings.network/lorawan-stack/v3/pkg/types" +) + +const ( + // cooldown between consecutive DeviceManager.Get calls, in order to avoid rate limits. + cooldown = 10 * time.Millisecond +) + +// Source implements the Source interface. +type Source struct { + ctx context.Context + + config config + mgr ttnsdk.DeviceManager + client ttnsdk.Client + + devices map[string]*ttnsdk.Device +} + +// NewSource creates a new TTNv2 Source. +func NewSource(ctx context.Context, flags *pflag.FlagSet) (source.Source, error) { + config, err := getConfig(ctx, flags) + if err != nil { + return nil, err + } + + return &Source{ + ctx: ctx, + config: config, + devices: make(map[string]*ttnsdk.Device), + }, nil +} + +func (s *Source) getDeviceManager(appID string) (ttnsdk.DeviceManager, error) { + if s.mgr == nil { + if s.client == nil { + s.client = s.config.sdkConfig.NewClient(appID, s.config.appAccessKey) + } + var err error + s.mgr, err = s.client.ManageDevices() + if err != nil { + return nil, errors.FromGRPCError(err) + } + } + return s.mgr, nil +} + +// ExportDevice implements the source.Source interface. +func (s *Source) ExportDevice(devID string) (*ttnpb.EndDevice, error) { + dev := s.devices[devID] + if s.config.withSession { + mgr, err := s.getDeviceManager(s.config.appID) + if err != nil { + return nil, err + } + dev, err = mgr.Get(devID) + if err != nil { + return nil, errors.FromGRPCError(err) + } + } + + v3dev := &ttnpb.EndDevice{} + v3dev.DeviceID = dev.DevID + v3dev.ApplicationID = s.config.appID + + v3dev.Name = dev.DevID + v3dev.Description = dev.Description + v3dev.Attributes = dev.Attributes + + v3dev.JoinEUI = &types.EUI64{} + if err := v3dev.JoinEUI.Unmarshal(dev.AppEUI.Bytes()); err != nil { + return nil, err + } + v3dev.DevEUI = &types.EUI64{} + if err := v3dev.DevEUI.Unmarshal(dev.DevEUI.Bytes()); err != nil { + return nil, err + } + + v3dev.LoRaWANVersion = ttnpb.MAC_V1_0_2 + v3dev.LoRaWANPHYVersion = ttnpb.PHY_V1_0_2_REV_B + v3dev.FrequencyPlanID = s.config.frequencyPlanID + + v3dev.MACSettings = &ttnpb.MACSettings{ + Rx1Delay: &ttnpb.RxDelayValue{ + Value: ttnpb.RX_DELAY_1, + }, + } + if dev.Uses32BitFCnt { + v3dev.MACSettings.Supports32BitFCnt = &pbtypes.BoolValue{ + Value: dev.Uses32BitFCnt, + } + } + if dev.DisableFCntCheck { + v3dev.MACSettings.ResetsFCnt = &pbtypes.BoolValue{ + Value: dev.DisableFCntCheck, + } + } + + if dev.AppKey != nil && !dev.AppKey.IsEmpty() { + v3dev.SupportsJoin = true + v3dev.RootKeys = &ttnpb.RootKeys{} + v3dev.RootKeys.AppKey = &ttnpb.KeyEnvelope{ + Key: &types.AES128Key{}, + } + if err := v3dev.RootKeys.AppKey.Key.Unmarshal(dev.AppKey.Bytes()); err != nil { + return nil, err + } + } + + if dev.Latitude != 0 || dev.Longitude != 0 { + v3dev.Locations = map[string]*ttnpb.Location{ + "user": { + Latitude: float64(dev.Latitude), + Longitude: float64(dev.Longitude), + Altitude: dev.Altitude, + Source: ttnpb.SOURCE_REGISTRY, + }, + } + } + + if s.config.withSession && dev.DevAddr != nil && dev.NwkSKey != nil && dev.AppSKey != nil { + v3dev.Session = &ttnpb.Session{ + SessionKeys: ttnpb.SessionKeys{ + AppSKey: &ttnpb.KeyEnvelope{ + Key: &types.AES128Key{}, + }, + FNwkSIntKey: &ttnpb.KeyEnvelope{ + Key: &types.AES128Key{}, + }, + }, + LastFCntUp: dev.FCntUp, + LastNFCntDown: dev.FCntDown, + StartedAt: time.Now(), + } + if err := v3dev.Session.DevAddr.Unmarshal(dev.DevAddr.Bytes()); err != nil { + return nil, err + } + if err := v3dev.Session.SessionKeys.AppSKey.Key.Unmarshal(dev.AppSKey.Bytes()); err != nil { + return nil, err + } + if err := v3dev.Session.SessionKeys.FNwkSIntKey.Key.Unmarshal(dev.NwkSKey.Bytes()); err != nil { + return nil, err + } + } + + return v3dev, nil +} + +// RangeDevices implements the source.Source interface. +func (s *Source) RangeDevices(appID string, f func(source.Source, string) error) error { + mgr, err := s.getDeviceManager(appID) + if err != nil { + return err + } + devices, err := mgr.List(0, 0) + if err != nil { + return errors.FromGRPCError(err) + } + + for _, dev := range devices { + s.devices[dev.DevID] = dev.AsDevice() + if err := f(s, dev.DevID); err != nil { + return err + } + } + return nil +} + +// Close implements the Source interface. +func (s *Source) Close() error { + return s.client.Close() +} diff --git a/pkg/source/ttnv2/ttnv2.go b/pkg/source/ttnv2/ttnv2.go new file mode 100644 index 0000000..7d7af9a --- /dev/null +++ b/pkg/source/ttnv2/ttnv2.go @@ -0,0 +1,26 @@ +// Copyright © 2020 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 ttnv2 + +import "go.thethings.network/lorawan-stack-migrate/pkg/source" + +func init() { + source.RegisterSource(source.Registration{ + Name: "ttnv2", + Description: "Migrate from The Things Network Stack V2", + FlagSet: flagSet(), + Create: NewSource, + }) +}