diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..b03a99de8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.go] +indent_style = tab +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab +indent_size = 2 diff --git a/.env/.gitignore b/.env/.gitignore new file mode 100644 index 000000000..8f0792831 --- /dev/null +++ b/.env/.gitignore @@ -0,0 +1,2 @@ +*/auth-*.pub +redis/ diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml new file mode 100644 index 000000000..284171401 --- /dev/null +++ b/.env/broker/dev.yml @@ -0,0 +1,14 @@ +id: dev +debug: true +discovery-address: "localhost:1900" +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" +tls: true +key-dir: "./.env/broker/" + +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImJyb2tlciIsImlhdCI6MTQ3NjQzOTQzOH0.nVH5FuQiN_WUEdntQ-kDocjTxq-UrlKey2Jm6NJmE18tts5K4PDPMzNgF-uenKKHEcDWImlf3k1vcI4LMPUj0sgPxWdyR5PnUNFRIupxdy1k_Q2cSnZcmpX-Mc4KN23JzXtmaDq-Qp7reyEOv7K7HpHwt6Jb_YQHEZEfkU1628LQaybUKJgCIttoUzBV12dFfKnC8tdL_NMfSVCquhITLOj5efXf-0CL6A_4s_vNctnIBFfpdKUeukpnT52B8-c2SCzk36g13n3L-6fc7MRTCaF0D1LlMQasy5Aq39e_1VJP10kH_-luoF8eOFHVFH-r4pLf6_RGz51oocDwt7w9Hw + +broker: + networkserver-cert: ./.env/broker/networkserver.cert + networkserver-token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Njk3OTQzNTksIm5iZiI6MTQ2OTc5NDM1OSwic3ViIjoiZGV2In0.Xj1AFEAblzFLdS5cUiDrG773EqHazARvGdkTHFoPa_c0XGhdKSrevVpTNkzvcXeTQTWnTQrg1t98atNIk7F13Q diff --git a/.env/broker/networkserver.cert b/.env/broker/networkserver.cert new file mode 100644 index 000000000..b04453e85 --- /dev/null +++ b/.env/broker/networkserver.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBljCCATygAwIBAgIRAOs3B7qZNgejMf+laNHTRuYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIxNVoXDTE3 +MDcyOTEyMTIxNVowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx +3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMqNdMFswDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +IwYDVR0RBBwwGoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVyMAoGCCqGSM49BAMC +A0gAMEUCIBrRl5a2PX+fn68Uefq15Cn1C1XE6NGVmI+HvmP1sA1JAiEA0L4WgKdo +HcUc8PnKlUUgN9nLVx98W9Sb2TvOaldspVE= +-----END CERTIFICATE----- diff --git a/.env/broker/server.cert b/.env/broker/server.cert new file mode 100644 index 000000000..b5fd01ef3 --- /dev/null +++ b/.env/broker/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATSgAwIBAgIQFzR5quaK8FbcvukuVI94uDAKBggqhkjOPQQDAjAdMRsw +GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswHhcNMTYwNzI5MTIxMTU2WhcNMTcw +NzI5MTIxMTU2WjAdMRswGQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARKJiC1vanAuqbOtVYPwSM8D6oSseAOeUra5dHi +Xtp72d59+kgaEfa6Zgp0KSaes+D2fxE+RJ4G6v7DYbKgWOGto1YwVDAOBgNVHQ8B +Af8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAc +BgNVHREEFTATgglsb2NhbGhvc3SCBmJyb2tlcjAKBggqhkjOPQQDAgNIADBFAiAU +fiL3mhXOFbG9T3QVv2lH7H58pnhdrJmIBN1n6qvDXgIhAPgpd2ZCkJ04GHQyTuoU +v75tKrSlLiJw62QdY+s93uSh +-----END CERTIFICATE----- diff --git a/.env/broker/server.key b/.env/broker/server.key new file mode 100644 index 000000000..4e4caebda --- /dev/null +++ b/.env/broker/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBugiJ1PxyuQto5UGUqnT3rSPhwAUzcpZvREmbSpATQioAoGCCqGSM49 +AwEHoUQDQgAESiYgtb2pwLqmzrVWD8EjPA+qErHgDnlK2uXR4l7ae9neffpIGhH2 +umYKdCkmnrPg9n8RPkSeBur+w2GyoFjhrQ== +-----END EC PRIVATE KEY----- diff --git a/.env/broker/server.pub b/.env/broker/server.pub new file mode 100644 index 000000000..5a298cda9 --- /dev/null +++ b/.env/broker/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESiYgtb2pwLqmzrVWD8EjPA+qErHg +DnlK2uXR4l7ae9neffpIGhH2umYKdCkmnrPg9n8RPkSeBur+w2GyoFjhrQ== +-----END PUBLIC KEY----- diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml new file mode 100644 index 000000000..175f3c49d --- /dev/null +++ b/.env/discovery/dev.yml @@ -0,0 +1,7 @@ +id: dev +debug: true +discovery-address: "localhost:1900" +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" +key-dir: "./.env/discovery/" diff --git a/.env/discovery/server.key b/.env/discovery/server.key new file mode 100644 index 000000000..71d888584 --- /dev/null +++ b/.env/discovery/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIGnSFCNG/2H/5usWkVu8jDBfolSeZqC/hm7FYlyHjSCMoAoGCCqGSM49 +AwEHoUQDQgAEGdwVP3pf5UGc+ISdvzQG/qDjzbQiZuXVflmyEkPkUSFxyqPRupgz +BIbsKxQOC8opGdcn7TcSqCUeyyCd8JHUfg== +-----END EC PRIVATE KEY----- diff --git a/.env/discovery/server.pub b/.env/discovery/server.pub new file mode 100644 index 000000000..ee8222998 --- /dev/null +++ b/.env/discovery/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGdwVP3pf5UGc+ISdvzQG/qDjzbQi +ZuXVflmyEkPkUSFxyqPRupgzBIbsKxQOC8opGdcn7TcSqCUeyyCd8JHUfg== +-----END PUBLIC KEY----- diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml new file mode 100644 index 000000000..a292d14b6 --- /dev/null +++ b/.env/handler/dev.yml @@ -0,0 +1,13 @@ +id: dev +debug: true +discovery-address: "localhost:1900" +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" +tls: true +key-dir: "./.env/handler/" +handler: + amqp-address: localhost:5672 + mqtt-address: localhost:1883 + +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg diff --git a/.env/handler/server.cert b/.env/handler/server.cert new file mode 100644 index 000000000..b52c6d1e1 --- /dev/null +++ b/.env/handler/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBkDCCATagAwIBAgIRANJJ3rP0cNrdrouE2GCkkaYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIwNloXDTE3 +MDcyOTEyMTIwNlowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEiXbWvyYjOMP4ebTYtVvdIsBwS+U3laWltR7V +ox4+kQWcGLLEg+suI9SRZyKK+frhw9JPKbVNIgEv/S50YKfMEaNXMFUwDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0RBBYwFIIJbG9jYWxob3N0ggdoYW5kbGVyMAoGCCqGSM49BAMCA0gAMEUC +IQDbNJPUIfKZ/1CkTF3+ukl64l3fn613hnMiqAJYO7yz7QIgTAwlr3vkLquSQZUO +yraf7CGvuvulKs4S8sd8im6Bdgs= +-----END CERTIFICATE----- diff --git a/.env/handler/server.key b/.env/handler/server.key new file mode 100644 index 000000000..8326b70ef --- /dev/null +++ b/.env/handler/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBuY3Ugapx0M58eCNcpQJF4YMYdTvC0h9NYDRl9xu12CoAoGCCqGSM49 +AwEHoUQDQgAEiXbWvyYjOMP4ebTYtVvdIsBwS+U3laWltR7Vox4+kQWcGLLEg+su +I9SRZyKK+frhw9JPKbVNIgEv/S50YKfMEQ== +-----END EC PRIVATE KEY----- diff --git a/.env/handler/server.pub b/.env/handler/server.pub new file mode 100644 index 000000000..a92fc1f6c --- /dev/null +++ b/.env/handler/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiXbWvyYjOMP4ebTYtVvdIsBwS+U3 +laWltR7Vox4+kQWcGLLEg+suI9SRZyKK+frhw9JPKbVNIgEv/S50YKfMEQ== +-----END PUBLIC KEY----- diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml new file mode 100644 index 000000000..ddca01c26 --- /dev/null +++ b/.env/networkserver/dev.yml @@ -0,0 +1,7 @@ +debug: true +discovery-address: "localhost:1900" +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" +tls: true +key-dir: "./.env/networkserver/" diff --git a/.env/networkserver/server.cert b/.env/networkserver/server.cert new file mode 100644 index 000000000..b04453e85 --- /dev/null +++ b/.env/networkserver/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBljCCATygAwIBAgIRAOs3B7qZNgejMf+laNHTRuYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIxNVoXDTE3 +MDcyOTEyMTIxNVowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx +3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMqNdMFswDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +IwYDVR0RBBwwGoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVyMAoGCCqGSM49BAMC +A0gAMEUCIBrRl5a2PX+fn68Uefq15Cn1C1XE6NGVmI+HvmP1sA1JAiEA0L4WgKdo +HcUc8PnKlUUgN9nLVx98W9Sb2TvOaldspVE= +-----END CERTIFICATE----- diff --git a/.env/networkserver/server.key b/.env/networkserver/server.key new file mode 100644 index 000000000..e43c0d345 --- /dev/null +++ b/.env/networkserver/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINI0PaT8sx614/iBnqZ3Z7TpS+lpVjoqXNYq7yfOagD6oAoGCCqGSM49 +AwEHoUQDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx3JSyWm19DW5dihzP +FrB0Ezu+lak91rTEaon9WNcVibhFNG5wMg== +-----END EC PRIVATE KEY----- diff --git a/.env/networkserver/server.pub b/.env/networkserver/server.pub new file mode 100644 index 000000000..6ead1f00e --- /dev/null +++ b/.env/networkserver/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCX +Z60mbgdx3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMg== +-----END PUBLIC KEY----- diff --git a/.env/router/dev.yml b/.env/router/dev.yml new file mode 100644 index 000000000..5adc0df17 --- /dev/null +++ b/.env/router/dev.yml @@ -0,0 +1,12 @@ +id: dev +debug: true +discovery-address: "localhost:1900" +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" +tls: true +key-dir: "./.env/router/" +router: + skip-verify-gateway-token: true + +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA diff --git a/.env/router/server.cert b/.env/router/server.cert new file mode 100644 index 000000000..fdc967e17 --- /dev/null +++ b/.env/router/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATSgAwIBAgIQS/57LijDDb+PB9+WltoR1DAKBggqhkjOPQQDAjAdMRsw +GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswHhcNMTYwNzI5MTIxMTQ2WhcNMTcw +NzI5MTIxMTQ2WjAdMRswGQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAQrcb9XbpbPrXWn8Qh8kRNxzt+Y3BpxyVgRkeST +30VcppXAv83B64oqklFFTr9BmOSsSXY1iKxcDUV+25TEkuCro1YwVDAOBgNVHQ8B +Af8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAc +BgNVHREEFTATgglsb2NhbGhvc3SCBnJvdXRlcjAKBggqhkjOPQQDAgNIADBFAiEA +sI4vft9oNO2iT5The9qOzgnM5UxIc/XPrQhpKMgELTwCIFn9pkIsZ0jeeb99uBdS +4MhSRxk4jgkBaWDPjCznaHVm +-----END CERTIFICATE----- diff --git a/.env/router/server.key b/.env/router/server.key new file mode 100644 index 000000000..620fc8d30 --- /dev/null +++ b/.env/router/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEWdn/Bs36aFUWfoswii0ASziiwZW2ctu9/1zn3sAR9JoAoGCCqGSM49 +AwEHoUQDQgAEK3G/V26Wz611p/EIfJETcc7fmNwacclYEZHkk99FXKaVwL/NweuK +KpJRRU6/QZjkrEl2NYisXA1FftuUxJLgqw== +-----END EC PRIVATE KEY----- diff --git a/.env/router/server.pub b/.env/router/server.pub new file mode 100644 index 000000000..6fb4f09d0 --- /dev/null +++ b/.env/router/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK3G/V26Wz611p/EIfJETcc7fmNwa +cclYEZHkk99FXKaVwL/NweuKKpJRRU6/QZjkrEl2NYisXA1FftuUxJLgqw== +-----END PUBLIC KEY----- diff --git a/.env/ttnctl.yml.dev-example b/.env/ttnctl.yml.dev-example new file mode 100644 index 000000000..3acc3ea2b --- /dev/null +++ b/.env/ttnctl.yml.dev-example @@ -0,0 +1,5 @@ +discovery-address: localhost:1900 +auth-server: https://preview.account.thethingsnetwork.org +router-id: dev +handler-id: dev +mqtt-address: localhost:1883 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..c44466f35 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,21 @@ +This is a **{bug report/feature request/question/...}** for **{the Backend/ttnctl/the Console/the NOC/an integration}**. + +- Explain what you want to do +- Explain what steps you took +- Explain what went wrong or what is missing + +## Environment + +- `ttn version` returns: `Commit={...}` +- `ttnctl version` returns: `Commit={...}` +- My application: + - App ID: `{...}` +- My gateway: + - EUI: `{...}` + - Connected to: `router.{...}.thethings.network:1700` +- My node: + - Device ID: `{...}` + - AppEUI: `{...}` + - DevEUI: `{...}` +- MQTT is connected to: `{...}.thethings.network:{1883/8883}` +- I am using the `{...}` library for the `{...}` programming language diff --git a/.gitignore b/.gitignore index daf913b1b..31e503fe1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,13 @@ _testmain.go *.exe *.test *.prof + +/release +/vendor/*/ +/.cover + +# Generated databases +*.db + +# Generate coverage profile +coverage.out diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..e038455e3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,113 @@ +stages: + - test + - build + - sign + - package + +variables: + CONTAINER_NAME: thethingsnetwork/ttn + +cache: + key: "$CI_PROJECT_PATH" + paths: + - .govendor + +before_script: + - mkdir -p $(pwd)/.govendor + - rm -rf $GOPATH + - mkdir -p $GOPATH/.cache && ln -s $(pwd)/.govendor $GOPATH/.cache/govendor + - mkdir -p $GOPATH/src/github.com/TheThingsNetwork && ln -s $(pwd) $GOPATH/src/github.com/TheThingsNetwork/ttn + +tests: + stage: test + image: golang:latest + services: + - thethingsnetwork/rabbitmq + - redis + variables: + REDIS_HOST: redis + MQTT_ADDRESS: thethingsnetwork__rabbitmq:1883 + AMQP_ADDRESS: thethingsnetwork__rabbitmq:5672 + script: + - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - make deps + - make test + - popd + +binaries: + stage: build + image: golang:latest + script: + - mkdir release + - export CI_BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + - echo "date $CI_BUILD_DATE" >> release/info + - echo "commit $CI_BUILD_REF" >> release/info + - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - make deps + - GOOS=linux GOARCH=386 make build + - GOOS=linux GOARCH=amd64 make build + - GOOS=linux GOARCH=arm make build + - GOOS=darwin GOARCH=amd64 make build + - GOOS=windows GOARCH=386 make build + - GOOS=windows GOARCH=amd64 make build + - popd + artifacts: + paths: + - release/ + +sign: + only: + - tags + - v1-staging@thethingsnetwork/ttn + - master@thethingsnetwork/ttn + stage: sign + image: golang:latest + script: + - pushd release + - shasum -a 256 $(ls) > checksums + - gpg --no-tty --batch --import /gpg/signing.ci.gpg-key + - gpg --no-tty --batch --no-use-agent --passphrase $GPG_PASSPHRASE --detach-sign checksums + - popd + artifacts: + paths: + - release/checksums + - release/checksums.sig + +gitlab-image: + stage: package + image: docker:git + services: + - "docker:dind" + script: + - docker build -t ttn . + - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" registry.gitlab.com + - docker tag ttn registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME + - docker push registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME + +dockerhub-image: + only: + - tags + - v1-staging@thethingsnetwork/ttn + - master@thethingsnetwork/ttn + stage: package + image: docker:git + services: + - "docker:dind" + script: + - docker build -t ttn . + - docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_PASSWORD" + - docker tag ttn $CONTAINER_NAME:$CI_BUILD_REF_NAME + - docker push $CONTAINER_NAME:$CI_BUILD_REF_NAME + - if [[ "$CI_BUILD_REF_NAME" == "master" ]]; then docker tag ttn $CONTAINER_NAME:latest && docker push $CONTAINER_NAME:latest; fi + +azure-binaries: + only: + - tags + - v1-staging@thethingsnetwork/ttn + - master@thethingsnetwork/ttn + stage: package + image: registry.gitlab.com/thethingsindustries/upload + script: + - cd release + - export STORAGE_CONTAINER=release STORAGE_KEY=$AZURE_STORAGE_KEY ZIP=true TGZ=true PREFIX=$CI_BUILD_REF_NAME/ + - upload * diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..8ea7fadac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: go + +sudo: required + +services: + - docker + +go: + - 1.7 + +install: + - make deps + - make cover-deps + +before_script: + - docker run -d -p 127.0.0.1:6379:6379 redis + - docker run -d -p 127.0.0.1:5672:5672 -p 127.0.0.1:1883:1883 thethingsnetwork/rabbitmq + +script: + - make test + - make fmt + - make vet + +after_success: + - make coveralls diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..68e1e9bc2 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,18 @@ +# This is the official list of The Things Network authors for copyright purposes. +# +# The copyright owners listed in this document agree to release their work under +# the MIT license that can be found in the LICENSE file. +# +# Names should be added to this file as +# Firstname Lastname +# +# Please keep the list sorted. + +Antoine Rondelet +Fokke Zandbergen +Hylke Visser +Johan Stokking +Matthias Benkort +Roman Volosatovs +Romeo Van Snick +Tobias Kaupat diff --git a/Brewfile b/Brewfile new file mode 100644 index 000000000..7cac5624b --- /dev/null +++ b/Brewfile @@ -0,0 +1,6 @@ +tap 'homebrew/bundle' + +brew 'go' +brew 'rabbitmq', restart_service: true +brew 'protobuf' +brew 'redis', restart_service: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..d4183019c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM alpine +RUN apk --update --no-cache add ca-certificates +ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn +RUN chmod 755 /usr/local/bin/ttn +ENTRYPOINT ["/usr/local/bin/ttn"] diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 000000000..63a72b1af --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,21 @@ +# History + +## 2.0.0 (2016-12-14) + +With the 2.0.0 release we now declare the v2 systems "out of preview". + +## v2-preview (2016-08-21) + +The v2-preview is a rewrite of almost all backend code, following a clear separation of concerns between components. Instead of using hard to read EUIs for applications and devices, you can now work with IDs that you can choose yourself. We added many new features and are sure that you'll love them. + +## v1-staging (2016-04-18) + +With the "staging" release we introduced device management, downlink messages, over the air activation, message encryption and MQTT feeds for receiving messages. + +![the command-line interface for managing devices](https://ttn.blob.core.windows.net/upload/ttnctl-staging.png) + +## v0-croft (2016-08-20) + +The day before the official launch of The Things Network, we sent our first text with an application built on The Things Network. + +![iPhone showing the first message sent over The Things Network](https://ttn.blob.core.windows.net/upload/slack_for_ios_upload_1024.jpg) diff --git a/LICENSE b/LICENSE index dceee0974..f2b059cd8 100644 --- a/LICENSE +++ b/LICENSE @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..fb5060531 --- /dev/null +++ b/Makefile @@ -0,0 +1,172 @@ +SHELL = bash + +# Environment + +GIT_BRANCH = $(or $(CI_BUILD_REF_NAME) ,`git rev-parse --abbrev-ref HEAD 2>/dev/null`) +GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) +GIT_TAG = $(shell git describe --abbrev=0 --tags) +BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) +GO_PATH = $(shell echo $(GOPATH) | awk -F':' '{print $$1}') +PARENT_DIRECTORY= $(shell dirname $(PWD)) +GO_SRC = $(shell pwd | xargs dirname | xargs dirname | xargs dirname) + +# All + +.PHONY: all build-deps deps dev-deps protos-clean protos protodoc mocks test cover-clean cover-deps cover coveralls fmt vet ttn ttnctl build link docs clean docker + +all: deps build + +# Deps + +build-deps: + @command -v govendor > /dev/null || go get "github.com/kardianos/govendor" + +deps: build-deps + govendor sync -v + +dev-deps: deps + @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast + @command -v protoc-gen-grpc-gateway > /dev/null || go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway + @command -v protoc-gen-ttndoc > /dev/null || go install github.com/TheThingsNetwork/ttn/utils/protoc-gen-ttndoc + @command -v mockgen > /dev/null || go get github.com/golang/mock/mockgen + @command -v golint > /dev/null || go get github.com/golang/lint/golint + @command -v forego > /dev/null || go get github.com/ddollar/forego + +# Protobuf + +PROTO_FILES = $(shell find api -name "*.proto" -and -not -name ".git") +COMPILED_PROTO_FILES = $(patsubst api%.proto, api%.pb.go, $(PROTO_FILES)) +PROTOC_IMPORTS= -I/usr/local/include -I$(GO_PATH)/src -I$(PARENT_DIRECTORY) \ +-I$(GO_PATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis +PROTOC = protoc $(PROTOC_IMPORTS) \ +--gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GO_SRC) \ +--grpc-gateway_out=:$(GO_SRC) `pwd`/ + +protos-clean: + rm -f $(COMPILED_PROTO_FILES) + +protos: $(COMPILED_PROTO_FILES) + +api/%.pb.go: api/%.proto + $(PROTOC)$< + +protodoc: $(PROTO_FILES) + protoc $(PROTOC_IMPORTS) --ttndoc_out=logtostderr=true,.handler.ApplicationManager=all:$(GO_SRC) `pwd`/api/handler/handler.proto + protoc $(PROTOC_IMPORTS) --ttndoc_out=logtostderr=true,.discovery.Discovery=all:$(GO_SRC) `pwd`/api/discovery/discovery.proto + +# Mocks + +mocks: + mockgen -source=./api/networkserver/networkserver.pb.go -package networkserver NetworkServerClient > api/networkserver/networkserver_mock.go + mockgen -source=./api/discovery/client.go -package discovery Client > api/discovery/client_mock.go + +# Go Test + +GO_FILES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor|.pb.go|_mock.go") +GO_PACKAGES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor" | sed 's:/[^/]*$$::' | sort | uniq) +GO_TEST_PACKAGES = $(shell find . -name "*_test.go" | grep -vE ".git|.env|vendor" | sed 's:/[^/]*$$::' | sort | uniq) +GO_COVER_PACKAGES = $(shell find . -name "*_test.go" | grep -vE ".git|.env|vendor|ttnctl|cmd|api" | sed 's:/[^/]*$$::' | sort | uniq) + +GO_COVER_FILE ?= coverage.out +GO_COVER_DIR ?= .cover +GO_COVER_FILES = $(patsubst ./%, $(GO_COVER_DIR)/%.out, $(shell echo "$(GO_COVER_PACKAGES)")) + +test: $(GO_FILES) + go test $(GO_TEST_PACKAGES) + +cover-clean: + rm -rf $(GO_COVER_DIR) $(GO_COVER_FILE) + +cover-deps: + @command -v goveralls > /dev/null || go get github.com/mattn/goveralls + +cover: $(GO_COVER_FILE) + +$(GO_COVER_FILE): cover-clean $(GO_COVER_FILES) + echo "mode: set" > $(GO_COVER_FILE) + cat $(GO_COVER_FILES) | grep -vE "mode: set|/server.go|/manager_server.go" | sort >> $(GO_COVER_FILE) + +$(GO_COVER_DIR)/%.out: % + @mkdir -p "$(GO_COVER_DIR)/$<" + go test -cover -coverprofile="$@" "./$<" + +coveralls: cover-deps $(GO_COVER_FILE) + goveralls -coverprofile=$(GO_COVER_FILE) -service=travis-ci -repotoken $$COVERALLS_TOKEN + +fmt: + [[ -z "`echo "$(GO_PACKAGES)" | xargs go fmt | tee -a /dev/stderr`" ]] + +vet: + echo $(GO_PACKAGES) | xargs go vet + +lint: + for pkg in `echo $(GO_PACKAGES)`; do golint $$pkg | grep -vE 'mock|.pb.go'; done + +# Go Build + +RELEASE_DIR ?= release +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) +GOEXE = $(shell GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE) +CGO_ENABLED ?= 0 + +ifeq ($(GIT_BRANCH), $(GIT_TAG)) + TTN_VERSION = $(GIT_TAG) +else + TTN_VERSION = $(GIT_TAG)-dev +endif + +DIST_FLAGS ?= -a -installsuffix cgo + +splitfilename = $(subst ., ,$(subst -, ,$(subst $(RELEASE_DIR)/,,$1))) +GOOSfromfilename = $(word 2, $(call splitfilename, $1)) +GOARCHfromfilename = $(word 3, $(call splitfilename, $1)) +LDFLAGS = -ldflags "-w -X main.version=${TTN_VERSION} -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" +GOBUILD = CGO_ENABLED=$(CGO_ENABLED) GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build $(DIST_FLAGS) ${LDFLAGS} -tags "${TAGS}" -o "$@" + +ttn: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) + +$(RELEASE_DIR)/ttn-%: $(GO_FILES) + $(GOBUILD) ./main.go + +ttnctl: $(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) + +$(RELEASE_DIR)/ttnctl-%: $(GO_FILES) + $(GOBUILD) ./ttnctl/main.go + +build: ttn ttnctl + +ttn-dev: DIST_FLAGS= +ttn-dev: CGO_ENABLED=1 +ttn-dev: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) + +ttnctl-dev: DIST_FLAGS= +ttnctl-dev: CGO_ENABLED=1 +ttnctl-dev: $(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) + +install: + go install -v . ./ttnctl + +dev: install ttn-dev ttnctl-dev + +GOBIN ?= $(GO_PATH)/bin + +link: build + ln -sf $(PWD)/$(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttn + ln -sf $(PWD)/$(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttnctl + +# Documentation + +docs: + cd cmd/docs && HOME='$$HOME' go run generate.go > README.md + cd ttnctl/cmd/docs && HOME='$$HOME' go run generate.go > README.md + +# Clean + +clean: + [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] + +docker: GOOS=linux +docker: GOARCH=amd64 +docker: $(RELEASE_DIR)/ttn-linux-amd64 + docker build -t thethingsnetwork/ttn -f Dockerfile . diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..8dd4221ea --- /dev/null +++ b/Procfile @@ -0,0 +1,5 @@ +rtr: ttn router --config ./.env/router/dev.yml --health-port 10901 +dsc: ttn discovery --config ./.env/discovery/dev.yml --health-port 10900 +ns: ttn networkserver --config ./.env/networkserver/dev.yml --health-port 10903 +brk: ttn broker --config ./.env/broker/dev.yml --health-port 10902 +hdl: ttn handler --config ./.env/handler/dev.yml --health-port 10904 diff --git a/README.md b/README.md new file mode 100644 index 000000000..2324188a8 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +The Things Network +================== + +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=master)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) [![Coverage Status](https://coveralls.io/repos/github/TheThingsNetwork/ttn/badge.svg?branch=master)](https://coveralls.io/github/TheThingsNetwork/ttn?branch=master) + +![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) + +The Things Network is a global open crowdsourced Internet of Things data network. + +## Getting Started With The Things Network + +When you get started with The Things Network, you'll probably have some questions. Here are some things you can do to find the answer to them: + +- Check out our [website](https://www.thethingsnetwork.org/) +- Read the [official documentation](https://www.thethingsnetwork.org/docs/) +- Register on the [forum](https://www.thethingsnetwork.org/forum/) and search around +- Join [Slack](https://slack.thethingsnetwork.org) and ask us what you want to know +- Read background information on the [wiki](https://www.thethingsnetwork.org/wiki/) + +## Prepare your Development Environment + +1. Make sure you have [Go](https://golang.org) installed (version 1.7 or later). +2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) +3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) +4. Install `make`. On Linux install `build-essential`. On macOS, `make` comes with XCode or the developer tools. On Windows you can get `make` from [https://gnuarmeclipse.github.io/windows-build-tools/](https://gnuarmeclipse.github.io/windows-build-tools/) +5. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. + On a fresh installation you might need to install the [MQTT plugin for RabbitMQ](https://www.rabbitmq.com/mqtt.html). + If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabbitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. +6. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` + +## Set up The Things Network's backend for Development + +1. Fork this repository +2. Clone your fork: `git clone https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` +3. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` +4. Install the dependencies for development: `make dev-deps` +5. Run the tests: `make test` +6. Run `make build` to build both `ttn` and `ttnctl` from source. +7. Run `make dev` to install the go binaries into `$GOPATH/bin/` + * Optionally on Linux or Mac you can use `make link` to link them to `$GOPATH/bin/` (In order to run the commands, you should have `export PATH="$GOPATH/bin:$PATH"` in your profile). +8. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. + +You can check your `ttnctl` configuration by running `ttnctl config`. It should look like this: + +``` + INFO Using config: + + config file: /home/your-user/.ttnctl.yml + data dir: /home/your-user/.ttnctl + + auth-server: https://preview.account.thethingsnetwork.org + discovery-address: localhost:1900 + router-id: dev + handler-id: dev + mqtt-address: localhost:1883 +``` + +**NOTE:** From now on you should run all commands from the `$GOPATH/src/github.com/TheThingsNetwork/ttn` directory. + +## Run The Things Network's backend locally + +- Set up the backend as described [above](#set-up-the-things-networks-backend-for-development). +- Run `forego start` to start all backend services at the same time. Make sure that Redis and RabbitMQ **are running** on your machine. +- First time only (or when Redis is flushed): + * Run `ttn broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` + * Restart the backend services + +## Build and run The Things Network's backend in Docker + +- Set up the backend as described [above](#set-up-the-things-networks-backend-for-development). +- Add the following line to your `/etc/hosts` file: + `127.0.0.1 router handler` +- Run `make docker` to build the docker image +- Run `docker-compose up` to start all backend services in Docker. Make sure that Redis and RabbitMQ **are not running** on your local machine, because they will be started by `docker-compose`. +- First time only (or when Redis is flushed): + * Run `docker-compose run broker broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` + * Restart the backend services + +## Contributing + +Source code for The Things Network is MIT licensed. We encourage users to make contributions on [Github](https://github.com/TheThingsNetwork/ttn) and to participate in discussions on [Slack](https://slack.thethingsnetwork.org). + +If you encounter any problems, please check [open issues](https://github.com/TheThingsNetwork/ttn/issues) before [creating a new issue](https://github.com/TheThingsNetwork/ttn/issues/new). Please be specific and give a detailed description of the issue. Explain the steps to reproduce the problem. If you're able to fix the issue yourself, please help the community by forking the repository and submitting a pull request with your fix. + +For contributing a feature, please open an issue that explains what you're working on. Work in your own fork of the repository and submit a pull request when you're done. + +If you want to contribute, but don't know where to start, you could have a look at issues with the label [*help wanted*](https://github.com/TheThingsNetwork/ttn/labels/help%20wanted) or [*difficulty/easy*](https://github.com/TheThingsNetwork/ttn/labels/difficulty%2Feasy). + +## License + +Source code for The Things Network is released under the MIT License, which can be found in the [LICENSE](LICENSE) file. A list of authors can be found in the [AUTHORS](AUTHORS) file. diff --git a/amqp/client.go b/amqp/client.go new file mode 100644 index 000000000..e61e0df1e --- /dev/null +++ b/amqp/client.go @@ -0,0 +1,199 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + "io" + "sync" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + AMQP "github.com/streadway/amqp" +) + +// Client connects to an AMQP server +type Client interface { + Connect() error + Disconnect() + IsConnected() bool + + NewPublisher(exchange string) Publisher + NewSubscriber(exchange, name string, durable, autoDelete bool) Subscriber +} + +// DefaultClient is the default AMQP client for The Things Network +type DefaultClient struct { + url string + ctx log.Interface + conn *AMQP.Connection + mutex *sync.Mutex + channels map[*DefaultChannelClient]*AMQP.Channel +} + +// ChannelClient represents an AMQP channel client +type ChannelClient interface { + Open() error + io.Closer +} + +// DefaultChannelClient represents the default client of an AMQP channel +type DefaultChannelClient struct { + ctx log.Interface + client *DefaultClient + channel *AMQP.Channel + name string + exchange string + exchangeType string +} + +var ( + // ConnectRetries says how many times the client should retry a failed connection + ConnectRetries = 10 + // ConnectRetryDelay says how long the client should wait between retries + ConnectRetryDelay = time.Second +) + +// NewClient creates a new DefaultClient +func NewClient(ctx log.Interface, username, password, host string) Client { + if ctx == nil { + ctx = log.Get() + } + credentials := "guest:guest" + if username != "" { + if password != "" { + credentials = fmt.Sprintf("%s:%s", username, password) + } else { + credentials = username + } + } + return &DefaultClient{ + ctx: ctx, + url: fmt.Sprintf("amqp://%s@%s", credentials, host), + mutex: &sync.Mutex{}, + channels: make(map[*DefaultChannelClient]*AMQP.Channel), + } +} + +func (c *DefaultClient) connect(reconnect bool) (chan *AMQP.Error, error) { + var err error + var conn *AMQP.Connection + for retries := 0; reconnect || retries < ConnectRetries; retries++ { + conn, err = AMQP.Dial(c.url) + if err == nil { + break + } + c.ctx.Warnf("Could not connect to AMQP server (%s). Retry attempt %d, reconnect is %v...", err.Error(), retries+1, reconnect) + <-time.After(ConnectRetryDelay) + } + if err != nil { + return nil, fmt.Errorf("Could not connect to AMQP server (%s)", err) + } + + closed := make(chan *AMQP.Error) + conn.NotifyClose(closed) + go func() { + err := <-closed + if err != nil { + c.ctx.Warnf("Connection closed (%s). Reconnecting...", err) + c.connect(true) + } else { + c.ctx.Info("Connection closed") + } + }() + + c.conn = conn + c.ctx.Info("Connected to AMQP") + + if reconnect { + c.mutex.Lock() + defer c.mutex.Unlock() + for user, channel := range c.channels { + channel.Close() + channel, err = c.conn.Channel() + if err != nil { + c.ctx.Warnf("Failed to reopen channel %s for %s (%s)", user.name, user.exchange, err) + continue + } + c.ctx.Infof("Reopened channel %s for %s", user.name, user.exchange) + user.channel = channel + c.channels[user] = channel + } + } + + return closed, nil +} + +// Connect to the AMQP server. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +func (c *DefaultClient) Connect() error { + _, err := c.connect(false) + return err +} + +// Disconnect from the AMQP server +func (c *DefaultClient) Disconnect() { + if !c.IsConnected() { + return + } + + c.ctx.Debug("Disconnecting from AMQP") + + c.mutex.Lock() + defer c.mutex.Unlock() + for user, channel := range c.channels { + channel.Close() + delete(c.channels, user) + } + + c.conn.Close() + c.conn = nil +} + +// IsConnected returns true if there is a connection to the AMQP server. +func (c *DefaultClient) IsConnected() bool { + return c.conn != nil +} + +func (c *DefaultClient) openChannel(u *DefaultChannelClient) (*AMQP.Channel, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + channel, err := c.conn.Channel() + if err != nil { + return nil, err + } + c.channels[u] = channel + + return channel, nil +} + +func (c *DefaultClient) closeChannel(u *DefaultChannelClient) error { + channel, ok := c.channels[u] + if !ok { + return nil + } + err := channel.Close() + + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.channels, u) + + return err +} + +// Open opens a new channel and declares the exchange +func (p *DefaultChannelClient) Open() error { + channel, err := p.client.openChannel(p) + if err != nil { + return fmt.Errorf("Could not open AMQP channel (%s)", err) + } + + p.channel = channel + return nil +} + +// Close closes the channel +func (p *DefaultChannelClient) Close() error { + return p.client.closeChannel(p) +} diff --git a/amqp/client_test.go b/amqp/client_test.go new file mode 100644 index 000000000..ea96cb141 --- /dev/null +++ b/amqp/client_test.go @@ -0,0 +1,119 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "os" + "testing" + "time" + + . "github.com/smartystreets/assertions" + AMQP "github.com/streadway/amqp" +) + +var host string + +func init() { + host = os.Getenv("AMQP_ADDRESS") + if host == "" { + host = "localhost:5672" + } +} + +func TestNewClient(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestNewClient"), "guest", "guest", host) + a.So(c, ShouldNotBeNil) +} + +func TestConnect(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestConnect"), "guest", "guest", host) + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) + + // Connecting while already connected should not change anything + err = c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) +} + +func TestConnectInvalidAddress(t *testing.T) { + a := New(t) + ConnectRetries = 2 + ConnectRetryDelay = 50 * time.Millisecond + c := NewClient(getLogger(t, "TestConnectInvalidAddress"), "guest", "guest", "localhost:56720") + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldNotBeNil) +} + +func TestIsConnected(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestIsConnected"), "guest", "guest", host) + + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + + a.So(c.IsConnected(), ShouldBeTrue) +} + +func TestDisconnect(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestDisconnect"), "guest", "guest", host) + + // Disconnecting when not connected should not change anything + c.Disconnect() + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + c.Disconnect() + + a.So(c.IsConnected(), ShouldBeFalse) +} + +func TestReopenChannelClient(t *testing.T) { + a := New(t) + ctx := getLogger(t, "TestReopenChannelClient") + c := NewClient(ctx, "guest", "guest", host).(*DefaultClient) + closed, err := c.connect(false) + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := &DefaultChannelClient{ + ctx: ctx, + client: c, + } + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + test := func() error { + ctx.Debug("Testing publish") + return p.channel.Publish("", "test", false, false, AMQP.Publishing{ + Body: []byte("test"), + }) + } + + // First attempt should be OK + err = test() + a.So(err, ShouldBeNil) + + // Make sure that the old channel is closed + p.channel.Close() + + // Simulate a connection close so a new channel should be opened + closed <- AMQP.ErrClosed + + // Give the reconnect some time + time.Sleep(100 * time.Millisecond) + + // Second attempt should be OK as well and will only work on a new channel + err = test() + a.So(err, ShouldBeNil) +} diff --git a/amqp/downlink.go b/amqp/downlink.go new file mode 100644 index 000000000..f22f8a626 --- /dev/null +++ b/amqp/downlink.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// DownlinkHandler is called for downlink messages +type DownlinkHandler func(subscriber Subscriber, appID string, devID string, req types.DownlinkMessage) + +// PublishDownlink publishes a downlink message to the AMQP broker +func (c *DefaultPublisher) PublishDownlink(dataDown types.DownlinkMessage) error { + key := DeviceKey{dataDown.AppID, dataDown.DevID, DeviceDownlink, ""} + msg, err := json.Marshal(dataDown) + if err != nil { + return fmt.Errorf("Unable to marshal the message payload") + } + return c.publish(key.String(), msg, time.Now()) +} + +// SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device +func (s *DefaultSubscriber) SubscribeDeviceDownlink(appID, devID string, handler DownlinkHandler) error { + key := DeviceKey{appID, devID, DeviceDownlink, ""} + messages, err := s.subscribe(key.String()) + if err != nil { + return err + } + + go func() { + for delivery := range messages { + dataDown := &types.DownlinkMessage{} + err := json.Unmarshal(delivery.Body, dataDown) + if err != nil { + s.ctx.Warnf("Could not unmarshal downlink %v (%s)", delivery, err) + continue + } + handler(s, dataDown.AppID, dataDown.DevID, *dataDown) + delivery.Ack(false) + break + } + }() + + return nil +} + +// SubscribeAppDownlink subscribes to all downlink messages for the given application +func (s *DefaultSubscriber) SubscribeAppDownlink(appID string, handler DownlinkHandler) error { + return s.SubscribeDeviceDownlink(appID, "", handler) +} + +// SubscribeDownlink subscribes to all downlink messages that the current user has access to +func (s *DefaultSubscriber) SubscribeDownlink(handler DownlinkHandler) error { + return s.SubscribeDeviceDownlink("", "", handler) +} diff --git a/amqp/downlink_test.go b/amqp/downlink_test.go new file mode 100644 index 000000000..b1932cde9 --- /dev/null +++ b/amqp/downlink_test.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "sync" + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestPublishDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestPublishDownlink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + err = p.PublishDownlink(types.DownlinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) +} + +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestSubscribeDownlink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("amq.topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + s := c.NewSubscriber("amq.topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() + + wg := &sync.WaitGroup{} + wg.Add(1) + err = s.SubscribeDownlink(func(_ Subscriber, appID, devID string, req types.DownlinkMessage) { + a.So(appID, ShouldEqual, "app") + a.So(devID, ShouldEqual, "test") + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x08}) + wg.Done() + }) + a.So(err, ShouldBeNil) + + err = p.PublishDownlink(types.DownlinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) + + wg.Wait() +} diff --git a/amqp/publisher.go b/amqp/publisher.go new file mode 100644 index 000000000..478f1dde3 --- /dev/null +++ b/amqp/publisher.go @@ -0,0 +1,45 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + AMQP "github.com/streadway/amqp" +) + +// Publisher represents a publisher for uplink messages +type Publisher interface { + ChannelClient + + PublishUplink(dataUp types.UplinkMessage) error + PublishDownlink(dataDown types.DownlinkMessage) error +} + +// DefaultPublisher represents the default AMQP publisher +type DefaultPublisher struct { + DefaultChannelClient +} + +// NewPublisher returns a new topic publisher on the specified exchange +func (c *DefaultClient) NewPublisher(exchange string) Publisher { + return &DefaultPublisher{ + DefaultChannelClient: DefaultChannelClient{ + ctx: c.ctx, + client: c, + exchange: exchange, + name: "Publisher", + }, + } +} + +func (p *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) error { + return p.channel.Publish(p.exchange, key, false, false, AMQP.Publishing{ + ContentType: "application/json", + DeliveryMode: AMQP.Persistent, + Timestamp: timestamp, + Body: msg, + }) +} diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go new file mode 100644 index 000000000..5732a2cbb --- /dev/null +++ b/amqp/publisher_test.go @@ -0,0 +1,23 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestOpenPublisher(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestOpenPublisher"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("amq.topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() +} diff --git a/amqp/routing_keys.go b/amqp/routing_keys.go new file mode 100644 index 000000000..8888c5881 --- /dev/null +++ b/amqp/routing_keys.go @@ -0,0 +1,121 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + "regexp" + "strings" +) + +const simpleWildcard = "*" +const wildard = "#" + +// DeviceKeyType represents the type of a device topic +type DeviceKeyType string + +// Topic types for Devices +const ( + DeviceEvents DeviceKeyType = "events" + DeviceUplink DeviceKeyType = "up" + DeviceDownlink DeviceKeyType = "down" +) + +// DeviceKey represents an AMQP routing key for devices +type DeviceKey struct { + AppID string + DevID string + Type DeviceKeyType + Field string +} + +// ParseDeviceKey parses an AMQP device routing key string to a DeviceKey struct +func ParseDeviceKey(key string) (*DeviceKey, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(devices)\\.([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(events|up|down)([0-9a-z\\.]+)?$") + matches := pattern.FindStringSubmatch(key) + if len(matches) < 4 { + return nil, fmt.Errorf("Invalid key format") + } + var appID string + if matches[1] != simpleWildcard { + appID = matches[1] + } + var devID string + if matches[3] != simpleWildcard { + devID = matches[3] + } + keyType := DeviceKeyType(matches[4]) + deviceKey := &DeviceKey{appID, devID, keyType, ""} + if keyType == DeviceEvents && len(matches) > 4 { + deviceKey.Field = strings.Trim(matches[5], ".") + } + return deviceKey, nil +} + +// String implements the Stringer interface +func (t DeviceKey) String() string { + appID := simpleWildcard + if t.AppID != "" { + appID = t.AppID + } + devID := simpleWildcard + if t.DevID != "" { + devID = t.DevID + } + key := fmt.Sprintf("%s.%s.%s.%s", appID, "devices", devID, t.Type) + if t.Type == DeviceEvents && t.Field != "" { + key += "." + t.Field + } + return key +} + +// ApplicationKeyType represents an AMQP application routing key +type ApplicationKeyType string + +// Topic types for Applications +const ( + AppEvents ApplicationKeyType = "events" +) + +// ApplicationKey represents an AMQP topic for applications +type ApplicationKey struct { + AppID string + Type ApplicationKeyType + Field string +} + +// ParseApplicationKey parses an AMQP application routing key string to an ApplicationKey struct +func ParseApplicationKey(key string) (*ApplicationKey, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(events)([0-9a-z\\.-]+|\\.#)?$") + matches := pattern.FindStringSubmatch(key) + if len(matches) < 2 { + return nil, fmt.Errorf("Invalid key format") + } + var appID string + if matches[1] != simpleWildcard { + appID = matches[1] + } + keyType := ApplicationKeyType(matches[2]) + appKey := &ApplicationKey{appID, keyType, ""} + if keyType == AppEvents && len(matches) > 2 { + appKey.Field = strings.Trim(matches[3], ".") + } + return appKey, nil +} + +// String implements the Stringer interface +func (t ApplicationKey) String() string { + appID := simpleWildcard + if t.AppID != "" { + appID = t.AppID + } + if t.Type == AppEvents && t.Field == "" { + t.Field = wildard + } + key := fmt.Sprintf("%s.%s", appID, t.Type) + if t.Type == AppEvents && t.Field != "" { + key += "." + t.Field + } + return key +} diff --git a/amqp/routing_keys_test.go b/amqp/routing_keys_test.go new file mode 100644 index 000000000..901245f60 --- /dev/null +++ b/amqp/routing_keys_test.go @@ -0,0 +1,153 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestParseDeviceKey(t *testing.T) { + a := New(t) + + key := "appid-1.devices.devid-1.up" + + expected := &DeviceKey{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceUplink, + } + + got, err := ParseDeviceKey(key) + + a.So(err, ShouldBeNil) + a.So(got, ShouldResemble, expected) +} + +func TestParseDeviceKeyInvalid(t *testing.T) { + a := New(t) + + _, err := ParseDeviceKey("appid:Invalid.devices.dev.up") + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceKey("appid-1.devices.devid:Invalid.up") // DevID contains hex chars + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceKey("appid-1.fridges.devid-1.up") // We don't support fridges (at least, not specifically fridges) + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceKey("appid-1.devices.devid-1.emotions") // Devices usually don't publish emotions + a.So(err, ShouldNotBeNil) +} + +func TestDeviceKeyString(t *testing.T) { + a := New(t) + + key := &DeviceKey{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceDownlink, + } + + expected := "appid-1.devices.devid-1.down" + + got := key.String() + + a.So(got, ShouldResemble, expected) +} + +func TestDeviceKeyParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + // Uppercase (not lowercase) + "0102030405060708.devices.abcdabcd12345678.up", + "0102030405060708.devices.abcdabcd12345678.down", + "0102030405060708.devices.abcdabcd12345678.events.activations", + // Numbers + "0102030405060708.devices.0000000012345678.up", + "0102030405060708.devices.0000000012345678.down", + "0102030405060708.devices.0000000012345678.events.activations", + // Wildcards + "*.devices.*.up", + "*.devices.*.down", + "*.devices.*.events.activations", + // Not Wildcard + "0102030405060708.devices.0100000000000000.up", + "0102030405060708.devices.0100000000000000.down", + "0102030405060708.devices.0100000000000000.events.activations", + } + + for _, expected := range expectedList { + key, err := ParseDeviceKey(expected) + a.So(err, ShouldBeNil) + a.So(key.String(), ShouldEqual, expected) + } +} + +func TestParseAppKey(t *testing.T) { + a := New(t) + + key := "appid-1.devices.devid-1.up" + + expected := &DeviceKey{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceUplink, + } + + got, err := ParseDeviceKey(key) + + a.So(err, ShouldBeNil) + a.So(got, ShouldResemble, expected) +} + +func TestParseAppKeyInvalid(t *testing.T) { + a := New(t) + + _, err := ParseApplicationKey("appid:Invalid.events") + a.So(err, ShouldNotBeNil) + + _, err = ParseApplicationKey("appid.randomstuff") + a.So(err, ShouldNotBeNil) +} + +func TestAppKeyString(t *testing.T) { + a := New(t) + + key := &ApplicationKey{ + AppID: "appid-1", + Type: AppEvents, + } + + a.So(key.String(), ShouldResemble, "appid-1.events.#") + + key = &ApplicationKey{ + AppID: "appid-1", + Type: AppEvents, + Field: "err", + } + + a.So(key.String(), ShouldResemble, "appid-1.events.err") +} + +func TestAppKeyParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + "*.events.#", + "appid.events.#", + "*.events.some-event", + "appid.events.some-event", + "*.events.some.event", + "appid.events.some.event", + } + + for _, expected := range expectedList { + key, err := ParseApplicationKey(expected) + a.So(err, ShouldBeNil) + a.So(key.String(), ShouldEqual, expected) + } +} diff --git a/amqp/subscriber.go b/amqp/subscriber.go new file mode 100644 index 000000000..8b84eef0f --- /dev/null +++ b/amqp/subscriber.go @@ -0,0 +1,73 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + + AMQP "github.com/streadway/amqp" +) + +var ( + // PrefetchCount represents the number of messages to prefetch before the AMQP server requires acknowledgment + PrefetchCount = 3 + // PrefetchSize represents the number of bytes to prefetch before the AMQP server requires acknowledgment + PrefetchSize = 0 +) + +// Subscriber represents a subscriber for uplink messages +type Subscriber interface { + ChannelClient + + SubscribeDeviceUplink(appID, devID string, handler UplinkHandler) error + SubscribeAppUplink(appID string, handler UplinkHandler) error + SubscribeUplink(handler UplinkHandler) error + + SubscribeDeviceDownlink(appID, devID string, handler DownlinkHandler) error + SubscribeAppDownlink(appID string, handler DownlinkHandler) error + SubscribeDownlink(handler DownlinkHandler) error +} + +// DefaultSubscriber represents the default AMQP subscriber +type DefaultSubscriber struct { + DefaultChannelClient + + name string + durable bool + autoDelete bool +} + +// NewSubscriber returns a new topic subscriber on the specified exchange +func (c *DefaultClient) NewSubscriber(exchange, name string, durable, autoDelete bool) Subscriber { + return &DefaultSubscriber{ + DefaultChannelClient: DefaultChannelClient{ + ctx: c.ctx, + client: c, + exchange: exchange, + name: "Subscriber", + }, + name: name, + durable: durable, + autoDelete: autoDelete, + } +} + +func (s *DefaultSubscriber) subscribe(key string) (<-chan AMQP.Delivery, error) { + queue, err := s.channel.QueueDeclare(s.name, s.durable, s.autoDelete, false, false, nil) + if err != nil { + return nil, fmt.Errorf("Failed to declare queue '%s' (%s)", s.name, err) + } + + err = s.channel.QueueBind(queue.Name, key, s.exchange, false, nil) + if err != nil { + return nil, fmt.Errorf("Failed to bind queue %s with key %s on exchange '%s' (%s)", queue.Name, key, s.exchange, err) + } + + err = s.channel.Qos(PrefetchCount, PrefetchSize, false) + if err != nil { + return nil, fmt.Errorf("Failed to set channel QoS (%s)", err) + } + + return s.channel.Consume(queue.Name, "", false, false, false, false, nil) +} diff --git a/amqp/subscriber_test.go b/amqp/subscriber_test.go new file mode 100644 index 000000000..18c394e49 --- /dev/null +++ b/amqp/subscriber_test.go @@ -0,0 +1,23 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestOpenSubscriber(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestOpenSubscriber"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + s := c.NewSubscriber("amq.topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() +} diff --git a/amqp/uplink.go b/amqp/uplink.go new file mode 100644 index 000000000..a5f1c2991 --- /dev/null +++ b/amqp/uplink.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// UplinkHandler is called for uplink messages +type UplinkHandler func(subscriber Subscriber, appID string, devID string, req types.UplinkMessage) + +// PublishUplink publishes an uplink message to the AMQP broker +func (c *DefaultPublisher) PublishUplink(dataUp types.UplinkMessage) error { + key := DeviceKey{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} + msg, err := json.Marshal(dataUp) + if err != nil { + return fmt.Errorf("Unable to marshal the message payload") + } + return c.publish(key.String(), msg, time.Time(dataUp.Metadata.Time)) +} + +// SubscribeDeviceUplink subscribes to all uplink messages for the given application and device +func (s *DefaultSubscriber) SubscribeDeviceUplink(appID, devID string, handler UplinkHandler) error { + key := DeviceKey{appID, devID, DeviceUplink, ""} + messages, err := s.subscribe(key.String()) + if err != nil { + return err + } + + go func() { + for delivery := range messages { + dataUp := &types.UplinkMessage{} + if err := json.Unmarshal(delivery.Body, dataUp); err != nil { + s.ctx.Warnf("Could not unmarshal uplink (%s)", err) + continue + } + handler(s, dataUp.AppID, dataUp.DevID, *dataUp) + if err := delivery.Ack(false); err != nil { + s.ctx.Warnf("Could not acknowledge message (%s)", err) + } + } + }() + + return nil +} + +// SubscribeAppUplink subscribes to all uplink messages for the given application +func (s *DefaultSubscriber) SubscribeAppUplink(appID string, handler UplinkHandler) error { + return s.SubscribeDeviceUplink(appID, "", handler) +} + +// SubscribeUplink subscribes to all uplink messages that the current user has access to +func (s *DefaultSubscriber) SubscribeUplink(handler UplinkHandler) error { + return s.SubscribeDeviceUplink("", "", handler) +} diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go new file mode 100644 index 000000000..fd8f8a422 --- /dev/null +++ b/amqp/uplink_test.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "sync" + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestPublishUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestPublishUplink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + err = p.PublishUplink(types.UplinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) +} + +func TestSubscribeUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "TestSubscribeUplink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("amq.topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + s := c.NewSubscriber("amq.topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() + + wg := &sync.WaitGroup{} + wg.Add(1) + err = s.SubscribeUplink(func(_ Subscriber, appID, devID string, req types.UplinkMessage) { + a.So(appID, ShouldEqual, "app") + a.So(devID, ShouldEqual, "test") + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x08}) + wg.Done() + }) + a.So(err, ShouldBeNil) + + err = p.PublishUplink(types.UplinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) + + wg.Wait() +} diff --git a/amqp/utils_test.go b/amqp/utils_test.go new file mode 100644 index 000000000..021a33c7a --- /dev/null +++ b/amqp/utils_test.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + tt "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func getLogger(t *testing.T, tag string) log.Interface { + return apex.Wrap(tt.GetLogger(t, tag)) +} diff --git a/api/API_AUTHENTICATION.md b/api/API_AUTHENTICATION.md new file mode 100644 index 000000000..f91bcc32b --- /dev/null +++ b/api/API_AUTHENTICATION.md @@ -0,0 +1,60 @@ +# Authentication + +Currently, there are two methods of authenticating to the gRPC and HTTP APIs: + +- Bearer token: OAuth 2.0 Bearer JSON Web Tokens (preferred) +- Access keys: Application access keys (only for `ApplicationManager` API) + +## Bearer Token + +This authentication method is the preferred method of authenticating. + +### gRPC + +You can authenticate to the gRPC endpoint by supplying a `token` field in the Metadata. The value of this field should be the JSON Web Token. + +**Example (Go):** + +```go +md := metadata.Pairs( + "token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl", +) +ctx := metadata.NewContext(context.Background(), md) +``` + +### HTTP + +For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Bearer `. + +**Example:** + +``` +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl +``` + +## Access Key + +With this authentication method, the server will exchange an Access Key for a Bearer Token internally. + +### gRPC + +You can authenticate to the gRPC endpoint by supplying a `key` field in the Metadata. The value of this field should be the Application access key. + +**Example (Go):** + +```go +md := metadata.Pairs( + "key", "ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ", +) +ctx := metadata.NewContext(context.Background(), md) +``` + +### HTTP + +For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Key `. + +**Example:** + +``` +Authorization: Key ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ +``` diff --git a/api/api.pb.go b/api/api.pb.go new file mode 100644 index 000000000..05ab56ca2 --- /dev/null +++ b/api/api.pb.go @@ -0,0 +1,1945 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/api.proto +// DO NOT EDIT! + +/* + Package api is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/api.proto + + It has these top-level messages: + Percentiles + Rates + SystemStats + ComponentStats +*/ +package api + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Percentiles struct { + Percentile1 float32 `protobuf:"fixed32,1,opt,name=percentile1,proto3" json:"percentile1,omitempty"` + Percentile5 float32 `protobuf:"fixed32,2,opt,name=percentile5,proto3" json:"percentile5,omitempty"` + Percentile10 float32 `protobuf:"fixed32,3,opt,name=percentile10,proto3" json:"percentile10,omitempty"` + Percentile25 float32 `protobuf:"fixed32,4,opt,name=percentile25,proto3" json:"percentile25,omitempty"` + Percentile50 float32 `protobuf:"fixed32,5,opt,name=percentile50,proto3" json:"percentile50,omitempty"` + Percentile75 float32 `protobuf:"fixed32,6,opt,name=percentile75,proto3" json:"percentile75,omitempty"` + Percentile90 float32 `protobuf:"fixed32,7,opt,name=percentile90,proto3" json:"percentile90,omitempty"` + Percentile95 float32 `protobuf:"fixed32,8,opt,name=percentile95,proto3" json:"percentile95,omitempty"` + Percentile99 float32 `protobuf:"fixed32,9,opt,name=percentile99,proto3" json:"percentile99,omitempty"` +} + +func (m *Percentiles) Reset() { *m = Percentiles{} } +func (m *Percentiles) String() string { return proto.CompactTextString(m) } +func (*Percentiles) ProtoMessage() {} +func (*Percentiles) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{0} } + +type Rates struct { + Rate1 float32 `protobuf:"fixed32,1,opt,name=rate1,proto3" json:"rate1,omitempty"` + Rate5 float32 `protobuf:"fixed32,2,opt,name=rate5,proto3" json:"rate5,omitempty"` + Rate15 float32 `protobuf:"fixed32,3,opt,name=rate15,proto3" json:"rate15,omitempty"` +} + +func (m *Rates) Reset() { *m = Rates{} } +func (m *Rates) String() string { return proto.CompactTextString(m) } +func (*Rates) ProtoMessage() {} +func (*Rates) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} } + +type SystemStats struct { + Load *SystemStats_Loadstats `protobuf:"bytes,1,opt,name=load" json:"load,omitempty"` + Cpu *SystemStats_CPUStats `protobuf:"bytes,2,opt,name=cpu" json:"cpu,omitempty"` + Memory *SystemStats_MemoryStats `protobuf:"bytes,3,opt,name=memory" json:"memory,omitempty"` +} + +func (m *SystemStats) Reset() { *m = SystemStats{} } +func (m *SystemStats) String() string { return proto.CompactTextString(m) } +func (*SystemStats) ProtoMessage() {} +func (*SystemStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} } + +func (m *SystemStats) GetLoad() *SystemStats_Loadstats { + if m != nil { + return m.Load + } + return nil +} + +func (m *SystemStats) GetCpu() *SystemStats_CPUStats { + if m != nil { + return m.Cpu + } + return nil +} + +func (m *SystemStats) GetMemory() *SystemStats_MemoryStats { + if m != nil { + return m.Memory + } + return nil +} + +type SystemStats_Loadstats struct { + Load1 float32 `protobuf:"fixed32,1,opt,name=load1,proto3" json:"load1,omitempty"` + Load5 float32 `protobuf:"fixed32,2,opt,name=load5,proto3" json:"load5,omitempty"` + Load15 float32 `protobuf:"fixed32,3,opt,name=load15,proto3" json:"load15,omitempty"` +} + +func (m *SystemStats_Loadstats) Reset() { *m = SystemStats_Loadstats{} } +func (m *SystemStats_Loadstats) String() string { return proto.CompactTextString(m) } +func (*SystemStats_Loadstats) ProtoMessage() {} +func (*SystemStats_Loadstats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 0} } + +type SystemStats_CPUStats struct { + User float32 `protobuf:"fixed32,1,opt,name=user,proto3" json:"user,omitempty"` + System float32 `protobuf:"fixed32,2,opt,name=system,proto3" json:"system,omitempty"` + Idle float32 `protobuf:"fixed32,3,opt,name=idle,proto3" json:"idle,omitempty"` +} + +func (m *SystemStats_CPUStats) Reset() { *m = SystemStats_CPUStats{} } +func (m *SystemStats_CPUStats) String() string { return proto.CompactTextString(m) } +func (*SystemStats_CPUStats) ProtoMessage() {} +func (*SystemStats_CPUStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 1} } + +type SystemStats_MemoryStats struct { + Total uint64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + Available uint64 `protobuf:"varint,2,opt,name=available,proto3" json:"available,omitempty"` + Used uint64 `protobuf:"varint,3,opt,name=used,proto3" json:"used,omitempty"` +} + +func (m *SystemStats_MemoryStats) Reset() { *m = SystemStats_MemoryStats{} } +func (m *SystemStats_MemoryStats) String() string { return proto.CompactTextString(m) } +func (*SystemStats_MemoryStats) ProtoMessage() {} +func (*SystemStats_MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 2} } + +type ComponentStats struct { + Uptime uint64 `protobuf:"varint,1,opt,name=uptime,proto3" json:"uptime,omitempty"` + Cpu *ComponentStats_CPUStats `protobuf:"bytes,2,opt,name=cpu" json:"cpu,omitempty"` + Memory *ComponentStats_MemoryStats `protobuf:"bytes,3,opt,name=memory" json:"memory,omitempty"` + Goroutines uint64 `protobuf:"varint,4,opt,name=goroutines,proto3" json:"goroutines,omitempty"` + GcCpuFraction float32 `protobuf:"fixed32,5,opt,name=gc_cpu_fraction,json=gcCpuFraction,proto3" json:"gc_cpu_fraction,omitempty"` +} + +func (m *ComponentStats) Reset() { *m = ComponentStats{} } +func (m *ComponentStats) String() string { return proto.CompactTextString(m) } +func (*ComponentStats) ProtoMessage() {} +func (*ComponentStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3} } + +func (m *ComponentStats) GetCpu() *ComponentStats_CPUStats { + if m != nil { + return m.Cpu + } + return nil +} + +func (m *ComponentStats) GetMemory() *ComponentStats_MemoryStats { + if m != nil { + return m.Memory + } + return nil +} + +type ComponentStats_CPUStats struct { + User float32 `protobuf:"fixed32,1,opt,name=user,proto3" json:"user,omitempty"` + System float32 `protobuf:"fixed32,2,opt,name=system,proto3" json:"system,omitempty"` + Idle float32 `protobuf:"fixed32,3,opt,name=idle,proto3" json:"idle,omitempty"` +} + +func (m *ComponentStats_CPUStats) Reset() { *m = ComponentStats_CPUStats{} } +func (m *ComponentStats_CPUStats) String() string { return proto.CompactTextString(m) } +func (*ComponentStats_CPUStats) ProtoMessage() {} +func (*ComponentStats_CPUStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3, 0} } + +type ComponentStats_MemoryStats struct { + Memory uint64 `protobuf:"varint,1,opt,name=memory,proto3" json:"memory,omitempty"` + Swap uint64 `protobuf:"varint,2,opt,name=swap,proto3" json:"swap,omitempty"` +} + +func (m *ComponentStats_MemoryStats) Reset() { *m = ComponentStats_MemoryStats{} } +func (m *ComponentStats_MemoryStats) String() string { return proto.CompactTextString(m) } +func (*ComponentStats_MemoryStats) ProtoMessage() {} +func (*ComponentStats_MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3, 1} } + +func init() { + proto.RegisterType((*Percentiles)(nil), "api.Percentiles") + proto.RegisterType((*Rates)(nil), "api.Rates") + proto.RegisterType((*SystemStats)(nil), "api.SystemStats") + proto.RegisterType((*SystemStats_Loadstats)(nil), "api.SystemStats.Loadstats") + proto.RegisterType((*SystemStats_CPUStats)(nil), "api.SystemStats.CPUStats") + proto.RegisterType((*SystemStats_MemoryStats)(nil), "api.SystemStats.MemoryStats") + proto.RegisterType((*ComponentStats)(nil), "api.ComponentStats") + proto.RegisterType((*ComponentStats_CPUStats)(nil), "api.ComponentStats.CPUStats") + proto.RegisterType((*ComponentStats_MemoryStats)(nil), "api.ComponentStats.MemoryStats") +} +func (m *Percentiles) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Percentiles) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Percentile1 != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile1)))) + } + if m.Percentile5 != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile5)))) + } + if m.Percentile10 != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile10)))) + } + if m.Percentile25 != 0 { + dAtA[i] = 0x25 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile25)))) + } + if m.Percentile50 != 0 { + dAtA[i] = 0x2d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile50)))) + } + if m.Percentile75 != 0 { + dAtA[i] = 0x35 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile75)))) + } + if m.Percentile90 != 0 { + dAtA[i] = 0x3d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile90)))) + } + if m.Percentile95 != 0 { + dAtA[i] = 0x45 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile95)))) + } + if m.Percentile99 != 0 { + dAtA[i] = 0x4d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile99)))) + } + return i, nil +} + +func (m *Rates) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Rates) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Rate1 != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Rate1)))) + } + if m.Rate5 != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Rate5)))) + } + if m.Rate15 != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Rate15)))) + } + return i, nil +} + +func (m *SystemStats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SystemStats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Load != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Load.Size())) + n1, err := m.Load.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.Cpu != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Cpu.Size())) + n2, err := m.Cpu.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.Memory != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Memory.Size())) + n3, err := m.Memory.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *SystemStats_Loadstats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SystemStats_Loadstats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Load1 != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Load1)))) + } + if m.Load5 != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Load5)))) + } + if m.Load15 != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Load15)))) + } + return i, nil +} + +func (m *SystemStats_CPUStats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SystemStats_CPUStats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.User != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.User)))) + } + if m.System != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.System)))) + } + if m.Idle != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Idle)))) + } + return i, nil +} + +func (m *SystemStats_MemoryStats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SystemStats_MemoryStats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Total != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Total)) + } + if m.Available != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Available)) + } + if m.Used != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Used)) + } + return i, nil +} + +func (m *ComponentStats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ComponentStats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Uptime != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Uptime)) + } + if m.Cpu != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Cpu.Size())) + n4, err := m.Cpu.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Memory != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Memory.Size())) + n5, err := m.Memory.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Goroutines != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Goroutines)) + } + if m.GcCpuFraction != 0 { + dAtA[i] = 0x2d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.GcCpuFraction)))) + } + return i, nil +} + +func (m *ComponentStats_CPUStats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ComponentStats_CPUStats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.User != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.User)))) + } + if m.System != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.System)))) + } + if m.Idle != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Idle)))) + } + return i, nil +} + +func (m *ComponentStats_MemoryStats) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ComponentStats_MemoryStats) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Memory != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Memory)) + } + if m.Swap != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Swap)) + } + return i, nil +} + +func encodeFixed64Api(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Api(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintApi(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Percentiles) Size() (n int) { + var l int + _ = l + if m.Percentile1 != 0 { + n += 5 + } + if m.Percentile5 != 0 { + n += 5 + } + if m.Percentile10 != 0 { + n += 5 + } + if m.Percentile25 != 0 { + n += 5 + } + if m.Percentile50 != 0 { + n += 5 + } + if m.Percentile75 != 0 { + n += 5 + } + if m.Percentile90 != 0 { + n += 5 + } + if m.Percentile95 != 0 { + n += 5 + } + if m.Percentile99 != 0 { + n += 5 + } + return n +} + +func (m *Rates) Size() (n int) { + var l int + _ = l + if m.Rate1 != 0 { + n += 5 + } + if m.Rate5 != 0 { + n += 5 + } + if m.Rate15 != 0 { + n += 5 + } + return n +} + +func (m *SystemStats) Size() (n int) { + var l int + _ = l + if m.Load != nil { + l = m.Load.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Cpu != nil { + l = m.Cpu.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Memory != nil { + l = m.Memory.Size() + n += 1 + l + sovApi(uint64(l)) + } + return n +} + +func (m *SystemStats_Loadstats) Size() (n int) { + var l int + _ = l + if m.Load1 != 0 { + n += 5 + } + if m.Load5 != 0 { + n += 5 + } + if m.Load15 != 0 { + n += 5 + } + return n +} + +func (m *SystemStats_CPUStats) Size() (n int) { + var l int + _ = l + if m.User != 0 { + n += 5 + } + if m.System != 0 { + n += 5 + } + if m.Idle != 0 { + n += 5 + } + return n +} + +func (m *SystemStats_MemoryStats) Size() (n int) { + var l int + _ = l + if m.Total != 0 { + n += 1 + sovApi(uint64(m.Total)) + } + if m.Available != 0 { + n += 1 + sovApi(uint64(m.Available)) + } + if m.Used != 0 { + n += 1 + sovApi(uint64(m.Used)) + } + return n +} + +func (m *ComponentStats) Size() (n int) { + var l int + _ = l + if m.Uptime != 0 { + n += 1 + sovApi(uint64(m.Uptime)) + } + if m.Cpu != nil { + l = m.Cpu.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Memory != nil { + l = m.Memory.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Goroutines != 0 { + n += 1 + sovApi(uint64(m.Goroutines)) + } + if m.GcCpuFraction != 0 { + n += 5 + } + return n +} + +func (m *ComponentStats_CPUStats) Size() (n int) { + var l int + _ = l + if m.User != 0 { + n += 5 + } + if m.System != 0 { + n += 5 + } + if m.Idle != 0 { + n += 5 + } + return n +} + +func (m *ComponentStats_MemoryStats) Size() (n int) { + var l int + _ = l + if m.Memory != 0 { + n += 1 + sovApi(uint64(m.Memory)) + } + if m.Swap != 0 { + n += 1 + sovApi(uint64(m.Swap)) + } + return n +} + +func sovApi(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozApi(x uint64) (n int) { + return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Percentiles) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Percentiles: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Percentiles: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile10", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile10 = float32(math.Float32frombits(v)) + case 4: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile25", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile25 = float32(math.Float32frombits(v)) + case 5: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile50", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile50 = float32(math.Float32frombits(v)) + case 6: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile75", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile75 = float32(math.Float32frombits(v)) + case 7: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile90", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile90 = float32(math.Float32frombits(v)) + case 8: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile95", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile95 = float32(math.Float32frombits(v)) + case 9: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile99", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Percentile99 = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Rates) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Rates: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Rates: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Rate1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Rate5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate15", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Rate15 = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SystemStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SystemStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Load", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Load == nil { + m.Load = &SystemStats_Loadstats{} + } + if err := m.Load.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Cpu", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Cpu == nil { + m.Cpu = &SystemStats_CPUStats{} + } + if err := m.Cpu.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Memory == nil { + m.Memory = &SystemStats_MemoryStats{} + } + if err := m.Memory.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats_Loadstats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Loadstats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Loadstats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load15", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load15 = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats_CPUStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CPUStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CPUStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.User = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.System = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Idle", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Idle = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats_MemoryStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MemoryStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemoryStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Total", wireType) + } + m.Total = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Total |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Available", wireType) + } + m.Available = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Available |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Used", wireType) + } + m.Used = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Used |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ComponentStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ComponentStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ComponentStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Uptime", wireType) + } + m.Uptime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Uptime |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Cpu", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Cpu == nil { + m.Cpu = &ComponentStats_CPUStats{} + } + if err := m.Cpu.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Memory == nil { + m.Memory = &ComponentStats_MemoryStats{} + } + if err := m.Memory.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Goroutines", wireType) + } + m.Goroutines = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Goroutines |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field GcCpuFraction", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.GcCpuFraction = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ComponentStats_CPUStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CPUStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CPUStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.User = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.System = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Idle", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Idle = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ComponentStats_MemoryStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MemoryStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemoryStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + m.Memory = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Memory |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Swap", wireType) + } + m.Swap = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Swap |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipApi(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthApi + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipApi(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthApi = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowApi = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } + +var fileDescriptorApi = []byte{ + // 544 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xdd, 0x8a, 0xd3, 0x40, + 0x14, 0xb6, 0x6d, 0x5a, 0xb7, 0x27, 0xfe, 0xc0, 0x20, 0x25, 0x96, 0xa5, 0x2e, 0x15, 0x44, 0x10, + 0xd3, 0x5a, 0x0d, 0x25, 0xb7, 0x16, 0xbc, 0xf0, 0x77, 0xc9, 0xee, 0xde, 0x78, 0xb3, 0x4c, 0xd3, + 0xb1, 0x0d, 0x26, 0x99, 0x90, 0x99, 0xb8, 0xec, 0x7b, 0x78, 0xe1, 0x23, 0x79, 0x23, 0xf8, 0x08, + 0x4b, 0x7d, 0x11, 0x99, 0x93, 0x69, 0x4c, 0xa6, 0x7b, 0xe1, 0xc5, 0x5e, 0x04, 0xce, 0x77, 0xe6, + 0x3b, 0xdf, 0x9c, 0xf3, 0x1d, 0x32, 0xf0, 0x7c, 0x1d, 0xc9, 0x4d, 0xb1, 0x74, 0x43, 0x9e, 0x4c, + 0x4e, 0x37, 0xec, 0x74, 0x13, 0xa5, 0x6b, 0xf1, 0x91, 0xc9, 0x0b, 0x9e, 0x7f, 0x9d, 0x48, 0x99, + 0x4e, 0x68, 0x16, 0xa9, 0xcf, 0xcd, 0x72, 0x2e, 0x39, 0xe9, 0xd0, 0x2c, 0x1a, 0xff, 0x6a, 0x83, + 0x7d, 0xcc, 0xf2, 0x90, 0xa5, 0x32, 0x8a, 0x99, 0x20, 0x47, 0x60, 0x67, 0x15, 0x7c, 0xe1, 0xb4, + 0x8e, 0x5a, 0x4f, 0xdb, 0x41, 0x3d, 0xd5, 0x64, 0x78, 0x4e, 0xdb, 0x64, 0x78, 0x64, 0x0c, 0x77, + 0x6a, 0x05, 0x53, 0xa7, 0x83, 0x94, 0x46, 0xae, 0xc9, 0x99, 0x79, 0x8e, 0x65, 0x72, 0x66, 0x86, + 0x8e, 0x37, 0x75, 0xba, 0x26, 0xc7, 0x33, 0x74, 0xe6, 0x9e, 0xd3, 0x33, 0x39, 0x73, 0x43, 0xc7, + 0x9f, 0x3a, 0xb7, 0x4d, 0x8e, 0x6f, 0xe8, 0xf8, 0x9e, 0x73, 0xb0, 0xc7, 0x31, 0x75, 0x7c, 0xa7, + 0xbf, 0xc7, 0xf1, 0xc7, 0xef, 0xa0, 0x1b, 0x50, 0xc9, 0x04, 0x79, 0x00, 0xdd, 0x9c, 0xca, 0xca, + 0xc2, 0x12, 0xec, 0xb2, 0x3b, 0xdb, 0x4a, 0x40, 0x06, 0xd0, 0xc3, 0x63, 0x4f, 0x5b, 0xa5, 0xd1, + 0xf8, 0x7b, 0x07, 0xec, 0x93, 0x4b, 0x21, 0x59, 0x72, 0x22, 0xa9, 0x14, 0xc4, 0x05, 0x2b, 0xe6, + 0x74, 0x85, 0x92, 0xf6, 0x6c, 0xe8, 0xaa, 0x5d, 0xd6, 0xce, 0xdd, 0xf7, 0x9c, 0xae, 0x84, 0x8a, + 0x02, 0xe4, 0x91, 0x67, 0xd0, 0x09, 0xb3, 0x02, 0xef, 0xb2, 0x67, 0x0f, 0xf7, 0xe8, 0x8b, 0xe3, + 0x33, 0x0c, 0x02, 0xc5, 0x22, 0xaf, 0xa0, 0x97, 0xb0, 0x84, 0xe7, 0x97, 0xd8, 0x84, 0x3d, 0x3b, + 0xdc, 0xe3, 0x7f, 0xc0, 0xe3, 0xb2, 0x44, 0x73, 0x87, 0x9f, 0xa0, 0x5f, 0xdd, 0xaa, 0xa6, 0x53, + 0xf7, 0x56, 0x33, 0x23, 0xd8, 0x65, 0xab, 0x99, 0x11, 0xa8, 0x99, 0xf1, 0xb8, 0x9a, 0xb9, 0x44, + 0xc3, 0xb7, 0x70, 0xb0, 0xeb, 0x8b, 0x10, 0xb0, 0x0a, 0xc1, 0x72, 0x2d, 0x87, 0xb1, 0xaa, 0x13, + 0xd8, 0x93, 0x96, 0xd3, 0x48, 0x71, 0xa3, 0x55, 0xcc, 0xb4, 0x1a, 0xc6, 0xc3, 0x33, 0xb0, 0x6b, + 0x3d, 0xab, 0x46, 0x24, 0x97, 0x34, 0x46, 0x3d, 0x2b, 0x28, 0x01, 0x39, 0x84, 0x3e, 0xfd, 0x46, + 0xa3, 0x98, 0x2e, 0x63, 0x86, 0x9a, 0x56, 0xf0, 0x2f, 0xa1, 0x5b, 0x58, 0xa1, 0xac, 0x85, 0x2d, + 0xac, 0xc6, 0x57, 0x6d, 0xb8, 0xb7, 0xe0, 0x49, 0xc6, 0x53, 0x96, 0xca, 0x52, 0x7a, 0x00, 0xbd, + 0x22, 0x93, 0x51, 0xc2, 0xb4, 0xb6, 0x46, 0xc4, 0xad, 0x6f, 0xa0, 0x74, 0xb4, 0x59, 0x69, 0x2c, + 0x61, 0x6e, 0x2c, 0xe1, 0xd1, 0x75, 0x25, 0xd7, 0xec, 0x81, 0x8c, 0x00, 0xd6, 0x3c, 0xe7, 0x85, + 0x8c, 0x52, 0x26, 0xf0, 0x6f, 0xb2, 0x82, 0x5a, 0x86, 0x3c, 0x81, 0xfb, 0xeb, 0xf0, 0x3c, 0xcc, + 0x8a, 0xf3, 0x2f, 0x39, 0x0d, 0x65, 0xc4, 0x53, 0xfd, 0x3b, 0xdd, 0x5d, 0x87, 0x8b, 0xac, 0x78, + 0xa3, 0x93, 0x37, 0x6a, 0xbf, 0xdf, 0xb4, 0x7f, 0x50, 0xcd, 0xa6, 0x3d, 0xd2, 0xad, 0x13, 0xb0, + 0xc4, 0x05, 0xcd, 0xb4, 0xf7, 0x18, 0xbf, 0xf6, 0x7e, 0x6e, 0x47, 0xad, 0xdf, 0xdb, 0x51, 0xeb, + 0x6a, 0x3b, 0x6a, 0xfd, 0xf8, 0x33, 0xba, 0xf5, 0xf9, 0xf1, 0x7f, 0x3c, 0x6e, 0xcb, 0x1e, 0xbe, + 0x6c, 0x2f, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x67, 0xf3, 0x67, 0x35, 0x0a, 0x05, 0x00, 0x00, +} diff --git a/api/api.proto b/api/api.proto new file mode 100644 index 000000000..f9c878e04 --- /dev/null +++ b/api/api.proto @@ -0,0 +1,65 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +package api; + +option go_package = "github.com/TheThingsNetwork/ttn/api"; + +message Percentiles { + float percentile1 = 1; + float percentile5 = 2; + float percentile10 = 3; + float percentile25 = 4; + float percentile50 = 5; + float percentile75 = 6; + float percentile90 = 7; + float percentile95 = 8; + float percentile99 = 9; +} + +message Rates { + float rate1 = 1; + float rate5 = 2; + float rate15 = 3; +} + +message SystemStats { + message Loadstats { + float load1 = 1; + float load5 = 2; + float load15 = 3; + } + Loadstats load = 1; + message CPUStats { + float user = 1; + float system = 2; + float idle = 3; + } + CPUStats cpu = 2; + message MemoryStats { + uint64 total = 1; + uint64 available = 2; + uint64 used = 3; + } + MemoryStats memory = 3; +} + +message ComponentStats { + uint64 uptime = 1; + message CPUStats { + float user = 1; + float system = 2; + float idle = 3; + } + CPUStats cpu = 2; + message MemoryStats { + uint64 memory = 1; + uint64 swap = 2; + } + MemoryStats memory = 3; + + uint64 goroutines = 4; + float gc_cpu_fraction = 5; +} diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go new file mode 100644 index 000000000..cfeec2c60 --- /dev/null +++ b/api/broker/broker.pb.go @@ -0,0 +1,5302 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/broker/broker.proto +// DO NOT EDIT! + +/* + Package broker is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/broker/broker.proto + + It has these top-level messages: + DownlinkOption + UplinkMessage + DownlinkMessage + DeviceActivationResponse + DeduplicatedUplinkMessage + DeviceActivationRequest + DeduplicatedDeviceActivationRequest + ActivationChallengeRequest + ActivationChallengeResponse + SubscribeRequest + StatusRequest + Status + ApplicationHandlerRegistration +*/ +package broker + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/gogo/protobuf/gogoproto" +import api "github.com/TheThingsNetwork/ttn/api" +import protocol "github.com/TheThingsNetwork/ttn/api/protocol" +import gateway "github.com/TheThingsNetwork/ttn/api/gateway" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type DownlinkOption struct { + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + GatewayId string `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + Score uint32 `protobuf:"varint,3,opt,name=score,proto3" json:"score,omitempty"` + Deadline int64 `protobuf:"varint,4,opt,name=deadline,proto3" json:"deadline,omitempty"` + ProtocolConfig *protocol.TxConfiguration `protobuf:"bytes,5,opt,name=protocol_config,json=protocolConfig" json:"protocol_config,omitempty"` + GatewayConfig *gateway.TxConfiguration `protobuf:"bytes,6,opt,name=gateway_config,json=gatewayConfig" json:"gateway_config,omitempty"` +} + +func (m *DownlinkOption) Reset() { *m = DownlinkOption{} } +func (m *DownlinkOption) String() string { return proto.CompactTextString(m) } +func (*DownlinkOption) ProtoMessage() {} +func (*DownlinkOption) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{0} } + +func (m *DownlinkOption) GetProtocolConfig() *protocol.TxConfiguration { + if m != nil { + return m.ProtocolConfig + } + return nil +} + +func (m *DownlinkOption) GetGatewayConfig() *gateway.TxConfiguration { + if m != nil { + return m.GatewayConfig + } + return nil +} + +// received from the Router +type UplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + // NOTE: For LoRaWAN, the Router doesn't know the DevEUI/ID and AppEUI/ID + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` +} + +func (m *UplinkMessage) Reset() { *m = UplinkMessage{} } +func (m *UplinkMessage) String() string { return proto.CompactTextString(m) } +func (*UplinkMessage) ProtoMessage() {} +func (*UplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{1} } + +func (m *UplinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *UplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *UplinkMessage) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *UplinkMessage) GetDownlinkOptions() []*DownlinkOption { + if m != nil { + return m.DownlinkOptions + } + return nil +} + +// received from the Handler, sent to the Router, used as Template +type DownlinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + DownlinkOption *DownlinkOption `protobuf:"bytes,21,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` +} + +func (m *DownlinkMessage) Reset() { *m = DownlinkMessage{} } +func (m *DownlinkMessage) String() string { return proto.CompactTextString(m) } +func (*DownlinkMessage) ProtoMessage() {} +func (*DownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } + +func (m *DownlinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DownlinkMessage) GetDownlinkOption() *DownlinkOption { + if m != nil { + return m.DownlinkOption + } + return nil +} + +// sent to the Router, used as Template +type DeviceActivationResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DownlinkOption *DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` +} + +func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } +func (m *DeviceActivationResponse) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationResponse) ProtoMessage() {} +func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } + +func (m *DeviceActivationResponse) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DeviceActivationResponse) GetDownlinkOption() *DownlinkOption { + if m != nil { + return m.DownlinkOption + } + return nil +} + +// sent to the Handler +type DeduplicatedUplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ServerTime int64 `protobuf:"varint,23,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"` + ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` +} + +func (m *DeduplicatedUplinkMessage) Reset() { *m = DeduplicatedUplinkMessage{} } +func (m *DeduplicatedUplinkMessage) String() string { return proto.CompactTextString(m) } +func (*DeduplicatedUplinkMessage) ProtoMessage() {} +func (*DeduplicatedUplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{4} } + +func (m *DeduplicatedUplinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DeduplicatedUplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeduplicatedUplinkMessage) GetGatewayMetadata() []*gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeduplicatedUplinkMessage) GetResponseTemplate() *DownlinkMessage { + if m != nil { + return m.ResponseTemplate + } + return nil +} + +// received from the Router +type DeviceActivationRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` +} + +func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } +func (m *DeviceActivationRequest) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationRequest) ProtoMessage() {} +func (*DeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{5} } + +func (m *DeviceActivationRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetDownlinkOptions() []*DownlinkOption { + if m != nil { + return m.DownlinkOptions + } + return nil +} + +// sent to the Handler +type DeduplicatedDeviceActivationRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + ServerTime int64 `protobuf:"varint,24,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"` + ResponseTemplate *DeviceActivationResponse `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` +} + +func (m *DeduplicatedDeviceActivationRequest) Reset() { *m = DeduplicatedDeviceActivationRequest{} } +func (m *DeduplicatedDeviceActivationRequest) String() string { return proto.CompactTextString(m) } +func (*DeduplicatedDeviceActivationRequest) ProtoMessage() {} +func (*DeduplicatedDeviceActivationRequest) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{6} +} + +func (m *DeduplicatedDeviceActivationRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetGatewayMetadata() []*gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetResponseTemplate() *DeviceActivationResponse { + if m != nil { + return m.ResponseTemplate + } + return nil +} + +type ActivationChallengeRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` +} + +func (m *ActivationChallengeRequest) Reset() { *m = ActivationChallengeRequest{} } +func (m *ActivationChallengeRequest) String() string { return proto.CompactTextString(m) } +func (*ActivationChallengeRequest) ProtoMessage() {} +func (*ActivationChallengeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{7} } + +func (m *ActivationChallengeRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +type ActivationChallengeResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` +} + +func (m *ActivationChallengeResponse) Reset() { *m = ActivationChallengeResponse{} } +func (m *ActivationChallengeResponse) String() string { return proto.CompactTextString(m) } +func (*ActivationChallengeResponse) ProtoMessage() {} +func (*ActivationChallengeResponse) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{8} +} + +func (m *ActivationChallengeResponse) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +// message SubscribeRequest is used by a Handler to subscribe to uplink messages +type SubscribeRequest struct { +} + +func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} } +func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) } +func (*SubscribeRequest) ProtoMessage() {} +func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{9} } + +// message StatusRequest is used to request the status of this Broker +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{10} } + +type Status struct { + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + UplinkUnique *api.Rates `protobuf:"bytes,12,opt,name=uplink_unique,json=uplinkUnique" json:"uplink_unique,omitempty"` + Downlink *api.Rates `protobuf:"bytes,13,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,14,opt,name=activations" json:"activations,omitempty"` + ActivationsUnique *api.Rates `protobuf:"bytes,15,opt,name=activations_unique,json=activationsUnique" json:"activations_unique,omitempty"` + Deduplication *api.Percentiles `protobuf:"bytes,16,opt,name=deduplication" json:"deduplication,omitempty"` + // Connections + ConnectedRouters uint32 `protobuf:"varint,21,opt,name=connected_routers,json=connectedRouters,proto3" json:"connected_routers,omitempty"` + ConnectedHandlers uint32 `protobuf:"varint,22,opt,name=connected_handlers,json=connectedHandlers,proto3" json:"connected_handlers,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{11} } + +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + +func (m *Status) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *Status) GetUplinkUnique() *api.Rates { + if m != nil { + return m.UplinkUnique + } + return nil +} + +func (m *Status) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *Status) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + +func (m *Status) GetActivationsUnique() *api.Rates { + if m != nil { + return m.ActivationsUnique + } + return nil +} + +func (m *Status) GetDeduplication() *api.Percentiles { + if m != nil { + return m.Deduplication + } + return nil +} + +type ApplicationHandlerRegistration struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + HandlerId string `protobuf:"bytes,2,opt,name=handler_id,json=handlerId,proto3" json:"handler_id,omitempty"` +} + +func (m *ApplicationHandlerRegistration) Reset() { *m = ApplicationHandlerRegistration{} } +func (m *ApplicationHandlerRegistration) String() string { return proto.CompactTextString(m) } +func (*ApplicationHandlerRegistration) ProtoMessage() {} +func (*ApplicationHandlerRegistration) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{12} +} + +func init() { + proto.RegisterType((*DownlinkOption)(nil), "broker.DownlinkOption") + proto.RegisterType((*UplinkMessage)(nil), "broker.UplinkMessage") + proto.RegisterType((*DownlinkMessage)(nil), "broker.DownlinkMessage") + proto.RegisterType((*DeviceActivationResponse)(nil), "broker.DeviceActivationResponse") + proto.RegisterType((*DeduplicatedUplinkMessage)(nil), "broker.DeduplicatedUplinkMessage") + proto.RegisterType((*DeviceActivationRequest)(nil), "broker.DeviceActivationRequest") + proto.RegisterType((*DeduplicatedDeviceActivationRequest)(nil), "broker.DeduplicatedDeviceActivationRequest") + proto.RegisterType((*ActivationChallengeRequest)(nil), "broker.ActivationChallengeRequest") + proto.RegisterType((*ActivationChallengeResponse)(nil), "broker.ActivationChallengeResponse") + proto.RegisterType((*SubscribeRequest)(nil), "broker.SubscribeRequest") + proto.RegisterType((*StatusRequest)(nil), "broker.StatusRequest") + proto.RegisterType((*Status)(nil), "broker.Status") + proto.RegisterType((*ApplicationHandlerRegistration)(nil), "broker.ApplicationHandlerRegistration") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Broker service + +type BrokerClient interface { + // Router initiates an Association with the Broker. + Associate(ctx context.Context, opts ...grpc.CallOption) (Broker_AssociateClient, error) + // Handler subscribes to uplink stream. + Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Broker_SubscribeClient, error) + // Handler initiates downlink stream. + Publish(ctx context.Context, opts ...grpc.CallOption) (Broker_PublishClient, error) + // Router requests device activation + Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) +} + +type brokerClient struct { + cc *grpc.ClientConn +} + +func NewBrokerClient(cc *grpc.ClientConn) BrokerClient { + return &brokerClient{cc} +} + +func (c *brokerClient) Associate(ctx context.Context, opts ...grpc.CallOption) (Broker_AssociateClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Broker_serviceDesc.Streams[0], c.cc, "/broker.Broker/Associate", opts...) + if err != nil { + return nil, err + } + x := &brokerAssociateClient{stream} + return x, nil +} + +type Broker_AssociateClient interface { + Send(*UplinkMessage) error + Recv() (*DownlinkMessage, error) + grpc.ClientStream +} + +type brokerAssociateClient struct { + grpc.ClientStream +} + +func (x *brokerAssociateClient) Send(m *UplinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *brokerAssociateClient) Recv() (*DownlinkMessage, error) { + m := new(DownlinkMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *brokerClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Broker_SubscribeClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Broker_serviceDesc.Streams[1], c.cc, "/broker.Broker/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &brokerSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Broker_SubscribeClient interface { + Recv() (*DeduplicatedUplinkMessage, error) + grpc.ClientStream +} + +type brokerSubscribeClient struct { + grpc.ClientStream +} + +func (x *brokerSubscribeClient) Recv() (*DeduplicatedUplinkMessage, error) { + m := new(DeduplicatedUplinkMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *brokerClient) Publish(ctx context.Context, opts ...grpc.CallOption) (Broker_PublishClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Broker_serviceDesc.Streams[2], c.cc, "/broker.Broker/Publish", opts...) + if err != nil { + return nil, err + } + x := &brokerPublishClient{stream} + return x, nil +} + +type Broker_PublishClient interface { + Send(*DownlinkMessage) error + CloseAndRecv() (*google_protobuf.Empty, error) + grpc.ClientStream +} + +type brokerPublishClient struct { + grpc.ClientStream +} + +func (x *brokerPublishClient) Send(m *DownlinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *brokerPublishClient) CloseAndRecv() (*google_protobuf.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *brokerClient) Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { + out := new(DeviceActivationResponse) + err := grpc.Invoke(ctx, "/broker.Broker/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Broker service + +type BrokerServer interface { + // Router initiates an Association with the Broker. + Associate(Broker_AssociateServer) error + // Handler subscribes to uplink stream. + Subscribe(*SubscribeRequest, Broker_SubscribeServer) error + // Handler initiates downlink stream. + Publish(Broker_PublishServer) error + // Router requests device activation + Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) +} + +func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { + s.RegisterService(&_Broker_serviceDesc, srv) +} + +func _Broker_Associate_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(BrokerServer).Associate(&brokerAssociateServer{stream}) +} + +type Broker_AssociateServer interface { + Send(*DownlinkMessage) error + Recv() (*UplinkMessage, error) + grpc.ServerStream +} + +type brokerAssociateServer struct { + grpc.ServerStream +} + +func (x *brokerAssociateServer) Send(m *DownlinkMessage) error { + return x.ServerStream.SendMsg(m) +} + +func (x *brokerAssociateServer) Recv() (*UplinkMessage, error) { + m := new(UplinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Broker_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(BrokerServer).Subscribe(m, &brokerSubscribeServer{stream}) +} + +type Broker_SubscribeServer interface { + Send(*DeduplicatedUplinkMessage) error + grpc.ServerStream +} + +type brokerSubscribeServer struct { + grpc.ServerStream +} + +func (x *brokerSubscribeServer) Send(m *DeduplicatedUplinkMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _Broker_Publish_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(BrokerServer).Publish(&brokerPublishServer{stream}) +} + +type Broker_PublishServer interface { + SendAndClose(*google_protobuf.Empty) error + Recv() (*DownlinkMessage, error) + grpc.ServerStream +} + +type brokerPublishServer struct { + grpc.ServerStream +} + +func (x *brokerPublishServer) SendAndClose(m *google_protobuf.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *brokerPublishServer) Recv() (*DownlinkMessage, error) { + m := new(DownlinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Broker_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BrokerServer).Activate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.Broker/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerServer).Activate(ctx, req.(*DeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Broker_serviceDesc = grpc.ServiceDesc{ + ServiceName: "broker.Broker", + HandlerType: (*BrokerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Activate", + Handler: _Broker_Activate_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Associate", + Handler: _Broker_Associate_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "Subscribe", + Handler: _Broker_Subscribe_Handler, + ServerStreams: true, + }, + { + StreamName: "Publish", + Handler: _Broker_Publish_Handler, + ClientStreams: true, + }, + }, + Metadata: "github.com/TheThingsNetwork/ttn/api/broker/broker.proto", +} + +// Client API for BrokerManager service + +type BrokerManagerClient interface { + // Handler announces a new application to Broker. This is a temporary method that will be removed + // when we can push updates from the Discovery service to the routing services. + RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // Network operator requests Broker status + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) +} + +type brokerManagerClient struct { + cc *grpc.ClientConn +} + +func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { + return &brokerManagerClient{cc} +} + +func (c *brokerManagerClient) RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/broker.BrokerManager/RegisterApplicationHandler", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/broker.BrokerManager/GetStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for BrokerManager service + +type BrokerManagerServer interface { + // Handler announces a new application to Broker. This is a temporary method that will be removed + // when we can push updates from the Discovery service to the routing services. + RegisterApplicationHandler(context.Context, *ApplicationHandlerRegistration) (*google_protobuf.Empty, error) + // Network operator requests Broker status + GetStatus(context.Context, *StatusRequest) (*Status, error) +} + +func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { + s.RegisterService(&_BrokerManager_serviceDesc, srv) +} + +func _BrokerManager_RegisterApplicationHandler_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationHandlerRegistration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BrokerManagerServer).RegisterApplicationHandler(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/RegisterApplicationHandler", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).RegisterApplicationHandler(ctx, req.(*ApplicationHandlerRegistration)) + } + return interceptor(ctx, in, info, handler) +} + +func _BrokerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BrokerManagerServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/GetStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).GetStatus(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _BrokerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "broker.BrokerManager", + HandlerType: (*BrokerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterApplicationHandler", + Handler: _BrokerManager_RegisterApplicationHandler_Handler, + }, + { + MethodName: "GetStatus", + Handler: _BrokerManager_GetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/broker/broker.proto", +} + +func (m *DownlinkOption) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DownlinkOption) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Identifier) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Identifier))) + i += copy(dAtA[i:], m.Identifier) + } + if len(m.GatewayId) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.GatewayId))) + i += copy(dAtA[i:], m.GatewayId) + } + if m.Score != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Score)) + } + if m.Deadline != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Deadline)) + } + if m.ProtocolConfig != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolConfig.Size())) + n1, err := m.ProtocolConfig.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.GatewayConfig != nil { + dAtA[i] = 0x32 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.GatewayConfig.Size())) + n2, err := m.GatewayConfig.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *UplinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *UplinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n3, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n4, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n5, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if len(m.AppId) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + if m.ProtocolMetadata != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n6, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.GatewayMetadata != nil { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.GatewayMetadata.Size())) + n7, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if len(m.DownlinkOptions) > 0 { + for _, msg := range m.DownlinkOptions { + dAtA[i] = 0xfa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DownlinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DownlinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n8, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n9, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n9 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n10, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + } + if len(m.AppId) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + if m.DownlinkOption != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DownlinkOption.Size())) + n11, err := m.DownlinkOption.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 + } + return i, nil +} + +func (m *DeviceActivationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceActivationResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n12, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n12 + } + if m.DownlinkOption != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DownlinkOption.Size())) + n13, err := m.DownlinkOption.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n13 + } + return i, nil +} + +func (m *DeduplicatedUplinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeduplicatedUplinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n14, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n14 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n15, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n15 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n16, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n16 + } + if len(m.AppId) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + if m.ProtocolMetadata != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n17, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n17 + } + if len(m.GatewayMetadata) > 0 { + for _, msg := range m.GatewayMetadata { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.ServerTime != 0 { + dAtA[i] = 0xb8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ServerTime)) + } + if m.ResponseTemplate != nil { + dAtA[i] = 0xfa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ResponseTemplate.Size())) + n18, err := m.ResponseTemplate.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n18 + } + return i, nil +} + +func (m *DeviceActivationRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceActivationRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n19, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n19 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n20, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n20 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n21, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n21 + } + if m.ProtocolMetadata != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n22, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n22 + } + if m.GatewayMetadata != nil { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.GatewayMetadata.Size())) + n23, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n23 + } + if m.ActivationMetadata != nil { + dAtA[i] = 0xba + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ActivationMetadata.Size())) + n24, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n24 + } + if len(m.DownlinkOptions) > 0 { + for _, msg := range m.DownlinkOptions { + dAtA[i] = 0xfa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DeduplicatedDeviceActivationRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeduplicatedDeviceActivationRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n25, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n25 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n26, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n26 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n27, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n27 + } + if len(m.AppId) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + if m.ProtocolMetadata != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n28, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n28 + } + if len(m.GatewayMetadata) > 0 { + for _, msg := range m.GatewayMetadata { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.ActivationMetadata != nil { + dAtA[i] = 0xba + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ActivationMetadata.Size())) + n29, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n29 + } + if m.ServerTime != 0 { + dAtA[i] = 0xc0 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ServerTime)) + } + if m.ResponseTemplate != nil { + dAtA[i] = 0xfa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ResponseTemplate.Size())) + n30, err := m.ResponseTemplate.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n30 + } + return i, nil +} + +func (m *ActivationChallengeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ActivationChallengeRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n31, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n31 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n32, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n32 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n33, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n33 + } + if len(m.AppId) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + return i, nil +} + +func (m *ActivationChallengeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ActivationChallengeResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n34, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n34 + } + return i, nil +} + +func (m *SubscribeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SubscribeRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.System != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.System.Size())) + n35, err := m.System.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n35 + } + if m.Component != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Component.Size())) + n36, err := m.Component.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n36 + } + if m.Uplink != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Uplink.Size())) + n37, err := m.Uplink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n37 + } + if m.UplinkUnique != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.UplinkUnique.Size())) + n38, err := m.UplinkUnique.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n38 + } + if m.Downlink != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Downlink.Size())) + n39, err := m.Downlink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n39 + } + if m.Activations != nil { + dAtA[i] = 0x72 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Activations.Size())) + n40, err := m.Activations.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n40 + } + if m.ActivationsUnique != nil { + dAtA[i] = 0x7a + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ActivationsUnique.Size())) + n41, err := m.ActivationsUnique.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n41 + } + if m.Deduplication != nil { + dAtA[i] = 0x82 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.Deduplication.Size())) + n42, err := m.Deduplication.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n42 + } + if m.ConnectedRouters != 0 { + dAtA[i] = 0xa8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ConnectedRouters)) + } + if m.ConnectedHandlers != 0 { + dAtA[i] = 0xb0 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintBroker(dAtA, i, uint64(m.ConnectedHandlers)) + } + return i, nil +} + +func (m *ApplicationHandlerRegistration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ApplicationHandlerRegistration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.HandlerId) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintBroker(dAtA, i, uint64(len(m.HandlerId))) + i += copy(dAtA[i:], m.HandlerId) + } + return i, nil +} + +func encodeFixed64Broker(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Broker(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintBroker(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *DownlinkOption) Size() (n int) { + var l int + _ = l + l = len(m.Identifier) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Score != 0 { + n += 1 + sovBroker(uint64(m.Score)) + } + if m.Deadline != 0 { + n += 1 + sovBroker(uint64(m.Deadline)) + } + if m.ProtocolConfig != nil { + l = m.ProtocolConfig.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.GatewayConfig != nil { + l = m.GatewayConfig.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *UplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if len(m.DownlinkOptions) > 0 { + for _, e := range m.DownlinkOptions { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + return n +} + +func (m *DownlinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.DownlinkOption != nil { + l = m.DownlinkOption.Size() + n += 2 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DeviceActivationResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DownlinkOption != nil { + l = m.DownlinkOption.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DeduplicatedUplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if len(m.GatewayMetadata) > 0 { + for _, e := range m.GatewayMetadata { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + if m.ServerTime != 0 { + n += 2 + sovBroker(uint64(m.ServerTime)) + } + if m.ResponseTemplate != nil { + l = m.ResponseTemplate.Size() + n += 2 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DeviceActivationRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if len(m.DownlinkOptions) > 0 { + for _, e := range m.DownlinkOptions { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + return n +} + +func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if len(m.GatewayMetadata) > 0 { + for _, e := range m.GatewayMetadata { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ServerTime != 0 { + n += 2 + sovBroker(uint64(m.ServerTime)) + } + if m.ResponseTemplate != nil { + l = m.ResponseTemplate.Size() + n += 2 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *ActivationChallengeRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *ActivationChallengeResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *SubscribeRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *Status) Size() (n int) { + var l int + _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.UplinkUnique != nil { + l = m.UplinkUnique.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.ActivationsUnique != nil { + l = m.ActivationsUnique.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Deduplication != nil { + l = m.Deduplication.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ConnectedRouters != 0 { + n += 2 + sovBroker(uint64(m.ConnectedRouters)) + } + if m.ConnectedHandlers != 0 { + n += 2 + sovBroker(uint64(m.ConnectedHandlers)) + } + return n +} + +func (m *ApplicationHandlerRegistration) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.HandlerId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func sovBroker(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozBroker(x uint64) (n int) { + return sovBroker(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DownlinkOption) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DownlinkOption: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DownlinkOption: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Score", wireType) + } + m.Score = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Score |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Deadline", wireType) + } + m.Deadline = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Deadline |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolConfig == nil { + m.ProtocolConfig = &protocol.TxConfiguration{} + } + if err := m.ProtocolConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayConfig == nil { + m.GatewayConfig = &gateway.TxConfiguration{} + } + if err := m.GatewayConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOptions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DownlinkOptions = append(m.DownlinkOptions, &DownlinkOption{}) + if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DownlinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DownlinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DownlinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeduplicatedUplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeduplicatedUplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeduplicatedUplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayMetadata = append(m.GatewayMetadata, &gateway.RxMetadata{}) + if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerTime", wireType) + } + m.ServerTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ServerTime |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseTemplate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ResponseTemplate == nil { + m.ResponseTemplate = &DownlinkMessage{} + } + if err := m.ResponseTemplate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOptions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DownlinkOptions = append(m.DownlinkOptions, &DownlinkOption{}) + if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeduplicatedDeviceActivationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeduplicatedDeviceActivationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeduplicatedDeviceActivationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayMetadata = append(m.GatewayMetadata, &gateway.RxMetadata{}) + if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 24: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerTime", wireType) + } + m.ServerTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ServerTime |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseTemplate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ResponseTemplate == nil { + m.ResponseTemplate = &DeviceActivationResponse{} + } + if err := m.ResponseTemplate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationChallengeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationChallengeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationChallengeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationChallengeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationChallengeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationChallengeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubscribeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubscribeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubscribeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.System == nil { + m.System = &api.SystemStats{} + } + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UplinkUnique", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UplinkUnique == nil { + m.UplinkUnique = &api.Rates{} + } + if err := m.UplinkUnique.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationsUnique", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationsUnique == nil { + m.ActivationsUnique = &api.Rates{} + } + if err := m.ActivationsUnique.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Deduplication", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Deduplication == nil { + m.Deduplication = &api.Percentiles{} + } + if err := m.Deduplication.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedRouters", wireType) + } + m.ConnectedRouters = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConnectedRouters |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedHandlers", wireType) + } + m.ConnectedHandlers = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConnectedHandlers |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ApplicationHandlerRegistration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationHandlerRegistration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationHandlerRegistration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HandlerId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HandlerId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBroker(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthBroker + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipBroker(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthBroker = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBroker = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/broker/broker.proto", fileDescriptorBroker) +} + +var fileDescriptorBroker = []byte{ + // 1178 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x58, 0xdf, 0x8e, 0xdb, 0xc4, + 0x17, 0xfe, 0x79, 0x77, 0x9b, 0xed, 0x9e, 0x34, 0xff, 0xa6, 0xed, 0xae, 0x9b, 0xfe, 0x9a, 0x84, + 0x20, 0x55, 0x11, 0xa5, 0x49, 0x1b, 0x04, 0x08, 0xa9, 0xa2, 0xca, 0x76, 0x2b, 0x58, 0xa4, 0x94, + 0xca, 0xdd, 0x72, 0x81, 0x90, 0xa2, 0x89, 0x7d, 0xea, 0x8c, 0xea, 0xd8, 0xae, 0x67, 0x9c, 0x36, + 0x2f, 0xc0, 0x1b, 0x20, 0x21, 0xee, 0xe8, 0x1b, 0xf0, 0x16, 0x5c, 0x72, 0xcd, 0x05, 0xa0, 0x72, + 0x81, 0xc4, 0x33, 0x70, 0x81, 0x3c, 0x9e, 0xb1, 0x93, 0xdd, 0xa6, 0x2d, 0x68, 0x25, 0x40, 0xdd, + 0x2b, 0x7b, 0xbe, 0xf3, 0xcd, 0xe7, 0xe3, 0x73, 0xce, 0x1c, 0xcf, 0x18, 0xde, 0x77, 0x99, 0x98, + 0xc4, 0xe3, 0xae, 0x1d, 0x4c, 0x7b, 0x07, 0x13, 0x3c, 0x98, 0x30, 0xdf, 0xe5, 0x77, 0x50, 0x3c, + 0x0e, 0xa2, 0x87, 0x3d, 0x21, 0xfc, 0x1e, 0x0d, 0x59, 0x6f, 0x1c, 0x05, 0x0f, 0x31, 0x52, 0x97, + 0x6e, 0x18, 0x05, 0x22, 0x20, 0x85, 0x74, 0x54, 0xbf, 0xe8, 0x06, 0x81, 0xeb, 0x61, 0x4f, 0xa2, + 0xe3, 0xf8, 0x41, 0x0f, 0xa7, 0xa1, 0x98, 0xa7, 0xa4, 0xfa, 0xd5, 0x05, 0x75, 0x37, 0x70, 0x83, + 0x9c, 0x95, 0x8c, 0xe4, 0x40, 0xde, 0x29, 0x7a, 0x4d, 0x3f, 0x90, 0x86, 0x4c, 0x41, 0x4d, 0x0d, + 0xc9, 0xa1, 0x1d, 0x78, 0xd9, 0x8d, 0x22, 0x5c, 0xd2, 0x04, 0x97, 0x0a, 0x7c, 0x4c, 0xe7, 0xfa, + 0x9a, 0x9a, 0xdb, 0x5f, 0xae, 0x41, 0x79, 0x2f, 0x78, 0xec, 0x7b, 0xcc, 0x7f, 0xf8, 0x69, 0x28, + 0x58, 0xe0, 0x93, 0x06, 0x00, 0x73, 0xd0, 0x17, 0xec, 0x01, 0xc3, 0xc8, 0x34, 0x5a, 0x46, 0x67, + 0xcb, 0x5a, 0x40, 0xc8, 0x25, 0x00, 0xa5, 0x31, 0x62, 0x8e, 0xb9, 0x26, 0xed, 0x5b, 0x0a, 0xd9, + 0x77, 0xc8, 0x39, 0x38, 0xc5, 0xed, 0x20, 0x42, 0x73, 0xbd, 0x65, 0x74, 0x4a, 0x56, 0x3a, 0x20, + 0x75, 0x38, 0xed, 0x20, 0x75, 0x3c, 0xe6, 0xa3, 0xb9, 0xd1, 0x32, 0x3a, 0xeb, 0x56, 0x36, 0x26, + 0xbb, 0x50, 0xd1, 0x4e, 0x8f, 0xec, 0xc0, 0x7f, 0xc0, 0x5c, 0xf3, 0x54, 0xcb, 0xe8, 0x14, 0xfb, + 0x17, 0xba, 0xd9, 0xcb, 0x1c, 0x3c, 0xb9, 0x25, 0x2d, 0x71, 0x44, 0x13, 0x27, 0xad, 0xb2, 0xb6, + 0xa4, 0x30, 0xb9, 0x09, 0x65, 0xed, 0x94, 0x92, 0x28, 0x48, 0x09, 0xb3, 0xab, 0xdf, 0xf7, 0xb0, + 0x42, 0x49, 0x19, 0x52, 0xb4, 0xfd, 0xfb, 0x3a, 0x94, 0xee, 0x87, 0x49, 0x18, 0x86, 0xc8, 0x39, + 0x75, 0x91, 0x98, 0xb0, 0x19, 0xd2, 0xb9, 0x17, 0x50, 0x47, 0x06, 0xe1, 0x8c, 0xa5, 0x87, 0xe4, + 0x0a, 0x6c, 0x4e, 0x53, 0x92, 0x7c, 0xfd, 0x62, 0xbf, 0x96, 0x3b, 0xaa, 0x66, 0x5b, 0x9a, 0x41, + 0xee, 0xc0, 0xa6, 0x83, 0xb3, 0x11, 0xc6, 0xcc, 0x2c, 0x26, 0x32, 0xbb, 0xef, 0xfe, 0xf8, 0x53, + 0xf3, 0xfa, 0xcb, 0xca, 0x2a, 0x09, 0x5a, 0x4f, 0xcc, 0x43, 0xe4, 0xdd, 0x3d, 0x9c, 0xdd, 0xbe, + 0xbf, 0x6f, 0x15, 0x1c, 0x9c, 0xdd, 0x8e, 0x59, 0xa2, 0x47, 0xc3, 0x50, 0xea, 0x9d, 0xf9, 0x5b, + 0x7a, 0x83, 0x30, 0x94, 0x7a, 0x34, 0x0c, 0x13, 0xbd, 0xf3, 0x90, 0xdc, 0x25, 0xa9, 0x2c, 0xc9, + 0x54, 0x9e, 0xa2, 0x61, 0xb8, 0xef, 0x24, 0x70, 0xe2, 0x36, 0x73, 0xcc, 0x72, 0x0a, 0x3b, 0x38, + 0xdb, 0x77, 0xc8, 0x00, 0x6a, 0x59, 0xae, 0xa6, 0x28, 0xa8, 0x43, 0x05, 0x35, 0xcf, 0xcb, 0x20, + 0x9c, 0xcb, 0x83, 0x60, 0x3d, 0x19, 0x2a, 0x9b, 0x55, 0xd5, 0xa0, 0x46, 0xc8, 0x87, 0x50, 0xd5, + 0xa9, 0xca, 0x14, 0xb6, 0xa5, 0xc2, 0xd9, 0x2c, 0x59, 0x0b, 0x02, 0x15, 0x85, 0x65, 0xf3, 0x07, + 0x50, 0x75, 0x54, 0xc5, 0x8e, 0x02, 0x59, 0xb2, 0xdc, 0x6c, 0xb6, 0xd6, 0x3b, 0xc5, 0xfe, 0x76, + 0x57, 0x2d, 0xc1, 0xe5, 0x8a, 0xb6, 0x2a, 0xce, 0xd2, 0x98, 0xb7, 0x7f, 0x5b, 0x83, 0x8a, 0xe6, + 0x9c, 0xa4, 0xfb, 0x05, 0xe9, 0xbe, 0x09, 0x95, 0x43, 0xb1, 0x56, 0xc9, 0x5e, 0x15, 0xea, 0xf2, + 0x72, 0xa8, 0xdb, 0x4f, 0x0d, 0x30, 0xf7, 0x70, 0xc6, 0x6c, 0x1c, 0xd8, 0x82, 0xcd, 0xd2, 0xa5, + 0x87, 0x3c, 0x0c, 0x7c, 0x7e, 0x6c, 0x21, 0x7f, 0x8e, 0x93, 0xc5, 0xbf, 0xe4, 0xe4, 0x37, 0x1b, + 0x70, 0x61, 0x0f, 0x9d, 0x38, 0xf4, 0x98, 0x4d, 0x05, 0x3a, 0x27, 0x7d, 0xe0, 0x9f, 0xeb, 0x03, + 0xeb, 0xaf, 0xdc, 0x07, 0x9a, 0x50, 0xe4, 0x18, 0xcd, 0x30, 0x1a, 0x09, 0x36, 0x45, 0x73, 0x47, + 0x7e, 0x55, 0x20, 0x85, 0x0e, 0xd8, 0x14, 0xc9, 0x1e, 0xd4, 0x22, 0x55, 0x6a, 0x23, 0x81, 0xd3, + 0xd0, 0xa3, 0x02, 0xcd, 0xa6, 0xf4, 0x71, 0xe7, 0x70, 0x65, 0xe8, 0x74, 0x55, 0xf5, 0x8c, 0x03, + 0x35, 0xa1, 0xfd, 0xd5, 0x06, 0xec, 0x1c, 0xad, 0xe0, 0x47, 0x31, 0x72, 0xf1, 0xba, 0x94, 0xc6, + 0xbf, 0xa0, 0xe9, 0x0f, 0xe1, 0x2c, 0xcd, 0xc2, 0x9f, 0x4b, 0xec, 0x48, 0x89, 0xff, 0xe7, 0x4e, + 0xe4, 0x39, 0xca, 0xb4, 0x08, 0x3d, 0x82, 0x1d, 0xc7, 0x37, 0xe4, 0x8f, 0x0d, 0x78, 0x73, 0xb1, + 0x69, 0xbc, 0xe6, 0x35, 0xf2, 0x9f, 0x6b, 0x1f, 0xc7, 0x5c, 0x51, 0x87, 0xba, 0x91, 0x79, 0xa4, + 0x1b, 0x0d, 0x57, 0x77, 0xa3, 0x56, 0x56, 0x73, 0x2b, 0xbe, 0x94, 0xcf, 0x69, 0x4b, 0xdf, 0xad, + 0x41, 0x3d, 0x27, 0xde, 0x9a, 0x50, 0xcf, 0x43, 0xdf, 0xc5, 0x93, 0xaa, 0x5b, 0x5d, 0x75, 0x6d, + 0x07, 0x2e, 0x3e, 0x37, 0x64, 0xc7, 0xba, 0x1d, 0x69, 0x13, 0xa8, 0xde, 0x8b, 0xc7, 0xdc, 0x8e, + 0xd8, 0x58, 0xa7, 0xa3, 0x5d, 0x81, 0xd2, 0x3d, 0x41, 0x45, 0xcc, 0x35, 0xf0, 0xf3, 0x3a, 0x14, + 0x52, 0x84, 0x74, 0xa0, 0xc0, 0xe7, 0x5c, 0xe0, 0x54, 0x3e, 0xb5, 0xd8, 0xaf, 0x76, 0x93, 0xe3, + 0xdd, 0x3d, 0x09, 0x25, 0x14, 0x6e, 0x29, 0x3b, 0xb9, 0x0e, 0x5b, 0x76, 0x30, 0x0d, 0x03, 0x1f, + 0x7d, 0xa1, 0x1c, 0x39, 0x2b, 0xc9, 0xb7, 0x34, 0x9a, 0xf2, 0x73, 0x16, 0x69, 0x43, 0x21, 0x96, + 0xbb, 0x19, 0xb5, 0x25, 0x02, 0xc9, 0xb7, 0xa8, 0x40, 0x6e, 0x29, 0x0b, 0xe9, 0x41, 0x29, 0xbd, + 0x1b, 0xc5, 0x3e, 0x7b, 0x14, 0xa3, 0x4c, 0xcd, 0x32, 0xf5, 0x4c, 0x4a, 0xb8, 0x2f, 0xed, 0xe4, + 0x32, 0x9c, 0xd6, 0xdd, 0x50, 0xc6, 0x7d, 0x99, 0x9b, 0xd9, 0xc8, 0xdb, 0x50, 0xcc, 0x57, 0x0a, + 0x97, 0xb9, 0x58, 0xa6, 0x2e, 0x9a, 0xc9, 0x07, 0xb0, 0xb0, 0xae, 0xb8, 0xf6, 0xa5, 0x72, 0x64, + 0x52, 0x6d, 0x81, 0xa5, 0x1c, 0x7a, 0x0f, 0x4a, 0x4e, 0xd6, 0x8a, 0x93, 0xfd, 0x5f, 0x75, 0x21, + 0x92, 0x77, 0x31, 0xb2, 0x93, 0xc3, 0xab, 0x87, 0xdc, 0x5a, 0xa6, 0x91, 0x2b, 0x50, 0xb3, 0x03, + 0xdf, 0x47, 0x5b, 0xa0, 0x33, 0x8a, 0x82, 0x58, 0x60, 0xc4, 0x65, 0x1b, 0x2a, 0x59, 0xd5, 0xcc, + 0x60, 0xa5, 0x38, 0xb9, 0x0a, 0x24, 0x27, 0x4f, 0xa8, 0xef, 0x78, 0x09, 0x7b, 0x5b, 0xb2, 0x73, + 0x99, 0x8f, 0x95, 0xa1, 0xfd, 0x19, 0x34, 0x06, 0x61, 0xf6, 0x28, 0x05, 0x5b, 0xe8, 0x32, 0x2e, + 0xd2, 0x13, 0xe8, 0x42, 0xf1, 0x1a, 0x8b, 0xc5, 0x7b, 0x09, 0x40, 0xa9, 0x2f, 0x9c, 0xaf, 0x15, + 0xb2, 0xef, 0xf4, 0x9f, 0xae, 0x41, 0x61, 0x57, 0xb6, 0x0b, 0x72, 0x13, 0xb6, 0x06, 0x9c, 0x07, + 0x36, 0xa3, 0x02, 0xc9, 0x79, 0xdd, 0x44, 0x96, 0x76, 0xaf, 0xf5, 0x55, 0x3b, 0x9d, 0x8e, 0x71, + 0xcd, 0x20, 0x9f, 0xc0, 0x56, 0x56, 0xaa, 0xc4, 0xd4, 0xcc, 0xc3, 0xd5, 0x5b, 0x7f, 0x23, 0xef, + 0x4f, 0x2b, 0x36, 0xc9, 0xd7, 0x0c, 0x72, 0x03, 0x36, 0xef, 0xc6, 0x63, 0x8f, 0xf1, 0x09, 0x59, + 0xf5, 0xcc, 0xfa, 0x76, 0x37, 0xfd, 0x1b, 0xd2, 0xd5, 0xff, 0x39, 0xba, 0xb7, 0xa7, 0xa1, 0x98, + 0x77, 0x0c, 0x32, 0x84, 0xd3, 0x6a, 0x69, 0x22, 0x69, 0xae, 0x6e, 0x87, 0xa9, 0x3f, 0x2f, 0xed, + 0x97, 0xfd, 0x6f, 0x0d, 0x28, 0xa5, 0x41, 0x1a, 0x52, 0x9f, 0xba, 0x18, 0x91, 0x2f, 0xa0, 0x9e, + 0x06, 0x1f, 0xa3, 0xa3, 0x69, 0x21, 0x97, 0xb5, 0xe2, 0x8b, 0x53, 0xb6, 0xea, 0x05, 0x48, 0x1f, + 0xb6, 0x3e, 0x42, 0xa1, 0x16, 0x74, 0x96, 0x89, 0xa5, 0x25, 0x5f, 0x2f, 0x2f, 0xc3, 0xbb, 0x37, + 0xbe, 0x7f, 0xd6, 0x30, 0x7e, 0x78, 0xd6, 0x30, 0x7e, 0x79, 0xd6, 0x30, 0xbe, 0xfe, 0xb5, 0xf1, + 0xbf, 0xcf, 0xdf, 0x7a, 0xf5, 0x9f, 0x4d, 0xe3, 0x82, 0xf4, 0xe0, 0x9d, 0x3f, 0x03, 0x00, 0x00, + 0xff, 0xff, 0x1f, 0x69, 0x15, 0xd0, 0xa1, 0x12, 0x00, 0x00, +} diff --git a/api/broker/broker.proto b/api/broker/broker.proto new file mode 100644 index 000000000..5916a6dcc --- /dev/null +++ b/api/broker/broker.proto @@ -0,0 +1,161 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "ttn/api/api.proto"; +import "ttn/api/protocol/protocol.proto"; +import "ttn/api/gateway/gateway.proto"; + +package broker; + +option go_package = "github.com/TheThingsNetwork/ttn/api/broker"; + +message DownlinkOption { + string identifier = 1; + string gateway_id = 2; + uint32 score = 3; // lower is better, 0 is best + int64 deadline = 4; // deadline time at server represented as the number of nanoseconds elapsed since January 1, 1970 UTC + protocol.TxConfiguration protocol_config = 5; + gateway.TxConfiguration gateway_config = 6; +} + +// received from the Router +message UplinkMessage { + bytes payload = 1; + protocol.Message message = 2; + // NOTE: For LoRaWAN, the Router doesn't know the DevEUI/ID and AppEUI/ID + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + repeated DownlinkOption downlink_options = 31; +} + +// received from the Handler, sent to the Router, used as Template +message DownlinkMessage { + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; + DownlinkOption downlink_option = 21; +} + +//sent to the Router, used as Template +message DeviceActivationResponse { + bytes payload = 1; + protocol.Message message = 2; + DownlinkOption downlink_option = 11; +} + +// sent to the Handler +message DeduplicatedUplinkMessage { + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; + protocol.RxMetadata protocol_metadata = 21; + repeated gateway.RxMetadata gateway_metadata = 22; + int64 server_time = 23; + DownlinkMessage response_template = 31; +} + +// received from the Router +message DeviceActivationRequest { + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + protocol.ActivationMetadata activation_metadata = 23; + repeated DownlinkOption downlink_options = 31; +} + +// sent to the Handler +message DeduplicatedDeviceActivationRequest { + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; + protocol.RxMetadata protocol_metadata = 21; + repeated gateway.RxMetadata gateway_metadata = 22; + protocol.ActivationMetadata activation_metadata = 23; + int64 server_time = 24; + DeviceActivationResponse response_template = 31; +} + +message ActivationChallengeRequest { + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; +} + +message ActivationChallengeResponse { + bytes payload = 1; + protocol.Message message = 2; +} + +// message SubscribeRequest is used by a Handler to subscribe to uplink messages +message SubscribeRequest {} + +// The Broker service provides pure network functionality +service Broker { + // Router initiates an Association with the Broker. + rpc Associate(stream UplinkMessage) returns (stream DownlinkMessage); + + // Handler subscribes to uplink stream. + rpc Subscribe(SubscribeRequest) returns (stream DeduplicatedUplinkMessage); + + // Handler initiates downlink stream. + rpc Publish(stream DownlinkMessage) returns (google.protobuf.Empty); + + // Router requests device activation + rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); +} + +// message StatusRequest is used to request the status of this Broker +message StatusRequest {} + +message Status { + api.SystemStats system = 1; + api.ComponentStats component = 2; + + api.Rates uplink = 11; + api.Rates uplink_unique = 12; + api.Rates downlink = 13; + api.Rates activations = 14; + api.Rates activations_unique = 15; + api.Percentiles deduplication = 16; + + // Connections + uint32 connected_routers = 21; + uint32 connected_handlers = 22; +} + +message ApplicationHandlerRegistration { + string app_id = 1; + string handler_id = 2; +} + +// The BrokerManager service provides configuration and monitoring functionality +service BrokerManager { + // Handler announces a new application to Broker. This is a temporary method that will be removed + // when we can push updates from the Discovery service to the routing services. + rpc RegisterApplicationHandler(ApplicationHandlerRegistration) returns (google.protobuf.Empty); + // Network operator requests Broker status + rpc GetStatus(StatusRequest) returns (Status); +} diff --git a/api/broker/client_streams.go b/api/broker/client_streams.go new file mode 100644 index 000000000..d90f20e69 --- /dev/null +++ b/api/broker/client_streams.go @@ -0,0 +1,397 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "io" + "sync" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +// Stream interface +type Stream interface { + SetLogger(log.Interface) + Close() +} + +type stream struct { + closing bool + setup sync.WaitGroup + ctx log.Interface + client BrokerClient +} + +func (s *stream) SetLogger(logger log.Interface) { + s.ctx = logger +} + +// DefaultBufferSize indicates the default send and receive buffer sizes +var DefaultBufferSize = 10 + +// RouterStream for sending gateway statuses +type RouterStream interface { + Stream + Send(*UplinkMessage) error + Channel() <-chan *DownlinkMessage +} + +// NewMonitoredRouterStream starts and monitors a RouterStream +func NewMonitoredRouterStream(client BrokerClient, getContextFunc func() context.Context) RouterStream { + s := &routerStream{ + up: make(chan *UplinkMessage, DefaultBufferSize), + down: make(chan *DownlinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = log.Get() + + go func() { + var retries int + + for { + // Session channels + up := make(chan *UplinkMessage) + errCh := make(chan error) + + // Session client + var ctx context.Context + ctx, s.cancel = context.WithCancel(getContextFunc()) + client, err := s.client.Associate(ctx) + s.setup.Done() + if err != nil { + s.ctx.WithError(err).Warn("Could not start Associate stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Debug("Started Associate stream") + + // Receive downlink errors + go func() { + for { + message, err := client.Recv() + if message != nil { + s.ctx.Debug("Receiving Downlink message") + select { + case s.down <- message: + default: + s.ctx.Warn("Dropping Downlink message, buffer full") + } + } + if err != nil { + errCh <- err + break + } + } + close(errCh) + }() + + // Send uplink + go func() { + for message := range up { + s.ctx.Debug("Sending Uplink message") + if err := client.Send(message); err != nil { + s.ctx.WithError(err).Warn("Error sending Uplink message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case mErr = <-errCh: + break monitor + case msg, ok := <-s.up: + if !ok { + break monitor // channel closed + } + up <- msg + } + } + + close(up) + client.CloseSend() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped Associate stream") + } else { + s.ctx.WithError(mErr).Warn("Error in Associate stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type routerStream struct { + stream + cancel context.CancelFunc + up chan *UplinkMessage + down chan *DownlinkMessage + err chan error +} + +func (s *routerStream) Send(uplink *UplinkMessage) error { + select { + case s.up <- uplink: + default: + s.ctx.Warn("Dropping Uplink message, buffer full") + } + return nil +} + +func (s *routerStream) Channel() <-chan *DownlinkMessage { + return s.down +} + +func (s *routerStream) Close() { + s.closing = true + close(s.up) + if s.cancel != nil { + s.cancel() + } +} + +// HandlerPublishStream for sending downlink messages to the broker +type HandlerPublishStream interface { + Stream + Send(*DownlinkMessage) error +} + +// NewMonitoredHandlerPublishStream starts and monitors a HandlerPublishStream +func NewMonitoredHandlerPublishStream(client BrokerClient, getContextFunc func() context.Context) HandlerPublishStream { + s := &handlerPublishStream{ + ch: make(chan *DownlinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = log.Get() + + go func() { + var retries int + + for { + // Session channels + ch := make(chan *DownlinkMessage) + errCh := make(chan error) + + // Session client + client, err := s.client.Publish(getContextFunc()) + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + break + } + s.ctx.WithError(err).Warn("Could not start Downlink stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Info("Started Downlink stream") + + // Receive errors + go func() { + empty := new(empty.Empty) + if err := client.RecvMsg(empty); err != nil { + errCh <- err + } + close(errCh) + }() + + // Send + go func() { + for message := range ch { + s.ctx.Debug("Sending Downlink message") + if err := client.Send(message); err != nil { + s.ctx.WithError(err).Warn("Error sending Downlink message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case mErr = <-errCh: + break monitor + case msg, ok := <-s.ch: + if !ok { + break monitor // channel closed + } + ch <- msg + } + } + + close(ch) + client.CloseAndRecv() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + } else { + s.ctx.WithError(mErr).Warn("Error in Downlink stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type handlerPublishStream struct { + stream + ch chan *DownlinkMessage + err chan error +} + +func (s *handlerPublishStream) Send(message *DownlinkMessage) error { + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Downlink message, buffer full") + } + return nil +} + +func (s *handlerPublishStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Downlink stream") + s.closing = true + close(s.ch) +} + +// HandlerSubscribeStream for receiving uplink messages +type HandlerSubscribeStream interface { + Stream + Channel() <-chan *DeduplicatedUplinkMessage +} + +// NewMonitoredHandlerSubscribeStream starts and monitors a HandlerSubscribeStream +func NewMonitoredHandlerSubscribeStream(client BrokerClient, getContextFunc func() context.Context) HandlerSubscribeStream { + s := &handlerSubscribeStream{ + ch: make(chan *DeduplicatedUplinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = log.Get() + + go func() { + var client Broker_SubscribeClient + var err error + var retries int + var message *DeduplicatedUplinkMessage + + for { + // Session client + var ctx context.Context + ctx, s.cancel = context.WithCancel(getContextFunc()) + client, err = s.client.Subscribe(ctx, &SubscribeRequest{}) + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + break + } + s.ctx.WithError(err).Warn("Could not start Uplink stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Info("Started Uplink stream") + + for { + message, err = client.Recv() + if message != nil { + s.ctx.Debug("Receiving Uplink message") + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Uplink message, buffer full") + } + } + if err != nil { + break + } + } + + if err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + } else { + s.ctx.WithError(err).Warn("Error in Uplink stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + + close(s.ch) + }() + return s +} + +type handlerSubscribeStream struct { + stream + cancel context.CancelFunc + ch chan *DeduplicatedUplinkMessage + err chan error +} + +func (s *handlerSubscribeStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Uplink stream") + s.closing = true + if s.cancel != nil { + s.cancel() + } +} + +func (s *handlerSubscribeStream) Channel() <-chan *DeduplicatedUplinkMessage { + return s.ch +} diff --git a/api/broker/communication_test.go b/api/broker/communication_test.go new file mode 100644 index 000000000..47f2155d7 --- /dev/null +++ b/api/broker/communication_test.go @@ -0,0 +1,223 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "fmt" + "math/rand" + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/api" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +func newTestBroker() *testBroker { + return &testBroker{ + BrokerStreamServer: NewBrokerStreamServer(), + } +} + +type testBroker struct { + *BrokerStreamServer +} + +var _ BrokerServer = &testBroker{} + +func (s *testBroker) Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) { + return nil, grpc.Errorf(codes.Unimplemented, "Not implemented") +} + +func (s *testBroker) Serve(port int) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + srv := grpc.NewServer() + RegisterBrokerServer(srv, s) + srv.Serve(lis) +} + +func TestHandlerBrokerCommunication(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestHandlerBrokerCommunication") + log.Set(apex.Wrap(ctx)) + + brk := newTestBroker() + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go brk.Serve(port) + + conn, _ := api.Dial(fmt.Sprintf("localhost:%d", port)) + + { + brk.HandlerPublishChanFunc = func(md metadata.MD) (chan *DownlinkMessage, error) { + ch := make(chan *DownlinkMessage, 1) + go func() { + ctx.Info("[SERVER] Channel opened") + for message := range ch { + ctx.WithField("Message", message).Info("[SERVER] Received Downlink") + } + ctx.Info("[SERVER] Channel closed") + }() + return ch, nil + } + + brkClient := NewBrokerClient(conn) + downlink := NewMonitoredHandlerPublishStream(brkClient, func() context.Context { + return context.Background() + }) + + err := downlink.Send(&DownlinkMessage{ + Payload: []byte{1, 2, 3, 4}, + }) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + downlink.Close() + + time.Sleep(10 * time.Millisecond) + } + + { + brk.HandlerSubscribeChanFunc = func(md metadata.MD) (<-chan *DeduplicatedUplinkMessage, func(), error) { + ch := make(chan *DeduplicatedUplinkMessage, 1) + stop := make(chan struct{}) + cancel := func() { + ctx.Info("[SERVER] Canceling uplink") + close(stop) + } + go func() { + loop: + for { + select { + case <-stop: + break loop + case <-time.After(5 * time.Millisecond): + ctx.Info("[SERVER] Sending Uplink") + ch <- &DeduplicatedUplinkMessage{ + Payload: []byte{1, 2, 3, 4}, + } + } + } + close(ch) + ctx.Info("[SERVER] Closed Uplink") + }() + return ch, cancel, nil + } + + brkClient := NewBrokerClient(conn) + uplink := NewMonitoredHandlerSubscribeStream(brkClient, func() context.Context { + return context.Background() + }) + + ch := uplink.Channel() + + go func() { + for uplink := range ch { + ctx.WithField("Uplink", uplink).Info("[CLIENT] Received Uplink") + } + ctx.Info("[CLIENT] Closed Uplink") + }() + + time.Sleep(10 * time.Millisecond) + + uplink.Close() + + time.Sleep(10 * time.Millisecond) + } + +} + +func TestRouterBrokerCommunication(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestRouterBrokerCommunication") + log.Set(apex.Wrap(ctx)) + + brk := newTestBroker() + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go brk.Serve(port) + + conn, _ := api.Dial(fmt.Sprintf("localhost:%d", port)) + + { + brk.RouterAssociateChanFunc = func(md metadata.MD) (chan *UplinkMessage, <-chan *DownlinkMessage, func(), error) { + up := make(chan *UplinkMessage, 1) + down := make(chan *DownlinkMessage, 1) + + stop := make(chan struct{}) + cancel := func() { + ctx.Info("[SERVER] Canceling downlink") + close(stop) + } + + go func() { + ctx.Info("[SERVER] Uplink channel opened") + for message := range up { + ctx.WithField("Message", message).Info("[SERVER] Received Uplink") + } + ctx.Info("[SERVER] Uplink channel closed") + }() + + go func() { + loop: + for { + select { + case <-stop: + break loop + case <-time.After(5 * time.Millisecond): + ctx.Info("[SERVER] Sending Downlink") + down <- &DownlinkMessage{ + Payload: []byte{1, 2, 3, 4}, + } + } + } + close(down) + ctx.Info("[SERVER] Closed Downlink") + }() + + return up, down, cancel, nil + } + + brkClient := NewBrokerClient(conn) + stream := NewMonitoredRouterStream(brkClient, func() context.Context { + return context.Background() + }) + + ch := stream.Channel() + + go func() { + for downlink := range ch { + ctx.WithField("Downlink", downlink).Info("[CLIENT] Received Downlink") + } + ctx.Info("[CLIENT] Closed Downlink") + }() + + err := stream.Send(&UplinkMessage{ + Payload: []byte{1, 2, 3, 4}, + }) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + stream.Close() + + time.Sleep(10 * time.Millisecond) + } + +} diff --git a/api/broker/message_marshaling.go b/api/broker/message_marshaling.go new file mode 100644 index 000000000..1b52b86c9 --- /dev/null +++ b/api/broker/message_marshaling.go @@ -0,0 +1,225 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "errors" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/brocaar/lorawan" +) + +func msgFromPayload(payload []byte) (*pb_protocol.Message, error) { + var phy lorawan.PHYPayload + if err := phy.UnmarshalBinary(payload); err != nil { + return nil, err + } + msg := pb_lorawan.MessageFromPHYPayload(phy) + return &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}}, nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *UplinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DownlinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationResponse) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeduplicatedUplinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationRequest) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeduplicatedDeviceActivationRequest) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *ActivationChallengeRequest) UnmarshalPayload() error { + if m.GetMessage() == nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *ActivationChallengeResponse) UnmarshalPayload() error { + if m.GetMessage() == nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +func payloadFromMsg(msg *pb_protocol.Message) ([]byte, error) { + if msg.GetLorawan() == nil { + return nil, errors.New("No LoRaWAN message to marshal") + } + phy := msg.GetLorawan().PHYPayload() + bin, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + return bin, nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *UplinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DownlinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationResponse) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeduplicatedUplinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeduplicatedDeviceActivationRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *ActivationChallengeRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *ActivationChallengeResponse) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} diff --git a/api/broker/message_marshaling_test.go b/api/broker/message_marshaling_test.go new file mode 100644 index 000000000..e5a5eb910 --- /dev/null +++ b/api/broker/message_marshaling_test.go @@ -0,0 +1,165 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type payloadMarshalerUnmarshaler interface { + UnmarshalPayload() error + MarshalPayload() error +} + +func TestMarshalUnmarshalPayload(t *testing.T) { + a := New(t) + + var subjects []payloadMarshalerUnmarshaler + + // Do nothing when message and payload are nil + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{}, + &DownlinkMessage{}, + &DeviceActivationResponse{}, + &DeduplicatedUplinkMessage{}, + &DeviceActivationRequest{}, + &DeduplicatedDeviceActivationRequest{}, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + + rxMeta := &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}} + txConf := &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}} + + macMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_UNCONFIRMED_UP, + }, + Payload: &pb_lorawan.Message_MacPayload{MacPayload: &pb_lorawan.MACPayload{ + FHDR: pb_lorawan.FHDR{ + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + macBin := []byte{65, 4, 3, 2, 1, 0, 1, 0, 0, 1, 2, 3, 4} + joinReqMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_REQUEST, + }, + Payload: &pb_lorawan.Message_JoinRequestPayload{JoinRequestPayload: &pb_lorawan.JoinRequestPayload{ + AppEui: types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEui: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevNonce: types.DevNonce([2]byte{1, 2}), + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinReqBin := []byte{1, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 2, 1, 1, 2, 3, 4} + joinAccMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_ACCEPT, + }, + Payload: &pb_lorawan.Message_JoinAcceptPayload{JoinAcceptPayload: &pb_lorawan.JoinAcceptPayload{ + AppNonce: types.AppNonce([3]byte{1, 2, 3}), + NetId: types.NetID([3]byte{1, 2, 3}), + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + DLSettings: pb_lorawan.DLSettings{ + Rx2Dr: 3, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinAccBin := []byte{33, 3, 2, 1, 3, 2, 1, 4, 3, 2, 1, 3, 0, 1, 2, 3, 4} + + // Only Marshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Message: macMsg, + }, + &DownlinkMessage{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Message: macMsg, + }, + &DeviceActivationResponse{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Message: joinAccMsg, + }, + &DeduplicatedUplinkMessage{ + ProtocolMetadata: rxMeta, + Message: macMsg, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Message: joinReqMsg, + }, + &DeduplicatedDeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Message: joinReqMsg, + }, + &ActivationChallengeRequest{ + Message: joinReqMsg, + }, + &ActivationChallengeResponse{ + Message: joinReqMsg, + }, + } + + for _, sub := range subjects { + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + a.So(sub.MarshalPayload(), ShouldEqual, nil) + } + + // Only Unmarshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Payload: macBin, + }, + &DownlinkMessage{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Payload: macBin, + }, + &DeviceActivationResponse{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Payload: joinAccBin, + }, + &DeduplicatedUplinkMessage{ + ProtocolMetadata: rxMeta, + Payload: macBin, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Payload: joinReqBin, + }, + &DeduplicatedDeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Payload: joinReqBin, + }, + &ActivationChallengeRequest{ + Payload: joinReqBin, + }, + &ActivationChallengeResponse{ + Payload: joinReqBin, + }, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + +} diff --git a/api/broker/server_streams.go b/api/broker/server_streams.go new file mode 100644 index 000000000..b89056680 --- /dev/null +++ b/api/broker/server_streams.go @@ -0,0 +1,146 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "io" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc/metadata" +) + +// BrokerStreamServer handles gRPC streams as channels +type BrokerStreamServer struct { + ctx log.Interface + RouterAssociateChanFunc func(md metadata.MD) (up chan *UplinkMessage, down <-chan *DownlinkMessage, cancel func(), err error) + HandlerSubscribeChanFunc func(md metadata.MD) (ch <-chan *DeduplicatedUplinkMessage, cancel func(), err error) + HandlerPublishChanFunc func(md metadata.MD) (ch chan *DownlinkMessage, err error) +} + +// NewBrokerStreamServer returns a new BrokerStreamServer +func NewBrokerStreamServer() *BrokerStreamServer { + return &BrokerStreamServer{ + ctx: log.Get(), + } +} + +// SetLogger sets the logger +func (s *BrokerStreamServer) SetLogger(logger log.Interface) { + s.ctx = logger +} + +// Associate handles uplink streams from and downlink streams to the router +func (s *BrokerStreamServer) Associate(stream Broker_AssociateServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + upChan, downChan, downCancel, err := s.RouterAssociateChanFunc(md) + if err != nil { + return err + } + defer func() { + ctx := s.ctx + if err != nil { + ctx = ctx.WithError(err) + } + downCancel() + close(upChan) + ctx.Debug("Closed Associate stream") + }() + + upErr := make(chan error) + go func() (err error) { + defer func() { + if err != nil { + upErr <- err + } + close(upErr) + }() + for { + uplink, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + if err := uplink.Validate(); err != nil { + return errors.Wrap(err, "Invalid Uplink") + } + upChan <- uplink + } + }() + + for { + select { + case err, errPresent := <-upErr: + if !errPresent { + return nil // stream closed + } + return err + case downlink, downlinkPresent := <-downChan: + if !downlinkPresent { + return nil // stream closed + } + if err := stream.Send(downlink); err != nil { + return err + } + } + } +} + +// Subscribe handles uplink streams towards the handler +func (s *BrokerStreamServer) Subscribe(req *SubscribeRequest, stream Broker_SubscribeServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, cancel, err := s.HandlerSubscribeChanFunc(md) + if err != nil { + return err + } + go func() { + <-stream.Context().Done() + err = stream.Context().Err() + cancel() + }() + for uplink := range ch { + if err := stream.Send(uplink); err != nil { + return err + } + } + return +} + +// Publish handles downlink streams from the handler +func (s *BrokerStreamServer) Publish(stream Broker_PublishServer) error { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, err := s.HandlerPublishChanFunc(md) + if err != nil { + return err + } + defer func() { + close(ch) + }() + for { + downlink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + if err := downlink.Validate(); err != nil { + return errors.Wrap(err, "Invalid Downlink") + } + ch <- downlink + } +} diff --git a/api/broker/validation.go b/api/broker/validation.go new file mode 100644 index 000000000..f4db3cc83 --- /dev/null +++ b/api/broker/validation.go @@ -0,0 +1,131 @@ +package broker + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validate implements the api.Validator interface +func (m *DownlinkOption) Validate() error { + if m.Identifier == "" { + return errors.NewErrInvalidArgument("Identifier", "can not be empty") + } + if m.GatewayId == "" { + return errors.NewErrInvalidArgument("GatewayId", "can not be empty") + } + if err := api.NotNilAndValid(m.ProtocolConfig, "ProtocolConfig"); err != nil { + return err + } + if err := api.NotNilAndValid(m.GatewayConfig, "GatewayConfig"); err != nil { + return err + } + return nil +} + +// Validate implements the api.Validator interface +func (m *UplinkMessage) Validate() error { + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err + } + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DownlinkMessage) Validate() error { + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { + return err + } + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + + if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DeduplicatedUplinkMessage) Validate() error { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { + return err + } + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err + } + if m.ResponseTemplate != nil { + if err := m.ResponseTemplate.Validate(); err != nil { + return errors.NewErrInvalidArgument("ResponseTemplate", err.Error()) + } + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DeviceActivationRequest) Validate() error { + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err + } + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err + } + if err := api.NotNilAndValid(m.ActivationMetadata, "ActivationMetadata"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DeduplicatedDeviceActivationRequest) Validate() error { + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *ActivationChallengeRequest) Validate() error { + return nil +} + +// Validate implements the api.Validator interface +func (m *ApplicationHandlerRegistration) Validate() error { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + if m.HandlerId == "" { + return errors.NewErrInvalidArgument("HandlerId", "can not be empty") + } + return nil +} diff --git a/api/context.go b/api/context.go new file mode 100644 index 000000000..99ff509b5 --- /dev/null +++ b/api/context.go @@ -0,0 +1,27 @@ +package api + +import "context" + +func TokenFromContext(ctx context.Context) (token string, err error) { + md, err := MetadataFromContext(ctx) + if err != nil { + return "", err + } + return TokenFromMetadata(md) +} + +func KeyFromContext(ctx context.Context) (key string, err error) { + md, err := MetadataFromContext(ctx) + if err != nil { + return "", err + } + return KeyFromMetadata(md) +} + +func IDFromContext(ctx context.Context) (token string, err error) { + md, err := MetadataFromContext(ctx) + if err != nil { + return "", err + } + return IDFromMetadata(md) +} diff --git a/api/dial.go b/api/dial.go new file mode 100644 index 000000000..5a17133d8 --- /dev/null +++ b/api/dial.go @@ -0,0 +1,117 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package api + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "strings" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +// KeepAlive indicates the keep-alive time for the Dialer +var KeepAlive = 10 * time.Second + +// MaxRetries indicates how often clients should retry dialing a component +var MaxRetries = 100 + +// DialOptions to use in TTN gRPC +var DialOptions = []grpc.DialOption{ + WithTTNDialer(), + grpc.WithBlock(), + grpc.FailOnNonTempDialError(true), +} + +func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.ClientConn, err error) { + ctx := log.Get().WithField("Address", address) + opts := DialOptions + if tlsConfig != nil { + tlsConfig.ServerName = strings.SplitN(address, ":", 2)[0] // trim the port + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + } else { + opts = append(opts, grpc.WithInsecure()) + } + conn, err = grpc.Dial( + address, + opts..., + ) + if err == nil { + return + } + + switch err := err.(type) { + case x509.CertificateInvalidError, + x509.ConstraintViolationError, + x509.HostnameError, + x509.InsecureAlgorithmError, + x509.SystemRootsError, + x509.UnhandledCriticalExtension, + x509.UnknownAuthorityError: + // Non-temporary error while connecting to a TLS-enabled server + return nil, err + case tls.RecordHeaderError: + if fallback { + ctx.WithError(err).Warn("Could not connect to gRPC server with TLS, reconnecting without it...") + return dial(address, nil, fallback) + } + return nil, err + } + + log.Get().WithField("ErrType", fmt.Sprintf("%T", err)).WithError(err).Error("Unhandled dial error [please create issue on Github]") + return nil, err +} + +// RootCAs to use in API connections +var RootCAs *x509.CertPool + +func init() { + RootCAs, _ = x509.SystemCertPool() +} + +// Dial an address +func Dial(address string) (*grpc.ClientConn, error) { + tlsConfig := &tls.Config{RootCAs: RootCAs} + return dial(address, tlsConfig, true) +} + +// DialWithCert dials the address using the given TLS cert +func DialWithCert(address string, cert string) (*grpc.ClientConn, error) { + rootCAs := x509.NewCertPool() + ok := rootCAs.AppendCertsFromPEM([]byte(cert)) + if !ok { + panic("failed to parse root certificate") + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + return dial(address, tlsConfig, false) +} + +// WithTTNDialer creates a dialer for TTN +func WithTTNDialer() grpc.DialOption { + return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + ctx := log.Get().WithField("Address", addr) + d := net.Dialer{Timeout: timeout, KeepAlive: KeepAlive} + var retries int + for { + conn, err := d.Dial("tcp", addr) + if err == nil { + ctx.Debug("Connected to gRPC server") + return conn, nil + } + if err, ok := err.(*net.OpError); ok && err.Op == "dial" && retries <= MaxRetries { + ctx.WithError(err).Debug("Could not connect to gRPC server, reconnecting...") + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + return nil, err + } + }) +} diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md new file mode 100644 index 000000000..d23a5c41d --- /dev/null +++ b/api/discovery/Discovery.md @@ -0,0 +1,187 @@ +# Discovery API Reference + +The Discovery service is used to discover services within The Things Network. + +## Methods + +### `Announce` + +Announce a component to the Discovery server. +A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. +Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. + +- Request: [`Announcement`](#discoveryannouncement) +- Response: [`Empty`](#discoveryannouncement) + +### `GetAll` + +Get all announcements for a specific service type + +- Request: [`GetServiceRequest`](#discoverygetservicerequest) +- Response: [`AnnouncementsResponse`](#discoverygetservicerequest) + +#### HTTP Endpoint + +- `GET` `/announcements/{service_name}`(`service_name` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "service_name": "handler" +} +``` + +#### JSON Response Format + +```json +{ + "services": [ + { + "api_address": "http://eu.thethings.network:8084", + "certificate": "-----BEGIN CERTIFICATE-----\n...", + "description": "", + "id": "ttn-handler-eu", + "metadata": [ + { + "app_eui": "", + "app_id": "some-app-id", + "dev_addr_prefix": "AAAAAAA=" + } + ], + "net_address": "eu.thethings.network:1904", + "public": true, + "public_key": "-----BEGIN PUBLIC KEY-----\n...", + "service_name": "handler", + "service_version": "2.0.0-abcdef...", + "url": "" + } + ] +} +``` + +### `Get` + +Get a specific announcement + +- Request: [`GetRequest`](#discoverygetrequest) +- Response: [`Announcement`](#discoverygetrequest) + +#### HTTP Endpoint + +- `GET` `/announcements/{service_name}/{id}`(`service_name`, `id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "id": "ttn-handler-eu", + "service_name": "handler" +} +``` + +#### JSON Response Format + +```json +{ + "api_address": "http://eu.thethings.network:8084", + "certificate": "-----BEGIN CERTIFICATE-----\n...", + "description": "", + "id": "ttn-handler-eu", + "metadata": [ + { + "app_eui": "", + "app_id": "some-app-id", + "dev_addr_prefix": "AAAAAAA=" + } + ], + "net_address": "eu.thethings.network:1904", + "public": true, + "public_key": "-----BEGIN PUBLIC KEY-----\n...", + "service_name": "handler", + "service_version": "2.0.0-abcdef...", + "url": "" +} +``` + +### `AddMetadata` + +Add metadata to an announement + +- Request: [`MetadataRequest`](#discoverymetadatarequest) +- Response: [`Empty`](#discoverymetadatarequest) + +### `DeleteMetadata` + +Delete metadata from an announcement + +- Request: [`MetadataRequest`](#discoverymetadatarequest) +- Response: [`Empty`](#discoverymetadatarequest) + +## Messages + +### `.discovery.Announcement` + +The Announcement of a service (also called component) + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `id` | `string` | The ID of the component | +| `service_name` | `string` | The name of the component (router/broker/handler) | +| `service_version` | `string` | Service version in the form "[version]-[commit] ([build date])" | +| `description` | `string` | Description of the component | +| `url` | `string` | URL with documentation or more information about this component | +| `public` | `bool` | Indicates whether this service is part of The Things Network (the public community network) | +| `net_address` | `string` | Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) | +| `public_key` | `string` | ECDSA public key of this component | +| `certificate` | `string` | TLS Certificate (if TLS is enabled) | +| `api_address` | `string` | Contains the address where the HTTP API is exposed (if there is one) | +| `metadata` | _repeated_ [`Metadata`](#discoverymetadata) | Metadata for this component | + +### `.discovery.AnnouncementsResponse` + +A list of announcements + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `services` | _repeated_ [`Announcement`](#discoveryannouncement) | | + +### `.discovery.GetRequest` + +The identifier of the service that should be returned + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `id` | `string` | The ID of the service | +| `service_name` | `string` | The name of the service (router/broker/handler) | + +### `.discovery.GetServiceRequest` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `service_name` | `string` | The name of the service (router/broker/handler) | + +### `.discovery.Metadata` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| **metadata** | **oneof 3** | one of the following 3 | +| `dev_addr_prefix` | `bytes` | DevAddr prefix that is routed by this Broker 5 bytes; the first byte is the prefix length, the following 4 bytes are the address. Only authorized Brokers can announce PREFIX metadata. | +| `app_id` | `string` | AppID that is registered to this Handler This metadata can only be added if the requesting client is authorized to manage this AppID. | +| `app_eui` | `bytes` | AppEUI that is registered to this Join Handler Only authorized Join Handlers can announce APP_EUI metadata (and we don't have any of those yet). | + +### `.discovery.MetadataRequest` + +The metadata to add or remove from an announement + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `id` | `string` | The ID of the service that should be modified | +| `service_name` | `string` | The name of the service (router/broker/handler) that should be modified | +| `metadata` | [`Metadata`](#discoverymetadata) | Metadata to add or remove | + +### `.google.protobuf.Empty` + +A generic empty message that you can re-use to avoid defining duplicated +empty messages in your APIs. + diff --git a/api/discovery/client.go b/api/discovery/client.go new file mode 100644 index 000000000..5d7358f33 --- /dev/null +++ b/api/discovery/client.go @@ -0,0 +1,248 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package discovery + +import ( + "fmt" + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/bluele/gcache" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711 + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// CacheSize indicates the number of components that are cached +var CacheSize = 1000 + +// CacheExpiration indicates the time a cached item is valid +var CacheExpiration = 5 * time.Minute + +// Client is used as the main client to the Discovery server +type Client interface { + Announce(token string) error + GetAll(serviceName string) ([]*Announcement, error) + Get(serviceName, id string) (*Announcement, error) + AddDevAddrPrefix(prefix types.DevAddrPrefix) error + AddAppID(appID string, token string) error + RemoveDevAddrPrefix(prefix types.DevAddrPrefix) error + RemoveAppID(appID string, token string) error + GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) + GetAllHandlersForAppID(appID string) ([]*Announcement, error) + Close() error +} + +// NewClient returns a new Client +func NewClient(server string, announcement *Announcement, tokenFunc func() string) (Client, error) { + conn, err := api.Dial(server) + if err != nil { + return nil, err + } + client := &DefaultClient{ + lists: make(map[string][]*Announcement), + listsUpdated: make(map[string]time.Time), + self: announcement, + tokenFunc: tokenFunc, + conn: conn, + client: NewDiscoveryClient(conn), + } + client.cache = gcache. + New(CacheSize). + Expiration(CacheExpiration). + ARC(). + LoaderFunc(func(k interface{}) (interface{}, error) { + key, ok := k.(cacheKey) + if !ok { + return nil, fmt.Errorf("wrong type for cacheKey: %T", k) + } + return client.get(key.serviceName, key.id) + }). + Build() + return client, nil +} + +// DefaultClient is a wrapper around DiscoveryClient +type DefaultClient struct { + sync.Mutex + cache gcache.Cache + listsUpdated map[string]time.Time + lists map[string][]*Announcement + self *Announcement + tokenFunc func() string + conn *grpc.ClientConn + client DiscoveryClient +} + +type cacheKey struct { + serviceName string + id string +} + +func (c *DefaultClient) getContext(token string) context.Context { + if token == "" { + token = c.tokenFunc() + } + md := metadata.Pairs( + "service-name", c.self.ServiceName, + "id", c.self.Id, + "token", token, + "net-address", c.self.NetAddress, + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} + +func (c *DefaultClient) get(serviceName, id string) (*Announcement, error) { + res, err := c.client.Get(c.getContext(""), &GetRequest{ + ServiceName: serviceName, + Id: id, + }) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *DefaultClient) getAll(serviceName string) ([]*Announcement, error) { + res, err := c.client.GetAll(c.getContext(""), &GetServiceRequest{ServiceName: serviceName}) + if err != nil { + return nil, err + } + c.lists[serviceName] = res.Services + c.listsUpdated[serviceName] = time.Now() + for _, announcement := range res.Services { + c.cache.Set(&cacheKey{serviceName: announcement.ServiceName, id: announcement.Id}, announcement) + } + return res.Services, nil +} + +// Announce announces the configured announcement to the discovery server +func (c *DefaultClient) Announce(token string) error { + _, err := c.client.Announce(c.getContext(token), c.self) + return err +} + +// GetAll returns all services of the given service type +func (c *DefaultClient) GetAll(serviceName string) ([]*Announcement, error) { + c.Lock() + defer c.Unlock() + + // If list initialized, return cached version + if list, ok := c.lists[serviceName]; ok && len(list) > 0 { + // And update if expired + if c.listsUpdated[serviceName].Add(CacheExpiration).Before(time.Now()) { + go func() { + c.Lock() + defer c.Unlock() + c.getAll(serviceName) + }() + } + return list, nil + } + + // If list not initialized, do request + return c.getAll(serviceName) +} + +// Get returns the (cached) service annoucement for the given service type and id +func (c *DefaultClient) Get(serviceName, id string) (*Announcement, error) { + res, err := c.cache.Get(cacheKey{serviceName, id}) + if err != nil { + return nil, err + } + return res.(*Announcement), nil +} + +// AddDevAddrPrefix adds a DevAddrPrefix to the current component +func (c *DefaultClient) AddDevAddrPrefix(prefix types.DevAddrPrefix) error { + _, err := c.client.AddMetadata(c.getContext(""), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{Metadata: &Metadata_DevAddrPrefix{ + DevAddrPrefix: prefix.Bytes(), + }}, + }) + return err +} + +// AddAppID adds an AppID to the current component +func (c *DefaultClient) AddAppID(appID string, token string) error { + _, err := c.client.AddMetadata(c.getContext(token), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{Metadata: &Metadata_AppId{ + AppId: appID, + }}, + }) + return err +} + +// RemoveDevAddrPrefix removes a DevAddrPrefix from the current component +func (c *DefaultClient) RemoveDevAddrPrefix(prefix types.DevAddrPrefix) error { + _, err := c.client.DeleteMetadata(c.getContext(""), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{Metadata: &Metadata_DevAddrPrefix{ + DevAddrPrefix: prefix.Bytes(), + }}, + }) + return err +} + +// RemoveAppID removes an AppID from the current component +func (c *DefaultClient) RemoveAppID(appID string, token string) error { + _, err := c.client.DeleteMetadata(c.getContext(token), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{Metadata: &Metadata_AppId{ + AppId: appID, + }}, + }) + return err +} + +// GetAllBrokersForDevAddr returns all brokers that can handle the given DevAddr +func (c *DefaultClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) (announcements []*Announcement, err error) { + brokers, err := c.GetAll("broker") + if err != nil { + return nil, err + } +next: + for _, broker := range brokers { + for _, prefix := range broker.DevAddrPrefixes() { + if devAddr.HasPrefix(prefix) { + announcements = append(announcements, broker) + continue next + } + } + } + return +} + +// GetAllHandlersForAppID returns all handlers that can handle the given AppID +func (c *DefaultClient) GetAllHandlersForAppID(appID string) (announcements []*Announcement, err error) { + handlers, err := c.GetAll("handler") + if err != nil { + return nil, err + } +next: + for _, handler := range handlers { + for _, handlerAppID := range handler.AppIDs() { + if handlerAppID == appID { + announcements = append(announcements, handler) + continue next + } + } + } + return +} + +// Close purges the cache and closes the connection with the Discovery server +func (c *DefaultClient) Close() error { + c.cache.Purge() + return c.conn.Close() +} diff --git a/api/discovery/client_mock.go b/api/discovery/client_mock.go new file mode 100644 index 000000000..8a7ec081e --- /dev/null +++ b/api/discovery/client_mock.go @@ -0,0 +1,134 @@ +// Automatically generated by MockGen. DO NOT EDIT! +// Source: ./api/discovery/client.go + +package discovery + +import ( + types "github.com/TheThingsNetwork/ttn/core/types" + gomock "github.com/golang/mock/gomock" +) + +// Mock of Client interface +type MockClient struct { + ctrl *gomock.Controller + recorder *_MockClientRecorder +} + +// Recorder for MockClient (not exported) +type _MockClientRecorder struct { + mock *MockClient +} + +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &_MockClientRecorder{mock} + return mock +} + +func (_m *MockClient) EXPECT() *_MockClientRecorder { + return _m.recorder +} + +func (_m *MockClient) Announce(token string) error { + ret := _m.ctrl.Call(_m, "Announce", token) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) Announce(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Announce", arg0) +} + +func (_m *MockClient) GetAll(serviceName string) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAll", serviceName) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAll(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAll", arg0) +} + +func (_m *MockClient) Get(serviceName string, id string) (*Announcement, error) { + ret := _m.ctrl.Call(_m, "Get", serviceName, id) + ret0, _ := ret[0].(*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Get", arg0, arg1) +} + +func (_m *MockClient) AddDevAddrPrefix(prefix types.DevAddrPrefix) error { + ret := _m.ctrl.Call(_m, "AddDevAddrPrefix", prefix) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) AddDevAddrPrefix(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "AddDevAddrPrefix", arg0) +} + +func (_m *MockClient) AddAppID(appID string, token string) error { + ret := _m.ctrl.Call(_m, "AddAppID", appID, token) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) AddAppID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "AddAppID", arg0, arg1) +} + +func (_m *MockClient) RemoveDevAddrPrefix(prefix types.DevAddrPrefix) error { + ret := _m.ctrl.Call(_m, "RemoveDevAddrPrefix", prefix) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) RemoveDevAddrPrefix(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "RemoveDevAddrPrefix", arg0) +} + +func (_m *MockClient) RemoveAppID(appID string, token string) error { + ret := _m.ctrl.Call(_m, "RemoveAppID", appID, token) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) RemoveAppID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "RemoveAppID", arg0, arg1) +} + +func (_m *MockClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAllBrokersForDevAddr", devAddr) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAllBrokersForDevAddr(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllBrokersForDevAddr", arg0) +} + +func (_m *MockClient) GetAllHandlersForAppID(appID string) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAllHandlersForAppID", appID) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAllHandlersForAppID(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllHandlersForAppID", arg0) +} + +func (_m *MockClient) Close() error { + ret := _m.ctrl.Call(_m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) Close() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Close") +} diff --git a/api/discovery/dial.go b/api/discovery/dial.go new file mode 100644 index 000000000..bad0f934d --- /dev/null +++ b/api/discovery/dial.go @@ -0,0 +1,20 @@ +package discovery + +import ( + "errors" + "strings" + + "github.com/TheThingsNetwork/ttn/api" + "google.golang.org/grpc" +) + +// Dial dials the component represented by this Announcement +func (a *Announcement) Dial() (*grpc.ClientConn, error) { + if a.NetAddress == "" { + return nil, errors.New("Can not dial this component") + } + if a.Certificate == "" { + return api.Dial(strings.Split(a.NetAddress, ",")[0]) + } + return api.DialWithCert(strings.Split(a.NetAddress, ",")[0], a.Certificate) +} diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go new file mode 100644 index 000000000..35106377a --- /dev/null +++ b/api/discovery/discovery.pb.go @@ -0,0 +1,2065 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto +// DO NOT EDIT! + +/* + Package discovery is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto + + It has these top-level messages: + Metadata + Announcement + GetServiceRequest + GetRequest + MetadataRequest + AnnouncementsResponse +*/ +package discovery + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Metadata struct { + // Types that are valid to be assigned to Metadata: + // *Metadata_DevAddrPrefix + // *Metadata_AppId + // *Metadata_AppEui + Metadata isMetadata_Metadata `protobuf_oneof:"metadata"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } + +type isMetadata_Metadata interface { + isMetadata_Metadata() + MarshalTo([]byte) (int, error) + Size() int +} + +type Metadata_DevAddrPrefix struct { + DevAddrPrefix []byte `protobuf:"bytes,20,opt,name=dev_addr_prefix,json=devAddrPrefix,proto3,oneof"` +} +type Metadata_AppId struct { + AppId string `protobuf:"bytes,30,opt,name=app_id,json=appId,proto3,oneof"` +} +type Metadata_AppEui struct { + AppEui []byte `protobuf:"bytes,31,opt,name=app_eui,json=appEui,proto3,oneof"` +} + +func (*Metadata_DevAddrPrefix) isMetadata_Metadata() {} +func (*Metadata_AppId) isMetadata_Metadata() {} +func (*Metadata_AppEui) isMetadata_Metadata() {} + +func (m *Metadata) GetMetadata() isMetadata_Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Metadata) GetDevAddrPrefix() []byte { + if x, ok := m.GetMetadata().(*Metadata_DevAddrPrefix); ok { + return x.DevAddrPrefix + } + return nil +} + +func (m *Metadata) GetAppId() string { + if x, ok := m.GetMetadata().(*Metadata_AppId); ok { + return x.AppId + } + return "" +} + +func (m *Metadata) GetAppEui() []byte { + if x, ok := m.GetMetadata().(*Metadata_AppEui); ok { + return x.AppEui + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Metadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Metadata_OneofMarshaler, _Metadata_OneofUnmarshaler, _Metadata_OneofSizer, []interface{}{ + (*Metadata_DevAddrPrefix)(nil), + (*Metadata_AppId)(nil), + (*Metadata_AppEui)(nil), + } +} + +func _Metadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Metadata) + // metadata + switch x := m.Metadata.(type) { + case *Metadata_DevAddrPrefix: + _ = b.EncodeVarint(20<<3 | proto.WireBytes) + _ = b.EncodeRawBytes(x.DevAddrPrefix) + case *Metadata_AppId: + _ = b.EncodeVarint(30<<3 | proto.WireBytes) + _ = b.EncodeStringBytes(x.AppId) + case *Metadata_AppEui: + _ = b.EncodeVarint(31<<3 | proto.WireBytes) + _ = b.EncodeRawBytes(x.AppEui) + case nil: + default: + return fmt.Errorf("Metadata.Metadata has unexpected type %T", x) + } + return nil +} + +func _Metadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Metadata) + switch tag { + case 20: // metadata.dev_addr_prefix + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeRawBytes(true) + m.Metadata = &Metadata_DevAddrPrefix{x} + return true, err + case 30: // metadata.app_id + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Metadata = &Metadata_AppId{x} + return true, err + case 31: // metadata.app_eui + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeRawBytes(true) + m.Metadata = &Metadata_AppEui{x} + return true, err + default: + return false, nil + } +} + +func _Metadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Metadata) + // metadata + switch x := m.Metadata.(type) { + case *Metadata_DevAddrPrefix: + n += proto.SizeVarint(20<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.DevAddrPrefix))) + n += len(x.DevAddrPrefix) + case *Metadata_AppId: + n += proto.SizeVarint(30<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.AppId))) + n += len(x.AppId) + case *Metadata_AppEui: + n += proto.SizeVarint(31<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.AppEui))) + n += len(x.AppEui) + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +// The Announcement of a service (also called component) +type Announcement struct { + // The ID of the component + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the component (router/broker/handler) + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Service version in the form "[version]-[commit] ([build date])" + ServiceVersion string `protobuf:"bytes,3,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + // Description of the component + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + // URL with documentation or more information about this component + Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` + // Indicates whether this service is part of The Things Network (the public community network) + Public bool `protobuf:"varint,6,opt,name=public,proto3" json:"public,omitempty"` + // Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) + NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` + // ECDSA public key of this component + PublicKey string `protobuf:"bytes,12,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + // TLS Certificate (if TLS is enabled) + Certificate string `protobuf:"bytes,13,opt,name=certificate,proto3" json:"certificate,omitempty"` + // Contains the address where the HTTP API is exposed (if there is one) + ApiAddress string `protobuf:"bytes,14,opt,name=api_address,json=apiAddress,proto3" json:"api_address,omitempty"` + // Metadata for this component + Metadata []*Metadata `protobuf:"bytes,22,rep,name=metadata" json:"metadata,omitempty"` +} + +func (m *Announcement) Reset() { *m = Announcement{} } +func (m *Announcement) String() string { return proto.CompactTextString(m) } +func (*Announcement) ProtoMessage() {} +func (*Announcement) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{1} } + +func (m *Announcement) GetMetadata() []*Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type GetServiceRequest struct { + // The name of the service (router/broker/handler) + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` +} + +func (m *GetServiceRequest) Reset() { *m = GetServiceRequest{} } +func (m *GetServiceRequest) String() string { return proto.CompactTextString(m) } +func (*GetServiceRequest) ProtoMessage() {} +func (*GetServiceRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } + +// The identifier of the service that should be returned +type GetRequest struct { + // The ID of the service + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the service (router/broker/handler) + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` +} + +func (m *GetRequest) Reset() { *m = GetRequest{} } +func (m *GetRequest) String() string { return proto.CompactTextString(m) } +func (*GetRequest) ProtoMessage() {} +func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{3} } + +// The metadata to add or remove from an announement +type MetadataRequest struct { + // The ID of the service that should be modified + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the service (router/broker/handler) that should be modified + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Metadata to add or remove + Metadata *Metadata `protobuf:"bytes,12,opt,name=metadata" json:"metadata,omitempty"` +} + +func (m *MetadataRequest) Reset() { *m = MetadataRequest{} } +func (m *MetadataRequest) String() string { return proto.CompactTextString(m) } +func (*MetadataRequest) ProtoMessage() {} +func (*MetadataRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{4} } + +func (m *MetadataRequest) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +// A list of announcements +type AnnouncementsResponse struct { + Services []*Announcement `protobuf:"bytes,1,rep,name=services" json:"services,omitempty"` +} + +func (m *AnnouncementsResponse) Reset() { *m = AnnouncementsResponse{} } +func (m *AnnouncementsResponse) String() string { return proto.CompactTextString(m) } +func (*AnnouncementsResponse) ProtoMessage() {} +func (*AnnouncementsResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{5} } + +func (m *AnnouncementsResponse) GetServices() []*Announcement { + if m != nil { + return m.Services + } + return nil +} + +func init() { + proto.RegisterType((*Metadata)(nil), "discovery.Metadata") + proto.RegisterType((*Announcement)(nil), "discovery.Announcement") + proto.RegisterType((*GetServiceRequest)(nil), "discovery.GetServiceRequest") + proto.RegisterType((*GetRequest)(nil), "discovery.GetRequest") + proto.RegisterType((*MetadataRequest)(nil), "discovery.MetadataRequest") + proto.RegisterType((*AnnouncementsResponse)(nil), "discovery.AnnouncementsResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Discovery service + +type DiscoveryClient interface { + // Announce a component to the Discovery server. + // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. + // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. + Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // Get all announcements for a specific service type + GetAll(ctx context.Context, in *GetServiceRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) + // Get a specific announcement + Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) + // Add metadata to an announement + AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // Delete metadata from an announcement + DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) +} + +type discoveryClient struct { + cc *grpc.ClientConn +} + +func NewDiscoveryClient(cc *grpc.ClientConn) DiscoveryClient { + return &discoveryClient{cc} +} + +func (c *discoveryClient) Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/discovery.Discovery/Announce", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discoveryClient) GetAll(ctx context.Context, in *GetServiceRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) { + out := new(AnnouncementsResponse) + err := grpc.Invoke(ctx, "/discovery.Discovery/GetAll", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discoveryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) { + out := new(Announcement) + err := grpc.Invoke(ctx, "/discovery.Discovery/Get", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discoveryClient) AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/discovery.Discovery/AddMetadata", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/discovery.Discovery/DeleteMetadata", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Discovery service + +type DiscoveryServer interface { + // Announce a component to the Discovery server. + // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. + // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. + Announce(context.Context, *Announcement) (*google_protobuf.Empty, error) + // Get all announcements for a specific service type + GetAll(context.Context, *GetServiceRequest) (*AnnouncementsResponse, error) + // Get a specific announcement + Get(context.Context, *GetRequest) (*Announcement, error) + // Add metadata to an announement + AddMetadata(context.Context, *MetadataRequest) (*google_protobuf.Empty, error) + // Delete metadata from an announcement + DeleteMetadata(context.Context, *MetadataRequest) (*google_protobuf.Empty, error) +} + +func RegisterDiscoveryServer(s *grpc.Server, srv DiscoveryServer) { + s.RegisterService(&_Discovery_serviceDesc, srv) +} + +func _Discovery_Announce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Announcement) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).Announce(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/Announce", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).Announce(ctx, req.(*Announcement)) + } + return interceptor(ctx, in, info, handler) +} + +func _Discovery_GetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).GetAll(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/GetAll", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).GetAll(ctx, req.(*GetServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Discovery_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).Get(ctx, req.(*GetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Discovery_AddMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).AddMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/AddMetadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).AddMetadata(ctx, req.(*MetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Discovery_DeleteMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).DeleteMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/DeleteMetadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).DeleteMetadata(ctx, req.(*MetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Discovery_serviceDesc = grpc.ServiceDesc{ + ServiceName: "discovery.Discovery", + HandlerType: (*DiscoveryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Announce", + Handler: _Discovery_Announce_Handler, + }, + { + MethodName: "GetAll", + Handler: _Discovery_GetAll_Handler, + }, + { + MethodName: "Get", + Handler: _Discovery_Get_Handler, + }, + { + MethodName: "AddMetadata", + Handler: _Discovery_AddMetadata_Handler, + }, + { + MethodName: "DeleteMetadata", + Handler: _Discovery_DeleteMetadata_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto", +} + +// Client API for DiscoveryManager service + +type DiscoveryManagerClient interface { +} + +type discoveryManagerClient struct { + cc *grpc.ClientConn +} + +func NewDiscoveryManagerClient(cc *grpc.ClientConn) DiscoveryManagerClient { + return &discoveryManagerClient{cc} +} + +// Server API for DiscoveryManager service + +type DiscoveryManagerServer interface { +} + +func RegisterDiscoveryManagerServer(s *grpc.Server, srv DiscoveryManagerServer) { + s.RegisterService(&_DiscoveryManager_serviceDesc, srv) +} + +var _DiscoveryManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "discovery.DiscoveryManager", + HandlerType: (*DiscoveryManagerServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto", +} + +func (m *Metadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Metadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Metadata != nil { + nn1, err := m.Metadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *Metadata_DevAddrPrefix) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.DevAddrPrefix != nil { + dAtA[i] = 0xa2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.DevAddrPrefix))) + i += copy(dAtA[i:], m.DevAddrPrefix) + } + return i, nil +} +func (m *Metadata_AppId) MarshalTo(dAtA []byte) (int, error) { + i := 0 + dAtA[i] = 0xf2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + return i, nil +} +func (m *Metadata_AppEui) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.AppEui != nil { + dAtA[i] = 0xfa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.AppEui))) + i += copy(dAtA[i:], m.AppEui) + } + return i, nil +} +func (m *Announcement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Announcement) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Id) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) + } + if len(m.ServiceName) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) + } + if len(m.ServiceVersion) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceVersion))) + i += copy(dAtA[i:], m.ServiceVersion) + } + if len(m.Description) > 0 { + dAtA[i] = 0x22 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Description))) + i += copy(dAtA[i:], m.Description) + } + if len(m.Url) > 0 { + dAtA[i] = 0x2a + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Url))) + i += copy(dAtA[i:], m.Url) + } + if m.Public { + dAtA[i] = 0x30 + i++ + if m.Public { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if len(m.NetAddress) > 0 { + dAtA[i] = 0x5a + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.NetAddress))) + i += copy(dAtA[i:], m.NetAddress) + } + if len(m.PublicKey) > 0 { + dAtA[i] = 0x62 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.PublicKey))) + i += copy(dAtA[i:], m.PublicKey) + } + if len(m.Certificate) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Certificate))) + i += copy(dAtA[i:], m.Certificate) + } + if len(m.ApiAddress) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ApiAddress))) + i += copy(dAtA[i:], m.ApiAddress) + } + if len(m.Metadata) > 0 { + for _, msg := range m.Metadata { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *GetServiceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetServiceRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ServiceName) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) + } + return i, nil +} + +func (m *GetRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Id) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) + } + if len(m.ServiceName) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) + } + return i, nil +} + +func (m *MetadataRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MetadataRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Id) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) + } + if len(m.ServiceName) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) + } + if m.Metadata != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(m.Metadata.Size())) + n2, err := m.Metadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *AnnouncementsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AnnouncementsResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Services) > 0 { + for _, msg := range m.Services { + dAtA[i] = 0xa + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeFixed64Discovery(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Discovery(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintDiscovery(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Metadata) Size() (n int) { + var l int + _ = l + if m.Metadata != nil { + n += m.Metadata.Size() + } + return n +} + +func (m *Metadata_DevAddrPrefix) Size() (n int) { + var l int + _ = l + if m.DevAddrPrefix != nil { + l = len(m.DevAddrPrefix) + n += 2 + l + sovDiscovery(uint64(l)) + } + return n +} +func (m *Metadata_AppId) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + n += 2 + l + sovDiscovery(uint64(l)) + return n +} +func (m *Metadata_AppEui) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = len(m.AppEui) + n += 2 + l + sovDiscovery(uint64(l)) + } + return n +} +func (m *Announcement) Size() (n int) { + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ServiceVersion) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.Url) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + if m.Public { + n += 2 + } + l = len(m.NetAddress) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.PublicKey) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.Certificate) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ApiAddress) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + if len(m.Metadata) > 0 { + for _, e := range m.Metadata { + l = e.Size() + n += 2 + l + sovDiscovery(uint64(l)) + } + } + return n +} + +func (m *GetServiceRequest) Size() (n int) { + var l int + _ = l + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + return n +} + +func (m *GetRequest) Size() (n int) { + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + return n +} + +func (m *MetadataRequest) Size() (n int) { + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovDiscovery(uint64(l)) + } + return n +} + +func (m *AnnouncementsResponse) Size() (n int) { + var l int + _ = l + if len(m.Services) > 0 { + for _, e := range m.Services { + l = e.Size() + n += 1 + l + sovDiscovery(uint64(l)) + } + } + return n +} + +func sovDiscovery(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozDiscovery(x uint64) (n int) { + return sovDiscovery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Metadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 20: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddrPrefix", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Metadata = &Metadata_DevAddrPrefix{v} + iNdEx = postIndex + case 30: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = &Metadata_AppId{string(dAtA[iNdEx:postIndex])} + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Metadata = &Metadata_AppEui{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Announcement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Announcement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Announcement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Url = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Public", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Public = bool(v != 0) + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PublicKey = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Certificate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Certificate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ApiAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ApiAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = append(m.Metadata, &Metadata{}) + if err := m.Metadata[len(m.Metadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetServiceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetServiceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetServiceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MetadataRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MetadataRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MetadataRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AnnouncementsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AnnouncementsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnnouncementsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Services", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Services = append(m.Services, &Announcement{}) + if err := m.Services[len(m.Services)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDiscovery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthDiscovery + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipDiscovery(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthDiscovery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDiscovery = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto", fileDescriptorDiscovery) +} + +var fileDescriptorDiscovery = []byte{ + // 660 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xdf, 0x4e, 0x13, 0x4f, + 0x14, 0x66, 0xdb, 0x1f, 0xfd, 0xb5, 0xa7, 0xa5, 0xe0, 0x28, 0x30, 0x56, 0x28, 0xb5, 0xd1, 0xd8, + 0x98, 0xd8, 0x4d, 0x20, 0xf1, 0xc6, 0x18, 0x52, 0x02, 0x29, 0x46, 0x21, 0x66, 0x25, 0x5e, 0x78, + 0xd3, 0x4c, 0x77, 0x0e, 0x65, 0x42, 0x3b, 0xbb, 0xee, 0xcc, 0x56, 0x09, 0xe1, 0xc6, 0x57, 0xf0, + 0xc6, 0x17, 0xf0, 0x5d, 0xbc, 0x34, 0xf1, 0x05, 0x0c, 0x7a, 0xe5, 0x53, 0x98, 0xfd, 0xdb, 0x25, + 0x58, 0x0d, 0x7a, 0xb7, 0xfb, 0x9d, 0xef, 0x7c, 0xdf, 0xcc, 0x77, 0xf6, 0x2c, 0x3c, 0x1e, 0x08, + 0x7d, 0xe4, 0xf7, 0xdb, 0xb6, 0x33, 0x32, 0x0f, 0x8e, 0xf0, 0xe0, 0x48, 0xc8, 0x81, 0xda, 0x47, + 0xfd, 0xc6, 0xf1, 0x8e, 0x4d, 0xad, 0xa5, 0xc9, 0x5c, 0x61, 0x72, 0xa1, 0x6c, 0x67, 0x8c, 0xde, + 0xc9, 0xe4, 0xa9, 0xed, 0x7a, 0x8e, 0x76, 0x48, 0x29, 0x05, 0x6a, 0xb7, 0x06, 0x8e, 0x33, 0x18, + 0xa2, 0x19, 0x16, 0xfa, 0xfe, 0xa1, 0x89, 0x23, 0x57, 0xc7, 0xbc, 0xda, 0x4a, 0x5c, 0x0c, 0xd4, + 0x98, 0x94, 0x8e, 0x66, 0x5a, 0x38, 0x52, 0x45, 0xd5, 0xa6, 0x86, 0xe2, 0x1e, 0x6a, 0xc6, 0x99, + 0x66, 0xa4, 0x05, 0xf3, 0x1c, 0xc7, 0x3d, 0xc6, 0xb9, 0xd7, 0x73, 0x3d, 0x3c, 0x14, 0x6f, 0xe9, + 0x8d, 0x86, 0xd1, 0xaa, 0xec, 0xce, 0x58, 0x73, 0x1c, 0xc7, 0x1d, 0xce, 0xbd, 0xe7, 0x21, 0x4c, + 0x96, 0xa1, 0xc0, 0x5c, 0xb7, 0x27, 0x38, 0xad, 0x37, 0x8c, 0x56, 0x69, 0x77, 0xc6, 0x9a, 0x65, + 0xae, 0xfb, 0x84, 0x93, 0x9b, 0xf0, 0x7f, 0x50, 0x40, 0x5f, 0xd0, 0xb5, 0xb8, 0x35, 0x60, 0xee, + 0xf8, 0x62, 0x0b, 0xa0, 0x38, 0x8a, 0x9d, 0x9a, 0x3f, 0x72, 0x50, 0xe9, 0x48, 0xe9, 0xf8, 0xd2, + 0xc6, 0x11, 0x4a, 0x4d, 0xaa, 0x90, 0x13, 0x9c, 0x1a, 0x81, 0x98, 0x95, 0x13, 0x9c, 0xdc, 0x86, + 0x8a, 0x42, 0x6f, 0x2c, 0x6c, 0xec, 0x49, 0x36, 0x42, 0x9a, 0x0b, 0x2b, 0xe5, 0x18, 0xdb, 0x67, + 0x23, 0x24, 0xf7, 0x60, 0x3e, 0xa1, 0x8c, 0xd1, 0x53, 0xc2, 0x91, 0x34, 0x1f, 0xb2, 0xaa, 0x31, + 0xfc, 0x32, 0x42, 0x49, 0x03, 0xca, 0x1c, 0x95, 0xed, 0x09, 0x37, 0xb8, 0x38, 0xfd, 0x2f, 0x92, + 0xca, 0x40, 0x64, 0x01, 0xf2, 0xbe, 0x37, 0xa4, 0xb3, 0x61, 0x25, 0x78, 0x24, 0x4b, 0x50, 0x70, + 0xfd, 0xfe, 0x50, 0xd8, 0xb4, 0xd0, 0x30, 0x5a, 0x45, 0x2b, 0x7e, 0x23, 0x6b, 0x50, 0x96, 0xa8, + 0xc3, 0x88, 0x50, 0x29, 0x5a, 0x0e, 0x3b, 0x40, 0xa2, 0xee, 0x44, 0x08, 0x59, 0x05, 0x88, 0xa8, + 0xbd, 0x63, 0x3c, 0xa1, 0x95, 0xb0, 0x5e, 0x8a, 0x90, 0xa7, 0x78, 0x12, 0x9c, 0xc5, 0x46, 0x4f, + 0x8b, 0x43, 0x61, 0x33, 0x8d, 0x74, 0x2e, 0x3a, 0x4b, 0x06, 0x0a, 0x1c, 0x98, 0x2b, 0x52, 0x87, + 0x6a, 0xe4, 0xc0, 0x5c, 0x91, 0x38, 0x98, 0x93, 0x1c, 0xe9, 0x52, 0x23, 0xdf, 0x2a, 0xaf, 0x5f, + 0x6f, 0x4f, 0xbe, 0x8d, 0x64, 0x98, 0xd6, 0x24, 0xec, 0x87, 0x70, 0xad, 0x8b, 0xfa, 0x45, 0x14, + 0x8a, 0x85, 0xaf, 0x7d, 0x54, 0xfa, 0x52, 0xc0, 0xc6, 0xa5, 0x80, 0x9b, 0x9b, 0x00, 0x5d, 0xd4, + 0x49, 0xc3, 0xd5, 0x27, 0xd4, 0xf4, 0x61, 0x3e, 0x3d, 0xce, 0x5f, 0xab, 0x5c, 0xb8, 0x6f, 0x90, + 0xe7, 0x1f, 0xef, 0xfb, 0x0c, 0x16, 0xb3, 0xdf, 0x96, 0xb2, 0x50, 0xb9, 0x8e, 0x54, 0x48, 0x36, + 0xa0, 0x18, 0x0b, 0x2b, 0x6a, 0x84, 0xc9, 0x2d, 0x67, 0x94, 0xb2, 0x3d, 0x56, 0x4a, 0x5c, 0xff, + 0x98, 0x87, 0xd2, 0x76, 0x42, 0x22, 0x8f, 0xa0, 0x98, 0xf0, 0xc8, 0xb4, 0xe6, 0xda, 0x52, 0x3b, + 0x5a, 0xb9, 0x76, 0xb2, 0x8f, 0xed, 0x9d, 0x60, 0x1f, 0x89, 0x03, 0x85, 0x2e, 0xea, 0xce, 0x70, + 0x48, 0x56, 0x32, 0xad, 0x97, 0x66, 0x53, 0x6b, 0x4c, 0x11, 0x4e, 0x6f, 0xd2, 0xbc, 0xfb, 0xee, + 0xcb, 0xf7, 0xf7, 0xb9, 0x35, 0xb2, 0x1a, 0x6e, 0x74, 0x5a, 0x37, 0x4f, 0xb3, 0x61, 0x9e, 0x11, + 0x06, 0xf9, 0x2e, 0x6a, 0xb2, 0x78, 0xd1, 0x2d, 0xb1, 0x99, 0x76, 0xfe, 0xe6, 0xfd, 0x50, 0xfd, + 0x0e, 0x69, 0xfe, 0x56, 0xdd, 0x3c, 0x15, 0xfc, 0x8c, 0x74, 0xa0, 0xdc, 0xe1, 0x3c, 0xfd, 0x85, + 0xd4, 0x7e, 0x35, 0x9a, 0xd8, 0x6f, 0x5a, 0x2c, 0xdb, 0x50, 0xdd, 0xc6, 0x21, 0x6a, 0xfc, 0x17, + 0x95, 0x75, 0x02, 0x0b, 0xe9, 0x98, 0xf6, 0x98, 0x64, 0x03, 0xf4, 0xb6, 0x36, 0x3f, 0x9d, 0xd7, + 0x8d, 0xcf, 0xe7, 0x75, 0xe3, 0xeb, 0x79, 0xdd, 0xf8, 0xf0, 0xad, 0x3e, 0xf3, 0xea, 0xc1, 0x95, + 0xfe, 0xb9, 0xfd, 0x42, 0x68, 0xb2, 0xf1, 0x33, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xc1, 0x3f, 0xec, + 0xab, 0x05, 0x00, 0x00, +} diff --git a/api/discovery/discovery.pb.gw.go b/api/discovery/discovery.pb.gw.go new file mode 100644 index 000000000..08925d7af --- /dev/null +++ b/api/discovery/discovery.pb.gw.go @@ -0,0 +1,194 @@ +// Code generated by protoc-gen-grpc-gateway +// source: github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto +// DO NOT EDIT! + +/* +Package discovery is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package discovery + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" +) + +var _ codes.Code +var _ io.Reader +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_Discovery_GetAll_0(ctx context.Context, marshaler runtime.Marshaler, client DiscoveryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetServiceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["service_name"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "service_name") + } + + protoReq.ServiceName, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetAll(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_Discovery_Get_0(ctx context.Context, marshaler runtime.Marshaler, client DiscoveryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["service_name"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "service_name") + } + + protoReq.ServiceName, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterDiscoveryHandlerFromEndpoint is same as RegisterDiscoveryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterDiscoveryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterDiscoveryHandler(ctx, mux, conn) +} + +// RegisterDiscoveryHandler registers the http handlers for service Discovery to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterDiscoveryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewDiscoveryClient(conn) + + mux.Handle("GET", pattern_Discovery_GetAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_Discovery_GetAll_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_Discovery_GetAll_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Discovery_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_Discovery_Get_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_Discovery_Get_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Discovery_GetAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"announcements", "service_name"}, "")) + + pattern_Discovery_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 1, 0, 4, 1, 5, 2}, []string{"announcements", "service_name", "id"}, "")) +) + +var ( + forward_Discovery_GetAll_0 = runtime.ForwardResponseMessage + + forward_Discovery_Get_0 = runtime.ForwardResponseMessage +) diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto new file mode 100644 index 000000000..22948d04c --- /dev/null +++ b/api/discovery/discovery.proto @@ -0,0 +1,140 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; + +package discovery; + +option go_package = "github.com/TheThingsNetwork/ttn/api/discovery"; + +message Metadata { + oneof metadata { + // General metadata (0-9) + + // + + // Metadata for Router component (10-19) + + // + + // Metadata for Broker component (20-29) + + // DevAddr prefix that is routed by this Broker + // 5 bytes; the first byte is the prefix length, the following 4 bytes are the address. + // Only authorized Brokers can announce PREFIX metadata. + bytes dev_addr_prefix = 20; // for some reason gogoproto customtype doesn't work in a oneof, so we do this manually + + // Metadata for Handler component (30-39) + + // AppID that is registered to this Handler + // This metadata can only be added if the requesting client is authorized to manage this AppID. + string app_id = 30; + + // AppEUI that is registered to this Join Handler + // Only authorized Join Handlers can announce APP_EUI metadata (and we don't have any of those yet). + bytes app_eui = 31; // for some reason gogoproto customtype doesn't work in a oneof, so we do this manually + } +} + +// The Announcement of a service (also called component) +message Announcement { + // The ID of the component + string id = 1; + + // The name of the component (router/broker/handler) + string service_name = 2; + + // Service version in the form "[version]-[commit] ([build date])" + string service_version = 3; + + // Description of the component + string description = 4; + + // URL with documentation or more information about this component + string url = 5; + + // Indicates whether this service is part of The Things Network (the public community network) + bool public = 6; + + // Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) + string net_address = 11; + + // ECDSA public key of this component + string public_key = 12; + + // TLS Certificate (if TLS is enabled) + string certificate = 13; + + // Contains the address where the HTTP API is exposed (if there is one) + string api_address = 14; + + // Metadata for this component + repeated Metadata metadata = 22; +} + +message GetServiceRequest { + // The name of the service (router/broker/handler) + string service_name = 1; +} + +// The identifier of the service that should be returned +message GetRequest { + // The ID of the service + string id = 1; + + // The name of the service (router/broker/handler) + string service_name = 2; +} + +// The metadata to add or remove from an announement +message MetadataRequest { + // The ID of the service that should be modified + string id = 1; + + // The name of the service (router/broker/handler) that should be modified + string service_name = 2; + + // Metadata to add or remove + Metadata metadata = 12; +} + +// A list of announcements +message AnnouncementsResponse { + repeated Announcement services = 1; +} + +// The Discovery service is used to discover services within The Things Network. +service Discovery { + // Announce a component to the Discovery server. + // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. + // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. + rpc Announce(Announcement) returns (google.protobuf.Empty); + + // Get all announcements for a specific service type + rpc GetAll(GetServiceRequest) returns (AnnouncementsResponse) { + option (google.api.http) = { + get: "/announcements/{service_name}" + }; + } + + // Get a specific announcement + rpc Get(GetRequest) returns (Announcement) { + option (google.api.http) = { + get: "/announcements/{service_name}/{id}" + }; + } + + // Add metadata to an announement + rpc AddMetadata(MetadataRequest) returns (google.protobuf.Empty); + + // Delete metadata from an announcement + rpc DeleteMetadata(MetadataRequest) returns (google.protobuf.Empty); +} + +// The DiscoveryManager service provides configuration and monitoring functionality +service DiscoveryManager { + +} diff --git a/api/discovery/metadata.go b/api/discovery/metadata.go new file mode 100644 index 000000000..eebe65189 --- /dev/null +++ b/api/discovery/metadata.go @@ -0,0 +1,41 @@ +package discovery + +import "github.com/TheThingsNetwork/ttn/core/types" + +// AppIDs that are handled by this component +func (a *Announcement) AppIDs() (appIDs []string) { + for _, meta := range a.Metadata { + if appID := meta.GetAppId(); appID != "" { + appIDs = append(appIDs, appID) + } + } + return +} + +// DevAddrPrefixes that are handled by this component +func (a *Announcement) DevAddrPrefixes() (prefixes []types.DevAddrPrefix) { + for _, meta := range a.Metadata { + if prefixBytes := meta.GetDevAddrPrefix(); prefixBytes != nil { + prefix := new(types.DevAddrPrefix) + if err := prefix.Unmarshal(prefixBytes); err != nil { + continue + } + prefixes = append(prefixes, *prefix) + } + } + return +} + +// AppEUIs that are handled by this component +func (a *Announcement) AppEUIs() (euis []types.AppEUI) { + for _, meta := range a.Metadata { + if euiBytes := meta.GetAppEui(); euiBytes != nil { + eui := new(types.AppEUI) + if err := eui.Unmarshal(euiBytes); err != nil { + continue + } + euis = append(euis, *eui) + } + } + return +} diff --git a/api/discovery/validation.go b/api/discovery/validation.go new file mode 100644 index 000000000..ac309da12 --- /dev/null +++ b/api/discovery/validation.go @@ -0,0 +1,19 @@ +package discovery + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validate implements the api.Validator interface +func (m *Announcement) Validate() error { + if err := api.NotEmptyAndValidID(m.Id, "Id"); err != nil { + return err + } + switch m.ServiceName { + case "router", "broker", "handler": + default: + return errors.NewErrInvalidArgument("ServiceName", "expected one of router, broker, handler but was "+m.ServiceName) + } + return nil +} diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go new file mode 100644 index 000000000..c9823f314 --- /dev/null +++ b/api/gateway/gateway.pb.go @@ -0,0 +1,1989 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto +// DO NOT EDIT! + +/* + Package gateway is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto + + It has these top-level messages: + GPSMetadata + RxMetadata + TxConfiguration + Status +*/ +package gateway + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type GPSMetadata struct { + Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` + Latitude float32 `protobuf:"fixed32,2,opt,name=latitude,proto3" json:"latitude,omitempty"` + Longitude float32 `protobuf:"fixed32,3,opt,name=longitude,proto3" json:"longitude,omitempty"` + Altitude int32 `protobuf:"varint,4,opt,name=altitude,proto3" json:"altitude,omitempty"` +} + +func (m *GPSMetadata) Reset() { *m = GPSMetadata{} } +func (m *GPSMetadata) String() string { return proto.CompactTextString(m) } +func (*GPSMetadata) ProtoMessage() {} +func (*GPSMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{0} } + +type RxMetadata struct { + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Time int64 `protobuf:"varint,12,opt,name=time,proto3" json:"time,omitempty"` + RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Channel uint32 `protobuf:"varint,22,opt,name=channel,proto3" json:"channel,omitempty"` + Frequency uint64 `protobuf:"varint,31,opt,name=frequency,proto3" json:"frequency,omitempty"` + Rssi float32 `protobuf:"fixed32,32,opt,name=rssi,proto3" json:"rssi,omitempty"` + Snr float32 `protobuf:"fixed32,33,opt,name=snr,proto3" json:"snr,omitempty"` + Gps *GPSMetadata `protobuf:"bytes,41,opt,name=gps" json:"gps,omitempty"` +} + +func (m *RxMetadata) Reset() { *m = RxMetadata{} } +func (m *RxMetadata) String() string { return proto.CompactTextString(m) } +func (*RxMetadata) ProtoMessage() {} +func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{1} } + +func (m *RxMetadata) GetGps() *GPSMetadata { + if m != nil { + return m.Gps + } + return nil +} + +type TxConfiguration struct { + Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Frequency uint64 `protobuf:"varint,22,opt,name=frequency,proto3" json:"frequency,omitempty"` + Power int32 `protobuf:"varint,23,opt,name=power,proto3" json:"power,omitempty"` + PolarizationInversion bool `protobuf:"varint,31,opt,name=polarization_inversion,json=polarizationInversion,proto3" json:"polarization_inversion,omitempty"` + FrequencyDeviation uint32 `protobuf:"varint,32,opt,name=frequency_deviation,json=frequencyDeviation,proto3" json:"frequency_deviation,omitempty"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{2} } + +// message Status represents a status update from a Gateway. +type Status struct { + Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Ip []string `protobuf:"bytes,11,rep,name=ip" json:"ip,omitempty"` + Platform string `protobuf:"bytes,12,opt,name=platform,proto3" json:"platform,omitempty"` + ContactEmail string `protobuf:"bytes,13,opt,name=contact_email,json=contactEmail,proto3" json:"contact_email,omitempty"` + Description string `protobuf:"bytes,14,opt,name=description,proto3" json:"description,omitempty"` + Region string `protobuf:"bytes,15,opt,name=region,proto3" json:"region,omitempty"` + Bridge string `protobuf:"bytes,16,opt,name=bridge,proto3" json:"bridge,omitempty"` + Router string `protobuf:"bytes,17,opt,name=router,proto3" json:"router,omitempty"` + Gps *GPSMetadata `protobuf:"bytes,21,opt,name=gps" json:"gps,omitempty"` + Rtt uint32 `protobuf:"varint,31,opt,name=rtt,proto3" json:"rtt,omitempty"` + RxIn uint32 `protobuf:"varint,41,opt,name=rx_in,json=rxIn,proto3" json:"rx_in,omitempty"` + RxOk uint32 `protobuf:"varint,42,opt,name=rx_ok,json=rxOk,proto3" json:"rx_ok,omitempty"` + TxIn uint32 `protobuf:"varint,43,opt,name=tx_in,json=txIn,proto3" json:"tx_in,omitempty"` + TxOk uint32 `protobuf:"varint,44,opt,name=tx_ok,json=txOk,proto3" json:"tx_ok,omitempty"` + Os *Status_OSMetrics `protobuf:"bytes,51,opt,name=os" json:"os,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3} } + +func (m *Status) GetGps() *GPSMetadata { + if m != nil { + return m.Gps + } + return nil +} + +func (m *Status) GetOs() *Status_OSMetrics { + if m != nil { + return m.Os + } + return nil +} + +// Additional metrics from the operating system +type Status_OSMetrics struct { + Load_1 float32 `protobuf:"fixed32,1,opt,name=load_1,json=load1,proto3" json:"load_1,omitempty"` + Load_5 float32 `protobuf:"fixed32,2,opt,name=load_5,json=load5,proto3" json:"load_5,omitempty"` + Load_15 float32 `protobuf:"fixed32,3,opt,name=load_15,json=load15,proto3" json:"load_15,omitempty"` + CpuPercentage float32 `protobuf:"fixed32,11,opt,name=cpu_percentage,json=cpuPercentage,proto3" json:"cpu_percentage,omitempty"` + MemoryPercentage float32 `protobuf:"fixed32,21,opt,name=memory_percentage,json=memoryPercentage,proto3" json:"memory_percentage,omitempty"` + Temperature float32 `protobuf:"fixed32,31,opt,name=temperature,proto3" json:"temperature,omitempty"` +} + +func (m *Status_OSMetrics) Reset() { *m = Status_OSMetrics{} } +func (m *Status_OSMetrics) String() string { return proto.CompactTextString(m) } +func (*Status_OSMetrics) ProtoMessage() {} +func (*Status_OSMetrics) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3, 0} } + +func init() { + proto.RegisterType((*GPSMetadata)(nil), "gateway.GPSMetadata") + proto.RegisterType((*RxMetadata)(nil), "gateway.RxMetadata") + proto.RegisterType((*TxConfiguration)(nil), "gateway.TxConfiguration") + proto.RegisterType((*Status)(nil), "gateway.Status") + proto.RegisterType((*Status_OSMetrics)(nil), "gateway.Status.OSMetrics") +} +func (m *GPSMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GPSMetadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Time != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Time)) + } + if m.Latitude != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Latitude)))) + } + if m.Longitude != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Longitude)))) + } + if m.Altitude != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Altitude)) + } + return i, nil +} + +func (m *RxMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RxMetadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.GatewayId))) + i += copy(dAtA[i:], m.GatewayId) + } + if m.Timestamp != 0 { + dAtA[i] = 0x58 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Timestamp)) + } + if m.Time != 0 { + dAtA[i] = 0x60 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Time)) + } + if m.RfChain != 0 { + dAtA[i] = 0xa8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.RfChain)) + } + if m.Channel != 0 { + dAtA[i] = 0xb0 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Channel)) + } + if m.Frequency != 0 { + dAtA[i] = 0xf8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Frequency)) + } + if m.Rssi != 0 { + dAtA[i] = 0x85 + i++ + dAtA[i] = 0x2 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Rssi)))) + } + if m.Snr != 0 { + dAtA[i] = 0x8d + i++ + dAtA[i] = 0x2 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Snr)))) + } + if m.Gps != nil { + dAtA[i] = 0xca + i++ + dAtA[i] = 0x2 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Gps.Size())) + n1, err := m.Gps.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + return i, nil +} + +func (m *TxConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TxConfiguration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Timestamp != 0 { + dAtA[i] = 0x58 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Timestamp)) + } + if m.RfChain != 0 { + dAtA[i] = 0xa8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.RfChain)) + } + if m.Frequency != 0 { + dAtA[i] = 0xb0 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Frequency)) + } + if m.Power != 0 { + dAtA[i] = 0xb8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Power)) + } + if m.PolarizationInversion { + dAtA[i] = 0xf8 + i++ + dAtA[i] = 0x1 + i++ + if m.PolarizationInversion { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if m.FrequencyDeviation != 0 { + dAtA[i] = 0x80 + i++ + dAtA[i] = 0x2 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.FrequencyDeviation)) + } + return i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Timestamp != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Timestamp)) + } + if m.Time != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Time)) + } + if len(m.Ip) > 0 { + for _, s := range m.Ip { + dAtA[i] = 0x5a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.Platform) > 0 { + dAtA[i] = 0x62 + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Platform))) + i += copy(dAtA[i:], m.Platform) + } + if len(m.ContactEmail) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.ContactEmail))) + i += copy(dAtA[i:], m.ContactEmail) + } + if len(m.Description) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Description))) + i += copy(dAtA[i:], m.Description) + } + if len(m.Region) > 0 { + dAtA[i] = 0x7a + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Region))) + i += copy(dAtA[i:], m.Region) + } + if len(m.Bridge) > 0 { + dAtA[i] = 0x82 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Bridge))) + i += copy(dAtA[i:], m.Bridge) + } + if len(m.Router) > 0 { + dAtA[i] = 0x8a + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Router))) + i += copy(dAtA[i:], m.Router) + } + if m.Gps != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Gps.Size())) + n2, err := m.Gps.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.Rtt != 0 { + dAtA[i] = 0xf8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Rtt)) + } + if m.RxIn != 0 { + dAtA[i] = 0xc8 + i++ + dAtA[i] = 0x2 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.RxIn)) + } + if m.RxOk != 0 { + dAtA[i] = 0xd0 + i++ + dAtA[i] = 0x2 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.RxOk)) + } + if m.TxIn != 0 { + dAtA[i] = 0xd8 + i++ + dAtA[i] = 0x2 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.TxIn)) + } + if m.TxOk != 0 { + dAtA[i] = 0xe0 + i++ + dAtA[i] = 0x2 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.TxOk)) + } + if m.Os != nil { + dAtA[i] = 0x9a + i++ + dAtA[i] = 0x3 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Os.Size())) + n3, err := m.Os.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *Status_OSMetrics) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status_OSMetrics) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Load_1 != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Load_1)))) + } + if m.Load_5 != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Load_5)))) + } + if m.Load_15 != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Load_15)))) + } + if m.CpuPercentage != 0 { + dAtA[i] = 0x5d + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.CpuPercentage)))) + } + if m.MemoryPercentage != 0 { + dAtA[i] = 0xad + i++ + dAtA[i] = 0x1 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.MemoryPercentage)))) + } + if m.Temperature != 0 { + dAtA[i] = 0xfd + i++ + dAtA[i] = 0x1 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Temperature)))) + } + return i, nil +} + +func encodeFixed64Gateway(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Gateway(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGateway(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *GPSMetadata) Size() (n int) { + var l int + _ = l + if m.Time != 0 { + n += 1 + sovGateway(uint64(m.Time)) + } + if m.Latitude != 0 { + n += 5 + } + if m.Longitude != 0 { + n += 5 + } + if m.Altitude != 0 { + n += 1 + sovGateway(uint64(m.Altitude)) + } + return n +} + +func (m *RxMetadata) Size() (n int) { + var l int + _ = l + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovGateway(uint64(m.Timestamp)) + } + if m.Time != 0 { + n += 1 + sovGateway(uint64(m.Time)) + } + if m.RfChain != 0 { + n += 2 + sovGateway(uint64(m.RfChain)) + } + if m.Channel != 0 { + n += 2 + sovGateway(uint64(m.Channel)) + } + if m.Frequency != 0 { + n += 2 + sovGateway(uint64(m.Frequency)) + } + if m.Rssi != 0 { + n += 6 + } + if m.Snr != 0 { + n += 6 + } + if m.Gps != nil { + l = m.Gps.Size() + n += 2 + l + sovGateway(uint64(l)) + } + return n +} + +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovGateway(uint64(m.Timestamp)) + } + if m.RfChain != 0 { + n += 2 + sovGateway(uint64(m.RfChain)) + } + if m.Frequency != 0 { + n += 2 + sovGateway(uint64(m.Frequency)) + } + if m.Power != 0 { + n += 2 + sovGateway(uint64(m.Power)) + } + if m.PolarizationInversion { + n += 3 + } + if m.FrequencyDeviation != 0 { + n += 2 + sovGateway(uint64(m.FrequencyDeviation)) + } + return n +} + +func (m *Status) Size() (n int) { + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovGateway(uint64(m.Timestamp)) + } + if m.Time != 0 { + n += 1 + sovGateway(uint64(m.Time)) + } + if len(m.Ip) > 0 { + for _, s := range m.Ip { + l = len(s) + n += 1 + l + sovGateway(uint64(l)) + } + } + l = len(m.Platform) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + l = len(m.ContactEmail) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + l = len(m.Region) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + l = len(m.Bridge) + if l > 0 { + n += 2 + l + sovGateway(uint64(l)) + } + l = len(m.Router) + if l > 0 { + n += 2 + l + sovGateway(uint64(l)) + } + if m.Gps != nil { + l = m.Gps.Size() + n += 2 + l + sovGateway(uint64(l)) + } + if m.Rtt != 0 { + n += 2 + sovGateway(uint64(m.Rtt)) + } + if m.RxIn != 0 { + n += 2 + sovGateway(uint64(m.RxIn)) + } + if m.RxOk != 0 { + n += 2 + sovGateway(uint64(m.RxOk)) + } + if m.TxIn != 0 { + n += 2 + sovGateway(uint64(m.TxIn)) + } + if m.TxOk != 0 { + n += 2 + sovGateway(uint64(m.TxOk)) + } + if m.Os != nil { + l = m.Os.Size() + n += 2 + l + sovGateway(uint64(l)) + } + return n +} + +func (m *Status_OSMetrics) Size() (n int) { + var l int + _ = l + if m.Load_1 != 0 { + n += 5 + } + if m.Load_5 != 0 { + n += 5 + } + if m.Load_15 != 0 { + n += 5 + } + if m.CpuPercentage != 0 { + n += 5 + } + if m.MemoryPercentage != 0 { + n += 6 + } + if m.Temperature != 0 { + n += 6 + } + return n +} + +func sovGateway(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGateway(x uint64) (n int) { + return sovGateway(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GPSMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GPSMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GPSMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Time |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Latitude = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Longitude = float32(math.Float32frombits(v)) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) + } + m.Altitude = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Altitude |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGateway(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RxMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RxMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RxMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Time |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) + } + m.RfChain = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RfChain |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + m.Channel = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Channel |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 31: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) + } + m.Frequency = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Frequency |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 32: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rssi", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Rssi = float32(math.Float32frombits(v)) + case 33: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Snr", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Snr = float32(math.Float32frombits(v)) + case 41: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gps", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + if err := m.Gps.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGateway(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) + } + m.RfChain = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RfChain |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) + } + m.Frequency = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Frequency |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 23: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) + } + m.Power = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Power |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 31: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PolarizationInversion", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.PolarizationInversion = bool(v != 0) + case 32: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FrequencyDeviation", wireType) + } + m.FrequencyDeviation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FrequencyDeviation |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGateway(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Time |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ip", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ip = append(m.Ip, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Platform", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Platform = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContactEmail", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContactEmail = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Region", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Region = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Bridge", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Bridge = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Router", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Router = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gps", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + if err := m.Gps.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rtt", wireType) + } + m.Rtt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Rtt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 41: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxIn", wireType) + } + m.RxIn = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RxIn |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 42: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxOk", wireType) + } + m.RxOk = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RxOk |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 43: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TxIn", wireType) + } + m.TxIn = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TxIn |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 44: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TxOk", wireType) + } + m.TxOk = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TxOk |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 51: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Os", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Os == nil { + m.Os = &Status_OSMetrics{} + } + if err := m.Os.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGateway(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status_OSMetrics) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: OSMetrics: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: OSMetrics: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load_1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load_1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load_5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load_5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load_15", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load_15 = float32(math.Float32frombits(v)) + case 11: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field CpuPercentage", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.CpuPercentage = float32(math.Float32frombits(v)) + case 21: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field MemoryPercentage", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.MemoryPercentage = float32(math.Float32frombits(v)) + case 31: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Temperature", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Temperature = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipGateway(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGateway(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGateway + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGateway(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGateway = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGateway = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto", fileDescriptorGateway) +} + +var fileDescriptorGateway = []byte{ + // 727 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xe3, 0x44, + 0x1c, 0xc7, 0xce, 0x57, 0x33, 0xd9, 0x74, 0xbb, 0xb3, 0x9b, 0xec, 0xb4, 0x82, 0x60, 0x82, 0x40, + 0x29, 0x85, 0x44, 0xa5, 0xca, 0x81, 0x03, 0x17, 0x0a, 0x42, 0x3d, 0x40, 0xab, 0x69, 0x4f, 0x5c, + 0xac, 0x89, 0x3d, 0x71, 0x46, 0xb1, 0x67, 0xcc, 0x78, 0xdc, 0xa4, 0x3c, 0x09, 0x8f, 0xd4, 0x23, + 0x8f, 0x80, 0x8a, 0xc4, 0x3b, 0x70, 0x41, 0x68, 0xfe, 0x76, 0x5c, 0x17, 0x09, 0xaa, 0x3d, 0x79, + 0x7e, 0x1f, 0xe3, 0xff, 0xa7, 0x06, 0x7d, 0x15, 0x09, 0xb3, 0xca, 0x17, 0xd3, 0x40, 0x25, 0xb3, + 0x9b, 0x15, 0xbf, 0x59, 0x09, 0x19, 0x65, 0x3f, 0x72, 0xb3, 0x51, 0x7a, 0x3d, 0x33, 0x46, 0xce, + 0x58, 0x2a, 0x66, 0x11, 0x33, 0x7c, 0xc3, 0xee, 0x76, 0xdf, 0x69, 0xaa, 0x95, 0x51, 0xb8, 0x53, + 0xc2, 0xa3, 0x2f, 0x6a, 0xff, 0x88, 0x54, 0xa4, 0x66, 0xa0, 0x2f, 0xf2, 0x25, 0x20, 0x00, 0x70, + 0x2a, 0xee, 0x8d, 0x37, 0xa8, 0xf7, 0xfd, 0xd5, 0xf5, 0x0f, 0xdc, 0xb0, 0x90, 0x19, 0x86, 0x31, + 0x6a, 0x1a, 0x91, 0x70, 0xe2, 0x78, 0xce, 0xa4, 0x41, 0xe1, 0x8c, 0x8f, 0xd0, 0x5e, 0xcc, 0x8c, + 0x30, 0x79, 0xc8, 0x89, 0xeb, 0x39, 0x13, 0x97, 0x56, 0x18, 0xbf, 0x8f, 0xba, 0xb1, 0x92, 0x51, + 0x21, 0x36, 0x40, 0x7c, 0x24, 0xec, 0x4d, 0x16, 0x97, 0x37, 0x9b, 0x9e, 0x33, 0x69, 0xd1, 0x0a, + 0x8f, 0xff, 0x76, 0x10, 0xa2, 0xdb, 0x2a, 0xf0, 0x07, 0x08, 0x95, 0x15, 0xf8, 0x22, 0x84, 0xf0, + 0x5d, 0xda, 0x2d, 0x99, 0x8b, 0xd0, 0xc6, 0xb1, 0xb9, 0x64, 0x86, 0x25, 0x29, 0xe9, 0x79, 0xce, + 0xa4, 0x4f, 0x1f, 0x89, 0x2a, 0xeb, 0x17, 0xb5, 0xac, 0x0f, 0xd1, 0x9e, 0x5e, 0xfa, 0xc1, 0x8a, + 0x09, 0x49, 0x06, 0x70, 0xa1, 0xa3, 0x97, 0xe7, 0x16, 0x62, 0x82, 0x3a, 0xc1, 0x8a, 0x49, 0xc9, + 0x63, 0x32, 0x2c, 0x94, 0x12, 0xda, 0x30, 0x4b, 0xcd, 0x7f, 0xce, 0xb9, 0x0c, 0xee, 0xc8, 0x87, + 0x9e, 0x33, 0x69, 0xd2, 0x47, 0xc2, 0x86, 0xd1, 0x59, 0x26, 0x88, 0x07, 0x75, 0xc2, 0x19, 0x1f, + 0xa0, 0x46, 0x26, 0x35, 0xf9, 0x08, 0x28, 0x7b, 0xc4, 0x9f, 0xa2, 0x46, 0x94, 0x66, 0xe4, 0xd8, + 0x73, 0x26, 0xbd, 0x2f, 0xdf, 0x4c, 0x77, 0x63, 0xaa, 0x75, 0x99, 0x5a, 0xc3, 0xf8, 0x4f, 0x07, + 0xbd, 0xbc, 0xd9, 0x9e, 0x2b, 0xb9, 0x14, 0x51, 0xae, 0x99, 0x11, 0x4a, 0x3e, 0x53, 0xe6, 0xff, + 0x94, 0xf4, 0x24, 0xf1, 0xe1, 0xbf, 0x13, 0x7f, 0x83, 0x5a, 0xa9, 0xda, 0x70, 0x4d, 0xde, 0xc2, + 0x10, 0x0a, 0x80, 0xe7, 0x68, 0x98, 0xaa, 0x98, 0x69, 0xf1, 0x0b, 0x04, 0xf7, 0x85, 0xbc, 0xe5, + 0x3a, 0x13, 0x4a, 0x42, 0xe5, 0x7b, 0x74, 0x50, 0x57, 0x2f, 0x76, 0x22, 0x9e, 0xa1, 0xd7, 0xd5, + 0x9f, 0xfd, 0x90, 0xdf, 0x0a, 0xd0, 0xa1, 0x29, 0x7d, 0x8a, 0x2b, 0xe9, 0xdb, 0x9d, 0x32, 0xfe, + 0xab, 0x89, 0xda, 0xd7, 0x86, 0x99, 0x3c, 0x7b, 0x5a, 0x9f, 0xf3, 0x5f, 0x63, 0x74, 0x6b, 0x63, + 0xdc, 0x47, 0xae, 0xb0, 0xad, 0x68, 0x4c, 0xba, 0xd4, 0x15, 0xa9, 0x5d, 0xa9, 0x34, 0x66, 0x66, + 0xa9, 0x74, 0x02, 0xe3, 0xee, 0xd2, 0x0a, 0xe3, 0x8f, 0x51, 0x3f, 0x50, 0xd2, 0xb0, 0xc0, 0xf8, + 0x3c, 0x61, 0x22, 0x26, 0x7d, 0x30, 0xbc, 0x28, 0xc9, 0xef, 0x2c, 0x87, 0x3d, 0xd4, 0x0b, 0x79, + 0x16, 0x68, 0x91, 0x42, 0xda, 0xfb, 0x60, 0xa9, 0x53, 0x78, 0x88, 0xda, 0x9a, 0x47, 0x56, 0x7c, + 0x09, 0x62, 0x89, 0x2c, 0xbf, 0xd0, 0x22, 0x8c, 0x38, 0x39, 0x28, 0xf8, 0x02, 0x81, 0x5f, 0xe5, + 0x86, 0x6b, 0xf2, 0xaa, 0xf4, 0x03, 0xda, 0x2d, 0xc2, 0xe0, 0x99, 0x45, 0xb0, 0x2b, 0xa4, 0x8d, + 0x81, 0xa6, 0xf7, 0xa9, 0x3d, 0xe2, 0xd7, 0xa8, 0xa5, 0xb7, 0xbe, 0x90, 0xb0, 0x44, 0x7d, 0xda, + 0xd4, 0xdb, 0x0b, 0x59, 0x92, 0x6a, 0x4d, 0x3e, 0xdb, 0x91, 0x97, 0x6b, 0x4b, 0x1a, 0x70, 0x9e, + 0x14, 0xa4, 0x29, 0x9d, 0x06, 0x9c, 0x9f, 0xef, 0xc8, 0xcb, 0x35, 0x3e, 0x46, 0xae, 0xca, 0xc8, + 0x19, 0x24, 0x73, 0x58, 0x25, 0x53, 0xcc, 0x65, 0x7a, 0x69, 0x53, 0xd2, 0x22, 0xc8, 0xa8, 0xab, + 0xb2, 0xa3, 0x7b, 0x07, 0x75, 0x2b, 0x06, 0x0f, 0x50, 0x3b, 0x56, 0x2c, 0xf4, 0x4f, 0x61, 0x60, + 0x2e, 0x6d, 0x59, 0x74, 0x5a, 0xd1, 0xf3, 0xf2, 0x4d, 0x00, 0x7a, 0x8e, 0xdf, 0xa2, 0x4e, 0xe1, + 0x9e, 0x97, 0xcf, 0x01, 0xb8, 0x4e, 0xe7, 0xf8, 0x13, 0xb4, 0x1f, 0xa4, 0xb9, 0x9f, 0x72, 0x1d, + 0x70, 0x69, 0x58, 0xc4, 0x61, 0xbf, 0x5d, 0xda, 0x0f, 0xd2, 0xfc, 0xaa, 0x22, 0xf1, 0x09, 0x7a, + 0x95, 0xf0, 0x44, 0xe9, 0xbb, 0xba, 0x73, 0x00, 0xce, 0x83, 0x42, 0xa8, 0x99, 0x3d, 0xd4, 0x33, + 0x3c, 0x49, 0xb9, 0x66, 0x26, 0xd7, 0x1c, 0x3a, 0xe8, 0xd2, 0x3a, 0xf5, 0xcd, 0xd7, 0xf7, 0x0f, + 0x23, 0xe7, 0xb7, 0x87, 0x91, 0xf3, 0xfb, 0xc3, 0xc8, 0xf9, 0xf5, 0x8f, 0xd1, 0x7b, 0x3f, 0x9d, + 0xbc, 0xc3, 0x1b, 0xbb, 0x68, 0xc3, 0x23, 0x79, 0xf6, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x79, + 0xa3, 0xfb, 0x4d, 0x99, 0x05, 0x00, 0x00, +} diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto new file mode 100644 index 000000000..bcb8a7a18 --- /dev/null +++ b/api/gateway/gateway.proto @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +package gateway; + +option go_package = "github.com/TheThingsNetwork/ttn/api/gateway"; + +message GPSMetadata { + int64 time = 1; + float latitude = 2; + float longitude = 3; + int32 altitude = 4; +} + +message RxMetadata { + string gateway_id = 1; + + uint32 timestamp = 11; + int64 time = 12; + + uint32 rf_chain = 21; + uint32 channel = 22; + + uint64 frequency = 31; // frequency in Hz + float rssi = 32; // received signal strength in dBm + float snr = 33; // signal-to-noise-ratio in dB + + GPSMetadata gps = 41; +} + +message TxConfiguration { + uint32 timestamp = 11; + + uint32 rf_chain = 21; + uint64 frequency = 22; // frequency in Hz + int32 power = 23; // transmit power in dBm + + bool polarization_inversion = 31; // LoRa polarization inversion (basically always true) + uint32 frequency_deviation = 32; // FSK frequency deviation in Hz +} + +// message Status represents a status update from a Gateway. +message Status { + uint32 timestamp = 1; + int64 time = 2; + + // Configuration and relatively static stuff + + repeated string ip = 11; + string platform = 12; + string contact_email = 13; + string description = 14; + string region = 15; + string bridge = 16; + string router = 17; + + GPSMetadata gps = 21; + + // Network (internet) stuff + + uint32 rtt = 31; + + // Rx and Tx stuff + + uint32 rx_in = 41; + uint32 rx_ok = 42; + uint32 tx_in = 43; + uint32 tx_ok = 44; + + // Additional metrics from the operating system + message OSMetrics { + float load_1 = 1; + float load_5 = 2; + float load_15 = 3; + + float cpu_percentage = 11; + + float memory_percentage = 21; + + float temperature = 31; + } + + OSMetrics os = 51; +} diff --git a/api/gateway/validation.go b/api/gateway/validation.go new file mode 100644 index 000000000..7cfbff8c7 --- /dev/null +++ b/api/gateway/validation.go @@ -0,0 +1,33 @@ +package gateway + +import "github.com/TheThingsNetwork/ttn/utils/errors" + +// Validate implements the api.Validator interface +func (m *RxMetadata) Validate() error { + if m.GatewayId == "" { + return errors.NewErrInvalidArgument("GatewayId", "can not be empty") + } + return nil +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration) Validate() error { + return nil +} + +// Validate implements the api.Validator interface +func (m *Status) Validate() error { + return nil +} + +// Validate implements the api.Validator interface +func (m *GPSMetadata) Validate() error { + if m == nil || m.IsZero() { + return errors.NewErrInvalidArgument("GPSMetadata", "can not be empty") + } + return nil +} + +func (m GPSMetadata) IsZero() bool { + return m.Latitude == 0 && m.Longitude == 0 +} diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md new file mode 100644 index 000000000..ff9f5be60 --- /dev/null +++ b/api/handler/ApplicationManager.md @@ -0,0 +1,414 @@ +# ApplicationManager API Reference + +ApplicationManager manages application and device registrations on the Handler + +To protect our quality of service, you can make up to 5000 calls to the +ApplicationManager API per hour. Once you go over the rate limit, you will +receive an error response. + +## Methods + +### `RegisterApplication` + +Applications should first be registered to the Handler with the `RegisterApplication` method + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`Empty`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `POST` `/applications` + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{} +``` + +### `GetApplication` + +GetApplication returns the application with the given identifier (app_id) + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`Application`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `GET` `/applications/{app_id}`(`app_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{ + "app_id": "some-app-id", + "converter": "function Converter(decoded, port) {...", + "decoder": "function Decoder(bytes, port) {...", + "encoder": "Encoder(object, port) {...", + "validator": "Validator(converted, port) {..." +} +``` + +### `SetApplication` + +SetApplication updates the settings for the application. All fields must be supplied. + +- Request: [`Application`](#handlerapplication) +- Response: [`Empty`](#handlerapplication) + +#### HTTP Endpoints + +- `POST` `/applications/{app_id}`(`app_id` can be left out of the request body) +- `PUT` `/applications/{app_id}`(`app_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "converter": "function Converter(decoded, port) {...", + "decoder": "function Decoder(bytes, port) {...", + "encoder": "Encoder(object, port) {...", + "validator": "Validator(converted, port) {..." +} +``` + +#### JSON Response Format + +```json +{} +``` + +### `DeleteApplication` + +DeleteApplication deletes the application with the given identifier (app_id) + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`Empty`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `DELETE` `/applications/{app_id}`(`app_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{} +``` + +### `GetDevice` + +GetDevice returns the device with the given identifier (app_id and dev_id) + +- Request: [`DeviceIdentifier`](#handlerdeviceidentifier) +- Response: [`Device`](#handlerdeviceidentifier) + +#### HTTP Endpoint + +- `GET` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id" +} +``` + +#### JSON Response Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id", + "lorawan_device": { + "activation_constraints": "local", + "app_eui": "0102030405060708", + "app_id": "some-app-id", + "app_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "dev_addr": "01020304", + "dev_eui": "0102030405060708", + "dev_id": "some-dev-id", + "disable_f_cnt_check": false, + "f_cnt_down": 0, + "f_cnt_up": 0, + "last_seen": 0, + "nwk_s_key": "01020304050607080102030405060708", + "uses32_bit_f_cnt": true + } +} +``` + +### `SetDevice` + +SetDevice creates or updates a device. All fields must be supplied. + +- Request: [`Device`](#handlerdevice) +- Response: [`Empty`](#handlerdevice) + +#### HTTP Endpoints + +- `POST` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) +- `PUT` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) +- `POST` `/applications/{app_id}/devices`(`app_id` can be left out of the request body) +- `PUT` `/applications/{app_id}/devices`(`app_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id", + "lorawan_device": { + "activation_constraints": "local", + "app_eui": "0102030405060708", + "app_id": "some-app-id", + "app_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "dev_addr": "01020304", + "dev_eui": "0102030405060708", + "dev_id": "some-dev-id", + "disable_f_cnt_check": false, + "f_cnt_down": 0, + "f_cnt_up": 0, + "last_seen": 0, + "nwk_s_key": "01020304050607080102030405060708", + "uses32_bit_f_cnt": true + } +} +``` + +#### JSON Response Format + +```json +{} +``` + +### `DeleteDevice` + +DeleteDevice deletes the device with the given identifier (app_id and dev_id) + +- Request: [`DeviceIdentifier`](#handlerdeviceidentifier) +- Response: [`Empty`](#handlerdeviceidentifier) + +#### HTTP Endpoint + +- `DELETE` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id" +} +``` + +#### JSON Response Format + +```json +{} +``` + +### `GetDevicesForApplication` + +GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`DeviceList`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `GET` `/applications/{app_id}/devices`(`app_id` can be left out of the request body) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{ + "devices": [ + { + "app_id": "some-app-id", + "dev_id": "some-dev-id", + "lorawan_device": { + "activation_constraints": "local", + "app_eui": "0102030405060708", + "app_id": "some-app-id", + "app_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "dev_addr": "01020304", + "dev_eui": "0102030405060708", + "dev_id": "some-dev-id", + "disable_f_cnt_check": false, + "f_cnt_down": 0, + "f_cnt_up": 0, + "last_seen": 0, + "nwk_s_key": "01020304050607080102030405060708", + "uses32_bit_f_cnt": true + } + } + ] +} +``` + +### `DryDownlink` + +DryUplink simulates processing an uplink message and returns the result + +- Request: [`DryDownlinkMessage`](#handlerdrydownlinkmessage) +- Response: [`DryDownlinkResult`](#handlerdrydownlinkmessage) + +### `DryUplink` + +DryUplink simulates processing a downlink message and returns the result + +- Request: [`DryUplinkMessage`](#handlerdryuplinkmessage) +- Response: [`DryUplinkResult`](#handlerdryuplinkmessage) + +## Messages + +### `.google.protobuf.Empty` + +A generic empty message that you can re-use to avoid defining duplicated +empty messages in your APIs. + +### `.handler.Application` + +The Application settings + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `app_id` | `string` | | +| `decoder` | `string` | The decoder is a JavaScript function that decodes a byte array to an object. | +| `converter` | `string` | The converter is a JavaScript function that can be used to convert values in the object returned from the decoder. This can for example be useful to convert a voltage to a temperature. | +| `validator` | `string` | The validator is a JavaScript function that checks the validity of the object returned by the decoder or converter. If validation fails, the message is dropped. | +| `encoder` | `string` | The encoder is a JavaScript function that encodes an object to a byte array. | + +### `.handler.ApplicationIdentifier` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `app_id` | `string` | | + +### `.handler.Device` + +The Device settings + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `app_id` | `string` | | +| `dev_id` | `string` | | +| `lorawan_device` | [`Device`](#lorawandevice) | | + +### `.handler.DeviceIdentifier` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `app_id` | `string` | | +| `dev_id` | `string` | | + +### `.handler.DeviceList` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `devices` | _repeated_ [`Device`](#handlerdevice) | | + +### `.handler.DryDownlinkMessage` + +DryDownlinkMessage is a simulated message to test downlink processing + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `payload` | `bytes` | The binary payload to use | +| `fields` | `string` | JSON-encoded object with fields to encode | +| `app` | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | +| `port` | `uint32` | The port number that should be passed to the payload function | + +### `.handler.DryDownlinkResult` + +DryDownlinkResult is the result from a downlink simulation + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `payload` | `bytes` | The payload that was encoded | +| `logs` | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | + +### `.handler.DryUplinkMessage` + +DryUplinkMessage is a simulated message to test uplink processing + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `payload` | `bytes` | The binary payload to use | +| `app` | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | +| `port` | `uint32` | The port number that should be passed to the payload function | + +### `.handler.DryUplinkResult` + +DryUplinkResult is the result from an uplink simulation + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `payload` | `bytes` | The binary payload | +| `fields` | `string` | The decoded fields | +| `valid` | `bool` | Was validation of the message successful | +| `logs` | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | + +### `.handler.LogEntry` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `function` | `string` | The location where the log was created (what payload function) | +| `fields` | _repeated_ `string` | A list of JSON-encoded fields that were logged | + +### `.lorawan.Device` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| `app_eui` | `bytes` | The AppEUI is a unique, 8 byte identifier for the application a device belongs to. | +| `dev_eui` | `bytes` | The DevEUI is a unique, 8 byte identifier for the device. | +| `app_id` | `string` | The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. | +| `dev_id` | `string` | The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. | +| `dev_addr` | `bytes` | The DevAddr is a dynamic, 4 byte session address for the device. | +| `nwk_s_key` | `bytes` | The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | +| `app_s_key` | `bytes` | The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | +| `app_key` | `bytes` | The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). | +| `f_cnt_up` | `uint32` | FCntUp is the uplink frame counter for a device session. | +| `f_cnt_down` | `uint32` | FCntDown is the downlink frame counter for a device session. | +| `disable_f_cnt_check` | `bool` | The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. | +| `uses32_bit_f_cnt` | `bool` | The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. | +| `activation_constraints` | `string` | The ActivationContstraints are used to allocate a device address for a device. There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. | +| `last_seen` | `int64` | When the device was last seen (Unix nanoseconds) | + diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go new file mode 100644 index 000000000..3fc10fc50 --- /dev/null +++ b/api/handler/handler.pb.go @@ -0,0 +1,3686 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/handler/handler.proto +// DO NOT EDIT! + +/* + Package handler is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/handler/handler.proto + + It has these top-level messages: + DeviceActivationResponse + StatusRequest + Status + ApplicationIdentifier + Application + DeviceIdentifier + Device + DeviceList + DryDownlinkMessage + DryUplinkMessage + LogEntry + DryUplinkResult + DryDownlinkResult +*/ +package handler + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" +import api "github.com/TheThingsNetwork/ttn/api" +import broker "github.com/TheThingsNetwork/ttn/api/broker" +import protocol "github.com/TheThingsNetwork/ttn/api/protocol" +import lorawan1 "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type DeviceActivationResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DownlinkOption *broker.DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` +} + +func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } +func (m *DeviceActivationResponse) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationResponse) ProtoMessage() {} +func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } + +func (m *DeviceActivationResponse) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DeviceActivationResponse) GetDownlinkOption() *broker.DownlinkOption { + if m != nil { + return m.DownlinkOption + } + return nil +} + +func (m *DeviceActivationResponse) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + +// message StatusRequest is used to request the status of this Handler +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } + +// message Status is the response to the StatusRequest +type Status struct { + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + Downlink *api.Rates `protobuf:"bytes,12,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,13,opt,name=activations" json:"activations,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{2} } + +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + +func (m *Status) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *Status) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *Status) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + +type ApplicationIdentifier struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` +} + +func (m *ApplicationIdentifier) Reset() { *m = ApplicationIdentifier{} } +func (m *ApplicationIdentifier) String() string { return proto.CompactTextString(m) } +func (*ApplicationIdentifier) ProtoMessage() {} +func (*ApplicationIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } + +// The Application settings +type Application struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + // The decoder is a JavaScript function that decodes a byte array to an object. + Decoder string `protobuf:"bytes,2,opt,name=decoder,proto3" json:"decoder,omitempty"` + // The converter is a JavaScript function that can be used to convert values + // in the object returned from the decoder. This can for example be useful to + // convert a voltage to a temperature. + Converter string `protobuf:"bytes,3,opt,name=converter,proto3" json:"converter,omitempty"` + // The validator is a JavaScript function that checks the validity of the + // object returned by the decoder or converter. If validation fails, the + // message is dropped. + Validator string `protobuf:"bytes,4,opt,name=validator,proto3" json:"validator,omitempty"` + // The encoder is a JavaScript function that encodes an object to a byte array. + Encoder string `protobuf:"bytes,5,opt,name=encoder,proto3" json:"encoder,omitempty"` +} + +func (m *Application) Reset() { *m = Application{} } +func (m *Application) String() string { return proto.CompactTextString(m) } +func (*Application) ProtoMessage() {} +func (*Application) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } + +type DeviceIdentifier struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,2,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` +} + +func (m *DeviceIdentifier) Reset() { *m = DeviceIdentifier{} } +func (m *DeviceIdentifier) String() string { return proto.CompactTextString(m) } +func (*DeviceIdentifier) ProtoMessage() {} +func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } + +// The Device settings +type Device struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,2,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + // The device can be of different kinds + // + // Types that are valid to be assigned to Device: + // *Device_LorawanDevice + Device isDevice_Device `protobuf_oneof:"device"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} +func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{6} } + +type isDevice_Device interface { + isDevice_Device() + MarshalTo([]byte) (int, error) + Size() int +} + +type Device_LorawanDevice struct { + LorawanDevice *lorawan1.Device `protobuf:"bytes,3,opt,name=lorawan_device,json=lorawanDevice,oneof"` +} + +func (*Device_LorawanDevice) isDevice_Device() {} + +func (m *Device) GetDevice() isDevice_Device { + if m != nil { + return m.Device + } + return nil +} + +func (m *Device) GetLorawanDevice() *lorawan1.Device { + if x, ok := m.GetDevice().(*Device_LorawanDevice); ok { + return x.LorawanDevice + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Device) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Device_OneofMarshaler, _Device_OneofUnmarshaler, _Device_OneofSizer, []interface{}{ + (*Device_LorawanDevice)(nil), + } +} + +func _Device_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Device) + // device + switch x := m.Device.(type) { + case *Device_LorawanDevice: + _ = b.EncodeVarint(3<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.LorawanDevice); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Device.Device has unexpected type %T", x) + } + return nil +} + +func _Device_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Device) + switch tag { + case 3: // device.lorawan_device + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan1.Device) + err := b.DecodeMessage(msg) + m.Device = &Device_LorawanDevice{msg} + return true, err + default: + return false, nil + } +} + +func _Device_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Device) + // device + switch x := m.Device.(type) { + case *Device_LorawanDevice: + s := proto.Size(x.LorawanDevice) + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type DeviceList struct { + Devices []*Device `protobuf:"bytes,1,rep,name=devices" json:"devices,omitempty"` +} + +func (m *DeviceList) Reset() { *m = DeviceList{} } +func (m *DeviceList) String() string { return proto.CompactTextString(m) } +func (*DeviceList) ProtoMessage() {} +func (*DeviceList) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{7} } + +func (m *DeviceList) GetDevices() []*Device { + if m != nil { + return m.Devices + } + return nil +} + +// DryDownlinkMessage is a simulated message to test downlink processing +type DryDownlinkMessage struct { + // The binary payload to use + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // JSON-encoded object with fields to encode + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + // The Application containing the payload functions that should be executed + App *Application `protobuf:"bytes,3,opt,name=app" json:"app,omitempty"` + // The port number that should be passed to the payload function + Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` +} + +func (m *DryDownlinkMessage) Reset() { *m = DryDownlinkMessage{} } +func (m *DryDownlinkMessage) String() string { return proto.CompactTextString(m) } +func (*DryDownlinkMessage) ProtoMessage() {} +func (*DryDownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{8} } + +func (m *DryDownlinkMessage) GetApp() *Application { + if m != nil { + return m.App + } + return nil +} + +// DryUplinkMessage is a simulated message to test uplink processing +type DryUplinkMessage struct { + // The binary payload to use + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // The Application containing the payload functions that should be executed + App *Application `protobuf:"bytes,2,opt,name=app" json:"app,omitempty"` + // The port number that should be passed to the payload function + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` +} + +func (m *DryUplinkMessage) Reset() { *m = DryUplinkMessage{} } +func (m *DryUplinkMessage) String() string { return proto.CompactTextString(m) } +func (*DryUplinkMessage) ProtoMessage() {} +func (*DryUplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{9} } + +func (m *DryUplinkMessage) GetApp() *Application { + if m != nil { + return m.App + } + return nil +} + +type LogEntry struct { + // The location where the log was created (what payload function) + Function string `protobuf:"bytes,1,opt,name=function,proto3" json:"function,omitempty"` + // A list of JSON-encoded fields that were logged + Fields []string `protobuf:"bytes,2,rep,name=fields" json:"fields,omitempty"` +} + +func (m *LogEntry) Reset() { *m = LogEntry{} } +func (m *LogEntry) String() string { return proto.CompactTextString(m) } +func (*LogEntry) ProtoMessage() {} +func (*LogEntry) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{10} } + +// DryUplinkResult is the result from an uplink simulation +type DryUplinkResult struct { + // The binary payload + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // The decoded fields + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + // Was validation of the message successful + Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` + // Logs that have been generated while processing + Logs []*LogEntry `protobuf:"bytes,4,rep,name=logs" json:"logs,omitempty"` +} + +func (m *DryUplinkResult) Reset() { *m = DryUplinkResult{} } +func (m *DryUplinkResult) String() string { return proto.CompactTextString(m) } +func (*DryUplinkResult) ProtoMessage() {} +func (*DryUplinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{11} } + +func (m *DryUplinkResult) GetLogs() []*LogEntry { + if m != nil { + return m.Logs + } + return nil +} + +// DryDownlinkResult is the result from a downlink simulation +type DryDownlinkResult struct { + // The payload that was encoded + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Logs that have been generated while processing + Logs []*LogEntry `protobuf:"bytes,2,rep,name=logs" json:"logs,omitempty"` +} + +func (m *DryDownlinkResult) Reset() { *m = DryDownlinkResult{} } +func (m *DryDownlinkResult) String() string { return proto.CompactTextString(m) } +func (*DryDownlinkResult) ProtoMessage() {} +func (*DryDownlinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{12} } + +func (m *DryDownlinkResult) GetLogs() []*LogEntry { + if m != nil { + return m.Logs + } + return nil +} + +func init() { + proto.RegisterType((*DeviceActivationResponse)(nil), "handler.DeviceActivationResponse") + proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") + proto.RegisterType((*Status)(nil), "handler.Status") + proto.RegisterType((*ApplicationIdentifier)(nil), "handler.ApplicationIdentifier") + proto.RegisterType((*Application)(nil), "handler.Application") + proto.RegisterType((*DeviceIdentifier)(nil), "handler.DeviceIdentifier") + proto.RegisterType((*Device)(nil), "handler.Device") + proto.RegisterType((*DeviceList)(nil), "handler.DeviceList") + proto.RegisterType((*DryDownlinkMessage)(nil), "handler.DryDownlinkMessage") + proto.RegisterType((*DryUplinkMessage)(nil), "handler.DryUplinkMessage") + proto.RegisterType((*LogEntry)(nil), "handler.LogEntry") + proto.RegisterType((*DryUplinkResult)(nil), "handler.DryUplinkResult") + proto.RegisterType((*DryDownlinkResult)(nil), "handler.DryDownlinkResult") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Handler service + +type HandlerClient interface { + ActivationChallenge(ctx context.Context, in *broker.ActivationChallengeRequest, opts ...grpc.CallOption) (*broker.ActivationChallengeResponse, error) + Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) +} + +type handlerClient struct { + cc *grpc.ClientConn +} + +func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { + return &handlerClient{cc} +} + +func (c *handlerClient) ActivationChallenge(ctx context.Context, in *broker.ActivationChallengeRequest, opts ...grpc.CallOption) (*broker.ActivationChallengeResponse, error) { + out := new(broker.ActivationChallengeResponse) + err := grpc.Invoke(ctx, "/handler.Handler/ActivationChallenge", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { + out := new(DeviceActivationResponse) + err := grpc.Invoke(ctx, "/handler.Handler/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Handler service + +type HandlerServer interface { + ActivationChallenge(context.Context, *broker.ActivationChallengeRequest) (*broker.ActivationChallengeResponse, error) + Activate(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*DeviceActivationResponse, error) +} + +func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { + s.RegisterService(&_Handler_serviceDesc, srv) +} + +func _Handler_ActivationChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.ActivationChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerServer).ActivationChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.Handler/ActivationChallenge", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).ActivationChallenge(ctx, req.(*broker.ActivationChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Handler_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.DeduplicatedDeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerServer).Activate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.Handler/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).Activate(ctx, req.(*broker.DeduplicatedDeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Handler_serviceDesc = grpc.ServiceDesc{ + ServiceName: "handler.Handler", + HandlerType: (*HandlerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ActivationChallenge", + Handler: _Handler_ActivationChallenge_Handler, + }, + { + MethodName: "Activate", + Handler: _Handler_Activate_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/handler/handler.proto", +} + +// Client API for ApplicationManager service + +type ApplicationManagerClient interface { + // Applications should first be registered to the Handler with the `RegisterApplication` method + RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // GetApplication returns the application with the given identifier (app_id) + GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) + // SetApplication updates the settings for the application. All fields must be supplied. + SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // DeleteApplication deletes the application with the given identifier (app_id) + DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // GetDevice returns the device with the given identifier (app_id and dev_id) + GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) + // SetDevice creates or updates a device. All fields must be supplied. + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // DeleteDevice deletes the device with the given identifier (app_id and dev_id) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) + GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) + // DryUplink simulates processing an uplink message and returns the result + DryDownlink(ctx context.Context, in *DryDownlinkMessage, opts ...grpc.CallOption) (*DryDownlinkResult, error) + // DryUplink simulates processing a downlink message and returns the result + DryUplink(ctx context.Context, in *DryUplinkMessage, opts ...grpc.CallOption) (*DryUplinkResult, error) +} + +type applicationManagerClient struct { + cc *grpc.ClientConn +} + +func NewApplicationManagerClient(cc *grpc.ClientConn) ApplicationManagerClient { + return &applicationManagerClient{cc} +} + +func (c *applicationManagerClient) RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/RegisterApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) { + out := new(Application) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/SetApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DeleteApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) { + out := new(Device) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/SetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DeleteDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) { + out := new(DeviceList) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetDevicesForApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DryDownlink(ctx context.Context, in *DryDownlinkMessage, opts ...grpc.CallOption) (*DryDownlinkResult, error) { + out := new(DryDownlinkResult) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DryDownlink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DryUplink(ctx context.Context, in *DryUplinkMessage, opts ...grpc.CallOption) (*DryUplinkResult, error) { + out := new(DryUplinkResult) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DryUplink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for ApplicationManager service + +type ApplicationManagerServer interface { + // Applications should first be registered to the Handler with the `RegisterApplication` method + RegisterApplication(context.Context, *ApplicationIdentifier) (*google_protobuf.Empty, error) + // GetApplication returns the application with the given identifier (app_id) + GetApplication(context.Context, *ApplicationIdentifier) (*Application, error) + // SetApplication updates the settings for the application. All fields must be supplied. + SetApplication(context.Context, *Application) (*google_protobuf.Empty, error) + // DeleteApplication deletes the application with the given identifier (app_id) + DeleteApplication(context.Context, *ApplicationIdentifier) (*google_protobuf.Empty, error) + // GetDevice returns the device with the given identifier (app_id and dev_id) + GetDevice(context.Context, *DeviceIdentifier) (*Device, error) + // SetDevice creates or updates a device. All fields must be supplied. + SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) + // DeleteDevice deletes the device with the given identifier (app_id and dev_id) + DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) + // GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) + GetDevicesForApplication(context.Context, *ApplicationIdentifier) (*DeviceList, error) + // DryUplink simulates processing an uplink message and returns the result + DryDownlink(context.Context, *DryDownlinkMessage) (*DryDownlinkResult, error) + // DryUplink simulates processing a downlink message and returns the result + DryUplink(context.Context, *DryUplinkMessage) (*DryUplinkResult, error) +} + +func RegisterApplicationManagerServer(s *grpc.Server, srv ApplicationManagerServer) { + s.RegisterService(&_ApplicationManager_serviceDesc, srv) +} + +func _ApplicationManager_RegisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).RegisterApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/RegisterApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).RegisterApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_GetApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).GetApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/GetApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).GetApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_SetApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Application) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).SetApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/SetApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).SetApplication(ctx, req.(*Application)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DeleteApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DeleteApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DeleteApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DeleteApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_GetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).GetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/GetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).GetDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_SetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Device) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).SetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/SetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).SetDevice(ctx, req.(*Device)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DeleteDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DeleteDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DeleteDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DeleteDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_GetDevicesForApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).GetDevicesForApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/GetDevicesForApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).GetDevicesForApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DryDownlink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DryDownlinkMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DryDownlink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DryDownlink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DryDownlink(ctx, req.(*DryDownlinkMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DryUplink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DryUplinkMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DryUplink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DryUplink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DryUplink(ctx, req.(*DryUplinkMessage)) + } + return interceptor(ctx, in, info, handler) +} + +var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "handler.ApplicationManager", + HandlerType: (*ApplicationManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterApplication", + Handler: _ApplicationManager_RegisterApplication_Handler, + }, + { + MethodName: "GetApplication", + Handler: _ApplicationManager_GetApplication_Handler, + }, + { + MethodName: "SetApplication", + Handler: _ApplicationManager_SetApplication_Handler, + }, + { + MethodName: "DeleteApplication", + Handler: _ApplicationManager_DeleteApplication_Handler, + }, + { + MethodName: "GetDevice", + Handler: _ApplicationManager_GetDevice_Handler, + }, + { + MethodName: "SetDevice", + Handler: _ApplicationManager_SetDevice_Handler, + }, + { + MethodName: "DeleteDevice", + Handler: _ApplicationManager_DeleteDevice_Handler, + }, + { + MethodName: "GetDevicesForApplication", + Handler: _ApplicationManager_GetDevicesForApplication_Handler, + }, + { + MethodName: "DryDownlink", + Handler: _ApplicationManager_DryDownlink_Handler, + }, + { + MethodName: "DryUplink", + Handler: _ApplicationManager_DryUplink_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/handler/handler.proto", +} + +// Client API for HandlerManager service + +type HandlerManagerClient interface { + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) +} + +type handlerManagerClient struct { + cc *grpc.ClientConn +} + +func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { + return &handlerManagerClient{cc} +} + +func (c *handlerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/handler.HandlerManager/GetStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for HandlerManager service + +type HandlerManagerServer interface { + GetStatus(context.Context, *StatusRequest) (*Status, error) +} + +func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { + s.RegisterService(&_HandlerManager_serviceDesc, srv) +} + +func _HandlerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerManagerServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.HandlerManager/GetStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).GetStatus(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _HandlerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "handler.HandlerManager", + HandlerType: (*HandlerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetStatus", + Handler: _HandlerManager_GetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/handler/handler.proto", +} + +func (m *DeviceActivationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceActivationResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Message.Size())) + n1, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.DownlinkOption != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.DownlinkOption.Size())) + n2, err := m.DownlinkOption.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.ActivationMetadata != nil { + dAtA[i] = 0xba + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.ActivationMetadata.Size())) + n3, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.System != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.System.Size())) + n4, err := m.System.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Component != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Component.Size())) + n5, err := m.Component.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Uplink != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Uplink.Size())) + n6, err := m.Uplink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.Downlink != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Downlink.Size())) + n7, err := m.Downlink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.Activations != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Activations.Size())) + n8, err := m.Activations.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} + +func (m *ApplicationIdentifier) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ApplicationIdentifier) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + return i, nil +} + +func (m *Application) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Application) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.Decoder) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Decoder))) + i += copy(dAtA[i:], m.Decoder) + } + if len(m.Converter) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Converter))) + i += copy(dAtA[i:], m.Converter) + } + if len(m.Validator) > 0 { + dAtA[i] = 0x22 + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Validator))) + i += copy(dAtA[i:], m.Validator) + } + if len(m.Encoder) > 0 { + dAtA[i] = 0x2a + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Encoder))) + i += copy(dAtA[i:], m.Encoder) + } + return i, nil +} + +func (m *DeviceIdentifier) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceIdentifier) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + return i, nil +} + +func (m *Device) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Device) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + if m.Device != nil { + nn9, err := m.Device.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn9 + } + return i, nil +} + +func (m *Device_LorawanDevice) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.LorawanDevice != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.LorawanDevice.Size())) + n10, err := m.LorawanDevice.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + } + return i, nil +} +func (m *DeviceList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Devices) > 0 { + for _, msg := range m.Devices { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DryDownlinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DryDownlinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if len(m.Fields) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Fields))) + i += copy(dAtA[i:], m.Fields) + } + if m.App != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.App.Size())) + n11, err := m.App.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 + } + if m.Port != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Port)) + } + return i, nil +} + +func (m *DryUplinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DryUplinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.App != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.App.Size())) + n12, err := m.App.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n12 + } + if m.Port != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintHandler(dAtA, i, uint64(m.Port)) + } + return i, nil +} + +func (m *LogEntry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LogEntry) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Function) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Function))) + i += copy(dAtA[i:], m.Function) + } + if len(m.Fields) > 0 { + for _, s := range m.Fields { + dAtA[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + return i, nil +} + +func (m *DryUplinkResult) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DryUplinkResult) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if len(m.Fields) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Fields))) + i += copy(dAtA[i:], m.Fields) + } + if m.Valid { + dAtA[i] = 0x18 + i++ + if m.Valid { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if len(m.Logs) > 0 { + for _, msg := range m.Logs { + dAtA[i] = 0x22 + i++ + i = encodeVarintHandler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DryDownlinkResult) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DryDownlinkResult) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if len(m.Logs) > 0 { + for _, msg := range m.Logs { + dAtA[i] = 0x12 + i++ + i = encodeVarintHandler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeFixed64Handler(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Handler(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintHandler(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *DeviceActivationResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.DownlinkOption != nil { + l = m.DownlinkOption.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *Status) Size() (n int) { + var l int + _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *ApplicationIdentifier) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *Application) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Decoder) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Converter) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Encoder) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *DeviceIdentifier) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *Device) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.Device != nil { + n += m.Device.Size() + } + return n +} + +func (m *Device_LorawanDevice) Size() (n int) { + var l int + _ = l + if m.LorawanDevice != nil { + l = m.LorawanDevice.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} +func (m *DeviceList) Size() (n int) { + var l int + _ = l + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.Size() + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + +func (m *DryDownlinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Fields) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.App != nil { + l = m.App.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Port != 0 { + n += 1 + sovHandler(uint64(m.Port)) + } + return n +} + +func (m *DryUplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.App != nil { + l = m.App.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Port != 0 { + n += 1 + sovHandler(uint64(m.Port)) + } + return n +} + +func (m *LogEntry) Size() (n int) { + var l int + _ = l + l = len(m.Function) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if len(m.Fields) > 0 { + for _, s := range m.Fields { + l = len(s) + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + +func (m *DryUplinkResult) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Fields) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.Valid { + n += 2 + } + if len(m.Logs) > 0 { + for _, e := range m.Logs { + l = e.Size() + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + +func (m *DryDownlinkResult) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if len(m.Logs) > 0 { + for _, e := range m.Logs { + l = e.Size() + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + +func sovHandler(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozHandler(x uint64) (n int) { + return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DeviceActivationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &broker.DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.System == nil { + m.System = &api.SystemStats{} + } + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ApplicationIdentifier) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationIdentifier: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Application) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Application: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Application: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Decoder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Converter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encoder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceIdentifier) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceIdentifier: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LorawanDevice", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan1.Device{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Device = &Device_LorawanDevice{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Devices = append(m.Devices, &Device{}) + if err := m.Devices[len(m.Devices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryDownlinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryDownlinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryDownlinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field App", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.App == nil { + m.App = &Application{} + } + if err := m.App.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Port |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryUplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryUplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryUplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field App", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.App == nil { + m.App = &Application{} + } + if err := m.App.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Port |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LogEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LogEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LogEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Function", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Function = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = append(m.Fields, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryUplinkResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryUplinkResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryUplinkResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Valid", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Valid = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Logs = append(m.Logs, &LogEntry{}) + if err := m.Logs[len(m.Logs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryDownlinkResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryDownlinkResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryDownlinkResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Logs = append(m.Logs, &LogEntry{}) + if err := m.Logs[len(m.Logs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipHandler(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthHandler + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipHandler(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthHandler = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHandler = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/handler/handler.proto", fileDescriptorHandler) +} + +var fileDescriptorHandler = []byte{ + // 1134 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0xdb, 0x6e, 0xdc, 0x44, + 0x18, 0xae, 0x73, 0xd8, 0xec, 0xfe, 0x9b, 0xe3, 0xa4, 0x0d, 0xc6, 0x89, 0x96, 0x30, 0x55, 0x43, + 0x9a, 0x54, 0xb6, 0x58, 0x90, 0x28, 0x95, 0x28, 0x3d, 0xa4, 0x69, 0x23, 0x35, 0x20, 0x39, 0xe1, + 0x26, 0x17, 0x44, 0x93, 0xf5, 0xc4, 0x6b, 0xc5, 0xeb, 0x31, 0x9e, 0xd9, 0x8d, 0x96, 0xaa, 0x08, + 0xf5, 0x15, 0xe0, 0xa2, 0x0f, 0xc0, 0x1d, 0xcf, 0x81, 0xc4, 0x25, 0x12, 0x0f, 0x00, 0x0a, 0xbc, + 0x00, 0x6f, 0x80, 0x3c, 0x33, 0xf6, 0x3a, 0x7b, 0xc8, 0x01, 0x71, 0x95, 0xfd, 0xff, 0xef, 0xf3, + 0xf7, 0x9f, 0xc6, 0xff, 0x38, 0xf0, 0xa9, 0x1f, 0x88, 0x66, 0xfb, 0xc8, 0x6e, 0xb0, 0x96, 0xb3, + 0xdf, 0xa4, 0xfb, 0xcd, 0x20, 0xf2, 0xf9, 0x17, 0x54, 0x9c, 0xb2, 0xe4, 0xc4, 0x11, 0x22, 0x72, + 0x48, 0x1c, 0x38, 0x4d, 0x12, 0x79, 0x21, 0x4d, 0xb2, 0xbf, 0x76, 0x9c, 0x30, 0xc1, 0xd0, 0x94, + 0x36, 0xad, 0x65, 0x9f, 0x31, 0x3f, 0xa4, 0x8e, 0x74, 0x1f, 0xb5, 0x8f, 0x1d, 0xda, 0x8a, 0x45, + 0x57, 0xb1, 0xac, 0x15, 0x0d, 0xa6, 0x3a, 0x24, 0x8a, 0x98, 0x20, 0x22, 0x60, 0x11, 0xd7, 0xe8, + 0x42, 0x16, 0x82, 0xc4, 0x81, 0x76, 0x2d, 0x67, 0xae, 0xa3, 0x84, 0x9d, 0xd0, 0x44, 0xff, 0xd1, + 0xe0, 0x7b, 0x19, 0x28, 0xcd, 0x06, 0x0b, 0xf3, 0x1f, 0x9a, 0x70, 0x67, 0x80, 0x10, 0xb2, 0x84, + 0x9c, 0x92, 0xc8, 0xf1, 0x68, 0x27, 0x68, 0x50, 0x45, 0xc3, 0xff, 0x18, 0x60, 0x6e, 0x49, 0xc7, + 0xe3, 0x86, 0x08, 0x3a, 0x32, 0x27, 0x97, 0xf2, 0x98, 0x45, 0x9c, 0x22, 0x13, 0xa6, 0x62, 0xd2, + 0x0d, 0x19, 0xf1, 0x4c, 0x63, 0xd5, 0x58, 0x9f, 0x76, 0x33, 0x13, 0x6d, 0xc2, 0x54, 0x8b, 0x72, + 0x4e, 0x7c, 0x6a, 0x8e, 0xad, 0x1a, 0xeb, 0xd5, 0xfa, 0x82, 0x9d, 0xc7, 0xdf, 0x55, 0x80, 0x9b, + 0x31, 0xd0, 0xe7, 0x30, 0xe7, 0xb1, 0xd3, 0x28, 0x0c, 0xa2, 0x93, 0x43, 0x16, 0xa7, 0x11, 0xcc, + 0xaa, 0x7c, 0x68, 0xc9, 0xd6, 0x35, 0x6d, 0x69, 0xf8, 0x4b, 0x89, 0xba, 0xb3, 0xde, 0x39, 0x1b, + 0xed, 0xc2, 0x22, 0xc9, 0xb3, 0x3b, 0x6c, 0x51, 0x41, 0x3c, 0x22, 0x88, 0xf9, 0x8e, 0x14, 0x59, + 0xe9, 0x45, 0xee, 0x95, 0xb0, 0xab, 0x39, 0x2e, 0x22, 0x03, 0x3e, 0x3c, 0x07, 0x33, 0x7b, 0x82, + 0x88, 0x36, 0x77, 0xe9, 0x37, 0x6d, 0xca, 0x05, 0xfe, 0xc3, 0x80, 0x92, 0xf2, 0xa0, 0x75, 0x28, + 0xf1, 0x2e, 0x17, 0xb4, 0x25, 0x2b, 0xae, 0xd6, 0xe7, 0xed, 0x74, 0x20, 0x7b, 0xd2, 0x95, 0x52, + 0xb8, 0xab, 0x71, 0xf4, 0x21, 0x54, 0x1a, 0xac, 0x15, 0xb3, 0x88, 0x46, 0x42, 0x37, 0x61, 0x51, + 0x92, 0x9f, 0x66, 0x5e, 0xc5, 0xef, 0xb1, 0x10, 0x86, 0x52, 0x3b, 0x4e, 0xeb, 0xd2, 0xf5, 0x83, + 0xe4, 0xbb, 0x44, 0x50, 0xee, 0x6a, 0x04, 0xad, 0x41, 0x39, 0xab, 0xde, 0x9c, 0x1e, 0x60, 0xe5, + 0x18, 0xba, 0x07, 0xd5, 0x5e, 0x69, 0xdc, 0x9c, 0x19, 0xa0, 0x16, 0x61, 0x6c, 0xc3, 0xad, 0xc7, + 0x71, 0x1c, 0x06, 0x0d, 0x69, 0xef, 0x78, 0x34, 0x12, 0xc1, 0x71, 0x40, 0x13, 0x74, 0x0b, 0x4a, + 0x24, 0x8e, 0x0f, 0x03, 0x35, 0xe1, 0x8a, 0x3b, 0x49, 0xe2, 0x78, 0xc7, 0xc3, 0x3f, 0x1a, 0x50, + 0x2d, 0x3c, 0x30, 0x82, 0x96, 0x1e, 0x10, 0x8f, 0x36, 0x98, 0x47, 0x13, 0xd9, 0x81, 0x8a, 0x9b, + 0x99, 0x68, 0x25, 0xed, 0x4e, 0xd4, 0xa1, 0x89, 0xa0, 0x89, 0x39, 0x2e, 0xb1, 0x9e, 0x23, 0x45, + 0x3b, 0x24, 0x0c, 0x3c, 0x22, 0x58, 0x62, 0x4e, 0x28, 0x34, 0x77, 0xa4, 0xaa, 0x34, 0x52, 0xaa, + 0x93, 0x4a, 0x55, 0x9b, 0xf8, 0x11, 0xcc, 0xab, 0xc3, 0x7a, 0x69, 0x05, 0xa9, 0xdb, 0xa3, 0x9d, + 0xd4, 0xad, 0x32, 0x9b, 0xf4, 0x68, 0x67, 0xc7, 0xc3, 0xdf, 0x42, 0x49, 0x29, 0x5c, 0xef, 0x39, + 0x74, 0x1f, 0x66, 0xf5, 0xfb, 0x73, 0xa8, 0xde, 0x1f, 0x59, 0x54, 0xb5, 0x3e, 0x67, 0x6b, 0xb7, + 0xad, 0x64, 0x5f, 0xdc, 0x70, 0x67, 0xb4, 0x47, 0x39, 0x9e, 0x94, 0xa5, 0x60, 0xd0, 0xa0, 0xf8, + 0x13, 0x00, 0xe5, 0x7b, 0x19, 0x70, 0x81, 0xee, 0xa6, 0xbd, 0x4b, 0x2d, 0x6e, 0x1a, 0xab, 0xe3, + 0x52, 0x2a, 0x5b, 0x2b, 0x8a, 0xe5, 0x66, 0x38, 0x7e, 0x63, 0x00, 0xda, 0x4a, 0xba, 0xd9, 0x5b, + 0xa2, 0x5f, 0xb0, 0x0b, 0x5e, 0xcf, 0x25, 0x28, 0x1d, 0x07, 0x34, 0xf4, 0xb8, 0x2e, 0x42, 0x5b, + 0x68, 0x0d, 0xc6, 0x49, 0x1c, 0xeb, 0xd4, 0x6f, 0xe6, 0xf1, 0x0a, 0x93, 0x76, 0x53, 0x02, 0x42, + 0x30, 0x11, 0xb3, 0x44, 0xc8, 0xd1, 0xcc, 0xb8, 0xf2, 0x37, 0x6e, 0xc2, 0xfc, 0x56, 0xd2, 0xfd, + 0x2a, 0xbe, 0x5a, 0x06, 0x3a, 0xd2, 0xd8, 0x55, 0x23, 0x8d, 0x17, 0x22, 0x3d, 0x84, 0xf2, 0x4b, + 0xe6, 0x3f, 0x8b, 0x44, 0xd2, 0x45, 0x16, 0x94, 0x8f, 0xdb, 0x51, 0x43, 0x2e, 0x0d, 0x35, 0xa7, + 0xdc, 0x3e, 0x57, 0xe5, 0x78, 0xaf, 0x4a, 0xfc, 0xbd, 0x01, 0x73, 0x79, 0xaa, 0x2e, 0xe5, 0xed, + 0x50, 0xfc, 0x87, 0x5e, 0xdd, 0x84, 0x49, 0x79, 0x24, 0x65, 0x6a, 0x65, 0x57, 0x19, 0xe8, 0x0e, + 0x4c, 0x84, 0xcc, 0xe7, 0xe6, 0x84, 0x1c, 0xd9, 0x42, 0x5e, 0x58, 0x96, 0xb0, 0x2b, 0x61, 0xbc, + 0x0f, 0x0b, 0x85, 0x81, 0x5d, 0x9a, 0x43, 0xa6, 0x3a, 0x76, 0xa1, 0x6a, 0xfd, 0x17, 0x03, 0xa6, + 0x5e, 0x28, 0x08, 0x7d, 0x0d, 0x8b, 0xbd, 0x75, 0xf7, 0xb4, 0x49, 0xc2, 0x90, 0x46, 0x3e, 0x45, + 0x38, 0x5b, 0xa9, 0x43, 0x40, 0xbd, 0xee, 0xac, 0xdb, 0x17, 0x72, 0xf4, 0xee, 0x3f, 0x80, 0xb2, + 0x86, 0x29, 0xda, 0xcc, 0xf7, 0x34, 0xf5, 0xda, 0x6a, 0x80, 0xd4, 0x1b, 0xbc, 0x35, 0x94, 0xfa, + 0xfb, 0x7d, 0xc7, 0x78, 0xf0, 0x5e, 0xa9, 0xbf, 0xad, 0x00, 0x2a, 0x9c, 0x84, 0x5d, 0x12, 0x11, + 0x9f, 0x26, 0xc8, 0x87, 0x45, 0x97, 0xfa, 0x01, 0x17, 0x34, 0x29, 0xee, 0x9e, 0xda, 0xb0, 0xd3, + 0xd3, 0x5b, 0x00, 0xd6, 0x92, 0xad, 0x6e, 0x56, 0x3b, 0xbb, 0x76, 0xed, 0x67, 0xe9, 0xb5, 0x8b, + 0xcd, 0x37, 0xbf, 0xff, 0xfd, 0xc3, 0x18, 0xc2, 0x33, 0x0e, 0xe9, 0x3d, 0xc7, 0x1f, 0x18, 0x1b, + 0xe8, 0x18, 0x66, 0x9f, 0x53, 0x71, 0x9d, 0x18, 0x43, 0x4f, 0x30, 0xae, 0xc9, 0x08, 0x26, 0x5a, + 0x3a, 0x17, 0xc1, 0x79, 0xa5, 0xf6, 0xca, 0x6b, 0xf4, 0x1d, 0xcc, 0xee, 0x9d, 0x8f, 0x33, 0x54, + 0x67, 0x64, 0x05, 0x0f, 0xa5, 0xfe, 0x7d, 0x3c, 0x42, 0xff, 0x81, 0xb1, 0x71, 0xb0, 0x6c, 0x8d, + 0x06, 0xd1, 0x09, 0x2c, 0x6c, 0xd1, 0x90, 0x0a, 0xfa, 0x7f, 0xb4, 0x53, 0x17, 0xbb, 0x31, 0xaa, + 0xd8, 0x26, 0x54, 0x9e, 0x53, 0xa1, 0x97, 0xeb, 0xbb, 0x7d, 0x87, 0xa0, 0xa0, 0xdf, 0xbf, 0xe6, + 0xb0, 0x23, 0x85, 0xef, 0xa2, 0x0f, 0x86, 0x0b, 0xeb, 0xef, 0x15, 0xee, 0xbc, 0x52, 0x7b, 0xf9, + 0x35, 0x3a, 0x33, 0xa0, 0xb2, 0x97, 0x87, 0xea, 0xd7, 0x1b, 0x59, 0xc0, 0xcf, 0x86, 0x0c, 0xf4, + 0x93, 0x81, 0xaf, 0x1a, 0x29, 0x6d, 0xf0, 0x3d, 0xeb, 0x3a, 0xec, 0xdb, 0xb8, 0x76, 0x31, 0x5b, + 0x92, 0xac, 0xcb, 0x49, 0x28, 0x81, 0x69, 0x35, 0xbb, 0xcb, 0x3b, 0x3a, 0xaa, 0x60, 0xdd, 0xd8, + 0x8d, 0x2b, 0x37, 0xf6, 0x14, 0xcc, 0x7c, 0x84, 0x7c, 0x9b, 0x5d, 0xeb, 0x2d, 0x5c, 0xec, 0xcb, + 0x2f, 0xbd, 0xe3, 0xf0, 0x9a, 0xcc, 0x60, 0x15, 0x5d, 0x52, 0x2f, 0xda, 0x86, 0x6a, 0x61, 0x5d, + 0xa2, 0xe5, 0x9e, 0xd6, 0xc0, 0xad, 0x67, 0x59, 0xc3, 0x40, 0xbd, 0x61, 0x1f, 0x41, 0x25, 0x5f, + 0xfc, 0xc5, 0x8e, 0xf5, 0xdd, 0x5b, 0x96, 0x39, 0x08, 0x29, 0x85, 0xfa, 0x36, 0xcc, 0xea, 0x0d, + 0x9b, 0x6d, 0xa5, 0x8f, 0xe5, 0xb9, 0xd6, 0x9f, 0x87, 0x4b, 0xf9, 0x83, 0xe7, 0xbe, 0x20, 0x0b, + 0x87, 0x5a, 0xf9, 0x9f, 0x7c, 0xf6, 0xeb, 0x59, 0xcd, 0xf8, 0xed, 0xac, 0x66, 0xfc, 0x79, 0x56, + 0x33, 0xde, 0xfe, 0x55, 0xbb, 0x71, 0xb0, 0x79, 0x8d, 0x7f, 0x30, 0x8e, 0x4a, 0x72, 0x94, 0x1f, + 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x10, 0x8d, 0xba, 0x96, 0x0c, 0x00, 0x00, +} diff --git a/api/handler/handler.pb.gw.go b/api/handler/handler.pb.gw.go new file mode 100644 index 000000000..369a9818d --- /dev/null +++ b/api/handler/handler.pb.gw.go @@ -0,0 +1,827 @@ +// Code generated by protoc-gen-grpc-gateway +// source: github.com/TheThingsNetwork/ttn/api/handler/handler.proto +// DO NOT EDIT! + +/* +Package handler is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package handler + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" +) + +var _ codes.Code +var _ io.Reader +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_ApplicationManager_RegisterApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.RegisterApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_GetApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Application + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetApplication_1(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Application + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_DeleteApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.DeleteApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_GetDevice_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeviceIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_1(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_2(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_3(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_DeleteDevice_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeviceIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.DeleteDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_GetDevicesForApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetDevicesForApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterApplicationManagerHandlerFromEndpoint is same as RegisterApplicationManagerHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterApplicationManagerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterApplicationManagerHandler(ctx, mux, conn) +} + +// RegisterApplicationManagerHandler registers the http handlers for service ApplicationManager to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterApplicationManagerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewApplicationManagerClient(conn) + + mux.Handle("POST", pattern_ApplicationManager_RegisterApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_RegisterApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_RegisterApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_ApplicationManager_GetApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_GetApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_GetApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_ApplicationManager_SetApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_ApplicationManager_SetApplication_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetApplication_1(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetApplication_1(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_ApplicationManager_DeleteApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_DeleteApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_DeleteApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_ApplicationManager_GetDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_GetDevice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_GetDevice_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_ApplicationManager_SetDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_ApplicationManager_SetDevice_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_1(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_1(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_ApplicationManager_SetDevice_2, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_2(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_2(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_ApplicationManager_SetDevice_3, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_3(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_3(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_ApplicationManager_DeleteDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_DeleteDevice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_DeleteDevice_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_ApplicationManager_GetDevicesForApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_GetDevicesForApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_GetDevicesForApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_ApplicationManager_RegisterApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"applications"}, "")) + + pattern_ApplicationManager_GetApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_SetApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_SetApplication_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_DeleteApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_GetDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_SetDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_SetDevice_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_SetDevice_2 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) + + pattern_ApplicationManager_SetDevice_3 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) + + pattern_ApplicationManager_DeleteDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_GetDevicesForApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) +) + +var ( + forward_ApplicationManager_RegisterApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_GetApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetApplication_1 = runtime.ForwardResponseMessage + + forward_ApplicationManager_DeleteApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_GetDevice_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_1 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_2 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_3 = runtime.ForwardResponseMessage + + forward_ApplicationManager_DeleteDevice_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_GetDevicesForApplication_0 = runtime.ForwardResponseMessage +) diff --git a/api/handler/handler.proto b/api/handler/handler.proto new file mode 100644 index 000000000..563608646 --- /dev/null +++ b/api/handler/handler.proto @@ -0,0 +1,231 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; +import "ttn/api/api.proto"; +import "ttn/api/broker/broker.proto"; +import "ttn/api/protocol/protocol.proto"; +import "ttn/api/protocol/lorawan/device.proto"; + +package handler; + +option go_package = "github.com/TheThingsNetwork/ttn/api/handler"; + +message DeviceActivationResponse { + bytes payload = 1; + protocol.Message message = 2; + broker.DownlinkOption downlink_option = 11; + protocol.ActivationMetadata activation_metadata = 23; +} + +// The Handler service provides pure network functionality +service Handler { + rpc ActivationChallenge(broker.ActivationChallengeRequest) returns (broker.ActivationChallengeResponse); + rpc Activate(broker.DeduplicatedDeviceActivationRequest) returns (DeviceActivationResponse); +} + +// message StatusRequest is used to request the status of this Handler +message StatusRequest {} + +// message Status is the response to the StatusRequest +message Status { + api.SystemStats system = 1; + api.ComponentStats component = 2; + + api.Rates uplink = 11; + api.Rates downlink = 12; + api.Rates activations = 13; +} + +message ApplicationIdentifier { + string app_id = 1; +} + +// The Application settings +message Application { + string app_id = 1; + + // The decoder is a JavaScript function that decodes a byte array to an object. + string decoder = 2; + + // The converter is a JavaScript function that can be used to convert values + // in the object returned from the decoder. This can for example be useful to + // convert a voltage to a temperature. + string converter = 3; + + // The validator is a JavaScript function that checks the validity of the + // object returned by the decoder or converter. If validation fails, the + // message is dropped. + string validator = 4; + + // The encoder is a JavaScript function that encodes an object to a byte array. + string encoder = 5; +} + +message DeviceIdentifier { + string app_id = 1; + string dev_id = 2; +} + +// The Device settings +message Device { + string app_id = 1; + string dev_id = 2; + + // The device can be of different kinds + oneof device { + lorawan.Device lorawan_device = 3; + } +} + +message DeviceList { + repeated Device devices = 1; +} + +// DryDownlinkMessage is a simulated message to test downlink processing +message DryDownlinkMessage { + // The binary payload to use + bytes payload = 1; + // JSON-encoded object with fields to encode + string fields = 2; + // The Application containing the payload functions that should be executed + Application app = 3; + // The port number that should be passed to the payload function + uint32 port = 4; +} + +// DryUplinkMessage is a simulated message to test uplink processing +message DryUplinkMessage { + // The binary payload to use + bytes payload = 1; + // The Application containing the payload functions that should be executed + Application app = 2; + // The port number that should be passed to the payload function + uint32 port = 3; +} + +message LogEntry { + // The location where the log was created (what payload function) + string function = 1; + + // A list of JSON-encoded fields that were logged + repeated string fields = 2; +} + +// DryUplinkResult is the result from an uplink simulation +message DryUplinkResult { + // The binary payload + bytes payload = 1; + // The decoded fields + string fields = 2; + // Was validation of the message successful + bool valid = 3; + // Logs that have been generated while processing + repeated LogEntry logs = 4; +} + +// DryDownlinkResult is the result from a downlink simulation +message DryDownlinkResult { + // The payload that was encoded + bytes payload = 1; + // Logs that have been generated while processing + repeated LogEntry logs = 2; +} + +// ApplicationManager manages application and device registrations on the Handler +// +// To protect our quality of service, you can make up to 5000 calls to the +// ApplicationManager API per hour. Once you go over the rate limit, you will +// receive an error response. +service ApplicationManager { + + // Applications should first be registered to the Handler with the `RegisterApplication` method + rpc RegisterApplication(ApplicationIdentifier) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/applications" + body: "*" + }; + } + + // GetApplication returns the application with the given identifier (app_id) + rpc GetApplication(ApplicationIdentifier) returns (Application) { + option (google.api.http) = { + get: "/applications/{app_id}" + }; + } + + // SetApplication updates the settings for the application. All fields must be supplied. + rpc SetApplication(Application) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/applications/{app_id}" + body: "*" + additional_bindings { + put: "/applications/{app_id}" + body: "*" + } + }; + } + + // DeleteApplication deletes the application with the given identifier (app_id) + rpc DeleteApplication(ApplicationIdentifier) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/applications/{app_id}" + }; + } + + // GetDevice returns the device with the given identifier (app_id and dev_id) + rpc GetDevice(DeviceIdentifier) returns (Device) { + option (google.api.http) = { + get: "/applications/{app_id}/devices/{dev_id}" + }; + } + + // SetDevice creates or updates a device. All fields must be supplied. + rpc SetDevice(Device) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/applications/{app_id}/devices/{dev_id}" + body: "*" + additional_bindings { + put: "/applications/{app_id}/devices/{dev_id}" + body: "*" + } + additional_bindings { + post: "/applications/{app_id}/devices" + body: "*" + } + additional_bindings { + put: "/applications/{app_id}/devices" + body: "*" + } + }; + } + + // DeleteDevice deletes the device with the given identifier (app_id and dev_id) + rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/applications/{app_id}/devices/{dev_id}" + }; + } + + // GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) + rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList) { + option (google.api.http) = { + get: "/applications/{app_id}/devices" + }; + } + + // DryUplink simulates processing an uplink message and returns the result + rpc DryDownlink(DryDownlinkMessage) returns (DryDownlinkResult); + + // DryUplink simulates processing a downlink message and returns the result + rpc DryUplink(DryUplinkMessage) returns (DryUplinkResult); +} + +// The HandlerManager service provides configuration and monitoring +// functionality +service HandlerManager { + rpc GetStatus(StatusRequest) returns (Status); +} diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go new file mode 100644 index 000000000..8ee483db7 --- /dev/null +++ b/api/handler/manager_client.go @@ -0,0 +1,192 @@ +package handler + +import ( + "encoding/json" + "os" + "os/user" + "sync" + + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// ManagerClient is used to manage applications and devices on a handler +type ManagerClient struct { + sync.RWMutex + id string + accessToken string + conn *grpc.ClientConn + applicationManagerClient ApplicationManagerClient +} + +// NewManagerClient returns a new ManagerClient for a handler on the given conn that accepts the given access token +func NewManagerClient(conn *grpc.ClientConn, accessToken string) (*ManagerClient, error) { + applicationManagerClient := NewApplicationManagerClient(conn) + + id := "client" + if user, err := user.Current(); err == nil { + id += "-" + user.Username + } + if hostname, err := os.Hostname(); err == nil { + id += "@" + hostname + } + + return &ManagerClient{ + id: id, + accessToken: accessToken, + conn: conn, + applicationManagerClient: applicationManagerClient, + }, nil +} + +// SetID sets the ID of this client +func (h *ManagerClient) SetID(id string) { + h.Lock() + defer h.Unlock() + h.id = id +} + +// UpdateAccessToken updates the access token that is used for running commands +func (h *ManagerClient) UpdateAccessToken(accessToken string) { + h.Lock() + defer h.Unlock() + h.accessToken = accessToken +} + +func (h *ManagerClient) getContext() context.Context { + h.RLock() + defer h.RUnlock() + md := metadata.Pairs( + "id", h.id, + "token", h.accessToken, + ) + return metadata.NewContext(context.Background(), md) +} + +// GetApplication retrieves an application from the Handler +func (h *ManagerClient) GetApplication(appID string) (*Application, error) { + res, err := h.applicationManagerClient.GetApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not get application from Handler") + } + return res, nil +} + +// SetApplication sets an application on the Handler +func (h *ManagerClient) SetApplication(in *Application) error { + _, err := h.applicationManagerClient.SetApplication(h.getContext(), in) + return errors.Wrap(errors.FromGRPCError(err), "Could not set application on Handler") +} + +// RegisterApplication registers an application on the Handler +func (h *ManagerClient) RegisterApplication(appID string) error { + _, err := h.applicationManagerClient.RegisterApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) + return errors.Wrap(errors.FromGRPCError(err), "Could not register application on Handler") +} + +// DeleteApplication deletes an application and all its devices from the Handler +func (h *ManagerClient) DeleteApplication(appID string) error { + _, err := h.applicationManagerClient.DeleteApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) + return errors.Wrap(errors.FromGRPCError(err), "Could not delete application from Handler") +} + +// GetDevice retrieves a device from the Handler +func (h *ManagerClient) GetDevice(appID string, devID string) (*Device, error) { + res, err := h.applicationManagerClient.GetDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not get device from Handler") + } + return res, nil +} + +// SetDevice sets a device on the Handler +func (h *ManagerClient) SetDevice(in *Device) error { + _, err := h.applicationManagerClient.SetDevice(h.getContext(), in) + return errors.Wrap(errors.FromGRPCError(err), "Could not set device on Handler") +} + +// DeleteDevice deletes a device from the Handler +func (h *ManagerClient) DeleteDevice(appID string, devID string) error { + _, err := h.applicationManagerClient.DeleteDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) + return errors.Wrap(errors.FromGRPCError(err), "Could not delete device from Handler") +} + +// GetDevicesForApplication retrieves all devices for an application from the Handler +func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Device, err error) { + res, err := h.applicationManagerClient.GetDevicesForApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not get devices for application from Handler") + } + for _, dev := range res.Devices { + devices = append(devices, dev) + } + return +} + +// GetDevAddr requests a random device address with the given constraints +func (h *ManagerClient) GetDevAddr(constraints ...string) (types.DevAddr, error) { + devAddrManager := lorawan.NewDevAddrManagerClient(h.conn) + resp, err := devAddrManager.GetDevAddr(h.getContext(), &lorawan.DevAddrRequest{ + Usage: constraints, + }) + if err != nil { + return types.DevAddr{}, errors.Wrap(errors.FromGRPCError(err), "Could not get DevAddr from Handler") + } + return *resp.DevAddr, nil +} + +// DryUplink transforms the uplink payload with the payload functions provided +// in the app.. +func (h *ManagerClient) DryUplink(payload []byte, app *Application, port uint32) (*DryUplinkResult, error) { + res, err := h.applicationManagerClient.DryUplink(h.getContext(), &DryUplinkMessage{ + App: app, + Payload: payload, + Port: port, + }) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run uplink on Handler") + } + return res, nil +} + +// DryDownlinkWithPayload transforms the downlink payload with the payload functions +// provided in app. +func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application, port uint32) (*DryDownlinkResult, error) { + res, err := h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ + App: app, + Payload: payload, + Port: port, + }) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run downlink with payload on Handler") + } + return res, nil +} + +// DryDownlinkWithFields transforms the downlink fields with the payload functions +// provided in app. +func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app *Application, port uint32) (*DryDownlinkResult, error) { + marshalled, err := json.Marshal(fields) + if err != nil { + return nil, err + } + + res, err := h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ + App: app, + Fields: string(marshalled), + Port: port, + }) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run downlink with fields on Handler") + } + return res, nil +} + +// Close closes the client +func (h *ManagerClient) Close() error { + return h.conn.Close() +} diff --git a/api/handler/message_marshaling.go b/api/handler/message_marshaling.go new file mode 100644 index 000000000..98280ad8e --- /dev/null +++ b/api/handler/message_marshaling.go @@ -0,0 +1,41 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "errors" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/brocaar/lorawan" +) + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationResponse) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + var phy lorawan.PHYPayload + if err := phy.UnmarshalBinary(m.Payload); err != nil { + return err + } + msg := pb_lorawan.MessageFromPHYPayload(phy) + m.Message = &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}} + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationResponse) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + if m.Message.GetLorawan() == nil { + return errors.New("No LoRaWAN message to marshal") + } + phy := m.Message.GetLorawan().PHYPayload() + bin, err := phy.MarshalBinary() + if err != nil { + return err + } + m.Payload = bin + } + return nil +} diff --git a/api/handler/message_marshaling_test.go b/api/handler/message_marshaling_test.go new file mode 100644 index 000000000..624257215 --- /dev/null +++ b/api/handler/message_marshaling_test.go @@ -0,0 +1,81 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type payloadMarshalerUnmarshaler interface { + UnmarshalPayload() error + MarshalPayload() error +} + +func TestMarshalUnmarshalPayload(t *testing.T) { + a := New(t) + + var subjects []payloadMarshalerUnmarshaler + + // Do nothing when message and payload are nil + subjects = []payloadMarshalerUnmarshaler{ + &DeviceActivationResponse{}, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + + txConf := &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}} + + joinAccMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_ACCEPT, + }, + Payload: &pb_lorawan.Message_JoinAcceptPayload{JoinAcceptPayload: &pb_lorawan.JoinAcceptPayload{ + AppNonce: types.AppNonce([3]byte{1, 2, 3}), + NetId: types.NetID([3]byte{1, 2, 3}), + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + DLSettings: pb_lorawan.DLSettings{ + Rx2Dr: 3, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinAccBin := []byte{33, 3, 2, 1, 3, 2, 1, 4, 3, 2, 1, 3, 0, 1, 2, 3, 4} + + // Only Marshal + subjects = []payloadMarshalerUnmarshaler{ + &DeviceActivationResponse{ + DownlinkOption: &pb_broker.DownlinkOption{ProtocolConfig: txConf}, + Message: joinAccMsg, + }, + } + + for _, sub := range subjects { + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + a.So(sub.MarshalPayload(), ShouldEqual, nil) + } + + // Only Unmarshal + subjects = []payloadMarshalerUnmarshaler{ + &DeviceActivationResponse{ + DownlinkOption: &pb_broker.DownlinkOption{ProtocolConfig: txConf}, + Payload: joinAccBin, + }, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + +} diff --git a/api/handler/validation.go b/api/handler/validation.go new file mode 100644 index 000000000..19fe82b33 --- /dev/null +++ b/api/handler/validation.go @@ -0,0 +1,71 @@ +package handler + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validate implements the api.Validator interface +func (m *DeviceActivationResponse) Validate() error { + if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { + return err + } + if err := api.NotNilAndValid(m.ActivationMetadata, "ActivationMetadata"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *ApplicationIdentifier) Validate() error { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + return nil +} + +// Validate implements the api.Validator interface +func (m *Application) Validate() error { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DeviceIdentifier) Validate() error { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { + return err + } + return nil +} + +// Validate implements the api.Validator interface +func (m *Device) Validate() error { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { + return err + } + if err := api.NotNilAndValid(m.Device, "Device"); err != nil { + return err + } + return nil +} + +// Validate implements the api.Validator interface +func (m *Device_LorawanDevice) Validate() error { + if err := api.NotNilAndValid(m.LorawanDevice, "LorawanDevice"); err != nil { + return err + } + return nil +} diff --git a/api/health/client.go b/api/health/client.go new file mode 100644 index 000000000..b8d2e043b --- /dev/null +++ b/api/health/client.go @@ -0,0 +1,19 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package health + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +// Check the health of a connection +func Check(conn *grpc.ClientConn) (bool, error) { + res, err := healthpb.NewHealthClient(conn).Check(context.Background(), &healthpb.HealthCheckRequest{}) + if err != nil { + return false, err + } + return res.Status == healthpb.HealthCheckResponse_SERVING, nil +} diff --git a/api/metadata.go b/api/metadata.go new file mode 100644 index 000000000..c674729c7 --- /dev/null +++ b/api/metadata.go @@ -0,0 +1,47 @@ +package api + +import ( + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc/metadata" +) + +// Errors that are returned when an item could not be retrieved +var ( + ErrContext = errors.NewErrInternal("Could not get metadata from context") + ErrNoToken = errors.NewErrInvalidArgument("Metadata", "token missing") + ErrNoKey = errors.NewErrInvalidArgument("Metadata", "key missing") + ErrNoID = errors.NewErrInvalidArgument("Metadata", "id missing") +) + +func MetadataFromContext(ctx context.Context) (metadata.MD, error) { + md, ok := metadata.FromContext(ctx) + if !ok { + return md, ErrContext + } + return md, nil +} + +func IDFromMetadata(md metadata.MD) (string, error) { + id, ok := md["id"] + if !ok || len(id) == 0 { + return "", ErrNoID + } + return id[0], nil +} + +func TokenFromMetadata(md metadata.MD) (string, error) { + token, ok := md["token"] + if !ok || len(token) == 0 { + return "", ErrNoToken + } + return token[0], nil +} + +func KeyFromMetadata(md metadata.MD) (string, error) { + key, ok := md["key"] + if !ok || len(key) == 0 { + return "", ErrNoKey + } + return key[0], nil +} diff --git a/api/monitor/client.go b/api/monitor/client.go new file mode 100644 index 000000000..93c7b2259 --- /dev/null +++ b/api/monitor/client.go @@ -0,0 +1,230 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "sync" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// BufferSize gives the size for the monitor buffers +const BufferSize = 10 + +// Client is a wrapper around MonitorClient +type Client struct { + Ctx log.Interface + + client MonitorClient + conn *grpc.ClientConn + addr string + + gateways map[string]GatewayClient + mutex sync.RWMutex +} + +// NewClient is a wrapper for NewMonitorClient, initializes +// connection to MonitorServer on monitorAddr with default gRPC options +func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { + cl = &Client{ + Ctx: ctx, + addr: monitorAddr, + gateways: make(map[string]GatewayClient), + } + return cl, cl.Open() +} + +// Open opens connection to the monitor +func (cl *Client) Open() (err error) { + cl.mutex.Lock() + defer cl.mutex.Unlock() + + return cl.open() +} +func (cl *Client) open() (err error) { + addr := cl.addr + ctx := cl.Ctx.WithField("addr", addr) + + defer func() { + if err != nil { + ctx.Warn("Failed to open monitor connection") + } else { + ctx.Info("Monitor connection opened") + } + }() + + ctx.Debug("Opening monitor connection...") + + cl.conn, err = api.Dial(addr) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection to gRPC service") + return err + } + + cl.client = NewMonitorClient(cl.conn) + return nil +} + +// Close closes connection to the monitor +func (cl *Client) Close() (err error) { + cl.mutex.Lock() + defer cl.mutex.Unlock() + + return cl.close() +} +func (cl *Client) close() (err error) { + defer func() { + if err != nil { + cl.Ctx.Warn("Failed to close monitor connection") + } else { + cl.Ctx.Info("Monitor connection closed") + } + }() + + for _, gtw := range cl.gateways { + ctx := cl.Ctx.WithField("GatewayID", gtw.(*gatewayClient).id) + + ctx.Debug("Closing gateway streams...") + err = gtw.Close() + if err != nil { + ctx.Warn("Failed to close gateway streams") + } + } + + cl.Ctx.Debug("Closing monitor connection...") + err = cl.conn.Close() + if err != nil { + return err + } + + cl.conn = nil + return nil +} + +// Reopen reopens connection to the monitor. It first attempts to close already opened connection +// and then opens a new one. If closing already opened connection fails, Reopen fails too. +func (cl *Client) Reopen() (err error) { + cl.mutex.Lock() + defer cl.mutex.Unlock() + + return cl.reopen() +} +func (cl *Client) reopen() (err error) { + defer func() { + if err != nil { + cl.Ctx.Warn("Failed to reopen monitor connection") + } else { + cl.Ctx.Info("Monitor connection reopened") + } + }() + + cl.Ctx.Debug("Reopening monitor connection...") + + err = cl.close() + if err != nil { + return err + } + return cl.open() +} + +// IsConnected returns whether connection to the monitor had been established or not +func (cl *Client) IsConnected() bool { + return cl.client != nil && cl.conn != nil +} + +// GatewayClient returns monitor GatewayClient for id and token specified +func (cl *Client) GatewayClient(id string) (gtwCl GatewayClient) { + cl.mutex.RLock() + gtwCl, ok := cl.gateways[id] + cl.mutex.RUnlock() + if !ok { + cl.mutex.Lock() + gtwCl = &gatewayClient{ + Ctx: cl.Ctx.WithField("GatewayID", id), + client: cl, + id: id, + } + cl.gateways[id] = gtwCl + cl.mutex.Unlock() + } + return gtwCl +} + +type gatewayClient struct { + sync.RWMutex + + client *Client + + Ctx log.Interface + + id, token string + + status struct { + init sync.Once + ch chan *gateway.Status + cancel func() + sync.RWMutex + } + + uplink struct { + init sync.Once + ch chan *router.UplinkMessage + cancel func() + sync.Mutex + } + + downlink struct { + init sync.Once + ch chan *router.DownlinkMessage + cancel func() + sync.RWMutex + } +} + +// GatewayClient is used as the main client for Gateways to communicate with the monitor +type GatewayClient interface { + SetToken(token string) + IsConfigured() bool + SendStatus(status *gateway.Status) (err error) + SendUplink(msg *router.UplinkMessage) (err error) + SendDownlink(msg *router.DownlinkMessage) (err error) + Close() (err error) +} + +func (cl *gatewayClient) SetToken(token string) { + cl.Lock() + defer cl.Unlock() + cl.token = token +} + +func (cl *gatewayClient) IsConfigured() bool { + cl.RLock() + defer cl.RUnlock() + return cl.token != "" && cl.token != "token" +} + +// Close closes all opened monitor streams for the gateway +func (cl *gatewayClient) Close() (err error) { + cl.closeStatus() + cl.closeUplink() + cl.closeDownlink() + return err +} + +// Context returns monitor connection context for gateway +func (cl *gatewayClient) Context() (monitorContext context.Context) { + cl.RLock() + defer cl.RUnlock() + return metadata.NewContext(context.Background(), metadata.Pairs( + "id", cl.id, + "token", cl.token, + )) +} diff --git a/api/monitor/client_test.go b/api/monitor/client_test.go new file mode 100644 index 000000000..f3c37de2f --- /dev/null +++ b/api/monitor/client_test.go @@ -0,0 +1,151 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestClient(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "Monitor Client") + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go startExampleServer(2, port) + + { + client, err := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) + a.So(err, ShouldBeNil) + a.So(client.IsConnected(), ShouldBeTrue) + a.So(client.Reopen(), ShouldBeNil) + a.So(client.IsConnected(), ShouldBeTrue) + a.So(client.Close(), ShouldBeNil) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) + gtw := client.GatewayClient("dev") + a.So(gtw.IsConfigured(), ShouldBeFalse) + gtw.SetToken("SOME.AWESOME.JWT") + a.So(gtw.IsConfigured(), ShouldBeTrue) + + ctx := gtw.(*gatewayClient).Context() + id, _ := api.IDFromContext(ctx) + a.So(id, ShouldEqual, "dev") + token, _ := api.TokenFromContext(ctx) + a.So(token, ShouldEqual, "SOME.AWESOME.JWT") + + a.So(client.Close(), ShouldBeNil) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) + defer client.Close() + gtw := client.GatewayClient("dev") + + err := gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldBeNil) + + gtw.SetToken("SOME.AWESOME.JWT") + + // The first two statuses are OK + for i := 0; i < 2; i++ { + err = gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldBeNil) + } + + // The next one will cause an error on the test server + err = gtw.SendStatus(&gateway.Status{}) + time.Sleep(10 * time.Millisecond) + + // Then, we are going to buffer 10 statuses locally + for i := 0; i < 10; i++ { + err = gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldBeNil) + } + + // After which statuses will get dropped + err = gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldNotBeNil) + + time.Sleep(100 * time.Millisecond) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) + defer client.Close() + gtw := client.GatewayClient("dev") + + err := gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldBeNil) + + gtw.SetToken("SOME.AWESOME.JWT") + + // The first two messages are OK + for i := 0; i < 2; i++ { + err = gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldBeNil) + } + + // The next one will cause an error on the test server + err = gtw.SendUplink(&router.UplinkMessage{}) + time.Sleep(10 * time.Millisecond) + + // Then, we are going to buffer 10 messages locally + for i := 0; i < 10; i++ { + err = gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldBeNil) + } + + // After which messages will get dropped + err = gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldNotBeNil) + + time.Sleep(100 * time.Millisecond) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) + defer client.Close() + gtw := client.GatewayClient("dev") + + err := gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldBeNil) + + gtw.SetToken("SOME.AWESOME.JWT") + + // The first two messages are OK + for i := 0; i < 2; i++ { + err = gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldBeNil) + } + + // The next one will cause an error on the test server + err = gtw.SendDownlink(&router.DownlinkMessage{}) + time.Sleep(10 * time.Millisecond) + + // Then, we are going to buffer 10 messages locally + for i := 0; i < 10; i++ { + err = gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldBeNil) + } + + // After which messages will get dropped + err = gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldNotBeNil) + + time.Sleep(100 * time.Millisecond) + } + +} diff --git a/api/monitor/downlink.go b/api/monitor/downlink.go new file mode 100644 index 000000000..fddfeff0d --- /dev/null +++ b/api/monitor/downlink.go @@ -0,0 +1,101 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" +) + +func (cl *gatewayClient) initDownlink() { + cl.downlink.ch = make(chan *router.DownlinkMessage, BufferSize) + go cl.monitorDownlink() +} + +func (cl *gatewayClient) monitorDownlink() { + var retries int +newStream: + for { + ctx, cancel := context.WithCancel(cl.Context()) + cl.downlink.Lock() + cl.downlink.cancel = cancel + cl.downlink.Unlock() + + stream, err := cl.client.client.GatewayDownlink(ctx) + if err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue + } + retries = 0 + cl.Ctx.Debug("Opened new monitor downlink stream") + + // The actual stream + go func() { + for { + select { + case <-ctx.Done(): + return + case downlink, ok := <-cl.downlink.ch: + if ok { + stream.Send(downlink) + cl.Ctx.Debug("Sent downlink to monitor") + } + } + } + }() + + msg := new(empty.Empty) + for { + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor downlink stream, closing...") + stream.CloseSend() + cl.Ctx.Debug("Closed monitor downlink stream") + + cl.downlink.Lock() + cl.downlink.cancel() + cl.downlink.cancel = nil + cl.downlink.Unlock() + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue newStream + } + } + } +} + +func (cl *gatewayClient) closeDownlink() { + cl.downlink.Lock() + defer cl.downlink.Unlock() + if cl.downlink.cancel != nil { + cl.downlink.cancel() + } +} + +// SendDownlink sends downlink to the monitor +func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { + if !cl.IsConfigured() { + return nil + } + + cl.downlink.init.Do(cl.initDownlink) + + select { + case cl.downlink.ch <- downlink: + default: + cl.Ctx.Warn("Not sending downlink to monitor, buffer full") + return errors.New("Not sending downlink to monitor, buffer full") + } + return +} diff --git a/api/monitor/example_server.go b/api/monitor/example_server.go new file mode 100644 index 000000000..353ce0540 --- /dev/null +++ b/api/monitor/example_server.go @@ -0,0 +1,117 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "fmt" + "io" + "net" + + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +var errNotImplemented = grpc.Errorf(codes.Unimplemented, "That's not implemented yet") +var errBufferFull = grpc.Errorf(codes.ResourceExhausted, "Take it easy, dude! My buffers are full") + +func newExampleServer(channelSize int) *exampleServer { + return &exampleServer{ + gatewayStatuses: make(chan *gateway.Status, channelSize), + uplinkMessages: make(chan *router.UplinkMessage, channelSize), + downlinkMessages: make(chan *router.DownlinkMessage, channelSize), + } +} + +type exampleServer struct { + gatewayStatuses chan *gateway.Status + uplinkMessages chan *router.UplinkMessage + downlinkMessages chan *router.DownlinkMessage +} + +func (s *exampleServer) GatewayStatus(stream Monitor_GatewayStatusServer) error { + for { + status, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + select { + case s.gatewayStatuses <- status: + fmt.Println("Saving gateway status to database and doing something cool") + default: + fmt.Println("Warning: Dropping gateway status [full buffer]") + return errBufferFull + } + } +} + +func (s *exampleServer) GatewayUplink(stream Monitor_GatewayUplinkServer) error { + for { + uplink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + select { + case s.uplinkMessages <- uplink: + fmt.Println("Saving uplink to database and doing something cool") + default: + fmt.Println("Warning: Dropping uplink [full buffer]") + return errBufferFull + } + } +} + +func (s *exampleServer) GatewayDownlink(stream Monitor_GatewayDownlinkServer) error { + for { + downlink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + select { + case s.downlinkMessages <- downlink: + fmt.Println("Saving downlink to database and doing something cool") + default: + fmt.Println("Warning: Dropping downlink [full buffer]") + return errBufferFull + } + } +} + +func (s *exampleServer) RouterStatus(stream Monitor_RouterStatusServer) error { + return errNotImplemented +} + +func (s *exampleServer) BrokerStatus(stream Monitor_BrokerStatusServer) error { + return errNotImplemented +} + +func (s *exampleServer) HandlerStatus(stream Monitor_HandlerStatusServer) error { + return errNotImplemented +} + +func (s *exampleServer) Serve(port int) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + srv := grpc.NewServer() + RegisterMonitorServer(srv, s) + srv.Serve(lis) +} + +func startExampleServer(channelSize, port int) { + s := newExampleServer(channelSize) + s.Serve(port) +} diff --git a/api/monitor/gateway_status.go b/api/monitor/gateway_status.go new file mode 100644 index 000000000..41cdf0c96 --- /dev/null +++ b/api/monitor/gateway_status.go @@ -0,0 +1,101 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" +) + +func (cl *gatewayClient) initStatus() { + cl.status.ch = make(chan *gateway.Status, BufferSize) + go cl.monitorStatus() +} + +func (cl *gatewayClient) monitorStatus() { + var retries int +newStream: + for { + ctx, cancel := context.WithCancel(cl.Context()) + cl.status.Lock() + cl.status.cancel = cancel + cl.status.Unlock() + + stream, err := cl.client.client.GatewayStatus(ctx) + if err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue + } + retries = 0 + cl.Ctx.Debug("Opened new monitor status stream") + + // The actual stream + go func() { + for { + select { + case <-ctx.Done(): + return + case status, ok := <-cl.status.ch: + if ok { + stream.Send(status) + cl.Ctx.Debug("Sent status to monitor") + } + } + } + }() + + msg := new(empty.Empty) + for { + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor status stream, closing...") + stream.CloseSend() + cl.Ctx.Debug("Closed monitor status stream") + + cl.status.Lock() + cl.status.cancel() + cl.status.cancel = nil + cl.status.Unlock() + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue newStream + } + } + } +} + +func (cl *gatewayClient) closeStatus() { + cl.status.Lock() + defer cl.status.Unlock() + if cl.status.cancel != nil { + cl.status.cancel() + } +} + +// SendStatus sends status to the monitor +func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { + if !cl.IsConfigured() { + return nil + } + + cl.status.init.Do(cl.initStatus) + + select { + case cl.status.ch <- status: + default: + cl.Ctx.Warn("Not sending status to monitor, buffer full") + return errors.New("Not sending status to monitor, buffer full") + } + return +} diff --git a/api/monitor/monitor.pb.go b/api/monitor/monitor.pb.go new file mode 100644 index 000000000..d3380d644 --- /dev/null +++ b/api/monitor/monitor.pb.go @@ -0,0 +1,507 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto +// DO NOT EDIT! + +/* + Package monitor is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto + + It has these top-level messages: +*/ +package monitor + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import gateway "github.com/TheThingsNetwork/ttn/api/gateway" +import router "github.com/TheThingsNetwork/ttn/api/router" +import broker "github.com/TheThingsNetwork/ttn/api/broker" +import handler "github.com/TheThingsNetwork/ttn/api/handler" +import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Monitor service + +type MonitorClient interface { + GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) + GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) + GatewayDownlink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayDownlinkClient, error) + RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) + BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) + HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) +} + +type monitorClient struct { + cc *grpc.ClientConn +} + +func NewMonitorClient(cc *grpc.ClientConn) MonitorClient { + return &monitorClient{cc} +} + +func (c *monitorClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/monitor.Monitor/GatewayStatus", opts...) + if err != nil { + return nil, err + } + x := &monitorGatewayStatusClient{stream} + return x, nil +} + +type Monitor_GatewayStatusClient interface { + Send(*gateway.Status) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorGatewayStatusClient struct { + grpc.ClientStream +} + +func (x *monitorGatewayStatusClient) Send(m *gateway.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorGatewayStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitorClient) GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[1], c.cc, "/monitor.Monitor/GatewayUplink", opts...) + if err != nil { + return nil, err + } + x := &monitorGatewayUplinkClient{stream} + return x, nil +} + +type Monitor_GatewayUplinkClient interface { + Send(*router.UplinkMessage) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorGatewayUplinkClient struct { + grpc.ClientStream +} + +func (x *monitorGatewayUplinkClient) Send(m *router.UplinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorGatewayUplinkClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitorClient) GatewayDownlink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayDownlinkClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[2], c.cc, "/monitor.Monitor/GatewayDownlink", opts...) + if err != nil { + return nil, err + } + x := &monitorGatewayDownlinkClient{stream} + return x, nil +} + +type Monitor_GatewayDownlinkClient interface { + Send(*router.DownlinkMessage) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorGatewayDownlinkClient struct { + grpc.ClientStream +} + +func (x *monitorGatewayDownlinkClient) Send(m *router.DownlinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorGatewayDownlinkClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitorClient) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[3], c.cc, "/monitor.Monitor/RouterStatus", opts...) + if err != nil { + return nil, err + } + x := &monitorRouterStatusClient{stream} + return x, nil +} + +type Monitor_RouterStatusClient interface { + Send(*router.Status) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorRouterStatusClient struct { + grpc.ClientStream +} + +func (x *monitorRouterStatusClient) Send(m *router.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorRouterStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitorClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[4], c.cc, "/monitor.Monitor/BrokerStatus", opts...) + if err != nil { + return nil, err + } + x := &monitorBrokerStatusClient{stream} + return x, nil +} + +type Monitor_BrokerStatusClient interface { + Send(*broker.Status) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorBrokerStatusClient struct { + grpc.ClientStream +} + +func (x *monitorBrokerStatusClient) Send(m *broker.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorBrokerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitorClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[5], c.cc, "/monitor.Monitor/HandlerStatus", opts...) + if err != nil { + return nil, err + } + x := &monitorHandlerStatusClient{stream} + return x, nil +} + +type Monitor_HandlerStatusClient interface { + Send(*handler.Status) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorHandlerStatusClient struct { + grpc.ClientStream +} + +func (x *monitorHandlerStatusClient) Send(m *handler.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorHandlerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for Monitor service + +type MonitorServer interface { + GatewayStatus(Monitor_GatewayStatusServer) error + GatewayUplink(Monitor_GatewayUplinkServer) error + GatewayDownlink(Monitor_GatewayDownlinkServer) error + RouterStatus(Monitor_RouterStatusServer) error + BrokerStatus(Monitor_BrokerStatusServer) error + HandlerStatus(Monitor_HandlerStatusServer) error +} + +func RegisterMonitorServer(s *grpc.Server, srv MonitorServer) { + s.RegisterService(&_Monitor_serviceDesc, srv) +} + +func _Monitor_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayStatus(&monitorGatewayStatusServer{stream}) +} + +type Monitor_GatewayStatusServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*gateway.Status, error) + grpc.ServerStream +} + +type monitorGatewayStatusServer struct { + grpc.ServerStream +} + +func (x *monitorGatewayStatusServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorGatewayStatusServer) Recv() (*gateway.Status, error) { + m := new(gateway.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitor_GatewayUplink_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayUplink(&monitorGatewayUplinkServer{stream}) +} + +type Monitor_GatewayUplinkServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*router.UplinkMessage, error) + grpc.ServerStream +} + +type monitorGatewayUplinkServer struct { + grpc.ServerStream +} + +func (x *monitorGatewayUplinkServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorGatewayUplinkServer) Recv() (*router.UplinkMessage, error) { + m := new(router.UplinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitor_GatewayDownlink_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayDownlink(&monitorGatewayDownlinkServer{stream}) +} + +type Monitor_GatewayDownlinkServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*router.DownlinkMessage, error) + grpc.ServerStream +} + +type monitorGatewayDownlinkServer struct { + grpc.ServerStream +} + +func (x *monitorGatewayDownlinkServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorGatewayDownlinkServer) Recv() (*router.DownlinkMessage, error) { + m := new(router.DownlinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitor_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).RouterStatus(&monitorRouterStatusServer{stream}) +} + +type Monitor_RouterStatusServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*router.Status, error) + grpc.ServerStream +} + +type monitorRouterStatusServer struct { + grpc.ServerStream +} + +func (x *monitorRouterStatusServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorRouterStatusServer) Recv() (*router.Status, error) { + m := new(router.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitor_BrokerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).BrokerStatus(&monitorBrokerStatusServer{stream}) +} + +type Monitor_BrokerStatusServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*broker.Status, error) + grpc.ServerStream +} + +type monitorBrokerStatusServer struct { + grpc.ServerStream +} + +func (x *monitorBrokerStatusServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorBrokerStatusServer) Recv() (*broker.Status, error) { + m := new(broker.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitor_HandlerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).HandlerStatus(&monitorHandlerStatusServer{stream}) +} + +type Monitor_HandlerStatusServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*handler.Status, error) + grpc.ServerStream +} + +type monitorHandlerStatusServer struct { + grpc.ServerStream +} + +func (x *monitorHandlerStatusServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorHandlerStatusServer) Recv() (*handler.Status, error) { + m := new(handler.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _Monitor_serviceDesc = grpc.ServiceDesc{ + ServiceName: "monitor.Monitor", + HandlerType: (*MonitorServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "GatewayStatus", + Handler: _Monitor_GatewayStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "GatewayUplink", + Handler: _Monitor_GatewayUplink_Handler, + ClientStreams: true, + }, + { + StreamName: "GatewayDownlink", + Handler: _Monitor_GatewayDownlink_Handler, + ClientStreams: true, + }, + { + StreamName: "RouterStatus", + Handler: _Monitor_RouterStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "BrokerStatus", + Handler: _Monitor_BrokerStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "HandlerStatus", + Handler: _Monitor_HandlerStatus_Handler, + ClientStreams: true, + }, + }, + Metadata: "github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto", +} + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto", fileDescriptorMonitor) +} + +var fileDescriptorMonitor = []byte{ + // 306 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x92, 0xc1, 0x4a, 0xfc, 0x30, + 0x10, 0xc6, 0xff, 0xfd, 0x1f, 0x5c, 0x08, 0xbb, 0x2e, 0x14, 0x54, 0x58, 0xb1, 0x67, 0x41, 0x48, + 0x40, 0x2f, 0xeb, 0x82, 0x20, 0xcb, 0x8a, 0x5e, 0xd6, 0x83, 0xae, 0x17, 0x6f, 0xa9, 0xc6, 0xb4, + 0xb4, 0xcd, 0x94, 0x74, 0x4a, 0xf1, 0x4d, 0x7c, 0x24, 0x8f, 0x3e, 0x82, 0xd4, 0xe7, 0x10, 0xc4, + 0x26, 0xd3, 0x83, 0x07, 0x8b, 0xa7, 0x8f, 0xc9, 0xe4, 0xfb, 0x75, 0xe6, 0x6b, 0xd8, 0xa9, 0x4e, + 0x31, 0xa9, 0x63, 0xfe, 0x00, 0x85, 0xd8, 0x24, 0x6a, 0x93, 0xa4, 0x46, 0x57, 0xd7, 0x0a, 0x1b, + 0xb0, 0x99, 0x40, 0x34, 0x42, 0x96, 0xa9, 0x28, 0xc0, 0xa4, 0x08, 0x96, 0x94, 0x97, 0x16, 0x10, + 0xc2, 0x91, 0x2f, 0x67, 0x07, 0x74, 0x4f, 0x4b, 0x54, 0x8d, 0x7c, 0x26, 0x75, 0xf7, 0x66, 0xfb, + 0xd4, 0xb6, 0x50, 0xa3, 0xb2, 0x5e, 0x7e, 0x36, 0x63, 0x0b, 0x99, 0xb2, 0x5e, 0x7c, 0xb3, 0x07, + 0x27, 0xd2, 0x3c, 0xe6, 0xca, 0x92, 0x92, 0x57, 0x03, 0xe8, 0x5c, 0x89, 0xae, 0x8a, 0xeb, 0x27, + 0xa1, 0x8a, 0x12, 0xfd, 0x57, 0x8f, 0x3f, 0xff, 0xb3, 0xd1, 0xda, 0x0d, 0x18, 0x2e, 0xd8, 0xe4, + 0xd2, 0x8d, 0x74, 0x8b, 0x12, 0xeb, 0x2a, 0x9c, 0x72, 0x1a, 0xd1, 0x1d, 0xcc, 0x76, 0xb9, 0x63, + 0x71, 0x62, 0xf1, 0x8b, 0x6f, 0xd6, 0x61, 0x10, 0x9e, 0xf7, 0xde, 0xbb, 0x32, 0x4f, 0x4d, 0x16, + 0xee, 0x70, 0xbf, 0x80, 0xab, 0xd7, 0xaa, 0xaa, 0xa4, 0x56, 0xbf, 0x10, 0x56, 0x6c, 0xea, 0x09, + 0x2b, 0x68, 0x4c, 0xc7, 0xd8, 0x23, 0x06, 0x9d, 0x0c, 0x53, 0xe6, 0x6c, 0x7c, 0xd3, 0x79, 0xfc, + 0x0a, 0xdb, 0x84, 0x18, 0xdc, 0x60, 0xce, 0xc6, 0xcb, 0x2e, 0xd5, 0xde, 0xe9, 0x43, 0x1e, 0x74, + 0x2e, 0xd8, 0xe4, 0xca, 0x25, 0xde, 0xe7, 0x46, 0x7f, 0x60, 0xc8, 0xbb, 0x3c, 0x7b, 0x6d, 0xa3, + 0xe0, 0xad, 0x8d, 0x82, 0xf7, 0x36, 0x0a, 0x5e, 0x3e, 0xa2, 0x7f, 0xf7, 0x47, 0x7f, 0x78, 0x6a, + 0xf1, 0x56, 0x07, 0x3c, 0xf9, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x06, 0x34, 0x73, 0x98, 0xa0, 0x02, + 0x00, 0x00, +} diff --git a/api/monitor/monitor.proto b/api/monitor/monitor.proto new file mode 100644 index 000000000..810fd755a --- /dev/null +++ b/api/monitor/monitor.proto @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "ttn/api/gateway/gateway.proto"; +import "ttn/api/router/router.proto"; +import "ttn/api/broker/broker.proto"; +import "ttn/api/handler/handler.proto"; + +import "google/protobuf/empty.proto"; + +package monitor; + +option go_package = "github.com/TheThingsNetwork/ttn/api/monitor"; + +service Monitor { + rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); + rpc GatewayUplink(stream router.UplinkMessage) returns (google.protobuf.Empty); + rpc GatewayDownlink(stream router.DownlinkMessage) returns (google.protobuf.Empty); + + rpc RouterStatus(stream router.Status) returns (google.protobuf.Empty); + rpc BrokerStatus(stream broker.Status) returns (google.protobuf.Empty); + rpc HandlerStatus(stream handler.Status) returns (google.protobuf.Empty); +} diff --git a/api/monitor/uplink.go b/api/monitor/uplink.go new file mode 100644 index 000000000..26771defd --- /dev/null +++ b/api/monitor/uplink.go @@ -0,0 +1,101 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" +) + +func (cl *gatewayClient) initUplink() { + cl.uplink.ch = make(chan *router.UplinkMessage, BufferSize) + go cl.monitorUplink() +} + +func (cl *gatewayClient) monitorUplink() { + var retries int +newStream: + for { + ctx, cancel := context.WithCancel(cl.Context()) + cl.uplink.Lock() + cl.uplink.cancel = cancel + cl.uplink.Unlock() + + stream, err := cl.client.client.GatewayUplink(ctx) + if err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue + } + retries = 0 + cl.Ctx.Debug("Opened new monitor uplink stream") + + // The actual stream + go func() { + for { + select { + case <-ctx.Done(): + return + case uplink, ok := <-cl.uplink.ch: + if ok { + stream.Send(uplink) + cl.Ctx.Debug("Sent uplink to monitor") + } + } + } + }() + + msg := new(empty.Empty) + for { + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor uplink stream, closing...") + stream.CloseSend() + cl.Ctx.Debug("Closed monitor uplink stream") + + cl.uplink.Lock() + cl.uplink.cancel() + cl.uplink.cancel = nil + cl.uplink.Unlock() + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue newStream + } + } + } +} + +func (cl *gatewayClient) closeUplink() { + cl.uplink.Lock() + defer cl.uplink.Unlock() + if cl.uplink.cancel != nil { + cl.uplink.cancel() + } +} + +// SendUplink sends uplink to the monitor +func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { + if !cl.IsConfigured() { + return nil + } + + cl.uplink.init.Do(cl.initUplink) + + select { + case cl.uplink.ch <- uplink: + default: + cl.Ctx.Warn("Not sending uplink to monitor, buffer full") + return errors.New("Not sending uplink to monitor, buffer full") + } + return +} diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go new file mode 100644 index 000000000..9dc6945af --- /dev/null +++ b/api/networkserver/networkserver.pb.go @@ -0,0 +1,1316 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto +// DO NOT EDIT! + +/* + Package networkserver is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto + + It has these top-level messages: + DevicesRequest + DevicesResponse + StatusRequest + Status +*/ +package networkserver + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" +import api "github.com/TheThingsNetwork/ttn/api" +import lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" +import broker "github.com/TheThingsNetwork/ttn/api/broker" +import handler "github.com/TheThingsNetwork/ttn/api/handler" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type DevicesRequest struct { + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + FCnt uint32 `protobuf:"varint,2,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` +} + +func (m *DevicesRequest) Reset() { *m = DevicesRequest{} } +func (m *DevicesRequest) String() string { return proto.CompactTextString(m) } +func (*DevicesRequest) ProtoMessage() {} +func (*DevicesRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{0} } + +type DevicesResponse struct { + Results []*lorawan.Device `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` +} + +func (m *DevicesResponse) Reset() { *m = DevicesResponse{} } +func (m *DevicesResponse) String() string { return proto.CompactTextString(m) } +func (*DevicesResponse) ProtoMessage() {} +func (*DevicesResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1} } + +func (m *DevicesResponse) GetResults() []*lorawan.Device { + if m != nil { + return m.Results + } + return nil +} + +// message StatusRequest is used to request the status of this NetworkServer +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{2} } + +// message Status is the response to the StatusRequest +type Status struct { + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + Downlink *api.Rates `protobuf:"bytes,12,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,13,opt,name=activations" json:"activations,omitempty"` + DevicesPerAddress *api.Percentiles `protobuf:"bytes,21,opt,name=devices_per_address,json=devicesPerAddress" json:"devices_per_address,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{3} } + +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + +func (m *Status) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *Status) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *Status) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + +func (m *Status) GetDevicesPerAddress() *api.Percentiles { + if m != nil { + return m.DevicesPerAddress + } + return nil +} + +func init() { + proto.RegisterType((*DevicesRequest)(nil), "networkserver.DevicesRequest") + proto.RegisterType((*DevicesResponse)(nil), "networkserver.DevicesResponse") + proto.RegisterType((*StatusRequest)(nil), "networkserver.StatusRequest") + proto.RegisterType((*Status)(nil), "networkserver.Status") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for NetworkServer service + +type NetworkServerClient interface { + // Broker requests devices with DevAddr for MIC check + GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) + // Broker requests device activation "template" from Network Server + PrepareActivation(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeduplicatedDeviceActivationRequest, error) + // Broker confirms device activation + Activate(ctx context.Context, in *handler.DeviceActivationResponse, opts ...grpc.CallOption) (*handler.DeviceActivationResponse, error) + // Broker informs Network Server about Uplink + Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*broker.DeduplicatedUplinkMessage, error) + // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... + Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) +} + +type networkServerClient struct { + cc *grpc.ClientConn +} + +func NewNetworkServerClient(cc *grpc.ClientConn) NetworkServerClient { + return &networkServerClient{cc} +} + +func (c *networkServerClient) GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) { + out := new(DevicesResponse) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/GetDevices", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) PrepareActivation(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeduplicatedDeviceActivationRequest, error) { + out := new(broker.DeduplicatedDeviceActivationRequest) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/PrepareActivation", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Activate(ctx context.Context, in *handler.DeviceActivationResponse, opts ...grpc.CallOption) (*handler.DeviceActivationResponse, error) { + out := new(handler.DeviceActivationResponse) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*broker.DeduplicatedUplinkMessage, error) { + out := new(broker.DeduplicatedUplinkMessage) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Uplink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) { + out := new(broker.DownlinkMessage) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Downlink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for NetworkServer service + +type NetworkServerServer interface { + // Broker requests devices with DevAddr for MIC check + GetDevices(context.Context, *DevicesRequest) (*DevicesResponse, error) + // Broker requests device activation "template" from Network Server + PrepareActivation(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) + // Broker confirms device activation + Activate(context.Context, *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) + // Broker informs Network Server about Uplink + Uplink(context.Context, *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) + // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... + Downlink(context.Context, *broker.DownlinkMessage) (*broker.DownlinkMessage, error) +} + +func RegisterNetworkServerServer(s *grpc.Server, srv NetworkServerServer) { + s.RegisterService(&_NetworkServer_serviceDesc, srv) +} + +func _NetworkServer_GetDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DevicesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerServer).GetDevices(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/GetDevices", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).GetDevices(ctx, req.(*DevicesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NetworkServer_PrepareActivation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.DeduplicatedDeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerServer).PrepareActivation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/PrepareActivation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).PrepareActivation(ctx, req.(*broker.DeduplicatedDeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NetworkServer_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(handler.DeviceActivationResponse) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerServer).Activate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).Activate(ctx, req.(*handler.DeviceActivationResponse)) + } + return interceptor(ctx, in, info, handler) +} + +func _NetworkServer_Uplink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.DeduplicatedUplinkMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerServer).Uplink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/Uplink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).Uplink(ctx, req.(*broker.DeduplicatedUplinkMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _NetworkServer_Downlink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.DownlinkMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerServer).Downlink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/Downlink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).Downlink(ctx, req.(*broker.DownlinkMessage)) + } + return interceptor(ctx, in, info, handler) +} + +var _NetworkServer_serviceDesc = grpc.ServiceDesc{ + ServiceName: "networkserver.NetworkServer", + HandlerType: (*NetworkServerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetDevices", + Handler: _NetworkServer_GetDevices_Handler, + }, + { + MethodName: "PrepareActivation", + Handler: _NetworkServer_PrepareActivation_Handler, + }, + { + MethodName: "Activate", + Handler: _NetworkServer_Activate_Handler, + }, + { + MethodName: "Uplink", + Handler: _NetworkServer_Uplink_Handler, + }, + { + MethodName: "Downlink", + Handler: _NetworkServer_Downlink_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto", +} + +// Client API for NetworkServerManager service + +type NetworkServerManagerClient interface { + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) +} + +type networkServerManagerClient struct { + cc *grpc.ClientConn +} + +func NewNetworkServerManagerClient(cc *grpc.ClientConn) NetworkServerManagerClient { + return &networkServerManagerClient{cc} +} + +func (c *networkServerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/GetStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for NetworkServerManager service + +type NetworkServerManagerServer interface { + GetStatus(context.Context, *StatusRequest) (*Status, error) +} + +func RegisterNetworkServerManagerServer(s *grpc.Server, srv NetworkServerManagerServer) { + s.RegisterService(&_NetworkServerManager_serviceDesc, srv) +} + +func _NetworkServerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerManagerServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServerManager/GetStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerManagerServer).GetStatus(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "networkserver.NetworkServerManager", + HandlerType: (*NetworkServerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetStatus", + Handler: _NetworkServerManager_GetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto", +} + +func (m *DevicesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DevicesRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevAddr != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.FCnt != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.FCnt)) + } + return i, nil +} + +func (m *DevicesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DevicesResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Results) > 0 { + for _, msg := range m.Results { + dAtA[i] = 0xa + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.System != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.System.Size())) + n2, err := m.System.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.Component != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Component.Size())) + n3, err := m.Component.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Uplink != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Uplink.Size())) + n4, err := m.Uplink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Downlink != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Downlink.Size())) + n5, err := m.Downlink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Activations != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Activations.Size())) + n6, err := m.Activations.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.DevicesPerAddress != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintNetworkserver(dAtA, i, uint64(m.DevicesPerAddress.Size())) + n7, err := m.DevicesPerAddress.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + return i, nil +} + +func encodeFixed64Networkserver(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Networkserver(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintNetworkserver(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *DevicesRequest) Size() (n int) { + var l int + _ = l + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovNetworkserver(uint64(m.FCnt)) + } + return n +} + +func (m *DevicesResponse) Size() (n int) { + var l int + _ = l + if len(m.Results) > 0 { + for _, e := range m.Results { + l = e.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + } + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *Status) Size() (n int) { + var l int + _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.DevicesPerAddress != nil { + l = m.DevicesPerAddress.Size() + n += 2 + l + sovNetworkserver(uint64(l)) + } + return n +} + +func sovNetworkserver(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozNetworkserver(x uint64) (n int) { + return sovNetworkserver(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DevicesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevicesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevicesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevicesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevicesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevicesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Results = append(m.Results, &lorawan.Device{}) + if err := m.Results[len(m.Results)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.System == nil { + m.System = &api.SystemStats{} + } + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevicesPerAddress", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DevicesPerAddress == nil { + m.DevicesPerAddress = &api.Percentiles{} + } + if err := m.DevicesPerAddress.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipNetworkserver(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthNetworkserver + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipNetworkserver(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthNetworkserver = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowNetworkserver = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto", fileDescriptorNetworkserver) +} + +var fileDescriptorNetworkserver = []byte{ + // 608 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x94, 0x4f, 0x4f, 0x13, 0x4f, + 0x18, 0xc7, 0x7f, 0x0b, 0x3f, 0x4b, 0x79, 0x4a, 0x45, 0x06, 0x89, 0x4d, 0x95, 0x0a, 0x4d, 0x34, + 0x35, 0xea, 0x6e, 0xa8, 0x89, 0x27, 0x0e, 0xfc, 0xa9, 0xe1, 0x60, 0x20, 0x75, 0xc1, 0xc4, 0x78, + 0x21, 0xd3, 0xdd, 0x87, 0x76, 0xc3, 0x76, 0x66, 0x9d, 0x99, 0x2d, 0xf2, 0x4e, 0xbc, 0x7a, 0xf6, + 0x8d, 0x78, 0xf4, 0xec, 0xc1, 0x18, 0x7c, 0x23, 0xa6, 0xf3, 0xa7, 0xb0, 0x02, 0x21, 0x9c, 0x76, + 0x9f, 0xef, 0xf7, 0xb3, 0x33, 0xcf, 0xce, 0xf7, 0xd9, 0x85, 0x37, 0xfd, 0x44, 0x0d, 0xf2, 0x9e, + 0x1f, 0xf1, 0x61, 0x70, 0x30, 0xc0, 0x83, 0x41, 0xc2, 0xfa, 0x72, 0x0f, 0xd5, 0x09, 0x17, 0xc7, + 0x81, 0x52, 0x2c, 0xa0, 0x59, 0x12, 0x30, 0x53, 0x4b, 0x14, 0x23, 0x14, 0xc5, 0xca, 0xcf, 0x04, + 0x57, 0x9c, 0x54, 0x0b, 0x62, 0xfd, 0xe5, 0x85, 0x55, 0xfb, 0xbc, 0xcf, 0x03, 0x4d, 0xf5, 0xf2, + 0x23, 0x5d, 0xe9, 0x42, 0xdf, 0x99, 0xa7, 0xeb, 0x0b, 0x6e, 0x23, 0x9a, 0x25, 0x56, 0x7a, 0xe2, + 0x24, 0x5d, 0x46, 0x3c, 0x0d, 0x52, 0x2e, 0xe8, 0x09, 0x65, 0x41, 0x8c, 0xa3, 0x24, 0x42, 0x8b, + 0x3d, 0x74, 0x58, 0x4f, 0xf0, 0x63, 0x14, 0xf6, 0x62, 0xcd, 0x65, 0x67, 0x0e, 0x28, 0x8b, 0x53, + 0x14, 0xee, 0x6a, 0xec, 0xe6, 0x67, 0xb8, 0xdb, 0xd1, 0x6b, 0xc9, 0x10, 0x3f, 0xe5, 0x28, 0x15, + 0x79, 0x07, 0xe5, 0x18, 0x47, 0x87, 0x34, 0x8e, 0x45, 0xcd, 0x5b, 0xf1, 0x5a, 0x73, 0x5b, 0xaf, + 0x7f, 0xfe, 0x7a, 0xdc, 0xbe, 0xe9, 0x88, 0x22, 0x2e, 0x30, 0x50, 0xa7, 0x19, 0x4a, 0xbf, 0x83, + 0xa3, 0xcd, 0x38, 0x16, 0xe1, 0x4c, 0x6c, 0x6e, 0xc8, 0x22, 0xdc, 0x39, 0x3a, 0x8c, 0x98, 0xaa, + 0x4d, 0xad, 0x78, 0xad, 0x6a, 0xf8, 0xff, 0xd1, 0x36, 0x53, 0xcd, 0x75, 0x98, 0x9f, 0xec, 0x2c, + 0x33, 0xce, 0x24, 0x92, 0x67, 0x30, 0x23, 0x50, 0xe6, 0xa9, 0x92, 0x35, 0x6f, 0x65, 0xba, 0x55, + 0x69, 0xcf, 0xfb, 0xf6, 0x85, 0x7d, 0x83, 0x86, 0xce, 0x6f, 0xce, 0x43, 0x75, 0x5f, 0x51, 0x95, + 0xbb, 0xb6, 0x9b, 0x5f, 0xa7, 0xa0, 0x64, 0x14, 0xd2, 0x82, 0x92, 0x3c, 0x95, 0x0a, 0x87, 0xba, + 0xff, 0x4a, 0xfb, 0x9e, 0x3f, 0x3e, 0xd2, 0x7d, 0x2d, 0x8d, 0x11, 0x19, 0x5a, 0x9f, 0xac, 0xc1, + 0x6c, 0xc4, 0x87, 0x19, 0x67, 0x68, 0x9b, 0xab, 0xb4, 0x17, 0x35, 0xbc, 0xed, 0x54, 0xc3, 0x9f, + 0x53, 0xa4, 0x09, 0xa5, 0x3c, 0x4b, 0x13, 0x76, 0x5c, 0xab, 0x68, 0x1e, 0x34, 0x1f, 0x52, 0x85, + 0x32, 0xb4, 0x0e, 0x79, 0x0a, 0xe5, 0x98, 0x9f, 0x30, 0x4d, 0xcd, 0x5d, 0xa2, 0x26, 0x1e, 0x79, + 0x01, 0x15, 0x1a, 0xa9, 0x64, 0x44, 0x55, 0xc2, 0x99, 0xac, 0x55, 0x2f, 0xa1, 0x17, 0x6d, 0xb2, + 0x01, 0x8b, 0x26, 0x76, 0x79, 0x98, 0xa1, 0xd0, 0x01, 0xa1, 0x94, 0xb5, 0xa5, 0x0b, 0xef, 0xd8, + 0x45, 0x11, 0x21, 0x53, 0x49, 0x8a, 0x32, 0x5c, 0xb0, 0x70, 0x17, 0xc5, 0xa6, 0x41, 0xdb, 0xdf, + 0xa6, 0xa1, 0x6a, 0x33, 0xdb, 0xd7, 0x33, 0x4a, 0xde, 0x02, 0xec, 0xa0, 0xb2, 0x39, 0x90, 0x65, + 0xbf, 0x38, 0xd6, 0xc5, 0xc9, 0xa8, 0x37, 0xae, 0xb3, 0x6d, 0x7c, 0x43, 0x58, 0xe8, 0x0a, 0xcc, + 0xa8, 0xc0, 0xcd, 0x49, 0xdb, 0xe4, 0xb9, 0x6f, 0xc7, 0xb1, 0x83, 0xf1, 0xf8, 0x78, 0x22, 0xaa, + 0x30, 0x36, 0x4f, 0x9e, 0x53, 0x6e, 0x87, 0xdb, 0xc0, 0xa4, 0x0b, 0x65, 0x2b, 0x22, 0x59, 0xf5, + 0xdd, 0x58, 0x5f, 0xa6, 0x4d, 0x77, 0xf5, 0x9b, 0x11, 0xb2, 0x07, 0xa5, 0xf7, 0x26, 0xc1, 0xd5, + 0xab, 0x1a, 0x31, 0xde, 0x2e, 0x4a, 0x49, 0xfb, 0xe3, 0xf5, 0x6e, 0x42, 0xc8, 0x3a, 0x94, 0x3b, + 0x2e, 0xeb, 0x07, 0x13, 0xdc, 0x2a, 0x6e, 0x9d, 0xeb, 0x8c, 0xf6, 0x07, 0xb8, 0x5f, 0x08, 0x6b, + 0x97, 0x32, 0xda, 0x47, 0x41, 0x36, 0x60, 0x76, 0x07, 0x95, 0x9d, 0xf5, 0x47, 0xff, 0x64, 0x52, + 0xf8, 0x28, 0xea, 0x4b, 0x57, 0xba, 0x5b, 0xdb, 0xdf, 0xcf, 0x1a, 0xde, 0x8f, 0xb3, 0x86, 0xf7, + 0xfb, 0xac, 0xe1, 0x7d, 0xf9, 0xd3, 0xf8, 0xef, 0xe3, 0xda, 0xad, 0xff, 0x80, 0xbd, 0x92, 0xfe, + 0x81, 0xbc, 0xfa, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x40, 0x55, 0xeb, 0x3d, 0x05, 0x00, 0x00, +} diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto new file mode 100644 index 000000000..7b69755c4 --- /dev/null +++ b/api/networkserver/networkserver.proto @@ -0,0 +1,62 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +import "ttn/api/api.proto"; +import "ttn/api/protocol/lorawan/device.proto"; +import "ttn/api/broker/broker.proto"; +import "ttn/api/handler/handler.proto"; + +package networkserver; + +option go_package = "github.com/TheThingsNetwork/ttn/api/networkserver"; + +message DevicesRequest { + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + uint32 f_cnt = 2; +} + +message DevicesResponse { + repeated lorawan.Device results = 1; +} + +service NetworkServer { + // Broker requests devices with DevAddr for MIC check + rpc GetDevices(DevicesRequest) returns (DevicesResponse); + + // Broker requests device activation "template" from Network Server + rpc PrepareActivation(broker.DeduplicatedDeviceActivationRequest) returns (broker.DeduplicatedDeviceActivationRequest); + + // Broker confirms device activation + rpc Activate(handler.DeviceActivationResponse) returns (handler.DeviceActivationResponse); + + // Broker informs Network Server about Uplink + rpc Uplink(broker.DeduplicatedUplinkMessage) returns (broker.DeduplicatedUplinkMessage); + + // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... + rpc Downlink(broker.DownlinkMessage) returns (broker.DownlinkMessage); +} + +// message StatusRequest is used to request the status of this NetworkServer +message StatusRequest {} + +// message Status is the response to the StatusRequest +message Status { + api.SystemStats system = 1; + api.ComponentStats component = 2; + + api.Rates uplink = 11; + api.Rates downlink = 12; + api.Rates activations = 13; + + api.Percentiles devices_per_address = 21; +} + +// The NetworkServerManager service provides configuration and monitoring +// functionality +service NetworkServerManager { + rpc GetStatus(StatusRequest) returns (Status); +} diff --git a/api/networkserver/networkserver_mock.go b/api/networkserver/networkserver_mock.go new file mode 100644 index 000000000..1d38ef7f8 --- /dev/null +++ b/api/networkserver/networkserver_mock.go @@ -0,0 +1,258 @@ +// Automatically generated by MockGen. DO NOT EDIT! +// Source: ./api/networkserver/networkserver.pb.go + +package networkserver + +import ( + broker "github.com/TheThingsNetwork/ttn/api/broker" + handler "github.com/TheThingsNetwork/ttn/api/handler" + gomock "github.com/golang/mock/gomock" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Mock of NetworkServerClient interface +type MockNetworkServerClient struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerClientRecorder +} + +// Recorder for MockNetworkServerClient (not exported) +type _MockNetworkServerClientRecorder struct { + mock *MockNetworkServerClient +} + +func NewMockNetworkServerClient(ctrl *gomock.Controller) *MockNetworkServerClient { + mock := &MockNetworkServerClient{ctrl: ctrl} + mock.recorder = &_MockNetworkServerClientRecorder{mock} + return mock +} + +func (_m *MockNetworkServerClient) EXPECT() *_MockNetworkServerClientRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerClient) GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "GetDevices", _s...) + ret0, _ := ret[0].(*DevicesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) GetDevices(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetDevices", _s...) +} + +func (_m *MockNetworkServerClient) PrepareActivation(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeduplicatedDeviceActivationRequest, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "PrepareActivation", _s...) + ret0, _ := ret[0].(*broker.DeduplicatedDeviceActivationRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) PrepareActivation(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "PrepareActivation", _s...) +} + +func (_m *MockNetworkServerClient) Activate(ctx context.Context, in *handler.DeviceActivationResponse, opts ...grpc.CallOption) (*handler.DeviceActivationResponse, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "Activate", _s...) + ret0, _ := ret[0].(*handler.DeviceActivationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) Activate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "Activate", _s...) +} + +func (_m *MockNetworkServerClient) Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*broker.DeduplicatedUplinkMessage, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "Uplink", _s...) + ret0, _ := ret[0].(*broker.DeduplicatedUplinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) Uplink(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "Uplink", _s...) +} + +func (_m *MockNetworkServerClient) Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "Downlink", _s...) + ret0, _ := ret[0].(*broker.DownlinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) Downlink(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "Downlink", _s...) +} + +// Mock of NetworkServerServer interface +type MockNetworkServerServer struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerServerRecorder +} + +// Recorder for MockNetworkServerServer (not exported) +type _MockNetworkServerServerRecorder struct { + mock *MockNetworkServerServer +} + +func NewMockNetworkServerServer(ctrl *gomock.Controller) *MockNetworkServerServer { + mock := &MockNetworkServerServer{ctrl: ctrl} + mock.recorder = &_MockNetworkServerServerRecorder{mock} + return mock +} + +func (_m *MockNetworkServerServer) EXPECT() *_MockNetworkServerServerRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerServer) GetDevices(_param0 context.Context, _param1 *DevicesRequest) (*DevicesResponse, error) { + ret := _m.ctrl.Call(_m, "GetDevices", _param0, _param1) + ret0, _ := ret[0].(*DevicesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) GetDevices(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetDevices", arg0, arg1) +} + +func (_m *MockNetworkServerServer) PrepareActivation(_param0 context.Context, _param1 *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { + ret := _m.ctrl.Call(_m, "PrepareActivation", _param0, _param1) + ret0, _ := ret[0].(*broker.DeduplicatedDeviceActivationRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) PrepareActivation(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "PrepareActivation", arg0, arg1) +} + +func (_m *MockNetworkServerServer) Activate(_param0 context.Context, _param1 *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { + ret := _m.ctrl.Call(_m, "Activate", _param0, _param1) + ret0, _ := ret[0].(*handler.DeviceActivationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) Activate(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Activate", arg0, arg1) +} + +func (_m *MockNetworkServerServer) Uplink(_param0 context.Context, _param1 *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { + ret := _m.ctrl.Call(_m, "Uplink", _param0, _param1) + ret0, _ := ret[0].(*broker.DeduplicatedUplinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) Uplink(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Uplink", arg0, arg1) +} + +func (_m *MockNetworkServerServer) Downlink(_param0 context.Context, _param1 *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { + ret := _m.ctrl.Call(_m, "Downlink", _param0, _param1) + ret0, _ := ret[0].(*broker.DownlinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) Downlink(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Downlink", arg0, arg1) +} + +// Mock of NetworkServerManagerClient interface +type MockNetworkServerManagerClient struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerManagerClientRecorder +} + +// Recorder for MockNetworkServerManagerClient (not exported) +type _MockNetworkServerManagerClientRecorder struct { + mock *MockNetworkServerManagerClient +} + +func NewMockNetworkServerManagerClient(ctrl *gomock.Controller) *MockNetworkServerManagerClient { + mock := &MockNetworkServerManagerClient{ctrl: ctrl} + mock.recorder = &_MockNetworkServerManagerClientRecorder{mock} + return mock +} + +func (_m *MockNetworkServerManagerClient) EXPECT() *_MockNetworkServerManagerClientRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "GetStatus", _s...) + ret0, _ := ret[0].(*Status) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerManagerClientRecorder) GetStatus(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetStatus", _s...) +} + +// Mock of NetworkServerManagerServer interface +type MockNetworkServerManagerServer struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerManagerServerRecorder +} + +// Recorder for MockNetworkServerManagerServer (not exported) +type _MockNetworkServerManagerServerRecorder struct { + mock *MockNetworkServerManagerServer +} + +func NewMockNetworkServerManagerServer(ctrl *gomock.Controller) *MockNetworkServerManagerServer { + mock := &MockNetworkServerManagerServer{ctrl: ctrl} + mock.recorder = &_MockNetworkServerManagerServerRecorder{mock} + return mock +} + +func (_m *MockNetworkServerManagerServer) EXPECT() *_MockNetworkServerManagerServerRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerManagerServer) GetStatus(_param0 context.Context, _param1 *StatusRequest) (*Status, error) { + ret := _m.ctrl.Call(_m, "GetStatus", _param0, _param1) + ret0, _ := ret[0].(*Status) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerManagerServerRecorder) GetStatus(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetStatus", arg0, arg1) +} diff --git a/api/networkserver/validation.go b/api/networkserver/validation.go new file mode 100644 index 000000000..9470b86d0 --- /dev/null +++ b/api/networkserver/validation.go @@ -0,0 +1,11 @@ +package networkserver + +import "github.com/TheThingsNetwork/ttn/utils/errors" + +// Validate implements the api.Validator interface +func (m *DevicesRequest) Validate() error { + if m.DevAddr == nil || m.DevAddr.IsEmpty() { + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") + } + return nil +} diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go new file mode 100644 index 000000000..1c426ad04 --- /dev/null +++ b/api/protocol/lorawan/device.pb.go @@ -0,0 +1,1207 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto +// DO NOT EDIT! + +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto + + It has these top-level messages: + DeviceIdentifier + Device +*/ +package lorawan + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/gogo/protobuf/gogoproto" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type DeviceIdentifier struct { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + // The DevEUI is a unique, 8 byte identifier for the device. + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` +} + +func (m *DeviceIdentifier) Reset() { *m = DeviceIdentifier{} } +func (m *DeviceIdentifier) String() string { return proto.CompactTextString(m) } +func (*DeviceIdentifier) ProtoMessage() {} +func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorDevice, []int{0} } + +type Device struct { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + // The DevEUI is a unique, 8 byte identifier for the device. + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + // The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. + AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + // The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. + DevId string `protobuf:"bytes,4,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + // The DevAddr is a dynamic, 4 byte session address for the device. + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,5,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + // The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,6,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + // The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. + AppSKey *github_com_TheThingsNetwork_ttn_core_types.AppSKey `protobuf:"bytes,7,opt,name=app_s_key,json=appSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppSKey" json:"app_s_key,omitempty"` + // The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). + AppKey *github_com_TheThingsNetwork_ttn_core_types.AppKey `protobuf:"bytes,8,opt,name=app_key,json=appKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppKey" json:"app_key,omitempty"` + // FCntUp is the uplink frame counter for a device session. + FCntUp uint32 `protobuf:"varint,9,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` + // FCntDown is the downlink frame counter for a device session. + FCntDown uint32 `protobuf:"varint,10,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` + // The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. + DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + // The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. + Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + // The ActivationContstraints are used to allocate a device address for a device. + // There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. + ActivationConstraints string `protobuf:"bytes,13,opt,name=activation_constraints,json=activationConstraints,proto3" json:"activation_constraints,omitempty"` + // When the device was last seen (Unix nanoseconds) + LastSeen int64 `protobuf:"varint,21,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} +func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorDevice, []int{1} } + +func init() { + proto.RegisterType((*DeviceIdentifier)(nil), "lorawan.DeviceIdentifier") + proto.RegisterType((*Device)(nil), "lorawan.Device") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for DeviceManager service + +type DeviceManagerClient interface { + GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) +} + +type deviceManagerClient struct { + cc *grpc.ClientConn +} + +func NewDeviceManagerClient(cc *grpc.ClientConn) DeviceManagerClient { + return &deviceManagerClient{cc} +} + +func (c *deviceManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) { + out := new(Device) + err := grpc.Invoke(ctx, "/lorawan.DeviceManager/GetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/lorawan.DeviceManager/SetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) + err := grpc.Invoke(ctx, "/lorawan.DeviceManager/DeleteDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for DeviceManager service + +type DeviceManagerServer interface { + GetDevice(context.Context, *DeviceIdentifier) (*Device, error) + SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) +} + +func RegisterDeviceManagerServer(s *grpc.Server, srv DeviceManagerServer) { + s.RegisterService(&_DeviceManager_serviceDesc, srv) +} + +func _DeviceManager_GetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeviceManagerServer).GetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DeviceManager/GetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeviceManagerServer).GetDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeviceManager_SetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Device) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeviceManagerServer).SetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DeviceManager/SetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeviceManagerServer).SetDevice(ctx, req.(*Device)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeviceManager_DeleteDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeviceManagerServer).DeleteDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DeviceManager/DeleteDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeviceManagerServer).DeleteDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +var _DeviceManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lorawan.DeviceManager", + HandlerType: (*DeviceManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetDevice", + Handler: _DeviceManager_GetDevice_Handler, + }, + { + MethodName: "SetDevice", + Handler: _DeviceManager_SetDevice_Handler, + }, + { + MethodName: "DeleteDevice", + Handler: _DeviceManager_DeleteDevice_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto", +} + +func (m *DeviceIdentifier) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceIdentifier) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEui != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.AppEui.Size())) + n1, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.DevEui != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.DevEui.Size())) + n2, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *Device) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Device) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEui != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.AppEui.Size())) + n3, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.DevEui != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.DevEui.Size())) + n4, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if len(m.AppId) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintDevice(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + } + if len(m.DevId) > 0 { + dAtA[i] = 0x22 + i++ + i = encodeVarintDevice(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) + } + if m.DevAddr != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.DevAddr.Size())) + n5, err := m.DevAddr.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.NwkSKey != nil { + dAtA[i] = 0x32 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.NwkSKey.Size())) + n6, err := m.NwkSKey.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.AppSKey != nil { + dAtA[i] = 0x3a + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.AppSKey.Size())) + n7, err := m.AppSKey.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.AppKey != nil { + dAtA[i] = 0x42 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.AppKey.Size())) + n8, err := m.AppKey.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + if m.FCntUp != 0 { + dAtA[i] = 0x48 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + dAtA[i] = 0x50 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.FCntDown)) + } + if m.DisableFCntCheck { + dAtA[i] = 0x58 + i++ + if m.DisableFCntCheck { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if m.Uses32BitFCnt { + dAtA[i] = 0x60 + i++ + if m.Uses32BitFCnt { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if len(m.ActivationConstraints) > 0 { + dAtA[i] = 0x6a + i++ + i = encodeVarintDevice(dAtA, i, uint64(len(m.ActivationConstraints))) + i += copy(dAtA[i:], m.ActivationConstraints) + } + if m.LastSeen != 0 { + dAtA[i] = 0xa8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintDevice(dAtA, i, uint64(m.LastSeen)) + } + return i, nil +} + +func encodeFixed64Device(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Device(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintDevice(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *DeviceIdentifier) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + return n +} + +func (m *Device) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.NwkSKey != nil { + l = m.NwkSKey.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.AppSKey != nil { + l = m.AppSKey.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.AppKey != nil { + l = m.AppKey.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.FCntUp != 0 { + n += 1 + sovDevice(uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + n += 1 + sovDevice(uint64(m.FCntDown)) + } + if m.DisableFCntCheck { + n += 2 + } + if m.Uses32BitFCnt { + n += 2 + } + l = len(m.ActivationConstraints) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.LastSeen != 0 { + n += 2 + sovDevice(uint64(m.LastSeen)) + } + return n +} + +func sovDevice(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozDevice(x uint64) (n int) { + return sovDevice(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DeviceIdentifier) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceIdentifier: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDevice(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey + m.NwkSKey = &v + if err := m.NwkSKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppSKey + m.AppSKey = &v + if err := m.AppSKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppKey + m.AppKey = &v + if err := m.AppKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) + } + m.FCntUp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FCntUp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) + } + m.FCntDown = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FCntDown |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DisableFCntCheck", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DisableFCntCheck = bool(v != 0) + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Uses32BitFCnt", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Uses32BitFCnt = bool(v != 0) + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationConstraints", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ActivationConstraints = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastSeen", wireType) + } + m.LastSeen = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LastSeen |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipDevice(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDevice(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthDevice + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipDevice(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthDevice = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDevice = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto", fileDescriptorDevice) +} + +var fileDescriptorDevice = []byte{ + // 586 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xcf, 0x6e, 0x13, 0x3d, + 0x14, 0xc5, 0xbf, 0xf9, 0x4a, 0x93, 0x8c, 0x69, 0x44, 0x65, 0xd4, 0xca, 0xa4, 0x28, 0x8d, 0xba, + 0x21, 0x9b, 0xce, 0x88, 0xfe, 0x81, 0x75, 0x9a, 0xa4, 0x28, 0x42, 0x54, 0x62, 0xda, 0x6e, 0xd8, + 0x8c, 0x9c, 0xf1, 0xcd, 0xc4, 0xca, 0xd4, 0xb6, 0x66, 0x3c, 0x33, 0xca, 0x03, 0xf0, 0x0e, 0x3c, + 0x07, 0x6f, 0xc0, 0x8e, 0x25, 0xeb, 0x2e, 0x2a, 0x54, 0x5e, 0x04, 0x79, 0x9c, 0x52, 0x14, 0x09, + 0x55, 0x64, 0xc5, 0xee, 0xce, 0x39, 0xc7, 0xbf, 0x6b, 0xc7, 0xf1, 0x45, 0xbd, 0x98, 0xeb, 0x69, + 0x3e, 0xf6, 0x22, 0x79, 0xe5, 0x5f, 0x4c, 0xe1, 0x62, 0xca, 0x45, 0x9c, 0x9d, 0x81, 0x2e, 0x65, + 0x3a, 0xf3, 0xb5, 0x16, 0x3e, 0x55, 0xdc, 0x57, 0xa9, 0xd4, 0x32, 0x92, 0x89, 0x9f, 0xc8, 0x94, + 0x96, 0x54, 0xf8, 0x0c, 0x0a, 0x1e, 0x81, 0x57, 0xe9, 0xb8, 0xbe, 0x50, 0x5b, 0x3b, 0xb1, 0x94, + 0x71, 0x02, 0x36, 0x3e, 0xce, 0x27, 0x3e, 0x5c, 0x29, 0x3d, 0xb7, 0xa9, 0xd6, 0xfe, 0x6f, 0x8d, + 0x62, 0x19, 0xcb, 0xfb, 0x94, 0xf9, 0xaa, 0x3e, 0xaa, 0xca, 0xc6, 0xf7, 0x3e, 0x3b, 0x68, 0x73, + 0x50, 0x75, 0x19, 0x31, 0x10, 0x9a, 0x4f, 0x38, 0xa4, 0xf8, 0x0c, 0xd5, 0xa9, 0x52, 0x21, 0xe4, + 0x9c, 0x38, 0x1d, 0xa7, 0xbb, 0x71, 0x72, 0x7c, 0x7d, 0xb3, 0xfb, 0xf2, 0xa1, 0x13, 0x44, 0x32, + 0x05, 0x5f, 0xcf, 0x15, 0x64, 0x5e, 0x4f, 0xa9, 0xe1, 0xe5, 0x28, 0xa8, 0x51, 0xa5, 0x86, 0x39, + 0x37, 0x3c, 0x06, 0x45, 0xc5, 0xfb, 0x7f, 0x25, 0xde, 0x00, 0x8a, 0x8a, 0xc7, 0xa0, 0x18, 0xe6, + 0x7c, 0xef, 0x63, 0x0d, 0xd5, 0xec, 0xa6, 0xff, 0xf5, 0xad, 0xe2, 0x2d, 0x64, 0xc8, 0x21, 0x67, + 0x64, 0xad, 0xe3, 0x74, 0xdd, 0x60, 0x9d, 0x2a, 0x35, 0x62, 0x46, 0x36, 0x6d, 0x38, 0x23, 0x8f, + 0xac, 0xcc, 0xa0, 0x18, 0x31, 0xfc, 0x1e, 0x35, 0x8c, 0x4c, 0x19, 0x4b, 0xc9, 0x7a, 0xd5, 0xfe, + 0xd5, 0xf5, 0xcd, 0xee, 0xc1, 0xdf, 0xb5, 0xef, 0x31, 0x96, 0x06, 0xe6, 0x14, 0xa6, 0xc0, 0x01, + 0x72, 0x45, 0x39, 0x0b, 0xb3, 0x70, 0x06, 0x73, 0x52, 0x5b, 0x89, 0x79, 0x56, 0xce, 0xce, 0xdf, + 0xc2, 0x3c, 0xa8, 0x0b, 0x5b, 0x18, 0xa6, 0x39, 0x94, 0x65, 0xd6, 0x57, 0x62, 0xf6, 0x94, 0xb2, + 0x4c, 0x6a, 0x8b, 0xbb, 0x8b, 0x34, 0xc4, 0xc6, 0xaa, 0x17, 0x69, 0x80, 0xe6, 0xe7, 0x36, 0x3c, + 0x82, 0x1a, 0x93, 0x30, 0x12, 0x3a, 0xcc, 0x15, 0x71, 0x3b, 0x4e, 0xb7, 0x19, 0xd4, 0x26, 0x7d, + 0xa1, 0x2f, 0x15, 0x7e, 0x8e, 0x90, 0x75, 0x98, 0x2c, 0x05, 0x41, 0x95, 0xd7, 0x30, 0xde, 0x40, + 0x96, 0x02, 0xef, 0xa3, 0xa7, 0x8c, 0x67, 0x74, 0x9c, 0x40, 0x68, 0x53, 0xd1, 0x14, 0xa2, 0x19, + 0x79, 0xdc, 0x71, 0xba, 0x8d, 0x60, 0x73, 0x61, 0x9d, 0xf6, 0x85, 0xee, 0x1b, 0x1d, 0xbf, 0x40, + 0x9b, 0x79, 0x06, 0xd9, 0xe1, 0x41, 0x38, 0xe6, 0xda, 0xae, 0x20, 0x1b, 0x55, 0xb6, 0x69, 0xf5, + 0x13, 0xae, 0x4d, 0x1a, 0x1f, 0xa3, 0x6d, 0x1a, 0x69, 0x5e, 0x50, 0xcd, 0xa5, 0x08, 0x23, 0x29, + 0x32, 0x9d, 0x52, 0x2e, 0x74, 0x46, 0x9a, 0xd5, 0x3f, 0x60, 0xeb, 0xde, 0xed, 0xdf, 0x9b, 0x78, + 0x07, 0xb9, 0x09, 0xcd, 0x74, 0x98, 0x01, 0x08, 0xb2, 0xd5, 0x71, 0xba, 0x6b, 0x41, 0xc3, 0x08, + 0xe7, 0x00, 0xe2, 0xe0, 0x8b, 0x83, 0x9a, 0xf6, 0x1d, 0xbc, 0xa3, 0x82, 0xc6, 0x90, 0xe2, 0xd7, + 0xc8, 0x7d, 0x03, 0x7a, 0xf1, 0x36, 0x9e, 0x79, 0x8b, 0x89, 0xe1, 0x2d, 0xbf, 0xf0, 0xd6, 0x93, + 0x25, 0x0b, 0x1f, 0x21, 0xf7, 0xfc, 0xd7, 0xc2, 0x65, 0xb7, 0xb5, 0xed, 0xd9, 0x91, 0xe3, 0xdd, + 0x0d, 0x13, 0x6f, 0x68, 0x46, 0x0e, 0xee, 0xa1, 0x8d, 0x01, 0x24, 0xa0, 0xe1, 0xe1, 0x8e, 0x7f, + 0x40, 0x9c, 0x9c, 0x7e, 0xbd, 0x6d, 0x3b, 0xdf, 0x6e, 0xdb, 0xce, 0xf7, 0xdb, 0xb6, 0xf3, 0xe9, + 0x47, 0xfb, 0xbf, 0x0f, 0x47, 0xab, 0x8c, 0xca, 0x71, 0xad, 0x52, 0x0e, 0x7f, 0x06, 0x00, 0x00, + 0xff, 0xff, 0x5e, 0xdc, 0x9f, 0x7c, 0x69, 0x05, 0x00, 0x00, +} diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto new file mode 100644 index 000000000..18d686525 --- /dev/null +++ b/api/protocol/lorawan/device.proto @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +package lorawan; + +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; + +message DeviceIdentifier { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + // The DevEUI is a unique, 8 byte identifier for the device. + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; +} + +message Device { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + // The DevEUI is a unique, 8 byte identifier for the device. + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + // The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. + string app_id = 3; + // The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. + string dev_id = 4; + // The DevAddr is a dynamic, 4 byte session address for the device. + bytes dev_addr = 5 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + // The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. + bytes nwk_s_key = 6 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + // The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. + bytes app_s_key = 7 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppSKey"]; + // The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). + bytes app_key = 8 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppKey"]; + // FCntUp is the uplink frame counter for a device session. + uint32 f_cnt_up = 9; + // FCntDown is the downlink frame counter for a device session. + uint32 f_cnt_down = 10; + + // The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. + bool disable_f_cnt_check = 11; + // The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. + bool uses32_bit_f_cnt = 12; + // The ActivationContstraints are used to allocate a device address for a device. + // There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. + string activation_constraints = 13; + + // When the device was last seen (Unix nanoseconds) + int64 last_seen = 21; +} + +service DeviceManager { + rpc GetDevice(DeviceIdentifier) returns (Device); + rpc SetDevice(Device) returns (google.protobuf.Empty); + rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty); +} diff --git a/api/protocol/lorawan/device_address.pb.go b/api/protocol/lorawan/device_address.pb.go new file mode 100644 index 000000000..738aed402 --- /dev/null +++ b/api/protocol/lorawan/device_address.pb.go @@ -0,0 +1,990 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto +// DO NOT EDIT! + +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto + + It has these top-level messages: + PrefixesRequest + PrefixesResponse + DevAddrRequest + DevAddrResponse +*/ +package lorawan + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type PrefixesRequest struct { +} + +func (m *PrefixesRequest) Reset() { *m = PrefixesRequest{} } +func (m *PrefixesRequest) String() string { return proto.CompactTextString(m) } +func (*PrefixesRequest) ProtoMessage() {} +func (*PrefixesRequest) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{0} } + +type PrefixesResponse struct { + Prefixes []*PrefixesResponse_PrefixMapping `protobuf:"bytes,1,rep,name=prefixes" json:"prefixes,omitempty"` +} + +func (m *PrefixesResponse) Reset() { *m = PrefixesResponse{} } +func (m *PrefixesResponse) String() string { return proto.CompactTextString(m) } +func (*PrefixesResponse) ProtoMessage() {} +func (*PrefixesResponse) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{1} } + +func (m *PrefixesResponse) GetPrefixes() []*PrefixesResponse_PrefixMapping { + if m != nil { + return m.Prefixes + } + return nil +} + +type PrefixesResponse_PrefixMapping struct { + Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` + Usage []string `protobuf:"bytes,2,rep,name=usage" json:"usage,omitempty"` +} + +func (m *PrefixesResponse_PrefixMapping) Reset() { *m = PrefixesResponse_PrefixMapping{} } +func (m *PrefixesResponse_PrefixMapping) String() string { return proto.CompactTextString(m) } +func (*PrefixesResponse_PrefixMapping) ProtoMessage() {} +func (*PrefixesResponse_PrefixMapping) Descriptor() ([]byte, []int) { + return fileDescriptorDeviceAddress, []int{1, 0} +} + +type DevAddrRequest struct { + Usage []string `protobuf:"bytes,1,rep,name=usage" json:"usage,omitempty"` +} + +func (m *DevAddrRequest) Reset() { *m = DevAddrRequest{} } +func (m *DevAddrRequest) String() string { return proto.CompactTextString(m) } +func (*DevAddrRequest) ProtoMessage() {} +func (*DevAddrRequest) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{2} } + +type DevAddrResponse struct { + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` +} + +func (m *DevAddrResponse) Reset() { *m = DevAddrResponse{} } +func (m *DevAddrResponse) String() string { return proto.CompactTextString(m) } +func (*DevAddrResponse) ProtoMessage() {} +func (*DevAddrResponse) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{3} } + +func init() { + proto.RegisterType((*PrefixesRequest)(nil), "lorawan.PrefixesRequest") + proto.RegisterType((*PrefixesResponse)(nil), "lorawan.PrefixesResponse") + proto.RegisterType((*PrefixesResponse_PrefixMapping)(nil), "lorawan.PrefixesResponse.PrefixMapping") + proto.RegisterType((*DevAddrRequest)(nil), "lorawan.DevAddrRequest") + proto.RegisterType((*DevAddrResponse)(nil), "lorawan.DevAddrResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for DevAddrManager service + +type DevAddrManagerClient interface { + GetPrefixes(ctx context.Context, in *PrefixesRequest, opts ...grpc.CallOption) (*PrefixesResponse, error) + GetDevAddr(ctx context.Context, in *DevAddrRequest, opts ...grpc.CallOption) (*DevAddrResponse, error) +} + +type devAddrManagerClient struct { + cc *grpc.ClientConn +} + +func NewDevAddrManagerClient(cc *grpc.ClientConn) DevAddrManagerClient { + return &devAddrManagerClient{cc} +} + +func (c *devAddrManagerClient) GetPrefixes(ctx context.Context, in *PrefixesRequest, opts ...grpc.CallOption) (*PrefixesResponse, error) { + out := new(PrefixesResponse) + err := grpc.Invoke(ctx, "/lorawan.DevAddrManager/GetPrefixes", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *devAddrManagerClient) GetDevAddr(ctx context.Context, in *DevAddrRequest, opts ...grpc.CallOption) (*DevAddrResponse, error) { + out := new(DevAddrResponse) + err := grpc.Invoke(ctx, "/lorawan.DevAddrManager/GetDevAddr", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for DevAddrManager service + +type DevAddrManagerServer interface { + GetPrefixes(context.Context, *PrefixesRequest) (*PrefixesResponse, error) + GetDevAddr(context.Context, *DevAddrRequest) (*DevAddrResponse, error) +} + +func RegisterDevAddrManagerServer(s *grpc.Server, srv DevAddrManagerServer) { + s.RegisterService(&_DevAddrManager_serviceDesc, srv) +} + +func _DevAddrManager_GetPrefixes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PrefixesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DevAddrManagerServer).GetPrefixes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DevAddrManager/GetPrefixes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DevAddrManagerServer).GetPrefixes(ctx, req.(*PrefixesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DevAddrManager_GetDevAddr_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DevAddrRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DevAddrManagerServer).GetDevAddr(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DevAddrManager/GetDevAddr", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DevAddrManagerServer).GetDevAddr(ctx, req.(*DevAddrRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _DevAddrManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lorawan.DevAddrManager", + HandlerType: (*DevAddrManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetPrefixes", + Handler: _DevAddrManager_GetPrefixes_Handler, + }, + { + MethodName: "GetDevAddr", + Handler: _DevAddrManager_GetDevAddr_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto", +} + +func (m *PrefixesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PrefixesRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *PrefixesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PrefixesResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Prefixes) > 0 { + for _, msg := range m.Prefixes { + dAtA[i] = 0xa + i++ + i = encodeVarintDeviceAddress(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *PrefixesResponse_PrefixMapping) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PrefixesResponse_PrefixMapping) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Prefix) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintDeviceAddress(dAtA, i, uint64(len(m.Prefix))) + i += copy(dAtA[i:], m.Prefix) + } + if len(m.Usage) > 0 { + for _, s := range m.Usage { + dAtA[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + return i, nil +} + +func (m *DevAddrRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DevAddrRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Usage) > 0 { + for _, s := range m.Usage { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + return i, nil +} + +func (m *DevAddrResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DevAddrResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevAddr != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintDeviceAddress(dAtA, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + return i, nil +} + +func encodeFixed64DeviceAddress(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32DeviceAddress(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintDeviceAddress(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *PrefixesRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *PrefixesResponse) Size() (n int) { + var l int + _ = l + if len(m.Prefixes) > 0 { + for _, e := range m.Prefixes { + l = e.Size() + n += 1 + l + sovDeviceAddress(uint64(l)) + } + } + return n +} + +func (m *PrefixesResponse_PrefixMapping) Size() (n int) { + var l int + _ = l + l = len(m.Prefix) + if l > 0 { + n += 1 + l + sovDeviceAddress(uint64(l)) + } + if len(m.Usage) > 0 { + for _, s := range m.Usage { + l = len(s) + n += 1 + l + sovDeviceAddress(uint64(l)) + } + } + return n +} + +func (m *DevAddrRequest) Size() (n int) { + var l int + _ = l + if len(m.Usage) > 0 { + for _, s := range m.Usage { + l = len(s) + n += 1 + l + sovDeviceAddress(uint64(l)) + } + } + return n +} + +func (m *DevAddrResponse) Size() (n int) { + var l int + _ = l + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovDeviceAddress(uint64(l)) + } + return n +} + +func sovDeviceAddress(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozDeviceAddress(x uint64) (n int) { + return sovDeviceAddress(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *PrefixesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrefixesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrefixesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PrefixesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrefixesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrefixesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prefixes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prefixes = append(m.Prefixes, &PrefixesResponse_PrefixMapping{}) + if err := m.Prefixes[len(m.Prefixes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PrefixesResponse_PrefixMapping) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrefixMapping: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrefixMapping: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prefix", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prefix = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Usage = append(m.Usage, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevAddrRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevAddrRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevAddrRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Usage = append(m.Usage, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevAddrResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevAddrResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevAddrResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDeviceAddress(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthDeviceAddress + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipDeviceAddress(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthDeviceAddress = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDeviceAddress = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto", fileDescriptorDeviceAddress) +} + +var fileDescriptorDeviceAddress = []byte{ + // 369 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x52, 0x4d, 0x4f, 0xdb, 0x40, + 0x14, 0xec, 0x36, 0x6a, 0x3e, 0x36, 0x6d, 0xd3, 0xae, 0xaa, 0xd6, 0xf5, 0xc1, 0x8d, 0x72, 0x68, + 0x73, 0xa9, 0x2d, 0x05, 0xc4, 0x0d, 0x21, 0x02, 0x22, 0xe2, 0x10, 0x04, 0x56, 0x4e, 0x5c, 0xd0, + 0xc6, 0xfb, 0xe2, 0x58, 0x04, 0xaf, 0xd9, 0x5d, 0x27, 0xf0, 0x43, 0x40, 0xfc, 0x24, 0x8e, 0x9c, + 0x39, 0x20, 0x14, 0xfe, 0x08, 0xd2, 0x7a, 0xe3, 0x24, 0x7c, 0x08, 0x89, 0xdb, 0xbe, 0x99, 0x79, + 0xf3, 0xc6, 0x23, 0xe3, 0xdd, 0x30, 0x52, 0xc3, 0xb4, 0xef, 0x06, 0xfc, 0xc4, 0xeb, 0x0d, 0xa1, + 0x37, 0x8c, 0xe2, 0x50, 0xee, 0x81, 0x9a, 0x70, 0x71, 0xec, 0x29, 0x15, 0x7b, 0x34, 0x89, 0xbc, + 0x44, 0x70, 0xc5, 0x03, 0x3e, 0xf2, 0x46, 0x5c, 0xd0, 0x09, 0x8d, 0x3d, 0x06, 0xe3, 0x28, 0x80, + 0x23, 0xca, 0x98, 0x00, 0x29, 0x5d, 0xcd, 0x93, 0x92, 0x61, 0xed, 0xff, 0x0b, 0x9e, 0x21, 0x0f, + 0x79, 0xb6, 0xdf, 0x4f, 0x07, 0x7a, 0xd2, 0x83, 0x7e, 0x65, 0x7b, 0x8d, 0xef, 0xb8, 0xb6, 0x2f, + 0x60, 0x10, 0x9d, 0x81, 0xf4, 0xe1, 0x34, 0x05, 0xa9, 0x1a, 0x97, 0x08, 0x7f, 0x9b, 0x63, 0x32, + 0xe1, 0xb1, 0x04, 0xb2, 0x85, 0xcb, 0x89, 0xc1, 0x2c, 0x54, 0x2f, 0x34, 0xab, 0xad, 0x7f, 0xae, + 0x39, 0xe9, 0x3e, 0x15, 0x1b, 0xa0, 0x4b, 0x93, 0x24, 0x8a, 0x43, 0x3f, 0x5f, 0xb4, 0xd7, 0xf1, + 0x97, 0x25, 0x8a, 0xfc, 0xc4, 0xc5, 0x8c, 0xb4, 0x50, 0x1d, 0x35, 0x2b, 0xbe, 0x99, 0xc8, 0x0f, + 0xfc, 0x29, 0x95, 0x34, 0x04, 0xeb, 0x63, 0xbd, 0xd0, 0xac, 0xf8, 0xd9, 0xd0, 0xf8, 0x8b, 0xbf, + 0x6e, 0xc3, 0x78, 0x93, 0x31, 0x61, 0xa2, 0xce, 0x75, 0x68, 0x51, 0xc7, 0x70, 0x2d, 0xd7, 0x99, + 0xf8, 0x07, 0xb8, 0xcc, 0x60, 0xac, 0x3b, 0xd3, 0xa7, 0x3e, 0xb7, 0xd7, 0x6e, 0xef, 0xfe, 0xb4, + 0xde, 0xea, 0x3f, 0xe0, 0x02, 0x3c, 0x75, 0x9e, 0x80, 0x74, 0x67, 0x8e, 0x25, 0x96, 0x3d, 0x5a, + 0x17, 0x28, 0x8f, 0xd3, 0xa5, 0x31, 0x0d, 0x41, 0x90, 0x36, 0xae, 0x76, 0x40, 0xcd, 0xea, 0x20, + 0xd6, 0x0b, 0x0d, 0xe9, 0xdc, 0xf6, 0xef, 0x57, 0xbb, 0x23, 0x1b, 0x18, 0x77, 0x40, 0x19, 0x63, + 0xf2, 0x2b, 0x17, 0x2e, 0x7f, 0xb9, 0x6d, 0x3d, 0x27, 0x32, 0x83, 0xf6, 0xce, 0xf5, 0xd4, 0x41, + 0x37, 0x53, 0x07, 0xdd, 0x4f, 0x1d, 0x74, 0xf5, 0xe0, 0x7c, 0x38, 0x5c, 0x7d, 0xcf, 0x6f, 0xd6, + 0x2f, 0x6a, 0x64, 0xe5, 0x31, 0x00, 0x00, 0xff, 0xff, 0x04, 0x47, 0x60, 0xc6, 0xa5, 0x02, 0x00, + 0x00, +} diff --git a/api/protocol/lorawan/device_address.proto b/api/protocol/lorawan/device_address.proto new file mode 100644 index 000000000..7de5a3961 --- /dev/null +++ b/api/protocol/lorawan/device_address.proto @@ -0,0 +1,34 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +package lorawan; + +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; + +message PrefixesRequest {} + +message PrefixesResponse { + message PrefixMapping { + string prefix = 1; + repeated string usage = 2; + } + + repeated PrefixMapping prefixes = 1; +} + +message DevAddrRequest { + repeated string usage = 1; +} + +message DevAddrResponse { + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; +} + +service DevAddrManager { + rpc GetPrefixes(PrefixesRequest) returns (PrefixesResponse); + rpc GetDevAddr(DevAddrRequest) returns (DevAddrResponse); +} diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go new file mode 100644 index 000000000..0c0cb9606 --- /dev/null +++ b/api/protocol/lorawan/lorawan.pb.go @@ -0,0 +1,3630 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto +// DO NOT EDIT! + +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto + + It has these top-level messages: + Metadata + TxConfiguration + ActivationMetadata + Message + MHDR + MACPayload + FHDR + FCtrl + MACCommand + JoinRequestPayload + JoinAcceptPayload + DLSettings + CFList +*/ +package lorawan + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Modulation int32 + +const ( + Modulation_LORA Modulation = 0 + Modulation_FSK Modulation = 1 +) + +var Modulation_name = map[int32]string{ + 0: "LORA", + 1: "FSK", +} +var Modulation_value = map[string]int32{ + "LORA": 0, + "FSK": 1, +} + +func (x Modulation) String() string { + return proto.EnumName(Modulation_name, int32(x)) +} +func (Modulation) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } + +type Region int32 + +const ( + Region_EU_863_870 Region = 0 + Region_US_902_928 Region = 1 + Region_CN_779_787 Region = 2 + Region_EU_433 Region = 3 + Region_AU_915_928 Region = 4 + Region_CN_470_510 Region = 5 + Region_AS_923 Region = 6 + Region_KR_920_923 Region = 7 +) + +var Region_name = map[int32]string{ + 0: "EU_863_870", + 1: "US_902_928", + 2: "CN_779_787", + 3: "EU_433", + 4: "AU_915_928", + 5: "CN_470_510", + 6: "AS_923", + 7: "KR_920_923", +} +var Region_value = map[string]int32{ + "EU_863_870": 0, + "US_902_928": 1, + "CN_779_787": 2, + "EU_433": 3, + "AU_915_928": 4, + "CN_470_510": 5, + "AS_923": 6, + "KR_920_923": 7, +} + +func (x Region) String() string { + return proto.EnumName(Region_name, int32(x)) +} +func (Region) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } + +type Major int32 + +const ( + Major_LORAWAN_R1 Major = 0 +) + +var Major_name = map[int32]string{ + 0: "LORAWAN_R1", +} +var Major_value = map[string]int32{ + "LORAWAN_R1": 0, +} + +func (x Major) String() string { + return proto.EnumName(Major_name, int32(x)) +} +func (Major) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } + +type MType int32 + +const ( + MType_JOIN_REQUEST MType = 0 + MType_JOIN_ACCEPT MType = 1 + MType_UNCONFIRMED_UP MType = 2 + MType_UNCONFIRMED_DOWN MType = 3 + MType_CONFIRMED_UP MType = 4 + MType_CONFIRMED_DOWN MType = 5 +) + +var MType_name = map[int32]string{ + 0: "JOIN_REQUEST", + 1: "JOIN_ACCEPT", + 2: "UNCONFIRMED_UP", + 3: "UNCONFIRMED_DOWN", + 4: "CONFIRMED_UP", + 5: "CONFIRMED_DOWN", +} +var MType_value = map[string]int32{ + "JOIN_REQUEST": 0, + "JOIN_ACCEPT": 1, + "UNCONFIRMED_UP": 2, + "UNCONFIRMED_DOWN": 3, + "CONFIRMED_UP": 4, + "CONFIRMED_DOWN": 5, +} + +func (x MType) String() string { + return proto.EnumName(MType_name, int32(x)) +} +func (MType) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } + +type Metadata struct { + Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` + DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` + BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` + CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` + FCnt uint32 `protobuf:"varint,15,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } + +type TxConfiguration struct { + Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` + DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` + BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` + CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` + FCnt uint32 `protobuf:"varint,15,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } + +type ActivationMetadata struct { + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,3,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,4,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + Rx1DrOffset uint32 `protobuf:"varint,11,opt,name=rx1_dr_offset,json=rx1DrOffset,proto3" json:"rx1_dr_offset,omitempty"` + Rx2Dr uint32 `protobuf:"varint,12,opt,name=rx2_dr,json=rx2Dr,proto3" json:"rx2_dr,omitempty"` + RxDelay uint32 `protobuf:"varint,13,opt,name=rx_delay,json=rxDelay,proto3" json:"rx_delay,omitempty"` + CfList *CFList `protobuf:"bytes,14,opt,name=cf_list,json=cfList" json:"cf_list,omitempty"` +} + +func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } +func (m *ActivationMetadata) String() string { return proto.CompactTextString(m) } +func (*ActivationMetadata) ProtoMessage() {} +func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } + +func (m *ActivationMetadata) GetCfList() *CFList { + if m != nil { + return m.CfList + } + return nil +} + +type Message struct { + MHDR `protobuf:"bytes,1,opt,name=m_hdr,json=mHdr,embedded=m_hdr" json:"m_hdr"` + Mic []byte `protobuf:"bytes,2,opt,name=mic,proto3" json:"mic,omitempty"` + // Types that are valid to be assigned to Payload: + // *Message_MacPayload + // *Message_JoinRequestPayload + // *Message_JoinAcceptPayload + Payload isMessage_Payload `protobuf_oneof:"Payload"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } + +type isMessage_Payload interface { + isMessage_Payload() + MarshalTo([]byte) (int, error) + Size() int +} + +type Message_MacPayload struct { + MacPayload *MACPayload `protobuf:"bytes,3,opt,name=mac_payload,json=macPayload,oneof"` +} +type Message_JoinRequestPayload struct { + JoinRequestPayload *JoinRequestPayload `protobuf:"bytes,4,opt,name=join_request_payload,json=joinRequestPayload,oneof"` +} +type Message_JoinAcceptPayload struct { + JoinAcceptPayload *JoinAcceptPayload `protobuf:"bytes,5,opt,name=join_accept_payload,json=joinAcceptPayload,oneof"` +} + +func (*Message_MacPayload) isMessage_Payload() {} +func (*Message_JoinRequestPayload) isMessage_Payload() {} +func (*Message_JoinAcceptPayload) isMessage_Payload() {} + +func (m *Message) GetPayload() isMessage_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (m *Message) GetMacPayload() *MACPayload { + if x, ok := m.GetPayload().(*Message_MacPayload); ok { + return x.MacPayload + } + return nil +} + +func (m *Message) GetJoinRequestPayload() *JoinRequestPayload { + if x, ok := m.GetPayload().(*Message_JoinRequestPayload); ok { + return x.JoinRequestPayload + } + return nil +} + +func (m *Message) GetJoinAcceptPayload() *JoinAcceptPayload { + if x, ok := m.GetPayload().(*Message_JoinAcceptPayload); ok { + return x.JoinAcceptPayload + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Message) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Message_OneofMarshaler, _Message_OneofUnmarshaler, _Message_OneofSizer, []interface{}{ + (*Message_MacPayload)(nil), + (*Message_JoinRequestPayload)(nil), + (*Message_JoinAcceptPayload)(nil), + } +} + +func _Message_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Message) + // Payload + switch x := m.Payload.(type) { + case *Message_MacPayload: + _ = b.EncodeVarint(3<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.MacPayload); err != nil { + return err + } + case *Message_JoinRequestPayload: + _ = b.EncodeVarint(4<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.JoinRequestPayload); err != nil { + return err + } + case *Message_JoinAcceptPayload: + _ = b.EncodeVarint(5<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.JoinAcceptPayload); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Message.Payload has unexpected type %T", x) + } + return nil +} + +func _Message_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Message) + switch tag { + case 3: // Payload.mac_payload + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(MACPayload) + err := b.DecodeMessage(msg) + m.Payload = &Message_MacPayload{msg} + return true, err + case 4: // Payload.join_request_payload + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(JoinRequestPayload) + err := b.DecodeMessage(msg) + m.Payload = &Message_JoinRequestPayload{msg} + return true, err + case 5: // Payload.join_accept_payload + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(JoinAcceptPayload) + err := b.DecodeMessage(msg) + m.Payload = &Message_JoinAcceptPayload{msg} + return true, err + default: + return false, nil + } +} + +func _Message_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Message) + // Payload + switch x := m.Payload.(type) { + case *Message_MacPayload: + s := proto.Size(x.MacPayload) + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Message_JoinRequestPayload: + s := proto.Size(x.JoinRequestPayload) + n += proto.SizeVarint(4<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Message_JoinAcceptPayload: + s := proto.Size(x.JoinAcceptPayload) + n += proto.SizeVarint(5<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type MHDR struct { + MType MType `protobuf:"varint,1,opt,name=m_type,json=mType,proto3,enum=lorawan.MType" json:"m_type,omitempty"` + Major Major `protobuf:"varint,2,opt,name=major,proto3,enum=lorawan.Major" json:"major,omitempty"` +} + +func (m *MHDR) Reset() { *m = MHDR{} } +func (m *MHDR) String() string { return proto.CompactTextString(m) } +func (*MHDR) ProtoMessage() {} +func (*MHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{4} } + +type MACPayload struct { + FHDR `protobuf:"bytes,1,opt,name=f_hdr,json=fHdr,embedded=f_hdr" json:"f_hdr"` + FPort int32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"` + FrmPayload []byte `protobuf:"bytes,3,opt,name=frm_payload,json=frmPayload,proto3" json:"frm_payload,omitempty"` +} + +func (m *MACPayload) Reset() { *m = MACPayload{} } +func (m *MACPayload) String() string { return proto.CompactTextString(m) } +func (*MACPayload) ProtoMessage() {} +func (*MACPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } + +type FHDR struct { + DevAddr github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr"` + FCtrl `protobuf:"bytes,2,opt,name=f_ctrl,json=fCtrl,embedded=f_ctrl" json:"f_ctrl"` + FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + FOpts []MACCommand `protobuf:"bytes,4,rep,name=f_opts,json=fOpts" json:"f_opts"` +} + +func (m *FHDR) Reset() { *m = FHDR{} } +func (m *FHDR) String() string { return proto.CompactTextString(m) } +func (*FHDR) ProtoMessage() {} +func (*FHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } + +func (m *FHDR) GetFOpts() []MACCommand { + if m != nil { + return m.FOpts + } + return nil +} + +type FCtrl struct { + Adr bool `protobuf:"varint,1,opt,name=adr,proto3" json:"adr,omitempty"` + AdrAckReq bool `protobuf:"varint,2,opt,name=adr_ack_req,json=adrAckReq,proto3" json:"adr_ack_req,omitempty"` + Ack bool `protobuf:"varint,3,opt,name=ack,proto3" json:"ack,omitempty"` + FPending bool `protobuf:"varint,4,opt,name=f_pending,json=fPending,proto3" json:"f_pending,omitempty"` +} + +func (m *FCtrl) Reset() { *m = FCtrl{} } +func (m *FCtrl) String() string { return proto.CompactTextString(m) } +func (*FCtrl) ProtoMessage() {} +func (*FCtrl) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{7} } + +type MACCommand struct { + Cid uint32 `protobuf:"varint,1,opt,name=cid,proto3" json:"cid,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (m *MACCommand) Reset() { *m = MACCommand{} } +func (m *MACCommand) String() string { return proto.CompactTextString(m) } +func (*MACCommand) ProtoMessage() {} +func (*MACCommand) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{8} } + +type JoinRequestPayload struct { + AppEui github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui"` + DevEui github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui"` + DevNonce github_com_TheThingsNetwork_ttn_core_types.DevNonce `protobuf:"bytes,3,opt,name=dev_nonce,json=devNonce,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevNonce" json:"dev_nonce"` +} + +func (m *JoinRequestPayload) Reset() { *m = JoinRequestPayload{} } +func (m *JoinRequestPayload) String() string { return proto.CompactTextString(m) } +func (*JoinRequestPayload) ProtoMessage() {} +func (*JoinRequestPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{9} } + +type JoinAcceptPayload struct { + Encrypted []byte `protobuf:"bytes,1,opt,name=encrypted,proto3" json:"encrypted,omitempty"` + AppNonce github_com_TheThingsNetwork_ttn_core_types.AppNonce `protobuf:"bytes,2,opt,name=app_nonce,json=appNonce,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppNonce" json:"app_nonce"` + NetId github_com_TheThingsNetwork_ttn_core_types.NetID `protobuf:"bytes,3,opt,name=net_id,json=netId,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NetID" json:"net_id"` + DevAddr github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,4,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr"` + DLSettings `protobuf:"bytes,5,opt,name=dl_settings,json=dlSettings,embedded=dl_settings" json:"dl_settings"` + RxDelay uint32 `protobuf:"varint,6,opt,name=rx_delay,json=rxDelay,proto3" json:"rx_delay,omitempty"` + CfList *CFList `protobuf:"bytes,7,opt,name=cf_list,json=cfList" json:"cf_list,omitempty"` +} + +func (m *JoinAcceptPayload) Reset() { *m = JoinAcceptPayload{} } +func (m *JoinAcceptPayload) String() string { return proto.CompactTextString(m) } +func (*JoinAcceptPayload) ProtoMessage() {} +func (*JoinAcceptPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{10} } + +func (m *JoinAcceptPayload) GetCfList() *CFList { + if m != nil { + return m.CfList + } + return nil +} + +type DLSettings struct { + Rx1DrOffset uint32 `protobuf:"varint,1,opt,name=rx1_dr_offset,json=rx1DrOffset,proto3" json:"rx1_dr_offset,omitempty"` + Rx2Dr uint32 `protobuf:"varint,2,opt,name=rx2_dr,json=rx2Dr,proto3" json:"rx2_dr,omitempty"` +} + +func (m *DLSettings) Reset() { *m = DLSettings{} } +func (m *DLSettings) String() string { return proto.CompactTextString(m) } +func (*DLSettings) ProtoMessage() {} +func (*DLSettings) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{11} } + +type CFList struct { + Freq []uint32 `protobuf:"varint,1,rep,packed,name=freq" json:"freq,omitempty"` +} + +func (m *CFList) Reset() { *m = CFList{} } +func (m *CFList) String() string { return proto.CompactTextString(m) } +func (*CFList) ProtoMessage() {} +func (*CFList) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{12} } + +func init() { + proto.RegisterType((*Metadata)(nil), "lorawan.Metadata") + proto.RegisterType((*TxConfiguration)(nil), "lorawan.TxConfiguration") + proto.RegisterType((*ActivationMetadata)(nil), "lorawan.ActivationMetadata") + proto.RegisterType((*Message)(nil), "lorawan.Message") + proto.RegisterType((*MHDR)(nil), "lorawan.MHDR") + proto.RegisterType((*MACPayload)(nil), "lorawan.MACPayload") + proto.RegisterType((*FHDR)(nil), "lorawan.FHDR") + proto.RegisterType((*FCtrl)(nil), "lorawan.FCtrl") + proto.RegisterType((*MACCommand)(nil), "lorawan.MACCommand") + proto.RegisterType((*JoinRequestPayload)(nil), "lorawan.JoinRequestPayload") + proto.RegisterType((*JoinAcceptPayload)(nil), "lorawan.JoinAcceptPayload") + proto.RegisterType((*DLSettings)(nil), "lorawan.DLSettings") + proto.RegisterType((*CFList)(nil), "lorawan.CFList") + proto.RegisterEnum("lorawan.Modulation", Modulation_name, Modulation_value) + proto.RegisterEnum("lorawan.Region", Region_name, Region_value) + proto.RegisterEnum("lorawan.Major", Major_name, Major_value) + proto.RegisterEnum("lorawan.MType", MType_name, MType_value) +} +func (m *Metadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Metadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Modulation != 0 { + dAtA[i] = 0x58 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Modulation)) + } + if len(m.DataRate) > 0 { + dAtA[i] = 0x62 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.DataRate))) + i += copy(dAtA[i:], m.DataRate) + } + if m.BitRate != 0 { + dAtA[i] = 0x68 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.BitRate)) + } + if len(m.CodingRate) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.CodingRate))) + i += copy(dAtA[i:], m.CodingRate) + } + if m.FCnt != 0 { + dAtA[i] = 0x78 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.FCnt)) + } + return i, nil +} + +func (m *TxConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TxConfiguration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Modulation != 0 { + dAtA[i] = 0x58 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Modulation)) + } + if len(m.DataRate) > 0 { + dAtA[i] = 0x62 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.DataRate))) + i += copy(dAtA[i:], m.DataRate) + } + if m.BitRate != 0 { + dAtA[i] = 0x68 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.BitRate)) + } + if len(m.CodingRate) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.CodingRate))) + i += copy(dAtA[i:], m.CodingRate) + } + if m.FCnt != 0 { + dAtA[i] = 0x78 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.FCnt)) + } + return i, nil +} + +func (m *ActivationMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ActivationMetadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEui != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.AppEui.Size())) + n1, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.DevEui != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DevEui.Size())) + n2, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.DevAddr != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DevAddr.Size())) + n3, err := m.DevAddr.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.NwkSKey != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.NwkSKey.Size())) + n4, err := m.NwkSKey.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Rx1DrOffset != 0 { + dAtA[i] = 0x58 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + dAtA[i] = 0x60 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx2Dr)) + } + if m.RxDelay != 0 { + dAtA[i] = 0x68 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.RxDelay)) + } + if m.CfList != nil { + dAtA[i] = 0x72 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.CfList.Size())) + n5, err := m.CfList.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + return i, nil +} + +func (m *Message) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.MHDR.Size())) + n6, err := m.MHDR.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + if len(m.Mic) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.Mic))) + i += copy(dAtA[i:], m.Mic) + } + if m.Payload != nil { + nn7, err := m.Payload.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn7 + } + return i, nil +} + +func (m *Message_MacPayload) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.MacPayload != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.MacPayload.Size())) + n8, err := m.MacPayload.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} +func (m *Message_JoinRequestPayload) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.JoinRequestPayload != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.JoinRequestPayload.Size())) + n9, err := m.JoinRequestPayload.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n9 + } + return i, nil +} +func (m *Message_JoinAcceptPayload) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.JoinAcceptPayload != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.JoinAcceptPayload.Size())) + n10, err := m.JoinAcceptPayload.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + } + return i, nil +} +func (m *MHDR) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MHDR) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.MType != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.MType)) + } + if m.Major != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Major)) + } + return i, nil +} + +func (m *MACPayload) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MACPayload) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.FHDR.Size())) + n11, err := m.FHDR.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 + if m.FPort != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.FPort)) + } + if len(m.FrmPayload) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.FrmPayload))) + i += copy(dAtA[i:], m.FrmPayload) + } + return i, nil +} + +func (m *FHDR) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FHDR) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DevAddr.Size())) + n12, err := m.DevAddr.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n12 + dAtA[i] = 0x12 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.FCtrl.Size())) + n13, err := m.FCtrl.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n13 + if m.FCnt != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, msg := range m.FOpts { + dAtA[i] = 0x22 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *FCtrl) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FCtrl) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Adr { + dAtA[i] = 0x8 + i++ + if m.Adr { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if m.AdrAckReq { + dAtA[i] = 0x10 + i++ + if m.AdrAckReq { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if m.Ack { + dAtA[i] = 0x18 + i++ + if m.Ack { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + if m.FPending { + dAtA[i] = 0x20 + i++ + if m.FPending { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + return i, nil +} + +func (m *MACCommand) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MACCommand) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Cid != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Cid)) + } + if len(m.Payload) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + return i, nil +} + +func (m *JoinRequestPayload) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *JoinRequestPayload) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.AppEui.Size())) + n14, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n14 + dAtA[i] = 0x12 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DevEui.Size())) + n15, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n15 + dAtA[i] = 0x1a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DevNonce.Size())) + n16, err := m.DevNonce.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n16 + return i, nil +} + +func (m *JoinAcceptPayload) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *JoinAcceptPayload) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Encrypted) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(len(m.Encrypted))) + i += copy(dAtA[i:], m.Encrypted) + } + dAtA[i] = 0x12 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.AppNonce.Size())) + n17, err := m.AppNonce.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n17 + dAtA[i] = 0x1a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.NetId.Size())) + n18, err := m.NetId.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n18 + dAtA[i] = 0x22 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DevAddr.Size())) + n19, err := m.DevAddr.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n19 + dAtA[i] = 0x2a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.DLSettings.Size())) + n20, err := m.DLSettings.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n20 + if m.RxDelay != 0 { + dAtA[i] = 0x30 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.RxDelay)) + } + if m.CfList != nil { + dAtA[i] = 0x3a + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.CfList.Size())) + n21, err := m.CfList.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n21 + } + return i, nil +} + +func (m *DLSettings) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DLSettings) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Rx1DrOffset != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx2Dr)) + } + return i, nil +} + +func (m *CFList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CFList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Freq) > 0 { + dAtA23 := make([]byte, len(m.Freq)*10) + var j22 int + for _, num := range m.Freq { + for num >= 1<<7 { + dAtA23[j22] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j22++ + } + dAtA23[j22] = uint8(num) + j22++ + } + dAtA[i] = 0xa + i++ + i = encodeVarintLorawan(dAtA, i, uint64(j22)) + i += copy(dAtA[i:], dAtA23[:j22]) + } + return i, nil +} + +func encodeFixed64Lorawan(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Lorawan(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintLorawan(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Metadata) Size() (n int) { + var l int + _ = l + if m.Modulation != 0 { + n += 1 + sovLorawan(uint64(m.Modulation)) + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.BitRate != 0 { + n += 1 + sovLorawan(uint64(m.BitRate)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + return n +} + +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Modulation != 0 { + n += 1 + sovLorawan(uint64(m.Modulation)) + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.BitRate != 0 { + n += 1 + sovLorawan(uint64(m.BitRate)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + return n +} + +func (m *ActivationMetadata) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.NwkSKey != nil { + l = m.NwkSKey.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.Rx1DrOffset != 0 { + n += 1 + sovLorawan(uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + n += 1 + sovLorawan(uint64(m.Rx2Dr)) + } + if m.RxDelay != 0 { + n += 1 + sovLorawan(uint64(m.RxDelay)) + } + if m.CfList != nil { + l = m.CfList.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *Message) Size() (n int) { + var l int + _ = l + l = m.MHDR.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = len(m.Mic) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.Payload != nil { + n += m.Payload.Size() + } + return n +} + +func (m *Message_MacPayload) Size() (n int) { + var l int + _ = l + if m.MacPayload != nil { + l = m.MacPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} +func (m *Message_JoinRequestPayload) Size() (n int) { + var l int + _ = l + if m.JoinRequestPayload != nil { + l = m.JoinRequestPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} +func (m *Message_JoinAcceptPayload) Size() (n int) { + var l int + _ = l + if m.JoinAcceptPayload != nil { + l = m.JoinAcceptPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} +func (m *MHDR) Size() (n int) { + var l int + _ = l + if m.MType != 0 { + n += 1 + sovLorawan(uint64(m.MType)) + } + if m.Major != 0 { + n += 1 + sovLorawan(uint64(m.Major)) + } + return n +} + +func (m *MACPayload) Size() (n int) { + var l int + _ = l + l = m.FHDR.Size() + n += 1 + l + sovLorawan(uint64(l)) + if m.FPort != 0 { + n += 1 + sovLorawan(uint64(m.FPort)) + } + l = len(m.FrmPayload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *FHDR) Size() (n int) { + var l int + _ = l + l = m.DevAddr.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.FCtrl.Size() + n += 1 + l + sovLorawan(uint64(l)) + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, e := range m.FOpts { + l = e.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *FCtrl) Size() (n int) { + var l int + _ = l + if m.Adr { + n += 2 + } + if m.AdrAckReq { + n += 2 + } + if m.Ack { + n += 2 + } + if m.FPending { + n += 2 + } + return n +} + +func (m *MACCommand) Size() (n int) { + var l int + _ = l + if m.Cid != 0 { + n += 1 + sovLorawan(uint64(m.Cid)) + } + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *JoinRequestPayload) Size() (n int) { + var l int + _ = l + l = m.AppEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DevEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DevNonce.Size() + n += 1 + l + sovLorawan(uint64(l)) + return n +} + +func (m *JoinAcceptPayload) Size() (n int) { + var l int + _ = l + l = len(m.Encrypted) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + l = m.AppNonce.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.NetId.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DevAddr.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DLSettings.Size() + n += 1 + l + sovLorawan(uint64(l)) + if m.RxDelay != 0 { + n += 1 + sovLorawan(uint64(m.RxDelay)) + } + if m.CfList != nil { + l = m.CfList.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *DLSettings) Size() (n int) { + var l int + _ = l + if m.Rx1DrOffset != 0 { + n += 1 + sovLorawan(uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + n += 1 + sovLorawan(uint64(m.Rx2Dr)) + } + return n +} + +func (m *CFList) Size() (n int) { + var l int + _ = l + if len(m.Freq) > 0 { + l = 0 + for _, e := range m.Freq { + l += sovLorawan(uint64(e)) + } + n += 1 + sovLorawan(uint64(l)) + l + } + return n +} + +func sovLorawan(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozLorawan(x uint64) (n int) { + return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Metadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + m.Modulation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Modulation |= (Modulation(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + } + m.BitRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BitRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + m.Modulation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Modulation |= (Modulation(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + } + m.BitRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BitRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey + m.NwkSKey = &v + if err := m.NwkSKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx1DrOffset", wireType) + } + m.Rx1DrOffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx2Dr", wireType) + } + m.Rx2Dr = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Rx2Dr |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxDelay", wireType) + } + m.RxDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RxDelay |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CfList == nil { + m.CfList = &CFList{} + } + if err := m.CfList.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Message) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Message: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Message: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MHDR", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MHDR.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Mic", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Mic = append(m.Mic[:0], dAtA[iNdEx:postIndex]...) + if m.Mic == nil { + m.Mic = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MacPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &MACPayload{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Payload = &Message_MacPayload{v} + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JoinRequestPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &JoinRequestPayload{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Payload = &Message_JoinRequestPayload{v} + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JoinAcceptPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &JoinAcceptPayload{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Payload = &Message_JoinAcceptPayload{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MHDR) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MHDR: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MHDR: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) + } + m.MType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MType |= (MType(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) + } + m.Major = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Major |= (Major(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MACPayload) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MACPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MACPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FHDR", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.FHDR.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) + } + m.FPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FPort |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FrmPayload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FrmPayload = append(m.FrmPayload[:0], dAtA[iNdEx:postIndex]...) + if m.FrmPayload == nil { + m.FrmPayload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FHDR) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FHDR: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FHDR: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FCtrl", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.FCtrl.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FOpts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FOpts = append(m.FOpts, MACCommand{}) + if err := m.FOpts[len(m.FOpts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FCtrl) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FCtrl: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FCtrl: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Adr", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Adr = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AdrAckReq", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.AdrAckReq = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Ack = bool(v != 0) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPending", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FPending = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MACCommand) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MACCommand: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MACCommand: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Cid", wireType) + } + m.Cid = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Cid |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinRequestPayload) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinRequestPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinRequestPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DevNonce.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinAcceptPayload) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinAcceptPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinAcceptPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encrypted", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encrypted = append(m.Encrypted[:0], dAtA[iNdEx:postIndex]...) + if m.Encrypted == nil { + m.Encrypted = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AppNonce.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NetId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DLSettings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DLSettings.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxDelay", wireType) + } + m.RxDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RxDelay |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CfList == nil { + m.CfList = &CFList{} + } + if err := m.CfList.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DLSettings) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DLSettings: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DLSettings: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx1DrOffset", wireType) + } + m.Rx1DrOffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx2Dr", wireType) + } + m.Rx2Dr = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Rx2Dr |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CFList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CFList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CFList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + packedLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + for iNdEx < postIndex { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Freq = append(m.Freq, v) + } + } else if wireType == 0 { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Freq = append(m.Freq, v) + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Freq", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLorawan(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthLorawan + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipLorawan(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthLorawan = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLorawan = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto", fileDescriptorLorawan) +} + +var fileDescriptorLorawan = []byte{ + // 1281 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4b, 0x6f, 0x1b, 0x45, + 0x1c, 0xcf, 0xda, 0x5e, 0x3f, 0xfe, 0x8e, 0x93, 0xed, 0xb4, 0x15, 0xa6, 0xad, 0x92, 0xc8, 0x02, + 0x29, 0x8a, 0x20, 0x76, 0xec, 0xb6, 0x89, 0x41, 0x42, 0xf2, 0x2b, 0x34, 0x6d, 0x62, 0xa7, 0xe3, + 0x58, 0x45, 0x5c, 0x46, 0x9b, 0xdd, 0x59, 0x67, 0x63, 0xef, 0xa3, 0xe3, 0x71, 0x62, 0x73, 0xe2, + 0x53, 0x20, 0xbe, 0x03, 0xe2, 0xc6, 0x81, 0x8f, 0xd0, 0x63, 0x2f, 0x5c, 0x8a, 0x14, 0xa1, 0xf2, + 0x45, 0xd0, 0xcc, 0xae, 0x63, 0xc7, 0x81, 0xa2, 0xa6, 0x5c, 0x38, 0xed, 0xff, 0xf9, 0x9b, 0xff, + 0xfc, 0x5f, 0xb3, 0x50, 0xed, 0xda, 0xfc, 0x64, 0x78, 0xbc, 0x69, 0x78, 0x4e, 0xfe, 0xe8, 0x84, + 0x1e, 0x9d, 0xd8, 0x6e, 0x77, 0xd0, 0xa4, 0xfc, 0xdc, 0x63, 0xbd, 0x3c, 0xe7, 0x6e, 0x5e, 0xf7, + 0xed, 0xbc, 0xcf, 0x3c, 0xee, 0x19, 0x5e, 0x3f, 0xdf, 0xf7, 0x98, 0x7e, 0xae, 0xbb, 0x93, 0xef, + 0xa6, 0x54, 0xa0, 0x44, 0xc8, 0xde, 0xfb, 0x7c, 0x06, 0xac, 0xeb, 0x75, 0xbd, 0xc0, 0xf1, 0x78, + 0x68, 0x49, 0x4e, 0x32, 0x92, 0x0a, 0xfc, 0x72, 0x3f, 0x2b, 0x90, 0x3c, 0xa0, 0x5c, 0x37, 0x75, + 0xae, 0xa3, 0x12, 0x80, 0xe3, 0x99, 0xc3, 0xbe, 0xce, 0x6d, 0xcf, 0xcd, 0xa6, 0xd7, 0x94, 0xf5, + 0xa5, 0xe2, 0xed, 0xcd, 0xc9, 0x41, 0x07, 0x97, 0x2a, 0x3c, 0x63, 0x86, 0xee, 0x43, 0x4a, 0x38, + 0x13, 0xa6, 0x73, 0x9a, 0x5d, 0x5c, 0x53, 0xd6, 0x53, 0x38, 0x29, 0x04, 0x58, 0xe7, 0x14, 0x7d, + 0x0c, 0xc9, 0x63, 0x9b, 0x07, 0xba, 0xcc, 0x9a, 0xb2, 0x9e, 0xc1, 0x89, 0x63, 0x9b, 0x4b, 0xd5, + 0x2a, 0xa4, 0x0d, 0xcf, 0xb4, 0xdd, 0x6e, 0xa0, 0x5d, 0x92, 0x9e, 0x10, 0x88, 0xa4, 0xc1, 0x6d, + 0x50, 0x2d, 0x62, 0xb8, 0x3c, 0xbb, 0x2c, 0x1d, 0x63, 0x56, 0xcd, 0xe5, 0xb9, 0x5f, 0x14, 0x58, + 0x3e, 0x1a, 0xd5, 0x3c, 0xd7, 0xb2, 0xbb, 0x43, 0x16, 0x44, 0xf0, 0x3f, 0x08, 0xfb, 0xf7, 0x28, + 0xa0, 0x8a, 0xc1, 0xed, 0x33, 0x79, 0xf8, 0x65, 0xc2, 0x9b, 0x90, 0xd0, 0x7d, 0x9f, 0xd0, 0xa1, + 0x9d, 0x55, 0xd6, 0x94, 0xf5, 0xc5, 0xea, 0xa3, 0x37, 0x17, 0xab, 0x5b, 0xff, 0xd6, 0x0e, 0x86, + 0xc7, 0x68, 0x9e, 0x8f, 0x7d, 0x3a, 0xd8, 0xac, 0xf8, 0x7e, 0xa3, 0xb3, 0x87, 0xe3, 0xba, 0xef, + 0x37, 0x86, 0xb6, 0xc0, 0x33, 0xe9, 0x99, 0xc4, 0x8b, 0xdc, 0x08, 0xaf, 0x4e, 0xcf, 0x24, 0x9e, + 0x49, 0xcf, 0x04, 0xde, 0x73, 0x48, 0x0a, 0x3c, 0xdd, 0x34, 0x59, 0x36, 0x2a, 0x01, 0x1f, 0xbf, + 0xb9, 0x58, 0x2d, 0xbe, 0x1f, 0x60, 0xc5, 0x34, 0x19, 0x16, 0x71, 0x09, 0x02, 0x61, 0x48, 0xb9, + 0xe7, 0x3d, 0x32, 0x20, 0x3d, 0x3a, 0xce, 0xc6, 0x6e, 0x84, 0xd9, 0x3c, 0xef, 0xb5, 0x9f, 0xd1, + 0x31, 0x4e, 0xb8, 0x01, 0x81, 0x72, 0x90, 0x61, 0xa3, 0x2d, 0x62, 0x32, 0xe2, 0x59, 0xd6, 0x80, + 0x72, 0xd9, 0x03, 0x19, 0x9c, 0x66, 0xa3, 0xad, 0x3a, 0x6b, 0x49, 0x11, 0xba, 0x0b, 0x71, 0x36, + 0x2a, 0x12, 0x93, 0xc9, 0x62, 0x67, 0xb0, 0xca, 0x46, 0xc5, 0x3a, 0x13, 0x95, 0x66, 0x23, 0x62, + 0xd2, 0xbe, 0x3e, 0x9e, 0x54, 0x9a, 0x8d, 0xea, 0x82, 0x45, 0xeb, 0x90, 0x30, 0x2c, 0xd2, 0xb7, + 0x07, 0x5c, 0x56, 0x39, 0x5d, 0x5c, 0xbe, 0xec, 0xa9, 0xda, 0xee, 0xbe, 0x3d, 0xe0, 0x38, 0x6e, + 0x58, 0xe2, 0x9b, 0xfb, 0x29, 0x02, 0x89, 0x03, 0x3a, 0x18, 0xe8, 0x5d, 0x8a, 0x3e, 0x03, 0xd5, + 0x21, 0x27, 0x26, 0x93, 0x05, 0x4d, 0x17, 0x33, 0xd3, 0x3e, 0x7c, 0x52, 0xc7, 0xd5, 0xe4, 0xab, + 0x8b, 0xd5, 0x85, 0xd7, 0x17, 0xab, 0x0a, 0x8e, 0x39, 0x4f, 0x4c, 0x86, 0x34, 0x88, 0x3a, 0xb6, + 0x11, 0x14, 0x0b, 0x0b, 0x12, 0x3d, 0x86, 0xb4, 0xa3, 0x1b, 0xc4, 0xd7, 0xc7, 0x7d, 0x4f, 0x37, + 0x65, 0xd6, 0xd3, 0xb3, 0xdd, 0x5c, 0xa9, 0x1d, 0x06, 0xaa, 0x27, 0x0b, 0x18, 0x1c, 0xdd, 0x08, + 0x39, 0xd4, 0x82, 0x3b, 0xa7, 0x9e, 0xed, 0x12, 0x46, 0x5f, 0x0e, 0xe9, 0x80, 0x5f, 0x02, 0xc4, + 0x24, 0xc0, 0xfd, 0x4b, 0x80, 0xa7, 0x9e, 0xed, 0xe2, 0xc0, 0x66, 0x0a, 0x84, 0x4e, 0xaf, 0x49, + 0xd1, 0x3e, 0xdc, 0x96, 0x80, 0xba, 0x61, 0x50, 0x7f, 0x8a, 0xa7, 0x4a, 0xbc, 0x7b, 0x57, 0xf0, + 0x2a, 0xd2, 0x64, 0x0a, 0x77, 0xeb, 0x74, 0x5e, 0x58, 0x4d, 0x41, 0x22, 0x24, 0x73, 0x6d, 0x88, + 0x89, 0x5c, 0xa0, 0x4f, 0x21, 0xee, 0x10, 0x51, 0x51, 0x99, 0xaa, 0xa5, 0xe2, 0xd2, 0xf4, 0x92, + 0x47, 0x63, 0x9f, 0x62, 0xd5, 0x11, 0x1f, 0xf4, 0x09, 0xa8, 0x8e, 0x7e, 0xea, 0x31, 0x99, 0xa4, + 0x2b, 0x56, 0x42, 0x8a, 0x03, 0x65, 0x8e, 0x01, 0x4c, 0x53, 0x23, 0x8a, 0x60, 0xfd, 0x6d, 0x11, + 0x76, 0xe7, 0x8a, 0x60, 0x89, 0x22, 0xdc, 0x85, 0xb8, 0x45, 0x7c, 0x8f, 0x71, 0x79, 0x84, 0x8a, + 0x55, 0xeb, 0xd0, 0x63, 0x5c, 0x4c, 0xba, 0xc5, 0x9c, 0x2b, 0x95, 0x58, 0xc4, 0x60, 0x31, 0x67, + 0x72, 0x91, 0xdf, 0x14, 0x88, 0x09, 0x40, 0xd4, 0x99, 0x19, 0x93, 0x60, 0x8e, 0xbf, 0x10, 0x47, + 0x7c, 0xe8, 0xa8, 0xe4, 0x45, 0x5c, 0x06, 0x67, 0x7d, 0x19, 0x57, 0x7a, 0xe6, 0xea, 0xbb, 0x35, + 0xce, 0xfa, 0x33, 0xf7, 0x50, 0x2d, 0x21, 0x98, 0xae, 0x9e, 0xe8, 0x74, 0xf5, 0xa0, 0x82, 0x40, + 0xf1, 0x7c, 0x3e, 0xc8, 0xc6, 0xd6, 0xa2, 0xf3, 0xbd, 0x54, 0xf3, 0x1c, 0x47, 0x77, 0xcd, 0x6a, + 0x4c, 0x40, 0x61, 0xd5, 0x6a, 0xf9, 0x7c, 0x90, 0x3b, 0x01, 0x55, 0x1e, 0x20, 0xba, 0x53, 0x0f, + 0xaf, 0x94, 0xc4, 0x82, 0x44, 0x2b, 0x90, 0xd6, 0x4d, 0x46, 0x74, 0xa3, 0x27, 0x1a, 0x4d, 0xc6, + 0x95, 0xc4, 0x29, 0xdd, 0x64, 0x15, 0xa3, 0x87, 0xe9, 0x4b, 0xe9, 0x61, 0xf4, 0xe4, 0xf9, 0xc2, + 0xc3, 0xe8, 0x89, 0x3d, 0x6b, 0x11, 0x9f, 0xba, 0x62, 0x3f, 0xca, 0x66, 0x4c, 0xe2, 0xa4, 0x75, + 0x18, 0xf0, 0xb9, 0x1d, 0x59, 0xb5, 0x30, 0x08, 0xe1, 0x6c, 0xd8, 0xa6, 0x3c, 0x2e, 0x83, 0x05, + 0x89, 0xb2, 0x90, 0x98, 0xa4, 0x3f, 0x18, 0x91, 0x09, 0x9b, 0xfb, 0x21, 0x02, 0xe8, 0x7a, 0x2b, + 0x23, 0x3c, 0xbf, 0x50, 0xcb, 0x61, 0x21, 0x3e, 0x60, 0xa9, 0xe2, 0xf9, 0xa5, 0x7a, 0x13, 0xcc, + 0xb9, 0xc5, 0xfa, 0x0d, 0xa4, 0x04, 0xa6, 0xeb, 0xb9, 0x06, 0x0d, 0x37, 0xeb, 0x97, 0x21, 0x6a, + 0xe9, 0xfd, 0x50, 0x9b, 0x02, 0x02, 0x8b, 0xfe, 0x93, 0x54, 0xee, 0xd7, 0x28, 0xdc, 0xba, 0x36, + 0x93, 0xe8, 0x01, 0xa4, 0xa8, 0x6b, 0xb0, 0xb1, 0xcf, 0x69, 0x90, 0xe0, 0x45, 0x3c, 0x15, 0x88, + 0x68, 0x44, 0xd6, 0x82, 0x68, 0x22, 0x37, 0x8e, 0xa6, 0xe2, 0xfb, 0x61, 0x34, 0x7a, 0x48, 0xa1, + 0x16, 0xc4, 0x5d, 0xca, 0x89, 0x1d, 0x8e, 0x4f, 0x75, 0x27, 0x84, 0x2d, 0xbc, 0xcf, 0xba, 0xa7, + 0x7c, 0xaf, 0x8e, 0x55, 0x97, 0xf2, 0x3d, 0xf3, 0xca, 0xa8, 0xc5, 0xfe, 0xbb, 0x51, 0xfb, 0x0a, + 0xd2, 0x66, 0x9f, 0x0c, 0x28, 0xe7, 0xc2, 0x2b, 0x5c, 0x72, 0xd3, 0x49, 0xa9, 0xef, 0xb7, 0x43, + 0xd5, 0xcc, 0xd0, 0x81, 0xd9, 0x9f, 0x48, 0xaf, 0x3c, 0x23, 0xf1, 0x7f, 0x7c, 0x46, 0x12, 0xef, + 0x7e, 0x46, 0xbe, 0x06, 0x98, 0x1e, 0x74, 0xfd, 0x51, 0x53, 0xde, 0xf5, 0xa8, 0x45, 0x66, 0x1e, + 0xb5, 0xdc, 0x03, 0x88, 0x07, 0xd0, 0x08, 0x41, 0xcc, 0x12, 0x83, 0xaa, 0xac, 0x45, 0xe5, 0x42, + 0x60, 0xf4, 0xe5, 0xc6, 0x2a, 0xc0, 0xf4, 0x9f, 0x08, 0x25, 0x21, 0xb6, 0xdf, 0xc2, 0x15, 0x6d, + 0x01, 0x25, 0x20, 0xba, 0xdb, 0x7e, 0xa6, 0x29, 0x1b, 0xdf, 0x2b, 0x10, 0xc7, 0xb4, 0x2b, 0xb4, + 0x4b, 0x00, 0x8d, 0x0e, 0xd9, 0x79, 0x5c, 0x22, 0x3b, 0xdb, 0x05, 0x6d, 0x41, 0xf0, 0x9d, 0x36, + 0x29, 0x17, 0x8a, 0xa4, 0x5c, 0xdc, 0xd1, 0x14, 0xc1, 0xd7, 0x9a, 0x64, 0x7b, 0xbb, 0x4c, 0xb6, + 0x77, 0xb6, 0xb5, 0x08, 0x02, 0x88, 0x37, 0x3a, 0xe4, 0x61, 0xa9, 0xa4, 0x45, 0x85, 0xae, 0xd2, + 0x21, 0xe5, 0xad, 0x47, 0xd2, 0x36, 0x16, 0xda, 0x3e, 0xdc, 0x2e, 0x90, 0x47, 0x5b, 0x05, 0x4d, + 0x15, 0xb6, 0x95, 0x36, 0x29, 0x17, 0x4b, 0x5a, 0x5c, 0xe8, 0x9e, 0x61, 0x52, 0x2e, 0x16, 0x24, + 0x9f, 0xd8, 0xf8, 0x08, 0x54, 0xb9, 0xde, 0x85, 0x42, 0x84, 0xf7, 0xa2, 0xd2, 0x24, 0x78, 0x4b, + 0x5b, 0xd8, 0xf8, 0x0e, 0x54, 0xf9, 0x3a, 0x20, 0x0d, 0x16, 0x9f, 0xb6, 0xf6, 0x9a, 0x04, 0x37, + 0x9e, 0x77, 0x1a, 0xed, 0x23, 0x6d, 0x01, 0x2d, 0x43, 0x5a, 0x4a, 0x2a, 0xb5, 0x5a, 0xe3, 0xf0, + 0x48, 0x53, 0x10, 0x82, 0xa5, 0x4e, 0xb3, 0xd6, 0x6a, 0xee, 0xee, 0xe1, 0x83, 0x46, 0x9d, 0x74, + 0x0e, 0xb5, 0x08, 0xba, 0x03, 0xda, 0xac, 0xac, 0xde, 0x7a, 0xd1, 0xd4, 0xa2, 0x02, 0xec, 0x8a, + 0x5d, 0x4c, 0xf8, 0xce, 0x59, 0xa9, 0xd5, 0xdd, 0x57, 0x6f, 0x57, 0x94, 0xd7, 0x6f, 0x57, 0x94, + 0x3f, 0xde, 0xae, 0x28, 0x3f, 0xfe, 0xb9, 0xb2, 0xf0, 0xed, 0xc3, 0x9b, 0xfc, 0xb9, 0x1f, 0xc7, + 0xa5, 0xa4, 0xf4, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x4e, 0xb3, 0xab, 0xf8, 0x0b, 0x00, + 0x00, +} diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto new file mode 100644 index 000000000..a35476455 --- /dev/null +++ b/api/protocol/lorawan/lorawan.proto @@ -0,0 +1,135 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +package lorawan; + +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; + +enum Modulation { + LORA = 0; + FSK = 1; +} + +message Metadata { + Modulation modulation = 11; + string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} + uint32 bit_rate = 13; // FSK bit rate in bit/s + string coding_rate = 14; // LoRa coding rate + + uint32 f_cnt = 15; // Store the full 32 bit FCnt +} + +message TxConfiguration { + Modulation modulation = 11; + string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} + uint32 bit_rate = 13; // FSK bit rate in bit/s + string coding_rate = 14; // LoRa coding rate + + uint32 f_cnt = 15; // Store the full 32 bit FCnt +} + +message ActivationMetadata { + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes dev_addr = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + bytes nwk_s_key = 4 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + + uint32 rx1_dr_offset = 11; + uint32 rx2_dr = 12; + uint32 rx_delay = 13; + CFList cf_list = 14; +} + +enum Region { + EU_863_870 = 0; + US_902_928 = 1; + CN_779_787 = 2; + EU_433 = 3; + AU_915_928 = 4; + CN_470_510 = 5; + AS_923 = 6; + KR_920_923 = 7; +} + +message Message { + MHDR m_hdr = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + bytes mic = 2; + + oneof Payload { + MACPayload mac_payload = 3; + JoinRequestPayload join_request_payload = 4; + JoinAcceptPayload join_accept_payload = 5; + } +} + +enum Major { + LORAWAN_R1 = 0; +} + +enum MType { + JOIN_REQUEST = 0; + JOIN_ACCEPT = 1; + UNCONFIRMED_UP = 2; + UNCONFIRMED_DOWN = 3; + CONFIRMED_UP = 4; + CONFIRMED_DOWN = 5; +} + +message MHDR { + MType m_type = 1; + Major major = 2; +} + +message MACPayload { + FHDR f_hdr = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + int32 f_port = 2; + bytes frm_payload = 3; +} + +message FHDR { + bytes dev_addr = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + FCtrl f_ctrl = 2 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + uint32 f_cnt = 3; + repeated MACCommand f_opts = 4 [(gogoproto.nullable) = false]; +} + +message FCtrl { + bool adr = 1; + bool adr_ack_req = 2; + bool ack = 3; + bool f_pending = 4; +} + +message MACCommand { + uint32 cid = 1; + bytes payload = 2; +} + +message JoinRequestPayload { + bytes app_eui = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes dev_nonce = 3 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevNonce"]; +} + +message JoinAcceptPayload { + bytes encrypted = 1; + bytes app_nonce = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppNonce"]; + bytes net_id = 3 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NetID"]; + bytes dev_addr = 4 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + DLSettings dl_settings = 5 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + uint32 rx_delay = 6; + CFList cf_list = 7; +} + +message DLSettings { + uint32 rx1_dr_offset = 1; + uint32 rx2_dr = 2; +} + +message CFList { + repeated uint32 freq = 1; +} diff --git a/api/protocol/lorawan/message_conversion.go b/api/protocol/lorawan/message_conversion.go new file mode 100644 index 000000000..da09ad121 --- /dev/null +++ b/api/protocol/lorawan/message_conversion.go @@ -0,0 +1,226 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package lorawan + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" + "github.com/brocaar/lorawan/band" +) + +type payloader interface { + Payload() lorawan.Payload +} + +type macCommandPayload []byte + +func (m macCommandPayload) MarshalBinary() (data []byte, err error) { + return []byte(m), nil +} + +func (m *macCommandPayload) UnmarshalBinary(data []byte) error { + *m = data + return nil +} + +// MACCommand converts the MACCommand to a lorawan.MACCommand +func (m *MACCommand) MACCommand() (cmd lorawan.MACCommand) { + cmd.CID = lorawan.CID(m.Cid) + payload := macCommandPayload(m.Payload) + cmd.Payload = &payload + return +} + +// MACCommandFromMACCommand creates a new MACCommand from a lorawan.MACCommand +func MACCommandFromMACCommand(cmd lorawan.MACCommand) (m MACCommand) { + m.Cid = uint32(cmd.CID) + if cmd.Payload != nil { + m.Payload, _ = cmd.Payload.MarshalBinary() + } + return +} + +// Payload converts the MACPayload to a lorawan.Payload +func (msg *Message_MacPayload) Payload() lorawan.Payload { + m := *msg.MacPayload + var mac lorawan.MACPayload + mac.FHDR.DevAddr = lorawan.DevAddr(m.DevAddr) + mac.FHDR.FCtrl.ADR = m.Adr + mac.FHDR.FCtrl.ADRACKReq = m.AdrAckReq + mac.FHDR.FCtrl.ACK = m.Ack + mac.FHDR.FCtrl.FPending = m.FPending + mac.FHDR.FCnt = m.FCnt + for _, cmd := range m.FOpts { + mac.FHDR.FOpts = append(mac.FHDR.FOpts, cmd.MACCommand()) + } + if m.FPort >= 0 { + fPort := uint8(m.FPort) + mac.FPort = &fPort + } + mac.FRMPayload = []lorawan.Payload{ + &lorawan.DataPayload{Bytes: m.FrmPayload}, + } + return &mac +} + +// MACPayloadFromPayload creates a new MACPayload from a lorawan.Payload +func MACPayloadFromPayload(payload lorawan.Payload) (mac MACPayload) { + if payload, ok := payload.(*lorawan.MACPayload); ok { + mac.DevAddr = types.DevAddr(payload.FHDR.DevAddr) + mac.Adr = payload.FHDR.FCtrl.ADR + mac.AdrAckReq = payload.FHDR.FCtrl.ADRACKReq + mac.Ack = payload.FHDR.FCtrl.ACK + mac.FPending = payload.FHDR.FCtrl.FPending + mac.FCnt = payload.FHDR.FCnt + for _, cmd := range payload.FHDR.FOpts { + mac.FOpts = append(mac.FOpts, MACCommandFromMACCommand(cmd)) + } + if payload.FPort != nil { + mac.FPort = int32(*payload.FPort) + } + if len(payload.FRMPayload) == 1 { + if payload, ok := payload.FRMPayload[0].(*lorawan.DataPayload); ok { + mac.FrmPayload = payload.Bytes + } + } + } + return +} + +// Payload converts the JoinRequestPayload to a lorawan.Payload +func (msg *Message_JoinRequestPayload) Payload() lorawan.Payload { + m := *msg.JoinRequestPayload + var mac lorawan.JoinRequestPayload + mac.AppEUI = lorawan.EUI64(m.AppEui) + mac.DevEUI = lorawan.EUI64(m.DevEui) + mac.DevNonce = m.DevNonce + return &mac +} + +// JoinRequestPayloadFromPayload creates a new JoinRequestPayload from a lorawan.Payload +func JoinRequestPayloadFromPayload(payload lorawan.Payload) (request JoinRequestPayload) { + if payload, ok := payload.(*lorawan.JoinRequestPayload); ok { + request.AppEui = types.AppEUI(payload.AppEUI) + request.DevEui = types.DevEUI(payload.DevEUI) + request.DevNonce = types.DevNonce(payload.DevNonce) + } + return +} + +// Payload converts the JoinAcceptPayload to a lorawan.Payload +func (msg *Message_JoinAcceptPayload) Payload() lorawan.Payload { + m := *msg.JoinAcceptPayload + if len(m.Encrypted) != 0 { + return &lorawan.DataPayload{Bytes: m.Encrypted} + } + var mac lorawan.JoinAcceptPayload + mac.AppNonce = m.AppNonce + mac.NetID = m.NetId + mac.DevAddr = lorawan.DevAddr(m.DevAddr) + mac.DLSettings.RX1DROffset = uint8(m.Rx1DrOffset) + mac.DLSettings.RX2DataRate = uint8(m.Rx2Dr) + mac.RXDelay = uint8(m.RxDelay) + if m.CfList != nil && len(m.CfList.Freq) == 5 { + mac.CFList = &lorawan.CFList{ + m.CfList.Freq[0], + m.CfList.Freq[1], + m.CfList.Freq[2], + m.CfList.Freq[3], + m.CfList.Freq[4], + } + } + return &mac +} + +// JoinAcceptPayloadFromPayload creates a new JoinAcceptPayload from a lorawan.Payload +func JoinAcceptPayloadFromPayload(payload lorawan.Payload) (accept JoinAcceptPayload) { + if dataPayload, ok := payload.(*lorawan.DataPayload); ok { + accept.Encrypted = dataPayload.Bytes + joinAccept := &lorawan.JoinAcceptPayload{} + joinAccept.UnmarshalBinary(false, dataPayload.Bytes) + payload = joinAccept + } + + if payload, ok := payload.(*lorawan.JoinAcceptPayload); ok { + accept.AppNonce = types.AppNonce(payload.AppNonce) + accept.NetId = types.NetID(payload.NetID) + accept.DevAddr = types.DevAddr(payload.DevAddr) + accept.DLSettings.Rx1DrOffset = uint32(payload.DLSettings.RX1DROffset) + accept.DLSettings.Rx2Dr = uint32(payload.DLSettings.RX2DataRate) + accept.RxDelay = uint32(payload.RXDelay) + if payload.CFList != nil { + accept.CfList = &CFList{ + Freq: payload.CFList[:], + } + } + } + return +} + +// PHYPayload converts the Message to a lorawan.PHYPayload +func (m *Message) PHYPayload() (phy lorawan.PHYPayload) { + phy.MHDR.Major = lorawan.Major(m.Major) + phy.MHDR.MType = lorawan.MType(m.MType) + phy.MACPayload = m.Payload.(payloader).Payload() + copy(phy.MIC[:], m.Mic) + return +} + +// MessageFromPHYPayload converts a lorawan.PHYPayload to a Message +func MessageFromPHYPayload(phy lorawan.PHYPayload) Message { + var m Message + m.Major = Major(phy.MHDR.Major) + m.MType = MType(phy.MHDR.MType) + m.Mic = phy.MIC[:] + switch m.MType { + case MType_JOIN_REQUEST: + payload := JoinRequestPayloadFromPayload(phy.MACPayload) + m.Payload = &Message_JoinRequestPayload{JoinRequestPayload: &payload} + case MType_JOIN_ACCEPT: + payload := JoinAcceptPayloadFromPayload(phy.MACPayload) + m.Payload = &Message_JoinAcceptPayload{JoinAcceptPayload: &payload} + case MType_UNCONFIRMED_UP, + MType_UNCONFIRMED_DOWN, + MType_CONFIRMED_UP, + MType_CONFIRMED_DOWN: + payload := MACPayloadFromPayload(phy.MACPayload) + m.Payload = &Message_MacPayload{MacPayload: &payload} + } + return m +} + +// GetDataRate returns the band.Datarate for the current Metadata +func (m *Metadata) GetDataRate() (dataRate band.DataRate, err error) { + switch m.Modulation { + case Modulation_LORA: + dataRate.Modulation = band.LoRaModulation + dr, err := types.ParseDataRate(m.DataRate) + if err != nil { + return dataRate, err + } + dataRate.Bandwidth = int(dr.Bandwidth) + dataRate.SpreadFactor = int(dr.SpreadingFactor) + case Modulation_FSK: + dataRate.Modulation = band.FSKModulation + dataRate.BitRate = int(m.BitRate) + } + return +} + +// SetDataRate sets the dataRate for the current Metadata based from a band.Datarate +func (c *TxConfiguration) SetDataRate(dataRate band.DataRate) error { + switch dataRate.Modulation { + case band.LoRaModulation: + c.Modulation = Modulation_LORA + datr, err := types.ConvertDataRate(dataRate) + if err != nil { + return err + } + c.DataRate = datr.String() + case band.FSKModulation: + c.Modulation = Modulation_FSK + c.BitRate = uint32(dataRate.BitRate) + } + return nil +} diff --git a/api/protocol/lorawan/message_conversion_test.go b/api/protocol/lorawan/message_conversion_test.go new file mode 100644 index 000000000..a82a59025 --- /dev/null +++ b/api/protocol/lorawan/message_conversion_test.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package lorawan + +import ( + "testing" + + "github.com/brocaar/lorawan" + "github.com/brocaar/lorawan/band" + . "github.com/smartystreets/assertions" +) + +func TestConvertPHYPayload(t *testing.T) { + a := New(t) + + { + m1 := Message{Mic: []byte{0, 0, 0, 0}} + m1.MType = MType_UNCONFIRMED_UP + macPayload := MACPayload{} + macPayload.FOpts = []MACCommand{ + MACCommand{Cid: 0x02}, + } + m1.Payload = &Message_MacPayload{MacPayload: &macPayload} + phy := m1.PHYPayload() + m2 := MessageFromPHYPayload(phy) + a.So(m2, ShouldResemble, m1) + } + + { + m1 := Message{Mic: []byte{0, 0, 0, 0}} + m1.MType = MType_JOIN_REQUEST + joinRequestPayload := JoinRequestPayload{} + m1.Payload = &Message_JoinRequestPayload{JoinRequestPayload: &joinRequestPayload} + phy := m1.PHYPayload() + m2 := MessageFromPHYPayload(phy) + a.So(m2, ShouldResemble, m1) + } + + { + m1 := Message{Mic: []byte{0, 0, 0, 0}} + m1.MType = MType_JOIN_ACCEPT + joinAcceptPayload := JoinAcceptPayload{} + joinAcceptPayload.CfList = &CFList{ + Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}, + } + m1.Payload = &Message_JoinAcceptPayload{JoinAcceptPayload: &joinAcceptPayload} + phy := m1.PHYPayload() + m2 := MessageFromPHYPayload(phy) + a.So(m2, ShouldResemble, m1) + + phy.MACPayload = &lorawan.DataPayload{Bytes: []byte{0x01, 0x02, 0x03, 0x04}} + + m3 := MessageFromPHYPayload(phy) + + phy = m3.PHYPayload() + } + +} + +func TestConvertDataRate(t *testing.T) { + a := New(t) + + { + md := &Metadata{ + Modulation: Modulation_LORA, + DataRate: "SF7BW125", + } + dr, err := md.GetDataRate() + a.So(err, ShouldBeNil) + a.So(dr, ShouldResemble, band.DataRate{Modulation: band.LoRaModulation, SpreadFactor: 7, Bandwidth: 125}) + } + + { + md := &Metadata{ + Modulation: Modulation_FSK, + BitRate: 50000, + } + dr, err := md.GetDataRate() + a.So(err, ShouldBeNil) + a.So(dr, ShouldResemble, band.DataRate{Modulation: band.FSKModulation, BitRate: 50000}) + } + + { + tx := new(TxConfiguration) + err := tx.SetDataRate(band.DataRate{Modulation: band.LoRaModulation, SpreadFactor: 7, Bandwidth: 125}) + a.So(err, ShouldBeNil) + a.So(tx.Modulation, ShouldEqual, Modulation_LORA) + a.So(tx.DataRate, ShouldEqual, "SF7BW125") + } + + { + tx := new(TxConfiguration) + err := tx.SetDataRate(band.DataRate{Modulation: band.FSKModulation, BitRate: 50000}) + a.So(err, ShouldBeNil) + a.So(tx.Modulation, ShouldEqual, Modulation_FSK) + a.So(tx.BitRate, ShouldEqual, 50000) + } + +} diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go new file mode 100644 index 000000000..639a0827c --- /dev/null +++ b/api/protocol/lorawan/validation.go @@ -0,0 +1,161 @@ +package lorawan + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validate implements the api.Validator interface +func (m *DeviceIdentifier) Validate() error { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return errors.NewErrInvalidArgument("AppEui", "can not be empty") + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return errors.NewErrInvalidArgument("DevEui", "can not be empty") + } + return nil +} + +// Validate implements the api.Validator interface +func (m *Device) Validate() error { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return errors.NewErrInvalidArgument("AppEui", "can not be empty") + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return errors.NewErrInvalidArgument("DevEui", "can not be empty") + } + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { + return err + } + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { + return err + } + return nil +} + +// Validate implements the api.Validator interface +func (m *Metadata) Validate() error { + switch m.Modulation { + case Modulation_LORA: + if m.DataRate == "" { + return errors.NewErrInvalidArgument("DataRate", "can not be empty") + } + case Modulation_FSK: + if m.BitRate == 0 { + return errors.NewErrInvalidArgument("BitRate", "can not be empty") + } + } + if m.CodingRate == "" { + return errors.NewErrInvalidArgument("CodingRate", "can not be empty") + } + return nil +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration) Validate() error { + switch m.Modulation { + case Modulation_LORA: + if m.DataRate == "" { + return errors.NewErrInvalidArgument("DataRate", "can not be empty") + } + case Modulation_FSK: + if m.BitRate == 0 { + return errors.NewErrInvalidArgument("BitRate", "can not be empty") + } + } + if m.CodingRate == "" { + return errors.NewErrInvalidArgument("CodingRate", "can not be empty") + } + return nil +} + +// Validate implements the api.Validator interface +func (m *ActivationMetadata) Validate() error { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return errors.NewErrInvalidArgument("AppEui", "can not be empty") + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return errors.NewErrInvalidArgument("DevEui", "can not be empty") + } + if m.DevAddr != nil && m.DevAddr.IsEmpty() { + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") + } + if m.NwkSKey != nil && m.NwkSKey.IsEmpty() { + return errors.NewErrInvalidArgument("NwkSKey", "can not be empty") + } + return nil +} + +// Validate implements the api.Validator interface +func (m *Message) Validate() error { + if m.Major != Major_LORAWAN_R1 { + return errors.NewErrInvalidArgument("Major", "invalid value "+Major_LORAWAN_R1.String()) + } + switch m.MType { + case MType_JOIN_REQUEST: + if m.GetJoinRequestPayload() == nil { + return errors.NewErrInvalidArgument("JoinRequestPayload", "can not be empty") + } + if err := m.GetJoinRequestPayload().Validate(); err != nil { + return errors.NewErrInvalidArgument("JoinRequestPayload", err.Error()) + } + case MType_JOIN_ACCEPT: + if m.GetJoinAcceptPayload() == nil { + return errors.NewErrInvalidArgument("JoinAcceptPayload", "can not be empty") + } + if err := m.GetJoinAcceptPayload().Validate(); err != nil { + return errors.NewErrInvalidArgument("JoinAcceptPayload", err.Error()) + } + case MType_UNCONFIRMED_UP, MType_UNCONFIRMED_DOWN, MType_CONFIRMED_UP, MType_CONFIRMED_DOWN: + if m.GetMacPayload() == nil { + return errors.NewErrInvalidArgument("MacPayload", "can not be empty") + } + if err := m.GetMacPayload().Validate(); err != nil { + return errors.NewErrInvalidArgument("MacPayload", err.Error()) + } + default: + return errors.NewErrInvalidArgument("MType", "unknown type "+m.MType.String()) + } + + return nil +} + +// Validate implements the api.Validator interface +func (m *JoinRequestPayload) Validate() error { + if m.AppEui.IsEmpty() { + return errors.NewErrInvalidArgument("AppEui", "can not be empty") + } + if m.DevEui.IsEmpty() { + return errors.NewErrInvalidArgument("DevEui", "can not be empty") + } + + return nil +} + +// Validate implements the api.Validator interface +func (m *JoinAcceptPayload) Validate() error { + if len(m.Encrypted) != 0 { + return nil + } + + if m.CfList != nil && len(m.CfList.Freq) != 5 { + return errors.NewErrInvalidArgument("CfList.Freq", "length must be 5") + } + + if m.DevAddr.IsEmpty() { + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") + } + if m.NetId.IsEmpty() { + return errors.NewErrInvalidArgument("NetId", "can not be empty") + } + + return nil +} + +// Validate implements the api.Validator interface +func (m *MACPayload) Validate() error { + if m.DevAddr.IsEmpty() { + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") + } + return nil +} diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go new file mode 100644 index 000000000..f4c9d2e6f --- /dev/null +++ b/api/protocol/protocol.pb.go @@ -0,0 +1,1134 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto +// DO NOT EDIT! + +/* + Package protocol is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto + + It has these top-level messages: + Message + RxMetadata + TxConfiguration + ActivationMetadata +*/ +package protocol + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Message struct { + // Types that are valid to be assigned to Protocol: + // *Message_Lorawan + Protocol isMessage_Protocol `protobuf_oneof:"protocol"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{0} } + +type isMessage_Protocol interface { + isMessage_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type Message_Lorawan struct { + Lorawan *lorawan.Message `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*Message_Lorawan) isMessage_Protocol() {} + +func (m *Message) GetProtocol() isMessage_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *Message) GetLorawan() *lorawan.Message { + if x, ok := m.GetProtocol().(*Message_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Message) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Message_OneofMarshaler, _Message_OneofUnmarshaler, _Message_OneofSizer, []interface{}{ + (*Message_Lorawan)(nil), + } +} + +func _Message_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Message) + // protocol + switch x := m.Protocol.(type) { + case *Message_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Message.Protocol has unexpected type %T", x) + } + return nil +} + +func _Message_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Message) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.Message) + err := b.DecodeMessage(msg) + m.Protocol = &Message_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _Message_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Message) + // protocol + switch x := m.Protocol.(type) { + case *Message_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type RxMetadata struct { + // Types that are valid to be assigned to Protocol: + // *RxMetadata_Lorawan + Protocol isRxMetadata_Protocol `protobuf_oneof:"protocol"` +} + +func (m *RxMetadata) Reset() { *m = RxMetadata{} } +func (m *RxMetadata) String() string { return proto.CompactTextString(m) } +func (*RxMetadata) ProtoMessage() {} +func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{1} } + +type isRxMetadata_Protocol interface { + isRxMetadata_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type RxMetadata_Lorawan struct { + Lorawan *lorawan.Metadata `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*RxMetadata_Lorawan) isRxMetadata_Protocol() {} + +func (m *RxMetadata) GetProtocol() isRxMetadata_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *RxMetadata) GetLorawan() *lorawan.Metadata { + if x, ok := m.GetProtocol().(*RxMetadata_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*RxMetadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _RxMetadata_OneofMarshaler, _RxMetadata_OneofUnmarshaler, _RxMetadata_OneofSizer, []interface{}{ + (*RxMetadata_Lorawan)(nil), + } +} + +func _RxMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*RxMetadata) + // protocol + switch x := m.Protocol.(type) { + case *RxMetadata_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("RxMetadata.Protocol has unexpected type %T", x) + } + return nil +} + +func _RxMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*RxMetadata) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.Metadata) + err := b.DecodeMessage(msg) + m.Protocol = &RxMetadata_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _RxMetadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*RxMetadata) + // protocol + switch x := m.Protocol.(type) { + case *RxMetadata_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type TxConfiguration struct { + // Types that are valid to be assigned to Protocol: + // *TxConfiguration_Lorawan + Protocol isTxConfiguration_Protocol `protobuf_oneof:"protocol"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{2} } + +type isTxConfiguration_Protocol interface { + isTxConfiguration_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type TxConfiguration_Lorawan struct { + Lorawan *lorawan.TxConfiguration `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*TxConfiguration_Lorawan) isTxConfiguration_Protocol() {} + +func (m *TxConfiguration) GetProtocol() isTxConfiguration_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *TxConfiguration) GetLorawan() *lorawan.TxConfiguration { + if x, ok := m.GetProtocol().(*TxConfiguration_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*TxConfiguration) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _TxConfiguration_OneofMarshaler, _TxConfiguration_OneofUnmarshaler, _TxConfiguration_OneofSizer, []interface{}{ + (*TxConfiguration_Lorawan)(nil), + } +} + +func _TxConfiguration_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*TxConfiguration) + // protocol + switch x := m.Protocol.(type) { + case *TxConfiguration_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("TxConfiguration.Protocol has unexpected type %T", x) + } + return nil +} + +func _TxConfiguration_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*TxConfiguration) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.TxConfiguration) + err := b.DecodeMessage(msg) + m.Protocol = &TxConfiguration_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _TxConfiguration_OneofSizer(msg proto.Message) (n int) { + m := msg.(*TxConfiguration) + // protocol + switch x := m.Protocol.(type) { + case *TxConfiguration_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type ActivationMetadata struct { + // Types that are valid to be assigned to Protocol: + // *ActivationMetadata_Lorawan + Protocol isActivationMetadata_Protocol `protobuf_oneof:"protocol"` +} + +func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } +func (m *ActivationMetadata) String() string { return proto.CompactTextString(m) } +func (*ActivationMetadata) ProtoMessage() {} +func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{3} } + +type isActivationMetadata_Protocol interface { + isActivationMetadata_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type ActivationMetadata_Lorawan struct { + Lorawan *lorawan.ActivationMetadata `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*ActivationMetadata_Lorawan) isActivationMetadata_Protocol() {} + +func (m *ActivationMetadata) GetProtocol() isActivationMetadata_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *ActivationMetadata) GetLorawan() *lorawan.ActivationMetadata { + if x, ok := m.GetProtocol().(*ActivationMetadata_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*ActivationMetadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _ActivationMetadata_OneofMarshaler, _ActivationMetadata_OneofUnmarshaler, _ActivationMetadata_OneofSizer, []interface{}{ + (*ActivationMetadata_Lorawan)(nil), + } +} + +func _ActivationMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*ActivationMetadata) + // protocol + switch x := m.Protocol.(type) { + case *ActivationMetadata_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("ActivationMetadata.Protocol has unexpected type %T", x) + } + return nil +} + +func _ActivationMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*ActivationMetadata) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.ActivationMetadata) + err := b.DecodeMessage(msg) + m.Protocol = &ActivationMetadata_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _ActivationMetadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*ActivationMetadata) + // protocol + switch x := m.Protocol.(type) { + case *ActivationMetadata_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +func init() { + proto.RegisterType((*Message)(nil), "protocol.Message") + proto.RegisterType((*RxMetadata)(nil), "protocol.RxMetadata") + proto.RegisterType((*TxConfiguration)(nil), "protocol.TxConfiguration") + proto.RegisterType((*ActivationMetadata)(nil), "protocol.ActivationMetadata") +} +func (m *Message) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn1, err := m.Protocol.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *Message_Lorawan) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n2, err := m.Lorawan.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} +func (m *RxMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RxMetadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn3, err := m.Protocol.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn3 + } + return i, nil +} + +func (m *RxMetadata_Lorawan) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n4, err := m.Lorawan.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} +func (m *TxConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TxConfiguration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn5, err := m.Protocol.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn5 + } + return i, nil +} + +func (m *TxConfiguration_Lorawan) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n6, err := m.Lorawan.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} +func (m *ActivationMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ActivationMetadata) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn7, err := m.Protocol.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn7 + } + return i, nil +} + +func (m *ActivationMetadata_Lorawan) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n8, err := m.Lorawan.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} +func encodeFixed64Protocol(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Protocol(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintProtocol(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Message) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *Message_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} +func (m *RxMetadata) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *RxMetadata_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *TxConfiguration_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} +func (m *ActivationMetadata) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *ActivationMetadata_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} + +func sovProtocol(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozProtocol(x uint64) (n int) { + return sovProtocol(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Message) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Message: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Message: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.Message{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &Message_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RxMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RxMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RxMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.Metadata{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &RxMetadata_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.TxConfiguration{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &TxConfiguration_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.ActivationMetadata{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &ActivationMetadata_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipProtocol(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthProtocol + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipProtocol(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthProtocol = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowProtocol = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto", fileDescriptorProtocol) +} + +var fileDescriptorProtocol = []byte{ + // 240 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, + 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, + 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0x81, 0x33, 0xf4, 0xc0, 0x0c, 0x21, 0x0e, 0x18, + 0x5f, 0x4a, 0x0d, 0x43, 0x69, 0x4e, 0x7e, 0x51, 0x62, 0x79, 0x62, 0x1e, 0x8c, 0x86, 0xe8, 0x50, + 0x72, 0xe6, 0x62, 0xf7, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x15, 0xd2, 0xe1, 0x62, 0x87, 0xca, + 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0xe8, 0xc1, 0xd4, 0x42, 0x95, 0x78, 0x30, 0x04, + 0xc1, 0x94, 0x38, 0x71, 0x71, 0xc1, 0x2d, 0x53, 0x72, 0xe7, 0xe2, 0x0a, 0xaa, 0xf0, 0x4d, 0x2d, + 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x14, 0xd2, 0x45, 0x37, 0x47, 0x10, 0xc9, 0x1c, 0x88, 0x1a, 0x5c, + 0x06, 0x05, 0x73, 0xf1, 0x87, 0x54, 0x38, 0xe7, 0xe7, 0xa5, 0x65, 0xa6, 0x97, 0x16, 0x25, 0x96, + 0x64, 0xe6, 0xe7, 0x09, 0x99, 0xa0, 0x9b, 0x26, 0x01, 0x37, 0x0d, 0x4d, 0x29, 0x2e, 0x43, 0x23, + 0xb9, 0x84, 0x1c, 0x93, 0x4b, 0x32, 0xcb, 0xc0, 0x8a, 0xe0, 0xae, 0x34, 0x47, 0x37, 0x57, 0x1a, + 0x6e, 0x2e, 0xa6, 0x6a, 0x1c, 0x46, 0x3b, 0xd9, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, + 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x44, 0xe9, 0x90, 0x12, 0x7d, 0x49, 0x6c, + 0x60, 0x96, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x48, 0xdc, 0x20, 0x24, 0xf5, 0x01, 0x00, 0x00, +} diff --git a/api/protocol/protocol.proto b/api/protocol/protocol.proto new file mode 100644 index 000000000..9a01f2121 --- /dev/null +++ b/api/protocol/protocol.proto @@ -0,0 +1,34 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "ttn/api/protocol/lorawan/lorawan.proto"; + +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol"; + +package protocol; + +message Message { + oneof protocol { + lorawan.Message lorawan = 1; + } +} + +message RxMetadata { + oneof protocol { + lorawan.Metadata lorawan = 1; + } +} + +message TxConfiguration { + oneof protocol { + lorawan.TxConfiguration lorawan = 1; + } +} + +message ActivationMetadata { + oneof protocol { + lorawan.ActivationMetadata lorawan = 1; + } +} diff --git a/api/protocol/validation.go b/api/protocol/validation.go new file mode 100644 index 000000000..20c2928a5 --- /dev/null +++ b/api/protocol/validation.go @@ -0,0 +1,72 @@ +package protocol + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validate implements the api.Validator interface +func (m *RxMetadata) Validate() error { + if m.Protocol == nil { + return errors.NewErrInvalidArgument("Protocol", "can not be empty") + } + if err := api.Validate(m.Protocol); err != nil { + return errors.NewErrInvalidArgument("Protocol", err.Error()) + } + return nil +} + +// Validate implements the api.Validator interface +func (m *RxMetadata_Lorawan) Validate() error { + if err := m.Lorawan.Validate(); err != nil { + return errors.NewErrInvalidArgument("Lorawan", err.Error()) + } + return nil +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration) Validate() error { + if m.Protocol == nil { + return errors.New("RxMetadata.Protocol is nil") + } + return api.Validate(m.Protocol) +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration_Lorawan) Validate() error { + return m.Lorawan.Validate() +} + +// Validate implements the api.Validator interface +func (m *ActivationMetadata) Validate() error { + if m.Protocol == nil { + return errors.NewErrInvalidArgument("Protocol", "can not be empty") + } + if err := api.Validate(m.Protocol); err != nil { + return errors.NewErrInvalidArgument("Protocol", err.Error()) + } + + return nil +} + +// Validate implements the api.Validator interface +func (m *ActivationMetadata_Lorawan) Validate() error { + return m.Lorawan.Validate() +} + +// Validate implements the api.Validator interface +func (m *Message) Validate() error { + if m.Protocol == nil { + return errors.NewErrInvalidArgument("Protocol", "can not be empty") + } + if err := api.Validate(m.Protocol); err != nil { + return errors.NewErrInvalidArgument("Protocol", err.Error()) + } + + return nil +} + +// Validate implements the api.Validator interface +func (m *Message_Lorawan) Validate() error { + return m.Lorawan.Validate() +} diff --git a/api/ratelimit/ratelimit.go b/api/ratelimit/ratelimit.go new file mode 100644 index 000000000..298a7b1f1 --- /dev/null +++ b/api/ratelimit/ratelimit.go @@ -0,0 +1,53 @@ +package ratelimit + +import ( + "sync" + "time" + + "github.com/juju/ratelimit" +) + +// Registry for rate limiting +type Registry struct { + rate int + per time.Duration + mu sync.RWMutex + entities map[string]*ratelimit.Bucket +} + +// NewRegistry returns a new Registry for rate limiting +func NewRegistry(rate int, per time.Duration) *Registry { + return &Registry{ + rate: rate, + per: per, + entities: make(map[string]*ratelimit.Bucket), + } +} + +func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.Bucket) *ratelimit.Bucket { + r.mu.RLock() + limiter, ok := r.entities[id] + r.mu.RUnlock() + if ok { + return limiter + } + limiter = createFunc() + r.mu.Lock() + r.entities[id] = limiter + r.mu.Unlock() + return limiter +} + +func (r *Registry) newFunc() *ratelimit.Bucket { + return ratelimit.NewBucketWithQuantum(r.per, int64(r.rate), int64(r.rate)) +} + +// Limit returns true if the ratelimit for the given entity has been reached +func (r *Registry) Limit(id string) bool { + return r.Wait(id) != 0 +} + +// Wait returns the time to wait until available +func (r *Registry) Wait(id string) time.Duration { + return r.getOrCreate(id, r.newFunc).Take(1) +} diff --git a/api/router/client_streams.go b/api/router/client_streams.go new file mode 100644 index 000000000..2929eefc1 --- /dev/null +++ b/api/router/client_streams.go @@ -0,0 +1,374 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "io" + "sync" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +// GatewayStream interface +type GatewayStream interface { + Close() +} + +type gatewayStream struct { + closing bool + setup sync.WaitGroup + ctx log.Interface + client RouterClientForGateway +} + +// DefaultBufferSize indicates the default send and receive buffer sizes +var DefaultBufferSize = 10 + +// GatewayStatusStream for sending gateway statuses +type GatewayStatusStream interface { + GatewayStream + Send(*gateway.Status) error +} + +// NewMonitoredGatewayStatusStream starts and monitors a GatewayStatusStream +func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatusStream { + s := &gatewayStatusStream{ + ch: make(chan *gateway.Status, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = client.GetLogger() + + go func() { + var retries int + + for { + // Session channels + ch := make(chan *gateway.Status) + errCh := make(chan error) + + // Session client + client, err := s.client.GatewayStatus() + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped GatewayStatus stream") + break + } + s.ctx.WithError(err).Warn("Could not start GatewayStatus stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + + s.ctx.Info("Started GatewayStatus stream") + + // Receive errors + go func() { + empty := new(empty.Empty) + if err := client.RecvMsg(empty); err != nil { + errCh <- err + } + close(errCh) + }() + + // Send + go func() { + for status := range ch { + s.ctx.Debug("Sending GatewayStatus message") + if err := client.Send(status); err != nil { + s.ctx.WithError(err).Warn("Error sending GatewayStatus message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case <-time.After(10 * time.Second): + retries = 0 + case mErr = <-errCh: + break monitor + case msg, ok := <-s.ch: + if !ok { + break monitor // channel closed + } + ch <- msg + } + } + + close(ch) + client.CloseAndRecv() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped GatewayStatus stream") + } else { + s.ctx.WithError(mErr).Warn("Error in GatewayStatus stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type gatewayStatusStream struct { + gatewayStream + ch chan *gateway.Status + err chan error +} + +func (s *gatewayStatusStream) Send(status *gateway.Status) error { + select { + case s.ch <- status: + default: + s.ctx.Warn("Dropping GatewayStatus message, buffer full") + } + return nil +} + +func (s *gatewayStatusStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing GatewayStatus stream") + s.closing = true + close(s.ch) +} + +// UplinkStream for sending uplink messages +type UplinkStream interface { + GatewayStream + Send(*UplinkMessage) error +} + +// NewMonitoredUplinkStream starts and monitors a UplinkStream +func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { + s := &uplinkStream{ + ch: make(chan *UplinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = client.GetLogger() + + go func() { + var retries int + + for { + // Session channels + ch := make(chan *UplinkMessage) + errCh := make(chan error) + + // Session client + client, err := s.client.Uplink() + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + break + } + s.ctx.WithError(err).Warn("Could not start Uplink stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + + s.ctx.Info("Started Uplink stream") + + // Receive errors + go func() { + empty := new(empty.Empty) + if err := client.RecvMsg(empty); err != nil { + errCh <- err + } + close(errCh) + }() + + // Send + go func() { + for message := range ch { + s.ctx.Debug("Sending Uplink message") + if err := client.Send(message); err != nil { + s.ctx.WithError(err).Warn("Error sending Uplink message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case <-time.After(10 * time.Second): + retries = 0 + case mErr = <-errCh: + break monitor + case msg, ok := <-s.ch: + if !ok { + break monitor // channel closed + } + ch <- msg + } + } + + close(ch) + client.CloseAndRecv() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + } else { + s.ctx.WithError(mErr).Warn("Error in Uplink stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type uplinkStream struct { + gatewayStream + ch chan *UplinkMessage + err chan error +} + +func (s *uplinkStream) Send(message *UplinkMessage) error { + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Uplink message, buffer full") + } + return nil +} + +func (s *uplinkStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Uplink stream") + s.closing = true + close(s.ch) +} + +// DownlinkStream for receiving downlink messages +type DownlinkStream interface { + GatewayStream + Channel() <-chan *DownlinkMessage +} + +// NewMonitoredDownlinkStream starts and monitors a DownlinkStream +func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { + s := &downlinkStream{ + ch: make(chan *DownlinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = client.GetLogger() + + go func() { + var client Router_SubscribeClient + var err error + var retries int + var message *DownlinkMessage + + for { + client, s.cancel, err = s.client.Subscribe() + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + break + } + s.ctx.WithError(err).Warn("Could not start Downlink stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + + s.ctx.Info("Started Downlink stream") + + for { + message, err = client.Recv() + if message != nil { + s.ctx.Debug("Receiving Downlink message") + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Downlink message, buffer full") + } + } + if err != nil { + break + } + retries = 0 + } + + if err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + } else { + s.ctx.WithError(err).Warn("Error in Downlink stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + + close(s.ch) + }() + return s +} + +type downlinkStream struct { + gatewayStream + cancel context.CancelFunc + ch chan *DownlinkMessage + err chan error +} + +func (s *downlinkStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Downlink stream") + s.closing = true + if s.cancel != nil { + s.cancel() + } +} + +func (s *downlinkStream) Channel() <-chan *DownlinkMessage { + return s.ch +} diff --git a/api/router/communication_test.go b/api/router/communication_test.go new file mode 100644 index 000000000..48471cb62 --- /dev/null +++ b/api/router/communication_test.go @@ -0,0 +1,189 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "math/rand" + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +func newTestRouter() *testRouter { + return &testRouter{ + RouterStreamServer: NewRouterStreamServer(), + } +} + +type testRouter struct { + *RouterStreamServer +} + +func (s *testRouter) Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) { + return nil, grpc.Errorf(codes.Unimplemented, "Not implemented") +} + +func (s *testRouter) Serve(port int) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + srv := grpc.NewServer() + RegisterRouterServer(srv, s) + srv.Serve(lis) +} + +func TestRouterCommunication(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestRouterCommunication") + log.Set(apex.Wrap(ctx)) + + rtr := newTestRouter() + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go rtr.Serve(port) + + conn, _ := api.Dial(fmt.Sprintf("localhost:%d", port)) + + { + rtr.UplinkChanFunc = func(md metadata.MD) (chan *UplinkMessage, error) { + ch := make(chan *UplinkMessage, 1) + go func() { + ctx.Info("[SERVER] Channel opened") + for message := range ch { + ctx.WithField("Message", message).Info("[SERVER] Received Uplink") + } + ctx.Info("[SERVER] Channel closed") + }() + return ch, nil + } + + rtrClient := NewRouterClient(conn) + gtwClient := NewRouterClientForGateway(rtrClient, "dev", "token") + uplink := NewMonitoredUplinkStream(gtwClient) + + err := uplink.Send(&UplinkMessage{ + Payload: []byte{1, 2, 3, 4}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &lorawan.Metadata{ + Modulation: lorawan.Modulation_LORA, + DataRate: "SF7BW125", + CodingRate: "4/7", + }}}, + GatewayMetadata: &gateway.RxMetadata{ + GatewayId: "dev", + }, + }) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + uplink.Close() + + time.Sleep(10 * time.Millisecond) + + gtwClient.Close() + + time.Sleep(10 * time.Millisecond) + } + + { + rtr.GatewayStatusChanFunc = func(md metadata.MD) (chan *gateway.Status, error) { + ch := make(chan *gateway.Status, 1) + go func() { + ctx.Info("[SERVER] Channel opened") + for message := range ch { + ctx.WithField("Message", message).Info("[SERVER] Received GatewayStatus") + } + ctx.Info("[SERVER] Channel closed") + }() + return ch, nil + } + + rtrClient := NewRouterClient(conn) + gtwClient := NewRouterClientForGateway(rtrClient, "dev", "token") + status := NewMonitoredGatewayStatusStream(gtwClient) + + err := status.Send(&gateway.Status{Time: time.Now().UnixNano()}) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + status.Close() + + time.Sleep(10 * time.Millisecond) + + gtwClient.Close() + + time.Sleep(10 * time.Millisecond) + } + + { + rtr.DownlinkChanFunc = func(md metadata.MD) (<-chan *DownlinkMessage, func(), error) { + ch := make(chan *DownlinkMessage, 1) + stop := make(chan struct{}) + cancel := func() { + ctx.Info("[SERVER] Canceling downlink") + close(stop) + } + go func() { + loop: + for { + select { + case <-stop: + break loop + case <-time.After(5 * time.Millisecond): + ctx.Info("[SERVER] Sending Downlink") + ch <- &DownlinkMessage{ + Payload: []byte{1, 2, 3, 4}, + } + } + } + close(ch) + ctx.Info("[SERVER] Closed Downlink") + }() + return ch, cancel, nil + } + + rtrClient := NewRouterClient(conn) + gtwClient := NewRouterClientForGateway(rtrClient, "dev", "token") + downlink := NewMonitoredDownlinkStream(gtwClient) + + ch := downlink.Channel() + + go func() { + for downlink := range ch { + ctx.WithField("Downlink", downlink).Info("[CLIENT] Received Downlink") + } + ctx.Info("[CLIENT] Closed Downlink") + }() + + time.Sleep(10 * time.Millisecond) + + downlink.Close() + + time.Sleep(10 * time.Millisecond) + + gtwClient.Close() + + time.Sleep(10 * time.Millisecond) + } + +} diff --git a/api/router/gateway_client.go b/api/router/gateway_client.go new file mode 100644 index 000000000..f2088b7a4 --- /dev/null +++ b/api/router/gateway_client.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "sync" + + "github.com/TheThingsNetwork/go-utils/log" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +// RouterClientForGateway is a RouterClient for a specific gateway +type RouterClientForGateway interface { + Close() + + GetLogger() log.Interface + SetLogger(log.Interface) + + SetToken(token string) + + GatewayStatus() (Router_GatewayStatusClient, error) + Uplink() (Router_UplinkClient, error) + Subscribe() (Router_SubscribeClient, context.CancelFunc, error) + Activate(in *DeviceActivationRequest) (*DeviceActivationResponse, error) +} + +// NewRouterClientForGateway returns a new RouterClient for the given gateway ID and access token +func NewRouterClientForGateway(client RouterClient, gatewayID, token string) RouterClientForGateway { + ctx, cancel := context.WithCancel(context.Background()) + return &routerClientForGateway{ + ctx: log.Get().WithField("GatewayID", gatewayID), + client: client, + gatewayID: gatewayID, + token: token, + bgCtx: ctx, + cancel: cancel, + } +} + +type routerClientForGateway struct { + ctx log.Interface + client RouterClient + gatewayID string + token string + bgCtx context.Context + cancel context.CancelFunc + mu sync.RWMutex +} + +func (c *routerClientForGateway) Close() { + c.cancel() +} + +func (c *routerClientForGateway) GetLogger() log.Interface { + return c.ctx +} + +func (c *routerClientForGateway) SetLogger(logger log.Interface) { + c.ctx = logger +} + +func (c *routerClientForGateway) getContext() context.Context { + c.mu.RLock() + defer c.mu.RUnlock() + md := metadata.Pairs( + "id", c.gatewayID, + "token", c.token, + ) + return metadata.NewContext(c.bgCtx, md) +} + +func (c *routerClientForGateway) SetToken(token string) { + c.mu.Lock() + defer c.mu.Unlock() + c.token = token +} + +func (c *routerClientForGateway) GatewayStatus() (Router_GatewayStatusClient, error) { + c.ctx.Debug("Starting GatewayStatus stream") + return c.client.GatewayStatus(c.getContext()) +} + +func (c *routerClientForGateway) Uplink() (Router_UplinkClient, error) { + c.ctx.Debug("Starting Uplink stream") + return c.client.Uplink(c.getContext()) +} + +func (c *routerClientForGateway) Subscribe() (Router_SubscribeClient, context.CancelFunc, error) { + c.ctx.Debug("Starting Subscribe stream") + ctx, cancel := context.WithCancel(c.getContext()) + client, err := c.client.Subscribe(ctx, &SubscribeRequest{}) + return client, cancel, err +} + +func (c *routerClientForGateway) Activate(in *DeviceActivationRequest) (*DeviceActivationResponse, error) { + c.ctx.Debug("Calling Activate") + return c.client.Activate(c.getContext(), in) +} diff --git a/api/router/message_marshaling.go b/api/router/message_marshaling.go new file mode 100644 index 000000000..3e24ca909 --- /dev/null +++ b/api/router/message_marshaling.go @@ -0,0 +1,105 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "errors" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/brocaar/lorawan" +) + +func msgFromPayload(payload []byte) (*pb_protocol.Message, error) { + var phy lorawan.PHYPayload + if err := phy.UnmarshalBinary(payload); err != nil { + return nil, err + } + msg := pb_lorawan.MessageFromPHYPayload(phy) + return &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}}, nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *UplinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DownlinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolConfiguration() != nil && m.ProtocolConfiguration.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationRequest) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +func payloadFromMsg(msg *pb_protocol.Message) ([]byte, error) { + if msg.GetLorawan() == nil { + return nil, errors.New("No LoRaWAN message to marshal") + } + phy := msg.GetLorawan().PHYPayload() + bin, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + return bin, nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *UplinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DownlinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} diff --git a/api/router/message_marshaling_test.go b/api/router/message_marshaling_test.go new file mode 100644 index 000000000..c84e96383 --- /dev/null +++ b/api/router/message_marshaling_test.go @@ -0,0 +1,110 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type payloadMarshalerUnmarshaler interface { + UnmarshalPayload() error + MarshalPayload() error +} + +func TestMarshalUnmarshalPayload(t *testing.T) { + a := New(t) + + var subjects []payloadMarshalerUnmarshaler + + // Do nothing when message and payload are nil + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{}, + &DownlinkMessage{}, + &DeviceActivationRequest{}, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + + rxMeta := &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}} + txConf := &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}} + + macMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_UNCONFIRMED_UP, + }, + Payload: &pb_lorawan.Message_MacPayload{MacPayload: &pb_lorawan.MACPayload{ + FHDR: pb_lorawan.FHDR{ + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + macBin := []byte{65, 4, 3, 2, 1, 0, 1, 0, 0, 1, 2, 3, 4} + joinReqMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_REQUEST, + }, + Payload: &pb_lorawan.Message_JoinRequestPayload{JoinRequestPayload: &pb_lorawan.JoinRequestPayload{ + AppEui: types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEui: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevNonce: types.DevNonce([2]byte{1, 2}), + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinReqBin := []byte{1, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 2, 1, 1, 2, 3, 4} + + // Only Marshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Message: macMsg, + }, + &DownlinkMessage{ + ProtocolConfiguration: txConf, + Message: macMsg, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Message: joinReqMsg, + }, + } + + for _, sub := range subjects { + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + a.So(sub.MarshalPayload(), ShouldEqual, nil) + } + + // Only Unmarshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Payload: macBin, + }, + &DownlinkMessage{ + ProtocolConfiguration: txConf, + Payload: macBin, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Payload: joinReqBin, + }, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + +} diff --git a/api/router/router.pb.go b/api/router/router.pb.go new file mode 100644 index 000000000..d098268db --- /dev/null +++ b/api/router/router.pb.go @@ -0,0 +1,2682 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/router/router.proto +// DO NOT EDIT! + +/* + Package router is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/router/router.proto + + It has these top-level messages: + SubscribeRequest + UplinkMessage + DownlinkMessage + DeviceActivationRequest + DeviceActivationResponse + GatewayStatusRequest + GatewayStatusResponse + StatusRequest + Status +*/ +package router + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/gogo/protobuf/gogoproto" +import api "github.com/TheThingsNetwork/ttn/api" +import protocol "github.com/TheThingsNetwork/ttn/api/protocol" +import gateway "github.com/TheThingsNetwork/ttn/api/gateway" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type SubscribeRequest struct { +} + +func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} } +func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) } +func (*SubscribeRequest) ProtoMessage() {} +func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{0} } + +type UplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,11,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,12,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` +} + +func (m *UplinkMessage) Reset() { *m = UplinkMessage{} } +func (m *UplinkMessage) String() string { return proto.CompactTextString(m) } +func (*UplinkMessage) ProtoMessage() {} +func (*UplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{1} } + +func (m *UplinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *UplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *UplinkMessage) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +type DownlinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + ProtocolConfiguration *protocol.TxConfiguration `protobuf:"bytes,11,opt,name=protocol_configuration,json=protocolConfiguration" json:"protocol_configuration,omitempty"` + GatewayConfiguration *gateway.TxConfiguration `protobuf:"bytes,12,opt,name=gateway_configuration,json=gatewayConfiguration" json:"gateway_configuration,omitempty"` +} + +func (m *DownlinkMessage) Reset() { *m = DownlinkMessage{} } +func (m *DownlinkMessage) String() string { return proto.CompactTextString(m) } +func (*DownlinkMessage) ProtoMessage() {} +func (*DownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{2} } + +func (m *DownlinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DownlinkMessage) GetProtocolConfiguration() *protocol.TxConfiguration { + if m != nil { + return m.ProtocolConfiguration + } + return nil +} + +func (m *DownlinkMessage) GetGatewayConfiguration() *gateway.TxConfiguration { + if m != nil { + return m.GatewayConfiguration + } + return nil +} + +type DeviceActivationRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` +} + +func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } +func (m *DeviceActivationRequest) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationRequest) ProtoMessage() {} +func (*DeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{3} } + +func (m *DeviceActivationRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *DeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + +type DeviceActivationResponse struct { +} + +func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } +func (m *DeviceActivationResponse) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationResponse) ProtoMessage() {} +func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{4} } + +// message GatewayStatusRequest is used to request the status of a gateway from +// this Router +type GatewayStatusRequest struct { + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` +} + +func (m *GatewayStatusRequest) Reset() { *m = GatewayStatusRequest{} } +func (m *GatewayStatusRequest) String() string { return proto.CompactTextString(m) } +func (*GatewayStatusRequest) ProtoMessage() {} +func (*GatewayStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{5} } + +type GatewayStatusResponse struct { + LastSeen int64 `protobuf:"varint,1,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Status *gateway.Status `protobuf:"bytes,2,opt,name=status" json:"status,omitempty"` +} + +func (m *GatewayStatusResponse) Reset() { *m = GatewayStatusResponse{} } +func (m *GatewayStatusResponse) String() string { return proto.CompactTextString(m) } +func (*GatewayStatusResponse) ProtoMessage() {} +func (*GatewayStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{6} } + +func (m *GatewayStatusResponse) GetStatus() *gateway.Status { + if m != nil { + return m.Status + } + return nil +} + +// message StatusRequest is used to request the status of this Router +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{7} } + +// message Status is the response to the StatusRequest +type Status struct { + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + GatewayStatus *api.Rates `protobuf:"bytes,11,opt,name=gateway_status,json=gatewayStatus" json:"gateway_status,omitempty"` + Uplink *api.Rates `protobuf:"bytes,12,opt,name=uplink" json:"uplink,omitempty"` + Downlink *api.Rates `protobuf:"bytes,13,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,14,opt,name=activations" json:"activations,omitempty"` + // Connections + ConnectedGateways uint32 `protobuf:"varint,21,opt,name=connected_gateways,json=connectedGateways,proto3" json:"connected_gateways,omitempty"` + ConnectedBrokers uint32 `protobuf:"varint,22,opt,name=connected_brokers,json=connectedBrokers,proto3" json:"connected_brokers,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{8} } + +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + +func (m *Status) GetGatewayStatus() *api.Rates { + if m != nil { + return m.GatewayStatus + } + return nil +} + +func (m *Status) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *Status) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *Status) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + +func init() { + proto.RegisterType((*SubscribeRequest)(nil), "router.SubscribeRequest") + proto.RegisterType((*UplinkMessage)(nil), "router.UplinkMessage") + proto.RegisterType((*DownlinkMessage)(nil), "router.DownlinkMessage") + proto.RegisterType((*DeviceActivationRequest)(nil), "router.DeviceActivationRequest") + proto.RegisterType((*DeviceActivationResponse)(nil), "router.DeviceActivationResponse") + proto.RegisterType((*GatewayStatusRequest)(nil), "router.GatewayStatusRequest") + proto.RegisterType((*GatewayStatusResponse)(nil), "router.GatewayStatusResponse") + proto.RegisterType((*StatusRequest)(nil), "router.StatusRequest") + proto.RegisterType((*Status)(nil), "router.Status") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Router service + +type RouterClient interface { + // Gateway streams status messages to Router + GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Router_GatewayStatusClient, error) + // Gateway streams uplink messages to Router + Uplink(ctx context.Context, opts ...grpc.CallOption) (Router_UplinkClient, error) + // Gateway subscribes to downlink messages from Router + // It is possible to open multiple subscriptions (but not recommended). + // If you do this, you are responsible for de-duplication of downlink messages. + Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Router_SubscribeClient, error) + // Gateway requests device activation + Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) +} + +type routerClient struct { + cc *grpc.ClientConn +} + +func NewRouterClient(cc *grpc.ClientConn) RouterClient { + return &routerClient{cc} +} + +func (c *routerClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Router_GatewayStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Router_serviceDesc.Streams[0], c.cc, "/router.Router/GatewayStatus", opts...) + if err != nil { + return nil, err + } + x := &routerGatewayStatusClient{stream} + return x, nil +} + +type Router_GatewayStatusClient interface { + Send(*gateway.Status) error + CloseAndRecv() (*google_protobuf.Empty, error) + grpc.ClientStream +} + +type routerGatewayStatusClient struct { + grpc.ClientStream +} + +func (x *routerGatewayStatusClient) Send(m *gateway.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *routerGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Uplink(ctx context.Context, opts ...grpc.CallOption) (Router_UplinkClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Router_serviceDesc.Streams[1], c.cc, "/router.Router/Uplink", opts...) + if err != nil { + return nil, err + } + x := &routerUplinkClient{stream} + return x, nil +} + +type Router_UplinkClient interface { + Send(*UplinkMessage) error + CloseAndRecv() (*google_protobuf.Empty, error) + grpc.ClientStream +} + +type routerUplinkClient struct { + grpc.ClientStream +} + +func (x *routerUplinkClient) Send(m *UplinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *routerUplinkClient) CloseAndRecv() (*google_protobuf.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Router_SubscribeClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Router_serviceDesc.Streams[2], c.cc, "/router.Router/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &routerSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Router_SubscribeClient interface { + Recv() (*DownlinkMessage, error) + grpc.ClientStream +} + +type routerSubscribeClient struct { + grpc.ClientStream +} + +func (x *routerSubscribeClient) Recv() (*DownlinkMessage, error) { + m := new(DownlinkMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { + out := new(DeviceActivationResponse) + err := grpc.Invoke(ctx, "/router.Router/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Router service + +type RouterServer interface { + // Gateway streams status messages to Router + GatewayStatus(Router_GatewayStatusServer) error + // Gateway streams uplink messages to Router + Uplink(Router_UplinkServer) error + // Gateway subscribes to downlink messages from Router + // It is possible to open multiple subscriptions (but not recommended). + // If you do this, you are responsible for de-duplication of downlink messages. + Subscribe(*SubscribeRequest, Router_SubscribeServer) error + // Gateway requests device activation + Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) +} + +func RegisterRouterServer(s *grpc.Server, srv RouterServer) { + s.RegisterService(&_Router_serviceDesc, srv) +} + +func _Router_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(RouterServer).GatewayStatus(&routerGatewayStatusServer{stream}) +} + +type Router_GatewayStatusServer interface { + SendAndClose(*google_protobuf.Empty) error + Recv() (*gateway.Status, error) + grpc.ServerStream +} + +type routerGatewayStatusServer struct { + grpc.ServerStream +} + +func (x *routerGatewayStatusServer) SendAndClose(m *google_protobuf.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *routerGatewayStatusServer) Recv() (*gateway.Status, error) { + m := new(gateway.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Router_Uplink_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(RouterServer).Uplink(&routerUplinkServer{stream}) +} + +type Router_UplinkServer interface { + SendAndClose(*google_protobuf.Empty) error + Recv() (*UplinkMessage, error) + grpc.ServerStream +} + +type routerUplinkServer struct { + grpc.ServerStream +} + +func (x *routerUplinkServer) SendAndClose(m *google_protobuf.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *routerUplinkServer) Recv() (*UplinkMessage, error) { + m := new(UplinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Router_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RouterServer).Subscribe(m, &routerSubscribeServer{stream}) +} + +type Router_SubscribeServer interface { + Send(*DownlinkMessage) error + grpc.ServerStream +} + +type routerSubscribeServer struct { + grpc.ServerStream +} + +func (x *routerSubscribeServer) Send(m *DownlinkMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _Router_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).Activate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.Router/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).Activate(ctx, req.(*DeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Router_serviceDesc = grpc.ServiceDesc{ + ServiceName: "router.Router", + HandlerType: (*RouterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Activate", + Handler: _Router_Activate_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "GatewayStatus", + Handler: _Router_GatewayStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "Uplink", + Handler: _Router_Uplink_Handler, + ClientStreams: true, + }, + { + StreamName: "Subscribe", + Handler: _Router_Subscribe_Handler, + ServerStreams: true, + }, + }, + Metadata: "github.com/TheThingsNetwork/ttn/api/router/router.proto", +} + +// Client API for RouterManager service + +type RouterManagerClient interface { + // Gateway owner or network operator requests Gateway status from Router Manager + GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) + // Network operator requests Router status + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) +} + +type routerManagerClient struct { + cc *grpc.ClientConn +} + +func NewRouterManagerClient(cc *grpc.ClientConn) RouterManagerClient { + return &routerManagerClient{cc} +} + +func (c *routerManagerClient) GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) { + out := new(GatewayStatusResponse) + err := grpc.Invoke(ctx, "/router.RouterManager/GatewayStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/router.RouterManager/GetStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for RouterManager service + +type RouterManagerServer interface { + // Gateway owner or network operator requests Gateway status from Router Manager + GatewayStatus(context.Context, *GatewayStatusRequest) (*GatewayStatusResponse, error) + // Network operator requests Router status + GetStatus(context.Context, *StatusRequest) (*Status, error) +} + +func RegisterRouterManagerServer(s *grpc.Server, srv RouterManagerServer) { + s.RegisterService(&_RouterManager_serviceDesc, srv) +} + +func _RouterManager_GatewayStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GatewayStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterManagerServer).GatewayStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/GatewayStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).GatewayStatus(ctx, req.(*GatewayStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RouterManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterManagerServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/GetStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).GetStatus(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _RouterManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "router.RouterManager", + HandlerType: (*RouterManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GatewayStatus", + Handler: _RouterManager_GatewayStatus_Handler, + }, + { + MethodName: "GetStatus", + Handler: _RouterManager_GetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/TheThingsNetwork/ttn/api/router/router.proto", +} + +func (m *SubscribeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SubscribeRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *UplinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *UplinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRouter(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Message.Size())) + n1, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.ProtocolMetadata != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n2, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.GatewayMetadata != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayMetadata.Size())) + n3, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *DownlinkMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DownlinkMessage) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRouter(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Message.Size())) + n4, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.ProtocolConfiguration != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.ProtocolConfiguration.Size())) + n5, err := m.ProtocolConfiguration.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.GatewayConfiguration != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayConfiguration.Size())) + n6, err := m.GatewayConfiguration.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} + +func (m *DeviceActivationRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceActivationRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRouter(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) + } + if m.Message != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Message.Size())) + n7, err := m.Message.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.DevEui != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.DevEui.Size())) + n8, err := m.DevEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + if m.AppEui != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.AppEui.Size())) + n9, err := m.AppEui.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n9 + } + if m.ProtocolMetadata != nil { + dAtA[i] = 0xaa + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n10, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + } + if m.GatewayMetadata != nil { + dAtA[i] = 0xb2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayMetadata.Size())) + n11, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 + } + if m.ActivationMetadata != nil { + dAtA[i] = 0xba + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.ActivationMetadata.Size())) + n12, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n12 + } + return i, nil +} + +func (m *DeviceActivationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceActivationResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *GatewayStatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GatewayStatusRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayId) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRouter(dAtA, i, uint64(len(m.GatewayId))) + i += copy(dAtA[i:], m.GatewayId) + } + return i, nil +} + +func (m *GatewayStatusResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GatewayStatusResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.LastSeen != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.LastSeen)) + } + if m.Status != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Status.Size())) + n13, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n13 + } + return i, nil +} + +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.System != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.System.Size())) + n14, err := m.System.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n14 + } + if m.Component != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Component.Size())) + n15, err := m.Component.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n15 + } + if m.GatewayStatus != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayStatus.Size())) + n16, err := m.GatewayStatus.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n16 + } + if m.Uplink != nil { + dAtA[i] = 0x62 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Uplink.Size())) + n17, err := m.Uplink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n17 + } + if m.Downlink != nil { + dAtA[i] = 0x6a + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Downlink.Size())) + n18, err := m.Downlink.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n18 + } + if m.Activations != nil { + dAtA[i] = 0x72 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.Activations.Size())) + n19, err := m.Activations.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n19 + } + if m.ConnectedGateways != 0 { + dAtA[i] = 0xa8 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.ConnectedGateways)) + } + if m.ConnectedBrokers != 0 { + dAtA[i] = 0xb0 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintRouter(dAtA, i, uint64(m.ConnectedBrokers)) + } + return i, nil +} + +func encodeFixed64Router(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Router(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintRouter(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *SubscribeRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *UplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *DownlinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolConfiguration != nil { + l = m.ProtocolConfiguration.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayConfiguration != nil { + l = m.GatewayConfiguration.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *DeviceActivationRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovRouter(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 2 + l + sovRouter(uint64(l)) + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *DeviceActivationResponse) Size() (n int) { + var l int + _ = l + return n +} + +func (m *GatewayStatusRequest) Size() (n int) { + var l int + _ = l + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *GatewayStatusResponse) Size() (n int) { + var l int + _ = l + if m.LastSeen != 0 { + n += 1 + sovRouter(uint64(m.LastSeen)) + } + if m.Status != nil { + l = m.Status.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *Status) Size() (n int) { + var l int + _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayStatus != nil { + l = m.GatewayStatus.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.ConnectedGateways != 0 { + n += 2 + sovRouter(uint64(m.ConnectedGateways)) + } + if m.ConnectedBrokers != 0 { + n += 2 + sovRouter(uint64(m.ConnectedBrokers)) + } + return n +} + +func sovRouter(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRouter(x uint64) (n int) { + return sovRouter(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *SubscribeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubscribeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubscribeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DownlinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DownlinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DownlinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolConfiguration == nil { + m.ProtocolConfiguration = &protocol.TxConfiguration{} + } + if err := m.ProtocolConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayConfiguration == nil { + m.GatewayConfiguration = &gateway.TxConfiguration{} + } + if err := m.GatewayConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GatewayStatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GatewayStatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GatewayStatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GatewayStatusResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GatewayStatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GatewayStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastSeen", wireType) + } + m.LastSeen = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LastSeen |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Status == nil { + m.Status = &gateway.Status{} + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.System == nil { + m.System = &api.SystemStats{} + } + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayStatus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayStatus == nil { + m.GatewayStatus = &api.Rates{} + } + if err := m.GatewayStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedGateways", wireType) + } + m.ConnectedGateways = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConnectedGateways |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedBrokers", wireType) + } + m.ConnectedBrokers = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConnectedBrokers |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipRouter(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRouter(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRouter + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRouter(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRouter = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRouter = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/router/router.proto", fileDescriptorRouter) +} + +var fileDescriptorRouter = []byte{ + // 853 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x6f, 0xe3, 0x44, + 0x14, 0xc7, 0x5d, 0xc9, 0x6d, 0x5e, 0xeb, 0x36, 0x9d, 0x36, 0xad, 0xc9, 0x6e, 0x3f, 0xe4, 0x03, + 0x54, 0x2c, 0xeb, 0xd0, 0xa0, 0x15, 0x02, 0x21, 0x44, 0xbb, 0xad, 0x56, 0x2b, 0x91, 0x15, 0x72, + 0xbb, 0x17, 0x24, 0x14, 0x4d, 0x9c, 0xb7, 0xae, 0xd5, 0xc4, 0x63, 0x3c, 0xe3, 0x74, 0xf3, 0x5f, + 0xc0, 0x8d, 0x3f, 0x89, 0x23, 0xe2, 0x06, 0x07, 0x84, 0xca, 0x9d, 0x3b, 0x37, 0xe4, 0xf9, 0xb0, + 0xe3, 0xb4, 0x0b, 0x15, 0xb0, 0xa7, 0x78, 0x7e, 0xef, 0xf7, 0x7e, 0x33, 0xef, 0x63, 0xe6, 0x05, + 0x3e, 0x8a, 0x62, 0x71, 0x91, 0x0f, 0xfc, 0x90, 0x8d, 0x3b, 0xe7, 0x17, 0x78, 0x7e, 0x11, 0x27, + 0x11, 0x7f, 0x8e, 0xe2, 0x8a, 0x65, 0x97, 0x1d, 0x21, 0x92, 0x0e, 0x4d, 0xe3, 0x4e, 0xc6, 0x72, + 0x81, 0x99, 0xfe, 0xf1, 0xd3, 0x8c, 0x09, 0x46, 0x6c, 0xb5, 0x6a, 0xdf, 0x8f, 0x18, 0x8b, 0x46, + 0xd8, 0x91, 0xe8, 0x20, 0x7f, 0xd9, 0xc1, 0x71, 0x2a, 0xa6, 0x8a, 0xd4, 0x7e, 0x34, 0xa3, 0x1e, + 0xb1, 0x88, 0x55, 0xac, 0x62, 0x25, 0x17, 0xf2, 0x4b, 0xd3, 0xd7, 0xcd, 0x86, 0x34, 0x8d, 0x35, + 0xb4, 0x67, 0x20, 0xb9, 0x0c, 0xd9, 0xa8, 0xfc, 0xd0, 0x84, 0x1d, 0x43, 0x88, 0xa8, 0xc0, 0x2b, + 0x3a, 0x35, 0xbf, 0xca, 0xec, 0x11, 0x68, 0x9e, 0xe5, 0x03, 0x1e, 0x66, 0xf1, 0x00, 0x03, 0xfc, + 0x26, 0x47, 0x2e, 0xbc, 0x9f, 0x2d, 0x70, 0x5e, 0xa4, 0xa3, 0x38, 0xb9, 0xec, 0x21, 0xe7, 0x34, + 0x42, 0xe2, 0xc2, 0x62, 0x4a, 0xa7, 0x23, 0x46, 0x87, 0xae, 0xb5, 0x6f, 0x1d, 0xac, 0x04, 0x66, + 0x49, 0x1e, 0xc2, 0xe2, 0x58, 0x91, 0xdc, 0x85, 0x7d, 0xeb, 0x60, 0xb9, 0xbb, 0xee, 0x97, 0x07, + 0xd0, 0xde, 0x81, 0x61, 0x90, 0x23, 0x58, 0x37, 0xc6, 0xfe, 0x18, 0x05, 0x1d, 0x52, 0x41, 0xdd, + 0x65, 0xe9, 0xb6, 0x59, 0xb9, 0x05, 0xaf, 0x7a, 0xda, 0x16, 0x34, 0x0d, 0x68, 0x10, 0xf2, 0x19, + 0x34, 0x75, 0x00, 0x95, 0xc2, 0x8a, 0x54, 0xd8, 0xf0, 0x4d, 0x64, 0x33, 0x02, 0x6b, 0x1a, 0x33, + 0x80, 0xf7, 0xa7, 0x05, 0x6b, 0x27, 0xec, 0x2a, 0x79, 0x03, 0xd1, 0x7d, 0x09, 0x5b, 0x65, 0x74, + 0x21, 0x4b, 0x5e, 0xc6, 0x51, 0x9e, 0x51, 0x11, 0xb3, 0x44, 0x87, 0xf8, 0x76, 0xe5, 0x7b, 0xfe, + 0xea, 0xc9, 0x2c, 0x21, 0x68, 0x19, 0x4b, 0x0d, 0x26, 0x3d, 0x68, 0x99, 0x60, 0xeb, 0x82, 0x2a, + 0x62, 0xb7, 0x8c, 0x78, 0x5e, 0x6f, 0x53, 0x1b, 0x6a, 0xa8, 0xf7, 0xd3, 0x3d, 0xd8, 0x3e, 0xc1, + 0x49, 0x1c, 0xe2, 0x51, 0x28, 0xe2, 0x89, 0xa2, 0xaa, 0x9a, 0xff, 0x5f, 0x39, 0x78, 0x0e, 0x8b, + 0x43, 0x9c, 0xf4, 0x31, 0x8f, 0x65, 0xd0, 0x2b, 0xc7, 0x8f, 0x7f, 0xf9, 0x75, 0xef, 0xf0, 0x9f, + 0xee, 0x50, 0xc8, 0x32, 0xec, 0x88, 0x69, 0x8a, 0xdc, 0x3f, 0xc1, 0xc9, 0xe9, 0x8b, 0x67, 0x81, + 0x3d, 0xc4, 0xc9, 0x69, 0x1e, 0x17, 0x7a, 0x34, 0x4d, 0xa5, 0xde, 0xca, 0xbf, 0xd2, 0x3b, 0x4a, + 0x53, 0xa9, 0x47, 0xd3, 0xb4, 0xd0, 0xbb, 0xb5, 0x03, 0x5b, 0xff, 0xb9, 0x03, 0xb7, 0xee, 0xde, + 0x81, 0xa4, 0x07, 0x1b, 0xb4, 0x4c, 0x7f, 0x25, 0xb1, 0x2d, 0x25, 0x1e, 0x54, 0x87, 0xa8, 0x6a, + 0x54, 0x6a, 0x11, 0x7a, 0x03, 0xf3, 0xda, 0xe0, 0xde, 0xac, 0x29, 0x4f, 0x59, 0xc2, 0xd1, 0x7b, + 0x0c, 0x9b, 0x4f, 0xd5, 0xee, 0x67, 0x82, 0x8a, 0x9c, 0x9b, 0x62, 0xef, 0x00, 0x98, 0x10, 0x62, + 0x55, 0xef, 0x46, 0xd0, 0xd0, 0xc8, 0xb3, 0xa1, 0xf7, 0x35, 0xb4, 0xe6, 0xdc, 0x94, 0x1e, 0xb9, + 0x0f, 0x8d, 0x11, 0xe5, 0xa2, 0xcf, 0x11, 0x13, 0xe9, 0x76, 0x2f, 0x58, 0x2a, 0x80, 0x33, 0xc4, + 0x84, 0xbc, 0x0b, 0x36, 0x97, 0x74, 0xdd, 0x26, 0x6b, 0x65, 0x36, 0xb4, 0x8a, 0x36, 0x7b, 0x6b, + 0xe0, 0xd4, 0x8e, 0xe3, 0xfd, 0xb1, 0x00, 0xb6, 0x42, 0xc8, 0x01, 0xd8, 0x7c, 0xca, 0x05, 0x8e, + 0xa5, 0xfc, 0x72, 0xb7, 0xe9, 0x17, 0x4f, 0xdd, 0x99, 0x84, 0x0a, 0x4a, 0xa1, 0x22, 0x17, 0xe4, + 0x10, 0x1a, 0x21, 0x1b, 0xa7, 0x2c, 0xc1, 0x44, 0xe8, 0x1d, 0x37, 0x24, 0xf9, 0x89, 0x41, 0x15, + 0xbf, 0x62, 0x91, 0x43, 0x58, 0x35, 0x61, 0xeb, 0x93, 0xaa, 0x8b, 0x09, 0xd2, 0x2f, 0xa0, 0x02, + 0x79, 0xe0, 0x44, 0xb3, 0x91, 0x13, 0x0f, 0xec, 0x5c, 0xbe, 0x84, 0xfa, 0xca, 0xcd, 0x52, 0xb5, + 0x85, 0xbc, 0x03, 0x4b, 0x43, 0xfd, 0xa2, 0xb8, 0xce, 0x0d, 0x56, 0x69, 0x23, 0xef, 0xc3, 0x72, + 0x55, 0x3f, 0xee, 0xae, 0xde, 0xa0, 0xce, 0x9a, 0xc9, 0x23, 0x20, 0x21, 0x4b, 0x12, 0x0c, 0x05, + 0x0e, 0xfb, 0xfa, 0x50, 0x5c, 0xb6, 0xaa, 0x13, 0xac, 0x97, 0x16, 0x5d, 0x27, 0x4e, 0x1e, 0x42, + 0x05, 0xf6, 0x07, 0x19, 0xbb, 0xc4, 0x8c, 0xcb, 0xb6, 0x74, 0x82, 0x66, 0x69, 0x38, 0x56, 0x78, + 0xf7, 0xdb, 0x05, 0xb0, 0x03, 0x39, 0x9e, 0xc8, 0x27, 0xe0, 0xd4, 0x6a, 0x4d, 0xe6, 0xcb, 0xd6, + 0xde, 0xf2, 0xd5, 0x04, 0xf3, 0xcd, 0x6c, 0xf2, 0x4f, 0x8b, 0x09, 0x76, 0x60, 0x91, 0x8f, 0xc1, + 0x56, 0x63, 0x82, 0xb4, 0x7c, 0x3d, 0xfb, 0x6a, 0x63, 0xe3, 0x6f, 0x5c, 0x3f, 0x87, 0x46, 0x39, + 0x76, 0x88, 0x6b, 0xbc, 0xe7, 0x27, 0x51, 0x7b, 0xdb, 0x58, 0xe6, 0x9e, 0xec, 0x0f, 0x2c, 0xd2, + 0x83, 0x25, 0xdd, 0xf1, 0x48, 0xf6, 0x4a, 0xda, 0xed, 0xaf, 0x5b, 0x7b, 0xff, 0xf5, 0x04, 0xd5, + 0xda, 0xdd, 0xef, 0x2c, 0x70, 0x54, 0x4a, 0x7a, 0x34, 0xa1, 0x11, 0x66, 0xe4, 0x8b, 0xf9, 0xcc, + 0x3c, 0x30, 0x22, 0xb7, 0xdd, 0xa9, 0xf6, 0xce, 0x6b, 0xac, 0xfa, 0xea, 0x74, 0xa1, 0xf1, 0x14, + 0x85, 0x56, 0x2a, 0xd3, 0x55, 0x97, 0x58, 0xad, 0xc3, 0xc7, 0x9f, 0xfe, 0x70, 0xbd, 0x6b, 0xfd, + 0x78, 0xbd, 0x6b, 0xfd, 0x76, 0xbd, 0x6b, 0x7d, 0xff, 0xfb, 0xee, 0x5b, 0x5f, 0xbd, 0x77, 0xf7, + 0x7f, 0x23, 0x03, 0x5b, 0x26, 0xfd, 0xc3, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x43, 0x88, 0x6d, + 0x23, 0xc2, 0x08, 0x00, 0x00, +} diff --git a/api/router/router.proto b/api/router/router.proto new file mode 100644 index 000000000..da1ed4330 --- /dev/null +++ b/api/router/router.proto @@ -0,0 +1,105 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "ttn/api/api.proto"; +import "ttn/api/protocol/protocol.proto"; +import "ttn/api/gateway/gateway.proto"; + +package router; + +option go_package = "github.com/TheThingsNetwork/ttn/api/router"; + +message SubscribeRequest {} + +message UplinkMessage { + bytes payload = 1; + protocol.Message message = 2; + protocol.RxMetadata protocol_metadata = 11; + gateway.RxMetadata gateway_metadata = 12; +} + +message DownlinkMessage { + bytes payload = 1; + protocol.Message message = 2; + protocol.TxConfiguration protocol_configuration = 11; + gateway.TxConfiguration gateway_configuration = 12; +} + +message DeviceActivationRequest { + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + protocol.ActivationMetadata activation_metadata = 23; +} + +message DeviceActivationResponse { + // NOTE: In LoRaWAN, device activations are accepted with DownlinkMessages, so + // this message is just an Ack. + // + // bytes payload = 1; + // protocol.Message message = 2; + // protocol.TxConfiguration protocol_configuration = 11; + // gateway.TxConfiguration gateway_configuration = 12; +} + +// The Router service provides pure network functionality +service Router { + // Gateway streams status messages to Router + rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); + + // Gateway streams uplink messages to Router + rpc Uplink(stream UplinkMessage) returns (google.protobuf.Empty); + + // Gateway subscribes to downlink messages from Router + // It is possible to open multiple subscriptions (but not recommended). + // If you do this, you are responsible for de-duplication of downlink messages. + rpc Subscribe(SubscribeRequest) returns (stream DownlinkMessage); + + // Gateway requests device activation + rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); +} + +// message GatewayStatusRequest is used to request the status of a gateway from +// this Router +message GatewayStatusRequest { + string gateway_id = 1; +} + +message GatewayStatusResponse { + int64 last_seen = 1; + gateway.Status status = 2; +} + +// message StatusRequest is used to request the status of this Router +message StatusRequest {} + +// message Status is the response to the StatusRequest +message Status { + api.SystemStats system = 1; + api.ComponentStats component = 2; + + api.Rates gateway_status = 11; + api.Rates uplink = 12; + api.Rates downlink = 13; + api.Rates activations = 14; + + // Connections + uint32 connected_gateways = 21; + uint32 connected_brokers = 22; +} + +// The RouterManager service provides configuration and monitoring functionality +service RouterManager { + // Gateway owner or network operator requests Gateway status from Router Manager + rpc GatewayStatus(GatewayStatusRequest) returns (GatewayStatusResponse); + + // Network operator requests Router status + rpc GetStatus(StatusRequest) returns (Status); +} diff --git a/api/router/server_streams.go b/api/router/server_streams.go new file mode 100644 index 000000000..e48ff0e1f --- /dev/null +++ b/api/router/server_streams.go @@ -0,0 +1,119 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "io" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc/metadata" +) + +// RouterStreamServer handles gRPC streams as channels +type RouterStreamServer struct { + ctx log.Interface + UplinkChanFunc func(md metadata.MD) (ch chan *UplinkMessage, err error) + GatewayStatusChanFunc func(md metadata.MD) (ch chan *gateway.Status, err error) + DownlinkChanFunc func(md metadata.MD) (ch <-chan *DownlinkMessage, cancel func(), err error) +} + +// NewRouterStreamServer returns a new RouterStreamServer +func NewRouterStreamServer() *RouterStreamServer { + return &RouterStreamServer{ + ctx: log.Get(), + } +} + +// SetLogger sets the logger +func (s *RouterStreamServer) SetLogger(logger log.Interface) { + s.ctx = logger +} + +// Uplink handles uplink streams +func (s *RouterStreamServer) Uplink(stream Router_UplinkServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, err := s.UplinkChanFunc(md) + if err != nil { + return err + } + defer func() { + ctx := s.ctx + if err != nil { + ctx = ctx.WithError(err) + } + close(ch) + ctx.Debug("Closed Uplink stream") + }() + for { + uplink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + if err := uplink.Validate(); err != nil { + return errors.Wrap(err, "Invalid Uplink") + } + ch <- uplink + } +} + +// Subscribe handles downlink streams +func (s *RouterStreamServer) Subscribe(req *SubscribeRequest, stream Router_SubscribeServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, cancel, err := s.DownlinkChanFunc(md) + if err != nil { + return err + } + go func() { + <-stream.Context().Done() + err = stream.Context().Err() + cancel() + }() + for downlink := range ch { + if err := stream.Send(downlink); err != nil { + return err + } + } + return +} + +// GatewayStatus handles gateway status streams +func (s *RouterStreamServer) GatewayStatus(stream Router_GatewayStatusServer) error { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, err := s.GatewayStatusChanFunc(md) + if err != nil { + return err + } + defer func() { + close(ch) + }() + for { + status, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + if err := status.Validate(); err != nil { + return errors.Wrap(err, "Invalid Gateway Status") + } + ch <- status + } +} diff --git a/api/router/validation.go b/api/router/validation.go new file mode 100644 index 000000000..adc55fcfb --- /dev/null +++ b/api/router/validation.go @@ -0,0 +1,54 @@ +package router + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validate implements the api.Validator interface +func (m *UplinkMessage) Validate() error { + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err + } + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DownlinkMessage) Validate() error { + if err := api.NotNilAndValid(m.ProtocolConfiguration, "ProtocolConfiguration"); err != nil { + return err + } + if err := api.NotNilAndValid(m.GatewayConfiguration, "GatewayConfiguration"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} + +// Validate implements the api.Validator interface +func (m *DeviceActivationRequest) Validate() error { + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err + } + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err + } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } + return nil +} diff --git a/api/stats/stats.go b/api/stats/stats.go new file mode 100644 index 000000000..fc6395c61 --- /dev/null +++ b/api/stats/stats.go @@ -0,0 +1,72 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package stats + +import ( + "os" + "runtime" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/load" + "github.com/shirou/gopsutil/mem" + "github.com/shirou/gopsutil/process" +) + +var startTime = time.Now() + +// GetSystem gets statistics about the system +func GetSystem() *api.SystemStats { + status := new(api.SystemStats) + if load, err := load.Avg(); err == nil { + status.Load = &api.SystemStats_Loadstats{ + Load1: float32(load.Load1), + Load5: float32(load.Load5), + Load15: float32(load.Load15), + } + } + if cpu, err := cpu.Times(false); err == nil && len(cpu) == 1 { + status.Cpu = &api.SystemStats_CPUStats{ + User: float32(cpu[0].User), + System: float32(cpu[0].System), + Idle: float32(cpu[0].Idle), + } + } + if mem, err := mem.VirtualMemory(); err == nil { + status.Memory = &api.SystemStats_MemoryStats{ + Total: mem.Total, + Available: mem.Available, + Used: mem.Used, + } + } + return status +} + +// GetComponent gets statistics about this component +func GetComponent() *api.ComponentStats { + status := new(api.ComponentStats) + status.Uptime = uint64(time.Now().Sub(startTime).Seconds()) + process, err := process.NewProcess(int32(os.Getpid())) + if err == nil { + if memory, err := process.MemoryInfo(); err == nil { + status.Memory = &api.ComponentStats_MemoryStats{ + Memory: memory.RSS, + Swap: memory.Swap, + } + } + if cpu, err := process.Times(); err == nil { + status.Cpu = &api.ComponentStats_CPUStats{ + User: float32(cpu.User), + System: float32(cpu.System), + Idle: float32(cpu.Idle), + } + } + } + status.Goroutines = uint64(runtime.NumGoroutine()) + memstats := new(runtime.MemStats) + runtime.ReadMemStats(memstats) + status.GcCpuFraction = float32(memstats.GCCPUFraction) + return status +} diff --git a/api/validation.go b/api/validation.go new file mode 100644 index 000000000..389d8dd65 --- /dev/null +++ b/api/validation.go @@ -0,0 +1,61 @@ +package api + +import ( + "reflect" + "regexp" + + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Validator interface is used to validate protos +type Validator interface { + // Returns the validation error or nil if valid + Validate() error +} + +// Validate the given object if it implements the Validator interface +// Must not be called with nil values! +func Validate(in interface{}) error { + if v, ok := in.(Validator); ok { + return v.Validate() + } + return nil +} + +var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") + +// ValidID returns true if the given ID is a valid application or device ID +func ValidID(id string) bool { + return idRegexp.MatchString(id) +} + +// NotEmptyAndValidID checks if the ID is not empty AND has a valid format +func NotEmptyAndValidID(id string, argument string) error { + if id == "" { + return errors.NewErrInvalidArgument(argument, "can not be empty") + } + if !ValidID(id) { + return errors.NewErrInvalidArgument(argument, "has wrong format "+id) + } + return nil +} + +// NotNilAndValid checks if the given interface is not nil AND validates it +func NotNilAndValid(in interface{}, argument string) error { + // Structs can not be nil and reflect.ValueOf(in).IsNil() would panic + if reflect.ValueOf(in).Kind() == reflect.Struct { + return errors.Wrap(Validate(in), "Invalid "+argument) + } + + // We need to check for the interface to be nil and the value of the interface + // See: https://stackoverflow.com/questions/13476349/check-for-nil-and-nil-interface-in-go + if in == nil || reflect.ValueOf(in).IsNil() { + return errors.NewErrInvalidArgument(argument, "can not be empty") + } + + if err := Validate(in); err != nil { + return errors.Wrap(Validate(in), "Invalid "+argument) + } + + return nil +} diff --git a/api/validation_test.go b/api/validation_test.go new file mode 100644 index 000000000..31c53a0d7 --- /dev/null +++ b/api/validation_test.go @@ -0,0 +1,88 @@ +package api + +import ( + "testing" +) + +func TestValidate_nil(t *testing.T) { + Validate(nil) +} + +type invalid struct { + nestedPtr *interface{} +} + +func (i invalid) Validate() error { + return NotNilAndValid(i.nestedPtr, "nestedPtr") +} + +func TestNotNilAndValidStruct(t *testing.T) { + subject := invalid{} + + err := NotNilAndValid(subject, "subject") + if err == nil || err.Error() != "Invalid subject: nestedPtr not valid: can not be empty" { + t.Error("Expected validation error: 'Invalid subject: nestedPtr not valid: can not be empty", err) + } +} + +func TestNotNilAndValidPtr(t *testing.T) { + subject := &invalid{} + err := NotNilAndValid(subject, "subject") + if err == nil || err.Error() != "Invalid subject: nestedPtr not valid: can not be empty" { + t.Error("Expected validation error: 'Invalid subject: nestedPtr not valid: can not be empty", err) + } +} + +type testInterface interface { + Nothing() +} + +func TestNotNilAndValidDifferentTypes(t *testing.T) { + err := NotNilAndValid(struct{}{}, "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(&struct{}{}, "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(func() {}, "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(make(chan byte), "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(make([]byte, 0), "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(make(map[byte]byte, 0), "subject") + if err != nil { + t.Error("Expected nil but got", err) + } +} + +func TestValidID(t *testing.T) { + if ValidID("a") { + t.Error("'a' is not a valid id") + } +} + +func TestNotEmptyAndValidID(t *testing.T) { + err := NotEmptyAndValidID("", "subject") + if err == nil || err.Error() != "subject not valid: can not be empty" { + t.Error("Expected validation error: 'subject not valid: can not be empty' but found", err) + } + + err = NotEmptyAndValidID("a", "subject") + if err == nil || err.Error() != "subject not valid: has wrong format a" { + t.Error("Expected validation error: 'subject not valid: has wrong format a' but found", err) + } +} diff --git a/check_upstream.sh b/check_upstream.sh new file mode 100755 index 000000000..9bf4ad434 --- /dev/null +++ b/check_upstream.sh @@ -0,0 +1,12 @@ +#!/bin/bash +branch=$(git rev-parse --abbrev-ref HEAD) +for remote in $(git remote) +do + status=$(git rev-list --left-right --count HEAD...$remote/$branch 2>/dev/null) + (( !$? )) || status="0 0" + IFS=' ' read -ra changes <<< "$status" + up=${changes[0]} + down=${changes[1]} + if [[ $up -eq 0 ]] && [[ $down -eq 0 ]]; then continue; fi + echo "Remote $remote: ⬆️ ${changes[0]} ⬇️ ${changes[1]}" +done diff --git a/cmd/broker.go b/cmd/broker.go new file mode 100644 index 000000000..6806156ba --- /dev/null +++ b/cmd/broker.go @@ -0,0 +1,106 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "os/signal" + "syscall" + "time" + + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/core/broker" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// brokerCmd represents the broker command +var brokerCmd = &cobra.Command{ + Use: "broker", + Short: "The Things Network broker", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "Server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port")), + "NetworkServer": viper.GetString("broker.networkserver-address"), + "DeduplicationDelay": viper.GetString("broker.deduplication-delay"), + }).Info("Initializing Broker") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Component + component, err := component.New(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } + + var nsCert string + if nsCertFile := viper.GetString("broker.networkserver-cert"); nsCertFile != "" { + contents, err := ioutil.ReadFile(nsCertFile) + if err != nil { + ctx.WithError(err).Fatal("Could not get Networkserver certificate") + } + nsCert = string(contents) + } + + // Broker + broker := broker.NewBroker( + time.Duration(viper.GetInt("broker.deduplication-delay")) * time.Millisecond, + ) + broker.SetNetworkServer(viper.GetString("broker.networkserver-address"), nsCert, viper.GetString("broker.networkserver-token")) + err = broker.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize broker") + } + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + component.RegisterHealthServer(grpc) + broker.RegisterRPC(grpc) + broker.RegisterManager(grpc) + go grpc.Serve(lis) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() + broker.Shutdown() + }, +} + +func init() { + RootCmd.AddCommand(brokerCmd) + + brokerCmd.Flags().String("networkserver-address", "localhost:1903", "Networkserver host and port") + viper.BindPFlag("broker.networkserver-address", brokerCmd.Flags().Lookup("networkserver-address")) + brokerCmd.Flags().String("networkserver-cert", "", "Networkserver certificate to use") + viper.BindPFlag("broker.networkserver-cert", brokerCmd.Flags().Lookup("networkserver-cert")) + brokerCmd.Flags().String("networkserver-token", "", "Networkserver token to use") + viper.BindPFlag("broker.networkserver-token", brokerCmd.Flags().Lookup("networkserver-token")) + + brokerCmd.Flags().Int("deduplication-delay", 200, "Deduplication delay (in ms)") + viper.BindPFlag("broker.deduplication-delay", brokerCmd.Flags().Lookup("deduplication-delay")) + + brokerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + brokerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + brokerCmd.Flags().Int("server-port", 1902, "The port for communication") + viper.BindPFlag("broker.server-address", brokerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("broker.server-address-announce", brokerCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("broker.server-port", brokerCmd.Flags().Lookup("server-port")) +} diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go new file mode 100644 index 000000000..4f186ba96 --- /dev/null +++ b/cmd/broker_register_prefix.go @@ -0,0 +1,84 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc/metadata" +) + +// brokerRegisterPrefixCmd represents the secure command +var brokerRegisterPrefixCmd = &cobra.Command{ + Use: "register-prefix [prefix ...]", + Short: "Register a prefix to this Broker", + Long: `ttn broker register prefix registers a prefix to this Broker`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + path := filepath.Clean(viper.GetString("key-dir") + "/ca.cert") + cert, err := ioutil.ReadFile(path) + if err == nil && !api.RootCAs.AppendCertsFromPEM(cert) { + ctx.Warnf("Could not add root certificates from %s", path) + } + + conn, err := api.Dial(viper.GetString("discovery-address")) + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Discovery server") + } + client := discovery.NewDiscoveryClient(conn) + + client.GetAll(context.Background(), &discovery.GetServiceRequest{}) + + md := metadata.Pairs( + "service-name", "broker", + "id", viper.GetString("id"), + "token", viper.GetString("auth-token"), + ) + dscContext := metadata.NewContext(context.Background(), md) + + success := true + for _, prefixString := range args { + ctx := ctx.WithField("Prefix", prefixString) + prefix, err := types.ParseDevAddrPrefix(prefixString) + if err != nil { + ctx.WithError(err).Error("Could not register prefix") + success = false + continue + } + _, err = client.AddMetadata(dscContext, &discovery.MetadataRequest{ + ServiceName: "broker", + Id: viper.GetString("id"), + Metadata: &discovery.Metadata{Metadata: &discovery.Metadata_DevAddrPrefix{ + DevAddrPrefix: prefix.Bytes(), + }}, + }) + if err != nil { + ctx.WithError(err).Error("Could not register prefix") + success = false + continue + } + ctx.Info("Registered prefix") + } + + if !success { + os.Exit(1) + } + }, +} + +func init() { + brokerCmd.AddCommand(brokerRegisterPrefixCmd) +} diff --git a/cmd/discovery.go b/cmd/discovery.go new file mode 100644 index 000000000..383f6ab0f --- /dev/null +++ b/cmd/discovery.go @@ -0,0 +1,135 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "syscall" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/discovery" + "github.com/TheThingsNetwork/ttn/core/discovery/announcement" + "github.com/TheThingsNetwork/ttn/core/proxy" + "github.com/apex/log" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc" + "gopkg.in/redis.v5" +) + +// discoveryCmd represents the discovery command +var discoveryCmd = &cobra.Command{ + Use: "discovery", + Short: "The Things Network discovery", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "Server": fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port")), + "HTTP Proxy": fmt.Sprintf("%s:%d", viper.GetString("discovery.http-address"), viper.GetInt("discovery.http-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("discovery.redis-address"), viper.GetInt("discovery.redis-db")), + }).Info("Initializing Discovery") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("discovery.redis-address"), + Password: "", // no password set + DB: viper.GetInt("discovery.redis-db"), + }) + + connectRedis(client) + + // Component + component, err := component.New(ctx, "discovery", fmt.Sprintf("%s:%d", "localhost", viper.GetInt("discovery.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } + + // Discovery Server + discovery := discovery.NewRedisDiscovery(client) + if viper.GetBool("discovery.cache") { + discovery.WithCache(announcement.DefaultCacheOptions) + } + discovery.WithMasterAuthServers(viper.GetStringSlice("discovery.master-auth-servers")...) + err = discovery.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize discovery") + } + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + component.RegisterHealthServer(grpc) + discovery.RegisterRPC(grpc) + go grpc.Serve(lis) + + if viper.GetString("discovery.http-address") != "" && viper.GetInt("discovery.http-port") != 0 { + proxyConn, err := component.Identity.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not start client for gRPC proxy") + } + mux := runtime.NewServeMux() + netCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + pb.RegisterDiscoveryHandler(netCtx, mux, proxyConn) + + prxy := proxy.WithLogger(mux, ctx) + + go func() { + err := http.ListenAndServe( + fmt.Sprintf("%s:%d", viper.GetString("discovery.http-address"), viper.GetInt("discovery.http-port")), + prxy, + ) + if err != nil { + ctx.WithError(err).Fatal("Error in gRPC proxy") + } + }() + } + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() + }, +} + +func init() { + RootCmd.AddCommand(discoveryCmd) + + discoveryCmd.Flags().String("redis-address", "localhost:6379", "Redis server and port") + viper.BindPFlag("discovery.redis-address", discoveryCmd.Flags().Lookup("redis-address")) + discoveryCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("discovery.redis-db", discoveryCmd.Flags().Lookup("redis-db")) + + discoveryCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + discoveryCmd.Flags().Int("server-port", 1900, "The port for communication") + viper.BindPFlag("discovery.server-address", discoveryCmd.Flags().Lookup("server-address")) + viper.BindPFlag("discovery.server-port", discoveryCmd.Flags().Lookup("server-port")) + + discoveryCmd.Flags().Bool("cache", false, "Add a cache in front of the database") + viper.BindPFlag("discovery.cache", discoveryCmd.Flags().Lookup("cache")) + + discoveryCmd.Flags().StringSlice("master-auth-servers", []string{"ttn-account"}, "Auth servers that are allowed to manage this network") + viper.BindPFlag("discovery.master-auth-servers", discoveryCmd.Flags().Lookup("master-auth-servers")) + + discoveryCmd.Flags().String("http-address", "0.0.0.0", "The IP address where the gRPC proxy should listen") + discoveryCmd.Flags().Int("http-port", 8080, "The port where the gRPC proxy should listen") + viper.BindPFlag("discovery.http-address", discoveryCmd.Flags().Lookup("http-address")) + viper.BindPFlag("discovery.http-port", discoveryCmd.Flags().Lookup("http-port")) +} diff --git a/cmd/discovery_authorize.go b/cmd/discovery_authorize.go new file mode 100644 index 000000000..c46a42e18 --- /dev/null +++ b/cmd/discovery_authorize.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/ttn/utils/security" + jwt "github.com/dgrijalva/jwt-go" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var discoveryAuthorizeCmd = &cobra.Command{ + Hidden: true, + Use: "authorize [router/broker/handler] [id]", + Short: "Generate a token that components should use to announce themselves", + Long: `ttn discovery authorize generates a token that components should use to announce themselves`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + privKey, err := security.LoadKeypair(viper.GetString("key-dir")) + if err != nil { + ctx.WithError(err).Fatal("Could not load security keys") + } + + ttl, err := cmd.Flags().GetInt("valid") + if err != nil { + ctx.WithError(err).Fatal("Could not read TTL") + } + + issuer, err := cmd.Flags().GetString("issuer") + if err != nil { + ctx.WithError(err).Fatal("Could not read issuer ID") + } + + var claims claims.ComponentClaims + claims.Subject = args[1] + claims.Type = args[0] + claims.Issuer = issuer + claims.IssuedAt = time.Now().Unix() + claims.NotBefore = time.Now().Unix() + if ttl > 0 { + claims.ExpiresAt = time.Now().Add(time.Duration(ttl) * time.Hour * 24).Unix() + } + tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + token, err := tokenBuilder.SignedString(privKey) + if err != nil { + ctx.WithError(err).Fatal("Could not sign JWT") + } + + ctx.WithField("ID", args[0]).Info("Generated token") + fmt.Println() + fmt.Println(token) + fmt.Println() + }, +} + +func init() { + discoveryCmd.AddCommand(discoveryAuthorizeCmd) + discoveryAuthorizeCmd.Flags().Int("valid", 0, "The number of days the token is valid") + discoveryAuthorizeCmd.Flags().String("issuer", "local", "The issuer ID to use") +} diff --git a/cmd/docs/README.md b/cmd/docs/README.md new file mode 100644 index 000000000..57c98ca5f --- /dev/null +++ b/cmd/docs/README.md @@ -0,0 +1,207 @@ +# API Reference + +The Things Network's backend servers. + +**Options** + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yml") + --description string The description of this component + --discovery-address string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --elasticsearch string Location of Elasticsearch server for logging + --health-port int The port number where the health server should be started + --id string The id of this component + --key-dir string The directory where public/private keys are stored (default "$HOME/.ttn") + --log-file string Location of the log file + --no-cli-logs Disable CLI logs + --public Announce this component as part of The Things Network (public community network) + --tls Use TLS +``` + + +## ttn broker + + + +**Usage:** `ttn broker` + +**Options** + +``` + --deduplication-delay int Deduplication delay (in ms) (default 200) + --networkserver-address string Networkserver host and port (default "localhost:1903") + --networkserver-cert string Networkserver certificate to use + --networkserver-token string Networkserver token to use + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1902) +``` + +### ttn broker gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn broker gen-cert` + +### ttn broker gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn broker gen-keypair` + +### ttn broker register-prefix + +ttn broker register prefix registers a prefix to this Broker + +**Usage:** `ttn broker register-prefix [prefix ...]` + +## ttn discovery + + + +**Usage:** `ttn discovery` + +**Options** + +``` + --cache Add a cache in front of the database + --http-address string The IP address where the gRPC proxy should listen (default "0.0.0.0") + --http-port int The port where the gRPC proxy should listen (default 8080) + --master-auth-servers stringSlice Auth servers that are allowed to manage this network (default [ttn-account]) + --redis-address string Redis server and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-port int The port for communication (default 1900) +``` + +### ttn discovery gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn discovery gen-cert` + +### ttn discovery gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn discovery gen-keypair` + +## ttn handler + + + +**Usage:** `ttn handler` + +**Options** + +``` + --amqp-address string AMQP host and port. Leave empty to disable AMQP + --amqp-exchange string AMQP exchange (default "ttn.handler") + --amqp-password string AMQP password (default "guest") + --amqp-username string AMQP username (default "guest") + --broker-id string The ID of the TTN Broker as announced in the Discovery server (default "dev") + --http-address string The IP address where the gRPC proxy should listen (default "0.0.0.0") + --http-port int The port where the gRPC proxy should listen (default 8084) + --mqtt-address string MQTT host and port. Leave empty to disable MQTT + --mqtt-password string MQTT password + --mqtt-username string MQTT username + --redis-address string Redis host and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1904) +``` + +### ttn handler gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn handler gen-cert` + +### ttn handler gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn handler gen-keypair` + +## ttn networkserver + + + +**Usage:** `ttn networkserver` + +**Options** + +``` + --net-id int LoRaWAN NetID (default 19) + --redis-address string Redis server and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1903) +``` + +### ttn networkserver authorize + +ttn networkserver authorize generates a token that Brokers should use to connect + +**Usage:** `ttn networkserver authorize [id]` + +**Options** + +``` + --valid int The number of days the token is valid +``` + +### ttn networkserver gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn networkserver gen-cert` + +### ttn networkserver gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn networkserver gen-keypair` + +## ttn router + + + +**Usage:** `ttn router` + +**Options** + +``` + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1901) + --skip-verify-gateway-token Skip verification of the gateway token +``` + +### ttn router gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn router gen-cert` + +### ttn router gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn router gen-keypair` + +## ttn selfupdate + +ttn selfupdate updates the current ttn to the latest version + +**Usage:** `ttn selfupdate` + +## ttn version + +ttn version gets the build and version information of ttn + +**Usage:** `ttn version` + diff --git a/cmd/docs/generate.go b/cmd/docs/generate.go new file mode 100644 index 000000000..bbaaec1dc --- /dev/null +++ b/cmd/docs/generate.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/cmd" + "github.com/TheThingsNetwork/ttn/utils/docs" +) + +func main() { + fmt.Println(`# API Reference + +The Things Network's backend servers. +`) + fmt.Print(docs.Generate(cmd.RootCmd)) +} diff --git a/cmd/genkeys.go b/cmd/genkeys.go new file mode 100644 index 000000000..eea0e4ba9 --- /dev/null +++ b/cmd/genkeys.go @@ -0,0 +1,57 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/utils/security" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func genKeypairCmd(component string) *cobra.Command { + return &cobra.Command{ + Use: "gen-keypair", + Short: "Generate a public/private keypair", + Long: `ttn gen-keypair generates a public/private keypair`, + Run: func(cmd *cobra.Command, args []string) { + if err := security.GenerateKeypair(viper.GetString("key-dir")); err != nil { + ctx.WithError(err).Fatal("Could not generate keypair") + } + ctx.WithField("TLSDir", viper.GetString("key-dir")).Info("Done") + }, + } +} + +func genCertCmd(component string) *cobra.Command { + return &cobra.Command{ + Use: "gen-cert", + Short: "Generate a TLS certificate", + Long: `ttn gen-cert generates a TLS Certificate`, + Run: func(cmd *cobra.Command, args []string) { + var names []string + if announcedName := viper.GetString(component + ".server-address-announce"); announcedName != "" { + names = append(names, announcedName) + } + names = append(names, args...) + if err := security.GenerateCert(viper.GetString("key-dir"), names...); err != nil { + ctx.WithError(err).Fatal("Could not generate certificate") + } + ctx.WithField("TLSDir", viper.GetString("key-dir")).Info("Done") + }, + } +} + +func init() { + routerCmd.AddCommand(genKeypairCmd("router")) + brokerCmd.AddCommand(genKeypairCmd("broker")) + handlerCmd.AddCommand(genKeypairCmd("handler")) + discoveryCmd.AddCommand(genKeypairCmd("discovery")) + networkserverCmd.AddCommand(genKeypairCmd("networkserver")) + + routerCmd.AddCommand(genCertCmd("router")) + brokerCmd.AddCommand(genCertCmd("broker")) + handlerCmd.AddCommand(genCertCmd("handler")) + discoveryCmd.AddCommand(genCertCmd("discovery")) + networkserverCmd.AddCommand(genCertCmd("networkserver")) +} diff --git a/cmd/handler.go b/cmd/handler.go new file mode 100644 index 000000000..070aa6bfe --- /dev/null +++ b/cmd/handler.go @@ -0,0 +1,183 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net" + "net/http" + "os" + "os/signal" + "syscall" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler" + "github.com/TheThingsNetwork/ttn/core/proxy" + "github.com/apex/log" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "gopkg.in/redis.v5" +) + +// handlerCmd represents the handler command +var handlerCmd = &cobra.Command{ + Use: "handler", + Short: "The Things Network handler", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "Server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + "HTTP Proxy": fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("handler.redis-address"), viper.GetInt("handler.redis-db")), + "TTN Broker ID": viper.GetString("handler.broker-id"), + "MQTT": viper.GetString("handler.mqtt-address"), + "AMQP": viper.GetString("handler.amqp-address"), + }).Info("Initializing Handler") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("handler.redis-address"), + Password: "", // no password set + DB: viper.GetInt("handler.redis-db"), + }) + + connectRedis(client) + + // Component + component, err := component.New(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } + + httpActive := viper.GetString("handler.http-address") != "" && viper.GetInt("handler.http-port") != 0 + if httpActive && component.Identity.ApiAddress == "" { + component.Identity.ApiAddress = fmt.Sprintf("http://%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.http-port")) + } + + // Handler + handler := handler.NewRedisHandler( + client, + viper.GetString("handler.broker-id"), + ) + if viper.GetString("handler.mqtt-address") != "" { + handler = handler.WithMQTT( + viper.GetString("handler.mqtt-username"), + viper.GetString("handler.mqtt-password"), + viper.GetString("handler.mqtt-address"), + ) + } else { + ctx.Warn("MQTT is not enabled in your configuration") + } + if viper.GetString("handler.amqp-address") != "" { + handler = handler.WithAMQP( + viper.GetString("handler.amqp-username"), + viper.GetString("handler.amqp-password"), + viper.GetString("handler.amqp-address"), + viper.GetString("handler.amqp-exchange")) + } else { + ctx.Warn("AMQP is not enabled in your configuration") + } + err = handler.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize handler") + } + defer handler.Shutdown() + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + component.RegisterHealthServer(grpc) + handler.RegisterRPC(grpc) + handler.RegisterManager(grpc) + go grpc.Serve(lis) + defer grpc.Stop() + + if httpActive { + proxyConn, err := component.Identity.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not start client for gRPC proxy") + } + mux := runtime.NewServeMux() + netCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + pb.RegisterApplicationManagerHandler(netCtx, mux, proxyConn) + + prxy := proxy.WithToken(mux) + prxy = proxy.WithLogger(prxy, ctx) + + go func() { + err := http.ListenAndServe( + fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), + prxy, + ) + if err != nil { + ctx.WithError(err).Fatal("Error in gRPC proxy") + } + }() + } + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + }, +} + +func init() { + RootCmd.AddCommand(handlerCmd) + + handlerCmd.Flags().String("redis-address", "localhost:6379", "Redis host and port") + viper.BindPFlag("handler.redis-address", handlerCmd.Flags().Lookup("redis-address")) + handlerCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("handler.redis-db", handlerCmd.Flags().Lookup("redis-db")) + + handlerCmd.Flags().String("broker-id", "dev", "The ID of the TTN Broker as announced in the Discovery server") + viper.BindPFlag("handler.broker-id", handlerCmd.Flags().Lookup("broker-id")) + + handlerCmd.Flags().String("mqtt-address", "", "MQTT host and port. Leave empty to disable MQTT") + viper.BindPFlag("handler.mqtt-address", handlerCmd.Flags().Lookup("mqtt-address")) + + handlerCmd.Flags().String("mqtt-username", "", "MQTT username") + viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) + + handlerCmd.Flags().String("mqtt-password", "", "MQTT password") + viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) + + handlerCmd.Flags().String("amqp-address", "", "AMQP host and port. Leave empty to disable AMQP") + viper.BindPFlag("handler.amqp-address", handlerCmd.Flags().Lookup("amqp-address")) + + handlerCmd.Flags().String("amqp-username", "guest", "AMQP username") + viper.BindPFlag("handler.amqp-username", handlerCmd.Flags().Lookup("amqp-username")) + + handlerCmd.Flags().String("amqp-password", "guest", "AMQP password") + viper.BindPFlag("handler.amqp-password", handlerCmd.Flags().Lookup("amqp-password")) + + handlerCmd.Flags().String("amqp-exchange", "ttn.handler", "AMQP exchange") + viper.BindPFlag("handler.amqp-exchange", handlerCmd.Flags().Lookup("amqp-exchange")) + + handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + handlerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + handlerCmd.Flags().Int("server-port", 1904, "The port for communication") + viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("handler.server-address-announce", handlerCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) + + handlerCmd.Flags().String("http-address", "0.0.0.0", "The IP address where the gRPC proxy should listen") + handlerCmd.Flags().Int("http-port", 8084, "The port where the gRPC proxy should listen") + viper.BindPFlag("handler.http-address", handlerCmd.Flags().Lookup("http-address")) + viper.BindPFlag("handler.http-port", handlerCmd.Flags().Lookup("http-port")) +} diff --git a/cmd/networkserver.go b/cmd/networkserver.go new file mode 100644 index 000000000..b639974e6 --- /dev/null +++ b/cmd/networkserver.go @@ -0,0 +1,120 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net" + "os" + "os/signal" + "strings" + "syscall" + + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/networkserver" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc" + "gopkg.in/redis.v5" +) + +// networkserverCmd represents the networkserver command +var networkserverCmd = &cobra.Command{ + Use: "networkserver", + Short: "The Things Network networkserver", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "Server": fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("networkserver.redis-address"), viper.GetInt("networkserver.redis-db")), + "NetID": viper.GetString("networkserver.net-id"), + }).Info("Initializing Network Server") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("networkserver.redis-address"), + Password: "", // no password set + DB: viper.GetInt("networkserver.redis-db"), + }) + + connectRedis(client) + + // Component + component, err := component.New(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } + + // networkserver Server + networkserver := networkserver.NewRedisNetworkServer(client, viper.GetInt("networkserver.net-id")) + + // Register Prefixes + for prefix, usage := range viper.GetStringMapString("networkserver.prefixes") { + prefix, err := types.ParseDevAddrPrefix(prefix) + if err != nil { + ctx.WithError(err).Warn("Could not use DevAddr Prefix. Skipping.") + continue + } + err = networkserver.UsePrefix(prefix, strings.Split(usage, ",")) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize networkserver") + continue + } + ctx.Infof("Using DevAddr prefix %s (%v)", prefix, usage) + } + + err = networkserver.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize networkserver") + } + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + component.RegisterHealthServer(grpc) + networkserver.RegisterRPC(grpc) + networkserver.RegisterManager(grpc) + go grpc.Serve(lis) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() + networkserver.Shutdown() + }, +} + +func init() { + RootCmd.AddCommand(networkserverCmd) + + networkserverCmd.Flags().String("redis-address", "localhost:6379", "Redis server and port") + viper.BindPFlag("networkserver.redis-address", networkserverCmd.Flags().Lookup("redis-address")) + networkserverCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("networkserver.redis-db", networkserverCmd.Flags().Lookup("redis-db")) + + networkserverCmd.Flags().Int("net-id", 19, "LoRaWAN NetID") + viper.BindPFlag("networkserver.net-id", networkserverCmd.Flags().Lookup("net-id")) + + viper.SetDefault("networkserver.prefixes", map[string]string{ + "26000000/20": "otaa,abp,world,local,private,testing", + }) + + networkserverCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + networkserverCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + networkserverCmd.Flags().Int("server-port", 1903, "The port for communication") + viper.BindPFlag("networkserver.server-address", networkserverCmd.Flags().Lookup("server-address")) + viper.BindPFlag("networkserver.server-address-announce", networkserverCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("networkserver.server-port", networkserverCmd.Flags().Lookup("server-port")) +} diff --git a/cmd/networkserver_authorize.go b/cmd/networkserver_authorize.go new file mode 100644 index 000000000..e6c58bd76 --- /dev/null +++ b/cmd/networkserver_authorize.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/utils/security" + jwt "github.com/dgrijalva/jwt-go" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// networkserverAuthorizeCmd represents the secure command +var networkserverAuthorizeCmd = &cobra.Command{ + Use: "authorize [id]", + Short: "Generate a token that Brokers should use to connect", + Long: `ttn networkserver authorize generates a token that Brokers should use to connect`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + privKey, err := security.LoadKeypair(viper.GetString("key-dir")) + if err != nil { + ctx.WithError(err).Fatal("Could not load security keys") + } + + ttl, err := cmd.Flags().GetInt("valid") + if err != nil { + ctx.WithError(err).Fatal("Could not read TTL") + } + claims := jwt.StandardClaims{ + Subject: args[0], + Issuer: viper.GetString("id"), + IssuedAt: time.Now().Unix(), + NotBefore: time.Now().Unix(), + } + if ttl > 0 { + claims.ExpiresAt = time.Now().Add(time.Duration(ttl) * time.Hour * 24).Unix() + } + tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + token, err := tokenBuilder.SignedString(privKey) + if err != nil { + ctx.WithError(err).Fatal("Could not sign JWT") + } + + ctx.WithField("ID", args[0]).Info("Generated NS token") + fmt.Println() + fmt.Println(token) + fmt.Println() + }, +} + +func init() { + networkserverCmd.AddCommand(networkserverAuthorizeCmd) + networkserverAuthorizeCmd.Flags().Int("valid", 0, "The number of days the token is valid") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 000000000..7dbeba0b4 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,207 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net/http" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "time" + + cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" + ttnlog "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/go-utils/log/grpc" + esHandler "github.com/TheThingsNetwork/ttn/utils/elasticsearch/handler" + "github.com/apex/log" + jsonHandler "github.com/apex/log/handlers/json" + levelHandler "github.com/apex/log/handlers/level" + multiHandler "github.com/apex/log/handlers/multi" + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tj/go-elastic" + "google.golang.org/grpc/grpclog" + "gopkg.in/redis.v5" +) + +var cfgFile string + +var logFile *os.File + +var ctx log.Interface + +// RootCmd is executed when ttn is executed without a subcommand +var RootCmd = &cobra.Command{ + Use: "ttn", + Short: "The Things Network's backend servers", + Long: `ttn launches The Things Network's backend servers`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var logLevel = log.InfoLevel + if viper.GetBool("debug") { + logLevel = log.DebugLevel + } + + var logHandlers []log.Handler + + if !viper.GetBool("no-cli-logs") { + logHandlers = append(logHandlers, levelHandler.New(cliHandler.New(os.Stdout), logLevel)) + } + + if logFileLocation := viper.GetString("log-file"); logFileLocation != "" { + absLogFileLocation, err := filepath.Abs(logFileLocation) + if err != nil { + panic(err) + } + logFile, err = os.OpenFile(absLogFileLocation, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + panic(err) + } + if err == nil { + logHandlers = append(logHandlers, levelHandler.New(jsonHandler.New(logFile), logLevel)) + } + } + + if esServer := viper.GetString("elasticsearch"); esServer != "" { + esClient := elastic.New(esServer) + esClient.HTTPClient = &http.Client{ + Timeout: 5 * time.Second, + } + logHandlers = append(logHandlers, levelHandler.New(esHandler.New(&esHandler.Config{ + Client: esClient, + Prefix: cmd.Name(), + BufferSize: 10, + }), logLevel)) + } + + ctx = &log.Logger{ + Handler: multiHandler.New(logHandlers...), + } + + // Set the API/gRPC logger + ttnlog.Set(apex.Wrap(ctx)) + grpclog.SetLogger(grpc.Wrap(ttnlog.Get())) + + ctx.WithFields(log.Fields{ + "ComponentID": viper.GetString("id"), + "Description": viper.GetString("description"), + "Discovery Server Address": viper.GetString("discovery-address"), + "Auth Servers": viper.GetStringMapString("auth-servers"), + "Monitors": viper.GetStringMapString("monitor-servers"), + }).Info("Initializing The Things Network") + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if logFile != nil { + logFile.Close() + } + }, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + defer func() { + buf := make([]byte, 1<<16) + runtime.Stack(buf, false) + if thePanic := recover(); thePanic != nil && ctx != nil { + ctx.WithField("panic", thePanic).WithField("stack", string(buf)).Fatal("Stopping because of panic") + } + }() + + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yml\")") + + RootCmd.PersistentFlags().Bool("no-cli-logs", false, "Disable CLI logs") + RootCmd.PersistentFlags().String("log-file", "", "Location of the log file") + RootCmd.PersistentFlags().String("elasticsearch", "", "Location of Elasticsearch server for logging") + + RootCmd.PersistentFlags().String("id", "", "The id of this component") + RootCmd.PersistentFlags().String("description", "", "The description of this component") + RootCmd.PersistentFlags().Bool("public", false, "Announce this component as part of The Things Network (public community network)") + + RootCmd.PersistentFlags().String("discovery-address", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") + RootCmd.PersistentFlags().String("auth-token", "", "The JWT token to be used for the discovery server") + + RootCmd.PersistentFlags().Int("health-port", 0, "The port number where the health server should be started") + + viper.SetDefault("auth-servers", map[string]string{ + "ttn-account": "https://account.thethingsnetwork.org", + }) + + dir, err := homedir.Dir() + if err == nil { + dir, _ = homedir.Expand(dir) + } + if dir == "" { + dir, err = os.Getwd() + if err != nil { + panic(err) + } + } + + RootCmd.PersistentFlags().Bool("tls", false, "Use TLS") + RootCmd.PersistentFlags().String("key-dir", path.Clean(dir+"/.ttn/"), "The directory where public/private keys are stored") + + viper.BindPFlags(RootCmd.PersistentFlags()) +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + viper.SetConfigType("yaml") + viper.SetConfigName(".ttn") // name of config file (without extension) + viper.AddConfigPath("$HOME") // adding home directory as first search path + viper.SetEnvPrefix("ttn") // set environment prefix + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) + viper.AutomaticEnv() // read in environment variables that match + + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + + viper.BindEnv("debug") + + // If a config file is found, read it in. + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Error when reading config file:", err) + os.Exit(1) + } else if err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} + +// RedisConnectRetries indicates how many times the Redis connection should be retried +var RedisConnectRetries = 10 + +// RedisConnectRetryDelay indicates the time between Redis connection retries +var RedisConnectRetryDelay = 1 * time.Second + +func connectRedis(client *redis.Client) error { + var err error + for retries := 0; retries < RedisConnectRetries; retries++ { + _, err = client.Ping().Result() + if err == nil { + break + } + ctx.WithError(err).Warn("Could not connect to Redis. Retrying...") + <-time.After(RedisConnectRetryDelay) + } + if err != nil { + client.Close() + return err + } + return nil +} diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 000000000..1f52afd5f --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestRootCmd(t *testing.T) { + a := New(t) + a.So(RootCmd.IsAvailableCommand(), ShouldBeTrue) +} diff --git a/cmd/router.go b/cmd/router.go new file mode 100644 index 000000000..c45b754b5 --- /dev/null +++ b/cmd/router.go @@ -0,0 +1,80 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net" + "os" + "os/signal" + "syscall" + + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/router" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// routerCmd represents the router command +var routerCmd = &cobra.Command{ + Use: "router", + Short: "The Things Network router", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "Server": fmt.Sprintf("%s:%d", viper.GetString("router.server-address"), viper.GetInt("router.server-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port")), + }).Info("Initializing Router") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Component + component, err := component.New(ctx, "router", fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } + + // Router + router := router.NewRouter() + err = router.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize router") + } + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("router.server-address"), viper.GetInt("router.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + component.RegisterHealthServer(grpc) + router.RegisterRPC(grpc) + router.RegisterManager(grpc) + go grpc.Serve(lis) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() + router.Shutdown() + }, +} + +func init() { + RootCmd.AddCommand(routerCmd) + routerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + routerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + routerCmd.Flags().Int("server-port", 1901, "The port for communication") + routerCmd.Flags().Bool("skip-verify-gateway-token", false, "Skip verification of the gateway token") + viper.BindPFlag("router.server-address", routerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("router.server-address-announce", routerCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("router.server-port", routerCmd.Flags().Lookup("server-port")) + viper.BindPFlag("router.skip-verify-gateway-token", routerCmd.Flags().Lookup("skip-verify-gateway-token")) +} diff --git a/cmd/selfupdate.go b/cmd/selfupdate.go new file mode 100644 index 000000000..ee2c15267 --- /dev/null +++ b/cmd/selfupdate.go @@ -0,0 +1,24 @@ +// +build !homebrew + +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/spf13/cobra" +) + +var selfUpdateCmd = &cobra.Command{ + Use: "selfupdate", + Short: "Update ttn to the latest version", + Long: `ttn selfupdate updates the current ttn to the latest version`, + Run: func(cmd *cobra.Command, args []string) { + version.Selfupdate(ctx, "ttn") + }, +} + +func init() { + RootCmd.AddCommand(selfUpdateCmd) +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..da1f97bc6 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,66 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const unknown = "unknown" + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get build and version information", + Long: `ttn version gets the build and version information of ttn`, + Run: func(cmd *cobra.Command, args []string) { + gitBranch := viper.GetString("gitBranch") + gitCommit := viper.GetString("gitCommit") + buildDate := viper.GetString("buildDate") + + ctx.WithFields(log.Fields{ + "Version": viper.GetString("version"), + "Branch": gitBranch, + "Commit": gitCommit, + "BuildDate": buildDate, + }).Info("Got build information") + + if gitBranch == unknown || gitCommit == unknown || buildDate == unknown { + ctx.Warn("This is not an official ttn build") + ctx.Warn("If you're building ttn from source, you should use the Makefile") + return + } + + latest, err := version.GetLatestInfo() + if err != nil { + ctx.WithError(err).Warn("Could not get latest version information") + return + } + + if latest.Commit == gitCommit { + ctx.Info("This is an up-to-date ttn build") + return + } + + if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { + ctx.Warn("This is not the latest official ttn build") + if buildDate.Before(latest.Date) { + ctx.Warnf("The newest ttn build is %s newer.", latest.Date.Sub(buildDate)) + } else { + ctx.Warn("Your ttn build is newer than the latest official one, which is fine if you're a developer") + } + } else { + ctx.Warn("This ttn contains invalid build information") + } + + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/core/band/band.go b/core/band/band.go new file mode 100644 index 000000000..6b9c4e696 --- /dev/null +++ b/core/band/band.go @@ -0,0 +1,81 @@ +package band + +import ( + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" + lora "github.com/brocaar/lorawan/band" +) + +// FrequencyPlan includes band configuration and CFList +type FrequencyPlan struct { + lora.Band + CFList *lorawan.CFList +} + +// Guess the region based on frequency +func Guess(frequency uint64) string { + switch { + case frequency >= 863000000 && frequency <= 870000000: + return pb_lorawan.Region_EU_863_870.String() + case frequency >= 902300000 && frequency <= 914900000: + return pb_lorawan.Region_US_902_928.String() + case frequency >= 779500000 && frequency <= 786500000: + return pb_lorawan.Region_CN_779_787.String() + case frequency >= 433175000 && frequency <= 434665000: + return pb_lorawan.Region_EU_433.String() + case frequency == 923200000 || frequency == 923400000: + return pb_lorawan.Region_AS_923.String() + case frequency >= 920900000 || frequency == 923300000: + return pb_lorawan.Region_KR_920_923.String() + case frequency >= 915200000 && frequency <= 927800000: + return pb_lorawan.Region_AU_915_928.String() + case frequency >= 470300000 && frequency <= 489300000: + return pb_lorawan.Region_CN_470_510.String() + } + return "" +} + +// Get the frequency plan for the given region +func Get(region string) (frequencyPlan FrequencyPlan, err error) { + switch region { + case pb_lorawan.Region_EU_863_870.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.EU_863_870, false, lorawan.DwellTimeNoLimit) + // TTN uses SF9BW125 in RX2 + frequencyPlan.RX2DataRate = 3 + // TTN frequency plan includes extra channels next to the default channels: + frequencyPlan.UplinkChannels = []lora.Channel{ + lora.Channel{Frequency: 868100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 868300000, DataRates: []int{0, 1, 2, 3, 4, 5, 6}}, // Also SF7BW250 + lora.Channel{Frequency: 868500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 868800000, DataRates: []int{7}}, // FSK 50kbps + lora.Channel{Frequency: 867100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + } + frequencyPlan.DownlinkChannels = frequencyPlan.UplinkChannels + frequencyPlan.CFList = &lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000} + case pb_lorawan.Region_US_902_928.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.US_902_928, false, lorawan.DwellTime400ms) + case pb_lorawan.Region_CN_779_787.String(): + err = errors.NewErrInternal("China 779-787 MHz band not supported") + case pb_lorawan.Region_EU_433.String(): + err = errors.NewErrInternal("Europe 433 MHz band not supported") + case pb_lorawan.Region_AU_915_928.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.AU_915_928, false, lorawan.DwellTime400ms) + case pb_lorawan.Region_CN_470_510.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.CN_470_510, false, lorawan.DwellTimeNoLimit) + case pb_lorawan.Region_AS_923.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.AS_923, false, lorawan.DwellTime400ms) + case pb_lorawan.Region_KR_920_923.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.KR_920_923, false, lorawan.DwellTimeNoLimit) + default: + err = errors.NewErrInvalidArgument("Frequency Band", "unknown") + } + if err != nil { + return + } + return +} diff --git a/core/broker/activation.go b/core/broker/activation.go new file mode 100644 index 000000000..c789ece8b --- /dev/null +++ b/core/broker/activation.go @@ -0,0 +1,223 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "sync" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/gateway" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/brocaar/lorawan" +) + +type challengeResponseWithHandler struct { + handler *pb_discovery.Announcement + client pb_handler.HandlerClient + response *pb.ActivationChallengeResponse +} + +var errDuplicateActivation = errors.New("Not handling duplicate activation on this gateway") + +func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { + ctx := b.Ctx.WithFields(log.Fields{ + "GatewayID": activation.GatewayMetadata.GatewayId, + "AppEUI": *activation.AppEui, + "DevEUI": *activation.DevEui, + }) + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle activation") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") + } + }() + + time := time.Now() + + b.status.activations.Mark(1) + + // De-duplicate uplink messages + duplicates := b.deduplicateActivation(activation) + if len(duplicates) == 0 { + return nil, errDuplicateActivation + } + + b.status.activationsUnique.Mark(1) + + base := duplicates[0] + + // Collect GatewayMetadata and DownlinkOptions + var gatewayMetadata []*gateway.RxMetadata + var downlinkOptions []*pb.DownlinkOption + var deviceActivationResponse *pb.DeviceActivationResponse + for _, duplicate := range duplicates { + gatewayMetadata = append(gatewayMetadata, duplicate.GatewayMetadata) + downlinkOptions = append(downlinkOptions, duplicate.DownlinkOptions...) + } + + // Select best DownlinkOption + if len(downlinkOptions) > 0 { + deviceActivationResponse = &pb.DeviceActivationResponse{ + DownlinkOption: selectBestDownlink(downlinkOptions), + } + } + + // Build Uplink + deduplicatedActivationRequest := &pb.DeduplicatedDeviceActivationRequest{ + Payload: base.Payload, + DevEui: base.DevEui, + AppEui: base.AppEui, + ProtocolMetadata: base.ProtocolMetadata, + GatewayMetadata: gatewayMetadata, + ActivationMetadata: base.ActivationMetadata, + ServerTime: time.UnixNano(), + ResponseTemplate: deviceActivationResponse, + } + + // Send Activate to NS + deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused to prepare activation") + } + + ctx = ctx.WithFields(log.Fields{ + "AppID": deduplicatedActivationRequest.AppId, + "DevID": deduplicatedActivationRequest.DevId, + }) + + // Find Handler (based on AppEUI) + var announcements []*pb_discovery.Announcement + announcements, err = b.Discovery.GetAllHandlersForAppID(deduplicatedActivationRequest.AppId) + if err != nil { + return nil, err + } + if len(announcements) == 0 { + return nil, errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) + } + + ctx = ctx.WithField("NumHandlers", len(announcements)) + + // LoRaWAN: Unmarshal and prepare version without MIC + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(deduplicatedActivationRequest.Payload) + if err != nil { + return nil, err + } + correctMIC := phyPayload.MIC + phyPayload.MIC = [4]byte{0, 0, 0, 0} + phyPayloadWithoutMIC, err := phyPayload.MarshalBinary() + if err != nil { + return nil, err + } + + // Build Challenge + challenge := &pb.ActivationChallengeRequest{ + Payload: phyPayloadWithoutMIC, + AppId: deduplicatedActivationRequest.AppId, + DevId: deduplicatedActivationRequest.DevId, + AppEui: deduplicatedActivationRequest.AppEui, + DevEui: deduplicatedActivationRequest.DevEui, + } + + // Send Challenge to all handlers and collect responses + var wg sync.WaitGroup + responses := make(chan *challengeResponseWithHandler, len(announcements)) + for _, announcement := range announcements { + conn, err := announcement.Dial() + if err != nil { + ctx.WithError(err).Warn("Could not dial handler for Activation") + continue + } + client := pb_handler.NewHandlerClient(conn) + + // Do async request + wg.Add(1) + go func(announcement *pb_discovery.Announcement) { + res, err := client.ActivationChallenge(b.Component.GetContext(""), challenge) + if err == nil && res != nil { + responses <- &challengeResponseWithHandler{ + handler: announcement, + client: client, + response: res, + } + } + wg.Done() + }(announcement) + } + + // Make sure to close channel when all requests are done + go func() { + wg.Wait() + close(responses) + }() + + var gotFirst bool + var joinHandler *pb_discovery.Announcement + var joinHandlerClient pb_handler.HandlerClient + for res := range responses { + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(res.response.Payload) + if err != nil { + continue + } + if phyPayload.MIC != correctMIC { + continue + } + + if gotFirst { + ctx.Warn("Duplicate Activation Response") + } else { + gotFirst = true + joinHandler = res.handler + joinHandlerClient = res.client + } + } + + // Activation not accepted by any broker + if !gotFirst { + ctx.Debug("Activation not accepted by any Handler") + return nil, errors.New("Activation not accepted by any Handler") + } + + ctx.WithField("HandlerID", joinHandler.Id).Debug("Forward Activation") + + handlerResponse, err := joinHandlerClient.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Handler refused activation") + } + handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused activation") + } + + res = &pb.DeviceActivationResponse{ + Payload: handlerResponse.Payload, + Message: handlerResponse.Message, + DownlinkOption: handlerResponse.DownlinkOption, + } + + return res, nil +} + +func (b *broker) deduplicateActivation(duplicate *pb.DeviceActivationRequest) (activations []*pb.DeviceActivationRequest) { + sum := md5.Sum(duplicate.Payload) + key := hex.EncodeToString(sum[:]) + list := b.activationDeduplicator.Deduplicate(key, duplicate) + if len(list) == 0 { + return + } + for _, duplicate := range list { + activations = append(activations, duplicate.(*pb.DeviceActivationRequest)) + } + return +} diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go new file mode 100644 index 000000000..ee8475f15 --- /dev/null +++ b/core/broker/activation_test.go @@ -0,0 +1,87 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "sync" + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/golang/mock/gomock" + . "github.com/smartystreets/assertions" +) + +func TestHandleActivation(t *testing.T) { + a := New(t) + + gtwID := "eui-0102030405060708" + devEUI := types.DevEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + appEUI := types.AppEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + + b := getTestBroker(t) + b.ns.EXPECT().PrepareActivation(gomock.Any(), gomock.Any()).Return(&pb_broker.DeduplicatedDeviceActivationRequest{ + Payload: []byte{}, + DevEui: &devEUI, + AppEui: &appEUI, + AppId: "appid", + DevId: "devid", + GatewayMetadata: []*gateway.RxMetadata{ + &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + }, + ProtocolMetadata: &protocol.RxMetadata{}, + }, nil) + b.discovery.EXPECT().GetAllHandlersForAppID("appid").Return([]*pb_discovery.Announcement{}, nil) + + res, err := b.HandleActivation(&pb_broker.DeviceActivationRequest{ + Payload: []byte{}, + DevEui: &devEUI, + AppEui: &appEUI, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + b.ctrl.Finish() + + // TODO: Integration test with Handler +} + +func TestDeduplicateActivation(t *testing.T) { + a := New(t) + + payload := []byte{0x01, 0x02, 0x03} + protocolMetadata := &protocol.RxMetadata{} + activation1 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, ProtocolMetadata: protocolMetadata} + activation2 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 3.4}, ProtocolMetadata: protocolMetadata} + activation3 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} + activation4 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} + + b := getTestBroker(t) + b.activationDeduplicator = NewDeduplicator(20 * time.Millisecond).(*deduplicator) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + res := b.deduplicateActivation(activation1) + a.So(res, ShouldResemble, []*pb_broker.DeviceActivationRequest{activation1, activation2, activation3}) + wg.Done() + }() + + <-time.After(10 * time.Millisecond) + + a.So(b.deduplicateActivation(activation2), ShouldBeNil) + a.So(b.deduplicateActivation(activation3), ShouldBeNil) + + <-time.After(50 * time.Millisecond) + + a.So(b.deduplicateActivation(activation4), ShouldNotBeNil) + + wg.Wait() +} diff --git a/core/broker/broker.go b/core/broker/broker.go new file mode 100644 index 000000000..f6758a048 --- /dev/null +++ b/core/broker/broker.go @@ -0,0 +1,197 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "google.golang.org/grpc" +) + +type Broker interface { + component.Interface + component.ManagementInterface + + SetNetworkServer(addr, cert, token string) + + HandleUplink(uplink *pb.UplinkMessage) error + HandleDownlink(downlink *pb.DownlinkMessage) error + HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) + + ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) + DeactivateRouter(id string) error + ActivateHandler(id string) (<-chan *pb.DeduplicatedUplinkMessage, error) + DeactivateHandler(id string) error +} + +func NewBroker(timeout time.Duration) Broker { + return &broker{ + routers: make(map[string]chan *pb.DownlinkMessage), + handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), + uplinkDeduplicator: NewDeduplicator(timeout), + activationDeduplicator: NewDeduplicator(timeout), + } +} + +func (b *broker) SetNetworkServer(addr, cert, token string) { + b.nsAddr = addr + b.nsCert = cert + b.nsToken = token +} + +type broker struct { + *component.Component + routers map[string]chan *pb.DownlinkMessage + routersLock sync.RWMutex + handlers map[string]chan *pb.DeduplicatedUplinkMessage + handlersLock sync.RWMutex + nsAddr string + nsCert string + nsToken string + nsConn *grpc.ClientConn + ns networkserver.NetworkServerClient + uplinkDeduplicator Deduplicator + activationDeduplicator Deduplicator + status *status +} + +func (b *broker) checkPrefixAnnouncements() error { + // Get prefixes from NS + nsPrefixes := map[types.DevAddrPrefix]string{} + devAddrClient := pb_lorawan.NewDevAddrManagerClient(b.nsConn) + resp, err := devAddrClient.GetPrefixes(b.GetContext(""), &pb_lorawan.PrefixesRequest{}) + if err != nil { + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return prefixes") + } + for _, mapping := range resp.Prefixes { + prefix, err := types.ParseDevAddrPrefix(mapping.Prefix) + if err != nil { + continue + } + nsPrefixes[prefix] = strings.Join(mapping.Usage, ",") + } + + // Get self from Discovery + self, err := b.Component.Discover("broker", b.Component.Identity.Id) + if err != nil { + return err + } + announcedPrefixes := self.DevAddrPrefixes() + +nextPrefix: + for nsPrefix, usage := range nsPrefixes { + if !strings.Contains(usage, "world") && !strings.Contains(usage, "local") { + continue + } + for _, announcedPrefix := range announcedPrefixes { + if nsPrefix.DevAddr == announcedPrefix.DevAddr && nsPrefix.Length == announcedPrefix.Length { + b.Ctx.WithField("NSPrefix", nsPrefix).WithField("DPrefix", announcedPrefix).Info("Prefix found in Discovery") + continue nextPrefix + } + } + b.Ctx.WithField("Prefix", nsPrefix).Warn("Prefix not announced in Discovery") + } + + return nil +} + +func (b *broker) Init(c *component.Component) error { + b.Component = c + b.InitStatus() + err := b.Component.UpdateTokenKey() + if err != nil { + return err + } + err = b.Component.Announce() + if err != nil { + return err + } + b.Discovery.GetAll("handler") // Update cache + var conn *grpc.ClientConn + if b.nsCert == "" { + conn, err = api.Dial(b.nsAddr) + } else { + conn, err = api.DialWithCert(b.nsAddr, b.nsCert) + } + if err != nil { + return err + } + b.nsConn = conn + b.ns = networkserver.NewNetworkServerClient(conn) + b.checkPrefixAnnouncements() + b.Component.SetStatus(component.StatusHealthy) + return nil +} + +func (b *broker) Shutdown() {} + +func (b *broker) ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) { + b.routersLock.Lock() + defer b.routersLock.Unlock() + if existing, ok := b.routers[id]; ok { + return existing, errors.NewErrInternal(fmt.Sprintf("Router %s already active", id)) + } + b.routers[id] = make(chan *pb.DownlinkMessage) + return b.routers[id], nil +} + +func (b *broker) DeactivateRouter(id string) error { + b.routersLock.Lock() + defer b.routersLock.Unlock() + if channel, ok := b.routers[id]; ok { + close(channel) + delete(b.routers, id) + return nil + } + return errors.NewErrInternal(fmt.Sprintf("Router %s not active", id)) +} + +func (b *broker) getRouter(id string) (chan<- *pb.DownlinkMessage, error) { + b.routersLock.RLock() + defer b.routersLock.RUnlock() + if router, ok := b.routers[id]; ok { + return router, nil + } + return nil, errors.NewErrInternal(fmt.Sprintf("Router %s not active", id)) +} + +func (b *broker) ActivateHandler(id string) (<-chan *pb.DeduplicatedUplinkMessage, error) { + b.handlersLock.Lock() + defer b.handlersLock.Unlock() + if existing, ok := b.handlers[id]; ok { + return existing, errors.NewErrInternal(fmt.Sprintf("Handler %s already active", id)) + } + b.handlers[id] = make(chan *pb.DeduplicatedUplinkMessage) + return b.handlers[id], nil +} + +func (b *broker) DeactivateHandler(id string) error { + b.handlersLock.Lock() + defer b.handlersLock.Unlock() + if channel, ok := b.handlers[id]; ok { + close(channel) + delete(b.handlers, id) + return nil + } + return errors.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) +} + +func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, error) { + b.handlersLock.RLock() + defer b.handlersLock.RUnlock() + if handler, ok := b.handlers[id]; ok { + return handler, nil + } + return nil, errors.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) +} diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go new file mode 100644 index 000000000..e48aa566a --- /dev/null +++ b/core/broker/broker_test.go @@ -0,0 +1,134 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "sync" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" +) + +type mockHandlerDiscovery struct { + a *pb_discovery.Announcement +} + +func (d *mockHandlerDiscovery) ForAppID(appID string) (a []*pb_discovery.Announcement, err error) { + return d.All() +} + +func (d *mockHandlerDiscovery) Get(id string) (a *pb_discovery.Announcement, err error) { + if d.a == nil { + return nil, errors.New("Not found") + } + return d.a, nil +} + +func (d *mockHandlerDiscovery) All() (a []*pb_discovery.Announcement, err error) { + if d.a == nil { + return []*pb_discovery.Announcement{}, nil + } + return []*pb_discovery.Announcement{d.a}, nil +} + +func (d *mockHandlerDiscovery) AddAppID(_, _ string) error { + return nil +} + +type mockNetworkServer struct { + devices []*pb_lorawan.Device +} + +func (s *mockNetworkServer) GetDevices(ctx context.Context, req *pb_networkserver.DevicesRequest, options ...grpc.CallOption) (*pb_networkserver.DevicesResponse, error) { + return &pb_networkserver.DevicesResponse{ + Results: s.devices, + }, nil +} + +func (s *mockNetworkServer) PrepareActivation(ctx context.Context, activation *pb.DeduplicatedDeviceActivationRequest, options ...grpc.CallOption) (*pb.DeduplicatedDeviceActivationRequest, error) { + return activation, nil +} + +func (s *mockNetworkServer) Activate(ctx context.Context, activation *pb_handler.DeviceActivationResponse, options ...grpc.CallOption) (*pb_handler.DeviceActivationResponse, error) { + return activation, nil +} + +func (s *mockNetworkServer) Uplink(ctx context.Context, message *pb.DeduplicatedUplinkMessage, options ...grpc.CallOption) (*pb.DeduplicatedUplinkMessage, error) { + return message, nil +} + +func (s *mockNetworkServer) Downlink(ctx context.Context, message *pb.DownlinkMessage, options ...grpc.CallOption) (*pb.DownlinkMessage, error) { + return message, nil +} + +func TestActivateDeactivateRouter(t *testing.T) { + a := New(t) + + b := &broker{ + routers: make(map[string]chan *pb.DownlinkMessage), + } + + err := b.DeactivateRouter("RouterID") + a.So(err, ShouldNotBeNil) + + ch, err := b.ActivateRouter("RouterID") + a.So(err, ShouldBeNil) + a.So(ch, ShouldNotBeNil) + + _, err = b.ActivateRouter("RouterID") + a.So(err, ShouldNotBeNil) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + for range ch { + } + wg.Done() + }() + + err = b.DeactivateRouter("RouterID") + a.So(err, ShouldBeNil) + + wg.Wait() +} + +func TestActivateDeactivateHandler(t *testing.T) { + a := New(t) + + b := &broker{ + handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), + } + + err := b.DeactivateHandler("HandlerID") + a.So(err, ShouldNotBeNil) + + ch, err := b.ActivateHandler("HandlerID") + a.So(err, ShouldBeNil) + a.So(ch, ShouldNotBeNil) + + _, err = b.ActivateHandler("HandlerID") + a.So(err, ShouldNotBeNil) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + for range ch { + } + wg.Done() + }() + + err = b.DeactivateHandler("HandlerID") + a.So(err, ShouldBeNil) + + wg.Wait() +} diff --git a/core/broker/deduplicator.go b/core/broker/deduplicator.go new file mode 100644 index 000000000..ff308fa61 --- /dev/null +++ b/core/broker/deduplicator.go @@ -0,0 +1,93 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "sync" + "time" +) + +type collection struct { + sync.Mutex + ready chan bool + values []interface{} +} + +func newCollection() *collection { + return &collection{ + ready: make(chan bool, 1), + values: []interface{}{}, + } +} + +func (c *collection) Add(value interface{}) { + c.Lock() + defer c.Unlock() + c.values = append(c.values, value) +} + +func (c *collection) GetAndClear() []interface{} { + c.Lock() + defer c.Unlock() + values := c.values + c.values = []interface{}{} + return values +} + +func (c *collection) done() { + c.ready <- true +} + +func (c *collection) wait() { + <-c.ready +} + +type Deduplicator interface { + Deduplicate(key string, value interface{}) []interface{} +} + +type deduplicator struct { + sync.Mutex + timeout time.Duration + collections map[string]*collection +} + +func (d *deduplicator) add(key string, value interface{}) (c *collection, isFirst bool) { + d.Lock() + defer d.Unlock() + var ok bool + if c, ok = d.collections[key]; ok { + c.Add(value) + } else { + isFirst = true + c = newCollection() + c.Add(value) + d.collections[key] = c + } + return +} + +func (d *deduplicator) Deduplicate(key string, value interface{}) (values []interface{}) { + collection, isFirst := d.add(key, value) + if isFirst { + go func() { + <-time.After(d.timeout) + collection.done() + <-time.After(d.timeout) + d.Lock() + defer d.Unlock() + delete(d.collections, key) + }() + collection.wait() + values = collection.GetAndClear() + } + return +} + +func NewDeduplicator(timeout time.Duration) Deduplicator { + return &deduplicator{ + timeout: timeout, + collections: map[string]*collection{}, + } +} diff --git a/core/broker/deduplicator_test.go b/core/broker/deduplicator_test.go new file mode 100644 index 000000000..b8d58b619 --- /dev/null +++ b/core/broker/deduplicator_test.go @@ -0,0 +1,70 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "sync" + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestCollectionAdd(t *testing.T) { + a := New(t) + c := newCollection() + c.Add("item") + a.So(c.values, ShouldContain, "item") +} + +func TestCollectionGetAndClear(t *testing.T) { + a := New(t) + c := newCollection() + a.So(c.GetAndClear(), ShouldBeEmpty) + c.Add("item1") + c.Add("item2") + a.So(c.GetAndClear(), ShouldResemble, []interface{}{"item1", "item2"}) + a.So(c.GetAndClear(), ShouldBeEmpty) +} + +func TestCollectionWaitDone(t *testing.T) { + c := newCollection() + c.done() + c.wait() + var wg sync.WaitGroup + wg.Add(1) + go func() { + c.wait() + wg.Done() + }() + c.done() + wg.Wait() +} + +func TestDeduplicatorAdd(t *testing.T) { + a := New(t) + d := NewDeduplicator(5 * time.Millisecond).(*deduplicator) + a.So(d.collections, ShouldBeEmpty) + d.add("key", "item") + a.So(d.collections, ShouldNotBeEmpty) +} + +func TestDeduplicatorDeduplicate(t *testing.T) { + a := New(t) + d := NewDeduplicator(10 * time.Millisecond).(*deduplicator) + var wg sync.WaitGroup + wg.Add(1) + go func() { + res := d.Deduplicate("key", "value1") + a.So(res, ShouldResemble, []interface{}{"value1", "value2", "value3"}) + wg.Done() + }() + + <-time.After(5 * time.Millisecond) + + a.So(d.Deduplicate("key", "value2"), ShouldBeNil) + a.So(d.Deduplicate("key", "value3"), ShouldBeNil) + + wg.Wait() +} diff --git a/core/broker/downlink.go b/core/broker/downlink.go new file mode 100644 index 000000000..777273b98 --- /dev/null +++ b/core/broker/downlink.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "strings" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" +) + +// ByScore is used to sort a list of DownlinkOptions based on Score +type ByScore []*pb.DownlinkOption + +func (a ByScore) Len() int { return len(a) } +func (a ByScore) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByScore) Less(i, j int) bool { return a[i].Score < a[j].Score } + +func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { + ctx := b.Ctx.WithFields(log.Fields{ + "DevEUI": *downlink.DevEui, + "AppEUI": *downlink.AppEui, + }) + var err error + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle downlink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled downlink") + } + }() + + b.status.downlink.Mark(1) + + downlink, err = b.ns.Downlink(b.Component.GetContext(b.nsToken), downlink) + if err != nil { + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle downlink") + } + + var routerID string + if id := strings.Split(downlink.DownlinkOption.Identifier, ":"); len(id) == 2 { + routerID = id[0] + } else { + return errors.NewErrInvalidArgument("DownlinkOption Identifier", "invalid format") + } + ctx = ctx.WithField("RouterID", routerID) + + var router chan<- *pb.DownlinkMessage + router, err = b.getRouter(routerID) + if err != nil { + return err + } + + router <- downlink + + return nil +} diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go new file mode 100644 index 000000000..4eb7bf8a6 --- /dev/null +++ b/core/broker/downlink_test.go @@ -0,0 +1,62 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestDownlink(t *testing.T) { + a := New(t) + + appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} + devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} + + dlch := make(chan *pb.DownlinkMessage, 2) + + b := &broker{ + Component: &component.Component{ + Ctx: GetLogger(t, "TestDownlink"), + }, + ns: &mockNetworkServer{}, + routers: map[string]chan *pb.DownlinkMessage{ + "routerID": dlch, + }, + } + b.InitStatus() + + err := b.HandleDownlink(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, + DownlinkOption: &pb.DownlinkOption{ + Identifier: "fakeID", + }, + }) + a.So(err, ShouldNotBeNil) + + err = b.HandleDownlink(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, + DownlinkOption: &pb.DownlinkOption{ + Identifier: "nonExistentRouterID:scheduleID", + }, + }) + a.So(err, ShouldNotBeNil) + + err = b.HandleDownlink(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, + DownlinkOption: &pb.DownlinkOption{ + Identifier: "routerID:scheduleID", + }, + }) + a.So(err, ShouldBeNil) + a.So(len(dlch), ShouldEqual, 1) +} diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go new file mode 100644 index 000000000..1ac1938dc --- /dev/null +++ b/core/broker/manager_server.go @@ -0,0 +1,138 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/rights" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/ratelimit" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type brokerManager struct { + broker *broker + deviceManager pb_lorawan.DeviceManagerClient + devAddrManager pb_lorawan.DevAddrManagerClient + clientRate *ratelimit.Registry +} + +func (b *brokerManager) validateClient(ctx context.Context) (*claims.Claims, error) { + claims, err := b.broker.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if b.clientRate.Limit(claims.Subject) { + return claims, grpc.Errorf(codes.ResourceExhausted, "Rate limit for client reached") + } + return claims, nil +} + +func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { + if _, err := b.validateClient(ctx); err != nil { + return nil, err + } + res, err := b.deviceManager.GetDevice(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return device") + } + return res, nil +} + +func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*empty.Empty, error) { + if _, err := b.validateClient(ctx); err != nil { + return nil, err + } + res, err := b.deviceManager.SetDevice(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not set device") + } + return res, nil +} + +func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*empty.Empty, error) { + if _, err := b.validateClient(ctx); err != nil { + return nil, err + } + res, err := b.deviceManager.DeleteDevice(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not delete device") + } + return res, nil +} + +func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*empty.Empty, error) { + claims, err := b.broker.Component.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Application Handler Registration") + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied("No access to this application") + } + // Add Handler in local cache + handler, err := b.broker.Discovery.Get("handler", in.HandlerId) + if err != nil { + return nil, errors.NewErrInternal("Could not get Handler Announcement") + } + handler.Metadata = append(handler.Metadata, &discovery.Metadata{Metadata: &discovery.Metadata_AppId{ + AppId: in.AppId, + }}) + return &empty.Empty{}, nil +} + +func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesRequest) (*lorawan.PrefixesResponse, error) { + res, err := b.devAddrManager.GetPrefixes(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return prefixes") + } + return res, nil +} + +func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrRequest) (*lorawan.DevAddrResponse, error) { + res, err := b.devAddrManager.GetDevAddr(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return DevAddr") + } + return res, nil +} + +func (b *brokerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + if b.broker.Identity.Id != "dev" { + claims, err := b.broker.ValidateTTNAuthContext(ctx) + if err != nil || !claims.ComponentAccess(b.broker.Identity.Id) { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := b.broker.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil +} + +func (b *broker) RegisterManager(s *grpc.Server) { + server := &brokerManager{ + broker: b, + deviceManager: pb_lorawan.NewDeviceManagerClient(b.nsConn), + devAddrManager: pb_lorawan.NewDevAddrManagerClient(b.nsConn), + } + + server.clientRate = ratelimit.NewRegistry(5000, time.Hour) + + pb.RegisterBrokerManagerServer(s, server) + lorawan.RegisterDeviceManagerServer(s, server) + lorawan.RegisterDevAddrManagerServer(s, server) +} diff --git a/core/broker/server.go b/core/broker/server.go new file mode 100644 index 000000000..b83d7d1ec --- /dev/null +++ b/core/broker/server.go @@ -0,0 +1,138 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "time" + + "github.com/TheThingsNetwork/go-utils/log/apex" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/ratelimit" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +type brokerRPC struct { + broker *broker + pb.BrokerStreamServer + + routerUpRate *ratelimit.Registry + handlerDownRate *ratelimit.Registry +} + +func (b *brokerRPC) associateRouter(md metadata.MD) (chan *pb.UplinkMessage, <-chan *pb.DownlinkMessage, func(), error) { + ctx := metadata.NewContext(context.Background(), md) + router, err := b.broker.ValidateNetworkContext(ctx) + if err != nil { + return nil, nil, nil, err + } + down, err := b.broker.ActivateRouter(router.Id) + if err != nil { + return nil, nil, nil, err + } + + up := make(chan *pb.UplinkMessage, 1) + + cancel := func() { + b.broker.DeactivateRouter(router.Id) + } + + go func() { + for message := range up { + if waitTime := b.routerUpRate.Wait(router.Id); waitTime != 0 { + b.broker.Ctx.WithField("RouterID", router.Id).WithField("Wait", waitTime).Warn("Router reached uplink rate limit") + time.Sleep(waitTime) + } + go b.broker.HandleUplink(message) + } + }() + + return up, down, cancel, nil +} + +func (b *brokerRPC) getHandlerSubscribe(md metadata.MD) (<-chan *pb.DeduplicatedUplinkMessage, func(), error) { + ctx := metadata.NewContext(context.Background(), md) + handler, err := b.broker.ValidateNetworkContext(ctx) + if err != nil { + return nil, nil, err + } + + ch, err := b.broker.ActivateHandler(handler.Id) + if err != nil { + return nil, nil, err + } + + cancel := func() { + b.broker.DeactivateHandler(handler.Id) + } + + return ch, cancel, nil +} + +func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, error) { + ctx := metadata.NewContext(context.Background(), md) + handler, err := b.broker.ValidateNetworkContext(ctx) + if err != nil { + return nil, err + } + + ch := make(chan *pb.DownlinkMessage, 1) + go func() { + for message := range ch { + go func(downlink *pb.DownlinkMessage) { + // Get latest Handler metadata + handler, err := b.broker.Component.Discover("handler", handler.Id) + if err != nil { + return + } + for _, announcedID := range handler.AppIDs() { + if announcedID == downlink.AppId { + if waitTime := b.handlerDownRate.Wait(handler.Id); waitTime != 0 { + b.broker.Ctx.WithField("HandlerID", handler.Id).WithField("Wait", waitTime).Warn("Handler reached downlink rate limit") + time.Sleep(waitTime) + } + b.broker.HandleDownlink(downlink) + return + } + } + }(message) + } + }() + return ch, nil +} + +func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { + _, err = b.broker.ValidateNetworkContext(ctx) + if err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Activation Request") + } + res, err = b.broker.HandleActivation(req) + if err == errDuplicateActivation { + return nil, grpc.Errorf(codes.OutOfRange, err.Error()) + } + if err != nil { + return nil, err + } + return +} + +func (b *broker) RegisterRPC(s *grpc.Server) { + server := &brokerRPC{broker: b} + server.SetLogger(apex.Wrap(b.Ctx)) + server.RouterAssociateChanFunc = server.associateRouter + server.HandlerPublishChanFunc = server.getHandlerPublish + server.HandlerSubscribeChanFunc = server.getHandlerSubscribe + + // TODO: Monitor actual rates and configure sensible limits + server.routerUpRate = ratelimit.NewRegistry(1000, time.Second) + server.handlerDownRate = ratelimit.NewRegistry(125, time.Second) // one eight of uplink + + pb.RegisterBrokerServer(s, server) +} diff --git a/core/broker/status.go b/core/broker/status.go new file mode 100644 index 000000000..01893865f --- /dev/null +++ b/core/broker/status.go @@ -0,0 +1,97 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + uplinkUnique metrics.Meter + downlink metrics.Meter + activations metrics.Meter + activationsUnique metrics.Meter + deduplication metrics.Histogram + connectedRouters metrics.Gauge + connectedHandlers metrics.Gauge +} + +func (b *broker) InitStatus() { + b.status = &status{ + uplink: metrics.NewMeter(), + uplinkUnique: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + activationsUnique: metrics.NewMeter(), + deduplication: metrics.NewHistogram(metrics.NewUniformSample(512)), + connectedRouters: metrics.NewFunctionalGauge(func() int64 { + b.routersLock.RLock() + defer b.routersLock.RUnlock() + return int64(len(b.routers)) + }), + connectedHandlers: metrics.NewFunctionalGauge(func() int64 { + b.handlersLock.RLock() + defer b.handlersLock.RUnlock() + return int64(len(b.handlers)) + }), + } +} + +func (b *broker) GetStatus() *pb.Status { + status := new(pb.Status) + if b.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := b.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + uplinkUnique := b.status.uplinkUnique.Snapshot() + status.UplinkUnique = &api.Rates{ + Rate1: float32(uplinkUnique.Rate1()), + Rate5: float32(uplinkUnique.Rate5()), + Rate15: float32(uplinkUnique.Rate15()), + } + downlink := b.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := b.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + activationsUnique := b.status.activationsUnique.Snapshot() + status.UplinkUnique = &api.Rates{ + Rate1: float32(activationsUnique.Rate1()), + Rate5: float32(activationsUnique.Rate5()), + Rate15: float32(activationsUnique.Rate15()), + } + deduplication := b.status.deduplication.Snapshot().Percentiles([]float64{0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99}) + status.Deduplication = &api.Percentiles{ + Percentile1: float32(deduplication[0]), + Percentile5: float32(deduplication[1]), + Percentile10: float32(deduplication[2]), + Percentile25: float32(deduplication[3]), + Percentile50: float32(deduplication[4]), + Percentile75: float32(deduplication[5]), + Percentile90: float32(deduplication[6]), + Percentile95: float32(deduplication[7]), + Percentile99: float32(deduplication[8]), + } + status.ConnectedRouters = uint32(b.status.connectedRouters.Snapshot().Value()) + status.ConnectedHandlers = uint32(b.status.connectedHandlers.Snapshot().Value()) + return status +} diff --git a/core/broker/status_test.go b/core/broker/status_test.go new file mode 100644 index 000000000..21945649f --- /dev/null +++ b/core/broker/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + b := new(broker) + a.So(b.GetStatus(), ShouldResemble, new(pb.Status)) + b.InitStatus() + a.So(b.status, ShouldNotBeNil) + status := b.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/broker/uplink.go b/core/broker/uplink.go new file mode 100644 index 000000000..a9690bdf3 --- /dev/null +++ b/core/broker/uplink.go @@ -0,0 +1,245 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "sort" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/fcnt" + "github.com/apex/log" + "github.com/brocaar/lorawan" +) + +const maxFCntGap = 16384 + +func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { + ctx := b.Ctx.WithField("GatewayID", uplink.GatewayMetadata.GatewayId) + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle uplink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") + } + }() + + time := time.Now() + + b.status.uplink.Mark(1) + + // De-duplicate uplink messages + duplicates := b.deduplicateUplink(uplink) + if len(duplicates) == 0 { + return nil + } + + b.status.uplinkUnique.Mark(1) + + ctx = ctx.WithField("Duplicates", len(duplicates)) + + base := duplicates[0] + + if base.ProtocolMetadata.GetLorawan() == nil { + return errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") + } + + // LoRaWAN: Unmarshal + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(base.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + } + + // Request devices from NS + devAddr := types.DevAddr(macPayload.FHDR.DevAddr) + ctx = ctx.WithFields(log.Fields{ + "DevAddr": devAddr, + "FCnt": macPayload.FHDR.FCnt, + }) + var getDevicesResp *networkserver.DevicesResponse + getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(b.nsToken), &networkserver.DevicesRequest{ + DevAddr: &devAddr, + FCnt: macPayload.FHDR.FCnt, + }) + if err != nil { + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices") + } + b.status.deduplication.Update(int64(len(getDevicesResp.Results))) + if len(getDevicesResp.Results) == 0 { + return errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) + } + ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) + + // Sort by FCntUp to optimize the number of MIC checks + sort.Sort(ByFCntUp(getDevicesResp.Results)) + + // Find AppEUI/DevEUI through MIC check + var device *pb_lorawan.Device + var micChecks int + var originalFCnt uint32 + for _, candidate := range getDevicesResp.Results { + nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) + + // First check with the 16 bit counter + micChecks++ + ok, err = phyPayload.ValidateMIC(nwkSKey) + if err != nil { + return err + } + if ok { + device = candidate + break + } + + originalFCnt = macPayload.FHDR.FCnt + if candidate.Uses32BitFCnt { + macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCntUp, uint16(originalFCnt)) + + // If 32 bit counter has different value, perform another MIC check + if macPayload.FHDR.FCnt != originalFCnt { + micChecks++ + ok, err = phyPayload.ValidateMIC(nwkSKey) + if err != nil { + return err + } + if ok { + device = candidate + break + } + } + } + + return errors.NewErrNotFound("device that validates MIC") + } + ctx = ctx.WithFields(log.Fields{ + "MICChecks": micChecks, + "DevEUI": device.DevEui, + "AppEUI": device.AppEui, + "AppID": device.AppId, + "DevID": device.DevId, + "FCnt": originalFCnt, + }) + if macPayload.FHDR.FCnt != originalFCnt { + ctx = ctx.WithField("RealFCnt", macPayload.FHDR.FCnt) + } + + if device.DisableFCntCheck { + // TODO: Add warning to message? + } else if device.FCntUp == 0 { + + } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { + // Replay attack or FCnt gap too big + return errors.NewErrNotFound("device with matching FCnt") + } + + // Add FCnt to Metadata (because it's not marshaled in lorawan payload) + base.ProtocolMetadata.GetLorawan().FCnt = macPayload.FHDR.FCnt + + // Collect GatewayMetadata and DownlinkOptions + var gatewayMetadata []*gateway.RxMetadata + var downlinkOptions []*pb.DownlinkOption + var downlinkMessage *pb.DownlinkMessage + for _, duplicate := range duplicates { + gatewayMetadata = append(gatewayMetadata, duplicate.GatewayMetadata) + downlinkOptions = append(downlinkOptions, duplicate.DownlinkOptions...) + } + + // Select best DownlinkOption + if len(downlinkOptions) > 0 { + downlinkMessage = &pb.DownlinkMessage{ + DevEui: device.DevEui, + AppEui: device.AppEui, + AppId: device.AppId, + DevId: device.DevId, + DownlinkOption: selectBestDownlink(downlinkOptions), + } + } + + // Build Uplink + deduplicatedUplink := &pb.DeduplicatedUplinkMessage{ + Payload: base.Payload, + DevEui: device.DevEui, + DevId: device.DevId, + AppEui: device.AppEui, + AppId: device.AppId, + ProtocolMetadata: base.ProtocolMetadata, + GatewayMetadata: gatewayMetadata, + ServerTime: time.UnixNano(), + ResponseTemplate: downlinkMessage, + } + + // Pass Uplink through NS + deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(b.nsToken), deduplicatedUplink) + if err != nil { + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle uplink") + } + + var announcements []*pb_discovery.Announcement + announcements, err = b.Discovery.GetAllHandlersForAppID(device.AppId) + if err != nil { + return err + } + if len(announcements) == 0 { + return errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) + } + if len(announcements) > 1 { + return errors.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) + } + + var handler chan<- *pb.DeduplicatedUplinkMessage + handler, err = b.getHandler(announcements[0].Id) + if err != nil { + return err + } + + handler <- deduplicatedUplink + + return nil +} + +func (b *broker) deduplicateUplink(duplicate *pb.UplinkMessage) (uplinks []*pb.UplinkMessage) { + sum := md5.Sum(duplicate.Payload) + key := hex.EncodeToString(sum[:]) + list := b.uplinkDeduplicator.Deduplicate(key, duplicate) + if len(list) == 0 { + return + } + for _, duplicate := range list { + uplinks = append(uplinks, duplicate.(*pb.UplinkMessage)) + } + return +} + +func selectBestDownlink(options []*pb.DownlinkOption) *pb.DownlinkOption { + sort.Sort(ByScore(options)) + return options[0] +} + +// ByFCntUp implements sort.Interface for []*pb_lorawan.Device based on FCnt +type ByFCntUp []*pb_lorawan.Device + +func (a ByFCntUp) Len() int { return len(a) } +func (a ByFCntUp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByFCntUp) Less(i, j int) bool { + // Devices that disable the FCnt check have low priority + if a[i].DisableFCntCheck { + return 2*int(a[i].FCntUp)+100 < int(a[j].FCntUp) + } + return int(a[i].FCntUp) < int(a[j].FCntUp) +} diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go new file mode 100644 index 000000000..fc4fea464 --- /dev/null +++ b/core/broker/uplink_test.go @@ -0,0 +1,185 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "sync" + "testing" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/gateway" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" + "github.com/golang/mock/gomock" + . "github.com/smartystreets/assertions" +) + +func TestHandleUplink(t *testing.T) { + a := New(t) + + b := getTestBroker(t) + + gtwID := "eui-0102030405060708" + + // Invalid Payload + err := b.HandleUplink(&pb.UplinkMessage{ + Payload: []byte{0x01, 0x02, 0x03}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldNotBeNil) + + // Valid Payload + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + // Device not found + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(&pb_networkserver.DevicesResponse{ + Results: []*pb_lorawan.Device{}, + }, nil) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, + }) + a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) + + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9} + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "appid-1" + nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} + + // Add devices + b = getTestBroker(t) + nsResponse := &pb_networkserver.DevicesResponse{ + Results: []*pb_lorawan.Device{ + &pb_lorawan.Device{ + DevEui: &wrongDevEUI, + AppEui: &appEUI, + AppId: appID, + NwkSKey: &nwkSKey, + FCntUp: 4, + }, + &pb_lorawan.Device{ + DevEui: &devEUI, + AppEui: &appEUI, + AppId: appID, + NwkSKey: &nwkSKey, + FCntUp: 3, + }, + }, + } + b.handlers["handlerID"] = make(chan *pb.DeduplicatedUplinkMessage, 10) + + // Device doesn't match + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, + }) + a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) + + phy.SetMIC(lorawan.AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) + bytes, _ = phy.MarshalBinary() + + // Wrong FCnt + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, + }) + a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) + + // Disable FCnt Check + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + nsResponse.Results[0].DisableFCntCheck = true + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) + b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any()) + b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{ + &pb_discovery.Announcement{ + Id: "handlerID", + }, + }, nil) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, + }) + a.So(err, ShouldBeNil) + + // OK FCnt + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + nsResponse.Results[0].FCntUp = 0 + nsResponse.Results[0].DisableFCntCheck = false + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) + b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any()) + b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{ + &pb_discovery.Announcement{ + Id: "handlerID", + }, + }, nil) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, + }) + a.So(err, ShouldBeNil) +} + +func TestDeduplicateUplink(t *testing.T) { + a := New(t) + + payload := []byte{0x01, 0x02, 0x03} + protocolMetadata := &protocol.RxMetadata{} + uplink1 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, ProtocolMetadata: protocolMetadata} + uplink2 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 3.4}, ProtocolMetadata: protocolMetadata} + uplink3 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} + uplink4 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} + + b := getTestBroker(t) + b.uplinkDeduplicator = NewDeduplicator(20 * time.Millisecond).(*deduplicator) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + res := b.deduplicateUplink(uplink1) + a.So(res, ShouldResemble, []*pb.UplinkMessage{uplink1, uplink2, uplink3}) + a.So(res, ShouldNotContain, uplink4) + wg.Done() + }() + + <-time.After(10 * time.Millisecond) + + a.So(b.deduplicateUplink(uplink2), ShouldBeNil) + a.So(b.deduplicateUplink(uplink3), ShouldBeNil) + + <-time.After(50 * time.Millisecond) + + a.So(b.deduplicateUplink(uplink4), ShouldNotBeNil) + + wg.Wait() +} diff --git a/core/broker/util_test.go b/core/broker/util_test.go new file mode 100644 index 000000000..2c0332cf4 --- /dev/null +++ b/core/broker/util_test.go @@ -0,0 +1,46 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/component" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" +) + +type testBroker struct { + *broker + ctrl *gomock.Controller + discovery *pb_discovery.MockClient + ns *pb_networkserver.MockNetworkServerClient +} + +func getTestBroker(t *testing.T) *testBroker { + ctrl := gomock.NewController(t) + discovery := pb_discovery.NewMockClient(ctrl) + ns := pb_networkserver.NewMockNetworkServerClient(ctrl) + b := &testBroker{ + broker: &broker{ + Component: &component.Component{ + Discovery: discovery, + Ctx: GetLogger(t, "TestBroker"), + }, + handlers: make(map[string]chan *pb_broker.DeduplicatedUplinkMessage), + activationDeduplicator: NewDeduplicator(10 * time.Millisecond), + uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), + ns: ns, + }, + ns: ns, + ctrl: ctrl, + discovery: discovery, + } + b.InitStatus() + return b +} diff --git a/core/component/auth.go b/core/component/auth.go new file mode 100644 index 000000000..ee36eb21c --- /dev/null +++ b/core/component/auth.go @@ -0,0 +1,302 @@ +package component + +import ( + "crypto/tls" + "fmt" + "io/ioutil" + "net/url" + "path" + "path/filepath" + "strings" + "time" + + "github.com/TheThingsNetwork/go-account-lib/cache" + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/keys" + "github.com/TheThingsNetwork/go-account-lib/oauth" + "github.com/TheThingsNetwork/go-account-lib/tokenkey" + "github.com/TheThingsNetwork/ttn/api" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/security" + jwt "github.com/dgrijalva/jwt-go" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +// InitAuth initializes Auth functionality +func (c *Component) InitAuth() error { + inits := []func() error{ + c.initAuthServers, + c.initKeyPair, + c.initRoots, + } + if c.Config.UseTLS { + inits = append(inits, c.initTLS) + } + + for _, init := range inits { + if err := init(); err != nil { + return err + } + } + + return nil +} + +type authServer struct { + url string + username string + password string +} + +func parseAuthServer(str string) (srv authServer, err error) { + url, err := url.Parse(str) + if err != nil { + return srv, err + } + srv.url = fmt.Sprintf("%s://%s", url.Scheme, url.Host) + if url.User != nil { + srv.username = url.User.Username() + srv.password, _ = url.User.Password() + } + return srv, nil +} + +func (c *Component) initAuthServers() error { + urlMap := make(map[string]string) + funcMap := make(map[string]tokenkey.TokenFunc) + var httpProvider tokenkey.Provider + for id, url := range c.Config.AuthServers { + id, url := id, url // deliberately shadow these + if strings.HasPrefix(url, "file://") { + file := strings.TrimPrefix(url, "file://") + contents, err := ioutil.ReadFile(path.Clean(file)) + if err != nil { + return err + } + funcMap[id] = func(renew bool) (*tokenkey.TokenKey, error) { + return &tokenkey.TokenKey{Algorithm: "ES256", Key: string(contents)}, nil + } + continue + } + srv, err := parseAuthServer(url) + if err != nil { + return err + } + urlMap[id] = srv.url + funcMap[id] = func(renew bool) (*tokenkey.TokenKey, error) { + return httpProvider.Get(id, renew) + } + } + httpProvider = tokenkey.HTTPProvider( + urlMap, + cache.WriteTroughCacheWithFormat(c.Config.KeyDir, "auth-%s.pub"), + ) + c.TokenKeyProvider = tokenkey.FuncProvider(funcMap) + return nil +} + +// UpdateTokenKey updates the OAuth Bearer token key +func (c *Component) UpdateTokenKey() error { + if c.TokenKeyProvider == nil { + return errors.NewErrInternal("No public key provider configured for token validation") + } + + // Set up Auth Server Token Validation + err := c.TokenKeyProvider.Update() + if err != nil { + c.Ctx.Warnf("ttn: Failed to refresh public keys for token validation: %s", err.Error()) + } else { + c.Ctx.Info("ttn: Got public keys for token validation") + } + + return nil +} + +func (c *Component) initKeyPair() error { + priv, err := security.LoadKeypair(c.Config.KeyDir) + if err != nil { + return err + } + c.privateKey = priv + + pubPEM, _ := security.PublicPEM(priv) + c.Identity.PublicKey = string(pubPEM) + + return nil +} + +func (c *Component) initTLS() error { + cert, err := security.LoadCert(c.Config.KeyDir) + if err != nil { + return err + } + c.Identity.Certificate = string(cert) + + privPEM, _ := security.PrivatePEM(c.privateKey) + cer, err := tls.X509KeyPair(cert, privPEM) + if err != nil { + return err + } + + c.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} + return nil +} + +func (c *Component) initRoots() error { + path := filepath.Clean(c.Config.KeyDir + "/ca.cert") + cert, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + if !api.RootCAs.AppendCertsFromPEM(cert) { + return fmt.Errorf("Could not add root certificates from %s", path) + } + return nil +} + +// BuildJWT builds a short-lived JSON Web Token for this component +func (c *Component) BuildJWT() (string, error) { + if c.privateKey != nil { + privPEM, err := security.PrivatePEM(c.privateKey) + if err != nil { + return "", err + } + return security.BuildJWT(c.Identity.Id, 20*time.Second, privPEM) + } + return "", nil +} + +// GetContext returns a context for outgoing RPC request. If token is "", this function will generate a short lived token from the component +func (c *Component) GetContext(token string) context.Context { + var serviceName, serviceVersion, id, netAddress string + if c.Identity != nil { + serviceName = c.Identity.ServiceName + id = c.Identity.Id + if token == "" { + token, _ = c.BuildJWT() + } + serviceVersion = c.Identity.ServiceVersion + netAddress = c.Identity.NetAddress + } + md := metadata.Pairs( + "service-name", serviceName, + "service-version", serviceVersion, + "id", id, + "token", token, + "net-address", netAddress, + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} + +// ExchangeAppKeyForToken enables authentication with the App Access Key +func (c *Component) ExchangeAppKeyForToken(appID, key string) (string, error) { + issuerID := keys.KeyIssuer(key) + if issuerID == "" { + // Take the first configured auth server + for k := range c.Config.AuthServers { + issuerID = k + break + } + key = fmt.Sprintf("%s.%s", issuerID, key) + } + issuer, ok := c.Config.AuthServers[issuerID] + if !ok { + return "", fmt.Errorf("Auth server %s not registered", issuer) + } + + srv, _ := parseAuthServer(issuer) + + oauth := oauth.OAuth(srv.url, &oauth.Client{ + ID: srv.username, + Secret: srv.password, + }) + + token, err := oauth.ExchangeAppKeyForToken(appID, key) + if err != nil { + return "", err + } + + return token.AccessToken, nil +} + +// ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) +func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_discovery.Announcement, err error) { + defer func() { + if err != nil { + time.Sleep(time.Second) + } + }() + + md, ok := metadata.FromContext(ctx) + if !ok { + err = errors.NewErrInternal("Could not get metadata from context") + return + } + var id, serviceName, token string + if ids, ok := md["id"]; ok && len(ids) == 1 { + id = ids[0] + } + if id == "" { + err = errors.NewErrInvalidArgument("Metadata", "id missing") + return + } + if serviceNames, ok := md["service-name"]; ok && len(serviceNames) == 1 { + serviceName = serviceNames[0] + } + if serviceName == "" { + err = errors.NewErrInvalidArgument("Metadata", "service-name missing") + return + } + if tokens, ok := md["token"]; ok && len(tokens) == 1 { + token = tokens[0] + } + + var announcement *pb_discovery.Announcement + announcement, err = c.Discover(serviceName, id) + if err != nil { + return + } + + if announcement.PublicKey == "" { + return announcement, nil + } + + if token == "" { + err = errors.NewErrInvalidArgument("Metadata", "token missing") + return + } + + var claims *jwt.StandardClaims + claims, err = security.ValidateJWT(token, []byte(announcement.PublicKey)) + if err != nil { + return + } + if claims.Issuer != id { + err = errors.NewErrInvalidArgument("Metadata", "token was issued by different component id") + return + } + + return announcement, nil +} + +// ValidateTTNAuthContext gets a token from the context and validates it +func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) { + token, err := api.TokenFromContext(ctx) + if err != nil { + return nil, err + } + + if c.TokenKeyProvider == nil { + return nil, errors.NewErrInternal("No token provider configured") + } + + claims, err := claims.FromToken(c.TokenKeyProvider, token) + if err != nil { + return nil, errors.NewErrPermissionDenied(err.Error()) + } + + return claims, nil +} diff --git a/core/component/auth_test.go b/core/component/auth_test.go new file mode 100644 index 000000000..d9bd483b3 --- /dev/null +++ b/core/component/auth_test.go @@ -0,0 +1,286 @@ +package component + +import ( + "context" + "fmt" + "math/rand" + "os" + "strings" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/security" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" + "github.com/smartystreets/assertions" + "google.golang.org/grpc/metadata" +) + +func TestParseAuthServer(t *testing.T) { + a := assertions.New(t) + { + srv, err := parseAuthServer("https://user:pass@account.thethingsnetwork.org/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "https://account.thethingsnetwork.org") + a.So(srv.username, assertions.ShouldEqual, "user") + a.So(srv.password, assertions.ShouldEqual, "pass") + } + { + srv, err := parseAuthServer("https://user@account.thethingsnetwork.org/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "https://account.thethingsnetwork.org") + a.So(srv.username, assertions.ShouldEqual, "user") + } + { + srv, err := parseAuthServer("http://account.thethingsnetwork.org/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "http://account.thethingsnetwork.org") + } + { + srv, err := parseAuthServer("http://localhost:9090/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "http://localhost:9090") + } +} + +func TestInitAuthServers(t *testing.T) { + for _, env := range strings.Split("ACCOUNT_SERVER_PROTO ACCOUNT_SERVER_USERNAME ACCOUNT_SERVER_PASSWORD ACCOUNT_SERVER_URL", " ") { + if os.Getenv(env) == "" { + t.Skipf("Skipping auth server test: %s configured", env) + } + } + + a := assertions.New(t) + c := new(Component) + c.Config.KeyDir = os.TempDir() + c.Ctx = GetLogger(t, "TestInitAuthServers") + c.Config.AuthServers = map[string]string{ + "ttn": fmt.Sprintf("%s://%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + "ttn-user": fmt.Sprintf("%s://%s@%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_USERNAME"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + "ttn-user-pass": fmt.Sprintf("%s://%s:%s@%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_USERNAME"), + os.Getenv("ACCOUNT_SERVER_PASSWORD"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + } + err := c.initAuthServers() + a.So(err, assertions.ShouldBeNil) + + { + k, err := c.TokenKeyProvider.Get("ttn", true) + a.So(err, assertions.ShouldBeNil) + a.So(k.Algorithm, assertions.ShouldEqual, "RS256") + } + + { + k, err := c.TokenKeyProvider.Get("ttn-user", true) + a.So(err, assertions.ShouldBeNil) + a.So(k.Algorithm, assertions.ShouldEqual, "RS256") + } + + { + k, err := c.TokenKeyProvider.Get("ttn-user-pass", true) + a.So(err, assertions.ShouldBeNil) + a.So(k.Algorithm, assertions.ShouldEqual, "RS256") + } + + a.So(c.UpdateTokenKey(), assertions.ShouldBeNil) +} + +func TestValidateTTNAuthContext(t *testing.T) { + for _, env := range strings.Split("ACCOUNT_SERVER_PROTO ACCOUNT_SERVER_URL", " ") { + if os.Getenv(env) == "" { + t.Skipf("Skipping auth server test: %s configured", env) + } + } + accountServer := fmt.Sprintf("%s://%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_URL"), + ) + + a := assertions.New(t) + c := new(Component) + c.Config.KeyDir = os.TempDir() + c.Config.AuthServers = map[string]string{ + "ttn-account-preview": accountServer, + } + err := c.initAuthServers() + a.So(err, assertions.ShouldBeNil) + + { + ctx := context.Background() + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + { + md := metadata.Pairs() + ctx := metadata.NewContext(context.Background(), md) + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + { + md := metadata.Pairs( + "id", "dev", + ) + ctx := metadata.NewContext(context.Background(), md) + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + { + md := metadata.Pairs( + "id", "dev", + "token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA", + ) + ctx := metadata.NewContext(context.Background(), md) + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldBeNil) + } +} + +func TestExchangeAppKeyForToken(t *testing.T) { + for _, env := range strings.Split("ACCOUNT_SERVER_PROTO ACCOUNT_SERVER_USERNAME ACCOUNT_SERVER_PASSWORD ACCOUNT_SERVER_URL APP_ID APP_TOKEN", " ") { + if os.Getenv(env) == "" { + t.Skipf("Skipping auth server test: %s configured", env) + } + } + + a := assertions.New(t) + c := new(Component) + c.Config.KeyDir = os.TempDir() + c.Config.AuthServers = map[string]string{ + "ttn-account-preview": fmt.Sprintf("%s://%s:%s@%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_USERNAME"), + os.Getenv("ACCOUNT_SERVER_PASSWORD"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + } + c.initAuthServers() + + { + token, err := c.ExchangeAppKeyForToken(os.Getenv("APP_ID"), "ttn-account-preview."+os.Getenv("APP_TOKEN")) + a.So(err, assertions.ShouldBeNil) + a.So(token, assertions.ShouldNotBeEmpty) + } + + { + token, err := c.ExchangeAppKeyForToken(os.Getenv("APP_ID"), os.Getenv("APP_TOKEN")) + a.So(err, assertions.ShouldBeNil) + a.So(token, assertions.ShouldNotBeEmpty) + } +} + +func TestInitKeyPair(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + c.Identity = new(discovery.Announcement) + c.Config.KeyDir = tmpDir + + a.So(c.initKeyPair(), assertions.ShouldNotBeNil) + + security.GenerateKeypair(tmpDir) + + a.So(c.initKeyPair(), assertions.ShouldBeNil) + + a.So(c.Identity.PublicKey, assertions.ShouldNotBeEmpty) + a.So(c.privateKey, assertions.ShouldNotBeNil) +} + +func TestInitTLS(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + c.Identity = new(discovery.Announcement) + c.Config.KeyDir = tmpDir + + security.GenerateKeypair(tmpDir) + c.initKeyPair() + + a.So(c.initTLS(), assertions.ShouldNotBeNil) + + security.GenerateCert(tmpDir) + + a.So(c.initTLS(), assertions.ShouldBeNil) + + a.So(c.Identity.Certificate, assertions.ShouldNotBeEmpty) + a.So(c.tlsConfig, assertions.ShouldNotBeNil) +} + +func TestInit(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + c.Identity = new(discovery.Announcement) + c.Config.KeyDir = tmpDir + + security.GenerateKeypair(tmpDir) + + a.So(c.InitAuth(), assertions.ShouldBeNil) +} + +func TestGetAndVerifyContext(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + + c.Identity = new(discovery.Announcement) + + c.Config.KeyDir = tmpDir + security.GenerateKeypair(tmpDir) + c.initKeyPair() + + { + ctx := c.GetContext("") + _, err := c.ValidateNetworkContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + c.Identity.Id = "test-context" + { + ctx := c.GetContext("") + _, err := c.ValidateNetworkContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + c.Identity.ServiceName = "test-service" + + ctrl := gomock.NewController(t) + discoveryClient := discovery.NewMockClient(ctrl) + c.Discovery = discoveryClient + + discoveryClient.EXPECT().Get("test-service", "test-context").Return(c.Identity, nil) + + ctx := c.GetContext("") + _, err := c.ValidateNetworkContext(ctx) + a.So(err, assertions.ShouldBeNil) + +} diff --git a/core/component/component.go b/core/component/component.go new file mode 100644 index 000000000..3ecfb318c --- /dev/null +++ b/core/component/component.go @@ -0,0 +1,131 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package component contains code that is shared by all components (discovery, router, broker, networkserver, handler) +package component + +import ( + "crypto/ecdsa" + "crypto/tls" + "fmt" + "net/http" + "runtime" + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/tokenkey" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" + "github.com/apex/log" + "github.com/spf13/viper" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/health" +) + +// Component contains the common attributes for all TTN components +type Component struct { + Config Config + Identity *pb_discovery.Announcement + Discovery pb_discovery.Client + Monitors map[string]*pb_monitor.Client + Ctx log.Interface + AccessToken string + privateKey *ecdsa.PrivateKey + tlsConfig *tls.Config + TokenKeyProvider tokenkey.Provider + status int64 + healthServer *health.Server +} + +type Interface interface { + RegisterRPC(s *grpc.Server) + Init(c *Component) error + Shutdown() + ValidateNetworkContext(ctx context.Context) (*pb_discovery.Announcement, error) + ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) +} + +type ManagementInterface interface { + RegisterManager(s *grpc.Server) +} + +// New creates a new Component +func New(ctx log.Interface, serviceName string, announcedAddress string) (*Component, error) { + go func() { + memstats := new(runtime.MemStats) + for range time.Tick(time.Minute) { + runtime.ReadMemStats(memstats) + ctx.WithFields(log.Fields{ + "Goroutines": runtime.NumGoroutine(), + "Memory": float64(memstats.Alloc) / 1000000, + }).Debugf("Stats") + } + }() + + // Disable gRPC tracing + // SEE: https://github.com/grpc/grpc-go/issues/695 + grpc.EnableTracing = false + + component := &Component{ + Config: ConfigFromViper(), + Ctx: ctx, + Identity: &pb_discovery.Announcement{ + Id: viper.GetString("id"), + Description: viper.GetString("description"), + ServiceName: serviceName, + ServiceVersion: fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), + NetAddress: announcedAddress, + Public: viper.GetBool("public"), + }, + AccessToken: viper.GetString("auth-token"), + } + + if err := component.InitAuth(); err != nil { + return nil, err + } + + if serviceName != "discovery" && serviceName != "networkserver" { + var err error + component.Discovery, err = pb_discovery.NewClient( + viper.GetString("discovery-address"), + component.Identity, + func() string { + token, _ := component.BuildJWT() + return token + }, + ) + if err != nil { + return nil, err + } + } + + if healthPort := viper.GetInt("health-port"); healthPort > 0 { + http.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { + switch component.GetStatus() { + case StatusHealthy: + w.WriteHeader(200) + w.Write([]byte("Status is HEALTHY")) + return + case StatusUnhealthy: + w.WriteHeader(503) + w.Write([]byte("Status is UNHEALTHY")) + return + } + }) + go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) + } + + if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { + component.Monitors = make(map[string]*pb_monitor.Client) + for name, addr := range monitors { + var err error + component.Monitors[name], err = pb_monitor.NewClient(ctx.WithField("Monitor", name), addr) + if err != nil { + return nil, err + } + } + } + + return component, nil +} diff --git a/core/component/config.go b/core/component/config.go new file mode 100644 index 000000000..a20402355 --- /dev/null +++ b/core/component/config.go @@ -0,0 +1,19 @@ +package component + +import "github.com/spf13/viper" + +// Config is the configuration for this component +type Config struct { + AuthServers map[string]string + KeyDir string + UseTLS bool +} + +// ConfigFromViper imports configuration from Viper +func ConfigFromViper() Config { + return Config{ + AuthServers: viper.GetStringMapString("auth-servers"), + KeyDir: viper.GetString("key-dir"), + UseTLS: viper.GetBool("tls"), + } +} diff --git a/core/component/discovery.go b/core/component/discovery.go new file mode 100644 index 000000000..6a3ab66d8 --- /dev/null +++ b/core/component/discovery.go @@ -0,0 +1,29 @@ +package component + +import ( + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Discover is used to discover another component +func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { + res, err := c.Discovery.Get(serviceName, id) + if err != nil { + return nil, errors.Wrapf(errors.FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) + } + return res, nil +} + +// Announce the component to TTN discovery +func (c *Component) Announce() error { + if c.Identity.Id == "" { + return errors.NewErrInvalidArgument("Component ID", "can not be empty") + } + err := c.Discovery.Announce(c.AccessToken) + if err != nil { + return errors.Wrapf(errors.FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) + } + c.Ctx.Info("ttn: Announced to TTN discovery") + + return nil +} diff --git a/core/component/grpc.go b/core/component/grpc.go new file mode 100644 index 000000000..8ea5e2140 --- /dev/null +++ b/core/component/grpc.go @@ -0,0 +1,91 @@ +package component + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/mwitkow/go-grpc-middleware" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" +) + +func (c *Component) ServerOptions() []grpc.ServerOption { + unary := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + var peerAddr string + peer, ok := peer.FromContext(ctx) + if ok { + peerAddr = peer.Addr.String() + } + var peerID string + meta, ok := metadata.FromContext(ctx) + if ok { + id, ok := meta["id"] + if ok && len(id) > 0 { + peerID = id[0] + } + } + logCtx := c.Ctx.WithFields(log.Fields{ + "CallerID": peerID, + "CallerIP": peerAddr, + "Method": info.FullMethod, + }) + t := time.Now() + iface, err := handler(ctx, req) + err = errors.BuildGRPCError(err) + logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) + if grpc.Code(err) == codes.OK || grpc.Code(err) == codes.Canceled { + logCtx.Debug("Handled request") + } else { + logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Debug("Handled request with error") + } + return iface, err + } + + stream := func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + var peerAddr string + peer, ok := peer.FromContext(stream.Context()) + if ok { + peerAddr = peer.Addr.String() + } + var peerID string + meta, ok := metadata.FromContext(stream.Context()) + if ok { + id, ok := meta["id"] + if ok && len(id) > 0 { + peerID = id[0] + } + } + logCtx := c.Ctx.WithFields(log.Fields{ + "CallerID": peerID, + "CallerIP": peerAddr, + "Method": info.FullMethod, + }) + t := time.Now() + logCtx.Debug("Start stream") + err := handler(srv, stream) + err = errors.BuildGRPCError(err) + logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) + if grpc.Code(err) == codes.OK || grpc.Code(err) == codes.Canceled { + logCtx.Debug("End stream") + } else { + logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Debug("End stream with error") + } + return err + } + + opts := []grpc.ServerOption{ + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary)), + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream)), + } + + if c.tlsConfig != nil { + opts = append(opts, grpc.Creds(credentials.NewTLS(c.tlsConfig))) + } + + return opts +} diff --git a/core/component/pprof.go b/core/component/pprof.go new file mode 100644 index 000000000..0512776ac --- /dev/null +++ b/core/component/pprof.go @@ -0,0 +1,8 @@ +// +build pprof + +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package component + +import _ "net/http/pprof" diff --git a/core/component/status.go b/core/component/status.go new file mode 100644 index 000000000..bd0516c53 --- /dev/null +++ b/core/component/status.go @@ -0,0 +1,43 @@ +package component + +import ( + "sync/atomic" + + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +// Status indicates the health status of this component +type Status int + +const ( + // StatusHealthy indicates a healthy component + StatusHealthy Status = iota + // StatusUnhealthy indicates an unhealthy component + StatusUnhealthy +) + +// GetStatus gets the health status of the component +func (c *Component) GetStatus() Status { + return Status(atomic.LoadInt64(&c.status)) +} + +// SetStatus sets the health status of the component +func (c *Component) SetStatus(status Status) { + atomic.StoreInt64(&c.status, int64(status)) + if c.healthServer != nil { + switch status { + case StatusHealthy: + c.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) + case StatusUnhealthy: + c.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING) + } + } +} + +// RegisterHealthServer registers the component's health status to the gRPC server +func (c *Component) RegisterHealthServer(srv *grpc.Server) { + c.healthServer = health.NewServer() + healthpb.RegisterHealthServer(srv, c.healthServer) +} diff --git a/core/discovery/announcement/announcement.go b/core/discovery/announcement/announcement.go new file mode 100644 index 000000000..75f5955a0 --- /dev/null +++ b/core/discovery/announcement/announcement.go @@ -0,0 +1,207 @@ +package announcement + +import ( + "encoding" + "fmt" + "reflect" + "strings" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/fatih/structs" +) + +// Metadata represents metadata that is stored with an Announcement +type Metadata interface { + encoding.TextMarshaler + ToProto() *pb.Metadata +} + +// AppEUIMetadata is used to store an AppEUI +type AppEUIMetadata struct { + AppEUI types.AppEUI +} + +// ToProto implements the Metadata interface +func (m AppEUIMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Metadata: &pb.Metadata_AppEui{ + AppEui: m.AppEUI.Bytes(), + }, + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m AppEUIMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("AppEUI %s", m.AppEUI)), nil +} + +// AppIDMetadata is used to store an AppID +type AppIDMetadata struct { + AppID string +} + +// ToProto implements the Metadata interface +func (m AppIDMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Metadata: &pb.Metadata_AppId{ + AppId: m.AppID, + }, + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m AppIDMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("AppID %s", m.AppID)), nil +} + +// PrefixMetadata is used to store a DevAddr prefix +type PrefixMetadata struct { + Prefix types.DevAddrPrefix +} + +// ToProto implements the Metadata interface +func (m PrefixMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Metadata: &pb.Metadata_DevAddrPrefix{ + DevAddrPrefix: m.Prefix.Bytes(), + }, + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m PrefixMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("Prefix %s", m.Prefix)), nil +} + +// MetadataFromProto converts a protocol buffer metadata to a Metadata +func MetadataFromProto(proto *pb.Metadata) Metadata { + if euiBytes := proto.GetAppEui(); euiBytes != nil { + eui := new(types.AppEUI) + if err := eui.Unmarshal(euiBytes); err != nil { + return nil + } + return AppEUIMetadata{*eui} + } + if id := proto.GetAppId(); id != "" { + return AppIDMetadata{id} + } + if prefixBytes := proto.GetDevAddrPrefix(); prefixBytes != nil { + prefix := new(types.DevAddrPrefix) + if err := prefix.Unmarshal(prefixBytes); err != nil { + return nil + } + return PrefixMetadata{*prefix} + } + return nil +} + +// MetadataFromString converts a string to a Metadata +func MetadataFromString(str string) Metadata { + meta := strings.SplitAfterN(str, " ", 2) + key := strings.TrimSpace(meta[0]) + value := meta[1] + switch key { + case "AppEUI": + var appEUI types.AppEUI + appEUI.UnmarshalText([]byte(value)) + return AppEUIMetadata{appEUI} + case "AppID": + return AppIDMetadata{value} + case "Prefix": + prefix := &types.DevAddrPrefix{ + Length: 32, + } + prefix.UnmarshalText([]byte(value)) + return PrefixMetadata{*prefix} + } + return nil +} + +// Announcement of a network component +type Announcement struct { + old *Announcement + ID string `redis:"id"` + ServiceName string `redis:"service_name"` + ServiceVersion string `redis:"service_version"` + Description string `redis:"description"` + URL string `redis:"url"` + Public bool `redis:"public"` + NetAddress string `redis:"net_address"` + PublicKey string `redis:"public_key"` + Certificate string `redis:"certificate"` + APIAddress string `redis:"api_address"` + Metadata []Metadata + + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` +} + +// StartUpdate stores the state of the announcement +func (a *Announcement) StartUpdate() { + old := *a + a.old = &old +} + +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (a Announcement) ChangedFields() (changed []string) { + new := structs.New(a) + fields := new.Names() + if a.old == nil { + return fields + } + old := structs.New(*a.old) + + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue + } + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) + } + } + return +} + +// ToProto converts the Announcement to a protobuf Announcement +func (a Announcement) ToProto() *pb.Announcement { + metadata := make([]*pb.Metadata, 0, len(a.Metadata)) + for _, meta := range a.Metadata { + metadata = append(metadata, meta.ToProto()) + } + return &pb.Announcement{ + Id: a.ID, + ServiceName: a.ServiceName, + ServiceVersion: a.ServiceVersion, + Description: a.Description, + Url: a.URL, + Public: a.Public, + NetAddress: a.NetAddress, + PublicKey: a.PublicKey, + Certificate: a.Certificate, + ApiAddress: a.APIAddress, + Metadata: metadata, + } +} + +// FromProto converts an Announcement protobuf to an Announcement +func FromProto(a *pb.Announcement) Announcement { + metadata := make([]Metadata, 0, len(a.Metadata)) + for _, meta := range a.Metadata { + metadata = append(metadata, MetadataFromProto(meta)) + } + return Announcement{ + ID: a.Id, + ServiceName: a.ServiceName, + ServiceVersion: a.ServiceVersion, + Description: a.Description, + URL: a.Url, + Public: a.Public, + NetAddress: a.NetAddress, + PublicKey: a.PublicKey, + Certificate: a.Certificate, + APIAddress: a.ApiAddress, + Metadata: metadata, + } +} diff --git a/core/discovery/announcement/announcement_test.go b/core/discovery/announcement/announcement_test.go new file mode 100644 index 000000000..9e9b60dad --- /dev/null +++ b/core/discovery/announcement/announcement_test.go @@ -0,0 +1,83 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestMetadataTextMarshaling(t *testing.T) { + a := New(t) + subjects := map[string]Metadata{ + "AppEUI 0102030405060708": AppEUIMetadata{types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})}, + "AppID AppID": AppIDMetadata{"AppID"}, + "Prefix 00000000/0": PrefixMetadata{types.DevAddrPrefix{}}, + } + + for str, obj := range subjects { + marshaled, err := obj.MarshalText() + a.So(err, ShouldBeNil) + a.So(string(marshaled), ShouldEqual, str) + unmarshaled := MetadataFromString(str) + a.So(unmarshaled, ShouldResemble, obj) + } +} + +func TestAnnouncementUpdate(t *testing.T) { + a := New(t) + announcement := &Announcement{ + ID: "ID", + } + announcement.StartUpdate() + a.So(announcement.old.ID, ShouldEqual, announcement.ID) +} + +func TestAnnouncementChangedFields(t *testing.T) { + a := New(t) + announcement := &Announcement{ + ID: "ID", + } + announcement.StartUpdate() + announcement.ID = "ID2" + + a.So(announcement.ChangedFields(), ShouldHaveLength, 1) + a.So(announcement.ChangedFields(), ShouldContain, "ID") +} + +func TestAnnouncementToProto(t *testing.T) { + a := New(t) + announcement := &Announcement{ + ID: "ID", + Metadata: []Metadata{ + AppEUIMetadata{types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})}, + AppIDMetadata{"AppID"}, + PrefixMetadata{types.DevAddrPrefix{}}, + }, + } + proto := announcement.ToProto() + a.So(proto.Id, ShouldEqual, announcement.ID) + a.So(proto.Metadata, ShouldHaveLength, 3) + a.So(proto.Metadata[0].GetAppEui(), ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7, 8}) + a.So(proto.Metadata[1].GetAppId(), ShouldEqual, "AppID") + a.So(proto.Metadata[2].GetDevAddrPrefix(), ShouldResemble, []byte{0, 0, 0, 0, 0}) +} + +func TestAnnouncementFromProto(t *testing.T) { + a := New(t) + proto := &pb.Announcement{ + Id: "ID", + Metadata: []*pb.Metadata{ + &pb.Metadata{Metadata: &pb.Metadata_AppEui{AppEui: []byte{1, 2, 3, 4, 5, 6, 7, 8}}}, + &pb.Metadata{Metadata: &pb.Metadata_AppId{AppId: "AppID"}}, + &pb.Metadata{Metadata: &pb.Metadata_DevAddrPrefix{DevAddrPrefix: []byte{0, 0, 0, 0, 0}}}, + }, + } + announcement := FromProto(proto) + a.So(announcement.ID, ShouldEqual, proto.Id) + a.So(announcement.Metadata, ShouldHaveLength, 3) +} diff --git a/core/discovery/announcement/cache.go b/core/discovery/announcement/cache.go new file mode 100644 index 000000000..711013b29 --- /dev/null +++ b/core/discovery/announcement/cache.go @@ -0,0 +1,144 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "fmt" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/bluele/gcache" +) + +type cachedAnnouncementStore struct { + backingStore Store + serviceCache gcache.Cache + listCache gcache.Cache +} + +// CacheOptions used for the cache +type CacheOptions struct { + ServiceCacheSize int + ServiceCacheExpiration time.Duration + ListCacheSize int + ListCacheExpiration time.Duration +} + +// DefaultCacheOptions are the default CacheOptions +var DefaultCacheOptions = CacheOptions{ + ServiceCacheSize: 1000, // Total number of announcements to cache, thousand should be enough for now + ServiceCacheExpiration: 10 * time.Minute, // Items be updated by ListCache fetch anyway + ListCacheSize: 10, // We actually only need 3: router/broker/handler + ListCacheExpiration: 10 * time.Second, // We can afford to fetch every 10 seconds +} + +func serviceCacheKey(serviceName, serviceID string) string { + return fmt.Sprintf("%s:%s", serviceName, serviceID) +} + +// NewCachedAnnouncementStore returns a cache wrapper around the existing store +func NewCachedAnnouncementStore(store Store, options CacheOptions) Store { + serviceCache := gcache.New(options.ServiceCacheSize).Expiration(options.ServiceCacheExpiration).LFU(). + LoaderFunc(func(k interface{}) (interface{}, error) { + key := strings.Split(k.(string), ":") + return store.Get(key[0], key[1]) + }).Build() + + listCache := gcache.New(options.ListCacheSize).Expiration(options.ListCacheExpiration).LFU(). + LoaderFunc(func(k interface{}) (interface{}, error) { + key := k.(string) + announcements, err := store.ListService(key) + if err != nil { + return nil, err + } + go func(announcements []*Announcement) { + for _, announcement := range announcements { + serviceCache.Set(serviceCacheKey(announcement.ServiceName, announcement.ID), announcement) + } + }(announcements) + return announcements, nil + }).Build() + + return &cachedAnnouncementStore{ + backingStore: store, + serviceCache: serviceCache, + listCache: listCache, + } +} + +func (s *cachedAnnouncementStore) List() ([]*Announcement, error) { + // TODO: We're not using this function. Implement cache when we start using it. + return s.backingStore.List() +} + +func (s *cachedAnnouncementStore) ListService(serviceName string) ([]*Announcement, error) { + l, err := s.listCache.Get(serviceName) + if err != nil { + return nil, err + } + return l.([]*Announcement), nil +} + +func (s *cachedAnnouncementStore) Get(serviceName, serviceID string) (*Announcement, error) { + a, err := s.serviceCache.Get(serviceCacheKey(serviceName, serviceID)) + if err != nil { + return nil, err + } + return a.(*Announcement), nil +} + +func (s *cachedAnnouncementStore) GetMetadata(serviceName, serviceID string) ([]Metadata, error) { + a, err := s.serviceCache.Get(serviceCacheKey(serviceName, serviceID)) + if err != nil { + return nil, err + } + return a.(*Announcement).Metadata, nil +} + +func (s *cachedAnnouncementStore) GetForAppID(appID string) (*Announcement, error) { + // TODO: We're not using this function. Implement cache when we start using it. + return s.backingStore.GetForAppID(appID) +} + +func (s *cachedAnnouncementStore) GetForAppEUI(appEUI types.AppEUI) (*Announcement, error) { + // TODO: We're not using this function. Implement cache when we start using it. + return s.backingStore.GetForAppEUI(appEUI) +} + +func (s *cachedAnnouncementStore) Set(new *Announcement) error { + if err := s.backingStore.Set(new); err != nil { + return err + } + s.serviceCache.Remove(serviceCacheKey(new.ServiceName, new.ID)) + s.listCache.Remove(&new.ServiceName) + return nil +} + +func (s *cachedAnnouncementStore) AddMetadata(serviceName, serviceID string, metadata ...Metadata) error { + if err := s.backingStore.AddMetadata(serviceName, serviceID, metadata...); err != nil { + return err + } + s.serviceCache.Remove(serviceCacheKey(serviceName, serviceID)) + s.listCache.Remove(&serviceName) + return nil +} + +func (s *cachedAnnouncementStore) RemoveMetadata(serviceName, serviceID string, metadata ...Metadata) error { + if err := s.backingStore.RemoveMetadata(serviceName, serviceID, metadata...); err != nil { + return err + } + s.serviceCache.Remove(serviceCacheKey(serviceName, serviceID)) + s.listCache.Remove(&serviceName) + return nil +} + +func (s *cachedAnnouncementStore) Delete(serviceName, serviceID string) error { + if err := s.backingStore.Delete(serviceName, serviceID); err != nil { + return err + } + s.serviceCache.Remove(serviceCacheKey(serviceName, serviceID)) + s.listCache.Remove(&serviceName) + return nil +} diff --git a/core/discovery/announcement/cache_test.go b/core/discovery/announcement/cache_test.go new file mode 100644 index 000000000..0a4558e12 --- /dev/null +++ b/core/discovery/announcement/cache_test.go @@ -0,0 +1,146 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestCachedAnnouncementStore(t *testing.T) { + a := New(t) + + s := NewRedisAnnouncementStore(GetRedisClient(), "discovery-test-announcement-store") + + s = NewCachedAnnouncementStore(s, DefaultCacheOptions) + + // Get non-existing + dev, err := s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&Announcement{ + ServiceName: "router", + ID: "router1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("router", "router1") + }() + + // Get existing + dev, err = s.Get("router", "router1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + // Create extra + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler1") + }() + + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler2", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler2") + }() + + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + + err = s.AddMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + handler, err := s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + AppIDMetadata{AppID: "OtherAppID"}, + ) + a.So(err, ShouldBeNil) + + metadata, err := s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 3) + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + metadata, err = s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 3) + + handler, err = s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + err = s.RemoveMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + err = s.RemoveMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + // List + announcements, err := s.List() + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 3) + + // List + announcements, err = s.ListService("router") + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 1) + + // Delete + err = s.Delete("router", "router1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Delete with Metadata + err = s.Delete("handler", "handler2") + a.So(err, ShouldBeNil) +} diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go new file mode 100644 index 000000000..995bd7f77 --- /dev/null +++ b/core/discovery/announcement/store.go @@ -0,0 +1,265 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "fmt" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v5" +) + +// Store interface for Announcements +type Store interface { + List() ([]*Announcement, error) + ListService(serviceName string) ([]*Announcement, error) + Get(serviceName, serviceID string) (*Announcement, error) + GetMetadata(serviceName, serviceID string) ([]Metadata, error) + GetForAppID(appID string) (*Announcement, error) + GetForAppEUI(appEUI types.AppEUI) (*Announcement, error) + Set(new *Announcement) error + AddMetadata(serviceName, serviceID string, metadata ...Metadata) error + RemoveMetadata(serviceName, serviceID string, metadata ...Metadata) error + Delete(serviceName, serviceID string) error +} + +const defaultRedisPrefix = "discovery" + +const redisAnnouncementPrefix = "announcement" +const redisMetadataPrefix = "metadata" +const redisAppIDPrefix = "app_id" +const redisAppEUIPrefix = "app_eui" + +// NewRedisAnnouncementStore creates a new Redis-based Announcement store +func NewRedisAnnouncementStore(client *redis.Client, prefix string) Store { + if prefix == "" { + prefix = defaultRedisPrefix + } + store := storage.NewRedisMapStore(client, prefix+":"+redisAnnouncementPrefix) + store.SetBase(Announcement{}, "") + return &RedisAnnouncementStore{ + store: store, + metadata: storage.NewRedisSetStore(client, prefix+":"+redisMetadataPrefix), + byAppID: storage.NewRedisKVStore(client, prefix+":"+redisAppIDPrefix), + byAppEUI: storage.NewRedisKVStore(client, prefix+":"+redisAppEUIPrefix), + } +} + +// RedisAnnouncementStore stores Announcements in Redis. +// - Announcements are stored as a Hash +// - Metadata is stored in a Set +// - AppIDs and AppEUIs are indexed with key/value pairs +type RedisAnnouncementStore struct { + store *storage.RedisMapStore + metadata *storage.RedisSetStore + byAppID *storage.RedisKVStore + byAppEUI *storage.RedisKVStore +} + +// List all Announcements +// The resulting Announcements do *not* include metadata +func (s *RedisAnnouncementStore) List() ([]*Announcement, error) { + announcementsI, err := s.store.List("", nil) + if err != nil { + return nil, err + } + announcements := make([]*Announcement, 0, len(announcementsI)) + for _, announcementI := range announcementsI { + if announcement, ok := announcementI.(Announcement); ok { + announcements = append(announcements, &announcement) + } + } + return announcements, nil +} + +// ListService lists all Announcements for a given service (router/broker/handler) +// The resulting Announcements *do* include metadata +func (s *RedisAnnouncementStore) ListService(serviceName string) ([]*Announcement, error) { + announcementsI, err := s.store.List(serviceName+":*", nil) + if err != nil { + return nil, err + } + announcements := make([]*Announcement, 0, len(announcementsI)) + for _, announcementI := range announcementsI { + if announcement, ok := announcementI.(Announcement); ok { + announcements = append(announcements, &announcement) + announcement.Metadata, err = s.GetMetadata(announcement.ServiceName, announcement.ID) + if err != nil { + return nil, err + } + } + } + return announcements, nil +} + +// Get a specific service Announcement +// The result *does* include metadata +func (s *RedisAnnouncementStore) Get(serviceName, serviceID string) (*Announcement, error) { + announcementI, err := s.store.Get(fmt.Sprintf("%s:%s", serviceName, serviceID)) + if err != nil { + return nil, err + } + announcement, ok := announcementI.(Announcement) + if !ok { + return nil, errors.New("Database did not return an Announcement") + } + announcement.Metadata, err = s.GetMetadata(serviceName, serviceID) + if err != nil { + return nil, err + } + return &announcement, nil +} + +// GetMetadata returns the metadata of the specified service +func (s *RedisAnnouncementStore) GetMetadata(serviceName, serviceID string) ([]Metadata, error) { + var out []Metadata + metadata, err := s.metadata.Get(fmt.Sprintf("%s:%s", serviceName, serviceID)) + if errors.GetErrType(err) == errors.NotFound { + return nil, nil + } + if err != nil { + return nil, err + } + for _, meta := range metadata { + if meta := MetadataFromString(meta); meta != nil { + out = append(out, meta) + } + } + return out, nil +} + +// GetForAppID returns the last Announcement that contains metadata for the given AppID +func (s *RedisAnnouncementStore) GetForAppID(appID string) (*Announcement, error) { + key, err := s.byAppID.Get(appID) + if err != nil { + return nil, err + } + service := strings.Split(key, ":") + return s.Get(service[0], service[1]) +} + +// GetForAppEUI returns the last Announcement that contains metadata for the given AppEUI +func (s *RedisAnnouncementStore) GetForAppEUI(appEUI types.AppEUI) (*Announcement, error) { + key, err := s.byAppEUI.Get(appEUI.String()) + if err != nil { + return nil, err + } + service := strings.Split(key, ":") + return s.Get(service[0], service[1]) +} + +// Set a new Announcement or update an existing one +// The metadata of the announcement is ignored, as metadata should be managed with AddMetadata and RemoveMetadata +func (s *RedisAnnouncementStore) Set(new *Announcement) error { + key := fmt.Sprintf("%s:%s", new.ServiceName, new.ID) + now := time.Now() + new.UpdatedAt = now + err := s.store.Update(key, *new) + if errors.GetErrType(err) == errors.NotFound { + new.CreatedAt = now + err = s.store.Create(key, *new) + } + if err != nil { + return err + } + return nil +} + +// AddMetadata adds metadata to the announcement of the specified service +func (s *RedisAnnouncementStore) AddMetadata(serviceName, serviceID string, metadata ...Metadata) error { + key := fmt.Sprintf("%s:%s", serviceName, serviceID) + + metadataStrings := make([]string, 0, len(metadata)) + for _, meta := range metadata { + txt, err := meta.MarshalText() + if err != nil { + return err + } + metadataStrings = append(metadataStrings, string(txt)) + + switch meta := meta.(type) { + case AppIDMetadata: + existing, err := s.byAppID.Get(meta.AppID) + switch { + case errors.GetErrType(err) == errors.NotFound: + if err := s.byAppID.Create(meta.AppID, key); err != nil { + return err + } + case err != nil: + return err + case existing == key: + continue + default: + go s.metadata.Remove(existing, string(txt)) + if err := s.byAppID.Update(meta.AppID, key); err != nil { + return err + } + } + case AppEUIMetadata: + existing, err := s.byAppEUI.Get(meta.AppEUI.String()) + switch { + case errors.GetErrType(err) == errors.NotFound: + if err := s.byAppEUI.Create(meta.AppEUI.String(), key); err != nil { + return err + } + case err != nil: + return err + case existing == key: + continue + default: + go s.metadata.Remove(existing, string(txt)) + if err := s.byAppEUI.Update(meta.AppEUI.String(), key); err != nil { + return err + } + } + } + } + err := s.metadata.Add(key, metadataStrings...) + if err != nil { + return err + } + return nil +} + +// RemoveMetadata removes metadata from the announcement of the specified service +func (s *RedisAnnouncementStore) RemoveMetadata(serviceName, serviceID string, metadata ...Metadata) error { + metadataStrings := make([]string, 0, len(metadata)) + for _, meta := range metadata { + if txt, err := meta.MarshalText(); err == nil { + metadataStrings = append(metadataStrings, string(txt)) + } + switch meta := meta.(type) { + case AppIDMetadata: + s.byAppID.Delete(meta.AppID) + case AppEUIMetadata: + s.byAppEUI.Delete(meta.AppEUI.String()) + } + } + err := s.metadata.Remove(fmt.Sprintf("%s:%s", serviceName, serviceID), metadataStrings...) + if err != nil { + return err + } + return nil +} + +// Delete an Announcement and its metadata +func (s *RedisAnnouncementStore) Delete(serviceName, serviceID string) error { + metadata, err := s.GetMetadata(serviceName, serviceID) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return err + } + if len(metadata) > 0 { + s.RemoveMetadata(serviceName, serviceID, metadata...) + } + key := fmt.Sprintf("%s:%s", serviceName, serviceID) + err = s.store.Delete(key) + if err != nil { + return err + } + return nil +} diff --git a/core/discovery/announcement/store_test.go b/core/discovery/announcement/store_test.go new file mode 100644 index 000000000..2301e450d --- /dev/null +++ b/core/discovery/announcement/store_test.go @@ -0,0 +1,144 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestRedisAnnouncementStore(t *testing.T) { + a := New(t) + + s := NewRedisAnnouncementStore(GetRedisClient(), "discovery-test-announcement-store") + + // Get non-existing + dev, err := s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&Announcement{ + ServiceName: "router", + ID: "router1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("router", "router1") + }() + + // Get existing + dev, err = s.Get("router", "router1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + // Create extra + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler1") + }() + + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler2", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler2") + }() + + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + + err = s.AddMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + handler, err := s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + AppIDMetadata{AppID: "OtherAppID"}, + ) + a.So(err, ShouldBeNil) + + metadata, err := s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 3) + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + metadata, err = s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 3) + + handler, err = s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + err = s.RemoveMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + err = s.RemoveMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + // List + announcements, err := s.List() + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 3) + + // List + announcements, err = s.ListService("router") + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 1) + + // Delete + err = s.Delete("router", "router1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Delete with Metadata + err = s.Delete("handler", "handler2") + a.So(err, ShouldBeNil) +} diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go new file mode 100644 index 000000000..a280f62fa --- /dev/null +++ b/core/discovery/discovery.go @@ -0,0 +1,122 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package discovery implements TTN Service Discovery. +package discovery + +import ( + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/discovery/announcement" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v5" +) + +// Discovery specifies the interface for the TTN Service Discovery component +type Discovery interface { + component.Interface + WithCache(options announcement.CacheOptions) + WithMasterAuthServers(serverID ...string) + Announce(announcement *pb.Announcement) error + GetAll(serviceName string) ([]*pb.Announcement, error) + Get(serviceName string, id string) (*pb.Announcement, error) + AddMetadata(serviceName string, id string, metadata *pb.Metadata) error + DeleteMetadata(serviceName string, id string, metadata *pb.Metadata) error +} + +// discovery is a reference implementation for a TTN Service Discovery component. +type discovery struct { + *component.Component + services announcement.Store + masterAuthServers map[string]struct{} +} + +func (d *discovery) WithCache(options announcement.CacheOptions) { + d.services = announcement.NewCachedAnnouncementStore(d.services, options) +} + +func (d *discovery) WithMasterAuthServers(serverID ...string) { + for _, serverID := range serverID { + d.masterAuthServers[serverID] = struct{}{} + } +} + +func (d *discovery) IsMasterAuthServer(serverID string) bool { + _, ok := d.masterAuthServers[serverID] + return ok +} + +func (d *discovery) Init(c *component.Component) error { + d.Component = c + err := d.Component.UpdateTokenKey() + if err != nil { + return err + } + d.Component.SetStatus(component.StatusHealthy) + return nil +} + +func (d *discovery) Shutdown() {} + +func (d *discovery) Announce(in *pb.Announcement) error { + service, err := d.services.Get(in.ServiceName, in.Id) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return err + } + if service == nil { + service = new(announcement.Announcement) + } + + service.StartUpdate() + + service.ID = in.Id + service.ServiceName = in.ServiceName + service.ServiceVersion = in.ServiceVersion + service.Description = in.Description + service.URL = in.Url + service.Public = in.Public + service.NetAddress = in.NetAddress + service.PublicKey = in.PublicKey + service.Certificate = in.Certificate + service.APIAddress = in.ApiAddress + + return d.services.Set(service) +} + +func (d *discovery) Get(serviceName string, id string) (*pb.Announcement, error) { + service, err := d.services.Get(serviceName, id) + if err != nil { + return nil, err + } + return service.ToProto(), nil +} + +func (d *discovery) GetAll(serviceName string) ([]*pb.Announcement, error) { + services, err := d.services.ListService(serviceName) + if err != nil { + return nil, err + } + serviceCopies := make([]*pb.Announcement, 0, len(services)) + for _, service := range services { + serviceCopies = append(serviceCopies, service.ToProto()) + } + return serviceCopies, nil +} + +func (d *discovery) AddMetadata(serviceName string, id string, in *pb.Metadata) error { + meta := announcement.MetadataFromProto(in) + return d.services.AddMetadata(serviceName, id, meta) +} + +func (d *discovery) DeleteMetadata(serviceName string, id string, in *pb.Metadata) error { + meta := announcement.MetadataFromProto(in) + return d.services.RemoveMetadata(serviceName, id, meta) +} + +// NewRedisDiscovery creates a new Redis-based discovery service +func NewRedisDiscovery(client *redis.Client) Discovery { + return &discovery{ + services: announcement.NewRedisAnnouncementStore(client, "discovery"), + masterAuthServers: make(map[string]struct{}), + } +} diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go new file mode 100644 index 000000000..2f1feb742 --- /dev/null +++ b/core/discovery/discovery_test.go @@ -0,0 +1,173 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package discovery + +import ( + "fmt" + "os" + "testing" + + "gopkg.in/redis.v5" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + . "github.com/smartystreets/assertions" +) + +func getRedisClient(db int) *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: db, + }) +} + +func TestDiscoveryAnnounce(t *testing.T) { + a := New(t) + + client := getRedisClient(1) + d := NewRedisDiscovery(client) + defer func() { + client.Del("discovery:announcement:broker:broker1.1") + client.Del("discovery:announcement:broker:broker1.2") + }() + + broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "current address"} + broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "updated address"} + broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker1.2", NetAddress: "other address"} + + err := d.Announce(broker1a) + a.So(err, ShouldBeNil) + + services, err := d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "current address") + + err = d.Announce(broker1b) + a.So(err, ShouldBeNil) + + services, err = d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "updated address") + + err = d.Announce(broker2) + a.So(err, ShouldBeNil) + + services, err = d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) + +} + +func TestDiscoveryDiscover(t *testing.T) { + a := New(t) + + client := getRedisClient(2) + d := NewRedisDiscovery(client) + defer func() { + client.Del("discovery:announcement:router:router2.0") + client.Del("discovery:announcement:broker:broker2.1") + client.Del("discovery:announcement:broker:broker2.2") + }() + + // This depends on the previous test to pass + d.Announce(&pb.Announcement{ServiceName: "router", Id: "router2.0"}) + d.Announce(&pb.Announcement{ServiceName: "broker", Id: "broker2.1"}) + d.Announce(&pb.Announcement{ServiceName: "broker", Id: "broker2.2"}) + + services, err := d.GetAll("random") + a.So(err, ShouldBeNil) + a.So(services, ShouldBeEmpty) + + services, err = d.GetAll("router") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].Id, ShouldEqual, "router2.0") + + services, err = d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) + +} + +func TestDiscoveryMetadata(t *testing.T) { + a := New(t) + + client := getRedisClient(1) + d := NewRedisDiscovery(client) + defer func() { + client.Del("discovery:announcement:broker:broker3") + client.Del("discovery:announcement:broker:broker4") + client.Del("discovery:app-id:app-id-2") + }() + + broker3 := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ + Metadata: &pb.Metadata_AppId{AppId: "app-id-1"}, + }}} + broker4 := &pb.Announcement{ServiceName: "broker", Id: "broker4", Metadata: []*pb.Metadata{&pb.Metadata{ + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, + }}} + + // Announce should not change metadata + err := d.Announce(broker3) + a.So(err, ShouldBeNil) + service, err := d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + + d.Announce(broker4) + + // AddMetadata should add one + err = d.AddMetadata("broker", "broker3", &pb.Metadata{ + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // And should remove it from the other broker + service, err = d.Get("broker", "broker4") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + + // AddMetadata again should not add one + err = d.AddMetadata("broker", "broker3", &pb.Metadata{ + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, + }) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // DeleteMetadata for non-existing should not delete one + err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ + Metadata: &pb.Metadata_AppId{AppId: "app-id-3"}, + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // Announce should not change metadata + err = d.Announce(broker3) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // DeleteMetadata should delete one + err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + +} diff --git a/core/discovery/server.go b/core/discovery/server.go new file mode 100644 index 000000000..228cefa4d --- /dev/null +++ b/core/discovery/server.go @@ -0,0 +1,165 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package discovery + +import ( + "fmt" + + "github.com/TheThingsNetwork/go-account-lib/rights" + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" +) + +type discoveryServer struct { + discovery *discovery +} + +func errPermissionDeniedf(format string, args ...interface{}) error { + return errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args...)) +} + +func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { + claims, err := d.discovery.ValidateTTNAuthContext(ctx) + if err != nil { + return err + } + + appEUI := in.Metadata.GetAppEui() + appID := in.Metadata.GetAppId() + prefix := in.Metadata.GetDevAddrPrefix() + + if appEUI == nil && appID == "" && prefix == nil { + return errPermissionDeniedf("Unknown Metadata type") + } + + // AppEUI and AppID can only be added to Handlers + if (appEUI != nil || appID != "") && in.ServiceName != "handler" { + return errPermissionDeniedf("Announcement service type should be \"handler\"") + } + + // DevAddrPrefix can only be added to Brokers + if prefix != nil && in.ServiceName != "broker" { + return errPermissionDeniedf("Announcement service type should be \"broker\"") + } + + // DevAddrPrefix and AppEUI are network level changes + if prefix != nil || appEUI != nil { + + // If not in develop mode + if d.discovery.Component.Identity.Id != "dev" { + + // We require a signature from a master auth server + if !d.discovery.IsMasterAuthServer(claims.Issuer) { + return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer) + } + + // TODO: Check if claims allow DevAddrPrefix to be announced + + // AppEUI can not be announced yet + if appEUI != nil { + return errPermissionDeniedf("Can not announce AppEUIs at this time") + } + } + + // Can only be announced to "self" + if claims.Type != in.ServiceName { + return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + } + if claims.Subject != in.Id { + return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.Id) + } + } + + // Check claims for AppID + if appID != "" { + if !claims.AppRight(appID, rights.AppSettings) { + return errPermissionDeniedf("No access to this application") + } + } + return nil +} + +func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*empty.Empty, error) { + claims, err := d.discovery.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + + // If not in development mode + if d.discovery.Component.Identity.Id != "dev" { + if !d.discovery.IsMasterAuthServer(claims.Issuer) { + return nil, errPermissionDeniedf("Token issuer %s is not allowed to make changes to the network settings", claims.Issuer) + } + + // Can't announce development components + if claims.Subject == "dev" { + return nil, errPermissionDeniedf("Can't announce development components to production networks") + } + } + + if claims.Subject != announcement.Id { + return nil, errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.Id) + } + if claims.Type != announcement.ServiceName { + return nil, errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, announcement.ServiceName) + } + announcementCopy := *announcement + announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement + err = d.discovery.Announce(&announcementCopy) + if err != nil { + return nil, err + } + return &empty.Empty{}, nil +} + +func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { + err := d.checkMetadataEditRights(ctx, in) + if err != nil { + return nil, err + } + err = d.discovery.AddMetadata(in.ServiceName, in.Id, in.Metadata) + if err != nil { + return nil, err + } + return &empty.Empty{}, nil +} + +func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { + err := d.checkMetadataEditRights(ctx, in) + if err != nil { + return nil, err + } + err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) + if err != nil { + return nil, err + } + return &empty.Empty{}, nil +} + +func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetServiceRequest) (*pb.AnnouncementsResponse, error) { + services, err := d.discovery.GetAll(req.ServiceName) + if err != nil { + return nil, err + } + return &pb.AnnouncementsResponse{ + Services: services, + }, nil +} + +func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { + service, err := d.discovery.Get(req.ServiceName, req.Id) + if err != nil { + return nil, err + } + return service, nil +} + +// RegisterRPC registers the local discovery with a gRPC server +func (d *discovery) RegisterRPC(s *grpc.Server) { + server := &discoveryServer{d} + pb.RegisterDiscoveryServer(s, server) +} diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go new file mode 100644 index 000000000..757e44051 --- /dev/null +++ b/core/discovery/server_test.go @@ -0,0 +1,79 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package discovery + +import ( + "fmt" + "math/rand" + "net" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" +) + +func randomPort() uint { + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(5000) + 5000 + return uint(port) +} + +func buildTestDiscoveryServer(port uint) (*discovery, *grpc.Server) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + d := &discovery{} + s := grpc.NewServer() + d.RegisterRPC(s) + go s.Serve(lis) + + return d, s +} + +func buildMockDiscoveryServer(port uint) (*mockDiscoveryServer, *grpc.Server) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + d := &mockDiscoveryServer{} + s := grpc.NewServer() + pb.RegisterDiscoveryServer(s, d) + go s.Serve(lis) + return d, s +} + +type mockDiscoveryServer struct { + announce uint + discover uint + get uint +} + +func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*empty.Empty, error) { + d.announce++ + <-time.After(5 * time.Millisecond) + return &empty.Empty{}, nil +} +func (d *mockDiscoveryServer) GetAll(ctx context.Context, req *pb.GetServiceRequest) (*pb.AnnouncementsResponse, error) { + d.discover++ + <-time.After(5 * time.Millisecond) + return &pb.AnnouncementsResponse{ + Services: []*pb.Announcement{}, + }, nil +} +func (d *mockDiscoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { + d.get++ + <-time.After(5 * time.Millisecond) + return &pb.Announcement{}, nil +} +func (d *mockDiscoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { + <-time.After(5 * time.Millisecond) + return &empty.Empty{}, nil +} +func (d *mockDiscoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { + <-time.After(5 * time.Millisecond) + return &empty.Empty{}, nil +} diff --git a/core/handler/activation.go b/core/handler/activation.go new file mode 100644 index 000000000..9076f0265 --- /dev/null +++ b/core/handler/activation.go @@ -0,0 +1,248 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/otaa" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" + "github.com/brocaar/lorawan" +) + +func (h *handler) getActivationMetadata(ctx log.Interface, activation *pb_broker.DeduplicatedDeviceActivationRequest) (types.Metadata, error) { + ttnUp := &pb_broker.DeduplicatedUplinkMessage{ + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + ServerTime: activation.ServerTime, + } + mqttUp := &types.UplinkMessage{} + err := h.ConvertMetadata(ctx, ttnUp, mqttUp) + if err != nil { + return types.Metadata{}, err + } + return mqttUp.Metadata, nil +} + +func (h *handler) HandleActivationChallenge(challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { + + // Find Device + dev, err := h.devices.Get(challenge.AppId, challenge.DevId) + if err != nil { + return nil, err + } + + if dev.AppKey.IsEmpty() { + err = errors.NewErrNotFound(fmt.Sprintf("AppKey for device %s", challenge.DevId)) + return nil, err + } + + // Unmarshal LoRaWAN + var reqPHY lorawan.PHYPayload + if err = reqPHY.UnmarshalBinary(challenge.Payload); err != nil { + return nil, err + } + + // Set MIC + if err := reqPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { + err = errors.NewErrNotFound("Could not set MIC") + return nil, err + } + + // Marshal + bytes, err := reqPHY.MarshalBinary() + if err != nil { + return nil, err + } + + return &pb_broker.ActivationChallengeResponse{ + Payload: bytes, + }, nil +} + +func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { + appID, devID := activation.AppId, activation.DevId + var appEUI types.AppEUI + if activation.AppEui != nil { + appEUI = *activation.AppEui + } + var devEUI types.DevEUI + if activation.DevEui != nil { + devEUI = *activation.DevEui + } + ctx := h.Ctx.WithFields(log.Fields{ + "DevEUI": devEUI, + "AppEUI": appEUI, + "AppID": appID, + "DevID": devID, + }) + start := time.Now() + defer func() { + if err != nil { + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.ActivationErrorEvent, + Data: types.ErrorEventData{Error: err.Error()}, + } + ctx.WithError(err).Warn("Could not handle activation") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") + } + }() + h.status.activations.Mark(1) + + if activation.ResponseTemplate == nil { + err = errors.NewErrInternal("No downlink available") + return nil, err + } + + // Find Device + var dev *device.Device + dev, err = h.devices.Get(appID, devID) + if err != nil { + return nil, err + } + + if dev.AppKey.IsEmpty() { + err = errors.NewErrNotFound(fmt.Sprintf("AppKey for device %s", devID)) + return nil, err + } + + // Check for LoRaWAN + if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { + err = errors.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") + return nil, err + } + + // Unmarshal LoRaWAN + var reqPHY lorawan.PHYPayload + if err = reqPHY.UnmarshalBinary(activation.Payload); err != nil { + return nil, err + } + reqMAC, ok := reqPHY.MACPayload.(*lorawan.JoinRequestPayload) + if !ok { + err = errors.NewErrInvalidArgument("Activation", "does not contain a JoinRequestPayload") + return nil, err + } + + // Validate MIC + if ok, err = reqPHY.ValidateMIC(lorawan.AES128Key(dev.AppKey)); err != nil || !ok { + err = errors.NewErrNotFound("MIC does not match device") + return nil, err + } + + // Validate DevNonce + var alreadyUsed bool + for _, usedNonce := range dev.UsedDevNonces { + if usedNonce == device.DevNonce(reqMAC.DevNonce) { + alreadyUsed = true + break + } + } + if alreadyUsed { + err = errors.NewErrInvalidArgument("Activation DevNonce", "already used") + return nil, err + } + + ctx.Debug("Accepting Join Request") + + // Prepare Device Activation Response + var resPHY lorawan.PHYPayload + if err = resPHY.UnmarshalBinary(activation.ResponseTemplate.Payload); err != nil { + return nil, err + } + resMAC, ok := resPHY.MACPayload.(*lorawan.DataPayload) + if !ok { + err = errors.NewErrInvalidArgument("Activation ResponseTemplate", "MACPayload must be a *DataPayload") + return nil, err + } + joinAccept := &lorawan.JoinAcceptPayload{} + if err = joinAccept.UnmarshalBinary(false, resMAC.Bytes); err != nil { + return nil, err + } + resPHY.MACPayload = joinAccept + + // Publish Activation + mqttMetadata, _ := h.getActivationMetadata(ctx, activation) + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.ActivationEvent, + Data: types.ActivationEventData{ + AppEUI: *activation.AppEui, + DevEUI: *activation.DevEui, + DevAddr: types.DevAddr(joinAccept.DevAddr), + Metadata: mqttMetadata, + }, + } + + // Generate random AppNonce + var appNonce device.AppNonce + for { + // NOTE: As DevNonces are only 2 bytes, we will start rejecting those before we run out of AppNonces. + // It might just take some time to get one we didn't use yet... + alreadyUsed = false + copy(appNonce[:], random.Bytes(3)) + for _, usedNonce := range dev.UsedAppNonces { + if usedNonce == appNonce { + alreadyUsed = true + break + } + } + if !alreadyUsed { + break + } + } + joinAccept.AppNonce = appNonce + + // Calculate session keys + appSKey, nwkSKey, err := otaa.CalculateSessionKeys(dev.AppKey, joinAccept.AppNonce, joinAccept.NetID, reqMAC.DevNonce) + if err != nil { + return nil, err + } + + // Update Device + dev.StartUpdate() + dev.DevAddr = types.DevAddr(joinAccept.DevAddr) + dev.AppSKey = appSKey + dev.NwkSKey = nwkSKey + dev.UsedAppNonces = append(dev.UsedAppNonces, appNonce) + dev.UsedDevNonces = append(dev.UsedDevNonces, reqMAC.DevNonce) + err = h.devices.Set(dev) + if err != nil { + return nil, err + } + + if err = resPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { + return nil, err + } + if err = resPHY.EncryptJoinAcceptPayload(lorawan.AES128Key(dev.AppKey)); err != nil { + return nil, err + } + + var resBytes []byte + resBytes, err = resPHY.MarshalBinary() + if err != nil { + return nil, err + } + + metadata := activation.ActivationMetadata + metadata.GetLorawan().NwkSKey = &dev.NwkSKey + metadata.GetLorawan().DevAddr = &dev.DevAddr + res = &pb.DeviceActivationResponse{ + Payload: resBytes, + DownlinkOption: activation.ResponseTemplate.DownlinkOption, + ActivationMetadata: metadata, + } + + return res, nil +} diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go new file mode 100644 index 000000000..53e465aee --- /dev/null +++ b/core/handler/activation_test.go @@ -0,0 +1,173 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func doTestHandleActivation(h *handler, appEUI types.AppEUI, devEUI types.DevEUI, devNonce [2]byte, appKey types.AppKey) (*pb.DeviceActivationResponse, error) { + devAddr := types.DevAddr{1, 2, 3, 4} + + requestPHY := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinRequest, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinRequestPayload{ + AppEUI: lorawan.EUI64(appEUI), + DevEUI: lorawan.EUI64(devEUI), + DevNonce: devNonce, + }, + } + requestPHY.SetMIC(lorawan.AES128Key(appKey)) + requestBytes, _ := requestPHY.MarshalBinary() + + responsePHY := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinAccept, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinAcceptPayload{}, + } + templateBytes, _ := responsePHY.MarshalBinary() + return h.HandleActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + Payload: requestBytes, + AppEui: &appEUI, + AppId: appEUI.String(), + DevEui: &devEUI, + DevId: devEUI.String(), + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + DevAddr: &devAddr, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{ + Payload: templateBytes, + }, + }) +} + +func TestHandleActivation(t *testing.T) { + a := New(t) + + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestHandleActivation")}, + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), + } + h.InitStatus() + h.mqttEvent = make(chan *types.DeviceEvent, 10) + var wg WaitGroup + + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := appEUI.String() + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + devID := devEUI.String() + unknownDevEUI := types.DevEUI{8, 7, 6, 5, 4, 3, 2, 1} + + appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + + h.applications.Set(&application.Application{ + AppID: appID, + }) + defer func() { + h.applications.Delete(appID) + }() + + h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, + AppEUI: appEUI, + DevEUI: devEUI, + AppKey: appKey, + }) + defer func() { + h.devices.Delete(appID, devID) + }() + + // Unknown + res, err := doTestHandleActivation(h, + appEUI, + unknownDevEUI, + [2]byte{1, 2}, + appKey, + ) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + // Wrong AppKey + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{1, 2}, + [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + ) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + wg.Add(1) + go func() { + <-h.mqttEvent + wg.Done() + }() + + // Known + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{1, 2}, + appKey, + ) + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + + wg.WaitFor(50 * time.Millisecond) + + // Same DevNonce used twice + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{1, 2}, + appKey, + ) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + wg.Add(1) + go func() { + <-h.mqttEvent + wg.Done() + }() + + // Other DevNonce + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{2, 1}, + appKey, + ) + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + + wg.WaitFor(50 * time.Millisecond) + + // TODO: Validate response + + // TODO: Check DB + +} diff --git a/core/handler/amqp.go b/core/handler/amqp.go new file mode 100644 index 000000000..5b5a10308 --- /dev/null +++ b/core/handler/amqp.go @@ -0,0 +1,70 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/apex/log" + + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/amqp" + "github.com/TheThingsNetwork/ttn/core/types" +) + +func (h *handler) HandleAMQP(username, password, host, exchange, downlinkQueue string) error { + h.amqpClient = amqp.NewClient(apex.Wrap(h.Ctx), username, password, host) + + err := h.amqpClient.Connect() + if err != nil { + return err + } + defer func() { + if err != nil { + h.amqpClient.Disconnect() + } + }() + + h.amqpUp = make(chan *types.UplinkMessage) + + subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, downlinkQueue, downlinkQueue != "", downlinkQueue == "") + err = subscriber.Open() + if err != nil { + return err + } + defer func() { + if err != nil { + subscriber.Close() + } + }() + err = subscriber.SubscribeDownlink(func(_ amqp.Subscriber, _, _ string, req types.DownlinkMessage) { + h.EnqueueDownlink(&req) + }) + if err != nil { + return err + } + + ctx := h.Ctx.WithField("Protocol", "AMQP") + + go func() { + publisher := h.amqpClient.NewPublisher(h.amqpExchange) + err := publisher.Open() + if err != nil { + ctx.WithError(err).Error("Could not open publisher channel") + return + } + defer publisher.Close() + + for up := range h.amqpUp { + ctx.WithFields(log.Fields{ + "DevID": up.DevID, + "AppID": up.AppID, + }).Debug("Publish Uplink") + err := publisher.PublishUplink(*up) + if err != nil { + ctx.WithError(err).Warn("Could not publish Uplink") + } + } + }() + + return nil +} diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go new file mode 100644 index 000000000..8ca425ba5 --- /dev/null +++ b/core/handler/amqp_test.go @@ -0,0 +1,87 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "os" + "testing" + "time" + + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/amqp" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleAMQP(t *testing.T) { + host := os.Getenv("AMQP_ADDRESS") + if host == "" { + host = "localhost:5672" + } + + a := New(t) + var wg WaitGroup + c := amqp.NewClient(apex.Wrap(GetLogger(t, "TestHandleAMQP")), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + appID := "handler-amqp-app1" + devID := "handler-amqp-dev1" + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestHandleAMQP")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-amqp"), + } + h.WithAMQP("guest", "guest", host, "amq.topic") + h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, + }) + defer func() { + h.devices.Delete(appID, devID) + }() + err = h.HandleAMQP("guest", "guest", host, "amq.topic", "") + a.So(err, ShouldBeNil) + + p := c.NewPublisher("amq.topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + err = p.PublishDownlink(types.DownlinkMessage{ + AppID: appID, + DevID: devID, + PayloadRaw: []byte{0xAA, 0xBC}, + }) + a.So(err, ShouldBeNil) + <-time.After(50 * time.Millisecond) + dev, _ := h.devices.Get(appID, devID) + a.So(dev.NextDownlink, ShouldNotBeNil) + + wg.Add(1) + s := c.NewSubscriber("amq.topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() + err = s.SubscribeDeviceUplink(appID, devID, func(_ amqp.Subscriber, r_appID string, r_devID string, req types.UplinkMessage) { + a.So(r_appID, ShouldEqual, appID) + a.So(r_devID, ShouldEqual, devID) + a.So(req.PayloadRaw, ShouldResemble, []byte{0xAA, 0xBC}) + wg.Done() + }) + a.So(err, ShouldBeNil) + + h.amqpUp <- &types.UplinkMessage{ + DevID: devID, + AppID: appID, + PayloadRaw: []byte{0xAA, 0xBC}, + PayloadFields: map[string]interface{}{ + "field": "value", + }, + } + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) +} diff --git a/core/handler/application/application.go b/core/handler/application/application.go new file mode 100644 index 000000000..08b6db7c6 --- /dev/null +++ b/core/handler/application/application.go @@ -0,0 +1,58 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package application + +import ( + "reflect" + "time" + + "github.com/fatih/structs" +) + +// Application contains the state of an application +type Application struct { + old *Application + AppID string `redis:"app_id"` + // Decoder is a JavaScript function that accepts the payload as byte array and + // returns an object containing the decoded values + Decoder string `redis:"decoder"` + // Converter is a JavaScript function that accepts the data as decoded by + // Decoder and returns an object containing the converted values + Converter string `redis:"converter"` + // Validator is a JavaScript function that validates the data is converted by + // Converter and returns a boolean value indicating the validity of the data + Validator string `redis:"validator"` + // Encoder is a JavaScript function that encode the data send on Downlink messages + // Returns an object containing the converted values in []byte + Encoder string `redis:"encoder"` + + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` +} + +// StartUpdate stores the state of the device +func (a *Application) StartUpdate() { + old := *a + a.old = &old +} + +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (a Application) ChangedFields() (changed []string) { + new := structs.New(a) + fields := new.Names() + if a.old == nil { + return fields + } + old := structs.New(*a.old) + + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue + } + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) + } + } + return +} diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go new file mode 100644 index 000000000..149857b61 --- /dev/null +++ b/core/handler/application/application_test.go @@ -0,0 +1,31 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package application + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestApplicationUpdate(t *testing.T) { + a := New(t) + application := &Application{ + AppID: "App", + } + application.StartUpdate() + a.So(application.old.AppID, ShouldEqual, application.AppID) +} + +func TestApplicationChangedFields(t *testing.T) { + a := New(t) + application := &Application{ + AppID: "Application", + } + application.StartUpdate() + application.AppID = "NewAppID" + + a.So(application.ChangedFields(), ShouldHaveLength, 1) + a.So(application.ChangedFields(), ShouldContain, "AppID") +} diff --git a/core/handler/application/store.go b/core/handler/application/store.go new file mode 100644 index 000000000..7aadf3e41 --- /dev/null +++ b/core/handler/application/store.go @@ -0,0 +1,92 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package application + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v5" +) + +// Store interface for Applications +type Store interface { + List() ([]*Application, error) + Get(appID string) (*Application, error) + Set(new *Application, properties ...string) (err error) + Delete(appID string) error +} + +const defaultRedisPrefix = "handler" +const redisApplicationPrefix = "application" + +// NewRedisApplicationStore creates a new Redis-based Application store +// if an empty prefix is passed, a default prefix will be used. +func NewRedisApplicationStore(client *redis.Client, prefix string) Store { + if prefix == "" { + prefix = defaultRedisPrefix + } + store := storage.NewRedisMapStore(client, prefix+":"+redisApplicationPrefix) + store.SetBase(Application{}, "") + return &RedisApplicationStore{ + store: store, + } +} + +// RedisApplicationStore stores Applications in Redis. +// - Applications are stored as a Hash +type RedisApplicationStore struct { + store *storage.RedisMapStore +} + +// List all Applications +func (s *RedisApplicationStore) List() ([]*Application, error) { + applicationsI, err := s.store.List("", nil) + if err != nil { + return nil, err + } + applications := make([]*Application, 0, len(applicationsI)) + for _, applicationI := range applicationsI { + if application, ok := applicationI.(Application); ok { + applications = append(applications, &application) + } + } + return applications, nil +} + +// Get a specific Application +func (s *RedisApplicationStore) Get(appID string) (*Application, error) { + applicationI, err := s.store.Get(appID) + if err != nil { + return nil, err + } + if application, ok := applicationI.(Application); ok { + return &application, nil + } + return nil, errors.New("Database did not return a Application") +} + +// Set a new Application or update an existing one +func (s *RedisApplicationStore) Set(new *Application, properties ...string) (err error) { + now := time.Now() + new.UpdatedAt = now + + if new.old != nil { + err = s.store.Update(new.AppID, *new, properties...) + } else { + new.CreatedAt = now + err = s.store.Create(new.AppID, *new, properties...) + } + if err != nil { + return + } + + return nil +} + +// Delete an Application +func (s *RedisApplicationStore) Delete(appID string) error { + return s.store.Delete(appID) +} diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go new file mode 100644 index 000000000..7b3b5170a --- /dev/null +++ b/core/handler/application/store_test.go @@ -0,0 +1,71 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package application + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestApplicationStore(t *testing.T) { + a := New(t) + + NewRedisApplicationStore(GetRedisClient(), "") + + s := NewRedisApplicationStore(GetRedisClient(), "handler-test-application-store") + + appID := "AppID-1" + + // Get non-existing + app, err := s.Get(appID) + a.So(err, ShouldNotBeNil) + a.So(app, ShouldBeNil) + + // Create + app = &Application{ + AppID: appID, + Encoder: "encoder", + } + err = s.Set(app) + defer func() { + s.Delete(appID) + }() + a.So(err, ShouldBeNil) + + // Get existing + app, err = s.Get(appID) + a.So(err, ShouldBeNil) + a.So(app, ShouldNotBeNil) + a.So(app.Encoder, ShouldEqual, "encoder") + + // Update + err = s.Set(&Application{ + old: app, + AppID: appID, + Encoder: "new encoder", + }) + a.So(err, ShouldBeNil) + + // Get existing + app, err = s.Get(appID) + a.So(err, ShouldBeNil) + a.So(app, ShouldNotBeNil) + a.So(app.Encoder, ShouldEqual, "new encoder") + + // List + apps, err := s.List() + a.So(err, ShouldBeNil) + a.So(apps, ShouldHaveLength, 1) + + // Delete + err = s.Delete(appID) + a.So(err, ShouldBeNil) + + // Get deleted + app, err = s.Get(appID) + a.So(err, ShouldNotBeNil) + a.So(app, ShouldBeNil) +} diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go new file mode 100644 index 000000000..5afa9e78c --- /dev/null +++ b/core/handler/convert_fields.go @@ -0,0 +1,314 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "reflect" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/handler/functions" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" +) + +// ConvertFieldsUp converts the payload to fields using payload functions +func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { + // Find Application + app, err := h.applications.Get(ttnUp.AppId) + if err != nil { + return nil // Do not process if application not found + } + + functions := &UplinkFunctions{ + Decoder: app.Decoder, + Converter: app.Converter, + Validator: app.Validator, + Logger: functions.Ignore, + } + + fields, valid, err := functions.Process(appUp.PayloadRaw, appUp.FPort) + if err != nil { + return nil // Do not set fields if processing failed + } + + if !valid { + return errors.NewErrInvalidArgument("Payload", "payload validator function returned false") + } + + appUp.PayloadFields = fields + + return nil +} + +// UplinkFunctions decodes, converts and validates payload using JavaScript functions +type UplinkFunctions struct { + // Decoder is a JavaScript function that accepts the payload as byte array and + // returns an object containing the decoded values + Decoder string + // Converter is a JavaScript function that accepts the data as decoded by + // Decoder and returns an object containing the converted values + Converter string + // Validator is a JavaScript function that validates the data is converted by + // Converter and returns a boolean value indicating the validity of the data + Validator string + + // Logger is the logger that will be used to store logs + Logger functions.Logger +} + +// timeOut is the maximum allowed time a payload function is allowed to run +var timeOut = 100 * time.Millisecond + +// Decode decodes the payload using the Decoder function into a map +func (f *UplinkFunctions) Decode(payload []byte, port uint8) (map[string]interface{}, error) { + if f.Decoder == "" { + return nil, errors.NewErrInternal("Decoder function not set") + } + + env := map[string]interface{}{ + "payload": payload, + "port": port, + } + code := fmt.Sprintf(` + %s; + Decoder(payload.slice(0), port); + `, f.Decoder) + + value, err := functions.RunCode("decoder", code, env, timeOut, f.Logger) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.NewErrInvalidArgument("Decoder", "does not return an object") + } + + v, _ := value.Export() + m, ok := v.(map[string]interface{}) + if !ok { + return nil, errors.NewErrInvalidArgument("Decoder", "does not return an object") + } + return m, nil +} + +// Convert converts the values in the specified map to a another map using the +// Converter function. If the Converter function is not set, this function +// returns the data as-is +func (f *UplinkFunctions) Convert(fields map[string]interface{}, port uint8) (map[string]interface{}, error) { + if f.Converter == "" { + return fields, nil + } + + env := map[string]interface{}{ + "fields": fields, + "port": port, + } + + code := fmt.Sprintf(` + %s; + Converter(fields, port) + `, f.Converter) + + value, err := functions.RunCode("converter", code, env, timeOut, f.Logger) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.NewErrInvalidArgument("Converter", "does not return an object") + } + + v, _ := value.Export() + m, ok := v.(map[string]interface{}) + if !ok { + return nil, errors.NewErrInvalidArgument("Converter", "does not return an object") + } + + return m, nil +} + +// Validate validates the values in the specified map using the Validator +// function. If the Validator function is not set, this function returns true +func (f *UplinkFunctions) Validate(fields map[string]interface{}, port uint8) (bool, error) { + if f.Validator == "" { + return true, nil + } + + env := map[string]interface{}{ + "fields": fields, + "port": port, + } + code := fmt.Sprintf(` + %s; + Validator(fields, port) + `, f.Validator) + + value, err := functions.RunCode("valdator", code, env, timeOut, f.Logger) + if err != nil { + return false, err + } + + if !value.IsBoolean() { + return false, errors.NewErrInvalidArgument("Validator", "does not return a boolean") + } + + return value.ToBoolean() +} + +// Process decodes the specified payload, converts it and test the validity +func (f *UplinkFunctions) Process(payload []byte, port uint8) (map[string]interface{}, bool, error) { + decoded, err := f.Decode(payload, port) + if err != nil { + return nil, false, err + } + + converted, err := f.Convert(decoded, port) + if err != nil { + return nil, false, err + } + + valid, err := f.Validate(converted, port) + return converted, valid, err +} + +// DownlinkFunctions encodes payload using JavaScript functions +type DownlinkFunctions struct { + // Encoder is a JavaScript function that accepts the payload as JSON and + // returns an array of bytes + Encoder string + + // Logger is the logger that will be used to store logs + Logger functions.Logger +} + +// Encode encodes the map into a byte slice using the encoder payload function +// If no encoder function is set, this function returns an array. +func (f *DownlinkFunctions) Encode(payload map[string]interface{}, port uint8) ([]byte, error) { + if f.Encoder == "" { + return nil, errors.NewErrInternal("Encoder function not set") + } + + env := map[string]interface{}{ + "payload": payload, + "port": port, + } + code := fmt.Sprintf(` + %s; + Encoder(payload, port) + `, f.Encoder) + + value, err := functions.RunCode("encoder", code, env, timeOut, f.Logger) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.NewErrInvalidArgument("Encoder", "does not return an object") + } + + v, err := value.Export() + if err != nil { + return nil, err + } + + if reflect.TypeOf(v).Kind() != reflect.Slice { + return nil, errors.NewErrInvalidArgument("Encoder", "does not return an Array") + } + + s := reflect.ValueOf(v) + l := s.Len() + + res := make([]byte, l) + + var n int64 + for i := 0; i < l; i++ { + el := s.Index(i).Interface() + + // type switch does not have fallthrough so we need + // to check every element individually + switch t := el.(type) { + case byte: + n = int64(t) + case int: + n = int64(t) + case int8: + n = int64(t) + case int16: + n = int64(t) + case uint16: + n = int64(t) + case int32: + n = int64(t) + case uint32: + n = int64(t) + case int64: + n = int64(t) + case uint64: + n = int64(t) + case float32: + n = int64(t) + if float32(n) != t { + return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") + } + case float64: + n = int64(t) + if float64(n) != t { + return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") + } + default: + return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") + } + + if n < 0 || n > 255 { + return nil, errors.NewErrInvalidArgument("Encoder Output", "Numbers in Array should be between 0 and 255") + } + + res[i] = byte(n) + } + + return res, nil +} + +// Process encode the specified field, converts it into a valid payload +func (f *DownlinkFunctions) Process(payload map[string]interface{}, port uint8) ([]byte, bool, error) { + encoded, err := f.Encode(payload, port) + if err != nil { + return nil, false, err + } + + return encoded, true, nil +} + +// ConvertFieldsDown converts the fields into a payload +func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { + if appDown.PayloadFields == nil { + return nil + } + + if appDown.PayloadRaw != nil { + return errors.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") + } + + app, err := h.applications.Get(appDown.AppID) + if err != nil { + return nil + } + + functions := &DownlinkFunctions{ + Encoder: app.Encoder, + Logger: functions.Ignore, + } + + message, _, err := functions.Process(appDown.PayloadFields, appDown.FPort) + if err != nil { + return err + } + + appDown.PayloadRaw = message + + return nil +} diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go new file mode 100644 index 000000000..26febac16 --- /dev/null +++ b/core/handler/convert_fields_test.go @@ -0,0 +1,464 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func buildConversionUplink(appID string) (*pb_broker.DeduplicatedUplinkMessage, *types.UplinkMessage) { + ttnUp := &pb_broker.DeduplicatedUplinkMessage{ + AppId: appID, + DevId: "DevID-1", + } + appUp := &types.UplinkMessage{ + FPort: 1, + AppID: appID, + DevID: "DevID-1", + PayloadRaw: []byte{0x08, 0x70}, + } + return ttnUp, appUp +} + +func TestConvertFieldsUp(t *testing.T) { + a := New(t) + appID := "AppID-1" + + h := &handler{ + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-up"), + } + + // No functions + ttnUp, appUp := buildConversionUplink(appID) + err := h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.PayloadFields, ShouldBeEmpty) + + // Normal flow + app := &application.Application{ + AppID: appID, + Decoder: `function Decoder (data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, + } + a.So(h.applications.Set(app), ShouldBeNil) + defer func() { + h.applications.Delete(appID) + }() + ttnUp, appUp = buildConversionUplink(appID) + err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) + a.So(err, ShouldBeNil) + + a.So(appUp.PayloadFields, ShouldResemble, map[string]interface{}{ + "temperature": 21.6, + }) + + // Invalidate data + app.StartUpdate() + app.Validator = `function Validator (data) { return false; }` + h.applications.Set(app) + ttnUp, appUp = buildConversionUplink(appID) + err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) + a.So(err, ShouldNotBeNil) + a.So(appUp.PayloadFields, ShouldBeEmpty) + + // Function error + app.StartUpdate() + app.Validator = `function Validator (data) { throw "expected"; }` + h.applications.Set(app) + ttnUp, appUp = buildConversionUplink(appID) + err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.PayloadFields, ShouldBeEmpty) +} + +func TestDecode(t *testing.T) { + a := New(t) + + functions := &UplinkFunctions{ + Decoder: `function Decoder (payload, port) { + return { + value: (payload[0] << 8) | payload[1], + port: port, + }; +}`, + } + payload := []byte{0x48, 0x65} + + m, err := functions.Decode(payload, 12) + a.So(err, ShouldBeNil) + + size, ok := m["value"] + a.So(ok, ShouldBeTrue) + a.So(size, ShouldEqual, 18533) + + port, ok := m["port"] + a.So(ok, ShouldBeTrue) + a.So(port, ShouldEqual, 12) +} + +func TestConvert(t *testing.T) { + a := New(t) + + withFunction := &UplinkFunctions{ + Converter: `function Converter (data, port) { + return { + celcius: data.temperature * 2 + port: port, + }; +}`, + } + data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}, 33) + a.So(err, ShouldBeNil) + a.So(data["celcius"], ShouldEqual, 22) + a.So(data["port"], ShouldEqual, 33) + + withoutFunction := &UplinkFunctions{} + data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}, 33) + a.So(err, ShouldBeNil) + a.So(data["temperature"], ShouldEqual, 11) +} + +func TestValidate(t *testing.T) { + a := New(t) + + withFunction := &UplinkFunctions{ + Validator: `function Validator (data) { + return data.temperature < 20; + }`, + } + valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}, 1) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) + valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}, 1) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) + + withoutFunction := &UplinkFunctions{} + valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}, 1) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) +} + +func TestProcessUplink(t *testing.T) { + a := New(t) + + functions := &UplinkFunctions{ + Decoder: `function Decoder (payload) { + return { + temperature: payload[0], + humidity: payload[1] + } +}`, + Converter: `function Converter (data) { + data.temperature /= 2; + return data; +}`, + Validator: `function Validator (data) { + return data.humidity >= 0 && data.humidity <= 100; +}`, + } + + data, valid, err := functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) + a.So(data["temperature"], ShouldEqual, 20) + a.So(data["humidity"], ShouldEqual, 110) +} + +func TestProcessInvalidUplinkFunction(t *testing.T) { + a := New(t) + + // Empty Function + functions := &UplinkFunctions{ + Decoder: ``, + } + _, _, err := functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &UplinkFunctions{ + Decoder: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Converter: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Return + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Converter: `function Converter (data) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Validator: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Return + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Validator: `function Validator (data) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Object (Arrays are Objects too, but don't jive well with + // map[string]interface{}) + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return [1] }`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Object (Arrays are Objects too, but don't jive well with + // map[string]interface{}) + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Converter: `function Converter (payload) { return [1] }`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Object (Arrays are Objects too), this should work error because + // we're expecting a Boolean + functions = &UplinkFunctions{ + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Validator: `function Validator (payload) { return [1] }`, + } + _, _, err = functions.Process([]byte{40, 110}, 1) + a.So(err, ShouldNotBeNil) +} + +func TestTimeoutExceeded(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + a := New(t) + start := time.Now() + + functions := &UplinkFunctions{ + Decoder: `function(payload){ while (true) { } }`, + } + + interrupted := make(chan bool, 2) + go func() { + _, _, err := functions.Process([]byte{0}, 1) + a.So(time.Since(start), ShouldAlmostEqual, 100*time.Millisecond, 0.5e9) + a.So(err, ShouldNotBeNil) + interrupted <- true + }() + + <-time.After(200 * time.Millisecond) + a.So(interrupted, ShouldHaveLength, 1) +} + +func TestEncode(t *testing.T) { + a := New(t) + + // This function return an array of bytes (random) + functions := &DownlinkFunctions{ + Encoder: `function Encoder (payload){ + return [ 1, 2, 3, 4, 5, 6, 7 ] + }`, + } + + // The payload is a JSON structure + payload := map[string]interface{}{"temperature": 11} + + m, err := functions.Encode(payload, 1) + a.So(err, ShouldBeNil) + + a.So(m, ShouldHaveLength, 7) + a.So(m, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) + + // Return int type + functions = &DownlinkFunctions{ + Encoder: `function Encoder (payload, port) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldBeNil) +} + +func buildConversionDownlink() (*pb_broker.DownlinkMessage, *types.DownlinkMessage) { + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + ttnDown := &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + } + appDown := &types.DownlinkMessage{ + FPort: 1, + AppID: "AppID-1", + DevID: "DevID-1", + PayloadFields: map[string]interface{}{"temperature": 30}, + // We want to "build" the payload with the content of the fields + } + return ttnDown, appDown +} + +func TestConvertFieldsDown(t *testing.T) { + a := New(t) + appID := "AppID-1" + + h := &handler{ + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-down"), + } + + // Case1: No Encoder + ttnDown, appDown := buildConversionDownlink() + err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(appDown.PayloadRaw, ShouldBeEmpty) + + // Case2: Normal flow with Encoder + h.applications.Set(&application.Application{ + AppID: appID, + // Encoder takes JSON fields as argument and return the payload as []byte + Encoder: `function Encoder (payload, port){ + return [ port, 1, 2, 3, 4, 5, 6, 7 ] + }`, + }) + defer func() { + h.applications.Delete(appID) + }() + + ttnDown, appDown = buildConversionDownlink() + err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(appDown.PayloadRaw, ShouldResemble, []byte{byte(appDown.FPort), 1, 2, 3, 4, 5, 6, 7}) +} + +func TestConvertFieldsDownNoPort(t *testing.T) { + a := New(t) + appID := "AppID-1" + + h := &handler{ + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-down"), + } + + // Case1: No Encoder + ttnDown, appDown := buildConversionDownlink() + err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(appDown.PayloadRaw, ShouldBeEmpty) + + // Case2: Normal flow with Encoder + h.applications.Set(&application.Application{ + AppID: appID, + // Encoder takes JSON fields as argument and return the payload as []byte + Encoder: `function Encoder (payload){ + return [ 1, 2, 3, 4, 5, 6, 7 ] + }`, + }) + defer func() { + h.applications.Delete(appID) + }() + + ttnDown, appDown = buildConversionDownlink() + err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(appDown.PayloadRaw, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) +} + +func TestProcessDownlinkInvalidFunction(t *testing.T) { + a := New(t) + + // Empty Function + functions := &DownlinkFunctions{ + Encoder: ``, + } + _, _, err := functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &DownlinkFunctions{ + Encoder: `this is not valid JavaScript`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function Encoder (payload) { return "Hello" }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function Encoder (payload) { return [ 100, 2256, 7 ] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function Encoder (payload) { return [0, -1, "blablabla"] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function Encoder (payload) { + return { + temperature: payload[0], + humidity: payload[1] + } +} }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) + + functions = &DownlinkFunctions{ + Encoder: `function Encoder (payload) { return [ 1, 1.5 ] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldNotBeNil) +} + +func TestEncodeCharCode(t *testing.T) { + a := New(t) + + // return arr of charcodes + functions := &DownlinkFunctions{ + Encoder: `function Encoder(obj) { + return "Hi".split('').map(function(char) { + return char.charCodeAt(); + }); + }`, + } + val, _, err := functions.Process(map[string]interface{}{"key": 11}, 1) + a.So(err, ShouldBeNil) + + fmt.Println("VALUE", val) +} diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go new file mode 100644 index 000000000..100f31b62 --- /dev/null +++ b/core/handler/convert_lorawan.go @@ -0,0 +1,141 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/apex/log" + "github.com/brocaar/lorawan" +) + +func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { + // Find Device + dev, err := h.devices.Get(ttnUp.AppId, ttnUp.DevId) + if err != nil { + return err + } + + // Check for LoRaWAN + if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil { + return errors.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") + } + + // LoRaWAN: Unmarshal Uplink + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(ttnUp.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + } + macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt + appUp.FCnt = macPayload.FHDR.FCnt + + ctx = ctx.WithField("FCnt", appUp.FCnt) + + // LoRaWAN: Validate MIC + ok, err = phyPayload.ValidateMIC(lorawan.AES128Key(dev.NwkSKey)) + if err != nil { + return err + } + if !ok { + return errors.NewErrNotFound("device that validates MIC") + } + + // LoRaWAN: Decrypt + if macPayload.FPort != nil && *macPayload.FPort != 0 && len(macPayload.FRMPayload) == 1 { + appUp.FPort = *macPayload.FPort + ctx = ctx.WithField("FCnt", appUp.FPort) + if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil { + return errors.NewErrInternal("Could not decrypt payload") + } + if len(macPayload.FRMPayload) == 1 { + payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) + if !ok { + return errors.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload") + } + appUp.PayloadRaw = payload.Bytes + } + } + + // LoRaWAN: Publish ACKs as events + if macPayload.FHDR.FCtrl.ACK { + h.mqttEvent <- &types.DeviceEvent{ + AppID: appUp.AppID, + DevID: appUp.DevID, + Event: types.DownlinkAckEvent, + } + } + + return nil +} + +func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { + // Find Device + dev, err := h.devices.Get(appDown.AppID, appDown.DevID) + if err != nil { + return err + } + + // LoRaWAN: Unmarshal Downlink + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(ttnDown.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") + } + if ttnDown.DownlinkOption != nil && ttnDown.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt + } + + // Abort when downlink not needed + if len(appDown.PayloadRaw) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 { + return ErrNotNeeded + } + + // Set FPort + if appDown.FPort != 0 { + macPayload.FPort = &appDown.FPort + } + + // Set Payload + if len(appDown.PayloadRaw) > 0 { + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.PayloadRaw}} + if macPayload.FPort == nil || *macPayload.FPort == 0 { + macPayload.FPort = pointer.Uint8(1) + } + } else { + macPayload.FRMPayload = []lorawan.Payload{} + } + + // Encrypt + err = phyPayload.EncryptFRMPayload(lorawan.AES128Key(dev.AppSKey)) + if err != nil { + return err + } + + // Set MIC + err = phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) + if err != nil { + return err + } + + // Marshal + phyPayloadBytes, err := phyPayload.MarshalBinary() + if err != nil { + return err + } + + ttnDown.Payload = phyPayloadBytes + + return nil +} diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go new file mode 100644 index 000000000..6b22a79ef --- /dev/null +++ b/core/handler/convert_lorawan_test.go @@ -0,0 +1,97 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, *types.UplinkMessage) { + ttnUp := &pb_broker.DeduplicatedUplinkMessage{ + DevId: "devid", + AppId: "appid", + Payload: payload, + ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ + Lorawan: &pb_lorawan.Metadata{ + FCnt: 1, + }, + }}, + } + appUp := &types.UplinkMessage{} + return ttnUp, appUp +} + +func TestConvertFromLoRaWAN(t *testing.T) { + a := New(t) + h := &handler{ + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-from-lorawan"), + Component: &component.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, + mqttEvent: make(chan *types.DeviceEvent, 10), + } + h.devices.Set(&device.Device{ + DevID: "devid", + AppID: "appid", + }) + defer func() { + h.devices.Delete("appid", "devid") + }() + ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x20, 0x01, 0x00, 0x0A, 0x46, 0x55, 0x96, 0x42, 0x92, 0xF2}) + err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.PayloadRaw, ShouldResemble, []byte{0xaa, 0xbc}) + a.So(appUp.FCnt, ShouldEqual, 1) +} + +func buildLorawanDownlink(payload []byte) (*types.DownlinkMessage, *pb_broker.DownlinkMessage) { + appDown := &types.DownlinkMessage{ + DevID: "devid", + AppID: "appid", + PayloadRaw: []byte{0xaa, 0xbc}, + } + ttnDown := &pb_broker.DownlinkMessage{ + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + DownlinkOption: &pb_broker.DownlinkOption{ + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{ + Lorawan: &pb_lorawan.TxConfiguration{ + FCnt: 1, + }, + }}, + }, + } + return appDown, ttnDown +} + +func TestConvertToLoRaWAN(t *testing.T) { + a := New(t) + h := &handler{ + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-to-lorawan"), + Component: &component.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, + } + h.devices.Set(&device.Device{ + DevID: "devid", + AppID: "appid", + }) + defer func() { + h.devices.Delete("appid", "devid") + }() + appDown, ttnDown := buildLorawanDownlink([]byte{0xaa, 0xbc}) + err := h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0xa1, 0x33, 0x68, 0x0A, 0x08, 0xBD}) + + appDown, ttnDown = buildLorawanDownlink([]byte{0xaa, 0xbc}) + appDown.FPort = 8 + err = h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x08, 0xa1, 0x33, 0x41, 0xA9, 0xFA, 0x03}) +} diff --git a/core/handler/convert_metadata.go b/core/handler/convert_metadata.go new file mode 100644 index 000000000..b9f3a114e --- /dev/null +++ b/core/handler/convert_metadata.go @@ -0,0 +1,54 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// ConvertMetadata converts the protobuf matadata to application metadata +func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { + ctx = ctx.WithField("NumGateways", len(ttnUp.GatewayMetadata)) + + // Transform Metadata + appUp.Metadata.Time = types.BuildTime(ttnUp.ServerTime) + if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan != nil { + appUp.Metadata.Modulation = lorawan.Modulation.String() + appUp.Metadata.DataRate = lorawan.DataRate + appUp.Metadata.Bitrate = lorawan.BitRate + appUp.Metadata.CodingRate = lorawan.CodingRate + } + + // Transform Gateway Metadata + appUp.Metadata.Gateways = make([]types.GatewayMetadata, 0, len(ttnUp.GatewayMetadata)) + for i, in := range ttnUp.GatewayMetadata { + + // Same for all gateways, take first one + if i == 0 { + appUp.Metadata.Frequency = float32(float64(in.Frequency) / 1000000) + } + + gatewayMetadata := types.GatewayMetadata{ + GtwID: in.GatewayId, + Timestamp: in.Timestamp, + Time: types.BuildTime(in.Time), + Channel: in.Channel, + RFChain: in.RfChain, + RSSI: in.Rssi, + SNR: in.Snr, + } + + if gps := in.GetGps(); gps != nil { + gatewayMetadata.Altitude = gps.Altitude + gatewayMetadata.Longitude = gps.Longitude + gatewayMetadata.Latitude = gps.Latitude + } + + appUp.Metadata.Gateways = append(appUp.Metadata.Gateways, gatewayMetadata) + } + + return nil +} diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go new file mode 100644 index 000000000..c066d70e4 --- /dev/null +++ b/core/handler/convert_metadata_test.go @@ -0,0 +1,66 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestConvertMetadata(t *testing.T) { + a := New(t) + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestConvertMetadata")}, + } + + ttnUp := &pb_broker.DeduplicatedUplinkMessage{} + appUp := &types.UplinkMessage{} + + err := h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + + gtwID := "eui-0102030405060708" + ttnUp.GatewayMetadata = []*pb_gateway.RxMetadata{ + &pb_gateway.RxMetadata{ + GatewayId: gtwID, + }, + &pb_gateway.RxMetadata{ + GatewayId: gtwID, + }, + } + + err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Metadata.Gateways, ShouldHaveLength, 2) + + ttnUp.ProtocolMetadata = &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ + Lorawan: &pb_lorawan.Metadata{ + DataRate: "SF7BW125", + }, + }} + + err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Metadata.DataRate, ShouldEqual, "SF7BW125") + + ttnUp.GatewayMetadata[0].Time = 1465831736000000000 + ttnUp.GatewayMetadata[0].Gps = &pb_gateway.GPSMetadata{ + Latitude: 42, + } + + err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Metadata.Gateways[0].Latitude, ShouldEqual, 42) + a.So(time.Time(appUp.Metadata.Gateways[0].Time).UTC(), ShouldResemble, time.Date(2016, 06, 13, 15, 28, 56, 0, time.UTC)) + +} diff --git a/core/handler/device/device.go b/core/handler/device/device.go new file mode 100644 index 000000000..349cf56e8 --- /dev/null +++ b/core/handler/device/device.go @@ -0,0 +1,86 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "reflect" + "time" + + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/fatih/structs" +) + +type DevNonce [2]byte +type AppNonce [3]byte + +// Options for the device +type Options struct { + ActivationConstraints string `json:"activation_constraints,omitempty"` // Activation Constraints (public/local/private) + DisableFCntCheck bool `json:"disable_fcnt_check,omitemtpy"` // Disable Frame counter check (insecure) + Uses32BitFCnt bool `json:"uses_32_bit_fcnt,omitemtpy"` // Use 32-bit Frame counters +} + +// Device contains the state of a device +type Device struct { + old *Device + DevEUI types.DevEUI `redis:"dev_eui"` + AppEUI types.AppEUI `redis:"app_eui"` + AppID string `redis:"app_id"` + DevID string `redis:"dev_id"` + DevAddr types.DevAddr `redis:"dev_addr"` + AppKey types.AppKey `redis:"app_key"` + UsedDevNonces []DevNonce `redis:"used_dev_nonces"` + UsedAppNonces []AppNonce `redis:"used_app_nonces"` + NwkSKey types.NwkSKey `redis:"nwk_s_key"` + AppSKey types.AppSKey `redis:"app_s_key"` + Options Options `redis:"options"` + + NextDownlink *types.DownlinkMessage `redis:"next_downlink"` + + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` +} + +// StartUpdate stores the state of the device +func (d *Device) StartUpdate() { + old := *d + d.old = &old +} + +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (d Device) ChangedFields() (changed []string) { + new := structs.New(d) + fields := new.Names() + if d.old == nil { + return fields + } + old := structs.New(*d.old) + + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue + } + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) + } + } + return +} + +// GetLoRaWAN returns a LoRaWAN Device proto +func (d Device) GetLoRaWAN() *pb_lorawan.Device { + dev := &pb_lorawan.Device{ + AppId: d.AppID, + DevId: d.DevID, + AppEui: &d.AppEUI, + DevEui: &d.DevEUI, + DevAddr: &d.DevAddr, + NwkSKey: &d.NwkSKey, + DisableFCntCheck: d.Options.DisableFCntCheck, + Uses32BitFCnt: d.Options.Uses32BitFCnt, + ActivationConstraints: d.Options.ActivationConstraints, + } + return dev +} diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go new file mode 100644 index 000000000..a8a2c6516 --- /dev/null +++ b/core/handler/device/device_test.go @@ -0,0 +1,38 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDeviceUpdate(t *testing.T) { + a := New(t) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + a.So(device.old.DevID, ShouldEqual, device.DevID) +} + +func TestDeviceChangedFields(t *testing.T) { + a := New(t) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + device.DevID = "NewDevID" + + a.So(device.ChangedFields(), ShouldHaveLength, 1) + a.So(device.ChangedFields(), ShouldContain, "DevID") +} + +func TestDeviceGetLoRaWAN(t *testing.T) { + device := &Device{ + DevID: "Device", + } + device.GetLoRaWAN() +} diff --git a/core/handler/device/store.go b/core/handler/device/store.go new file mode 100644 index 000000000..451cecafd --- /dev/null +++ b/core/handler/device/store.go @@ -0,0 +1,111 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v5" +) + +// Store interface for Devices +type Store interface { + List() ([]*Device, error) + ListForApp(appID string) ([]*Device, error) + Get(appID, devID string) (*Device, error) + Set(new *Device, properties ...string) (err error) + Delete(appID, devID string) error +} + +const defaultRedisPrefix = "handler" +const redisDevicePrefix = "device" + +// NewRedisDeviceStore creates a new Redis-based Device store +func NewRedisDeviceStore(client *redis.Client, prefix string) *RedisDeviceStore { + if prefix == "" { + prefix = defaultRedisPrefix + } + store := storage.NewRedisMapStore(client, prefix+":"+redisDevicePrefix) + store.SetBase(Device{}, "") + return &RedisDeviceStore{ + store: store, + } +} + +// RedisDeviceStore stores Devices in Redis. +// - Devices are stored as a Hash +type RedisDeviceStore struct { + store *storage.RedisMapStore +} + +// List all Devices +func (s *RedisDeviceStore) List() ([]*Device, error) { + devicesI, err := s.store.List("", nil) + if err != nil { + return nil, err + } + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) + } + } + return devices, nil +} + +// ListForApp lists all devices for a specific Application +func (s *RedisDeviceStore) ListForApp(appID string) ([]*Device, error) { + devicesI, err := s.store.List(fmt.Sprintf("%s:*", appID), nil) + if err != nil { + return nil, err + } + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) + } + } + return devices, nil +} + +// Get a specific Device +func (s *RedisDeviceStore) Get(appID, devID string) (*Device, error) { + deviceI, err := s.store.Get(fmt.Sprintf("%s:%s", appID, devID)) + if err != nil { + return nil, err + } + if device, ok := deviceI.(Device); ok { + return &device, nil + } + return nil, errors.New("Database did not return a Device") +} + +// Set a new Device or update an existing one +func (s *RedisDeviceStore) Set(new *Device, properties ...string) (err error) { + + now := time.Now() + new.UpdatedAt = now + + key := fmt.Sprintf("%s:%s", new.AppID, new.DevID) + if new.old != nil { + err = s.store.Update(key, *new, properties...) + } else { + new.CreatedAt = now + err = s.store.Create(key, *new, properties...) + } + if err != nil { + return + } + + return nil +} + +// Delete a Device +func (s *RedisDeviceStore) Delete(appID, devID string) error { + key := fmt.Sprintf("%s:%s", appID, devID) + return s.store.Delete(key) +} diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go new file mode 100644 index 000000000..5b2eede2f --- /dev/null +++ b/core/handler/device/store_test.go @@ -0,0 +1,101 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestDeviceStore(t *testing.T) { + a := New(t) + + NewRedisDeviceStore(GetRedisClient(), "") + + s := NewRedisDeviceStore(GetRedisClient(), "handler-test-device-store") + + // Get non-existing + dev, err := s.Get("AppID-1", "DevID-1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + devs, err := s.ListForApp("AppID-1") + a.So(err, ShouldBeNil) + a.So(devs, ShouldHaveLength, 0) + + // Create + err = s.Set(&Device{ + DevAddr: types.DevAddr([4]byte{0, 0, 0, 1}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppID: "AppID-1", + DevID: "DevID-1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("AppID-1", "DevID-1") + }() + + // Get existing + dev, err = s.Get("AppID-1", "DevID-1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + devs, err = s.ListForApp("AppID-1") + a.So(err, ShouldBeNil) + a.So(devs, ShouldHaveLength, 1) + + // Create extra and update + dev = &Device{ + DevAddr: types.DevAddr([4]byte{0, 0, 0, 2}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppID: "AppID-1", + DevID: "DevID-2", + } + err = s.Set(dev) + a.So(err, ShouldBeNil) + + err = s.Set(&Device{ + old: dev, + DevAddr: types.DevAddr([4]byte{0, 0, 0, 3}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 3}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + AppID: "AppID-1", + DevID: "DevID-2", + }) + a.So(err, ShouldBeNil) + + dev, err = s.Get("AppID-1", "DevID-2") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + a.So(dev.DevEUI, ShouldEqual, types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 3})) + + defer func() { + s.Delete("AppID-1", "DevID-2") + }() + + // List + devices, err := s.List() + a.So(err, ShouldBeNil) + a.So(devices, ShouldHaveLength, 2) + + // Delete + err = s.Delete("AppID-1", "DevID-1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("AppID-1", "DevID-1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + devs, err = s.ListForApp("AppID-1") + a.So(err, ShouldBeNil) + a.So(devs, ShouldHaveLength, 1) + +} diff --git a/core/handler/downlink.go b/core/handler/downlink.go new file mode 100644 index 000000000..9fb310f4c --- /dev/null +++ b/core/handler/downlink.go @@ -0,0 +1,130 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +func (h *handler) EnqueueDownlink(appDownlink *types.DownlinkMessage) (err error) { + appID, devID := appDownlink.AppID, appDownlink.DevID + + ctx := h.Ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + }) + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not enqueue downlink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Debug("Enqueued downlink") + } + }() + + dev, err := h.devices.Get(appID, devID) + if err != nil { + return err + } + + // Clear redundant fields + appDownlink.AppID = "" + appDownlink.DevID = "" + + dev.StartUpdate() + dev.NextDownlink = appDownlink + err = h.devices.Set(dev) + if err != nil { + return err + } + + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.DownlinkScheduledEvent, + } + + return nil +} + +func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *pb_broker.DownlinkMessage) error { + appID, devID := appDownlink.AppID, appDownlink.DevID + + ctx := h.Ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + "AppEUI": downlink.AppEui, + "DevEUI": downlink.DevEui, + }) + + var err error + defer func() { + if err != nil { + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.DownlinkErrorEvent, + Data: types.ErrorEventData{Error: err.Error()}, + } + ctx.WithError(err).Warn("Could not handle downlink") + } + }() + + // Get Processors + processors := []DownlinkProcessor{ + h.ConvertFieldsDown, + h.ConvertToLoRaWAN, + } + + ctx.WithField("NumProcessors", len(processors)).Debug("Running Downlink Processors") + + // Run Processors + for _, processor := range processors { + err = processor(ctx, appDownlink, downlink) + if err == ErrNotNeeded { + err = nil + return nil + } else if err != nil { + return err + } + } + + h.status.downlink.Mark(1) + + ctx.Debug("Send Downlink") + + h.downlink <- downlink + + downlinkConfig := types.DownlinkEventConfigInfo{} + + if downlink.DownlinkOption.ProtocolConfig != nil { + if lorawan := downlink.DownlinkOption.ProtocolConfig.GetLorawan(); lorawan != nil { + downlinkConfig.Modulation = lorawan.Modulation.String() + downlinkConfig.DataRate = lorawan.DataRate + downlinkConfig.BitRate = uint(lorawan.BitRate) + downlinkConfig.FCnt = uint(lorawan.FCnt) + } + } + if gateway := downlink.DownlinkOption.GatewayConfig; gateway != nil { + downlinkConfig.Frequency = uint(downlink.DownlinkOption.GatewayConfig.Frequency) + downlinkConfig.Power = int(downlink.DownlinkOption.GatewayConfig.Power) + } + + h.mqttEvent <- &types.DeviceEvent{ + AppID: appDownlink.AppID, + DevID: appDownlink.DevID, + Event: types.DownlinkSentEvent, + Data: types.DownlinkEventData{ + Payload: downlink.Payload, + GatewayID: downlink.DownlinkOption.GatewayId, + Config: downlinkConfig, + }, + } + + return nil +} diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go new file mode 100644 index 000000000..624accd00 --- /dev/null +++ b/core/handler/downlink_test.go @@ -0,0 +1,162 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestEnqueueDownlink(t *testing.T) { + a := New(t) + appID := "app1" + devID := "dev1" + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-enqueue-downlink"), + mqttEvent: make(chan *types.DeviceEvent, 10), + } + err := h.EnqueueDownlink(&types.DownlinkMessage{ + AppID: appID, + DevID: devID, + }) + a.So(err, ShouldNotBeNil) + h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, + }) + defer func() { + h.devices.Delete(appID, devID) + }() + err = h.EnqueueDownlink(&types.DownlinkMessage{ + AppID: appID, + DevID: devID, + PayloadFields: map[string]interface{}{ + "string": "hello!", + "int": 42, + "bool": true, + }, + }) + a.So(err, ShouldBeNil) + dev, _ := h.devices.Get(appID, devID) + a.So(dev.NextDownlink, ShouldNotBeEmpty) + a.So(dev.NextDownlink.PayloadFields, ShouldHaveLength, 3) +} + +func TestHandleDownlink(t *testing.T) { + a := New(t) + var err error + var wg WaitGroup + appID := "app2" + devID := "dev2" + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-downlink"), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-enqueue-downlink"), + downlink: make(chan *pb_broker.DownlinkMessage), + mqttEvent: make(chan *types.DeviceEvent, 10), + } + h.InitStatus() + // Neither payload nor Fields provided : ERROR + err = h.HandleDownlink(&types.DownlinkMessage{ + AppID: appID, + DevID: devID, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + }) + a.So(err, ShouldNotBeNil) + + h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, + }) + defer func() { + h.devices.Delete(appID, devID) + }() + err = h.HandleDownlink(&types.DownlinkMessage{ + AppID: appID, + DevID: devID, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + }) + a.So(err, ShouldBeNil) + + // Payload provided + wg.Add(1) + go func() { + dl := <-h.downlink + a.So(dl.Payload, ShouldNotBeEmpty) + wg.Done() + }() + err = h.HandleDownlink(&types.DownlinkMessage{ + AppID: appID, + DevID: devID, + PayloadRaw: []byte{0xAA, 0xBC}, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + DownlinkOption: &pb_broker.DownlinkOption{}, + }) + a.So(err, ShouldBeNil) + wg.WaitFor(100 * time.Millisecond) + + // Both Payload and Fields provided + h.applications.Set(&application.Application{ + AppID: appID, + Encoder: `function Encoder (payload){ + return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0] + }`, + }) + defer func() { + h.applications.Delete(appID) + }() + jsonFields := map[string]interface{}{"temperature": 11} + err = h.HandleDownlink(&types.DownlinkMessage{ + FPort: 1, + AppID: appID, + DevID: devID, + PayloadFields: jsonFields, + PayloadRaw: []byte{0xAA, 0xBC}, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + }) + a.So(err, ShouldNotBeNil) + + // JSON Fields provided + wg.Add(1) + go func() { + dl := <-h.downlink + a.So(dl.Payload, ShouldNotBeEmpty) + wg.Done() + }() + err = h.HandleDownlink(&types.DownlinkMessage{ + FPort: 1, + AppID: appID, + DevID: devID, + PayloadFields: jsonFields, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + DownlinkOption: &pb_broker.DownlinkOption{}, + }) + a.So(err, ShouldBeNil) + wg.WaitFor(100 * time.Millisecond) +} diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go new file mode 100644 index 000000000..a1416d3d2 --- /dev/null +++ b/core/handler/dry_run.go @@ -0,0 +1,101 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "encoding/json" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/functions" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" +) + +// DryUplink converts the uplink message payload by running the payload +// functions that are provided in the DryUplinkMessage, without actually going to the network. +// This is helpful for testing the payload functions without having to save them. +func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) (*pb.DryUplinkResult, error) { + app := in.App + + logger := functions.NewEntryLogger() + + flds := "" + valid := true + if app != nil && app.Decoder != "" { + functions := &UplinkFunctions{ + Decoder: app.Decoder, + Converter: app.Converter, + Validator: app.Validator, + Logger: logger, + } + + fields, val, err := functions.Process(in.Payload, uint8(in.Port)) + if err != nil { + return nil, err + } + + valid = val + + marshalled, err := json.Marshal(fields) + if err != nil { + return nil, err + } + + flds = string(marshalled) + } + + return &pb.DryUplinkResult{ + Payload: in.Payload, + Fields: flds, + Valid: valid, + Logs: logger.Logs, + }, nil +} + +// DryDownlink converts the downlink message payload by running the payload +// functions that are provided in the DryDownlinkMessage, without actually going to the network. +// This is helpful for testing the payload functions without having to save them. +func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMessage) (*pb.DryDownlinkResult, error) { + app := in.App + + if in.Payload != nil { + if in.Fields != "" { + return nil, errors.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") + } + return &pb.DryDownlinkResult{ + Payload: in.Payload, + }, nil + } + + if in.Fields == "" { + return nil, errors.NewErrInvalidArgument("Downlink", "Neither Fields nor Payload provided") + } + + if app == nil || app.Encoder == "" { + return nil, errors.NewErrInvalidArgument("Encoder", "Not specified") + } + + logger := functions.NewEntryLogger() + + functions := &DownlinkFunctions{ + Encoder: app.Encoder, + Logger: logger, + } + + var parsed map[string]interface{} + err := json.Unmarshal([]byte(in.Fields), &parsed) + if err != nil { + return nil, errors.NewErrInvalidArgument("Fields", err.Error()) + } + + payload, _, err := functions.Process(parsed, uint8(in.Port)) + if err != nil { + return nil, err + } + + return &pb.DryDownlinkResult{ + Payload: payload, + Logs: logger.Logs, + }, nil +} diff --git a/core/handler/dry_run_test.go b/core/handler/dry_run_test.go new file mode 100644 index 000000000..ce3e8abf8 --- /dev/null +++ b/core/handler/dry_run_test.go @@ -0,0 +1,261 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/application" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" +) + +type countingStore struct { + store application.Store + counts map[string]int +} + +func newCountingStore(store application.Store) *countingStore { + return &countingStore{ + store: store, + } +} + +func (s *countingStore) inc(name string) { + val, ok := s.counts[name] + if !ok { + val = 0 + } + s.counts[name] = val + 1 +} + +func (s *countingStore) Count(name string) int { + val, ok := s.counts[name] + if !ok { + val = 0 + } + return val +} + +func (s *countingStore) List() ([]*application.Application, error) { + s.inc("list") + return s.store.List() +} + +func (s *countingStore) Get(appID string) (*application.Application, error) { + s.inc("get") + return s.store.Get(appID) +} + +func (s *countingStore) Set(app *application.Application, fields ...string) error { + s.inc("set") + return s.store.Set(app, fields...) +} + +func (s *countingStore) Delete(appID string) error { + s.inc("delete") + return s.store.Delete(appID) +} + +func TestDryUplinkFields(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + dryUplinkMessage := &pb.DryUplinkMessage{ + Payload: []byte{11, 22, 33}, + App: &pb.Application{ + AppId: "DryUplinkFields", + Decoder: `function Decoder (bytes) { + console.log("hi", 11) + return { length: bytes.length }}`, + Converter: `function Converter (obj) { + console.log("foo") + return obj + }`, + Validator: `function Validator (bytes) { return true; }`, + }, + } + + res, err := m.DryUplink(context.TODO(), dryUplinkMessage) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) + a.So(res.Fields, ShouldEqual, `{"length":3}`) + a.So(res.Valid, ShouldBeTrue) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ + &pb.LogEntry{ + Function: "decoder", + Fields: []string{`"hi"`, "11"}, + }, + &pb.LogEntry{ + Function: "converter", + Fields: []string{`"foo"`}, + }, + }) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryUplinkEmptyApp(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + dryUplinkMessage := &pb.DryUplinkMessage{ + Payload: []byte{11, 22, 33}, + } + + res, err := m.DryUplink(context.TODO(), dryUplinkMessage) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) + a.So(res.Fields, ShouldEqual, "") + a.So(res.Valid, ShouldBeTrue) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryDownlinkFields(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Fields: `{ "foo": [ 1, 2, 3 ] }`, + App: &pb.Application{ + Encoder: ` + function Encoder (fields) { + console.log("hello", { foo: 33 }) + return fields.foo + }`, + }, + } + + res, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, []byte{1, 2, 3}) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ + &pb.LogEntry{ + Function: "encoder", + Fields: []string{`"hello"`, `{"foo":33}`}, + }, + }) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryDownlinkPayload(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Payload: []byte{0x1, 0x2, 0x3}, + App: &pb.Application{ + Encoder: `function (fields) { return fields.foo }`, + }, + } + + res, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, []byte{0x1, 0x2, 0x3}) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry(nil)) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryDownlinkEmptyApp(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Fields: `{ "foo": [ 1, 2, 3 ] }`, + } + + _, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldNotBeNil) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestLogs(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Fields: `{ "foo": [ 1, 2, 3 ] }`, + App: &pb.Application{ + Encoder: ` + function Encoder (fields) { + console.log("foo", 1, "bar", new Date(0)) + console.log(1, { baz: 10, baa: "foo", bal: { "bar": 10 }}) + return fields.foo + }`, + }, + } + + res, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldBeNil) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ + &pb.LogEntry{ + Function: "encoder", + Fields: []string{`"foo"`, "1", `"bar"`, `"1970-01-01T00:00:00.000Z"`}, + }, + &pb.LogEntry{ + Function: "encoder", + Fields: []string{"1", `{"baa":"foo","bal":{"bar":10},"baz":10}`}, + }, + }) +} diff --git a/core/handler/functions/functions.go b/core/handler/functions/functions.go new file mode 100644 index 000000000..970217093 --- /dev/null +++ b/core/handler/functions/functions.go @@ -0,0 +1,65 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package functions + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/robertkrimen/otto" +) + +var errTimeOutExceeded = errors.NewErrInternal("Code has been running to long") + +func RunCode(name, code string, env map[string]interface{}, timeout time.Duration, logger Logger) (otto.Value, error) { + vm := otto.New() + + // load the environment + for key, val := range env { + vm.Set(key, val) + } + + if logger == nil { + logger = Ignore + } + logger.Enter(name) + + vm.Set("__log", func(call otto.FunctionCall) otto.Value { + logger.Log(call) + return otto.UndefinedValue() + }) + vm.Run("console.log = __log") + + var value otto.Value + var err error + + start := time.Now() + + defer func() { + duration := time.Since(start) + if caught := recover(); caught != nil { + if caught == errTimeOutExceeded { + value = otto.Value{} + err = errors.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) + return + } + // if this is not the our timeout interrupt, raise the panic again + // so someone else can handle it + panic(caught) + } + }() + + vm.Interrupt = make(chan func(), 1) + + go func() { + time.Sleep(timeout) + vm.Interrupt <- func() { + panic(errTimeOutExceeded) + } + }() + val, err := vm.Run(code) + + return val, err +} diff --git a/core/handler/functions/functions_test.go b/core/handler/functions/functions_test.go new file mode 100644 index 000000000..bfb2c5e75 --- /dev/null +++ b/core/handler/functions/functions_test.go @@ -0,0 +1,74 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package functions + +import ( + "testing" + "time" + + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/robertkrimen/otto" + + . "github.com/smartystreets/assertions" +) + +func TestRunCode(t *testing.T) { + a := New(t) + + logger := NewEntryLogger() + foo := 10 + env := map[string]interface{}{ + "foo": foo, + "bar": "baz", + } + + code := ` + (function (foo, bar) { + console.log("hello", foo, bar) + return foo + })(foo,bar) + ` + + val, err := RunCode("test", code, env, time.Second, logger) + a.So(err, ShouldBeNil) + e, _ := val.Export() + a.So(e, ShouldEqual, foo) + a.So(logger.Logs, ShouldResemble, []*pb_handler.LogEntry{ + &pb_handler.LogEntry{ + Function: "test", + Fields: []string{`"hello"`, "10", `"baz"`}, + }, + }) +} + +var result string + +func BenchmarkJSON(b *testing.B) { + v, _ := otto.ToValue("foo") + var r string + for n := 0; n < b.N; n++ { + r = JSON(v) + } + result = r +} + +func TestRunInvalidCode(t *testing.T) { + a := New(t) + + logger := NewEntryLogger() + foo := 10 + env := map[string]interface{}{ + "foo": foo, + "bar": "baz", + } + + code := ` + (function (foo, bar) { + derp + })(foo,bar) + ` + + _, err := RunCode("test", code, env, time.Second, logger) + a.So(err, ShouldNotBeNil) +} diff --git a/core/handler/functions/logger.go b/core/handler/functions/logger.go new file mode 100644 index 000000000..329fb1ea0 --- /dev/null +++ b/core/handler/functions/logger.go @@ -0,0 +1,64 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package functions + +import ( + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/robertkrimen/otto" +) + +// Logger is something that can be logged to, saving the logs for later use +type Logger interface { + // Log passes the console.log function call to the logger + Log(call otto.FunctionCall) + + // Enter tells the Logger what function it is currently in + Enter(function string) +} + +// console is a Logger that saves the logs as LogEntries +type EntryLogger struct { + Logs []*pb_handler.LogEntry + function string +} + +// Console returns a new Logger that save the logs to +func NewEntryLogger() *EntryLogger { + return &EntryLogger{ + Logs: make([]*pb_handler.LogEntry, 0), + } +} + +// JSON stringifies a value inside of the otto vm, yielding better +// results than Export for Object-like class such as Date, but being much +// slower. +func JSON(val otto.Value) string { + vm := otto.New() + vm.Set("value", val) + res, _ := vm.Run(`JSON.stringify(value)`) + return res.String() +} + +func (c *EntryLogger) Log(call otto.FunctionCall) { + fields := []string{} + for _, field := range call.ArgumentList { + fields = append(fields, JSON(field)) + } + + c.Logs = append(c.Logs, &pb_handler.LogEntry{ + Function: c.function, + Fields: fields, + }) +} + +func (c *EntryLogger) Enter(function string) { + c.function = function +} + +type IgnoreLogger struct{} + +var Ignore = &IgnoreLogger{} + +func (c *IgnoreLogger) Log(call otto.FunctionCall) {} +func (c *IgnoreLogger) Enter(function string) {} diff --git a/core/handler/handler.go b/core/handler/handler.go new file mode 100644 index 000000000..9948ace7c --- /dev/null +++ b/core/handler/handler.go @@ -0,0 +1,184 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/amqp" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "golang.org/x/net/context" + "google.golang.org/grpc" + "gopkg.in/redis.v5" +) + +// Handler component +type Handler interface { + component.Interface + component.ManagementInterface + + WithMQTT(username, password string, brokers ...string) Handler + WithAMQP(username, password, host, exchange string) Handler + + HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error + HandleActivationChallenge(challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) + HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) + EnqueueDownlink(appDownlink *types.DownlinkMessage) error +} + +// NewRedisHandler creates a new Redis-backed Handler +func NewRedisHandler(client *redis.Client, ttnBrokerID string) Handler { + return &handler{ + devices: device.NewRedisDeviceStore(client, "handler"), + applications: application.NewRedisApplicationStore(client, "handler"), + ttnBrokerID: ttnBrokerID, + } +} + +type handler struct { + *component.Component + + devices device.Store + applications application.Store + + ttnBrokerID string + ttnBrokerConn *grpc.ClientConn + ttnBroker pb_broker.BrokerClient + ttnBrokerManager pb_broker.BrokerManagerClient + + downlink chan *pb_broker.DownlinkMessage + + mqttClient mqtt.Client + mqttUsername string + mqttPassword string + mqttBrokers []string + mqttEnabled bool + mqttUp chan *types.UplinkMessage + mqttEvent chan *types.DeviceEvent + + amqpClient amqp.Client + amqpUsername string + amqpPassword string + amqpHost string + amqpExchange string + amqpEnabled bool + amqpUp chan *types.UplinkMessage + + status *status +} + +var ( + // AMQPDownlinkQueue is the AMQP queue to use for downlink + AMQPDownlinkQueue = "ttn-handler-downlink" +) + +func (h *handler) WithMQTT(username, password string, brokers ...string) Handler { + h.mqttUsername = username + h.mqttPassword = password + h.mqttBrokers = brokers + h.mqttEnabled = true + return h +} + +func (h *handler) WithAMQP(username, password, host, exchange string) Handler { + h.amqpUsername = username + h.amqpPassword = password + h.amqpHost = host + h.amqpExchange = exchange + h.amqpEnabled = true + return h +} + +func (h *handler) Init(c *component.Component) error { + h.Component = c + h.InitStatus() + err := h.Component.UpdateTokenKey() + if err != nil { + return err + } + + err = h.Announce() + if err != nil { + return err + } + + if h.mqttEnabled { + var brokers []string + for _, broker := range h.mqttBrokers { + brokers = append(brokers, fmt.Sprintf("tcp://%s", broker)) + } + err = h.HandleMQTT(h.mqttUsername, h.mqttPassword, brokers...) + if err != nil { + return err + } + } + + if h.amqpEnabled { + err = h.HandleAMQP(h.amqpUsername, h.amqpPassword, h.amqpHost, h.amqpExchange, AMQPDownlinkQueue) + if err != nil { + return err + } + } + + err = h.associateBroker() + if err != nil { + return err + } + + h.Component.SetStatus(component.StatusHealthy) + + return nil +} + +func (h *handler) Shutdown() { + if h.mqttEnabled { + h.mqttClient.Disconnect() + } + if h.amqpEnabled { + h.amqpClient.Disconnect() + } +} + +func (h *handler) associateBroker() error { + broker, err := h.Discover("broker", h.ttnBrokerID) + if err != nil { + return err + } + conn, err := broker.Dial() + if err != nil { + return err + } + h.ttnBrokerConn = conn + h.ttnBroker = pb_broker.NewBrokerClient(conn) + h.ttnBrokerManager = pb_broker.NewBrokerManagerClient(conn) + + h.downlink = make(chan *pb_broker.DownlinkMessage) + + contextFunc := func() context.Context { return h.GetContext("") } + + upStream := pb_broker.NewMonitoredHandlerSubscribeStream(h.ttnBroker, contextFunc) + downStream := pb_broker.NewMonitoredHandlerPublishStream(h.ttnBroker, contextFunc) + + go func() { + for message := range upStream.Channel() { + go h.HandleUplink(message) + } + }() + + go func() { + for message := range h.downlink { + if err := downStream.Send(message); err != nil { + h.Ctx.WithError(err).Warn("Could not send downlink to Broker") + } + } + }() + + return nil +} diff --git a/core/handler/handler_test.go b/core/handler/handler_test.go new file mode 100644 index 000000000..0339071bb --- /dev/null +++ b/core/handler/handler_test.go @@ -0,0 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +// TODO diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go new file mode 100644 index 000000000..b2287f4d3 --- /dev/null +++ b/core/handler/manager_server.go @@ -0,0 +1,499 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/rights" + "github.com/TheThingsNetwork/ttn/api" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/ratelimit" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +type handlerManager struct { + handler *handler + deviceManager pb_lorawan.DeviceManagerClient + devAddrManager pb_lorawan.DevAddrManagerClient + applicationRate *ratelimit.Registry + clientRate *ratelimit.Registry +} + +func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID string) (context.Context, *claims.Claims, error) { + md, err := api.MetadataFromContext(ctx) + if err != nil { + return ctx, nil, err + } + // If token is empty, try to get the access key and convert it into a token + token, err := api.TokenFromMetadata(md) + if err != nil || token == "" { + key, err := api.KeyFromMetadata(md) + if err != nil { + return ctx, nil, errors.NewErrInvalidArgument("Metadata", "neither token nor key present") + } + token, err := h.handler.Component.ExchangeAppKeyForToken(appID, key) + if err != nil { + return ctx, nil, err + } + md = metadata.Join(md, metadata.Pairs("token", token)) + ctx = metadata.NewContext(ctx, md) + } + claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) + if err != nil { + return ctx, nil, err + } + if h.clientRate.Limit(claims.Subject) { + return ctx, claims, grpc.Errorf(codes.ResourceExhausted, "Rate limit for client reached") + } + if h.applicationRate.Limit(appID) { + return ctx, claims, grpc.Errorf(codes.ResourceExhausted, "Rate limit for application reached") + } + return ctx, claims, nil +} + +func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Device Identifier") + } + + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) + } + + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + + dev, err := h.handler.devices.Get(in.AppId, in.DevId) + if err != nil { + return nil, err + } + + pbDev := &pb.Device{ + AppId: dev.AppID, + DevId: dev.DevID, + Device: &pb.Device_LorawanDevice{LorawanDevice: &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: &dev.AppEUI, + DevId: dev.DevID, + DevEui: &dev.DevEUI, + DevAddr: &dev.DevAddr, + NwkSKey: &dev.NwkSKey, + AppSKey: &dev.AppSKey, + AppKey: &dev.AppKey, + DisableFCntCheck: dev.Options.DisableFCntCheck, + Uses32BitFCnt: dev.Options.Uses32BitFCnt, + ActivationConstraints: dev.Options.ActivationConstraints, + }}, + } + + nsDev, err := h.deviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ + AppEui: &dev.AppEUI, + DevEui: &dev.DevEUI, + }) + if errors.GetErrType(errors.FromGRPCError(err)) == errors.NotFound { + // Re-register the device in the Broker (NetworkServer) + h.handler.Ctx.WithFields(log.Fields{ + "AppID": dev.AppID, + "DevID": dev.DevID, + "AppEUI": dev.AppEUI, + "DevEUI": dev.DevEUI, + }).Warn("Re-registering missing device to Broker") + nsDev = dev.GetLoRaWAN() + _, err = h.deviceManager.SetDevice(ctx, nsDev) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not re-register missing device to Broker") + } + } else if err != nil { + return pbDev, errors.Wrap(errors.FromGRPCError(err), "Broker did not return device") + } + + pbDev.GetLorawanDevice().FCntUp = nsDev.FCntUp + pbDev.GetLorawanDevice().FCntDown = nsDev.FCntDown + pbDev.GetLorawanDevice().LastSeen = nsDev.LastSeen + + return pbDev, nil +} + +func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Device") + } + + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) + } + + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + + dev, err := h.handler.devices.Get(in.AppId, in.DevId) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, err + } + + lorawan := in.GetLorawanDevice() + if lorawan == nil { + return nil, errors.NewErrInvalidArgument("Device", "No LoRaWAN Device") + } + + if dev != nil { // When this is an update + if dev.AppEUI != *lorawan.AppEui || dev.DevEUI != *lorawan.DevEui { + // If the AppEUI or DevEUI is changed, we should remove the device from the NetworkServer and re-add it later + _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{ + AppEui: &dev.AppEUI, + DevEui: &dev.DevEUI, + }) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") + } + } + dev.StartUpdate() + } else { // When this is a create + existingDevices, err := h.handler.devices.ListForApp(in.AppId) + if err != nil { + return nil, err + } + for _, existingDevice := range existingDevices { + if existingDevice.AppEUI == *lorawan.AppEui && existingDevice.DevEUI == *lorawan.DevEui { + return nil, errors.NewErrAlreadyExists("Device with AppEUI and DevEUI") + } + } + dev = new(device.Device) + } + + dev.AppID = in.AppId + dev.AppEUI = *lorawan.AppEui + dev.DevID = in.DevId + dev.DevEUI = *lorawan.DevEui + + dev.Options = device.Options{ + DisableFCntCheck: lorawan.DisableFCntCheck, + Uses32BitFCnt: lorawan.Uses32BitFCnt, + ActivationConstraints: lorawan.ActivationConstraints, + } + if dev.Options.ActivationConstraints == "" { + dev.Options.ActivationConstraints = "local" + } + + if lorawan.DevAddr != nil { + dev.DevAddr = *lorawan.DevAddr + } + if lorawan.NwkSKey != nil { + dev.NwkSKey = *lorawan.NwkSKey + } + if lorawan.AppSKey != nil { + dev.AppSKey = *lorawan.AppSKey + } + + if lorawan.AppKey != nil { + if dev.AppKey != *lorawan.AppKey { // When the AppKey of an existing device is changed + dev.UsedAppNonces = []device.AppNonce{} + dev.UsedDevNonces = []device.DevNonce{} + } + dev.AppKey = *lorawan.AppKey + } + + // Update the device in the Broker (NetworkServer) + nsUpdated := dev.GetLoRaWAN() + nsUpdated.FCntUp = lorawan.FCntUp + nsUpdated.FCntDown = lorawan.FCntDown + + _, err = h.deviceManager.SetDevice(ctx, nsUpdated) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not set device") + } + + err = h.handler.devices.Set(dev) + if err != nil { + return nil, err + } + + return &empty.Empty{}, nil +} + +func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Device Identifier") + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) + } + + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + + dev, err := h.handler.devices.Get(in.AppId, in.DevId) + if err != nil { + return nil, err + } + _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + if err != nil && errors.GetErrType(errors.FromGRPCError(err)) != errors.NotFound { + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") + } + err = h.handler.devices.Delete(in.AppId, in.DevId) + if err != nil { + return nil, err + } + return &empty.Empty{}, nil +} + +func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.DeviceList, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Application Identifier") + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) + } + + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + + devices, err := h.handler.devices.ListForApp(in.AppId) + if err != nil { + return nil, err + } + res := &pb.DeviceList{Devices: []*pb.Device{}} + for _, dev := range devices { + res.Devices = append(res.Devices, &pb.Device{ + AppId: dev.AppID, + DevId: dev.DevID, + Device: &pb.Device_LorawanDevice{LorawanDevice: &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: &dev.AppEUI, + DevId: dev.DevID, + DevEui: &dev.DevEUI, + DevAddr: &dev.DevAddr, + NwkSKey: &dev.NwkSKey, + AppSKey: &dev.AppSKey, + AppKey: &dev.AppKey, + }}, + }) + } + return res, nil +} + +func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { + if err := in.Validate(); err != nil { + return nil, errors.NewErrInvalidArgument("Application Identifier", err.Error()) + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) + } + app, err := h.handler.applications.Get(in.AppId) + if err != nil { + return nil, err + } + + return &pb.Application{ + AppId: app.AppID, + Decoder: app.Decoder, + Converter: app.Converter, + Validator: app.Validator, + Encoder: app.Encoder, + }, nil +} + +func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Application Identifier") + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) + } + app, err := h.handler.applications.Get(in.AppId) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, err + } + if app != nil { + return nil, errors.NewErrAlreadyExists("Application") + } + + err = h.handler.applications.Set(&application.Application{ + AppID: in.AppId, + }) + if err != nil { + return nil, err + } + + token, _ := api.TokenFromContext(ctx) + err = h.handler.Discovery.AddAppID(in.AppId, token) + if err != nil { + h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Discovery") + } + + _, err = h.handler.ttnBrokerManager.RegisterApplicationHandler(ctx, &pb_broker.ApplicationHandlerRegistration{ + AppId: in.AppId, + HandlerId: h.handler.Identity.Id, + }) + if err != nil { + h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") + } + + return &empty.Empty{}, nil + +} + +func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Application") + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) + } + app, err := h.handler.applications.Get(in.AppId) + if err != nil { + return nil, err + } + + app.StartUpdate() + + app.Decoder = in.Decoder + app.Converter = in.Converter + app.Validator = in.Validator + app.Encoder = in.Encoder + + err = h.handler.applications.Set(app) + if err != nil { + return nil, err + } + + return &empty.Empty{}, nil +} + +func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Application Identifier") + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) + } + _, err = h.handler.applications.Get(in.AppId) + if err != nil { + return nil, err + } + + // Get and delete all devices for this application + devices, err := h.handler.devices.ListForApp(in.AppId) + if err != nil { + return nil, err + } + for _, dev := range devices { + _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") + } + err = h.handler.devices.Delete(dev.AppID, dev.DevID) + if err != nil { + return nil, err + } + } + + // Delete the Application + err = h.handler.applications.Delete(in.AppId) + if err != nil { + return nil, err + } + + token, _ := api.TokenFromContext(ctx) + err = h.handler.Discovery.RemoveAppID(in.AppId, token) + if err != nil { + h.handler.Ctx.WithField("AppID", in.AppId).WithError(errors.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") + } + + return &empty.Empty{}, nil +} + +func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { + res, err := h.devAddrManager.GetPrefixes(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not return prefixes") + } + return res, nil +} + +func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { + res, err := h.devAddrManager.GetDevAddr(ctx, in) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not return DevAddr") + } + return res, nil +} + +func (h *handlerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + if h.handler.Identity.Id != "dev" { + claims, err := h.handler.ValidateTTNAuthContext(ctx) + if err != nil || !claims.ComponentAccess(h.handler.Identity.Id) { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := h.handler.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil +} + +func (h *handler) RegisterManager(s *grpc.Server) { + server := &handlerManager{ + handler: h, + deviceManager: pb_lorawan.NewDeviceManagerClient(h.ttnBrokerConn), + devAddrManager: pb_lorawan.NewDevAddrManagerClient(h.ttnBrokerConn), + } + + server.applicationRate = ratelimit.NewRegistry(5000, time.Hour) + server.clientRate = ratelimit.NewRegistry(5000, time.Hour) + + pb.RegisterHandlerManagerServer(s, server) + pb.RegisterApplicationManagerServer(s, server) + pb_lorawan.RegisterDevAddrManagerServer(s, server) +} diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go new file mode 100644 index 000000000..d5281f1a3 --- /dev/null +++ b/core/handler/mqtt.go @@ -0,0 +1,102 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "time" + + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +// MQTTTimeout indicates how long we should wait for an MQTT publish +var MQTTTimeout = 2 * time.Second + +// MQTTBufferSize indicates the size for uplink channel buffers +var MQTTBufferSize = 10 + +func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) error { + h.mqttClient = mqtt.NewClient(apex.Wrap(h.Ctx), "ttnhdl", username, password, mqttBrokers...) + + err := h.mqttClient.Connect() + if err != nil { + return err + } + + h.mqttUp = make(chan *types.UplinkMessage, MQTTBufferSize) + h.mqttEvent = make(chan *types.DeviceEvent, MQTTBufferSize) + + token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appID string, devID string, msg types.DownlinkMessage) { + down := &msg + down.DevID = devID + down.AppID = appID + go h.EnqueueDownlink(down) + }) + token.Wait() + if token.Error() != nil { + return err + } + + ctx := h.Ctx.WithField("Protocol", "MQTT") + + go func() { + for up := range h.mqttUp { + ctx.WithFields(log.Fields{ + "DevID": up.DevID, + "AppID": up.AppID, + }).Debug("Publish Uplink") + upToken := h.mqttClient.PublishUplink(*up) + go func() { + if upToken.WaitTimeout(MQTTTimeout) { + if upToken.Error() != nil { + ctx.WithError(upToken.Error()).Warn("Could not publish Uplink") + } + } else { + ctx.Warn("Uplink publish timeout") + } + }() + if len(up.PayloadFields) > 0 { + fieldsToken := h.mqttClient.PublishUplinkFields(up.AppID, up.DevID, up.PayloadFields) + go func() { + if fieldsToken.WaitTimeout(MQTTTimeout) { + if fieldsToken.Error() != nil { + ctx.WithError(fieldsToken.Error()).Warn("Could not publish Uplink Fields") + } + } else { + ctx.Warn("Uplink Fields publish timeout") + } + }() + } + } + }() + + go func() { + for event := range h.mqttEvent { + h.Ctx.WithFields(log.Fields{ + "DevID": event.DevID, + "AppID": event.AppID, + "Event": event.Event, + }).Debug("Publish Event") + var token mqtt.Token + if event.DevID == "" { + token = h.mqttClient.PublishAppEvent(event.AppID, event.Event, event.Data) + } else { + token = h.mqttClient.PublishDeviceEvent(event.AppID, event.DevID, event.Event, event.Data) + } + go func() { + if token.WaitTimeout(MQTTTimeout) { + if token.Error() != nil { + h.Ctx.WithError(token.Error()).Warn("Could not publish Event") + } + } else { + h.Ctx.Warn("Event publish timeout") + } + }() + } + }() + + return nil +} diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go new file mode 100644 index 000000000..d8f31ea54 --- /dev/null +++ b/core/handler/mqtt_test.go @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleMQTT(t *testing.T) { + host := os.Getenv("MQTT_ADDRESS") + if host == "" { + host = "localhost:1883" + } + + a := New(t) + var wg WaitGroup + c := mqtt.NewClient(apex.Wrap(GetLogger(t, "TestHandleMQTT")), "test", "", "", fmt.Sprintf("tcp://%s", host)) + err := c.Connect() + a.So(err, ShouldBeNil) + appID := "handler-mqtt-app1" + devID := "handler-mqtt-dev1" + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-mqtt"), + } + h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, + }) + defer func() { + h.devices.Delete(appID, devID) + }() + err = h.HandleMQTT("", "", fmt.Sprintf("tcp://%s", host)) + a.So(err, ShouldBeNil) + + c.PublishDownlink(types.DownlinkMessage{ + AppID: appID, + DevID: devID, + PayloadRaw: []byte{0xAA, 0xBC}, + }).Wait() + <-time.After(50 * time.Millisecond) + dev, _ := h.devices.Get(appID, devID) + a.So(dev.NextDownlink, ShouldNotBeNil) + + wg.Add(1) + c.SubscribeDeviceUplink(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req types.UplinkMessage) { + a.So(r_appID, ShouldEqual, appID) + a.So(r_devID, ShouldEqual, devID) + a.So(req.PayloadRaw, ShouldResemble, []byte{0xAA, 0xBC}) + wg.Done() + }).Wait() + + h.mqttUp <- &types.UplinkMessage{ + DevID: devID, + AppID: appID, + PayloadRaw: []byte{0xAA, 0xBC}, + PayloadFields: map[string]interface{}{ + "field": "value", + }, + } + + wg.Add(1) + c.SubscribeDeviceActivations(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req types.Activation) { + a.So(r_appID, ShouldEqual, appID) + a.So(r_devID, ShouldEqual, devID) + wg.Done() + }).Wait() + + h.mqttEvent <- &types.DeviceEvent{ + DevID: devID, + AppID: appID, + Event: types.ActivationEvent, + } + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) +} diff --git a/core/handler/server.go b/core/handler/server.go new file mode 100644 index 000000000..0b962d0aa --- /dev/null +++ b/core/handler/server.go @@ -0,0 +1,52 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" +) + +type handlerRPC struct { + handler Handler +} + +func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { + _, err := h.handler.ValidateNetworkContext(ctx) + if err != nil { + return nil, err + } + if err := challenge.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Activation Challenge Request") + } + res, err := h.handler.HandleActivationChallenge(challenge) + if err != nil { + return nil, err + } + return res, nil +} + +func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + _, err := h.handler.ValidateNetworkContext(ctx) + if err != nil { + return nil, err + } + if err := activation.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Activation Request") + } + res, err := h.handler.HandleActivation(activation) + if err != nil { + return nil, err + } + return res, nil +} + +// RegisterRPC registers this handler as a HandlerServer (github.com/TheThingsNetwork/ttn/api/handler) +func (h *handler) RegisterRPC(s *grpc.Server) { + server := &handlerRPC{h} + pb.RegisterHandlerServer(s, server) +} diff --git a/core/handler/status.go b/core/handler/status.go new file mode 100644 index 000000000..2eb5f9f05 --- /dev/null +++ b/core/handler/status.go @@ -0,0 +1,53 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter +} + +func (h *handler) InitStatus() { + h.status = &status{ + uplink: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + } +} + +func (h *handler) GetStatus() *pb.Status { + status := new(pb.Status) + if h.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := h.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + downlink := h.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := h.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + return status +} diff --git a/core/handler/status_test.go b/core/handler/status_test.go new file mode 100644 index 000000000..4187dddbb --- /dev/null +++ b/core/handler/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + h := new(handler) + a.So(h.GetStatus(), ShouldResemble, new(pb.Status)) + h.InitStatus() + a.So(h.status, ShouldNotBeNil) + status := h.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/handler/types.go b/core/handler/types.go new file mode 100644 index 000000000..14de0016c --- /dev/null +++ b/core/handler/types.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/apex/log" +) + +// UplinkProcessor processes an uplink protobuf to an application-layer uplink message +type UplinkProcessor func(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error + +// DownlinkProcessor processes an application-layer downlink message to a downlik protobuf +type DownlinkProcessor func(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error + +// ErrNotNeeded indicates that the processing of a message should be aborted +var ErrNotNeeded = errors.New("Further processing not needed") diff --git a/core/handler/uplink.go b/core/handler/uplink.go new file mode 100644 index 000000000..1baccaa21 --- /dev/null +++ b/core/handler/uplink.go @@ -0,0 +1,108 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// ResponseDeadline indicates how long +var ResponseDeadline = 100 * time.Millisecond + +func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err error) { + appID, devID := uplink.AppId, uplink.DevId + ctx := h.Ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + }) + start := time.Now() + defer func() { + if err != nil { + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.UplinkErrorEvent, + Data: types.ErrorEventData{Error: err.Error()}, + } + ctx.WithError(err).Warn("Could not handle uplink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") + } + }() + h.status.uplink.Mark(1) + + // Build AppUplink + appUplink := &types.UplinkMessage{ + AppID: appID, + DevID: devID, + } + + // Get Uplink Processors + processors := []UplinkProcessor{ + h.ConvertFromLoRaWAN, + h.ConvertMetadata, + h.ConvertFieldsUp, + } + + ctx.WithField("NumProcessors", len(processors)).Debug("Running Uplink Processors") + + // Run Uplink Processors + for _, processor := range processors { + err = processor(ctx, uplink, appUplink) + if err == ErrNotNeeded { + err = nil + return nil + } else if err != nil { + return err + } + } + + // Publish Uplink + h.mqttUp <- appUplink + if h.amqpEnabled { + h.amqpUp <- appUplink + } + + <-time.After(ResponseDeadline) + + // Find Device and scheduled downlink + var appDownlink types.DownlinkMessage + dev, err := h.devices.Get(uplink.AppId, uplink.DevId) + if err != nil { + return err + } + if dev.NextDownlink != nil { + appDownlink = *dev.NextDownlink + } + + if uplink.ResponseTemplate == nil { + ctx.Debug("No Downlink Available") + return nil + } + + // Prepare Downlink + downlink := uplink.ResponseTemplate + appDownlink.AppID = uplink.AppId + appDownlink.DevID = uplink.DevId + + // Handle Downlink + err = h.HandleDownlink(&appDownlink, downlink) + if err != nil { + return err + } + + // Clear Downlink + dev.StartUpdate() + dev.NextDownlink = nil + err = h.devices.Set(dev) + if err != nil { + return err + } + + return nil +} diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go new file mode 100644 index 000000000..93ed91cd2 --- /dev/null +++ b/core/handler/uplink_test.go @@ -0,0 +1,147 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleUplink(t *testing.T) { + a := New(t) + var err error + var wg WaitGroup + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + appID := "appid" + devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + devID := "devid" + h := &handler{ + Component: &component.Component{Ctx: GetLogger(t, "TestHandleUplink")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-uplink"), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-handle-uplink"), + } + h.InitStatus() + dev := &device.Device{ + AppID: appID, + DevID: devID, + AppEUI: appEUI, + DevEUI: devEUI, + } + h.devices.Set(dev) + defer func() { + h.devices.Delete(appID, devID) + }() + h.applications.Set(&application.Application{ + AppID: appID, + }) + defer func() { + h.applications.Delete(appID) + }() + h.mqttUp = make(chan *types.UplinkMessage) + h.mqttEvent = make(chan *types.DeviceEvent, 10) + h.downlink = make(chan *pb_broker.DownlinkMessage) + + uplink, _ := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0xDA, 0x23, 0x99, 0x61, 0xD4}) + + downlinkEmpty := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x21, 0xEA, 0x8B, 0x0E} + downlinkACK := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x20, 0x00, 0x00, 0x0A, 0x3B, 0x3F, 0x77, 0x0B} + downlinkMAC := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x05, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x0A, 0x4D, 0x11, 0x55, 0x01} + expected := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x66, 0xE6, 0x1D, 0x49, 0x82, 0x84} + + downlink := &pb_broker.DownlinkMessage{ + DownlinkOption: &pb_broker.DownlinkOption{ + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + FCnt: 0, + }}}, + }, + } + + // Test Uplink, no downlink option available + wg.Add(1) + go func() { + <-h.mqttUp + wg.Done() + }() + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.WaitFor(50 * time.Millisecond) + + uplink.ResponseTemplate = downlink + + // Test Uplink, no downlink needed + wg.Add(1) + go func() { + <-h.mqttUp + wg.Done() + }() + downlink.Payload = downlinkEmpty + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.WaitFor(50 * time.Millisecond) + + // Test Uplink, ACK downlink needed + wg.Add(2) + go func() { + <-h.mqttUp + wg.Done() + }() + go func() { + <-h.downlink + wg.Done() + }() + downlink.Payload = downlinkACK + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.WaitFor(50 * time.Millisecond) + + // Test Uplink, MAC downlink needed + wg.Add(2) + go func() { + <-h.mqttUp + wg.Done() + }() + go func() { + <-h.downlink + wg.Done() + }() + downlink.Payload = downlinkMAC + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.WaitFor(50 * time.Millisecond) + + dev.StartUpdate() + dev.NextDownlink = &types.DownlinkMessage{ + PayloadRaw: []byte{0xaa, 0xbc}, + } + + // Test Uplink, Data downlink needed + h.devices.Set(dev) + wg.Add(2) + go func() { + <-h.mqttUp + wg.Done() + }() + go func() { + dl := <-h.downlink + a.So(dl.Payload, ShouldResemble, expected) + wg.Done() + }() + downlink.Payload = downlinkEmpty + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.WaitFor(50 * time.Millisecond) + + dev, _ = h.devices.Get(appID, devID) + a.So(dev.NextDownlink, ShouldBeNil) +} diff --git a/core/networkserver/activation.go b/core/networkserver/activation.go new file mode 100644 index 000000000..56b3fe74d --- /dev/null +++ b/core/networkserver/activation.go @@ -0,0 +1,127 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "fmt" + "strings" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/brocaar/lorawan" +) + +func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) { + // Generate random DevAddr bytes + var devAddr types.DevAddr + copy(devAddr[:], random.Bytes(4)) + + // Get a random prefix that matches the constraints + prefixes := n.GetPrefixesFor(constraints...) + if len(prefixes) == 0 { + return types.DevAddr{}, errors.NewErrNotFound(fmt.Sprintf("DevAddr prefix with constraints %v", constraints)) + } + + // Select a prefix + prefix := prefixes[random.Intn(len(prefixes))] + + // Apply the prefix + devAddr = devAddr.WithPrefix(prefix) + + return devAddr, nil +} + +func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { + if activation.AppEui == nil || activation.DevEui == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") + } + dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) + if err != nil { + return nil, err + } + activation.AppId = dev.AppID + activation.DevId = dev.DevID + + // Get activation constraints (for DevAddr prefix selection) + activationConstraints := strings.Split(dev.Options.ActivationConstraints, ",") + if len(activationConstraints) == 1 && activationConstraints[0] == "" { + activationConstraints = []string{} + } + activationConstraints = append(activationConstraints, "otaa") + + // Build activation metadata if not present + if meta := activation.GetActivationMetadata(); meta == nil { + activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} + } + // Build lorawan metadata if not present + if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") + } + + // Build response template if not present + if pld := activation.GetResponseTemplate(); pld == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing response template") + } + lorawanMeta := activation.ActivationMetadata.GetLorawan() + + // Get a random device address + devAddr, err := n.getDevAddr(activationConstraints...) + if err != nil { + return nil, err + } + + // Set the DevAddr in the Activation Metadata + lorawanMeta.DevAddr = &devAddr + + // Build JoinAccept Payload + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinAccept, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinAcceptPayload{ + NetID: n.netID, + DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)}, + RXDelay: uint8(lorawanMeta.RxDelay), + DevAddr: lorawan.DevAddr(devAddr), + }, + } + if lorawanMeta.CfList != nil { + var cfList lorawan.CFList + for i, cfListItem := range lorawanMeta.CfList.Freq { + cfList[i] = cfListItem + } + phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList + } + + // Set the Payload + phyBytes, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + activation.ResponseTemplate.Payload = phyBytes + + return activation, nil +} + +func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) { + meta := activation.GetActivationMetadata() + if meta == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing ActivationMetadata") + } + lorawan := meta.GetLorawan() + if lorawan == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") + } + n.status.activations.Mark(1) + err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) + if err != nil { + return nil, err + } + return activation, nil +} diff --git a/core/networkserver/activation_test.go b/core/networkserver/activation_test.go new file mode 100644 index 000000000..c730b8032 --- /dev/null +++ b/core/networkserver/activation_test.go @@ -0,0 +1,139 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandlePrepareActivation(t *testing.T) { + a := New(t) + ns := &networkServer{ + netID: [3]byte{0x00, 0x00, 0x13}, + prefixes: map[types.DevAddrPrefix][]string{ + types.DevAddrPrefix{DevAddr: [4]byte{0x26, 0x00, 0x00, 0x00}, Length: 7}: []string{ + "otaa", + "local", + }, + }, + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-prepare-activation"), + } + + appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) + + // Device not registered + resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldNotBeNil) + + dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ + ActivationConstraints: "private", + }} + a.So(ns.devices.Set(dev), ShouldBeNil) + + defer func() { + ns.devices.Delete(appEUI, devEUI) + }() + + // Constrained Device + resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + DevEui: &devEUI, + AppEui: &appEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldNotBeNil) + + dev.StartUpdate() + dev.Options = device.Options{} + a.So(ns.devices.Set(dev), ShouldBeNil) + + // Device registered + resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + DevEui: &devEUI, + AppEui: &appEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldBeNil) + devAddr := resp.ActivationMetadata.GetLorawan().DevAddr + a.So(devAddr.IsEmpty(), ShouldBeFalse) + a.So(devAddr[0]&254, ShouldEqual, 19<<1) // 7 MSB should be NetID + + var resPHY lorawan.PHYPayload + resPHY.UnmarshalBinary(resp.ResponseTemplate.Payload) + resMAC, _ := resPHY.MACPayload.(*lorawan.DataPayload) + joinAccept := &lorawan.JoinAcceptPayload{} + joinAccept.UnmarshalBinary(false, resMAC.Bytes) + + a.So(joinAccept.DevAddr[0]&254, ShouldEqual, 19<<1) + a.So(*joinAccept.CFList, ShouldEqual, lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000}) +} + +func TestHandleActivate(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-activate"), + } + ns.InitStatus() + + dev := &device.Device{ + AppEUI: types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), + DevEUI: types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), + } + a.So(ns.devices.Set(dev), ShouldBeNil) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1))) + }() + + _, err := ns.HandleActivate(&pb_handler.DeviceActivationResponse{}) + a.So(err, ShouldNotBeNil) + + _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ + ActivationMetadata: &pb_protocol.ActivationMetadata{}, + }) + a.So(err, ShouldNotBeNil) + + devAddr := getDevAddr(0, 0, 3, 1) + var nwkSKey types.NwkSKey + copy(nwkSKey[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1}) + appEUI := types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) + devEUI := types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) + _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + AppEui: &appEUI, + DevEui: &devEUI, + DevAddr: &devAddr, + NwkSKey: &nwkSKey, + }, + }}, + }) + a.So(err, ShouldBeNil) +} diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go new file mode 100644 index 000000000..649794b5b --- /dev/null +++ b/core/networkserver/device/device.go @@ -0,0 +1,64 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "reflect" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/fatih/structs" +) + +// Options for the specified device +type Options struct { + ActivationConstraints string `json:"activation_constraints,omitempty"` // Activation Constraints (public/local/private) + DisableFCntCheck bool `json:"disable_fcnt_check,omitemtpy"` // Disable Frame counter check (insecure) + Uses32BitFCnt bool `json:"uses_32_bit_fcnt,omitemtpy"` // Use 32-bit Frame counters +} + +// Device contains the state of a device +type Device struct { + old *Device + DevEUI types.DevEUI `redis:"dev_eui"` + AppEUI types.AppEUI `redis:"app_eui"` + AppID string `redis:"app_id"` + DevID string `redis:"dev_id"` + DevAddr types.DevAddr `redis:"dev_addr"` + NwkSKey types.NwkSKey `redis:"nwk_s_key"` + FCntUp uint32 `redis:"f_cnt_up"` + FCntDown uint32 `redis:"f_cnt_down"` + LastSeen time.Time `redis:"last_seen"` + Options Options `redis:"options"` + Utilization Utilization `redis:"utilization"` + + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` +} + +// StartUpdate stores the state of the device +func (d *Device) StartUpdate() { + old := *d + d.old = &old +} + +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (d Device) ChangedFields() (changed []string) { + new := structs.New(d) + fields := new.Names() + if d.old == nil { + return fields + } + old := structs.New(*d.old) + + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue + } + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) + } + } + return +} diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go new file mode 100644 index 000000000..cdaae2951 --- /dev/null +++ b/core/networkserver/device/device_test.go @@ -0,0 +1,31 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDeviceUpdate(t *testing.T) { + a := New(t) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + a.So(device.old.DevID, ShouldEqual, device.DevID) +} + +func TestDeviceChangedFields(t *testing.T) { + a := New(t) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + device.DevID = "NewDevID" + + a.So(device.ChangedFields(), ShouldHaveLength, 1) + a.So(device.ChangedFields(), ShouldContain, "DevID") +} diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go new file mode 100644 index 000000000..3af6e443b --- /dev/null +++ b/core/networkserver/device/store.go @@ -0,0 +1,179 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v5" +) + +// Store interface for Devices +type Store interface { + List() ([]*Device, error) + ListForAddress(devAddr types.DevAddr) ([]*Device, error) + Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) + Set(new *Device, properties ...string) (err error) + Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error + Delete(appEUI types.AppEUI, devEUI types.DevEUI) error +} + +const defaultRedisPrefix = "ns" + +const redisDevicePrefix = "device" +const redisDevAddrPrefix = "dev_addr" + +// NewRedisDeviceStore creates a new Redis-based status store +func NewRedisDeviceStore(client *redis.Client, prefix string) Store { + if prefix == "" { + prefix = defaultRedisPrefix + } + store := storage.NewRedisMapStore(client, prefix+":"+redisDevicePrefix) + store.SetBase(Device{}, "") + + return &RedisDeviceStore{ + store: store, + devAddrIndex: storage.NewRedisSetStore(client, prefix+":"+redisDevAddrPrefix), + } +} + +// RedisDeviceStore stores Devices in Redis. +// - Devices are stored as a Hash +// - DevAddr mappings are indexed in a Set +type RedisDeviceStore struct { + store *storage.RedisMapStore + devAddrIndex *storage.RedisSetStore +} + +// List all Devices +func (s *RedisDeviceStore) List() ([]*Device, error) { + devicesI, err := s.store.List("", nil) + if err != nil { + return nil, err + } + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) + } + } + return devices, nil +} + +// ListForAddress lists all devices for a specific DevAddr +func (s *RedisDeviceStore) ListForAddress(devAddr types.DevAddr) ([]*Device, error) { + deviceKeys, err := s.devAddrIndex.Get(devAddr.String()) + if errors.GetErrType(err) == errors.NotFound { + return nil, nil + } + if err != nil { + return nil, err + } + devicesI, err := s.store.GetAll(deviceKeys, nil) + if err != nil { + return nil, err + } + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) + } + } + return devices, nil +} + +// Get a specific Device +func (s *RedisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { + deviceI, err := s.store.Get(fmt.Sprintf("%s:%s", appEUI, devEUI)) + if err != nil { + return nil, err + } + if device, ok := deviceI.(Device); ok { + return &device, nil + } + return nil, errors.New("Database did not return a Device") +} + +// Set a new Device or update an existing one +func (s *RedisDeviceStore) Set(new *Device, properties ...string) (err error) { + // If this is an update, check if AppEUI, DevEUI and DevAddr are still the same + old := new.old + var addrChanged bool + if old != nil { + addrChanged = new.DevAddr != old.DevAddr || new.DevEUI != old.DevEUI || new.AppEUI != old.AppEUI + if addrChanged { + if err := s.devAddrIndex.Remove(old.DevAddr.String(), fmt.Sprintf("%s:%s", old.AppEUI, old.DevEUI)); err != nil { + return err + } + } + } + + now := time.Now() + new.UpdatedAt = now + + key := fmt.Sprintf("%s:%s", new.AppEUI, new.DevEUI) + if new.old != nil { + err = s.store.Update(key, *new, properties...) + } else { + new.CreatedAt = now + err = s.store.Create(key, *new, properties...) + } + if err != nil { + return + } + + if (new.old == nil || addrChanged) && !new.DevAddr.IsEmpty() { + if err := s.devAddrIndex.Add(new.DevAddr.String(), key); err != nil { + return err + } + } + + return nil +} + +// Activate a Device +func (s *RedisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { + dev, err := s.Get(appEUI, devEUI) + if err != nil { + return err + } + + dev.StartUpdate() + + dev.LastSeen = time.Now() + dev.UpdatedAt = time.Now() + dev.DevAddr = devAddr + dev.NwkSKey = nwkSKey + dev.FCntUp = 0 + dev.FCntDown = 0 + + return s.Set(dev) +} + +// Delete a Device +func (s *RedisDeviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { + key := fmt.Sprintf("%s:%s", appEUI, devEUI) + + deviceI, err := s.store.GetFields(key, "dev_addr") + if err != nil { + return err + } + + device, ok := deviceI.(Device) + if !ok { + errors.New("Database did not return a Device") + } + + if !device.DevAddr.IsEmpty() { + if err := s.devAddrIndex.Remove(device.DevAddr.String(), key); err != nil { + return err + } + } + + return s.store.Delete(key) +} diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go new file mode 100644 index 000000000..0d4ba4e58 --- /dev/null +++ b/core/networkserver/device/store_test.go @@ -0,0 +1,189 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestDeviceStore(t *testing.T) { + a := New(t) + + NewRedisDeviceStore(GetRedisClient(), "") + + s := NewRedisDeviceStore(GetRedisClient(), "networkserver-test-device-store") + + // Non-existing App + err := s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, + }) + a.So(err, ShouldBeNil) + + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 1}) + + defer func() { + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + }() + + // Existing App + err = s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + }() + + res, err := s.ListForAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 2}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 0) + + // Existing Device, New DevAddr + err = s.Set(&Device{ + old: &Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }, + DevAddr: types.DevAddr{0, 0, 0, 3}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, + }) + a.So(err, ShouldBeNil) + + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + + s.Set(&Device{ + old: &Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }, + DevAddr: types.DevAddr{0, 0, 0, 3}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2}, + }) + + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 0) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 2}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 3}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 3}) + + // List + devices, err := s.List() + a.So(err, ShouldBeNil) + a.So(devices, ShouldHaveLength, 2) + + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) +} + +func TestDeviceActivate(t *testing.T) { + + a := New(t) + + s := NewRedisDeviceStore(GetRedisClient(), "networkserver-test-device-activate") + + // Device not registered + err := s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 1}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + ) + a.So(err, ShouldNotBeNil) + + // Device registered + s.Set(&Device{ + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + }) + err = s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 1}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + ) + a.So(err, ShouldBeNil) + + // It should register the device + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 1}) + + // It should register the DevAddr + res, err := s.ListForAddress(types.DevAddr{0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + + s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 1, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + FCntUp: 42, + FCntDown: 42, + }) + + // Activate the same device again + err = s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 2}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}, + ) + a.So(err, ShouldBeNil) + + // It should reset the DevAddr, NwkSKey and Frame Counters + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 2}) + a.So(dev.NwkSKey, ShouldEqual, types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}) + a.So(dev.FCntUp, ShouldEqual, 0) + a.So(dev.FCntDown, ShouldEqual, 0) + + // Cleanup + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + +} diff --git a/core/networkserver/device/utilization.go b/core/networkserver/device/utilization.go new file mode 100644 index 000000000..f1cf1975e --- /dev/null +++ b/core/networkserver/device/utilization.go @@ -0,0 +1,7 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package device + +// Utilization of the device +type Utilization interface{} diff --git a/core/networkserver/downlink.go b/core/networkserver/downlink.go new file mode 100644 index 000000000..3ed17c6e1 --- /dev/null +++ b/core/networkserver/downlink.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) { + // Get Device + dev, err := n.devices.Get(*message.AppEui, *message.DevEui) + if err != nil { + return nil, err + } + + n.status.downlink.Mark(1) + + dev.StartUpdate() + + if dev.AppID != message.AppId || dev.DevID != message.DevId { + return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") + } + + // Unmarshal LoRaWAN Payload + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(message.Payload) + if err != nil { + return nil, err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") + } + + // Set DevAddr + macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr) + + // FIRST set and THEN increment FCntDown + // TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK + macPayload.FHDR.FCnt = dev.FCntDown + dev.FCntDown++ + err = n.devices.Set(dev) + if err != nil { + return nil, err + } + + // Sign MIC + phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) + + // Update message + bytes, err := phyPayload.MarshalBinary() + if err != nil { + return nil, err + } + message.Payload = bytes + + return message, nil +} diff --git a/core/networkserver/downlink_test.go b/core/networkserver/downlink_test.go new file mode 100644 index 000000000..5df03a6d5 --- /dev/null +++ b/core/networkserver/downlink_test.go @@ -0,0 +1,91 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandleDownlink(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-downlink"), + } + ns.InitStatus() + + appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devAddr := getDevAddr(1, 2, 3, 4) + + // Device Not Found + message := &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err := ns.HandleDownlink(message) + a.So(err, ShouldNotBeNil) + + ns.devices.Set(&device.Device{ + DevAddr: devAddr, + AppEUI: appEUI, + DevEUI: devEUI, + }) + defer func() { + ns.devices.Delete(appEUI, devEUI) + }() + + // Invalid Payload + message = &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err = ns.HandleDownlink(message) + a.So(err, ShouldNotBeNil) + + fPort := uint8(3) + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FPort: &fPort, + FHDR: lorawan.FHDR{ + FCtrl: lorawan.FCtrl{ + ACK: true, + }, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + message = &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: bytes, + } + res, err := ns.HandleDownlink(message) + a.So(err, ShouldBeNil) + + var phyPayload lorawan.PHYPayload + phyPayload.UnmarshalBinary(res.Payload) + macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + a.So(*macPayload.FPort, ShouldEqual, 3) + a.So(macPayload.FHDR.DevAddr, ShouldEqual, lorawan.DevAddr{1, 2, 3, 4}) + a.So(macPayload.FHDR.FCnt, ShouldEqual, 0) // The first Frame counter is zero + a.So(phyPayload.MIC, ShouldNotEqual, [4]byte{0, 0, 0, 0}) // MIC should be set, we'll check it with actual examples in the integration test + + dev, _ := ns.devices.Get(appEUI, devEUI) + a.So(dev.FCntDown, ShouldEqual, 1) + +} diff --git a/core/networkserver/get_devices.go b/core/networkserver/get_devices.go new file mode 100644 index 000000000..708b70455 --- /dev/null +++ b/core/networkserver/get_devices.go @@ -0,0 +1,50 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/utils/fcnt" +) + +func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { + devices, err := n.devices.ListForAddress(*req.DevAddr) + if err != nil { + return nil, err + } + + // Return all devices with DevAddr with FCnt <= fCnt or Security off + + res := &pb.DevicesResponse{ + Results: make([]*pb_lorawan.Device, 0, len(devices)), + } + + for _, device := range devices { + fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) + dev := &pb_lorawan.Device{ + AppEui: &device.AppEUI, + AppId: device.AppID, + DevEui: &device.DevEUI, + DevId: device.DevID, + NwkSKey: &device.NwkSKey, + FCntUp: device.FCntUp, + Uses32BitFCnt: device.Options.Uses32BitFCnt, + DisableFCntCheck: device.Options.DisableFCntCheck, + } + if device.Options.DisableFCntCheck { + res.Results = append(res.Results, dev) + continue + } + if device.FCntUp <= req.FCnt { + res.Results = append(res.Results, dev) + continue + } else if device.Options.Uses32BitFCnt && device.FCntUp <= fullFCnt { + res.Results = append(res.Results, dev) + continue + } + } + + return res, nil +} diff --git a/core/networkserver/get_devices_test.go b/core/networkserver/get_devices_test.go new file mode 100644 index 000000000..0e7159750 --- /dev/null +++ b/core/networkserver/get_devices_test.go @@ -0,0 +1,135 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleGetDevices(t *testing.T) { + a := New(t) + + ns := &networkServer{ + devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-get-devices"), + } + + nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} + + // No Devices + devAddr1 := getDevAddr(1, 2, 3, 4) + res, err := ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr1, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldBeEmpty) + + // Matching Device + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(1, 2, 3, 4), + AppEUI: types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: nwkSKey, + FCntUp: 5, + }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8))) + }() + + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr1, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // Non-Matching DevAddr + devAddr2 := getDevAddr(5, 6, 7, 8) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr2, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 0) + + // Non-Matching FCnt + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr1, + FCnt: 4, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 0) + + // Non-Matching FCnt, but FCnt Check Disabled + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(5, 6, 7, 8), + AppEUI: types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), + DevEUI: types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), + NwkSKey: nwkSKey, + FCntUp: 5, + Options: device.Options{ + DisableFCntCheck: true, + }, + }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4))) + }() + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr2, + FCnt: 4, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // 32 Bit Frame Counter (A) + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(2, 2, 3, 4), + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: nwkSKey, + FCntUp: 5 + (2 << 16), + Options: device.Options{ + Uses32BitFCnt: true, + }, + }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8))) + }() + devAddr3 := getDevAddr(2, 2, 3, 4) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr3, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // 32 Bit Frame Counter (B) + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(2, 2, 3, 5), + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8)), + NwkSKey: nwkSKey, + FCntUp: (2 << 16) - 1, + Options: device.Options{ + Uses32BitFCnt: true, + }, + }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8)), types.DevEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8))) + }() + devAddr4 := getDevAddr(2, 2, 3, 5) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr4, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + +} diff --git a/core/networkserver/mac.go b/core/networkserver/mac.go new file mode 100644 index 000000000..53d665bd5 --- /dev/null +++ b/core/networkserver/mac.go @@ -0,0 +1,39 @@ +package networkserver + +import ( + "sort" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" +) + +type bySNR []*pb_gateway.RxMetadata + +func (a bySNR) Len() int { return len(a) } +func (a bySNR) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a bySNR) Less(i, j int) bool { return a[i].Snr < a[j].Snr } + +func bestSNR(metadata []*pb_gateway.RxMetadata) float32 { + if len(metadata) == 0 { + return 0 + } + sorted := bySNR(metadata) + sort.Sort(sorted) + return sorted[len(sorted)-1].Snr +} + +var demodulationFloor = map[string]float32{ + "SF7BW125": -7.5, + "SF8BW125": -10, + "SF9BW125": -12.5, + "SF10BW125": -15, + "SF11BW125": -17.5, + "SF12BW125": -20, + "SF7BW250": -4.5, +} + +func linkMargin(dataRate string, snr float32) float32 { + if floor, ok := demodulationFloor[dataRate]; ok { + return snr - floor + } + return 0 +} diff --git a/core/networkserver/mac_test.go b/core/networkserver/mac_test.go new file mode 100644 index 000000000..61af1ff85 --- /dev/null +++ b/core/networkserver/mac_test.go @@ -0,0 +1,25 @@ +package networkserver + +import ( + "testing" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + . "github.com/smartystreets/assertions" +) + +func TestBestSNR(t *testing.T) { + a := New(t) + best := bestSNR([]*pb_gateway.RxMetadata{ + &pb_gateway.RxMetadata{Snr: 1}, + &pb_gateway.RxMetadata{Snr: 2}, + &pb_gateway.RxMetadata{Snr: 0}, + &pb_gateway.RxMetadata{Snr: 10}, + &pb_gateway.RxMetadata{Snr: -10}, + }) + a.So(best, ShouldEqual, 10) +} + +func TestLinkMargin(t *testing.T) { + a := New(t) + a.So(linkMargin("SF7BW125", 4.3), ShouldEqual, 11.8) +} diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go new file mode 100644 index 000000000..7621bcecf --- /dev/null +++ b/core/networkserver/manager_server.go @@ -0,0 +1,182 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/go-account-lib/rights" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/ratelimit" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type networkServerManager struct { + networkServer *networkServer + clientRate *ratelimit.Registry +} + +func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Device Identifier") + } + claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if n.clientRate.Limit(claims.Subject) { + return nil, grpc.Errorf(codes.ResourceExhausted, "Rate limit for client reached") + } + dev, err := n.networkServer.devices.Get(*in.AppEui, *in.DevEui) + if err != nil { + return nil, err + } + if !claims.AppRight(dev.AppID, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) + } + return dev, nil +} + +func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { + dev, err := n.getDevice(ctx, in) + if err != nil { + return nil, err + } + + lastSeen := time.Unix(0, 0) + if !dev.LastSeen.IsZero() { + lastSeen = dev.LastSeen + } + + return &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: &dev.AppEUI, + DevId: dev.DevID, + DevEui: &dev.DevEUI, + DevAddr: &dev.DevAddr, + NwkSKey: &dev.NwkSKey, + FCntUp: dev.FCntUp, + FCntDown: dev.FCntDown, + DisableFCntCheck: dev.Options.DisableFCntCheck, + Uses32BitFCnt: dev.Options.Uses32BitFCnt, + LastSeen: lastSeen.UnixNano(), + }, nil +} + +func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*empty.Empty, error) { + dev, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, err + } + + if err := in.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Device") + } + + claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) + } + + if dev == nil { + dev = new(device.Device) + } else { + dev.StartUpdate() + } + + dev.AppID = in.AppId + dev.AppEUI = *in.AppEui + dev.DevID = in.DevId + dev.DevEUI = *in.DevEui + dev.FCntUp = in.FCntUp + dev.FCntDown = in.FCntDown + + dev.Options = device.Options{ + DisableFCntCheck: in.DisableFCntCheck, + Uses32BitFCnt: in.Uses32BitFCnt, + ActivationConstraints: in.ActivationConstraints, + } + + if in.NwkSKey != nil && in.DevAddr != nil { + dev.DevAddr = *in.DevAddr + dev.NwkSKey = *in.NwkSKey + } + + err = n.networkServer.devices.Set(dev) + if err != nil { + return nil, err + } + + return &empty.Empty{}, nil +} + +func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*empty.Empty, error) { + _, err := n.getDevice(ctx, in) + if err != nil { + return nil, err + } + err = n.networkServer.devices.Delete(*in.AppEui, *in.DevEui) + if err != nil { + return nil, err + } + return &empty.Empty{}, nil +} + +func (n *networkServerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { + var mapping []*pb_lorawan.PrefixesResponse_PrefixMapping + for prefix, usage := range n.networkServer.prefixes { + mapping = append(mapping, &pb_lorawan.PrefixesResponse_PrefixMapping{ + Prefix: prefix.String(), + Usage: usage, + }) + } + return &pb_lorawan.PrefixesResponse{ + Prefixes: mapping, + }, nil +} + +func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { + devAddr, err := n.networkServer.getDevAddr(in.Usage...) + if err != nil { + return nil, err + } + return &pb_lorawan.DevAddrResponse{ + DevAddr: &devAddr, + }, nil +} + +func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + if n.networkServer.Identity.Id != "dev" { + _, err := n.networkServer.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := n.networkServer.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil +} + +// RegisterManager registers this networkserver as a NetworkServerManagerServer (github.com/TheThingsNetwork/ttn/api/networkserver) +func (n *networkServer) RegisterManager(s *grpc.Server) { + server := &networkServerManager{networkServer: n} + + server.clientRate = ratelimit.NewRegistry(5000, time.Hour) + + pb.RegisterNetworkServerManagerServer(s, server) + pb_lorawan.RegisterDeviceManagerServer(s, server) + pb_lorawan.RegisterDevAddrManagerServer(s, server) +} diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go new file mode 100644 index 000000000..dc6ef2a72 --- /dev/null +++ b/core/networkserver/networkserver.go @@ -0,0 +1,90 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v5" +) + +// NetworkServer implements LoRaWAN-specific functionality for TTN +type NetworkServer interface { + component.Interface + component.ManagementInterface + + UsePrefix(prefix types.DevAddrPrefix, usage []string) error + GetPrefixesFor(requiredUsages ...string) []types.DevAddrPrefix + + HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error) + HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) + HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) + HandleUplink(*pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) + HandleDownlink(*pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) +} + +// NewRedisNetworkServer creates a new Redis-backed NetworkServer +func NewRedisNetworkServer(client *redis.Client, netID int) NetworkServer { + ns := &networkServer{ + devices: device.NewRedisDeviceStore(client, "ns"), + prefixes: map[types.DevAddrPrefix][]string{}, + } + ns.netID = [3]byte{byte(netID >> 16), byte(netID >> 8), byte(netID)} + return ns +} + +type networkServer struct { + *component.Component + devices device.Store + netID [3]byte + prefixes map[types.DevAddrPrefix][]string + status *status +} + +func (n *networkServer) UsePrefix(prefix types.DevAddrPrefix, usage []string) error { + if prefix.Length < 7 { + return errors.NewErrInvalidArgument("Prefix", "invalid length") + } + if prefix.DevAddr[0]>>1 != n.netID[2] { + return errors.NewErrInvalidArgument("Prefix", "invalid netID") + } + n.prefixes[prefix] = usage + return nil +} + +func (n *networkServer) GetPrefixesFor(requiredUsages ...string) []types.DevAddrPrefix { + var suitablePrefixes []types.DevAddrPrefix + for prefix, offeredUsages := range n.prefixes { + matches := 0 + for _, requiredUsage := range requiredUsages { + for _, offeredUsage := range offeredUsages { + if offeredUsage == requiredUsage { + matches++ + } + } + } + if matches == len(requiredUsages) { + suitablePrefixes = append(suitablePrefixes, prefix) + } + } + return suitablePrefixes +} + +func (n *networkServer) Init(c *component.Component) error { + n.Component = c + n.InitStatus() + err := n.Component.UpdateTokenKey() + if err != nil { + return err + } + n.Component.SetStatus(component.StatusHealthy) + return nil +} + +func (n *networkServer) Shutdown() {} diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go new file mode 100644 index 000000000..3897f11ea --- /dev/null +++ b/core/networkserver/networkserver_test.go @@ -0,0 +1,48 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" + "gopkg.in/redis.v5" +) + +func getDevAddr(bytes ...byte) (addr types.DevAddr) { + copy(addr[:], bytes[:4]) + return +} + +func getEUI(bytes ...byte) (eui types.EUI64) { + copy(eui[:], bytes[:8]) + return +} + +func TestNewNetworkServer(t *testing.T) { + a := New(t) + var client redis.Client + + // TTN NetID + ns := NewRedisNetworkServer(&client, 19) + a.So(ns, ShouldNotBeNil) + a.So(ns.(*networkServer).netID, ShouldEqual, [3]byte{0, 0, 0x13}) + + // Other NetID, same NwkID + ns = NewRedisNetworkServer(&client, 66067) + a.So(ns, ShouldNotBeNil) + a.So(ns.(*networkServer).netID, ShouldEqual, [3]byte{0x01, 0x02, 0x13}) +} + +func TestUsePrefix(t *testing.T) { + a := New(t) + var client redis.Client + ns := NewRedisNetworkServer(&client, 19) + + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0, 0, 0, 0}), Length: 0}, []string{"otaa"}), ShouldNotBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0x14, 0, 0, 0}), Length: 7}, []string{"otaa"}), ShouldNotBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0x26, 0, 0, 0}), Length: 7}, []string{"otaa"}), ShouldBeNil) + a.So(ns.(*networkServer).prefixes, ShouldHaveLength, 1) +} diff --git a/core/networkserver/server.go b/core/networkserver/server.go new file mode 100644 index 000000000..d4d90b0de --- /dev/null +++ b/core/networkserver/server.go @@ -0,0 +1,125 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/handler" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/security" + "github.com/dgrijalva/jwt-go" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type networkServerRPC struct { + networkServer NetworkServer +} + +func (s *networkServerRPC) ValidateContext(ctx context.Context) error { + md, ok := metadata.FromContext(ctx) + if !ok { + return errors.NewErrInternal("Could not get metadata from context") + } + var id, token string + if ids, ok := md["id"]; ok && len(ids) == 1 { + id = ids[0] + } + if id == "" { + return errors.NewErrInvalidArgument("Metadata", "id missing") + } + if tokens, ok := md["token"]; ok && len(tokens) == 1 { + token = tokens[0] + } + if token == "" { + return errors.NewErrInvalidArgument("Metadata", "token missing") + } + var claims *jwt.StandardClaims + claims, err := security.ValidateJWT(token, []byte(s.networkServer.(*networkServer).Identity.PublicKey)) + if err != nil { + return err + } + if claims.Subject != id { + return errors.NewErrInvalidArgument("Metadata", "token was issued for a different component id") + } + return nil +} + +func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesRequest) (*pb.DevicesResponse, error) { + if err := s.ValidateContext(ctx); err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Devices Request") + } + res, err := s.networkServer.HandleGetDevices(req) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { + if err := s.ValidateContext(ctx); err != nil { + return nil, err + } + if err := activation.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Activation Request") + } + res, err := s.networkServer.HandlePrepareActivation(activation) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { + if err := s.ValidateContext(ctx); err != nil { + return nil, err + } + if err := activation.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Activation Request") + } + res, err := s.networkServer.HandleActivate(activation) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { + if err := s.ValidateContext(ctx); err != nil { + return nil, err + } + if err := message.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Uplink") + } + res, err := s.networkServer.HandleUplink(message) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { + if err := s.ValidateContext(ctx); err != nil { + return nil, err + } + if err := message.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Downlink") + } + res, err := s.networkServer.HandleDownlink(message) + if err != nil { + return nil, err + } + return res, nil +} + +// RegisterRPC registers this networkserver as a NetworkServerServer (github.com/TheThingsNetwork/ttn/api/networkserver) +func (n *networkServer) RegisterRPC(s *grpc.Server) { + server := &networkServerRPC{n} + pb.RegisterNetworkServerServer(s, server) +} diff --git a/core/networkserver/status.go b/core/networkserver/status.go new file mode 100644 index 000000000..6c8311804 --- /dev/null +++ b/core/networkserver/status.go @@ -0,0 +1,53 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter +} + +func (n *networkServer) InitStatus() { + n.status = &status{ + uplink: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + } +} + +func (n *networkServer) GetStatus() *pb.Status { + status := new(pb.Status) + if n.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := n.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + downlink := n.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := n.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + return status +} diff --git a/core/networkserver/status_test.go b/core/networkserver/status_test.go new file mode 100644 index 000000000..6db9b5722 --- /dev/null +++ b/core/networkserver/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + ns := new(networkServer) + a.So(ns.GetStatus(), ShouldResemble, new(pb.Status)) + ns.InitStatus() + a.So(ns.status, ShouldNotBeNil) + status := ns.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go new file mode 100644 index 000000000..594742a77 --- /dev/null +++ b/core/networkserver/uplink.go @@ -0,0 +1,116 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) { + // Get Device + dev, err := n.devices.Get(*message.AppEui, *message.DevEui) + if err != nil { + return nil, err + } + + n.status.uplink.Mark(1) + + dev.StartUpdate() + + // Unmarshal LoRaWAN Payload + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(message.Payload) + if err != nil { + return nil, err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + } + + // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) + if lorawan := message.GetProtocolMetadata().GetLorawan(); lorawan != nil && lorawan.FCnt != 0 { + dev.FCntUp = lorawan.FCnt + } else { + dev.FCntUp = macPayload.FHDR.FCnt + } + dev.LastSeen = time.Now() + err = n.devices.Set(dev) + if err != nil { + return nil, err + } + + // Prepare Downlink + if message.ResponseTemplate == nil { + return message, nil + } + message.ResponseTemplate.AppEui = message.AppEui + message.ResponseTemplate.DevEui = message.DevEui + message.ResponseTemplate.AppId = message.AppId + message.ResponseTemplate.DevId = message.DevId + + // Add Full FCnt (avoiding nil pointer panics) + if option := message.ResponseTemplate.DownlinkOption; option != nil { + if protocol := option.ProtocolConfig; protocol != nil { + if lorawan := protocol.GetLorawan(); lorawan != nil { + lorawan.FCnt = dev.FCntDown + } + } + } + + mac := &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: macPayload.FHDR.DevAddr, + FCnt: dev.FCntDown, + }, + } + + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + }, + MACPayload: mac, + } + + // Confirmed Uplink + if phyPayload.MHDR.MType == lorawan.ConfirmedDataUp { + mac.FHDR.FCtrl.ACK = true + } + + // Adaptive DataRate + if macPayload.FHDR.FCtrl.ADR { + if macPayload.FHDR.FCtrl.ADRACKReq { + mac.FHDR.FCtrl.ACK = true + } + } + + // MAC Commands + for _, cmd := range macPayload.FHDR.FOpts { + switch cmd.CID { + case lorawan.LinkCheckReq: + mac.FHDR.FOpts = append(mac.FHDR.FOpts, lorawan.MACCommand{ + CID: lorawan.LinkCheckAns, + Payload: &lorawan.LinkCheckAnsPayload{ + Margin: uint8(linkMargin(message.GetProtocolMetadata().GetLorawan().DataRate, bestSNR(message.GetGatewayMetadata()))), + GwCnt: uint8(len(message.GatewayMetadata)), + }, + }) + default: + } + } + + phyBytes, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + + message.ResponseTemplate.Payload = phyBytes + + return message, nil +} diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go new file mode 100644 index 000000000..57d105869 --- /dev/null +++ b/core/networkserver/uplink_test.go @@ -0,0 +1,116 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandleUplink(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-uplink"), + } + ns.InitStatus() + + appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devAddr := getDevAddr(1, 2, 3, 4) + + // Device Not Found + message := &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err := ns.HandleUplink(message) + a.So(err, ShouldNotBeNil) + + ns.devices.Set(&device.Device{ + DevAddr: devAddr, + AppEUI: appEUI, + DevEUI: devEUI, + }) + defer func() { + ns.devices.Delete(appEUI, devEUI) + }() + + // Invalid Payload + message = &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err = ns.HandleUplink(message) + a.So(err, ShouldNotBeNil) + + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + FCtrl: lorawan.FCtrl{ + ADR: true, + ADRACKReq: true, + }, + FOpts: []lorawan.MACCommand{ + lorawan.MACCommand{CID: lorawan.LinkCheckReq}, + }, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + // Valid Uplink + message = &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: bytes, + ResponseTemplate: &pb_broker.DownlinkMessage{}, + GatewayMetadata: []*pb_gateway.RxMetadata{ + &pb_gateway.RxMetadata{}, + }, + ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ + Lorawan: &pb_lorawan.Metadata{ + DataRate: "SF7BW125", + }, + }}, + } + res, err := ns.HandleUplink(message) + a.So(err, ShouldBeNil) + a.So(res.ResponseTemplate, ShouldNotBeNil) + + // LoRaWAN: Unmarshal + var phyPayload lorawan.PHYPayload + phyPayload.UnmarshalBinary(res.ResponseTemplate.Payload) + macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + + // ResponseTemplate DevAddr should match + a.So([4]byte(macPayload.FHDR.DevAddr), ShouldEqual, [4]byte(devAddr)) + + // ResponseTemplate should ACK the ADRACKReq + a.So(macPayload.FHDR.FCtrl.ACK, ShouldBeTrue) + a.So(macPayload.FHDR.FOpts, ShouldHaveLength, 1) + a.So(macPayload.FHDR.FOpts[0].Payload, ShouldResemble, &lorawan.LinkCheckAnsPayload{GwCnt: 1, Margin: 7}) + + // Frame Counter should have been updated + dev, _ := ns.devices.Get(appEUI, devEUI) + a.So(dev.FCntUp, ShouldEqual, 1) + a.So(time.Now().Sub(dev.LastSeen), ShouldBeLessThan, 1*time.Second) +} diff --git a/core/proxy/proxy.go b/core/proxy/proxy.go new file mode 100644 index 000000000..867bf2032 --- /dev/null +++ b/core/proxy/proxy.go @@ -0,0 +1,51 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package proxy + +import ( + "net/http" + "strings" + + "github.com/apex/log" +) + +type tokenProxier struct { + handler http.Handler +} + +func (p *tokenProxier) ServeHTTP(res http.ResponseWriter, req *http.Request) { + if authorization := req.Header.Get("authorization"); authorization != "" { + if len(authorization) >= 7 && strings.ToLower(authorization[0:7]) == "bearer " { + req.Header.Set("Grpc-Metadata-Token", authorization[7:]) + } + if len(authorization) >= 4 && strings.ToLower(authorization[0:4]) == "key " { + req.Header.Set("Grpc-Metadata-Key", authorization[4:]) + } + } + p.handler.ServeHTTP(res, req) +} + +// WithToken wraps the handler so that each request gets the Bearer token attached +func WithToken(handler http.Handler) http.Handler { + return &tokenProxier{handler} +} + +type logProxier struct { + ctx log.Interface + handler http.Handler +} + +func (p *logProxier) ServeHTTP(res http.ResponseWriter, req *http.Request) { + p.ctx.WithFields(log.Fields{ + "RemoteAddress": req.RemoteAddr, + "Method": req.Method, + "URI": req.RequestURI, + }).Info("Proxy HTTP request") + p.handler.ServeHTTP(res, req) +} + +// WithLogger wraps the handler so that each request gets logged +func WithLogger(handler http.Handler, ctx log.Interface) http.Handler { + return &logProxier{ctx, handler} +} diff --git a/core/proxy/proxy_test.go b/core/proxy/proxy_test.go new file mode 100644 index 000000000..c74eb2517 --- /dev/null +++ b/core/proxy/proxy_test.go @@ -0,0 +1,62 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package proxy + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +type testHandler struct { + req *http.Request + res http.ResponseWriter +} + +func (h *testHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { + h.res = res + h.req = req +} + +func TestTokenProxier(t *testing.T) { + a := New(t) + + hdl := &testHandler{} + p := WithToken(hdl) + + req := httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldBeEmpty) + + req = httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + req.Header.Add("Authorization", "Key blabla") + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldBeEmpty) + + req = httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + req.Header.Add("Authorization", "bearer token") + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldEqual, "token") + + req = httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + req.Header.Add("Authorization", "Bearer token") + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldEqual, "token") +} + +func TestLogProxier(t *testing.T) { + a := New(t) + + hdl := &testHandler{} + p := WithLogger(hdl, GetLogger(t, "")) + + req := httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req, ShouldNotBeNil) + a.So(hdl.res, ShouldNotBeNil) +} diff --git a/core/router/activation.go b/core/router/activation.go new file mode 100644 index 000000000..dcbb91573 --- /dev/null +++ b/core/router/activation.go @@ -0,0 +1,159 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "sync" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/band" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" +) + +func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { + ctx := r.Ctx.WithFields(log.Fields{ + "GatewayID": gatewayID, + "AppEUI": *activation.AppEui, + "DevEUI": *activation.DevEui, + }) + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle activation") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") + } + }() + r.status.activations.Mark(1) + + gateway := r.getGateway(gatewayID) + gateway.LastSeen = time.Now() + + uplink := &pb.UplinkMessage{ + Payload: activation.Payload, + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + } + + if err = gateway.HandleUplink(uplink); err != nil { + return nil, err + } + + if !gateway.Schedule.IsActive() { + return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayID)) + } + + downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) + + // Find Broker + brokers, err := r.Discovery.GetAll("broker") + if err != nil { + return nil, err + } + + // Prepare request + request := &pb_broker.DeviceActivationRequest{ + Payload: activation.Payload, + DevEui: activation.DevEui, + AppEui: activation.AppEui, + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + ActivationMetadata: &pb_protocol.ActivationMetadata{ + Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + AppEui: activation.AppEui, + DevEui: activation.DevEui, + }, + }, + }, + DownlinkOptions: downlinkOptions, + } + + // Prepare LoRaWAN activation + status, err := gateway.Status.Get() + if err != nil { + return nil, err + } + region := status.Region + if region == "" { + region = band.Guess(uplink.GatewayMetadata.Frequency) + } + band, err := band.Get(region) + if err != nil { + return nil, err + } + lorawan := request.ActivationMetadata.GetLorawan() + lorawan.Rx1DrOffset = 0 + lorawan.Rx2Dr = uint32(band.RX2DataRate) + lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds()) + if band.CFList != nil { + lorawan.CfList = new(pb_lorawan.CFList) + for _, freq := range band.CFList { + lorawan.CfList.Freq = append(lorawan.CfList.Freq, freq) + } + } + + ctx = ctx.WithField("NumBrokers", len(brokers)) + + // Forward to all brokers and collect responses + var wg sync.WaitGroup + responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers)) + for _, broker := range brokers { + broker, err := r.getBroker(broker) + if err != nil { + continue + } + + // Do async request + wg.Add(1) + go func() { + res, err := broker.client.Activate(r.Component.GetContext(""), request) + if err == nil && res != nil { + responses <- res + } + wg.Done() + }() + } + + // Make sure to close channel when all requests are done + go func() { + wg.Wait() + close(responses) + }() + + var gotFirst bool + for res := range responses { + if gotFirst { + ctx.Warn("Duplicate Activation Response") + } else { + gotFirst = true + downlink := &pb_broker.DownlinkMessage{ + Payload: res.Payload, + Message: res.Message, + DownlinkOption: res.DownlinkOption, + } + err := r.HandleDownlink(downlink) + if err != nil { + ctx.Warn("Could not send downlink for Activation") + gotFirst = false // try again + } + } + } + + // Activation not accepted by any broker + if !gotFirst { + ctx.Debug("Activation not accepted at this gateway") + return nil, errors.New("Activation not accepted at this Gateway") + } + + // Activation accepted by (at least one) broker + ctx.Debug("Activation accepted") + return &pb.DeviceActivationResponse{}, nil +} diff --git a/core/router/activation_test.go b/core/router/activation_test.go new file mode 100644 index 000000000..daa755c43 --- /dev/null +++ b/core/router/activation_test.go @@ -0,0 +1,54 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleActivation(t *testing.T) { + a := New(t) + + gtwID := "eui-0102030405060708" + + r := &router{ + Component: &component.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, + gateways: map[string]*gateway.Gateway{ + gtwID: newReferenceGateway(t, "EU_863_870"), + }, + } + r.InitStatus() + + appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} + devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} + + uplink := newReferenceUplink() + activation := &pb.DeviceActivationRequest{ + Payload: []byte{}, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + AppEui: &appEUI, + DevEui: &devEUI, + } + + res, err := r.HandleActivation(gtwID, activation) + a.So(res, ShouldBeNil) + a.So(err, ShouldNotBeNil) + + utilization := r.getGateway(gtwID).Utilization + utilization.Tick() + rx, _ := utilization.Get() + a.So(rx, ShouldBeGreaterThan, 0) + + // TODO: Integration test that checks broker forward +} diff --git a/core/router/downlink.go b/core/router/downlink.go new file mode 100644 index 000000000..aec1e4f81 --- /dev/null +++ b/core/router/downlink.go @@ -0,0 +1,298 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "math" + "strings" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/band" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/apex/log" +) + +func (r *router) SubscribeDownlink(gatewayID string, subscriptionID string) (<-chan *pb.DownlinkMessage, error) { + ctx := r.Ctx.WithFields(log.Fields{ + "GatewayID": gatewayID, + }) + + gateway := r.getGateway(gatewayID) + if fromSchedule := gateway.Schedule.Subscribe(subscriptionID); fromSchedule != nil { + toGateway := make(chan *pb.DownlinkMessage) + go func() { + ctx.Debug("Activate downlink") + for message := range fromSchedule { + gateway.Utilization.AddTx(message) + ctx.Debug("Send downlink") + toGateway <- message + } + ctx.Debug("Deactivate downlink") + close(toGateway) + }() + return toGateway, nil + } + return nil, errors.NewErrInternal(fmt.Sprintf("Already subscribed to downlink for %s", gatewayID)) +} + +func (r *router) UnsubscribeDownlink(gatewayID string, subscriptionID string) error { + r.getGateway(gatewayID).Schedule.Stop(subscriptionID) + return nil +} + +func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { + r.status.downlink.Mark(1) + option := downlink.DownlinkOption + + downlinkMessage := &pb.DownlinkMessage{ + Payload: downlink.Payload, + ProtocolConfiguration: option.ProtocolConfig, + GatewayConfiguration: option.GatewayConfig, + } + + identifier := option.Identifier + if r.Component != nil && r.Component.Identity != nil { + identifier = strings.TrimPrefix(option.Identifier, fmt.Sprintf("%s:", r.Component.Identity.Id)) + } + + return r.getGateway(downlink.DownlinkOption.GatewayId).HandleDownlink(identifier, downlinkMessage) +} + +// buildDownlinkOption builds a DownlinkOption with default values +func (r *router) buildDownlinkOption(gatewayID string, band band.FrequencyPlan) *pb_broker.DownlinkOption { + dataRate, _ := types.ConvertDataRate(band.DataRates[band.RX2DataRate]) + return &pb_broker.DownlinkOption{ + GatewayId: gatewayID, + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + Modulation: pb_lorawan.Modulation_LORA, + DataRate: dataRate.String(), + CodingRate: "4/5", + }}}, + GatewayConfig: &pb_gateway.TxConfiguration{ + RfChain: 0, + PolarizationInversion: true, + Frequency: uint64(band.RX2Frequency), + Power: int32(band.DefaultTXPower), + }, + } +} + +func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway *gateway.Gateway) (downlinkOptions []*pb_broker.DownlinkOption) { + var options []*pb_broker.DownlinkOption + + gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing + + lorawanMetadata := uplink.ProtocolMetadata.GetLorawan() + if lorawanMetadata == nil { + return // We can't handle any other protocols than LoRaWAN yet + } + + region := gatewayStatus.Region + if region == "" { + region = band.Guess(uplink.GatewayMetadata.Frequency) + } + band, err := band.Get(region) + if err != nil { + return // We can't handle this region + } + if region == "EU_863_870" && isActivation { + band.RX2DataRate = 0 + } + + dataRate, err := lorawanMetadata.GetDataRate() + if err != nil { + return + } + + // Configuration for RX2 + buildRX2 := func() (*pb_broker.DownlinkOption, error) { + option := r.buildDownlinkOption(gateway.ID, band) + if region == "EU_863_870" { + option.GatewayConfig.Power = 27 // The EU RX2 frequency allows up to 27dBm + } + if isActivation { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.JoinAcceptDelay2/1000) + } else { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.ReceiveDelay2/1000) + } + option.ProtocolConfig.GetLorawan().CodingRate = lorawanMetadata.CodingRate + return option, nil + } + + if option, err := buildRX2(); err == nil { + options = append(options, option) + } + + // Configuration for RX1 + buildRX1 := func() (*pb_broker.DownlinkOption, error) { + option := r.buildDownlinkOption(gateway.ID, band) + if isActivation { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.JoinAcceptDelay1/1000) + } else { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.ReceiveDelay1/1000) + } + option.ProtocolConfig.GetLorawan().CodingRate = lorawanMetadata.CodingRate + + freq, err := band.GetRX1Frequency(int(uplink.GatewayMetadata.Frequency)) + if err != nil { + return nil, err + } + option.GatewayConfig.Frequency = uint64(freq) + + upDR, err := band.GetDataRate(dataRate) + if err != nil { + return nil, err + } + downDR, err := band.GetRX1DataRate(upDR, 0) + if err != nil { + return nil, err + } + + if err := option.ProtocolConfig.GetLorawan().SetDataRate(band.DataRates[downDR]); err != nil { + return nil, err + } + option.GatewayConfig.FrequencyDeviation = uint32(option.ProtocolConfig.GetLorawan().BitRate / 2) + + return option, nil + } + + if option, err := buildRX1(); err == nil { + options = append(options, option) + } + + computeDownlinkScores(gateway, uplink, options) + + for _, option := range options { + // Add router ID to downlink option + if r.Component != nil && r.Component.Identity != nil { + option.Identifier = fmt.Sprintf("%s:%s", r.Component.Identity.Id, option.Identifier) + } + + // Filter all illegal options + if option.Score < 1000 { + downlinkOptions = append(downlinkOptions, option) + } + } + + return +} + +// Calculating the score for each downlink option; lower is better, 0 is best +// If a score is over 1000, it may should not be used as feasible option. +// TODO: The weights of these parameters should be optimized. I'm sure someone +// can do some computer simulations to find the right values. +func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, options []*pb_broker.DownlinkOption) { + gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing + + region := gatewayStatus.Region + if region == "" { + region = band.Guess(uplink.GatewayMetadata.Frequency) + } + + gatewayRx, _ := gateway.Utilization.Get() + for _, option := range options { + + // Invalid if no LoRaWAN + lorawan := option.GetProtocolConfig().GetLorawan() + if lorawan == nil { + option.Score = 1000 + continue + } + + var time time.Duration + + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + // Calculate max ToA + time, _ = toa.ComputeLoRa( + 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? + lorawan.DataRate, + lorawan.CodingRate, + ) + } + + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + // Calculate max ToA + time, _ = toa.ComputeFSK( + 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? + int(lorawan.BitRate), + ) + } + + // Invalid if time is zero + if time == 0 { + option.Score = 1000 + continue + } + + timeScore := math.Min(time.Seconds()*5, 10) // 2 seconds will be 10 (max) + + signalScore := 0.0 // Between 0 and 20 (lower is better) + { + // Prefer high SNR + if uplink.GatewayMetadata.Snr < 5 { + signalScore += 10 + } + // Prefer good RSSI + signalScore += math.Min(float64(uplink.GatewayMetadata.Rssi*-0.1), 10) + } + + utilizationScore := 0.0 // Between 0 and 40 (lower is better) will be over 100 if forbidden + { + // Avoid gateways that do more Rx + utilizationScore += math.Min(gatewayRx*50, 20) / 2 // 40% utilization = 10 (max) + + // Avoid busy channels + freq := option.GatewayConfig.Frequency + channelRx, channelTx := gateway.Utilization.GetChannel(freq) + utilizationScore += math.Min((channelTx+channelRx)*200, 20) / 2 // 10% utilization = 10 (max) + + // European Duty Cycle + if region == "EU_863_870" { + var duty float64 + switch { + case freq >= 863000000 && freq < 868000000: + duty = 0.01 // g 863.0 – 868.0 MHz 1% + case freq >= 868000000 && freq < 868600000: + duty = 0.01 // g1 868.0 – 868.6 MHz 1% + case freq >= 868700000 && freq < 869200000: + duty = 0.001 // g2 868.7 – 869.2 MHz 0.1% + case freq >= 869400000 && freq < 869650000: + duty = 0.1 // g3 869.4 – 869.65 MHz 10% + case freq >= 869700000 && freq < 870000000: + duty = 0.01 // g4 869.7 – 870.0 MHz 1% + default: + utilizationScore += 100 // Transmissions on this frequency are forbidden + } + if channelTx > duty { + utilizationScore += 100 // Transmissions on this frequency are forbidden + } + if duty > 0 { + utilizationScore += math.Min(time.Seconds()/duty/100, 20) // Impact on duty-cycle (in order to prefer RX2 for SF9BW125) + } + } + } + + scheduleScore := 0.0 // Between 0 and 30 (lower is better) will be over 100 if forbidden + { + id, conflicts := gateway.Schedule.GetOption(option.GatewayConfig.Timestamp, uint32(time/1000)) + option.Identifier = id + if conflicts >= 100 { + scheduleScore += 100 + } else { + scheduleScore += math.Min(float64(conflicts*10), 30) // max 30 + } + } + + option.Score = uint32((timeScore + signalScore + utilizationScore + scheduleScore) * 10) + } +} diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go new file mode 100644 index 000000000..ea55605de --- /dev/null +++ b/core/router/downlink_test.go @@ -0,0 +1,486 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "sync" + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +// newReferenceDownlink returns a default uplink message +func newReferenceDownlink() *pb.DownlinkMessage { + up := &pb.DownlinkMessage{ + Payload: make([]byte, 20), + ProtocolConfiguration: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + CodingRate: "4/5", + DataRate: "SF7BW125", + Modulation: pb_lorawan.Modulation_LORA, + }}}, + GatewayConfiguration: &pb_gateway.TxConfiguration{ + Timestamp: 100, + Frequency: 868100000, + }, + } + return up +} + +func TestHandleDownlink(t *testing.T) { + a := New(t) + + r := &router{ + Component: &component.Component{ + Ctx: GetLogger(t, "TestHandleDownlink"), + }, + gateways: map[string]*gateway.Gateway{}, + } + r.InitStatus() + + gtwID := "eui-0102030405060708" + id, _ := r.getGateway(gtwID).Schedule.GetOption(0, 10*1000) + err := r.HandleDownlink(&pb_broker.DownlinkMessage{ + Payload: []byte{}, + DownlinkOption: &pb_broker.DownlinkOption{ + GatewayId: gtwID, + Identifier: id, + ProtocolConfig: &pb_protocol.TxConfiguration{}, + GatewayConfig: &pb_gateway.TxConfiguration{}, + }, + }) + + a.So(err, ShouldBeNil) +} + +func TestSubscribeUnsubscribeDownlink(t *testing.T) { + a := New(t) + + r := &router{ + Component: &component.Component{ + Ctx: GetLogger(t, "TestSubscribeUnsubscribeDownlink"), + }, + gateways: map[string]*gateway.Gateway{}, + } + r.InitStatus() + + gtwID := "eui-0102030405060708" + gateway.Deadline = 1 * time.Millisecond + gtw := r.getGateway(gtwID) + gtw.Schedule.Sync(0) + id, _ := gtw.Schedule.GetOption(5000, 10*1000) + + ch, err := r.SubscribeDownlink(gtwID, "") + a.So(err, ShouldBeNil) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + var gotDownlink bool + for dl := range ch { + gotDownlink = true + a.So(dl.Payload, ShouldResemble, []byte{0x02}) + } + a.So(gotDownlink, ShouldBeTrue) + wg.Done() + }() + + r.HandleDownlink(&pb_broker.DownlinkMessage{ + Payload: []byte{0x02}, + DownlinkOption: &pb_broker.DownlinkOption{ + GatewayId: gtwID, + Identifier: id, + ProtocolConfig: &pb_protocol.TxConfiguration{}, + GatewayConfig: &pb_gateway.TxConfiguration{}, + }, + }) + + // Wait for the downlink to arrive + <-time.After(10 * time.Millisecond) + + err = r.UnsubscribeDownlink(gtwID, "") + a.So(err, ShouldBeNil) + + wg.Wait() +} + +func TestUplinkBuildDownlinkOptions(t *testing.T) { + a := New(t) + + r := &router{} + + // If something is incorrect, it just returns an empty list + up := &pb.UplinkMessage{} + gtw := gateway.NewGateway(GetLogger(t, "TestUplinkBuildDownlinkOptions"), "eui-0102030405060708") + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldBeEmpty) + + // The reference gateway and uplink work as expected + gtw, up = newReferenceGateway(t, "EU_863_870"), newReferenceUplink() + options = r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].Score, ShouldBeLessThan, options[0].Score) + + // Check Delay + a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 1000100) + a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 2000100) + + // Check Frequency + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 868100000) + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 869525000) + + // Check Power + a.So(options[1].GatewayConfig.Power, ShouldEqual, 14) + a.So(options[0].GatewayConfig.Power, ShouldEqual, 27) + + // Check Data Rate + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW125") + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF9BW125") + + // Check Coding Rate + a.So(options[1].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") + a.So(options[0].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") + + // And for joins we want a different delay (both RX1 and RX2) and DataRate (RX2) + gtw, up = newReferenceGateway(t, "EU_863_870"), newReferenceUplink() + options = r.buildDownlinkOptions(up, true, gtw) + a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 5000100) + a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 6000100) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") +} + +func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { + a := New(t) + + r := &router{} + + // Unsupported frequencies use only RX2 for downlink + gtw, up := newReferenceGateway(t, "EU_863_870"), newReferenceUplink() + up.GatewayMetadata.Frequency = 869300000 + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 1) + + // Supported frequencies use RX1 (on the same frequency) for downlink + ttnEUFrequencies := []uint64{ + 868100000, + 868300000, + 868500000, + 867100000, + 867300000, + 867500000, + 867700000, + 867900000, + } + for _, freq := range ttnEUFrequencies { + up = newReferenceUplink() + up.GatewayMetadata.Frequency = freq + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, freq) + } + + // Unsupported frequencies use only RX2 for downlink + gtw, up = newReferenceGateway(t, "US_902_928"), newReferenceUplink() + up.GatewayMetadata.Frequency = 923300000 + options = r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 1) + + // Supported frequencies use RX1 (on the same frequency) for downlink + ttnUSFrequencies := map[uint64]uint64{ + 903900000: 923300000, + 904100000: 923900000, + 904300000: 924500000, + 904500000: 925100000, + 904700000: 925700000, + 904900000: 926300000, + 905100000: 926900000, + 905300000: 927500000, + } + for upFreq, downFreq := range ttnUSFrequencies { + up = newReferenceUplink() + up.GatewayMetadata.Frequency = upFreq + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, downFreq) + } + + // Unsupported frequencies use only RX2 for downlink + gtw, up = newReferenceGateway(t, "AU_915_928"), newReferenceUplink() + up.GatewayMetadata.Frequency = 923300000 + options = r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 1) + + // Supported frequencies use RX1 (on the same frequency) for downlink + ttnAUFrequencies := map[uint64]uint64{ + 916800000: 923300000, + 917000000: 923900000, + 917200000: 924500000, + 917400000: 925100000, + 917600000: 925700000, + 917800000: 926300000, + 918000000: 926900000, + 918200000: 927500000, + } + for upFreq, downFreq := range ttnAUFrequencies { + up = newReferenceUplink() + up.GatewayMetadata.Frequency = upFreq + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, downFreq) + } +} + +func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { + a := New(t) + + r := &router{} + + gtw := newReferenceGateway(t, "EU_863_870") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnEUDataRates := []string{ + "SF7BW125", + "SF8BW125", + "SF9BW125", + "SF10BW125", + "SF11BW125", + "SF12BW125", + } + for _, dr := range ttnEUDataRates { + up := newReferenceUplink() + up.ProtocolMetadata.GetLorawan().DataRate = dr + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + } + + gtw = newReferenceGateway(t, "US_902_928") + + // Test 500kHz channel + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 904600000 + up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnUSDataRates := map[string]string{ + "SF7BW125": "SF7BW500", + "SF8BW125": "SF8BW500", + "SF9BW125": "SF9BW500", + "SF10BW125": "SF10BW500", + } + for drUp, drDown := range ttnUSDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 903900000 + up.ProtocolMetadata.GetLorawan().DataRate = drUp + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + } + + gtw = newReferenceGateway(t, "AU_915_928") + + // Test 500kHz channel + up = newReferenceUplink() + up.GatewayMetadata.Frequency = 917500000 + up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" + options = r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnAUDataRates := map[string]string{ + "SF7BW125": "SF7BW500", + "SF8BW125": "SF8BW500", + "SF9BW125": "SF9BW500", + "SF10BW125": "SF10BW500", + } + for drUp, drDown := range ttnAUDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 916800000 + up.ProtocolMetadata.GetLorawan().DataRate = drUp + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + } + + gtw = newReferenceGateway(t, "CN_470_510") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnCNDataRates := []string{ + "SF7BW125", + "SF8BW125", + "SF9BW125", + "SF10BW125", + "SF11BW125", + "SF12BW125", + } + for _, dr := range ttnCNDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 470300000 + up.ProtocolMetadata.GetLorawan().DataRate = dr + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 500300000) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 505300000) + } + + gtw = newReferenceGateway(t, "AS_923") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnASDataRates := map[string]string{ + "SF7BW125": "SF7BW125", + "SF8BW125": "SF8BW125", + "SF9BW125": "SF9BW125", + "SF10BW125": "SF10BW125", + "SF11BW125": "SF10BW125", // MinDR = 2 + "SF12BW125": "SF10BW125", // MinDR = 2 + } + for drUp, drDown := range ttnASDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 923200000 + up.ProtocolMetadata.GetLorawan().DataRate = drUp + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 923200000) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF10BW125") + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 923200000) + } + + gtw = newReferenceGateway(t, "KR_920_923") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnKRDataRates := []string{ + "SF7BW125", + "SF8BW125", + "SF9BW125", + "SF10BW125", + "SF11BW125", + "SF12BW125", + } + for _, dr := range ttnKRDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 922100000 + up.ProtocolMetadata.GetLorawan().DataRate = dr + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 922100000) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 921900000) + } + +} + +// Note: This test uses r.buildDownlinkOptions which in turn calls computeDownlinkScores +func TestComputeDownlinkScores(t *testing.T) { + a := New(t) + r := &router{} + gtw := newReferenceGateway(t, "EU_863_870") + refScore := r.buildDownlinkOptions(newReferenceUplink(), false, gtw)[1].Score + + // Lower RSSI -> worse score + testSubject := newReferenceUplink() + testSubject.GatewayMetadata.Rssi = -80.0 + testSubjectgtw := newReferenceGateway(t, "EU_863_870") + testSubjectScore := r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score + a.So(testSubjectScore, ShouldBeGreaterThan, refScore) + + // Lower SNR -> worse score + testSubject = newReferenceUplink() + testSubject.GatewayMetadata.Snr = 2.0 + testSubjectgtw = newReferenceGateway(t, "EU_863_870") + testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score + a.So(testSubjectScore, ShouldBeGreaterThan, refScore) + + // Slower DataRate -> worse score + testSubject = newReferenceUplink() + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF8BW125" + testSubjectgtw = newReferenceGateway(t, "EU_863_870") + testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score + a.So(testSubjectScore, ShouldBeGreaterThan, refScore) + + // Gateway used for Rx -> worse score + testSubject1 := newReferenceUplink() + testSubject2 := newReferenceUplink() + testSubject2.GatewayMetadata.Timestamp = 10000000 + testSubject2.GatewayMetadata.Frequency = 868500000 + testSubjectgtw = newReferenceGateway(t, "EU_863_870") + testSubjectgtw.Utilization.AddRx(newReferenceUplink()) + testSubjectgtw.Utilization.Tick() + testSubject1Score := r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[1].Score + testSubject2Score := r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[1].Score + a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway + a.So(testSubject2Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway + a.So(testSubject1Score, ShouldBeGreaterThan, testSubject2Score) // Because of Rx on the same channel + + // European Alarm Band + // NOTE: This frequency is not part of the TTN DownlinkChannels. This test + // case makes sure we don't allow Tx on the alarm bands even if someone + // changes the frequency plan. + testSubject = newReferenceUplink() + testSubject.GatewayMetadata.Frequency = 869300000 + testSubjectgtw = newReferenceGateway(t, "EU_863_870") + options := r.buildDownlinkOptions(testSubject, false, testSubjectgtw) + a.So(options, ShouldHaveLength, 1) // RX1 Removed + a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 869300000) + + // European Duty-cycle Enforcement + testSubject = newReferenceUplink() + testSubjectgtw = newReferenceGateway(t, "EU_863_870") + for i := 0; i < 5; i++ { + testSubjectgtw.Utilization.AddTx(newReferenceDownlink()) + } + testSubjectgtw.Utilization.Tick() + options = r.buildDownlinkOptions(testSubject, false, testSubjectgtw) + a.So(options, ShouldHaveLength, 1) // RX1 Removed + a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 868100000) + + // European Duty-cycle Preferences - Prefer RX1 for low SF + testSubject = newReferenceUplink() + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF7BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) + a.So(options[1].Score, ShouldBeLessThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF8BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) + a.So(options[1].Score, ShouldBeLessThan, options[0].Score) + + // European Duty-cycle Preferences - Prefer RX2 for high SF + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF9BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF10BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF11BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF12BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + + // Scheduling Conflicts + testSubject1 = newReferenceUplink() + testSubject2 = newReferenceUplink() + testSubject2.GatewayMetadata.Timestamp = 2000000 + testSubjectgtw = newReferenceGateway(t, "EU_863_870") + testSubjectgtw.Schedule.GetOption(1000100, 50000) + testSubject1Score = r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[1].Score + testSubject2Score = r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[1].Score + a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Scheduling conflict with RX1 + a.So(testSubject2Score, ShouldEqual, refScore) // No scheduling conflicts +} diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go new file mode 100644 index 000000000..90257177d --- /dev/null +++ b/core/router/gateway/gateway.go @@ -0,0 +1,105 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "time" + + pb "github.com/TheThingsNetwork/ttn/api/gateway" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" + pb_router "github.com/TheThingsNetwork/ttn/api/router" + "github.com/apex/log" +) + +// NewGateway creates a new in-memory Gateway structure +func NewGateway(ctx log.Interface, id string) *Gateway { + ctx = ctx.WithField("GatewayID", id) + return &Gateway{ + ID: id, + Status: NewStatusStore(), + Utilization: NewUtilization(), + Schedule: NewSchedule(ctx), + Ctx: ctx, + } +} + +// Gateway contains the state of a gateway +type Gateway struct { + ID string + Status StatusStore + Utilization Utilization + Schedule Schedule + LastSeen time.Time + + token string + + Monitors map[string]pb_monitor.GatewayClient + + Ctx log.Interface +} + +func (g *Gateway) SetToken(token string) { + if token == g.token { + return + } + g.token = token + for _, monitor := range g.Monitors { + monitor.SetToken(token) + } +} + +func (g *Gateway) updateLastSeen() { + g.LastSeen = time.Now() +} + +func (g *Gateway) HandleStatus(status *pb.Status) (err error) { + if err = g.Status.Update(status); err != nil { + return err + } + g.updateLastSeen() + + if g.Monitors != nil { + for _, monitor := range g.Monitors { + go monitor.SendStatus(status) + } + } + return nil +} + +func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { + if err = g.Utilization.AddRx(uplink); err != nil { + return err + } + g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) + g.updateLastSeen() + + // Inject Gateway location + if uplink.GatewayMetadata.Gps == nil { + if status, err := g.Status.Get(); err == nil { + uplink.GatewayMetadata.Gps = status.GetGps() + } + } + + if g.Monitors != nil { + for _, monitor := range g.Monitors { + go monitor.SendUplink(uplink) + } + } + return nil +} + +func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.DownlinkMessage) (err error) { + ctx := g.Ctx.WithField("Identifier", identifier) + if err = g.Schedule.Schedule(identifier, downlink); err != nil { + ctx.WithError(err).Warn("Could not schedule downlink") + return err + } + + if g.Monitors != nil { + for _, monitor := range g.Monitors { + go monitor.SendDownlink(downlink) + } + } + return nil +} diff --git a/core/router/gateway/gateway_test.go b/core/router/gateway/gateway_test.go new file mode 100644 index 000000000..8593e2cc2 --- /dev/null +++ b/core/router/gateway/gateway_test.go @@ -0,0 +1,17 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestNewGateway(t *testing.T) { + a := New(t) + gtw := NewGateway(GetLogger(t, "TestNewGateway"), "eui-0102030405060708") + a.So(gtw, ShouldNotBeNil) +} diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go new file mode 100644 index 000000000..faaaa2597 --- /dev/null +++ b/core/router/gateway/schedule.go @@ -0,0 +1,276 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + router_pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/apex/log" +) + +// Schedule is used to schedule downlink transmissions +type Schedule interface { + fmt.GoStringer + // Synchronize the schedule with the gateway timestamp (in microseconds) + Sync(timestamp uint32) + // Get an "option" on a transmission slot at timestamp for the maximum duration of length (both in microseconds) + GetOption(timestamp uint32, length uint32) (id string, score uint) + // Schedule a transmission on a slot + Schedule(id string, downlink *router_pb.DownlinkMessage) error + // Subscribe to downlink messages + Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage + // Whether the gateway has active downlink + IsActive() bool + // Stop the subscription + Stop(subscriptionID string) +} + +// NewSchedule creates a new Schedule +func NewSchedule(ctx log.Interface) Schedule { + s := &schedule{ + ctx: ctx, + items: make(map[string]*scheduledItem), + downlinkSubscriptions: make(map[string]chan *router_pb.DownlinkMessage), + } + go func() { + for { + <-time.After(10 * time.Second) + s.RLock() + numItems := len(s.items) + s.RUnlock() + if numItems > 0 { + s.Lock() + for id, item := range s.items { + // Delete the item if we are more than 2 seconds after the deadline + if time.Now().After(item.deadlineAt.Add(2 * time.Second)) { + delete(s.items, id) + } + } + s.Unlock() + } + } + }() + return s +} + +type scheduledItem struct { + id string + deadlineAt time.Time + timestamp uint32 + length uint32 + score uint + payload *router_pb.DownlinkMessage +} + +type schedule struct { + sync.RWMutex + ctx log.Interface + offset int64 + items map[string]*scheduledItem + downlink chan *router_pb.DownlinkMessage + downlinkSubscriptionsLock sync.RWMutex + downlinkSubscriptions map[string]chan *router_pb.DownlinkMessage +} + +func (s *schedule) GoString() (str string) { + s.RLock() + defer s.RUnlock() + for _, item := range s.items { + str += fmt.Sprintf("%s at %s\n", item.id, item.deadlineAt) + } + return +} + +// Deadline for sending a downlink back to the gateway +// TODO: Make configurable +var Deadline = 400 * time.Millisecond + +const uintmax = 1 << 32 + +// getConflicts walks over the schedule and returns the number of conflicts. +// Both timestamp and length are in microseconds +func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint) { + s.RLock() + defer s.RUnlock() + for _, item := range s.items { + scheduledFrom := uint64(item.timestamp) % uintmax + scheduledTo := scheduledFrom + uint64(item.length) + from := uint64(timestamp) + to := from + uint64(length) + + if scheduledTo > uintmax || to > uintmax { + if scheduledTo-uintmax <= from || scheduledFrom >= to-uintmax { + continue + } + } else if scheduledTo <= from || scheduledFrom >= to { + continue + } + + if item.payload == nil { + conflicts++ + } else { + conflicts += 100 + } + } + return +} + +// realtime gets the synchronized time for a timestamp (in microseconds). Time +// should first be syncronized using func Sync() +func (s *schedule) realtime(timestamp uint32) (t time.Time) { + offset := atomic.LoadInt64(&s.offset) + t = time.Unix(0, 0) + t = t.Add(time.Duration(int64(timestamp)*1000 + offset)) + if t.Before(time.Now()) { + t = t.Add(time.Duration(int64(1<<32) * 1000)) + } + return +} + +// see interface +func (s *schedule) Sync(timestamp uint32) { + atomic.StoreInt64(&s.offset, time.Now().UnixNano()-int64(timestamp)*1000) +} + +// see interface +func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) { + id = random.String(32) + score = s.getConflicts(timestamp, length) + item := &scheduledItem{ + id: id, + deadlineAt: s.realtime(timestamp).Add(-1 * Deadline), + timestamp: timestamp, + length: length, + score: score, + } + s.Lock() + defer s.Unlock() + s.items[id] = item + return id, score +} + +// see interface +func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { + ctx := s.ctx.WithField("Identifier", id) + + s.Lock() + defer s.Unlock() + if item, ok := s.items[id]; ok { + item.payload = downlink + + if lorawan := downlink.GetProtocolConfiguration().GetLorawan(); lorawan != nil { + var time time.Duration + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + // Calculate max ToA + time, _ = toa.ComputeLoRa( + uint(len(downlink.Payload)), + lorawan.DataRate, + lorawan.CodingRate, + ) + } + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + // Calculate max ToA + time, _ = toa.ComputeFSK( + uint(len(downlink.Payload)), + int(lorawan.BitRate), + ) + } + item.length = uint32(time / 1000) + } + + if time.Now().Before(item.deadlineAt) { + // Schedule transmission before the Deadline + go func() { + waitTime := item.deadlineAt.Sub(time.Now()) + ctx.WithField("Remaining", waitTime).Info("Scheduled downlink") + <-time.After(waitTime) + s.RLock() + defer s.RUnlock() + if s.downlink != nil { + s.downlink <- item.payload + } + }() + } else { + go func() { + s.RLock() + defer s.RUnlock() + if s.downlink != nil { + overdue := time.Now().Sub(item.deadlineAt) + if overdue < Deadline { + // Immediately send it + ctx.WithField("Overdue", overdue).Warn("Send Late Downlink") + s.downlink <- item.payload + } else { + ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink") + } + } else { + ctx.Warn("Unable to send Downlink") + } + }() + } + + return nil + } + return errors.NewErrNotFound(id) +} + +func (s *schedule) Stop(subscriptionID string) { + s.downlinkSubscriptionsLock.Lock() + defer s.downlinkSubscriptionsLock.Unlock() + if sub, ok := s.downlinkSubscriptions[subscriptionID]; ok { + close(sub) + delete(s.downlinkSubscriptions, subscriptionID) + } + if len(s.downlinkSubscriptions) == 0 { + s.Lock() + defer s.Unlock() + close(s.downlink) + s.downlink = nil + } +} + +func (s *schedule) Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage { + s.Lock() + if s.downlink == nil { + s.downlink = make(chan *router_pb.DownlinkMessage) + go func() { + for downlink := range s.downlink { + s.downlinkSubscriptionsLock.RLock() + for _, ch := range s.downlinkSubscriptions { + select { + case ch <- downlink: + default: + s.ctx.WithField("SubscriptionID", subscriptionID).Warn("Could not send downlink message") + } + } + s.downlinkSubscriptionsLock.RUnlock() + } + }() + } + s.Unlock() + + s.downlinkSubscriptionsLock.Lock() + if _, ok := s.downlinkSubscriptions[subscriptionID]; ok { + return nil + } + sub := make(chan *router_pb.DownlinkMessage) + s.downlinkSubscriptions[subscriptionID] = sub + s.downlinkSubscriptionsLock.Unlock() + + return sub +} + +func (s *schedule) IsActive() bool { + s.RLock() + defer s.RUnlock() + return s.downlink != nil +} diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go new file mode 100644 index 000000000..908182d1a --- /dev/null +++ b/core/router/gateway/schedule_test.go @@ -0,0 +1,160 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "fmt" + "testing" + "time" + + router_pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +const almostEqual = time.Millisecond + +func TestScheduleSync(t *testing.T) { + a := New(t) + s := &schedule{} + s.Sync(0) + a.So(s.offset, ShouldAlmostEqual, time.Now().UnixNano(), almostEqual) + + s.Sync(1000) + a.So(s.offset, ShouldAlmostEqual, time.Now().UnixNano()-1000*1000, almostEqual) +} + +func TestScheduleRealtime(t *testing.T) { + a := New(t) + s := &schedule{} + s.Sync(0) + tm := s.realtime(10) + a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+10*1000, almostEqual) + + s.Sync(1000) + tm = s.realtime(1010) + a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+10*1000, almostEqual) + + // Don't go back in time when uint32 overflows + s.Sync(uintmax - 1) + tm = s.realtime(10) + a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+9*1000, almostEqual) +} + +func buildItems(items ...*scheduledItem) map[string]*scheduledItem { + m := make(map[string]*scheduledItem) + for idx, item := range items { + m[fmt.Sprintf("%d", idx)] = item + } + return m +} + +func TestScheduleGetConflicts(t *testing.T) { + a := New(t) + + // Test without overflow + s := &schedule{ + items: buildItems( + &scheduledItem{timestamp: 5, length: 15}, + &scheduledItem{timestamp: 25, length: 10}, + &scheduledItem{timestamp: 55, length: 5}, + &scheduledItem{timestamp: 70, length: 20}, + &scheduledItem{timestamp: 95, length: 5}, + &scheduledItem{timestamp: 105, length: 10}, + ), + } + a.So(s.getConflicts(0, 10), ShouldEqual, 1) + a.So(s.getConflicts(15, 15), ShouldEqual, 2) + a.So(s.getConflicts(40, 5), ShouldEqual, 0) + a.So(s.getConflicts(50, 15), ShouldEqual, 1) + a.So(s.getConflicts(75, 5), ShouldEqual, 1) + a.So(s.getConflicts(85, 25), ShouldEqual, 3) + + // Test with overflow (already scheduled) + s = &schedule{ + items: buildItems( + &scheduledItem{timestamp: 1<<32 - 1, length: 20}, + ), + } + a.So(s.getConflicts(0, 20), ShouldEqual, 1) + a.So(s.getConflicts(25, 5), ShouldEqual, 0) + + // Test with overflow (to schedule) + s = &schedule{ + items: buildItems( + &scheduledItem{timestamp: 10, length: 20}, + ), + } + a.So(s.getConflicts(1<<32-1, 5), ShouldEqual, 0) + a.So(s.getConflicts(1<<32-1, 20), ShouldEqual, 1) +} + +func TestScheduleGetOption(t *testing.T) { + a := New(t) + s := NewSchedule(nil).(*schedule) + + s.Sync(0) + _, conflicts := s.GetOption(100, 100) + a.So(conflicts, ShouldEqual, 0) + _, conflicts = s.GetOption(50, 100) + a.So(conflicts, ShouldEqual, 1) +} + +func TestScheduleSchedule(t *testing.T) { + a := New(t) + s := NewSchedule(GetLogger(t, "TestScheduleSchedule")).(*schedule) + + s.Sync(0) + + err := s.Schedule("random", &router_pb.DownlinkMessage{}) + a.So(err, ShouldNotBeNil) + + id, conflicts := s.GetOption(100, 100) + err = s.Schedule(id, &router_pb.DownlinkMessage{}) + a.So(err, ShouldBeNil) + + _, conflicts = s.GetOption(50, 100) + a.So(conflicts, ShouldEqual, 100) +} + +func TestScheduleSubscribe(t *testing.T) { + a := New(t) + s := NewSchedule(GetLogger(t, "TestScheduleSubscribe")).(*schedule) + s.Sync(0) + Deadline = 1 * time.Millisecond // Very short deadline + + downlink1 := &router_pb.DownlinkMessage{Payload: []byte{1}} + downlink2 := &router_pb.DownlinkMessage{Payload: []byte{2}} + downlink3 := &router_pb.DownlinkMessage{Payload: []byte{3}} + + go func() { + var i int + for out := range s.Subscribe("") { + switch i { + case 0: + a.So(out, ShouldEqual, downlink2) + case 1: + a.So(out, ShouldEqual, downlink1) + case 3: + a.So(out, ShouldEqual, downlink3) + } + i++ + } + }() + + id, _ := s.GetOption(30000, 50) + s.Schedule(id, downlink1) + id, _ = s.GetOption(20000, 50) + s.Schedule(id, downlink2) + id, _ = s.GetOption(40000, 50) + s.Schedule(id, downlink3) + + go func() { + <-time.After(400 * time.Millisecond) + s.Stop("") + }() + + <-time.After(500 * time.Millisecond) + +} diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go new file mode 100644 index 000000000..fa9e4796e --- /dev/null +++ b/core/router/gateway/status.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "sync" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" +) + +// StatusStore is a database for setting and retrieving the latest gateway status +type StatusStore interface { + // Insert or Update the status + Update(status *pb_gateway.Status) error + // Get the last status + Get() (*pb_gateway.Status, error) +} + +// NewStatusStore creates a new in-memory status store +func NewStatusStore() StatusStore { + return &statusStore{} +} + +type statusStore struct { + sync.RWMutex + lastStatus *pb_gateway.Status +} + +func (s *statusStore) Update(status *pb_gateway.Status) error { + s.Lock() + defer s.Unlock() + s.lastStatus = status + return nil +} + +func (s *statusStore) Get() (*pb_gateway.Status, error) { + s.RLock() + defer s.RUnlock() + if s.lastStatus != nil { + return s.lastStatus, nil + } + return &pb_gateway.Status{}, nil +} diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go new file mode 100644 index 000000000..57a71faeb --- /dev/null +++ b/core/router/gateway/status_test.go @@ -0,0 +1,39 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "testing" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + . "github.com/smartystreets/assertions" +) + +func TestNewGatewayStatusStore(t *testing.T) { + a := New(t) + store := NewStatusStore() + a.So(store, ShouldNotBeNil) +} + +func TestStatusGetUpsert(t *testing.T) { + a := New(t) + store := NewStatusStore() + + // Get non-existing gateway status -> expect empty + status, err := store.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, pb_gateway.Status{}) + + // Update -> expect no error + statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} + err = store.Update(statusMessage) + a.So(err, ShouldBeNil) + + // Get existing gateway status -> expect status + status, err = store.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} diff --git a/core/router/gateway/utilization.go b/core/router/gateway/utilization.go new file mode 100644 index 000000000..88bcd8e7c --- /dev/null +++ b/core/router/gateway/utilization.go @@ -0,0 +1,159 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "fmt" + "sync" + "time" + + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb_router "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/rcrowley/go-metrics" +) + +// Utilization manages the utilization of a gateway and its channels +// It is based on an exponentially weighted moving average over one minute +type Utilization interface { + fmt.GoStringer + // AddRx updates the utilization for receiving an uplink message + AddRx(uplink *pb_router.UplinkMessage) error + // AddRx updates the utilization for transmitting a downlink message + AddTx(downlink *pb_router.DownlinkMessage) error + // Get returns the overall rx and tx utilization for the gateway. If the gateway has multiple channels, the values will be 0 <= value < numChannels + Get() (rx float64, tx float64) + // GetChannel returns the rx and tx utilization for the given channel. The values will be 0 <= value < 1 + GetChannel(frequency uint64) (rx float64, tx float64) + // Tick the clock to update the moving average. It should be called every 5 seconds + Tick() +} + +// NewUtilization creates a new Utilization +func NewUtilization() Utilization { + return &utilization{ + overallRx: metrics.NewEWMA1(), + channelRx: map[uint64]metrics.EWMA{}, + overallTx: metrics.NewEWMA1(), + channelTx: map[uint64]metrics.EWMA{}, + } +} + +type utilization struct { + overallRx metrics.EWMA + channelRx map[uint64]metrics.EWMA + channelRxLock sync.RWMutex + overallTx metrics.EWMA + channelTx map[uint64]metrics.EWMA + channelTxLock sync.RWMutex +} + +func (u *utilization) GoString() (str string) { + str += fmt.Sprintf("Rx %5.2f ", u.overallRx.Rate()/1000) + for ch, r := range u.channelRx { + str += fmt.Sprintf("(%d:%5.2f) ", ch, r.Rate()/1000) + } + str += "\n" + str += fmt.Sprintf("Tx %5.2f ", u.overallTx.Rate()/1000) + for ch, r := range u.channelTx { + str += fmt.Sprintf("(%d:%5.2f) ", ch, r.Rate()/1000) + } + str += "\n" + return +} + +func (u *utilization) AddRx(uplink *pb_router.UplinkMessage) error { + var t time.Duration + var err error + if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + t, err = toa.ComputeLoRa(uint(len(uplink.Payload)), lorawan.DataRate, lorawan.CodingRate) + if err != nil { + return err + } + } + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + t, err = toa.ComputeFSK(uint(len(uplink.Payload)), int(lorawan.BitRate)) + if err != nil { + return err + } + } + } + if t == 0 { + return nil + } + u.overallRx.Update(int64(t) / 1000) + frequency := uplink.GatewayMetadata.Frequency + u.channelRxLock.Lock() + defer u.channelRxLock.Unlock() + if _, ok := u.channelRx[frequency]; !ok { + u.channelRx[frequency] = metrics.NewEWMA1() + } + u.channelRx[frequency].Update(int64(t) / 1000) + return nil +} + +func (u *utilization) AddTx(downlink *pb_router.DownlinkMessage) error { + var t time.Duration + var err error + if lorawan := downlink.ProtocolConfiguration.GetLorawan(); lorawan != nil { + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + t, err = toa.ComputeLoRa(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) + if err != nil { + return err + } + } + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + t, err = toa.ComputeFSK(uint(len(downlink.Payload)), int(lorawan.BitRate)) + if err != nil { + return err + } + } + } + if t == 0 { + return nil + } + u.overallTx.Update(int64(t) / 1000) + frequency := downlink.GatewayConfiguration.Frequency + u.channelTxLock.Lock() + defer u.channelTxLock.Unlock() + if _, ok := u.channelTx[frequency]; !ok { + u.channelTx[frequency] = metrics.NewEWMA1() + } + u.channelTx[frequency].Update(int64(t) / 1000) + return nil +} + +func (u *utilization) Tick() { + u.overallRx.Tick() + u.channelRxLock.RLock() + for _, ch := range u.channelRx { + ch.Tick() + } + u.channelRxLock.RUnlock() + u.overallTx.Tick() + u.channelTxLock.RLock() + for _, ch := range u.channelTx { + ch.Tick() + } + u.channelTxLock.RUnlock() +} + +func (u *utilization) Get() (float64, float64) { + return u.overallRx.Snapshot().Rate() * 1000.0 / float64(time.Second), u.overallTx.Snapshot().Rate() * 1000.0 / float64(time.Second) +} + +func (u *utilization) GetChannel(frequency uint64) (rx float64, tx float64) { + u.channelRxLock.RLock() + if channel, ok := u.channelRx[frequency]; ok { + rx = channel.Snapshot().Rate() * 1000.0 / float64(time.Second) + } + u.channelRxLock.RUnlock() + u.channelTxLock.RLock() + if channel, ok := u.channelTx[frequency]; ok { + tx = channel.Snapshot().Rate() * 1000.0 / float64(time.Second) + } + u.channelTxLock.RUnlock() + return +} diff --git a/core/router/gateway/utilization_test.go b/core/router/gateway/utilization_test.go new file mode 100644 index 000000000..fae29de5c --- /dev/null +++ b/core/router/gateway/utilization_test.go @@ -0,0 +1,95 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/smartystreets/assertions" +) + +func buildUplink(freq uint64) *pb.UplinkMessage { + return &pb.UplinkMessage{Payload: make([]byte, 10), ProtocolMetadata: &pb_protocol.RxMetadata{ + Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{ + DataRate: "SF7BW125", + CodingRate: "4/5", + }}, + }, GatewayMetadata: &gateway.RxMetadata{ + Frequency: freq, + }} +} + +func buildDownlink(freq uint64) *pb.DownlinkMessage { + return &pb.DownlinkMessage{Payload: make([]byte, 10), ProtocolConfiguration: &pb_protocol.TxConfiguration{ + Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + DataRate: "SF7BW125", + CodingRate: "4/5", + }}, + }, GatewayConfiguration: &gateway.TxConfiguration{ + Frequency: freq, + }} +} + +func TestRxUtilization(t *testing.T) { + a := New(t) + u := NewUtilization() + err := u.AddRx(buildUplink(8680000000)) + a.So(err, ShouldBeNil) + err = u.AddRx(buildUplink(8682000000)) + a.So(err, ShouldBeNil) + + rx, tx := u.Get() + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0) + + u.Tick() // 5 seconds later + + rx, tx = u.GetChannel(8680000000) + a.So(rx, ShouldAlmostEqual, 0.041216/5.0) // 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) + + rx, tx = u.Get() + a.So(rx, ShouldAlmostEqual, 0.082432/5.0) // two times 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) + + u.AddRx(buildUplink(8680000000)) + u.AddRx(buildUplink(8682000000)) + + u.Tick() // 5 seconds later + + rx, tx = u.GetChannel(8680000000) + a.So(rx, ShouldAlmostEqual, 0.041216/5.0) // still 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) + + rx, tx = u.Get() + a.So(rx, ShouldAlmostEqual, 0.082432/5.0) // still two times 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) +} + +func TestTxUtilization(t *testing.T) { + a := New(t) + u := NewUtilization() + err := u.AddTx(buildDownlink(8680000000)) + a.So(err, ShouldBeNil) + err = u.AddTx(buildDownlink(8682000000)) + a.So(err, ShouldBeNil) + + rx, tx := u.Get() + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0) + + u.Tick() // 5 seconds later + + rx, tx = u.GetChannel(8680000000) + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0.041216/5.0) // 41 ms per second + + rx, tx = u.Get() + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0.082432/5.0) // two times 41 ms per second +} diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go new file mode 100644 index 000000000..fbe38506e --- /dev/null +++ b/core/router/gateway_status.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "time" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" +) + +func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) (err error) { + ctx := r.Ctx.WithField("GatewayID", gatewayID) + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle gateway status") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled gateway status") + } + }() + r.status.gatewayStatus.Mark(1) + status.Router = r.Identity.Id + return r.getGateway(gatewayID).HandleStatus(status) +} diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go new file mode 100644 index 000000000..0e3128a40 --- /dev/null +++ b/core/router/gateway_status_test.go @@ -0,0 +1,40 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleGatewayStatus(t *testing.T) { + a := New(t) + gtwID := "eui-0102030405060708" + + router := &router{ + Component: &component.Component{ + Ctx: GetLogger(t, "TestHandleGatewayStatus"), + Identity: &pb_discovery.Announcement{}, + }, + gateways: map[string]*gateway.Gateway{}, + } + router.InitStatus() + + // Handle + statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} + err := router.HandleGatewayStatus(gtwID, statusMessage) + a.So(err, ShouldBeNil) + + // Check storage + status, err := router.getGateway(gtwID).Status.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} diff --git a/core/router/manager_server.go b/core/router/manager_server.go new file mode 100644 index 000000000..8b5e8bf4b --- /dev/null +++ b/core/router/manager_server.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" +) + +type routerManager struct { + router *router +} + +func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusRequest) (*pb.GatewayStatusResponse, error) { + if in.GatewayId == "" { + return nil, errors.NewErrInvalidArgument("Gateway Status Request", "ID is required") + } + _, err := r.router.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, errors.NewErrPermissionDenied("No access") + } + r.router.gatewaysLock.RLock() + gtw, ok := r.router.gateways[in.GatewayId] + r.router.gatewaysLock.RUnlock() + if !ok { + return nil, errors.NewErrNotFound(fmt.Sprintf("Gateway %s", in.GatewayId)) + } + status, err := gtw.Status.Get() + if err != nil { + return nil, err + } + return &pb.GatewayStatusResponse{ + LastSeen: gtw.LastSeen.UnixNano(), + Status: status, + }, nil +} + +func (r *routerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + if r.router.Identity.Id != "dev" { + claims, err := r.router.ValidateTTNAuthContext(ctx) + if err != nil || !claims.ComponentAccess(r.router.Identity.Id) { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := r.router.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil +} + +// RegisterManager registers this router as a RouterManagerServer (github.com/TheThingsNetwork/ttn/api/router) +func (r *router) RegisterManager(s *grpc.Server) { + server := &routerManager{r} + pb.RegisterRouterManagerServer(s, server) +} diff --git a/core/router/router.go b/core/router/router.go new file mode 100644 index 000000000..ffed69a87 --- /dev/null +++ b/core/router/router.go @@ -0,0 +1,187 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "sync" + "time" + + "google.golang.org/grpc" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "golang.org/x/net/context" +) + +// Router component +type Router interface { + component.Interface + component.ManagementInterface + + // Handle a status message from a gateway + HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) error + // Handle an uplink message from a gateway + HandleUplink(gatewayID string, uplink *pb.UplinkMessage) error + // Handle a downlink message + HandleDownlink(message *pb_broker.DownlinkMessage) error + // Subscribe to downlink messages + SubscribeDownlink(gatewayID string, subscriptionID string) (<-chan *pb.DownlinkMessage, error) + // Unsubscribe from downlink messages + UnsubscribeDownlink(gatewayID string, subscriptionID string) error + // Handle a device activation + HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) + + getGateway(gatewayID string) *gateway.Gateway +} + +type broker struct { + conn *grpc.ClientConn + association pb_broker.RouterStream + client pb_broker.BrokerClient + uplink chan *pb_broker.UplinkMessage + downlink chan *pb_broker.DownlinkMessage +} + +// NewRouter creates a new Router +func NewRouter() Router { + return &router{ + gateways: make(map[string]*gateway.Gateway), + brokers: make(map[string]*broker), + } +} + +type router struct { + *component.Component + gateways map[string]*gateway.Gateway + gatewaysLock sync.RWMutex + brokers map[string]*broker + brokersLock sync.RWMutex + status *status +} + +func (r *router) tickGateways() { + r.gatewaysLock.RLock() + defer r.gatewaysLock.RUnlock() + for _, gtw := range r.gateways { + gtw.Utilization.Tick() + } +} + +func (r *router) Init(c *component.Component) error { + r.Component = c + r.InitStatus() + err := r.Component.UpdateTokenKey() + if err != nil { + return err + } + err = r.Component.Announce() + if err != nil { + return err + } + r.Discovery.GetAll("broker") // Update cache + + go func() { + for range time.Tick(5 * time.Second) { + r.tickGateways() + } + }() + r.Component.SetStatus(component.StatusHealthy) + return nil +} + +func (r *router) Shutdown() { + r.brokersLock.Lock() + defer r.brokersLock.Unlock() + for _, broker := range r.brokers { + broker.association.Close() + broker.conn.Close() + } +} + +// getGateway gets or creates a Gateway +func (r *router) getGateway(id string) *gateway.Gateway { + // We're going to be optimistic and guess that the gateway is already active + r.gatewaysLock.RLock() + gtw, ok := r.gateways[id] + r.gatewaysLock.RUnlock() + if ok { + return gtw + } + // If it doesn't we still have to lock + r.gatewaysLock.Lock() + defer r.gatewaysLock.Unlock() + + gtw, ok = r.gateways[id] + if !ok { + gtw = gateway.NewGateway(r.Ctx, id) + + if r.Component.Monitors != nil { + gtw.Monitors = make(map[string]pb_monitor.GatewayClient) + for name, cl := range r.Component.Monitors { + gtw.Monitors[name] = cl.GatewayClient(gtw.ID) + } + } + + r.gateways[id] = gtw + } + + return gtw +} + +// getBroker gets or creates a broker association and returns the broker +// the first time it also starts a goroutine that receives downlink from the broker +func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*broker, error) { + // We're going to be optimistic and guess that the broker is already active + r.brokersLock.RLock() + brk, ok := r.brokers[brokerAnnouncement.Id] + r.brokersLock.RUnlock() + if ok { + return brk, nil + } + + // If it doesn't we still have to lock + r.brokersLock.Lock() + defer r.brokersLock.Unlock() + if _, ok := r.brokers[brokerAnnouncement.Id]; !ok { + + // Connect to the server + // TODO(htdvisser): This is blocking + conn, err := brokerAnnouncement.Dial() + if err != nil { + return nil, err + } + client := pb_broker.NewBrokerClient(conn) + + association := pb_broker.NewMonitoredRouterStream(client, func() context.Context { + return r.GetContext("") + }) + downlink := association.Channel() + + brk := &broker{ + conn: conn, + association: association, + client: client, + uplink: make(chan *pb_broker.UplinkMessage), + } + + go func() { + for { + select { + case message := <-brk.uplink: + association.Send(message) + case message := <-downlink: + go r.HandleDownlink(message) + } + } + }() + + r.brokers[brokerAnnouncement.Id] = brk + } + return r.brokers[brokerAnnouncement.Id], nil +} diff --git a/core/router/router_test.go b/core/router/router_test.go new file mode 100644 index 000000000..65e8c5a6b --- /dev/null +++ b/core/router/router_test.go @@ -0,0 +1,10 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import "testing" + +func TestRouterIntegration(t *testing.T) { + +} diff --git a/core/router/server.go b/core/router/server.go new file mode 100644 index 000000000..8a3ff3f0d --- /dev/null +++ b/core/router/server.go @@ -0,0 +1,160 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/api" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/ratelimit" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/spf13/viper" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +type routerRPC struct { + router *router + pb.RouterStreamServer + + uplinkRate *ratelimit.Registry + statusRate *ratelimit.Registry +} + +func (r *routerRPC) gatewayFromMetadata(md metadata.MD) (gtw *gateway.Gateway, err error) { + gatewayID, err := api.IDFromMetadata(md) + if err != nil { + return nil, err + } + + token, _ := api.TokenFromMetadata(md) + + if !viper.GetBool("router.skip-verify-gateway-token") { + if token == "" { + return nil, errors.NewErrPermissionDenied("No gateway token supplied") + } + if r.router.TokenKeyProvider == nil { + return nil, errors.NewErrInternal("No token provider configured") + } + claims, err := claims.FromToken(r.router.TokenKeyProvider, token) + if err != nil { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("Gateway token invalid: %s", err.Error())) + } + if claims.Type != "gateway" || claims.Subject != gatewayID { + return nil, errors.NewErrPermissionDenied("Gateway token not consistent") + } + } + + gtw = r.router.getGateway(gatewayID) + gtw.SetToken(token) + + return gtw, nil +} + +func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gateway, err error) { + md, err := api.MetadataFromContext(ctx) + if err != nil { + return nil, err + } + return r.gatewayFromMetadata(md) +} + +func (r *routerRPC) getUplink(md metadata.MD) (ch chan *pb.UplinkMessage, err error) { + gateway, err := r.gatewayFromMetadata(md) + if err != nil { + return nil, err + } + ch = make(chan *pb.UplinkMessage) + go func() { + for uplink := range ch { + if waitTime := r.uplinkRate.Wait(gateway.ID); waitTime != 0 { + r.router.Ctx.WithField("GatewayID", gateway.ID).WithField("Wait", waitTime).Warn("Gateway reached uplink rate limit") + time.Sleep(waitTime) + } + r.router.HandleUplink(gateway.ID, uplink) + + } + }() + return +} + +func (r *routerRPC) getGatewayStatus(md metadata.MD) (ch chan *pb_gateway.Status, err error) { + gateway, err := r.gatewayFromMetadata(md) + if err != nil { + return nil, err + } + ch = make(chan *pb_gateway.Status) + go func() { + for status := range ch { + if waitTime := r.statusRate.Wait(gateway.ID); waitTime != 0 { + r.router.Ctx.WithField("GatewayID", gateway.ID).WithField("Wait", waitTime).Warn("Gateway reached status rate limit") + time.Sleep(waitTime) + } + r.router.HandleGatewayStatus(gateway.ID, status) + } + }() + return +} + +func (r *routerRPC) getDownlink(md metadata.MD) (ch <-chan *pb.DownlinkMessage, cancel func(), err error) { + gateway, err := r.gatewayFromMetadata(md) + if err != nil { + return nil, nil, err + } + subscriptionID := random.String(10) + ch = make(chan *pb.DownlinkMessage) + cancel = func() { + r.router.UnsubscribeDownlink(gateway.ID, subscriptionID) + } + downlinkChannel, err := r.router.SubscribeDownlink(gateway.ID, subscriptionID) + if err != nil { + return nil, nil, err + } + return downlinkChannel, cancel, nil +} + +// Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) +func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + gateway, err := r.gatewayFromContext(ctx) + if err != nil { + return nil, err + } + if err := req.Validate(); err != nil { + return nil, errors.Wrap(err, "Invalid Activation Request") + } + if r.uplinkRate.Limit(gateway.ID) { + return nil, grpc.Errorf(codes.ResourceExhausted, "Gateway reached uplink rate limit") + } + return r.router.HandleActivation(gateway.ID, req) +} + +// RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) +func (r *router) RegisterRPC(s *grpc.Server) { + server := &routerRPC{router: r} + server.SetLogger(apex.Wrap(r.Ctx)) + server.UplinkChanFunc = server.getUplink + server.DownlinkChanFunc = server.getDownlink + server.GatewayStatusChanFunc = server.getGatewayStatus + + // TODO: Monitor actual rates and configure sensible limits + // + // The current values are based on the following: + // - 20 byte messages on all 6 orthogonal SFs at the same time -> ~1500 msgs/minute + // - 8 channels at 5% utilization: 600 msgs/minute + // - let's double that and round it to 1500/minute + + server.uplinkRate = ratelimit.NewRegistry(1500, time.Minute) // includes activations + server.statusRate = ratelimit.NewRegistry(10, time.Minute) // 10 per minute (pkt fwd default is 2 per minute) + + pb.RegisterRouterServer(s, server) +} diff --git a/core/router/status.go b/core/router/status.go new file mode 100644 index 000000000..116461d65 --- /dev/null +++ b/core/router/status.go @@ -0,0 +1,75 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter + gatewayStatus metrics.Meter + connectedGateways metrics.Gauge + connectedBrokers metrics.Gauge +} + +func (r *router) InitStatus() { + r.status = &status{ + uplink: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + gatewayStatus: metrics.NewMeter(), + connectedGateways: metrics.NewFunctionalGauge(func() int64 { + r.gatewaysLock.RLock() + defer r.gatewaysLock.RUnlock() + return int64(len(r.gateways)) + }), + connectedBrokers: metrics.NewFunctionalGauge(func() int64 { + r.brokersLock.RLock() + defer r.brokersLock.RUnlock() + return int64(len(r.brokers)) + }), + } +} + +func (r *router) GetStatus() *pb.Status { + status := new(pb.Status) + if r.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := r.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + downlink := r.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := r.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + gatewayStatus := r.status.gatewayStatus.Snapshot() + status.GatewayStatus = &api.Rates{ + Rate1: float32(gatewayStatus.Rate1()), + Rate5: float32(gatewayStatus.Rate5()), + Rate15: float32(gatewayStatus.Rate15()), + } + status.ConnectedGateways = uint32(r.status.connectedGateways.Snapshot().Value()) + status.ConnectedBrokers = uint32(r.status.connectedBrokers.Snapshot().Value()) + return status +} diff --git a/core/router/status_test.go b/core/router/status_test.go new file mode 100644 index 000000000..59a7c094e --- /dev/null +++ b/core/router/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + r := new(router) + a.So(r.GetStatus(), ShouldResemble, new(pb.Status)) + r.InitStatus() + a.So(r.status, ShouldNotBeNil) + status := r.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/router/uplink.go b/core/router/uplink.go new file mode 100644 index 000000000..2223e7f74 --- /dev/null +++ b/core/router/uplink.go @@ -0,0 +1,127 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/brocaar/lorawan" +) + +func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err error) { + ctx := r.Ctx.WithField("GatewayID", gatewayID) + start := time.Now() + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle uplink") + } + }() + r.status.uplink.Mark(1) + + // LoRaWAN: Unmarshal + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(uplink.Payload) + if err != nil { + return err + } + + if phyPayload.MHDR.MType == lorawan.JoinRequest { + joinRequestPayload, ok := phyPayload.MACPayload.(*lorawan.JoinRequestPayload) + if !ok { + return errors.NewErrInvalidArgument("Join Request", "does not contain a JoinRequest payload") + } + devEUI := types.DevEUI(joinRequestPayload.DevEUI) + appEUI := types.AppEUI(joinRequestPayload.AppEUI) + ctx.WithFields(log.Fields{ + "DevEUI": devEUI, + "AppEUI": appEUI, + }).Debug("Handle Uplink as Activation") + r.HandleActivation(gatewayID, &pb.DeviceActivationRequest{ + Payload: uplink.Payload, + DevEui: &devEUI, + AppEui: &appEUI, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + }) + return nil + } + + if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { + ctx = ctx.WithField("Modulation", lorawan.Modulation.String()) + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + ctx = ctx.WithField("DataRate", lorawan.DataRate) + } else { + ctx = ctx.WithField("BitRate", lorawan.BitRate) + } + } + + if gateway := uplink.GatewayMetadata; gateway != nil { + ctx = ctx.WithFields(log.Fields{ + "Frequency": gateway.Frequency, + "RSSI": gateway.Rssi, + "SNR": gateway.Snr, + }) + } + + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + } + devAddr := types.DevAddr(macPayload.FHDR.DevAddr) + + ctx = ctx.WithFields(log.Fields{ + "DevAddr": devAddr, + "FCnt": macPayload.FHDR.FCnt, + }) + + gateway := r.getGateway(gatewayID) + + if err = gateway.HandleUplink(uplink); err != nil { + return err + } + + var downlinkOptions []*pb_broker.DownlinkOption + if gateway.Schedule.IsActive() { + downlinkOptions = r.buildDownlinkOptions(uplink, false, gateway) + } + + ctx = ctx.WithField("DownlinkOptions", len(downlinkOptions)) + + // Find Broker + brokers, err := r.Discovery.GetAllBrokersForDevAddr(devAddr) + if err != nil { + return err + } + + if len(brokers) == 0 { + ctx.Debug("No brokers to forward message to") + return nil + } + + ctx = ctx.WithField("NumBrokers", len(brokers)) + + // Forward to all brokers + for _, broker := range brokers { + broker, err := r.getBroker(broker) + if err != nil { + continue + } + broker.uplink <- &pb_broker.UplinkMessage{ + Payload: uplink.Payload, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + DownlinkOptions: downlinkOptions, + } + } + + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") + + return nil +} diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go new file mode 100644 index 000000000..34a12956c --- /dev/null +++ b/core/router/uplink_test.go @@ -0,0 +1,82 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/discovery" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +// newReferenceGateway returns a default gateway +func newReferenceGateway(t *testing.T, region string) *gateway.Gateway { + gtw := gateway.NewGateway(GetLogger(t, "ReferenceGateway"), "eui-0102030405060708") + gtw.Status.Update(&pb_gateway.Status{ + Region: region, + }) + return gtw +} + +// newReferenceUplink returns a default uplink message +func newReferenceUplink() *pb.UplinkMessage { + gtwID := "eui-0102030405060708" + + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + }, + }, + } + bytes, _ := phy.MarshalBinary() + + up := &pb.UplinkMessage{ + Payload: bytes, + ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{ + CodingRate: "4/5", + DataRate: "SF7BW125", + Modulation: pb_lorawan.Modulation_LORA, + }}}, + GatewayMetadata: &pb_gateway.RxMetadata{ + GatewayId: gtwID, + Timestamp: 100, + Frequency: 868100000, + Rssi: -25.0, + Snr: 5.0, + }, + } + return up +} + +func TestHandleUplink(t *testing.T) { + a := New(t) + + r := getTestRouter(t) + r.discovery.EXPECT().GetAllBrokersForDevAddr(types.DevAddr([4]byte{1, 2, 3, 4})).Return([]*discovery.Announcement{}, nil) + + uplink := newReferenceUplink() + gtwID := "eui-0102030405060708" + + err := r.HandleUplink(gtwID, uplink) + a.So(err, ShouldBeNil) + utilization := r.getGateway(gtwID).Utilization + utilization.Tick() + rx, _ := utilization.Get() + a.So(rx, ShouldBeGreaterThan, 0) + + // TODO: Integration test that checks broker forward +} diff --git a/core/router/util_test.go b/core/router/util_test.go new file mode 100644 index 000000000..efe061e45 --- /dev/null +++ b/core/router/util_test.go @@ -0,0 +1,38 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/component" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" +) + +type testRouter struct { + *router + ctrl *gomock.Controller + discovery *discovery.MockClient +} + +func getTestRouter(t *testing.T) *testRouter { + ctrl := gomock.NewController(t) + discovery := discovery.NewMockClient(ctrl) + r := &testRouter{ + router: &router{ + Component: &component.Component{ + Discovery: discovery, + Ctx: GetLogger(t, "TestRouter"), + }, + gateways: map[string]*gateway.Gateway{}, + }, + ctrl: ctrl, + discovery: discovery, + } + r.InitStatus() + return r +} diff --git a/core/storage/conversions.go b/core/storage/conversions.go new file mode 100644 index 000000000..9c0f742fc --- /dev/null +++ b/core/storage/conversions.go @@ -0,0 +1,93 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding/hex" + "fmt" + "strconv" +) + +// FormatFloat32 does what its name suggests +func FormatFloat32(value float32) string { + return strconv.FormatFloat(float64(value), 'f', -1, 32) +} + +// FormatFloat64 does what its name suggests +func FormatFloat64(value float64) string { + return strconv.FormatFloat(value, 'f', -1, 64) +} + +// FormatInt32 does what its name suggests +func FormatInt32(value int32) string { + return FormatInt64(int64(value)) +} + +// FormatInt64 does what its name suggests +func FormatInt64(value int64) string { + return strconv.FormatInt(value, 10) +} + +// FormatUint32 does what its name suggests +func FormatUint32(value uint32) string { + return FormatUint64(uint64(value)) +} + +// FormatUint64 does what its name suggests +func FormatUint64(value uint64) string { + return strconv.FormatUint(value, 10) +} + +// FormatBool does what its name suggests +func FormatBool(value bool) string { + return strconv.FormatBool(value) +} + +// FormatBytes does what its name suggests +func FormatBytes(value []byte) string { + return fmt.Sprintf("%X", value) +} + +// ParseFloat32 does what its name suggests +func ParseFloat32(val string) (float32, error) { + res, err := strconv.ParseFloat(val, 32) + return float32(res), err +} + +// ParseFloat64 does what its name suggests +func ParseFloat64(val string) (float64, error) { + return strconv.ParseFloat(val, 64) +} + +// ParseInt32 does what its name suggests +func ParseInt32(val string) (int32, error) { + res, err := strconv.ParseInt(val, 10, 32) + return int32(res), err +} + +// ParseInt64 does what its name suggests +func ParseInt64(val string) (int64, error) { + return strconv.ParseInt(val, 10, 64) +} + +// ParseUint32 does what its name suggests +func ParseUint32(val string) (uint32, error) { + res, err := strconv.ParseUint(val, 10, 32) + return uint32(res), err +} + +// ParseUint64 does what its name suggests +func ParseUint64(val string) (uint64, error) { + return strconv.ParseUint(val, 10, 64) +} + +// ParseBool does what its name suggests +func ParseBool(val string) (bool, error) { + return strconv.ParseBool(val) +} + +// ParseBytes does what its name suggests +func ParseBytes(val string) ([]byte, error) { + return hex.DecodeString(val) +} diff --git a/core/storage/conversions_test.go b/core/storage/conversions_test.go new file mode 100644 index 000000000..7239318f1 --- /dev/null +++ b/core/storage/conversions_test.go @@ -0,0 +1,78 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "testing" + + "github.com/smartystreets/assertions" +) + +func TestFloat32(t *testing.T) { + a := assertions.New(t) + a.So(FormatFloat32(123.456), assertions.ShouldEqual, "123.456") + f32, err := ParseFloat32("123.456") + a.So(err, assertions.ShouldBeNil) + a.So(f32, assertions.ShouldEqual, 123.456) +} + +func TestFloat64(t *testing.T) { + a := assertions.New(t) + a.So(FormatFloat64(123.456), assertions.ShouldEqual, "123.456") + f64, err := ParseFloat64("123.456") + a.So(err, assertions.ShouldBeNil) + a.So(f64, assertions.ShouldEqual, 123.456) +} + +func TestInt32(t *testing.T) { + a := assertions.New(t) + a.So(FormatInt32(-123456), assertions.ShouldEqual, "-123456") + i32, err := ParseInt32("-123456") + a.So(err, assertions.ShouldBeNil) + a.So(i32, assertions.ShouldEqual, -123456) +} + +func TestInt64(t *testing.T) { + a := assertions.New(t) + a.So(FormatInt64(-123456), assertions.ShouldEqual, "-123456") + i64, err := ParseInt64("-123456") + a.So(err, assertions.ShouldBeNil) + a.So(i64, assertions.ShouldEqual, -123456) +} + +func TestUint32(t *testing.T) { + a := assertions.New(t) + a.So(FormatUint32(123456), assertions.ShouldEqual, "123456") + i32, err := ParseUint32("123456") + a.So(err, assertions.ShouldBeNil) + a.So(i32, assertions.ShouldEqual, 123456) +} + +func TestUint64(t *testing.T) { + a := assertions.New(t) + a.So(FormatUint64(123456), assertions.ShouldEqual, "123456") + i64, err := ParseUint64("123456") + a.So(err, assertions.ShouldBeNil) + a.So(i64, assertions.ShouldEqual, 123456) +} + +func TestBool(t *testing.T) { + a := assertions.New(t) + a.So(FormatBool(true), assertions.ShouldEqual, "true") + b, err := ParseBool("true") + a.So(err, assertions.ShouldBeNil) + a.So(b, assertions.ShouldEqual, true) + a.So(FormatBool(false), assertions.ShouldEqual, "false") + b, err = ParseBool("false") + a.So(err, assertions.ShouldBeNil) + a.So(b, assertions.ShouldEqual, false) +} + +func TestBytes(t *testing.T) { + a := assertions.New(t) + a.So(FormatBytes([]byte{0x12, 0x34, 0xcd, 0xef}), assertions.ShouldEqual, "1234CDEF") + i64, err := ParseBytes("1234CDEF") + a.So(err, assertions.ShouldBeNil) + a.So(i64, assertions.ShouldResemble, []byte{0x12, 0x34, 0xcd, 0xef}) +} diff --git a/core/storage/decoder.go b/core/storage/decoder.go new file mode 100644 index 000000000..504d0b09b --- /dev/null +++ b/core/storage/decoder.go @@ -0,0 +1,174 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + "strconv" + + "github.com/fatih/structs" +) + +// StringStringMapDecoder is used to decode a map[string]string to a struct +type StringStringMapDecoder func(input map[string]string) (interface{}, error) + +func decodeToType(typ reflect.Kind, value string) interface{} { + switch typ { + case reflect.String: + return value + case reflect.Bool: + v, _ := strconv.ParseBool(value) + return v + case reflect.Int: + v, _ := strconv.ParseInt(value, 10, 64) + return int(v) + case reflect.Int8: + return int8(decodeToType(reflect.Int, value).(int)) + case reflect.Int16: + return int16(decodeToType(reflect.Int, value).(int)) + case reflect.Int32: + return int32(decodeToType(reflect.Int, value).(int)) + case reflect.Int64: + return int64(decodeToType(reflect.Int, value).(int)) + case reflect.Uint: + v, _ := strconv.ParseUint(value, 10, 64) + return uint(v) + case reflect.Uint8: + return uint8(decodeToType(reflect.Uint, value).(uint)) + case reflect.Uint16: + return uint16(decodeToType(reflect.Uint, value).(uint)) + case reflect.Uint32: + return uint32(decodeToType(reflect.Uint, value).(uint)) + case reflect.Uint64: + return uint64(decodeToType(reflect.Uint, value).(uint)) + case reflect.Float64: + v, _ := strconv.ParseFloat(value, 64) + return v + case reflect.Float32: + return float32(decodeToType(reflect.Float64, value).(float64)) + } + return nil +} + +func unmarshalToType(typ reflect.Type, value string) (val interface{}, err error) { + // If we get a pointer in, we'll return a pointer out + if typ.Kind() == reflect.Ptr { + val = reflect.New(typ.Elem()).Interface() + } else { + val = reflect.New(typ).Interface() + } + defer func() { + if err == nil && typ.Kind() != reflect.Ptr { + val = reflect.Indirect(reflect.ValueOf(val)).Interface() + } + }() + + // If we can just assign the value, return the value + if typ.AssignableTo(reflect.TypeOf(value)) { + return value, nil + } + + // Try Unmarshalers + if um, ok := val.(encoding.TextUnmarshaler); ok { + if err = um.UnmarshalText([]byte(value)); err == nil { + return val, nil + } + } + if um, ok := val.(json.Unmarshaler); ok { + if err = um.UnmarshalJSON([]byte(value)); err == nil { + return val, nil + } + } + + // Try conversion + if typ.ConvertibleTo(reflect.TypeOf(value)) { + return reflect.ValueOf(value).Convert(typ).Interface(), nil + } + + // Try JSON + if err = json.Unmarshal([]byte(value), val); err == nil { + return val, nil + } + + // Return error if we have one + if err != nil { + return nil, err + } + + return val, fmt.Errorf("No way to unmarshal \"%s\" to %s", value, typ.Name()) +} + +// buildDefaultStructDecoder is used by the RedisMapStore +func buildDefaultStructDecoder(base interface{}, tagName string) StringStringMapDecoder { + if tagName == "" { + tagName = defaultTagName + } + + return func(input map[string]string) (output interface{}, err error) { + baseType := reflect.TypeOf(base) + // If we get a pointer in, we'll return a pointer out + if baseType.Kind() == reflect.Ptr { + output = reflect.New(baseType.Elem()).Interface() + } else { + output = reflect.New(baseType).Interface() + } + defer func() { + if err == nil && baseType.Kind() != reflect.Ptr { + output = reflect.Indirect(reflect.ValueOf(output)).Interface() + } + }() + + s := structs.New(output) + for _, field := range s.Fields() { + if !field.IsExported() { + continue + } + + tagName, _ := parseTag(field.Tag(tagName)) + if tagName == "" || tagName == "-" { + continue + } + if str, ok := input[tagName]; ok { + baseField, _ := baseType.FieldByName(field.Name()) + + if str == "" { + continue + } + + var val interface{} + switch field.Kind() { + case reflect.Ptr: + if str == "null" { + continue + } + fallthrough + case reflect.Struct, reflect.Array, reflect.Interface, reflect.Slice: + var err error + val, err = unmarshalToType(baseField.Type, str) + if err != nil { + return nil, err + } + default: + val = decodeToType(field.Kind(), str) + } + + if val == nil { + continue + } + + if !baseField.Type.AssignableTo(reflect.TypeOf(val)) && baseField.Type.ConvertibleTo(reflect.TypeOf(val)) { + val = reflect.ValueOf(val).Convert(baseField.Type).Interface() + } + + if err := field.Set(val); err != nil { + return nil, err + } + } + } + return output, nil + } +} diff --git a/core/storage/decoder_test.go b/core/storage/decoder_test.go new file mode 100644 index 000000000..9957da3f0 --- /dev/null +++ b/core/storage/decoder_test.go @@ -0,0 +1,119 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding/hex" + "reflect" + "strings" + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestDecodeToType(t *testing.T) { + a := New(t) + a.So(decodeToType(reflect.String, "abc"), ShouldEqual, "abc") + a.So(decodeToType(reflect.Bool, "true"), ShouldEqual, true) + a.So(decodeToType(reflect.Bool, "false"), ShouldEqual, false) + a.So(decodeToType(reflect.Int, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int8, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int16, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int32, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int64, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Uint, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint8, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint16, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint32, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint64, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Float64, "12.34"), ShouldEqual, 12.34) + a.So(decodeToType(reflect.Float32, "12.34"), ShouldEqual, 12.34) + a.So(decodeToType(reflect.Struct, "blabla"), ShouldBeNil) +} + +type noIdea struct { + something complex128 +} + +type strtp string + +type testTextUnmarshaler struct { + Data string +} + +func (um *testTextUnmarshaler) UnmarshalText(text []byte) error { + um.Data = string(text) + return nil +} + +type testCustomType [2]byte + +type testJSONUnmarshaler struct { + Customs []testCustomType +} + +type jsonStruct struct { + String string `json:"string"` +} + +func (jum *testJSONUnmarshaler) UnmarshalJSON(text []byte) error { + jum.Customs = []testCustomType{} + txt := strings.Trim(string(text), "[]") + txtlist := strings.Split(txt, ",") + for _, txtitem := range txtlist { + txtitem = strings.Trim(txtitem, `"`) + b, _ := hex.DecodeString(txtitem) + var o testCustomType + copy(o[:], b[:]) + jum.Customs = append(jum.Customs, o) + } + return nil +} + +func TestUnmarshalToType(t *testing.T) { + a := New(t) + + var str string + strOut, err := unmarshalToType(reflect.TypeOf(str), "data") + a.So(err, ShouldBeNil) + a.So(strOut, ShouldEqual, "data") + + var strtp strtp + strtpOut, err := unmarshalToType(reflect.TypeOf(strtp), "data") + a.So(err, ShouldBeNil) + a.So(strtpOut, ShouldEqual, "data") + + var um testTextUnmarshaler + umOut, err := unmarshalToType(reflect.TypeOf(um), "data") + a.So(err, ShouldBeNil) + a.So(umOut.(testTextUnmarshaler), ShouldResemble, testTextUnmarshaler{"data"}) + + var jum testJSONUnmarshaler + jumOut, err := unmarshalToType(reflect.TypeOf(jum), `["abcd","1234","def0"]`) + a.So(err, ShouldBeNil) + a.So(jumOut.(testJSONUnmarshaler), ShouldResemble, testJSONUnmarshaler{[]testCustomType{ + testCustomType{0xab, 0xcd}, + testCustomType{0x12, 0x34}, + testCustomType{0xde, 0xf0}, + }}) + + var js jsonStruct + jsOut, err := unmarshalToType(reflect.TypeOf(js), `{"string": "String"}`) + a.So(err, ShouldBeNil) + a.So(jsOut.(jsonStruct), ShouldResemble, jsonStruct{"String"}) + + _, err = unmarshalToType(reflect.TypeOf(js), `this is no json`) + a.So(err, ShouldNotBeNil) + + var eui types.DevEUI + euiOut, err := unmarshalToType(reflect.TypeOf(eui), "0102abcd0304abcd") + a.So(err, ShouldBeNil) + a.So(euiOut.(types.DevEUI), ShouldEqual, types.DevEUI{0x01, 0x02, 0xab, 0xcd, 0x03, 0x04, 0xab, 0xcd}) + + var euiPtr *types.DevEUI + euiPtrOut, err := unmarshalToType(reflect.TypeOf(euiPtr), "0102abcd0304abcd") + a.So(err, ShouldBeNil) + a.So(euiPtrOut.(*types.DevEUI), ShouldResemble, &types.DevEUI{0x01, 0x02, 0xab, 0xcd, 0x03, 0x04, 0xab, 0xcd}) +} diff --git a/core/storage/encoder.go b/core/storage/encoder.go new file mode 100644 index 000000000..cbfa4be5c --- /dev/null +++ b/core/storage/encoder.go @@ -0,0 +1,104 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + + "github.com/fatih/structs" +) + +// StringStringMapEncoder encodes the given properties of the input to a map[string]string for storage in Redis +type StringStringMapEncoder func(input interface{}, properties ...string) (map[string]string, error) + +type isZeroer interface { + IsZero() bool +} + +type isEmptier interface { + IsEmpty() bool +} + +func buildDefaultStructEncoder(tagName string) StringStringMapEncoder { + if tagName == "" { + tagName = defaultTagName + } + + return func(input interface{}, properties ...string) (map[string]string, error) { + vmap := make(map[string]string) + s := structs.New(input) + s.TagName = tagName + if len(properties) == 0 { + properties = s.Names() + } + for _, field := range s.Fields() { + if !field.IsExported() { + continue + } + + if !stringInSlice(field.Name(), properties) { + continue + } + + tagName, opts := parseTag(field.Tag(tagName)) + if tagName == "" || tagName == "-" { + continue + } + + val := field.Value() + + if opts.Has("omitempty") { + if field.IsZero() { + continue + } + if z, ok := val.(isZeroer); ok && z.IsZero() { + continue + } + if z, ok := val.(isEmptier); ok && z.IsEmpty() { + continue + } + } + + if v, ok := val.(string); ok { + vmap[tagName] = v + continue + } + + if !field.IsZero() { + if m, ok := val.(encoding.TextMarshaler); ok { + txt, err := m.MarshalText() + if err != nil { + return nil, err + } + vmap[tagName] = string(txt) + continue + } + if m, ok := val.(json.Marshaler); ok { + txt, err := m.MarshalJSON() + if err != nil { + return nil, err + } + vmap[tagName] = string(txt) + continue + } + } + + if field.Kind() == reflect.String { + vmap[tagName] = fmt.Sprint(val) + continue + } + + if txt, err := json.Marshal(val); err == nil { + vmap[tagName] = string(txt) + continue + } + + vmap[tagName] = fmt.Sprintf("%v", val) + } + return vmap, nil + } +} diff --git a/core/storage/encoder_test.go b/core/storage/encoder_test.go new file mode 100644 index 000000000..dde7a5368 --- /dev/null +++ b/core/storage/encoder_test.go @@ -0,0 +1,99 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type strtps []strtp + +type jm struct { + txt string +} + +func (j jm) MarshalJSON() ([]byte, error) { + return []byte("{" + j.txt + "}"), nil +} + +type testStruct struct { + unexported string `redis:"unexported"` + NoRedis string `` + DisRedis string `redis:"-"` + String string `redis:"string"` + Strings []string `redis:"strings"` + EmptyString string `redis:"empty_string,omitempty"` + EmptyStrings []string `redis:"empty_strings,omitempty"` + AppEUI types.AppEUI `redis:"app_eui"` + AppEUIPtr *types.AppEUI `redis:"app_eui_ptr"` + EmptyAppEUI types.AppEUI `redis:"empty_app_eui,omitempty"` + EmptyAppEUIPtr *types.AppEUI `redis:"empty_app_eui_ptr,omitempty"` + Time time.Time `redis:"time"` + TimePtr *time.Time `redis:"time_ptr"` + EmptyTime time.Time `redis:"empty_time,omitempty"` + EmptyTimePtr *time.Time `redis:"empty_time_ptr,omitempty"` + STime Time `redis:"stime"` + STimePtr *Time `redis:"stime_ptr"` + EmptySTime Time `redis:"empty_stime,omitempty"` + EmptySTimePtr *Time `redis:"empty_stime_ptr,omitempty"` + Str strtp `redis:"str"` + Strs []strtp `redis:"strs"` + JM jm `redis:"jm"` + JMs []jm `redis:"jms"` + Int int `redis:"int"` + Uint uint `redis:"uint"` +} + +func TestDefaultStructEncoder(t *testing.T) { + a := New(t) + + var emptyAppEUI types.AppEUI + var appEUI = types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + var now = time.Now() + var emptyTime time.Time + var stime = Time{now} + var emptySTime Time + + out, err := buildDefaultStructEncoder("")(&testStruct{ + unexported: "noop", + NoRedis: "noop", + DisRedis: "noop", + String: "string", + Strings: []string{"string1", "string2"}, + AppEUI: appEUI, + AppEUIPtr: &appEUI, + EmptyAppEUIPtr: &emptyAppEUI, + Time: now, + TimePtr: &now, + STime: stime, + STimePtr: &stime, + EmptySTimePtr: &emptySTime, + EmptyTimePtr: &emptyTime, + JM: jm{"cool"}, + }) + a.So(err, ShouldBeNil) + a.So(out, ShouldNotContainKey, "unexported") + a.So(out, ShouldNotContainKey, "empty_string") + a.So(out, ShouldNotContainKey, "empty_strings") + a.So(out, ShouldNotContainKey, "empty_app_eui") + a.So(out, ShouldNotContainKey, "empty_app_eui_ptr") + a.So(out, ShouldNotContainKey, "empty_time") + a.So(out, ShouldNotContainKey, "empty_time_ptr") + a.So(out, ShouldNotContainKey, "empty_stime") + a.So(out, ShouldNotContainKey, "empty_stime_ptr") + a.So(out["string"], ShouldEqual, "string") + a.So(out["strings"], ShouldEqual, `["string1","string2"]`) + a.So(out["jm"], ShouldEqual, "{cool}") + + out, err = buildDefaultStructEncoder("")(&testStruct{ + String: "noop", + Strings: []string{"string1", "string2"}, + }, "String") + a.So(err, ShouldBeNil) + a.So(out, ShouldNotContainKey, "strings") +} diff --git a/core/storage/redis_kv_store.go b/core/storage/redis_kv_store.go new file mode 100644 index 000000000..e4848fbce --- /dev/null +++ b/core/storage/redis_kv_store.go @@ -0,0 +1,193 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + "gopkg.in/redis.v5" +) + +// RedisKVStore stores arbitrary data in Redis +type RedisKVStore struct { + prefix string + client *redis.Client +} + +// NewRedisKVStore creates a new RedisKVStore +func NewRedisKVStore(client *redis.Client, prefix string) *RedisKVStore { + if !strings.HasSuffix(prefix, ":") { + prefix += ":" + } + return &RedisKVStore{ + client: client, + prefix: prefix, + } +} + +// GetAll returns all results for the given keys, prepending the prefix to the keys if necessary +func (s *RedisKVStore) GetAll(keys []string, options *ListOptions) (map[string]string, error) { + if len(keys) == 0 { + return map[string]string{}, nil + } + + for i, key := range keys { + if !strings.HasPrefix(key, s.prefix) { + keys[i] = s.prefix + key + } + } + + sort.Strings(keys) + + selectedKeys := selectKeys(keys, options) + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringCmd) + for _, key := range selectedKeys { + cmds[key] = pipe.Get(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + data := make(map[string]string) + for key, cmd := range cmds { + res, err := cmd.Result() + if err == nil { + data[strings.TrimPrefix(key, s.prefix)] = res + } + } + + return data, nil +} + +// List all results matching the selector, prepending the prefix to the selector if necessary +func (s *RedisKVStore) List(selector string, options *ListOptions) (map[string]string, error) { + if selector == "" { + selector = "*" + } + if !strings.HasPrefix(selector, s.prefix) { + selector = s.prefix + selector + } + keys, err := s.client.Keys(selector).Result() + if err != nil { + return nil, err + } + return s.GetAll(keys, options) +} + +// Get one result, prepending the prefix to the key if necessary +func (s *RedisKVStore) Get(key string) (string, error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + result, err := s.client.Get(key).Result() + if err == redis.Nil || result == "" { + return "", errors.NewErrNotFound(key) + } + if err != nil { + return "", err + } + return result, nil +} + +// Create a new record, prepending the prefix to the key if necessary +func (s *RedisKVStore) Create(key string, value string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if exists { + return errors.NewErrAlreadyExists(key) + } + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Set(key, value, 0) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Update an existing record, prepending the prefix to the key if necessary +func (s *RedisKVStore) Update(key string, value string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Set(key, value, 0) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Delete an existing record, prepending the prefix to the key if necessary +func (s *RedisKVStore) Delete(key string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Del(key) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} diff --git a/core/storage/redis_kv_store_test.go b/core/storage/redis_kv_store_test.go new file mode 100644 index 000000000..58ce18088 --- /dev/null +++ b/core/storage/redis_kv_store_test.go @@ -0,0 +1,134 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/smartystreets/assertions" +) + +func TestRedisKVStore(t *testing.T) { + a := New(t) + c := getRedisClient() + s := NewRedisKVStore(c, "test-redis-kv-store") + a.So(s, ShouldNotBeNil) + + // Get non-existing + { + _, err := s.Get("test") + a.So(err, ShouldNotBeNil) + a.So(errors.GetErrType(err), ShouldEqual, errors.NotFound) + } + + // Create New + { + defer func() { + c.Del("test-redis-kv-store:test").Result() + }() + err := s.Create("test", "value") + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-kv-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeTrue) + } + + // Create Existing + { + err := s.Create("test", "value") + a.So(err, ShouldNotBeNil) + } + + // Get + { + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldEqual, "value") + } + + for i := 1; i < 10; i++ { + // Create Extra + { + name := fmt.Sprintf("test-%d", i) + defer func() { + c.Del("test-redis-kv-store:" + name).Result() + }() + s.Create(name, name) + } + } + + // GetAll + { + res, err := s.GetAll([]string{"test"}, nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + a.So(res["test"], ShouldEqual, "value") + } + + // List + { + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 10) + a.So(res["test"], ShouldEqual, "value") + } + + // List With Options + { + res, _ := s.List("test-*", &ListOptions{Limit: 2}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-1"], ShouldEqual, "test-1") + a.So(res["test-2"], ShouldEqual, "test-2") + + res, _ = s.List("test-*", &ListOptions{Limit: 20}) + a.So(res, ShouldHaveLength, 9) + + res, _ = s.List("test-*", &ListOptions{Offset: 20}) + a.So(res, ShouldHaveLength, 0) + + res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-2"], ShouldEqual, "test-2") + a.So(res["test-3"], ShouldEqual, "test-3") + + res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) + a.So(res, ShouldHaveLength, 8) + } + + // Update Non-Existing + { + err := s.Update("not-there", "value") + a.So(err, ShouldNotBeNil) + } + + // Update Existing + { + err := s.Update("test", "updated") + a.So(err, ShouldBeNil) + + name, err := c.Get("test-redis-kv-store:test").Result() + a.So(err, ShouldBeNil) + a.So(name, ShouldEqual, "updated") + } + + // Delete Non-Existing + { + err := s.Delete("not-there") + a.So(err, ShouldNotBeNil) + } + + // Delete Existing + { + err := s.Delete("test") + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-kv-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeFalse) + } + +} diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go new file mode 100644 index 000000000..57b82e68c --- /dev/null +++ b/core/storage/redis_map_store.go @@ -0,0 +1,274 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + redis "gopkg.in/redis.v5" +) + +// RedisMapStore stores structs as HMaps in Redis +type RedisMapStore struct { + prefix string + client *redis.Client + encoder StringStringMapEncoder + decoder StringStringMapDecoder +} + +// NewRedisMapStore returns a new RedisMapStore that talks to the given Redis client and respects the given prefix +func NewRedisMapStore(client *redis.Client, prefix string) *RedisMapStore { + if !strings.HasSuffix(prefix, ":") { + prefix += ":" + } + return &RedisMapStore{ + client: client, + prefix: prefix, + } +} + +// SetBase sets the base struct for automatically encoding and decoding to and from Redis format +func (s *RedisMapStore) SetBase(base interface{}, tagName string) { + s.SetEncoder(buildDefaultStructEncoder(tagName)) + s.SetDecoder(buildDefaultStructDecoder(base, tagName)) +} + +// SetEncoder sets the encoder to convert structs to Redis format +func (s *RedisMapStore) SetEncoder(encoder StringStringMapEncoder) { + s.encoder = encoder +} + +// SetDecoder sets the decoder to convert structs from Redis format +func (s *RedisMapStore) SetDecoder(decoder StringStringMapDecoder) { + s.decoder = decoder +} + +// GetAll returns all results for the given keys, prepending the prefix to the keys if necessary +func (s *RedisMapStore) GetAll(keys []string, options *ListOptions) ([]interface{}, error) { + if len(keys) == 0 { + return []interface{}{}, nil + } + + for i, key := range keys { + if !strings.HasPrefix(key, s.prefix) { + keys[i] = s.prefix + key + } + } + + sort.Strings(keys) + + selectedKeys := selectKeys(keys, options) + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) + for _, key := range selectedKeys { + cmds[key] = pipe.HGetAll(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + results := make([]interface{}, 0, len(selectedKeys)) + for _, key := range selectedKeys { + if result, err := cmds[key].Result(); err == nil { + if result, err := s.decoder(result); err == nil { + results = append(results, result) + } + } + } + + return results, nil +} + +// List all results matching the selector, prepending the prefix to the selector if necessary +func (s *RedisMapStore) List(selector string, options *ListOptions) ([]interface{}, error) { + if selector == "" { + selector = "*" + } + if !strings.HasPrefix(selector, s.prefix) { + selector = s.prefix + selector + } + keys, err := s.client.Keys(selector).Result() + if err != nil { + return nil, err + } + return s.GetAll(keys, options) +} + +// Get one result, prepending the prefix to the key if necessary +func (s *RedisMapStore) Get(key string) (interface{}, error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + result, err := s.client.HGetAll(key).Result() + if err == redis.Nil || len(result) == 0 { + return nil, errors.NewErrNotFound(key) + } + if err != nil { + return nil, err + } + i, err := s.decoder(result) + if err != nil { + return nil, err + } + return i, nil +} + +// GetFields for a record, prepending the prefix to the key if necessary +func (s *RedisMapStore) GetFields(key string, fields ...string) (interface{}, error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + result, err := s.client.HMGet(key, fields...).Result() + if err == redis.Nil { + return nil, errors.NewErrNotFound(key) + } + if err != nil { + return nil, err + } + res := make(map[string]string) + for i, field := range fields { + if str, ok := result[i].(string); ok { + res[field] = str + } + } + i, err := s.decoder(res) + if err != nil { + return nil, err + } + return i, nil +} + +// ChangedFielder interface is used to see what fields to update +type ChangedFielder interface { + ChangedFields() []string +} + +// Create a new record, prepending the prefix to the key if necessary, optionally setting only the given properties +func (s *RedisMapStore) Create(key string, value interface{}, properties ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + if len(properties) == 0 { + if i, ok := value.(ChangedFielder); ok { + properties = i.ChangedFields() + } + } + + vmap, err := s.encoder(value, properties...) + if err != nil { + return err + } + if len(vmap) == 0 { + return nil + } + + err = s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if exists { + return errors.NewErrAlreadyExists(key) + } + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.HMSet(key, vmap) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Update an existing record, prepending the prefix to the key if necessary, optionally setting only the given properties +func (s *RedisMapStore) Update(key string, value interface{}, properties ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + if len(properties) == 0 { + if i, ok := value.(ChangedFielder); ok { + properties = i.ChangedFields() + } + } + + vmap, err := s.encoder(value, properties...) + if err != nil { + return err + } + if len(vmap) == 0 { + return nil + } + + err = s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.HMSet(key, vmap) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Delete an existing record, prepending the prefix to the key if necessary +func (s *RedisMapStore) Delete(key string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Del(key) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go new file mode 100644 index 000000000..a701a6e10 --- /dev/null +++ b/core/storage/redis_map_store_test.go @@ -0,0 +1,170 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/smartystreets/assertions" +) + +type testRedisStruct struct { + unexported string + Skipped string `redis:"-"` + Name string `redis:"name,omitempty"` + EmptyStr string `redis:"EmptyStr"` + UpdatedAt Time `redis:"updated_at,omitempty"` + Empty *map[string]string `redis:"empty"` + NotEmpty *map[string]string `redis:"not_empty"` +} + +func TestRedisMapStore(t *testing.T) { + a := New(t) + c := getRedisClient() + s := NewRedisMapStore(c, "test-redis-map-store") + a.So(s, ShouldNotBeNil) + + now := time.Now() + notEmpty := map[string]string{"ab": "cd"} + testRedisStructVal := testRedisStruct{ + Name: "My Name", + UpdatedAt: Time{now}, + NotEmpty: ¬Empty, + } + + s.SetBase(testRedisStructVal, "") + + // Get non-existing + { + res, err := s.Get("test") + a.So(err, ShouldNotBeNil) + a.So(errors.GetErrType(err), ShouldEqual, errors.NotFound) + a.So(res, ShouldBeNil) + } + + // Create New + { + defer func() { + c.Del("test-redis-map-store:test").Result() + }() + err := s.Create("test", &testRedisStructVal) + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-map-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeTrue) + } + + // Create Existing + { + err := s.Create("test", testRedisStructVal) + a.So(err, ShouldNotBeNil) + } + + // Get + { + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + a.So(res.(testRedisStruct).Name, ShouldEqual, "My Name") + a.So(res.(testRedisStruct).UpdatedAt.Nanosecond(), ShouldEqual, now.Nanosecond()) + } + + // GetFields + { + res, err := s.GetFields("test", "name") + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + a.So(res.(testRedisStruct).Name, ShouldEqual, "My Name") + } + + for i := 1; i < 10; i++ { + // Create Extra + { + name := fmt.Sprintf("test-%d", i) + defer func() { + c.Del("test-redis-map-store:" + name).Result() + }() + s.Create(name, testRedisStruct{ + Name: name, + }) + } + } + + // GetAll + { + res, err := s.GetAll([]string{"test"}, nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "My Name") + } + + // List + { + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 10) + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "My Name") + } + + // List With Options + { + res, _ := s.List("test-*", &ListOptions{Limit: 2}) + a.So(res, ShouldHaveLength, 2) + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "test-1") + a.So(res[1].(testRedisStruct).Name, ShouldEqual, "test-2") + + res, _ = s.List("test-*", &ListOptions{Limit: 20}) + a.So(res, ShouldHaveLength, 9) + + res, _ = s.List("test-*", &ListOptions{Offset: 20}) + a.So(res, ShouldHaveLength, 0) + + res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) + a.So(res, ShouldHaveLength, 2) + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "test-2") + a.So(res[1].(testRedisStruct).Name, ShouldEqual, "test-3") + + res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) + a.So(res, ShouldHaveLength, 8) + } + + // Update Non-Existing + { + err := s.Update("not-there", &testRedisStructVal) + a.So(err, ShouldNotBeNil) + } + + // Update Existing + { + err := s.Update("test", &testRedisStruct{ + Name: "New Name", + }, "Name") + a.So(err, ShouldBeNil) + + name, err := c.HGet("test-redis-map-store:test", "name").Result() + a.So(err, ShouldBeNil) + a.So(name, ShouldEqual, "New Name") + } + + // Delete Non-Existing + { + err := s.Delete("not-there") + a.So(err, ShouldNotBeNil) + } + + // Delete Existing + { + err := s.Delete("test") + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-map-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeFalse) + } + +} diff --git a/core/storage/redis_set_store.go b/core/storage/redis_set_store.go new file mode 100644 index 000000000..22a8a7971 --- /dev/null +++ b/core/storage/redis_set_store.go @@ -0,0 +1,146 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + "gopkg.in/redis.v5" +) + +// RedisSetStore stores sets in Redis +type RedisSetStore struct { + prefix string + client *redis.Client +} + +// NewRedisSetStore creates a new RedisSetStore +func NewRedisSetStore(client *redis.Client, prefix string) *RedisSetStore { + if !strings.HasSuffix(prefix, ":") { + prefix += ":" + } + return &RedisSetStore{ + client: client, + prefix: prefix, + } +} + +// GetAll returns all results for the given keys, prepending the prefix to the keys if necessary +func (s *RedisSetStore) GetAll(keys []string, options *ListOptions) (map[string][]string, error) { + if len(keys) == 0 { + return map[string][]string{}, nil + } + + for i, key := range keys { + if !strings.HasPrefix(key, s.prefix) { + keys[i] = s.prefix + key + } + } + + sort.Strings(keys) + + selectedKeys := selectKeys(keys, options) + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringSliceCmd) + for _, key := range selectedKeys { + cmds[key] = pipe.SMembers(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + data := make(map[string][]string) + for key, cmd := range cmds { + res, err := cmd.Result() + if err == nil { + sort.Strings(res) + data[strings.TrimPrefix(key, s.prefix)] = res + } + } + + return data, nil +} + +// List all results matching the selector, prepending the prefix to the selector if necessary +func (s *RedisSetStore) List(selector string, options *ListOptions) (map[string][]string, error) { + if selector == "" { + selector = "*" + } + if !strings.HasPrefix(selector, s.prefix) { + selector = s.prefix + selector + } + keys, err := s.client.Keys(selector).Result() + if err != nil { + return nil, err + } + return s.GetAll(keys, options) +} + +// Get one result, prepending the prefix to the key if necessary +func (s *RedisSetStore) Get(key string) (res []string, err error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + res, err = s.client.SMembers(key).Result() + if err == redis.Nil || len(res) == 0 { + return res, errors.NewErrNotFound(key) + } + sort.Strings(res) + return res, err +} + +// Contains returns wheter the set contains a given value, prepending the prefix to the key if necessary +func (s *RedisSetStore) Contains(key string, value string) (res bool, err error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + res, err = s.client.SIsMember(key, value).Result() + if err == redis.Nil { + return res, errors.NewErrNotFound(key) + } + return res, err +} + +// Add one or more values to the set, prepending the prefix to the key if necessary +func (s *RedisSetStore) Add(key string, values ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + var valuesI []interface{} + for _, v := range values { + valuesI = append(valuesI, v) + } + return s.client.SAdd(key, valuesI...).Err() +} + +// Remove one or more values from the set, prepending the prefix to the key if necessary +func (s *RedisSetStore) Remove(key string, values ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + var valuesI []interface{} + for _, v := range values { + valuesI = append(valuesI, v) + } + return s.client.SRem(key, valuesI...).Err() +} + +// Delete the entire set +func (s *RedisSetStore) Delete(key string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + return s.client.Del(key).Err() +} diff --git a/core/storage/redis_set_store_test.go b/core/storage/redis_set_store_test.go new file mode 100644 index 000000000..c46970667 --- /dev/null +++ b/core/storage/redis_set_store_test.go @@ -0,0 +1,133 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/smartystreets/assertions" +) + +func TestRedisSetStore(t *testing.T) { + a := New(t) + c := getRedisClient() + s := NewRedisSetStore(c, "test-redis-set-store") + a.So(s, ShouldNotBeNil) + + // Get non-existing + { + _, err := s.Get("test") + a.So(err, ShouldNotBeNil) + a.So(errors.GetErrType(err), ShouldEqual, errors.NotFound) + + contains, err := s.Contains("test", "value") + a.So(err, ShouldBeNil) + a.So(contains, ShouldBeFalse) + } + + defer func() { + c.Del("test-redis-set-store:test").Result() + }() + + // Add + { + err := s.Add("test", "value") + a.So(err, ShouldBeNil) + + contains, err := s.Contains("test", "value") + a.So(err, ShouldBeNil) + a.So(contains, ShouldBeTrue) + + contains, err = s.Contains("test", "not-there") + a.So(err, ShouldBeNil) + a.So(contains, ShouldBeFalse) + } + + // Get + { + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + } + + // Get More + { + s.Add("test", "othervalue") + + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + } + + // Remove + { + s.Remove("test", "othervalue") + + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + } + + for i := 1; i < 10; i++ { + // Create Extra + { + name := fmt.Sprintf("test-%d", i) + defer func() { + c.Del("test-redis-set-store:" + name).Result() + }() + s.Add(name, name) + } + } + + // GetAll + { + res, err := s.GetAll([]string{"test"}, nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + a.So(res["test"], ShouldHaveLength, 1) + } + + // List + { + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 10) + a.So(res["test"], ShouldHaveLength, 1) + } + + // List With Options + { + res, _ := s.List("test-*", &ListOptions{Limit: 2}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-1"], ShouldResemble, []string{"test-1"}) + a.So(res["test-2"], ShouldResemble, []string{"test-2"}) + + res, _ = s.List("test-*", &ListOptions{Limit: 20}) + a.So(res, ShouldHaveLength, 9) + + res, _ = s.List("test-*", &ListOptions{Offset: 20}) + a.So(res, ShouldHaveLength, 0) + + res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-2"], ShouldResemble, []string{"test-2"}) + a.So(res["test-3"], ShouldResemble, []string{"test-3"}) + + res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) + a.So(res, ShouldHaveLength, 8) + } + + // Delete + { + err := s.Delete("test-1") + a.So(err, ShouldBeNil) + + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 9) + } + +} diff --git a/core/storage/tags.go b/core/storage/tags.go new file mode 100644 index 000000000..5444ee01a --- /dev/null +++ b/core/storage/tags.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import "strings" + +var defaultTagName = "redis" + +type tagOptions []string + +// Has returns true if opt is one of the options +func (t tagOptions) Has(opt string) bool { + for _, opt := range t { + if opt == opt { + return true + } + } + return false +} + +func parseTag(tag string) (string, tagOptions) { + res := strings.Split(tag, ",") + return res[0], res[1:] +} diff --git a/core/storage/types.go b/core/storage/types.go new file mode 100644 index 000000000..060078b90 --- /dev/null +++ b/core/storage/types.go @@ -0,0 +1,26 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import "time" + +// Time is a wrapper around time.Time that marshals and unmarshals to the time.RFC3339Nano format +type Time struct { + time.Time +} + +// MarshalText implements the encoding.TextMarshaler interface +func (t Time) MarshalText() ([]byte, error) { + return []byte(t.Time.UTC().Format(time.RFC3339Nano)), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (t *Time) UnmarshalText(in []byte) error { + parsed, err := time.Parse(time.RFC3339Nano, string(in)) + if err != nil { + return err + } + t.Time = parsed + return nil +} diff --git a/core/storage/util.go b/core/storage/util.go new file mode 100644 index 000000000..e450d6425 --- /dev/null +++ b/core/storage/util.go @@ -0,0 +1,37 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +// ListOptions are options for all list commands +type ListOptions struct { + Limit int + Offset int +} + +func selectKeys(keys []string, options *ListOptions) []string { + var start int + var end = len(keys) + if options != nil { + if options.Offset >= len(keys) { + return []string{} + } + start = options.Offset + if options.Limit > 0 { + if options.Offset+options.Limit > len(keys) { + options.Limit = len(keys) - options.Offset + } + end = options.Offset + options.Limit + } + } + return keys[start:end] +} + +func stringInSlice(search string, slice []string) bool { + for _, i := range slice { + if i == search { + return true + } + } + return false +} diff --git a/core/storage/util_test.go b/core/storage/util_test.go new file mode 100644 index 000000000..93c89468d --- /dev/null +++ b/core/storage/util_test.go @@ -0,0 +1,23 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "os" + + redis "gopkg.in/redis.v5" +) + +func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: 1, // use default DB + }) +} diff --git a/core/types/access_keys.go b/core/types/access_keys.go new file mode 100644 index 000000000..f4501e26a --- /dev/null +++ b/core/types/access_keys.go @@ -0,0 +1,34 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// Right is the type that represents a right to do something on TTN +type Right string + +// AccessKey is an access key that gives someone the right to do something with +// an application +type AccessKey struct { + // The friendly name of the access key + Name string `json:"name"` + + // The hard-to-guess access key + Key string `json:"key"` + + // The rights associated with the key + Rights []Right `json:"rights"` +} + +// HasRight checks if an AccessKey has a certain right +func (k *AccessKey) HasRight(right Right) bool { + for _, r := range k.Rights { + if r == right { + return true + } + } + return false +} + +func (r *Right) String() string { + return string(*r) +} diff --git a/core/types/access_keys_test.go b/core/types/access_keys_test.go new file mode 100644 index 000000000..af61a08fd --- /dev/null +++ b/core/types/access_keys_test.go @@ -0,0 +1,31 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + s "github.com/smartystreets/assertions" +) + +func TestAccessKeysRights(t *testing.T) { + a := s.New(t) + c := AccessKey{ + Name: "name", + Key: "123456", + Rights: []Right{ + Right("right"), + }, + } + + a.So(c.HasRight(Right("right")), s.ShouldBeTrue) + a.So(c.HasRight(Right("foo")), s.ShouldBeFalse) +} + +func TestRightString(t *testing.T) { + a := s.New(t) + + right := Right("foo") + a.So(right.String(), s.ShouldEqual, "foo") +} diff --git a/core/types/activation.go b/core/types/activation.go new file mode 100644 index 000000000..858953a0e --- /dev/null +++ b/core/types/activation.go @@ -0,0 +1,239 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "encoding/hex" + "errors" + "strings" +) + +// Activation messages are used to notify application of a device activation +type Activation struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + AppEUI AppEUI `json:"app_eui,omitempty"` + DevEUI DevEUI `json:"dev_eui,omitempty"` + DevAddr DevAddr `json:"dev_addr,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` +} + +// DevNonce for LoRaWAN +type DevNonce [2]byte + +// Bytes returns the DevNonce as a byte slice +func (n DevNonce) Bytes() []byte { + return n[:] +} + +func (n DevNonce) String() string { + if n == [2]byte{0, 0} { + return "" + } + return strings.ToUpper(hex.EncodeToString(n.Bytes())) +} + +// GoString implements the GoStringer interface. +func (n DevNonce) GoString() string { + return n.String() +} + +// MarshalText implements the TextMarshaler interface. +func (n DevNonce) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (n *DevNonce) UnmarshalText(data []byte) error { + parsed, err := ParseHEX(string(data), 2) + if err != nil { + return err + } + copy(n[:], parsed[:]) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (n DevNonce) MarshalBinary() ([]byte, error) { + return n.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (n *DevNonce) UnmarshalBinary(data []byte) error { + if len(data) != 2 { + return errors.New("ttn/core: Invalid length for DevNonce") + } + copy(n[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (n *DevNonce) MarshalTo(b []byte) (int, error) { + copy(b, n.Bytes()) + return 2, nil +} + +// Size is used by Protobuf +func (n *DevNonce) Size() int { + return 2 +} + +// Marshal implements the Marshaler interface. +func (n DevNonce) Marshal() ([]byte, error) { + return n.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (n *DevNonce) Unmarshal(data []byte) error { + *n = [2]byte{} // Reset the receiver + return n.UnmarshalBinary(data) +} + +// AppNonce for LoRaWAN +type AppNonce [3]byte + +// Bytes returns the AppNonce as a byte slice +func (n AppNonce) Bytes() []byte { + return n[:] +} + +func (n AppNonce) String() string { + if n == [3]byte{0, 0, 0} { + return "" + } + return strings.ToUpper(hex.EncodeToString(n.Bytes())) +} + +// GoString implements the GoStringer interface. +func (n AppNonce) GoString() string { + return n.String() +} + +// MarshalText implements the TextMarshaler interface. +func (n AppNonce) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (n *AppNonce) UnmarshalText(data []byte) error { + parsed, err := ParseHEX(string(data), 3) + if err != nil { + return err + } + copy(n[:], parsed[:]) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (n AppNonce) MarshalBinary() ([]byte, error) { + return n.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (n *AppNonce) UnmarshalBinary(data []byte) error { + if len(data) != 3 { + return errors.New("ttn/core: Invalid length for AppNonce") + } + copy(n[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (n *AppNonce) MarshalTo(b []byte) (int, error) { + copy(b, n.Bytes()) + return 3, nil +} + +// Size is used by Protobuf +func (n *AppNonce) Size() int { + return 3 +} + +// Marshal implements the Marshaler interface. +func (n AppNonce) Marshal() ([]byte, error) { + return n.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (n *AppNonce) Unmarshal(data []byte) error { + *n = [3]byte{} // Reset the receiver + return n.UnmarshalBinary(data) +} + +// NetID for LoRaWAN +type NetID [3]byte + +var emptyNetID NetID + +// Bytes returns the NetID as a byte slice +func (n NetID) Bytes() []byte { + return n[:] +} + +func (n NetID) String() string { + if n == [3]byte{0, 0, 0} { + return "" + } + return strings.ToUpper(hex.EncodeToString(n.Bytes())) +} + +func (n NetID) IsEmpty() bool { + return n == emptyNetID +} + +// GoString implements the GoStringer interface. +func (n NetID) GoString() string { + return n.String() +} + +// MarshalText implements the TextMarshaler interface. +func (n NetID) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (n *NetID) UnmarshalText(data []byte) error { + parsed, err := ParseHEX(string(data), 3) + if err != nil { + return err + } + copy(n[:], parsed[:]) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (n NetID) MarshalBinary() ([]byte, error) { + return n.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (n *NetID) UnmarshalBinary(data []byte) error { + if len(data) != 3 { + return errors.New("ttn/core: Invalid length for NetID") + } + copy(n[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (n *NetID) MarshalTo(b []byte) (int, error) { + copy(b, n.Bytes()) + return 3, nil +} + +// Size is used by Protobuf +func (n *NetID) Size() int { + return 3 +} + +// Marshal implements the Marshaler interface. +func (n NetID) Marshal() ([]byte, error) { + return n.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (n *NetID) Unmarshal(data []byte) error { + *n = [3]byte{} // Reset the receiver + return n.UnmarshalBinary(data) +} diff --git a/core/types/activation_test.go b/core/types/activation_test.go new file mode 100644 index 000000000..055fd5863 --- /dev/null +++ b/core/types/activation_test.go @@ -0,0 +1,184 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDevNonce(t *testing.T) { + a := New(t) + + // Setup + nonce := DevNonce{1, 2} + str := "0102" + bin := []byte{0x01, 0x02} + + // Bytes + a.So(nonce.Bytes(), ShouldResemble, bin) + + // String + a.So(nonce.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := nonce.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := nonce.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := nonce.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 2) + _, err = nonce.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := nonce.Size() + a.So(s, ShouldEqual, 2) + + // UnmarshalText + utOut := &DevNonce{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &nonce) + + // UnmarshalBinary + ubOut := &DevNonce{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &nonce) + + // Unmarshal + uOut := &DevNonce{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &nonce) +} + +func TestAppNonce(t *testing.T) { + a := New(t) + + // Setup + nonce := AppNonce{1, 2, 3} + str := "010203" + bin := []byte{0x01, 0x02, 0x03} + + // Bytes + a.So(nonce.Bytes(), ShouldResemble, bin) + + // String + a.So(nonce.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := nonce.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := nonce.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := nonce.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 3) + _, err = nonce.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := nonce.Size() + a.So(s, ShouldEqual, 3) + + // UnmarshalText + utOut := &AppNonce{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &nonce) + + // UnmarshalBinary + ubOut := &AppNonce{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &nonce) + + // Unmarshal + uOut := &AppNonce{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &nonce) +} + +func TestNetID(t *testing.T) { + a := New(t) + + // Setup + nid := NetID{1, 2, 3} + str := "010203" + bin := []byte{0x01, 0x02, 0x03} + + // Bytes + a.So(nid.Bytes(), ShouldResemble, bin) + + // String + a.So(nid.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := nid.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := nid.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := nid.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 3) + _, err = nid.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := nid.Size() + a.So(s, ShouldEqual, 3) + + // UnmarshalText + utOut := &NetID{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &nid) + + // UnmarshalBinary + ubOut := &NetID{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &nid) + + // Unmarshal + uOut := &NetID{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &nid) +} diff --git a/core/types/data_rate.go b/core/types/data_rate.go new file mode 100644 index 000000000..27510b682 --- /dev/null +++ b/core/types/data_rate.go @@ -0,0 +1,109 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + "github.com/brocaar/lorawan/band" +) + +type DataRate struct { + SpreadingFactor uint `json:"spreading_factor,omitempty"` + Bandwidth uint `json:"bandwidth,omitempty"` +} + +// ParseDataRate parses a 32-bit hex-encoded string to a Devdatr +func ParseDataRate(input string) (datr *DataRate, err error) { + re := regexp.MustCompile("SF(7|8|9|10|11|12)BW(125|250|500)") + matches := re.FindStringSubmatch(input) + if len(matches) != 3 { + return nil, errors.New("ttn/core: Invalid DataRate") + } + + sf, _ := strconv.ParseUint(matches[1], 10, 64) + bw, _ := strconv.ParseUint(matches[2], 10, 64) + + return &DataRate{ + SpreadingFactor: uint(sf), + Bandwidth: uint(bw), + }, nil +} + +func ConvertDataRate(input band.DataRate) (datr *DataRate, err error) { + if input.Modulation != band.LoRaModulation { + err = errors.New(fmt.Sprintf("ttn/core: %s can not be converted to a LoRa DataRate", input.Modulation)) + } + datr = &DataRate{ + SpreadingFactor: uint(input.SpreadFactor), + Bandwidth: uint(input.Bandwidth), + } + return +} + +// Bytes returns the DataRate as a byte slice +func (datr DataRate) Bytes() []byte { + return []byte(datr.String()) +} + +// String implements the Stringer interface. +func (datr DataRate) String() string { + return fmt.Sprintf("SF%dBW%d", datr.SpreadingFactor, datr.Bandwidth) +} + +// GoString implements the GoStringer interface. +func (datr DataRate) GoString() string { + return datr.String() +} + +// MarshalText implements the TextMarshaler interface. +func (datr DataRate) MarshalText() ([]byte, error) { + return []byte(datr.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (datr *DataRate) UnmarshalText(data []byte) error { + parsed, err := ParseDataRate(string(data)) + if err != nil { + return err + } + *datr = *parsed + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (datr DataRate) MarshalBinary() ([]byte, error) { + return datr.MarshalText() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (datr *DataRate) UnmarshalBinary(data []byte) error { + return datr.UnmarshalText(data) +} + +// MarshalTo is used by Protobuf +func (datr DataRate) MarshalTo(b []byte) (int, error) { + copy(b, datr.Bytes()) + return len(datr.Bytes()), nil +} + +// Size is used by Protobuf +func (datr DataRate) Size() int { + return len(datr.Bytes()) +} + +// Marshal implements the Marshaler interface. +func (datr DataRate) Marshal() ([]byte, error) { + return datr.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (datr *DataRate) Unmarshal(data []byte) error { + *datr = DataRate{} // Reset the receiver + return datr.UnmarshalBinary(data) +} diff --git a/core/types/data_rate_test.go b/core/types/data_rate_test.go new file mode 100644 index 000000000..13e6ca407 --- /dev/null +++ b/core/types/data_rate_test.go @@ -0,0 +1,86 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + "github.com/brocaar/lorawan/band" + . "github.com/smartystreets/assertions" +) + +func TestDataRate(t *testing.T) { + a := New(t) + + // Setup + datr := DataRate{7, 125} + str := "SF7BW125" + bin := []byte("SF7BW125") + + // Bytes + a.So(datr.Bytes(), ShouldResemble, bin) + + // String + a.So(datr.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := datr.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := datr.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := datr.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 8) + _, err = datr.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := datr.Size() + a.So(s, ShouldEqual, 8) + + // Parse + pOut, err := ParseDataRate(str) + a.So(err, ShouldBeNil) + a.So(*pOut, ShouldResemble, datr) + + _, err = ParseDataRate("") + a.So(err, ShouldNotBeNil) + + // Convert + dr := band.DataRate{Modulation: band.LoRaModulation, SpreadFactor: 7, Bandwidth: 125} + cOut, err := ConvertDataRate(dr) + a.So(err, ShouldBeNil) + a.So(*cOut, ShouldResemble, datr) + + // UnmarshalText + utOut := &DataRate{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldResemble, datr) + + err = utOut.UnmarshalText([]byte("")) + a.So(err, ShouldNotBeNil) + + // UnmarshalBinary + ubOut := &DataRate{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldResemble, datr) + + // Unmarshal + uOut := &DataRate{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldResemble, datr) +} diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go new file mode 100644 index 000000000..01acbfc18 --- /dev/null +++ b/core/types/dev_addr.go @@ -0,0 +1,219 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "encoding/hex" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// DevAddr is a non-unique address for LoRaWAN devices. +type DevAddr [4]byte + +// ParseDevAddr parses a 32-bit hex-encoded string to a DevAddr +func ParseDevAddr(input string) (addr DevAddr, err error) { + bytes, err := ParseHEX(input, 4) + if err != nil { + return + } + copy(addr[:], bytes) + return +} + +// Bytes returns the DevAddr as a byte slice +func (addr DevAddr) Bytes() []byte { + return addr[:] +} + +// String implements the Stringer interface. +func (addr DevAddr) String() string { + if addr.IsEmpty() { + return "" + } + return strings.ToUpper(hex.EncodeToString(addr.Bytes())) +} + +// GoString implements the GoStringer interface. +func (addr DevAddr) GoString() string { + return addr.String() +} + +// MarshalText implements the TextMarshaler interface. +func (addr DevAddr) MarshalText() ([]byte, error) { + return []byte(addr.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (addr *DevAddr) UnmarshalText(data []byte) error { + parsed, err := ParseDevAddr(string(data)) + if err != nil { + return err + } + *addr = DevAddr(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (addr DevAddr) MarshalBinary() ([]byte, error) { + return addr.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (addr *DevAddr) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("ttn/core: Invalid length for DevAddr") + } + copy(addr[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (addr DevAddr) MarshalTo(b []byte) (int, error) { + copy(b, addr.Bytes()) + return 4, nil +} + +// Size is used by Protobuf +func (addr DevAddr) Size() int { + return 4 +} + +// Marshal implements the Marshaler interface. +func (addr DevAddr) Marshal() ([]byte, error) { + return addr.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (addr *DevAddr) Unmarshal(data []byte) error { + *addr = [4]byte{} // Reset the receiver + return addr.UnmarshalBinary(data) +} + +var empty DevAddr + +func (addr DevAddr) IsEmpty() bool { + return addr == empty +} + +// DevAddrPrefix is a DevAddr with a prefix length +type DevAddrPrefix struct { + DevAddr DevAddr + Length int +} + +// ParseDevAddrPrefix parses a DevAddr in prefix notation (01020304/24) to a prefix +func ParseDevAddrPrefix(prefixString string) (prefix DevAddrPrefix, err error) { + pattern := regexp.MustCompile("([[:xdigit:]]{8})/([[:digit:]]+)") + matches := pattern.FindStringSubmatch(prefixString) + if len(matches) != 3 { + err = errors.New("Invalid Prefix") + return + } + addr, _ := ParseDevAddr(matches[1]) // errors handled in regexp + prefix.Length, _ = strconv.Atoi(matches[2]) // errors handled in regexp + prefix.DevAddr = addr.Mask(prefix.Length) + return +} + +// Bytes returns the DevAddrPrefix as a byte slice +func (prefix DevAddrPrefix) Bytes() (bytes []byte) { + bytes = append(bytes, byte(prefix.Length)) + bytes = append(bytes, prefix.DevAddr.Bytes()...) + return bytes +} + +// String implements the fmt.Stringer interface +func (prefix DevAddrPrefix) String() string { + var addr string + if prefix.DevAddr.IsEmpty() { + addr = "00000000" + } else { + addr = prefix.DevAddr.String() + } + return fmt.Sprintf("%s/%d", addr, prefix.Length) +} + +// MarshalText implements the TextMarshaler interface. +func (prefix DevAddrPrefix) MarshalText() ([]byte, error) { + return []byte(prefix.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (prefix *DevAddrPrefix) UnmarshalText(data []byte) error { + parsed, err := ParseDevAddrPrefix(string(data)) + if err != nil { + return err + } + *prefix = DevAddrPrefix(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (prefix DevAddrPrefix) MarshalBinary() ([]byte, error) { + return prefix.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (prefix *DevAddrPrefix) UnmarshalBinary(data []byte) error { + if len(data) != 5 { + return errors.New("ttn/core: Invalid length for DevAddrPrefix") + } + copy(prefix.DevAddr[:], data[1:]) + prefix.Length = int(data[0]) + prefix.DevAddr = prefix.DevAddr.Mask(prefix.Length) + return nil +} + +// MarshalTo is used by Protobuf +func (prefix DevAddrPrefix) MarshalTo(b []byte) (int, error) { + copy(b, prefix.Bytes()) + return 4, nil +} + +// Size is used by Protobuf +func (prefix DevAddrPrefix) Size() int { + return 5 +} + +// Marshal implements the Marshaler interface. +func (prefix DevAddrPrefix) Marshal() ([]byte, error) { + return prefix.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (prefix *DevAddrPrefix) Unmarshal(data []byte) error { + prefix.DevAddr = [4]byte{} // Reset the receiver + prefix.Length = 0 + return prefix.UnmarshalBinary(data) +} + +// Mask returns a copy of the DevAddr with only the first "bits" bits +func (addr DevAddr) Mask(bits int) (masked DevAddr) { + return empty.WithPrefix(DevAddrPrefix{addr, bits}) +} + +// WithPrefix returns the DevAddr, but with the first length bits replaced by the Prefix +func (addr DevAddr) WithPrefix(prefix DevAddrPrefix) (prefixed DevAddr) { + k := uint(prefix.Length) + for i := 0; i < 4; i++ { + if k >= 8 { + prefixed[i] = prefix.DevAddr[i] & 0xff + k -= 8 + continue + } + prefixed[i] = (prefix.DevAddr[i] & ^byte(0xff>>k)) | (addr[i] & byte(0xff>>k)) + k = 0 + } + return +} + +// HasPrefix returns true if the DevAddr has a prefix of given length +func (addr DevAddr) HasPrefix(prefix DevAddrPrefix) bool { + return addr.Mask(prefix.Length) == prefix.DevAddr.Mask(prefix.Length) +} diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go new file mode 100644 index 000000000..3a3fd0321 --- /dev/null +++ b/core/types/dev_addr_test.go @@ -0,0 +1,177 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDevAddr(t *testing.T) { + a := New(t) + + // Setup + addr := DevAddr{1, 2, 254, 255} + str := "0102FEFF" + bin := []byte{0x01, 0x02, 0xfe, 0xff} + + // Bytes + a.So(addr.Bytes(), ShouldResemble, bin) + + // String + a.So(addr.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := addr.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := addr.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := addr.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 4) + _, err = addr.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := addr.Size() + a.So(s, ShouldEqual, 4) + + // Parse + pOut, err := ParseDevAddr(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, addr) + + _, err = ParseDevAddr("no-dev-addr") + a.So(err, ShouldNotBeNil) + + // UnmarshalText + utOut := &DevAddr{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, addr) + + // UnmarshalBinary + ubOut := &DevAddr{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, addr) + + // Unmarshal + uOut := &DevAddr{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, addr) + + // Empty + var empty DevAddr + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(addr.IsEmpty(), ShouldEqual, false) + a.So(empty.String(), ShouldEqual, "") +} + +func TestDevAddrMask(t *testing.T) { + a := New(t) + d1 := DevAddr{255, 255, 255, 255} + a.So(d1.Mask(1), ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(d1.Mask(2), ShouldEqual, DevAddr{192, 0, 0, 0}) + a.So(d1.Mask(3), ShouldEqual, DevAddr{224, 0, 0, 0}) + a.So(d1.Mask(4), ShouldEqual, DevAddr{240, 0, 0, 0}) + a.So(d1.Mask(5), ShouldEqual, DevAddr{248, 0, 0, 0}) + a.So(d1.Mask(6), ShouldEqual, DevAddr{252, 0, 0, 0}) + a.So(d1.Mask(7), ShouldEqual, DevAddr{254, 0, 0, 0}) + a.So(d1.Mask(8), ShouldEqual, DevAddr{255, 0, 0, 0}) +} + +func TestDevAddrWithPrefix(t *testing.T) { + a := New(t) + addr := DevAddr{0xAA, 0xAA, 0xAA, 0xAA} + prefix := DevAddr{0x55, 0x55, 0x55, 0x55} + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 4}), ShouldEqual, DevAddr{0x5A, 0xAA, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 8}), ShouldEqual, DevAddr{0x55, 0xAA, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 12}), ShouldEqual, DevAddr{0x55, 0x5A, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 16}), ShouldEqual, DevAddr{0x55, 0x55, 0xAA, 0xAA}) +} + +func TestDevAddrHasPrefix(t *testing.T) { + a := New(t) + addr := DevAddr{1, 2, 3, 4} + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{0, 0, 0, 0}, 0}), ShouldBeTrue) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{1, 2, 3, 0}, 24}), ShouldBeTrue) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{2, 2, 3, 4}, 31}), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{1, 1, 3, 4}, 31}), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{1, 1, 1, 1}, 15}), ShouldBeFalse) +} + +func TestParseDevAddrPrefix(t *testing.T) { + a := New(t) + prefix, err := ParseDevAddrPrefix("XYZ") + a.So(err, ShouldNotBeNil) + prefix, err = ParseDevAddrPrefix("00/bla") + a.So(err, ShouldNotBeNil) + prefix, err = ParseDevAddrPrefix("00/1") + a.So(err, ShouldNotBeNil) + prefix, err = ParseDevAddrPrefix("01020304/1") + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{0, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) + prefix, err = ParseDevAddrPrefix("ff020304/1") + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) +} + +func TestMarshalUnmarshalDevAddrPrefix(t *testing.T) { + a := New(t) + var prefix DevAddrPrefix + + txt, err := prefix.MarshalText() + a.So(err, ShouldBeNil) + a.So(string(txt), ShouldEqual, "00000000/0") + + err = prefix.UnmarshalText([]byte("ff556677/1")) + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) + + txt, err = prefix.MarshalText() + a.So(err, ShouldBeNil) + a.So(string(txt), ShouldEqual, "80000000/1") + + prefix = DevAddrPrefix{} + + bin, err := prefix.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(bin, ShouldResemble, []byte{0x00, 0x00, 0x00, 0x00, 0x00}) + + err = prefix.UnmarshalBinary([]byte{0x01, 0xff, 0x55, 0x66, 0x77}) + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) + + bin, err = prefix.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(bin, ShouldResemble, []byte{0x01, 0x80, 0x00, 0x00, 0x00}) + + prefix = DevAddrPrefix{} + + bin, err = prefix.Marshal() + a.So(err, ShouldBeNil) + a.So(bin, ShouldResemble, []byte{0x00, 0x00, 0x00, 0x00, 0x00}) + + err = prefix.Unmarshal([]byte{0x01, 0x80, 0x00, 0x00, 0x00}) + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) +} diff --git a/core/types/downlink_message.go b/core/types/downlink_message.go new file mode 100644 index 000000000..986ae6489 --- /dev/null +++ b/core/types/downlink_message.go @@ -0,0 +1,13 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// DownlinkMessage represents an application-layer downlink message +type DownlinkMessage struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + PayloadRaw []byte `json:"payload_raw,omitempty"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` +} diff --git a/core/types/eui.go b/core/types/eui.go new file mode 100644 index 000000000..61a9a9717 --- /dev/null +++ b/core/types/eui.go @@ -0,0 +1,270 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "encoding/hex" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// EUI64 is used for AppEUIs and DevEUIs. +type EUI64 [8]byte + +// AppEUI is a unique identifier for applications. +type AppEUI EUI64 + +// DevEUI is a unique identifier for devices. +type DevEUI EUI64 + +// ParseEUI64 parses a 64-bit hex-encoded string to an EUI64. +func ParseEUI64(input string) (eui EUI64, err error) { + bytes, err := ParseHEX(input, 8) + if err != nil { + return + } + copy(eui[:], bytes) + return +} + +// Bytes returns the EUI64 as a byte slice +func (eui EUI64) Bytes() []byte { + return eui[:] +} + +func (eui EUI64) String() string { + if eui.IsEmpty() { + return "" + } + return strings.ToUpper(hex.EncodeToString(eui.Bytes())) +} + +// GoString implements the GoStringer interface. +func (eui EUI64) GoString() string { + return eui.String() +} + +// MarshalText implements the TextMarshaler interface. +func (eui EUI64) MarshalText() ([]byte, error) { + return []byte(eui.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *EUI64) UnmarshalText(data []byte) error { + parsed, err := ParseEUI64(string(data)) + if err != nil { + return err + } + *eui = EUI64(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui EUI64) MarshalBinary() ([]byte, error) { + return eui.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *EUI64) UnmarshalBinary(data []byte) error { + if len(data) != 8 { + return errors.New("ttn/core: Invalid length for EUI64") + } + copy(eui[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (eui *EUI64) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *EUI64) Size() int { + return 8 +} + +// Marshal implements the Marshaler interface. +func (eui EUI64) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *EUI64) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} + +// ParseAppEUI parses a 64-bit hex-encoded string to an AppEUI +func ParseAppEUI(input string) (eui AppEUI, err error) { + eui64, err := ParseEUI64(input) + if err != nil { + return + } + eui = AppEUI(eui64) + return +} + +// Bytes returns the AppEUI as a byte slice +func (eui AppEUI) Bytes() []byte { + return EUI64(eui).Bytes() +} + +// String implements the Stringer interface. +func (eui AppEUI) String() string { + return EUI64(eui).String() +} + +// GoString implements the GoStringer interface. +func (eui AppEUI) GoString() string { + return eui.String() +} + +// MarshalText implements the TextMarshaler interface. +func (eui AppEUI) MarshalText() ([]byte, error) { + return EUI64(eui).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *AppEUI) UnmarshalText(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *eui = AppEUI(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui AppEUI) MarshalBinary() ([]byte, error) { + return EUI64(eui).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *AppEUI) UnmarshalBinary(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *eui = AppEUI(e) + return nil +} + +// MarshalTo is used by Protobuf +func (eui *AppEUI) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *AppEUI) Size() int { + return 8 +} + +// Marshal implements the Marshaler interface. +func (eui AppEUI) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *AppEUI) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} + +// ParseDevEUI parses a 64-bit hex-encoded string to an DevEUI +func ParseDevEUI(input string) (eui DevEUI, err error) { + eui64, err := ParseEUI64(input) + if err != nil { + return + } + eui = DevEUI(eui64) + return +} + +// Bytes returns the DevEUI as a byte slice +func (eui DevEUI) Bytes() []byte { + return EUI64(eui).Bytes() +} + +// String implements the Stringer interface. +func (eui DevEUI) String() string { + return EUI64(eui).String() +} + +// GoString implements the GoStringer interface. +func (eui DevEUI) GoString() string { + return eui.String() +} + +// MarshalText implements the TextMarshaler interface. +func (eui DevEUI) MarshalText() ([]byte, error) { + return EUI64(eui).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *DevEUI) UnmarshalText(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *eui = DevEUI(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui DevEUI) MarshalBinary() ([]byte, error) { + return EUI64(eui).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *DevEUI) UnmarshalBinary(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *eui = DevEUI(e) + return nil +} + +// MarshalTo is used by Protobuf +func (eui *DevEUI) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *DevEUI) Size() int { + return 8 +} + +// Marshal implements the Marshaler interface. +func (eui DevEUI) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *DevEUI) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} + +var emptyEUI64 EUI64 + +func (eui EUI64) IsEmpty() bool { + return eui == emptyEUI64 +} + +func (eui DevEUI) IsEmpty() bool { + return EUI64(eui).IsEmpty() +} + +func (eui AppEUI) IsEmpty() bool { + return EUI64(eui).IsEmpty() +} diff --git a/core/types/eui_test.go b/core/types/eui_test.go new file mode 100644 index 000000000..227567912 --- /dev/null +++ b/core/types/eui_test.go @@ -0,0 +1,214 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestEUI64(t *testing.T) { + a := New(t) + + // Setup + eui := EUI64{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 8) + _, err = eui.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := eui.Size() + a.So(s, ShouldEqual, 8) + + // Parse + pOut, err := ParseEUI64(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldResemble, eui) + + // UnmarshalText + utOut := &EUI64{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &eui) + + // UnmarshalBinary + ubOut := &EUI64{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &eui) + + // Unmarshal + uOut := &EUI64{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &eui) + + // IsEmpty + var empty EUI64 + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) +} + +func TestAppEUI(t *testing.T) { + a := New(t) + + // Setup + eui := AppEUI{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 8) + _, err = eui.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := eui.Size() + a.So(s, ShouldEqual, 8) + + // Parse + pOut, err := ParseAppEUI(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, eui) + + // UnmarshalText + utOut := &AppEUI{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, eui) + + // UnmarshalBinary + ubOut := &AppEUI{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, eui) + + // Unmarshal + uOut := &AppEUI{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, eui) + + // IsEmpty + var empty AppEUI + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) +} + +func TestDevEUI(t *testing.T) { + a := New(t) + + // Setup + eui := DevEUI{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 8) + _, err = eui.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := eui.Size() + a.So(s, ShouldEqual, 8) + + // Parse + pOut, err := ParseDevEUI(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, eui) + + // UnmarshalText + utOut := &DevEUI{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, eui) + + // UnmarshalBinary + ubOut := &DevEUI{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, eui) + + // Unmarshal + uOut := &DevEUI{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, eui) + + // IsEmpty + var empty DevEUI + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) +} diff --git a/core/types/event.go b/core/types/event.go new file mode 100644 index 000000000..696969809 --- /dev/null +++ b/core/types/event.go @@ -0,0 +1,56 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// EventType represents the type of event +type EventType string + +// Event types +const ( + UplinkErrorEvent EventType = "up/errors" + DownlinkScheduledEvent EventType = "down/scheduled" + DownlinkSentEvent EventType = "down/sent" + DownlinkErrorEvent EventType = "down/errors" + DownlinkAckEvent EventType = "down/acks" + ActivationEvent EventType = "activations" + ActivationErrorEvent EventType = "activations/errors" +) + +// DeviceEvent represents an application-layer event message for a device event +type DeviceEvent struct { + AppID string + DevID string + Event EventType + Data interface{} +} + +// ErrorEventData is added to error events +type ErrorEventData struct { + Error string `json:"error"` +} + +// ActivationEventData is added to activation events +type ActivationEventData struct { + AppEUI AppEUI `json:"app_eui"` + DevEUI DevEUI `json:"dev_eui"` + DevAddr DevAddr `json:"dev_addr"` + Metadata Metadata `json:"metadata"` +} + +// DownlinkEventConfigInfo contains configuration information for a downlink message, all fields are optional +type DownlinkEventConfigInfo struct { + Modulation string `json:"modulation,omitempty"` + DataRate string `json:"data_rate,omitempty"` + BitRate uint `json:"bit_rate,omitempty"` + FCnt uint `json:"counter,omitempty"` + Frequency uint `json:"frequency,omitempty"` + Power int `json:"power,omitempty"` +} + +// DownlinkEventData is added to downlink events +type DownlinkEventData struct { + Payload []byte `json:"payload"` + GatewayID string `json:"gateway_id"` + Config DownlinkEventConfigInfo `json:"config"` +} diff --git a/core/types/gateway_metadata.go b/core/types/gateway_metadata.go new file mode 100644 index 000000000..4c7cb9809 --- /dev/null +++ b/core/types/gateway_metadata.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// GatewayMetadata contains metadata for each gateway that received a message +type GatewayMetadata struct { + GtwID string `json:"gtw_id,omitempty"` + Timestamp uint32 `json:"timestamp,omitempty"` + Time JSONTime `json:"time,omitempty"` + Channel uint32 `json:"channel"` + RSSI float32 `json:"rssi,omitempty"` + SNR float32 `json:"snr,omitempty"` + RFChain uint32 `json:"rf_chain,omitempty"` + LocationMetadata +} diff --git a/core/types/json_time.go b/core/types/json_time.go new file mode 100644 index 000000000..459b92413 --- /dev/null +++ b/core/types/json_time.go @@ -0,0 +1,40 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import "time" + +// JSONTime is a time.Time that marshals to/from RFC3339Nano format +type JSONTime time.Time + +// MarshalText implements the encoding.TextMarshaler interface +func (t JSONTime) MarshalText() ([]byte, error) { + if time.Time(t).IsZero() || time.Time(t).Unix() == 0 { + return []byte{}, nil + } + stamp := time.Time(t).UTC().Format(time.RFC3339Nano) + return []byte(stamp), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (t *JSONTime) UnmarshalText(text []byte) error { + if len(text) == 0 { + *t = JSONTime{} + return nil + } + time, err := time.Parse(time.RFC3339Nano, string(text)) + if err != nil { + return err + } + *t = JSONTime(time) + return nil +} + +// BuildTime builds a new JSONTime +func BuildTime(unixNano int64) JSONTime { + if unixNano == 0 { + return JSONTime{} + } + return JSONTime(time.Unix(0, 0).Add(time.Duration(unixNano)).UTC()) +} diff --git a/core/types/json_time_test.go b/core/types/json_time_test.go new file mode 100644 index 000000000..7361ea8f6 --- /dev/null +++ b/core/types/json_time_test.go @@ -0,0 +1,35 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "encoding/json" + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestJSONTime(t *testing.T) { + a := New(t) + + a.So(BuildTime(0), ShouldResemble, JSONTime(time.Time{})) + + data, err := json.Marshal(JSONTime{}) + a.So(err, ShouldBeNil) + a.So(string(data), ShouldResemble, `""`) + + data, err = json.Marshal(BuildTime(0)) + a.So(err, ShouldBeNil) + a.So(string(data), ShouldResemble, `""`) + + data, err = json.Marshal(BuildTime(1465831736000000000)) + a.So(err, ShouldBeNil) + a.So(string(data), ShouldResemble, `"2016-06-13T15:28:56Z"`) + + var time JSONTime + err = json.Unmarshal([]byte(`"2016-06-13T15:28:56Z"`), &time) + a.So(err, ShouldBeNil) + a.So(time, ShouldResemble, BuildTime(1465831736000000000)) +} diff --git a/core/types/keys.go b/core/types/keys.go new file mode 100644 index 000000000..a34b11ebb --- /dev/null +++ b/core/types/keys.go @@ -0,0 +1,354 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "encoding/hex" + "github.com/TheThingsNetwork/ttn/utils/errors" + "strings" +) + +// AES128Key is an 128 bit AES key. +type AES128Key [16]byte + +// AppKey (Application Key) is used for LoRaWAN OTAA. +type AppKey AES128Key + +// NwkSKey (Network Session Key) is used for LoRaWAN MIC calculation. +type NwkSKey AES128Key + +// AppSKey (Application Session Key) is used for LoRaWAN payload encryption. +type AppSKey AES128Key + +// ParseAES128Key parses a 128-bit hex-encoded string to an AES128Key +func ParseAES128Key(input string) (key AES128Key, err error) { + bytes, err := ParseHEX(input, 16) + if err != nil { + return + } + copy(key[:], bytes) + return +} + +// Bytes returns the AES128Key as a byte slice +func (key AES128Key) Bytes() []byte { + return key[:] +} + +// String implements the Stringer interface. +func (key AES128Key) String() string { + if key.IsEmpty() { + return "" + } + return strings.ToUpper(hex.EncodeToString(key.Bytes())) +} + +// GoString implements the GoStringer interface. +func (key AES128Key) GoString() string { + return key.String() +} + +// MarshalText implements the TextMarshaler interface. +func (key AES128Key) MarshalText() ([]byte, error) { + return []byte(key.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *AES128Key) UnmarshalText(data []byte) error { + parsed, err := ParseAES128Key(string(data)) + if err != nil { + return err + } + *key = AES128Key(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key AES128Key) MarshalBinary() ([]byte, error) { + return key.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *AES128Key) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return errors.New("ttn/core: Invalid length for AES128Key") + } + copy(key[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (key *AES128Key) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *AES128Key) Size() int { + return 16 +} + +// Marshal implements the Marshaler interface. +func (key AES128Key) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *AES128Key) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +// ParseAppKey parses a 64-bit hex-encoded string to an AppKey +func ParseAppKey(input string) (key AppKey, err error) { + aes128key, err := ParseAES128Key(input) + if err != nil { + return + } + key = AppKey(aes128key) + return +} + +// Bytes returns the AppKey as a byte slice +func (key AppKey) Bytes() []byte { + return AES128Key(key).Bytes() +} + +func (key AppKey) String() string { + return AES128Key(key).String() +} + +// GoString implements the GoStringer interface. +func (key AppKey) GoString() string { + return key.String() +} + +// MarshalText implements the TextMarshaler interface. +func (key AppKey) MarshalText() ([]byte, error) { + return AES128Key(key).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *AppKey) UnmarshalText(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *key = AppKey(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key AppKey) MarshalBinary() ([]byte, error) { + return AES128Key(key).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *AppKey) UnmarshalBinary(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *key = AppKey(e) + return nil +} + +// MarshalTo is used by Protobuf +func (key *AppKey) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *AppKey) Size() int { + return 16 +} + +// Marshal implements the Marshaler interface. +func (key AppKey) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *AppKey) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +// ParseAppSKey parses a 64-bit hex-encoded string to an AppSKey +func ParseAppSKey(input string) (key AppSKey, err error) { + aes128key, err := ParseAES128Key(input) + if err != nil { + return + } + key = AppSKey(aes128key) + return +} + +// Bytes returns the AppSKey as a byte slice +func (key AppSKey) Bytes() []byte { + return AES128Key(key).Bytes() +} + +func (key AppSKey) String() string { + return AES128Key(key).String() +} + +// GoString implements the GoStringer interface. +func (key AppSKey) GoString() string { + return key.String() +} + +// MarshalText implements the TextMarshaler interface. +func (key AppSKey) MarshalText() ([]byte, error) { + return AES128Key(key).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *AppSKey) UnmarshalText(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *key = AppSKey(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key AppSKey) MarshalBinary() ([]byte, error) { + return AES128Key(key).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *AppSKey) UnmarshalBinary(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *key = AppSKey(e) + return nil +} + +// MarshalTo is used by Protobuf +func (key *AppSKey) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *AppSKey) Size() int { + return 16 +} + +// Marshal implements the Marshaler interface. +func (key AppSKey) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *AppSKey) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +// ParseNwkSKey parses a 64-bit hex-encoded string to an NwkSKey +func ParseNwkSKey(input string) (key NwkSKey, err error) { + aes128key, err := ParseAES128Key(input) + if err != nil { + return + } + key = NwkSKey(aes128key) + return +} + +// Bytes returns the NwkSKey as a byte slice +func (key NwkSKey) Bytes() []byte { + return AES128Key(key).Bytes() +} + +// String implements the Stringer interface. +func (key NwkSKey) String() string { + return AES128Key(key).String() +} + +// GoString implements the GoStringer interface. +func (key NwkSKey) GoString() string { + return key.String() +} + +// MarshalText implements the TextMarshaler interface. +func (key NwkSKey) MarshalText() ([]byte, error) { + return AES128Key(key).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *NwkSKey) UnmarshalText(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *key = NwkSKey(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key NwkSKey) MarshalBinary() ([]byte, error) { + return AES128Key(key).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *NwkSKey) UnmarshalBinary(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *key = NwkSKey(e) + return nil +} + +// MarshalTo is used by Protobuf +func (key *NwkSKey) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *NwkSKey) Size() int { + return 16 +} + +// Marshal implements the Marshaler interface. +func (key NwkSKey) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *NwkSKey) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +var emptyAES AES128Key + +func (key AES128Key) IsEmpty() bool { + return key == emptyAES +} + +func (key AppKey) IsEmpty() bool { + return AES128Key(key).IsEmpty() +} + +func (key AppSKey) IsEmpty() bool { + return AES128Key(key).IsEmpty() +} + +func (key NwkSKey) IsEmpty() bool { + return AES128Key(key).IsEmpty() +} diff --git a/core/types/keys_test.go b/core/types/keys_test.go new file mode 100644 index 000000000..5c637611e --- /dev/null +++ b/core/types/keys_test.go @@ -0,0 +1,282 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestAES128Key(t *testing.T) { + a := New(t) + + // Setup + key := AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + + // Parse + pOut, err := ParseAES128Key(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &AES128Key{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &AES128Key{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &AES128Key{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty AES128Key + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) +} + +func TestAppKey(t *testing.T) { + a := New(t) + + // Setup + key := AppKey{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + + // Parse + pOut, err := ParseAppKey(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &AppKey{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &AppKey{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &AppKey{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty AppKey + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) +} + +func TestNwkSKey(t *testing.T) { + a := New(t) + + // Setup + key := NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + + // Parse + pOut, err := ParseNwkSKey(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &NwkSKey{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &NwkSKey{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &NwkSKey{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty NwkSKey + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) +} + +func TestAppSKey(t *testing.T) { + a := New(t) + + // Setup + key := AppSKey{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + + // Parse + pOut, err := ParseAppSKey(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &AppSKey{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &AppSKey{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &AppSKey{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty AppSKey + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) +} diff --git a/core/types/location_metadata.go b/core/types/location_metadata.go new file mode 100644 index 000000000..f522a72c8 --- /dev/null +++ b/core/types/location_metadata.go @@ -0,0 +1,11 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// LocationMetadata contains GPS coordinates +type LocationMetadata struct { + Latitude float32 `json:"latitude,omitempty"` + Longitude float32 `json:"longitude,omitempty"` + Altitude int32 `json:"altitude,omitempty"` +} diff --git a/core/types/metadata.go b/core/types/metadata.go new file mode 100644 index 000000000..c9935a93e --- /dev/null +++ b/core/types/metadata.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// Metadata contains metadata of a message +type Metadata struct { + Time JSONTime `json:"time,omitempty,omitempty"` + Frequency float32 `json:"frequency,omitempty"` + Modulation string `json:"modulation,omitempty"` + DataRate string `json:"data_rate,omitempty"` + Bitrate uint32 `json:"bit_rate,omitempty"` + CodingRate string `json:"coding_rate,omitempty"` + Gateways []GatewayMetadata `json:"gateways,omitempty"` + LocationMetadata +} diff --git a/core/types/parse_hex.go b/core/types/parse_hex.go new file mode 100644 index 000000000..87a6ababb --- /dev/null +++ b/core/types/parse_hex.go @@ -0,0 +1,28 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "encoding/hex" + "fmt" + "regexp" +) + +// ParseHEX parses a string "input" to a byteslice with length "length". +func ParseHEX(input string, length int) ([]byte, error) { + if input == "" { + return make([]byte, length), nil + } + + pattern := regexp.MustCompile(fmt.Sprintf("^[[:xdigit:]]{%d}$", length*2)) + + valid := pattern.MatchString(input) + if !valid { + return nil, fmt.Errorf("Invalid input: %s", input) + } + + slice, _ := hex.DecodeString(input) + + return slice, nil +} diff --git a/core/types/parse_hex_test.go b/core/types/parse_hex_test.go new file mode 100644 index 000000000..14c18ba82 --- /dev/null +++ b/core/types/parse_hex_test.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestParseHex(t *testing.T) { + a := New(t) + b, err := ParseHEX("AABC", 2) + a.So(err, ShouldBeNil) + a.So(b, ShouldResemble, []byte{0xaa, 0xbc}) + + b, err = ParseHEX("", 2) + a.So(err, ShouldBeNil) + a.So(b, ShouldResemble, []byte{0x00, 0x00}) + + _, err = ParseHEX("ab", 2) + a.So(err, ShouldNotBeNil) +} diff --git a/core/types/uplink_message.go b/core/types/uplink_message.go new file mode 100644 index 000000000..f20129e8f --- /dev/null +++ b/core/types/uplink_message.go @@ -0,0 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// UplinkMessage represents an application-layer uplink message +type UplinkMessage struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + FCnt uint32 `json:"counter"` + PayloadRaw []byte `json:"payload_raw"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` +} diff --git a/doc.go b/doc.go new file mode 100644 index 000000000..d2b3deb6b --- /dev/null +++ b/doc.go @@ -0,0 +1,7 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +/* +Package ttn contains the backend server systems for The Things Network. +*/ +package main diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..687a902f8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,118 @@ +version: '2' +services: + redis: + image: redis + command: redis-server --appendonly yes + ports: + - "6379:6379" + volumes: + - ./.env/redis:/data + rabbitmq: + image: thethingsnetwork/rabbitmq + hostname: rabbitserver + ports: + - "1883:1883" + - "5672:5672" + - "15672:15672" + volumes: + - /var/lib/rabbitmq + discovery: + image: thethingsnetwork/ttn + hostname: discovery + working_dir: /root + command: discovery --config ./.env/discovery/dev.yml + depends_on: + - redis + environment: + - TERM + - TTN_DISCOVERY_REDIS_ADDRESS=redis:6379 + ports: + - "1900:1900" + - "8080:8080" + volumes: + - "./.env/:/root/.env/" + router: + image: thethingsnetwork/ttn + hostname: router + working_dir: /root + command: router --config ./.env/router/dev.yml + depends_on: + - discovery + environment: + - TERM + - TTN_DISCOVERY_ADDRESS=discovery:1900 + - TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE=router + ports: + - "1901:1901" + volumes: + - "./.env/:/root/.env/" + broker: + image: thethingsnetwork/ttn + hostname: broker + working_dir: /root + command: broker --config ./.env/broker/dev.yml + depends_on: + - discovery + - networkserver + environment: + - TERM + - TTN_DISCOVERY_ADDRESS=discovery:1900 + - TTN_BROKER_SERVER_ADDRESS_ANNOUNCE=broker + - TTN_BROKER_NETWORKSERVER_ADDRESS=networkserver:1903 + ports: + - "1902:1902" + volumes: + - "./.env/:/root/.env/" + networkserver: + image: thethingsnetwork/ttn + hostname: networkserver + working_dir: /root + command: networkserver --config ./.env/networkserver/dev.yml + depends_on: + - redis + environment: + - TERM + - TTN_DISCOVERY_ADDRESS=discovery:1900 + - TTN_NETWORKSERVER_REDIS_ADDRESS=redis:6379 + ports: + - "1903:1903" + volumes: + - "./.env/:/root/.env/" + handler: + image: thethingsnetwork/ttn + hostname: handler + working_dir: /root + command: handler --config ./.env/handler/dev.yml + depends_on: + - discovery + - redis + - rabbitmq + environment: + - TERM + - TTN_DISCOVERY_ADDRESS=discovery:1900 + - TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE=handler + - TTN_HANDLER_REDIS_ADDRESS=redis:6379 + - TTN_HANDLER_MQTT_ADDRESS=rabbitmq:1883 + - TTN_HANDLER_AMQP_ADDRESS=rabbitmq:5672 + ports: + - "1904:1904" + - "8084:8084" + volumes: + - "./.env/:/root/.env/" + bridge: + image: thethingsnetwork/lora-gateway-bridge + hostname: bridge + ports: + - "1700:1700/udp" + restart: always + depends_on: [ router ] + environment: + UDP_BIND: ":1700" + TTN_DISCOVERY_SERVER: discovery:1900 + TTN_ROUTER: dev + # The following environment variables make that communication for gateway + # "0102030405060708" is forwarded as authenticated communication for + # gateway "dev" + TTN_GATEWAY_EUI: 0102030405060708 + TTN_GATEWAY_ID: dev + TTN_GATEWAY_TOKEN: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImdhdGV3YXkiLCJpYXQiOjE0NzY0Mzk0Mzh9.kEOiLe9j4qRElZOt_bAXmZlva1nV6duIL0MDVa3bx2SEWC3qredaBWXWq4FmV4PKeI_zndovQtOoValH0B_6MW6vXuWL1wYzV6foTH5gQdxmn-iuQ1AmAIYbZeyHl9a-NPqDgkXLwKmo2iB1hUi9wV6HXfIOalguDtGJbmMfJ2tommsgmuNCXd-2zqhStSy8ArpROFXPm7voGDTcgm4hfchr7zhn-Er76R-eJa3RZ1Seo9BsiWrQ0N3VDSuh7ycCakZtkaLD4OTutAemcbzbrNJSOCvvZr8Asn-RmMkjKUdTN4Bgn3qlacIQ9iZikPLT8XyjFkj-8xjs3KAobWg40A diff --git a/main.go b/main.go new file mode 100644 index 000000000..03e5f21ee --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "github.com/TheThingsNetwork/ttn/cmd" + "github.com/spf13/viper" +) + +var ( + version = "2.x.x" + gitBranch = "unknown" + gitCommit = "unknown" + buildDate = "unknown" +) + +func main() { + viper.Set("version", version) + viper.Set("gitBranch", gitBranch) + viper.Set("gitCommit", gitCommit) + viper.Set("buildDate", buildDate) + cmd.Execute() +} diff --git a/mqtt/README.md b/mqtt/README.md new file mode 100644 index 000000000..f6c42bb93 --- /dev/null +++ b/mqtt/README.md @@ -0,0 +1,236 @@ +# API Reference + +* Host: `.thethings.network`, where `` is last part of the handler you registered your application to, e.g. `eu`. +* Port: `1883` or `8883` for TLS +* PEM encoded CA certificate for TLS: [mqtt-ca.pem](https://preview.console.thethingsnetwork.org/mqtt-ca.pem) +* Username: Application ID +* Password: Application Access Key + +## Uplink Messages + +**Topic:** `/devices//up` + +**Message:** + +```js +{ + "port": 1, // LoRaWAN FPort + "counter": 2, // LoRaWAN frame counter + "payload_raw": "AQIDBA==", // Base64 encoded payload: [0x01, 0x02, 0x03, 0x04] + "payload_fields": {}, // Object containing the results from the payload functions - left out when empty + "metadata": { + "time": "1970-01-01T00:00:00Z", // Time when the server received the message + "frequency": 868.1, // Frequency at which the message was sent + "modulation": "LORA", // Modulation that was used - currently only LORA. In the future we will support FSK as well + "data_rate": "SF7BW125", // Data rate that was used - if LORA modulation + "bit_rate": 50000, // Bit rate that was used - if FSK modulation + "coding_rate": "4/5", // Coding rate that was used + "gateways": [ + { + "id": "ttn-herengracht-ams", // EUI of the gateway + "timestamp": 12345, // Timestamp when the gateway received the message + "time": "1970-01-01T00:00:00Z", // Time when the gateway received the message - left out when gateway does not have synchronized time + "channel": 0, // Channel where the gateway received the message + "rssi": -25, // Signal strength of the received message + "snr": 5, // Signal to noise ratio of the received message + "rf_chain": 0, // RF chain where the gateway received the message + }, + //...more if received by more gateways... + ] + } +} +``` + +Note: Some values may be omitted if they are `null`, `""` or `0`. + +**Usage (Mosquitto):** `mosquitto_sub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/up'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req types.UplinkMessage) { + // Do something with the uplink message +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe") +} +``` + +### Uplink Fields + +Each uplink field will be published to its own topic `my-app-id/devices/my-dev-id/up/`. The payload will be a string with the value in a JSON-style encoding. + +If your fields look like the following: + +```js +{ + "water": true, + "analog": [0, 255, 500, 1000], + "gps": { + "lat": 52.3736735, + "lon": 4.886663 + }, + "text": "why are you using text?" +} +``` + +you will see this on MQTT: + +* `my-app-id/devices/my-dev-id/up/water`: `true` +* `my-app-id/devices/my-dev-id/up/analog`: `[0, 255, 500, 1000]` +* `my-app-id/devices/my-dev-id/up/gps`: `{"lat":52.3736735,"lon":4.886663}` +* `my-app-id/devices/my-dev-id/up/gps/lat`: `52.3736735` +* `my-app-id/devices/my-dev-id/up/gps/lon`: `4.886663` +* `my-app-id/devices/my-dev-id/up/text`: `"why are you using text?"` + +## Downlink Messages + +**Topic:** `/devices//down` + +**Message:** + +```js +{ + "port": 1, // LoRaWAN FPort + "payload_raw": "AQIDBA==", // Base64 encoded payload: [0x01, 0x02, 0x03, 0x04] +} +``` + +**Usage (Mosquitto):** `mosquitto_pub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_raw":"AQIDBA=="}'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.PublishDownlink(types.DownlinkMessage{ + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not publish") +} +``` + +### Downlink Fields + +Instead of `payload_raw` you can also use `payload_fields` with an object of fields. This requires the application to be configured with an Encoder Payload Function which encodes the fields into a Buffer. + +**Message:** + +```js +{ + "port": 1, // LoRaWAN FPort + "payload_fields": { + "led": true + } +} +``` + +**Usage (Mosquitto):** `mosquitto_pub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_fields":{"led":true}}'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.PublishDownlink(types.DownlinkMessage{ + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + Fields: map[string]interface{}{ + "led": true, + }, +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not publish") +} +``` + +## Device Activations + +**Topic:** `/devices//events/activations` + +**Message:** + +```js +{ + "app_eui": "0102030405060708", // EUI of the application + "dev_eui": "0102030405060708", // EUI of the device + "dev_addr": "26001716", // Assigned address of the device + "metadata": { + // Same as with Uplink Message + } +} +``` + +**Usage (Mosquitto):** `mosquitto_sub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/events/activations'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.SubscribeDeviceActivations("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req Activation) { + // Do something with the activation +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe") +} +``` + +## Device Events + +### Downlink Events + +**Downlink Scheduled:** `/devices//events/down/scheduled` +payload: _null_ + +**Downlink Sent:** `/devices//events/down/sent` + +```js +{ + "payload": "Base64 encoded LoRaWAN packet", + "gateway_id": "some-gateway", + "config": { + "modulation": "LORA", + "data_rate": "SF7BW125", + "counter": 123, + "frequency": 868300000, + "power": 14 + } +} +``` + +**Downlink Acknowledgements:** `/devices//events/down/acks` +payload: _null_ + +### Error Events + +The payload of error events is a JSON object with the error's description. + +**Uplink Errors:** `/devices//events/up/errors` +**Downlink Errors:** `/devices//events/down/errors` +**Activation Errors:** `/devices//events/activations/errors` + +Example: `{"error":"Activation DevNonce not valid: already used"}` diff --git a/mqtt/activations.go b/mqtt/activations.go new file mode 100644 index 000000000..92313b221 --- /dev/null +++ b/mqtt/activations.go @@ -0,0 +1,62 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// ActivationHandler is called for activations +type ActivationHandler func(client Client, appID string, devID string, req types.Activation) + +// PublishActivation publishes an activation +func (c *DefaultClient) PublishActivation(activation types.Activation) Token { + appID := activation.AppID + devID := activation.DevID + activation.AppID = "" + activation.DevID = "" + return c.PublishDeviceEvent(appID, devID, types.ActivationEvent, activation) +} + +// SubscribeDeviceActivations subscribes to all activations for the given application and device +func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { + return c.SubscribeDeviceEvents(appID, devID, types.ActivationEvent, func(_ Client, appID string, devID string, _ types.EventType, payload []byte) { + activation := types.Activation{} + if err := json.Unmarshal(payload, &activation); err != nil { + c.ctx.Warnf("Could not unmarshal activation (%s).", err.Error()) + return + } + activation.AppID = appID + activation.DevID = devID + // Call the Activation handler + handler(c, appID, devID, activation) + }) +} + +// SubscribeAppActivations subscribes to all activations for the given application +func (c *DefaultClient) SubscribeAppActivations(appID string, handler ActivationHandler) Token { + return c.SubscribeDeviceActivations(appID, "", handler) +} + +// SubscribeActivations subscribes to all activations that the current user has access to +func (c *DefaultClient) SubscribeActivations(handler ActivationHandler) Token { + return c.SubscribeDeviceActivations("", "", handler) +} + +// UnsubscribeDeviceActivations unsubscribes from the activations for the given application and device +func (c *DefaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { + return c.UnsubscribeDeviceEvents(appID, devID, types.ActivationEvent) +} + +// UnsubscribeAppActivations unsubscribes from the activations for the given application +func (c *DefaultClient) UnsubscribeAppActivations(appID string) Token { + return c.UnsubscribeDeviceEvents(appID, "", types.ActivationEvent) +} + +// UnsubscribeActivations unsubscribes from the activations that the current user has access to +func (c *DefaultClient) UnsubscribeActivations() Token { + return c.UnsubscribeDeviceEvents("", "", types.ActivationEvent) +} diff --git a/mqtt/activations_test.go b/mqtt/activations_test.go new file mode 100644 index 000000000..cf9edc2a0 --- /dev/null +++ b/mqtt/activations_test.go @@ -0,0 +1,152 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +// Activations pub/sub + +func TestPublishActivations(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + dataActivations := types.Activation{ + AppID: "someid", + DevID: "someid", + Metadata: types.Metadata{DataRate: "SF7BW125"}, + } + + token := c.PublishActivation(dataActivations) + waitForOK(token, a) + + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDeviceActivations(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req types.Activation) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeDeviceActivations("someid", "someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppActivations(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req types.Activation) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeAppActivations("someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeActivations(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeActivations(func(client Client, appID string, devID string, req types.Activation) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeActivations() + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubActivations(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(1) + + subToken := c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req types.Activation) { + a.So(appID, ShouldResemble, "app5") + a.So(devID, ShouldResemble, "dev1") + + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishActivation(types.Activation{ + AppID: "app5", + DevID: "dev1", + Metadata: types.Metadata{DataRate: "SF7BW125"}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeDeviceActivations("app5", "dev1") + waitForOK(unsubToken, a) +} + +func TestPubSubAppActivations(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(2) + + subToken := c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req types.Activation) { + a.So(appID, ShouldResemble, "app6") + a.So(req.Metadata.DataRate, ShouldEqual, "SF7BW125") + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishActivation(types.Activation{ + AppID: "app6", + DevID: "dev1", + Metadata: types.Metadata{DataRate: "SF7BW125"}, + }) + waitForOK(pubToken, a) + pubToken = c.PublishActivation(types.Activation{ + AppID: "app6", + DevID: "dev2", + Metadata: types.Metadata{DataRate: "SF7BW125"}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeAppActivations("app6") + waitForOK(unsubToken, a) +} diff --git a/mqtt/client.go b/mqtt/client.go new file mode 100644 index 000000000..ef4b60c46 --- /dev/null +++ b/mqtt/client.go @@ -0,0 +1,265 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "sync" + "time" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/random" + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// QoS indicates the MQTT Quality of Service level. +// 0: The broker/client will deliver the message once, with no confirmation. +// 1: The broker/client will deliver the message at least once, with confirmation required. +// 2: The broker/client will deliver the message exactly once by using a four step handshake. +var ( + PublishQoS byte = 0x00 + SubscribeQoS byte = 0x00 +) + +// Client connects to the MQTT server and can publish/subscribe on uplink, downlink and activations from devices +type Client interface { + Connect() error + Disconnect() + + IsConnected() bool + + // Uplink pub/sub + PublishUplink(payload types.UplinkMessage) Token + PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token + SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token + SubscribeAppUplink(appID string, handler UplinkHandler) Token + SubscribeUplink(handler UplinkHandler) Token + UnsubscribeDeviceUplink(appID string, devID string) Token + UnsubscribeAppUplink(appID string) Token + UnsubscribeUplink() Token + + // Downlink pub/sub + PublishDownlink(payload types.DownlinkMessage) Token + SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token + SubscribeAppDownlink(appID string, handler DownlinkHandler) Token + SubscribeDownlink(handler DownlinkHandler) Token + UnsubscribeDeviceDownlink(appID string, devID string) Token + UnsubscribeAppDownlink(appID string) Token + UnsubscribeDownlink() Token + + // Event pub/sub + PublishAppEvent(appID string, eventType types.EventType, payload interface{}) Token + PublishDeviceEvent(appID string, devID string, eventType types.EventType, payload interface{}) Token + SubscribeAppEvents(appID string, eventType types.EventType, handler AppEventHandler) Token + SubscribeDeviceEvents(appID string, devID string, eventType types.EventType, handler DeviceEventHandler) Token + UnsubscribeAppEvents(appID string, eventType types.EventType) Token + UnsubscribeDeviceEvents(appID string, devID string, eventType types.EventType) Token + + // Activation pub/sub + PublishActivation(payload types.Activation) Token + SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token + SubscribeAppActivations(appID string, handler ActivationHandler) Token + SubscribeActivations(handler ActivationHandler) Token + UnsubscribeDeviceActivations(appID string, devID string) Token + UnsubscribeAppActivations(appID string) Token + UnsubscribeActivations() Token +} + +// Token is returned on asyncronous functions +type Token interface { + // Wait for the function to finish + Wait() bool + // Wait for the function to finish or return false after a certain time + WaitTimeout(time.Duration) bool + // The error associated with the result of the function (nil if everything okay) + Error() error +} + +type simpleToken struct { + err error +} + +// Wait always returns true +func (t *simpleToken) Wait() bool { + return true +} + +// WaitTimeout always returns true +func (t *simpleToken) WaitTimeout(_ time.Duration) bool { + return true +} + +// Error contains the error if present +func (t *simpleToken) Error() error { + return t.err +} + +type token struct { + sync.RWMutex + complete chan bool + ready bool + err error +} + +func newToken() *token { + return &token{ + complete: make(chan bool), + } +} + +func (t *token) Wait() bool { + t.Lock() + defer t.Unlock() + if !t.ready { + <-t.complete + t.ready = true + } + return t.ready +} + +func (t *token) WaitTimeout(d time.Duration) bool { + t.Lock() + defer t.Unlock() + if !t.ready { + select { + case <-t.complete: + t.ready = true + case <-time.After(d): + } + } + return t.ready +} + +func (t *token) flowComplete() { + close(t.complete) +} + +func (t *token) Error() error { + t.RLock() + defer t.RUnlock() + return t.err +} + +// DefaultClient is the default MQTT client for The Things Network +type DefaultClient struct { + mqtt MQTT.Client + ctx log.Interface + subscriptions map[string]MQTT.MessageHandler +} + +// NewClient creates a new DefaultClient +func NewClient(ctx log.Interface, id, username, password string, brokers ...string) Client { + if ctx == nil { + ctx = log.Get() + } + + mqttOpts := MQTT.NewClientOptions() + + for _, broker := range brokers { + mqttOpts.AddBroker(broker) + } + + mqttOpts.SetClientID(fmt.Sprintf("%s-%s", id, random.String(16))) + mqttOpts.SetUsername(username) + mqttOpts.SetPassword(password) + + // TODO: Some tuning of these values probably won't hurt: + mqttOpts.SetKeepAlive(30 * time.Second) + mqttOpts.SetPingTimeout(10 * time.Second) + + mqttOpts.SetCleanSession(true) + + mqttOpts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) { + ctx.Warnf("Received unhandled message: %v", msg) + }) + + var reconnecting bool + + mqttOpts.SetConnectionLostHandler(func(client MQTT.Client, err error) { + ctx.Warnf("Disconnected (%s). Reconnecting...", err.Error()) + reconnecting = true + }) + + ttnClient := &DefaultClient{ + ctx: ctx, + subscriptions: make(map[string]MQTT.MessageHandler), + } + + mqttOpts.SetOnConnectHandler(func(client MQTT.Client) { + ctx.Info("Connected to MQTT") + if reconnecting { + for topic, handler := range ttnClient.subscriptions { + ctx.Infof("Re-subscribing to topic: %s", topic) + ttnClient.subscribe(topic, handler) + } + reconnecting = false + } + }) + + ttnClient.mqtt = MQTT.NewClient(mqttOpts) + + return ttnClient +} + +var ( + // ConnectRetries says how many times the client should retry a failed connection + ConnectRetries = 10 + // ConnectRetryDelay says how long the client should wait between retries + ConnectRetryDelay = time.Second +) + +// Connect to the MQTT broker. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +func (c *DefaultClient) Connect() error { + if c.mqtt.IsConnected() { + return nil + } + var err error + for retries := 0; retries < ConnectRetries; retries++ { + token := c.mqtt.Connect() + finished := token.WaitTimeout(1 * time.Second) + if !finished { + c.ctx.Warn("MQTT connection took longer than expected...") + token.Wait() + } + err = token.Error() + if err == nil { + break + } + c.ctx.Warnf("Could not connect to MQTT Broker (%s). Retrying...", err.Error()) + <-time.After(ConnectRetryDelay) + } + if err != nil { + return fmt.Errorf("Could not connect to MQTT Broker (%s)", err) + } + return nil +} + +func (c *DefaultClient) publish(topic string, msg []byte) Token { + return c.mqtt.Publish(topic, PublishQoS, false, msg) +} + +func (c *DefaultClient) subscribe(topic string, handler MQTT.MessageHandler) Token { + c.subscriptions[topic] = handler + return c.mqtt.Subscribe(topic, SubscribeQoS, handler) +} + +func (c *DefaultClient) unsubscribe(topic string) Token { + delete(c.subscriptions, topic) + return c.mqtt.Unsubscribe(topic) +} + +// Disconnect from the MQTT broker +func (c *DefaultClient) Disconnect() { + if !c.mqtt.IsConnected() { + return + } + c.ctx.Debug("Disconnecting from MQTT") + c.mqtt.Disconnect(25) +} + +// IsConnected returns true if there is a connection to the MQTT broker +func (c *DefaultClient) IsConnected() bool { + return c.mqtt.IsConnected() +} diff --git a/mqtt/client_test.go b/mqtt/client_test.go new file mode 100644 index 000000000..9e9861a32 --- /dev/null +++ b/mqtt/client_test.go @@ -0,0 +1,181 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "errors" + "fmt" + "os" + "testing" + "time" + + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" + . "github.com/smartystreets/assertions" +) + +var host string + +func init() { + host = os.Getenv("MQTT_ADDRESS") + if host == "" { + host = "localhost:1883" + } +} + +func waitForOK(token Token, a *Assertion) { + success := token.WaitTimeout(100 * time.Millisecond) + a.So(success, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestToken(t *testing.T) { + a := New(t) + + okToken := newToken() + go func() { + time.Sleep(1 * time.Millisecond) + okToken.flowComplete() + }() + okToken.Wait() + a.So(okToken.Error(), ShouldBeNil) + + failToken := newToken() + go func() { + time.Sleep(1 * time.Millisecond) + failToken.err = errors.New("Err") + failToken.flowComplete() + }() + failToken.Wait() + a.So(failToken.Error(), ShouldNotBeNil) + + timeoutToken := newToken() + timeoutTokenDone := timeoutToken.WaitTimeout(5 * time.Millisecond) + a.So(timeoutTokenDone, ShouldBeFalse) +} + +func TestSimpleToken(t *testing.T) { + a := New(t) + + okToken := simpleToken{} + okToken.Wait() + a.So(okToken.Error(), ShouldBeNil) + + failToken := simpleToken{fmt.Errorf("Err")} + failToken.Wait() + a.So(failToken.Error(), ShouldNotBeNil) +} + +func TestNewClient(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + a.So(c.(*DefaultClient).mqtt, ShouldNotBeNil) +} + +func TestConnect(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) + + // Connecting while already connected should not change anything + err = c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) +} + +func TestConnectInvalidAddress(t *testing.T) { + a := New(t) + ConnectRetries = 2 + ConnectRetryDelay = 50 * time.Millisecond + c := NewClient(getLogger(t, "Test"), "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldNotBeNil) +} + +func TestConnectInvalidCredentials(t *testing.T) { + t.Skipf("Need authenticated MQTT for TestConnectInvalidCredentials - Skipping") +} + +func TestIsConnected(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + + a.So(c.IsConnected(), ShouldBeTrue) +} + +func TestDisconnect(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + + // Disconnecting when not connected should not change anything + c.Disconnect() + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + c.Disconnect() + + a.So(c.IsConnected(), ShouldBeFalse) +} + +func TestRandomTopicPublish(t *testing.T) { + a := New(t) + ctx := getLogger(t, "TestRandomTopicPublish") + + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.(*DefaultClient).mqtt.Subscribe("randomtopic", SubscribeQoS, nil) + waitForOK(subToken, a) + pubToken := c.(*DefaultClient).mqtt.Publish("randomtopic", PublishQoS, false, []byte{0x00}) + waitForOK(pubToken, a) + + <-time.After(50 * time.Millisecond) + + ctx.Info("This test should have printed one message.") +} + +func ExampleNewClient() { + ctx := apex.Wrap(log.WithField("Example", "NewClient")) + exampleClient := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "eu.thethings.network:1883") + err := exampleClient.Connect() + if err != nil { + ctx.WithError(err).Fatal("Could not connect") + } +} + +var exampleClient Client + +func ExampleDefaultClient_SubscribeDeviceUplink() { + token := exampleClient.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req types.UplinkMessage) { + // Do something with the message + }) + token.Wait() + if err := token.Error(); err != nil { + panic(err) + } +} + +func ExampleDefaultClient_PublishDownlink() { + token := exampleClient.PublishDownlink(types.DownlinkMessage{ + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + }) + token.Wait() + if err := token.Error(); err != nil { + panic(err) + } +} diff --git a/mqtt/downlink.go b/mqtt/downlink.go new file mode 100644 index 000000000..9c7587dac --- /dev/null +++ b/mqtt/downlink.go @@ -0,0 +1,79 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "fmt" + + "github.com/TheThingsNetwork/ttn/core/types" + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// DownlinkHandler is called for downlink messages +type DownlinkHandler func(client Client, appID string, devID string, req types.DownlinkMessage) + +// PublishDownlink publishes a downlink message +func (c *DefaultClient) PublishDownlink(dataDown types.DownlinkMessage) Token { + topic := DeviceTopic{dataDown.AppID, dataDown.DevID, DeviceDownlink, ""} + dataDown.AppID = "" + dataDown.DevID = "" + msg, err := json.Marshal(dataDown) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.publish(topic.String(), msg) +} + +// SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device +func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { + topic := DeviceTopic{appID, devID, DeviceDownlink, ""} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseDeviceTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid downlink topic: %s", msg.Topic()) + return + } + + // Unmarshal the payload + dataDown := &types.DownlinkMessage{} + err = json.Unmarshal(msg.Payload(), dataDown) + if err != nil { + c.ctx.Warnf("Could not unmarshal downlink (%s).", err.Error()) + return + } + dataDown.AppID = topic.AppID + dataDown.DevID = topic.DevID + + // Call the Downlink handler + handler(c, topic.AppID, topic.DevID, *dataDown) + }) +} + +// SubscribeAppDownlink subscribes to all downlink messages for the given application +func (c *DefaultClient) SubscribeAppDownlink(appID string, handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink(appID, "", handler) +} + +// SubscribeDownlink subscribes to all downlink messages that the current user has access to +func (c *DefaultClient) SubscribeDownlink(handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink("", "", handler) +} + +// UnsubscribeDeviceDownlink unsubscribes from the downlink messages for the given application and device +func (c *DefaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, DeviceDownlink, ""} + return c.unsubscribe(topic.String()) +} + +// UnsubscribeAppDownlink unsubscribes from the downlink messages for the given application +func (c *DefaultClient) UnsubscribeAppDownlink(appID string) Token { + return c.UnsubscribeDeviceDownlink(appID, "") +} + +// UnsubscribeDownlink unsubscribes from the downlink messages that the current user has access to +func (c *DefaultClient) UnsubscribeDownlink() Token { + return c.UnsubscribeDeviceDownlink("", "") +} diff --git a/mqtt/downlink_test.go b/mqtt/downlink_test.go new file mode 100644 index 000000000..e51cf5f28 --- /dev/null +++ b/mqtt/downlink_test.go @@ -0,0 +1,152 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +// Downlink pub/sub + +func TestPublishDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + dataDown := types.DownlinkMessage{ + AppID: "someid", + DevID: "someid", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + } + + token := c.PublishDownlink(dataDown) + waitForOK(token, a) + + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDeviceDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req types.DownlinkMessage) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeDeviceDownlink("someid", "someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req types.DownlinkMessage) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeAppDownlink("someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeDownlink(func(client Client, appID string, devID string, req types.DownlinkMessage) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeDownlink() + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(1) + + subToken := c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req types.DownlinkMessage) { + a.So(appID, ShouldResemble, "app3") + a.So(devID, ShouldResemble, "dev3") + + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishDownlink(types.DownlinkMessage{ + AppID: "app3", + DevID: "dev3", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeDeviceDownlink("app3", "dev3") + waitForOK(unsubToken, a) +} + +func TestPubSubAppDownlink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(2) + + subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req types.DownlinkMessage) { + a.So(appID, ShouldResemble, "app4") + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishDownlink(types.DownlinkMessage{ + AppID: "app4", + DevID: "dev1", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + pubToken = c.PublishDownlink(types.DownlinkMessage{ + AppID: "app4", + DevID: "dev2", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeAppDownlink("app3") + waitForOK(unsubToken, a) +} diff --git a/mqtt/events.go b/mqtt/events.go new file mode 100644 index 000000000..7303a89dd --- /dev/null +++ b/mqtt/events.go @@ -0,0 +1,81 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "fmt" + + "github.com/TheThingsNetwork/ttn/core/types" + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// AppEventHandler is called for events +type AppEventHandler func(client Client, appID string, eventType types.EventType, payload []byte) + +// DeviceEventHandler is called for events +type DeviceEventHandler func(client Client, appID string, devID string, eventType types.EventType, payload []byte) + +// PublishAppEvent publishes an event to the topic for application events of the given type +// it will marshal the payload to json +func (c *DefaultClient) PublishAppEvent(appID string, eventType types.EventType, payload interface{}) Token { + topic := ApplicationTopic{appID, AppEvents, string(eventType)} + msg, err := json.Marshal(payload) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.publish(topic.String(), msg) +} + +// PublishDeviceEvent publishes an event to the topic for device events of the given type +// it will marshal the payload to json +func (c *DefaultClient) PublishDeviceEvent(appID string, devID string, eventType types.EventType, payload interface{}) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, string(eventType)} + msg, err := json.Marshal(payload) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.publish(topic.String(), msg) +} + +// SubscribeAppEvents subscribes to events of the given type for the given application. In order to subscribe to +// application events from all applications the user has access to, pass an empty string as appID. +func (c *DefaultClient) SubscribeAppEvents(appID string, eventType types.EventType, handler AppEventHandler) Token { + topic := ApplicationTopic{appID, AppEvents, string(eventType)} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + topic, err := ParseApplicationTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid events topic: %s", msg.Topic()) + return + } + handler(c, topic.AppID, types.EventType(topic.Field), msg.Payload()) + }) +} + +// SubscribeDeviceEvents subscribes to events of the given type for the given device. In order to subscribe to +// events from all devices within an application, pass an empty string as devID. In order to subscribe to all +// events from all devices in all applications the user has access to, pass an empty string as appID. +func (c *DefaultClient) SubscribeDeviceEvents(appID string, devID string, eventType types.EventType, handler DeviceEventHandler) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, string(eventType)} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + topic, err := ParseDeviceTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid events topic: %s", msg.Topic()) + return + } + handler(c, topic.AppID, topic.DevID, types.EventType(topic.Field), msg.Payload()) + }) +} + +// UnsubscribeAppEvents unsubscribes from the events that were subscribed to by SubscribeAppEvents +func (c *DefaultClient) UnsubscribeAppEvents(appID string, eventType types.EventType) Token { + topic := ApplicationTopic{appID, AppEvents, string(eventType)} + return c.unsubscribe(topic.String()) +} + +// UnsubscribeDeviceEvents unsubscribes from the events that were subscribed to by SubscribeDeviceEvents +func (c *DefaultClient) UnsubscribeDeviceEvents(appID string, devID string, eventType types.EventType) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, string(eventType)} + return c.unsubscribe(topic.String()) +} diff --git a/mqtt/events_test.go b/mqtt/events_test.go new file mode 100644 index 000000000..9bfd46c11 --- /dev/null +++ b/mqtt/events_test.go @@ -0,0 +1,57 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestPublishSubscribeAppEvents(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + var wg WaitGroup + wg.Add(1) + subToken := c.SubscribeAppEvents("app-id", "", func(_ Client, appID string, eventType types.EventType, payload []byte) { + a.So(appID, ShouldEqual, "app-id") + a.So(eventType, ShouldEqual, "some-event") + a.So(string(payload), ShouldEqual, `"payload"`) + wg.Done() + }) + waitForOK(subToken, a) + pubToken := c.PublishAppEvent("app-id", "some-event", "payload") + waitForOK(pubToken, a) + unsubToken := c.UnsubscribeAppEvents("app-id", "") + waitForOK(unsubToken, a) + wg.WaitFor(100 * time.Millisecond) +} + +func TestPublishSubscribeDeviceEvents(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + var wg WaitGroup + wg.Add(1) + subToken := c.SubscribeDeviceEvents("app-id", "dev-id", "", func(_ Client, appID string, devID string, eventType types.EventType, payload []byte) { + a.So(appID, ShouldEqual, "app-id") + a.So(devID, ShouldEqual, "dev-id") + a.So(eventType, ShouldEqual, "some-event") + a.So(string(payload), ShouldEqual, `"payload"`) + wg.Done() + }) + waitForOK(subToken, a) + pubToken := c.PublishDeviceEvent("app-id", "dev-id", "some-event", "payload") + waitForOK(pubToken, a) + unsubToken := c.UnsubscribeDeviceEvents("app-id", "dev-id", "") + waitForOK(unsubToken, a) + wg.WaitFor(100 * time.Millisecond) +} diff --git a/mqtt/topics.go b/mqtt/topics.go new file mode 100644 index 000000000..32b2b2e48 --- /dev/null +++ b/mqtt/topics.go @@ -0,0 +1,124 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "regexp" + "strings" +) + +const simpleWildcard = "+" +const wildcard = "#" + +// DeviceTopicType represents the type of a device topic +type DeviceTopicType string + +// Topic types for Devices +const ( + DeviceEvents DeviceTopicType = "events" + DeviceUplink DeviceTopicType = "up" + DeviceDownlink DeviceTopicType = "down" +) + +// DeviceTopic represents an MQTT topic for devices +type DeviceTopic struct { + AppID string + DevID string + Type DeviceTopicType + Field string +} + +// ParseDeviceTopic parses an MQTT device topic string to a DeviceTopic struct +func ParseDeviceTopic(topic string) (*DeviceTopic, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(devices)/([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(events|up|down)([0-9a-z/]+)?$") + matches := pattern.FindStringSubmatch(topic) + if len(matches) < 4 { + return nil, fmt.Errorf("Invalid topic format") + } + var appID string + if matches[1] != simpleWildcard { + appID = matches[1] + } + var devID string + if matches[3] != simpleWildcard { + devID = matches[3] + } + topicType := DeviceTopicType(matches[4]) + deviceTopic := &DeviceTopic{appID, devID, topicType, ""} + if (topicType == DeviceUplink || topicType == DeviceEvents) && len(matches) > 4 { + deviceTopic.Field = strings.Trim(matches[5], "/") + } + return deviceTopic, nil +} + +// String implements the Stringer interface +func (t DeviceTopic) String() string { + appID := simpleWildcard + if t.AppID != "" { + appID = t.AppID + } + devID := simpleWildcard + if t.DevID != "" { + devID = t.DevID + } + if t.Type == DeviceEvents && t.Field == "" { + t.Field = simpleWildcard + } + topic := fmt.Sprintf("%s/%s/%s/%s", appID, "devices", devID, t.Type) + if (t.Type == DeviceUplink || t.Type == DeviceEvents) && t.Field != "" { + topic += "/" + t.Field + } + return topic +} + +// ApplicationTopicType represents the type of an application topic +type ApplicationTopicType string + +// Topic types for Applications +const ( + AppEvents ApplicationTopicType = "events" +) + +// ApplicationTopic represents an MQTT topic for applications +type ApplicationTopic struct { + AppID string + Type ApplicationTopicType + Field string +} + +// ParseApplicationTopic parses an MQTT device topic string to an ApplicationTopic struct +func ParseApplicationTopic(topic string) (*ApplicationTopic, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(events)([0-9a-z/-]+|/#)?$") + matches := pattern.FindStringSubmatch(topic) + if len(matches) < 2 { + return nil, fmt.Errorf("Invalid topic format") + } + var appID string + if matches[1] != simpleWildcard { + appID = matches[1] + } + topicType := ApplicationTopicType(matches[2]) + appTopic := &ApplicationTopic{appID, topicType, ""} + if topicType == AppEvents && len(matches) > 2 { + appTopic.Field = strings.Trim(matches[3], "/") + } + return appTopic, nil +} + +// String implements the Stringer interface +func (t ApplicationTopic) String() string { + appID := simpleWildcard + if t.AppID != "" { + appID = t.AppID + } + if t.Type == AppEvents && t.Field == "" { + t.Field = wildcard + } + topic := fmt.Sprintf("%s/%s", appID, t.Type) + if t.Type == AppEvents && t.Field != "" { + topic += "/" + t.Field + } + return topic +} diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go new file mode 100644 index 000000000..a3c0384be --- /dev/null +++ b/mqtt/topics_test.go @@ -0,0 +1,141 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestParseDeviceTopic(t *testing.T) { + a := New(t) + + topic := "appid-1/devices/devid-1/up" + + expected := &DeviceTopic{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceUplink, + } + + got, err := ParseDeviceTopic(topic) + + a.So(err, ShouldBeNil) + a.So(got, ShouldResemble, expected) +} + +func TestParseDeviceTopicInvalid(t *testing.T) { + a := New(t) + + _, err := ParseDeviceTopic("appid:Invalid/devices/dev/up") + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceTopic("appid-1/devices/devid:Invalid/up") // DevEUI contains lowercase hex chars + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceTopic("appid-1/fridges/devid-1/up") // We don't support fridges (at least, not specifically fridges) + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceTopic("appid-1/devices/devid-1/emotions") // Devices usually don't publish emotions + a.So(err, ShouldNotBeNil) +} + +func TestDeviceTopicString(t *testing.T) { + a := New(t) + + topic := &DeviceTopic{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceDownlink, + } + + expected := "appid-1/devices/devid-1/down" + + got := topic.String() + + a.So(got, ShouldResemble, expected) +} + +func TestDeviceTopicParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + // Uppercase (not lowercase) + "0102030405060708/devices/abcdabcd12345678/up", + "0102030405060708/devices/abcdabcd12345678/up/value", + "0102030405060708/devices/abcdabcd12345678/down", + "0102030405060708/devices/abcdabcd12345678/events/activations", + // Numbers + "0102030405060708/devices/0000000012345678/up", + "0102030405060708/devices/0000000012345678/up/value", + "0102030405060708/devices/0000000012345678/down", + "0102030405060708/devices/0000000012345678/events/activations", + // Wildcards + "+/devices/+/up", + "+/devices/+/down", + "+/devices/+/events/activations", + // Not Wildcard + "0102030405060708/devices/0100000000000000/up", + "0102030405060708/devices/0100000000000000/up/value", + "0102030405060708/devices/0100000000000000/down", + "0102030405060708/devices/0100000000000000/events/activations", + } + + for _, expected := range expectedList { + topic, err := ParseDeviceTopic(expected) + a.So(err, ShouldBeNil) + a.So(topic.String(), ShouldEqual, expected) + } + +} + +func TestParseAppTopicInvalid(t *testing.T) { + a := New(t) + + _, err := ParseApplicationTopic("appid:Invalid/events") + a.So(err, ShouldNotBeNil) + + _, err = ParseApplicationTopic("appid/randomstuff") + a.So(err, ShouldNotBeNil) +} + +func TestAppTopicString(t *testing.T) { + a := New(t) + + topic := &ApplicationTopic{ + AppID: "appid-1", + Type: AppEvents, + } + + a.So(topic.String(), ShouldResemble, "appid-1/events/#") + + topic = &ApplicationTopic{ + AppID: "appid-1", + Type: AppEvents, + Field: "err", + } + + a.So(topic.String(), ShouldResemble, "appid-1/events/err") +} + +func TestAppTopicParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + "+/events/#", + "appid/events/#", + "+/events/some-event", + "appid/events/some-event", + "+/events/some/event", + "appid/events/some/event", + } + + for _, expected := range expectedList { + topic, err := ParseApplicationTopic(expected) + a.So(err, ShouldBeNil) + a.So(topic.String(), ShouldEqual, expected) + } + +} diff --git a/mqtt/uplink.go b/mqtt/uplink.go new file mode 100644 index 000000000..4e4b5e4a4 --- /dev/null +++ b/mqtt/uplink.go @@ -0,0 +1,118 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "fmt" + + "github.com/TheThingsNetwork/ttn/core/types" + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// UplinkHandler is called for uplink messages +type UplinkHandler func(client Client, appID string, devID string, req types.UplinkMessage) + +// PublishUplink publishes an uplink message to the MQTT broker +func (c *DefaultClient) PublishUplink(dataUp types.UplinkMessage) Token { + topic := DeviceTopic{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} + dataUp.AppID = "" + dataUp.DevID = "" + msg, err := json.Marshal(dataUp) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.publish(topic.String(), msg) +} + +// PublishUplinkFields publishes uplink fields to MQTT +func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token { + flattenedFields := make(map[string]interface{}) + flatten("", "/", fields, flattenedFields) + tokens := make([]Token, 0, len(flattenedFields)) + for field, value := range flattenedFields { + topic := DeviceTopic{appID, devID, DeviceUplink, field} + pld, _ := json.Marshal(value) + token := c.publish(topic.String(), pld) + tokens = append(tokens, token) + } + t := newToken() + go func() { + for _, token := range tokens { + token.Wait() + if token.Error() != nil { + c.ctx.Warnf("Error publishing uplink fields: %s", token.Error().Error()) + t.err = token.Error() + } + } + t.flowComplete() + }() + return t +} + +func flatten(prefix, sep string, in, out map[string]interface{}) { + for k, v := range in { + key := prefix + sep + k + if prefix == "" { + key = k + } + out[key] = v + if next, ok := v.(map[string]interface{}); ok { + flatten(key, sep, next, out) + } + } +} + +// SubscribeDeviceUplink subscribes to all uplink messages for the given application and device +func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { + topic := DeviceTopic{appID, devID, DeviceUplink, ""} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseDeviceTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid uplink topic: %s", msg.Topic()) + return + } + + // Unmarshal the payload + dataUp := &types.UplinkMessage{} + err = json.Unmarshal(msg.Payload(), dataUp) + dataUp.AppID = topic.AppID + dataUp.DevID = topic.DevID + + if err != nil { + c.ctx.Warnf("Could not unmarshal uplink (%s).", err.Error()) + return + } + + // Call the uplink handler + handler(c, topic.AppID, topic.DevID, *dataUp) + }) +} + +// SubscribeAppUplink subscribes to all uplink messages for the given application +func (c *DefaultClient) SubscribeAppUplink(appID string, handler UplinkHandler) Token { + return c.SubscribeDeviceUplink(appID, "", handler) +} + +// SubscribeUplink subscribes to all uplink messages that the current user has access to +func (c *DefaultClient) SubscribeUplink(handler UplinkHandler) Token { + return c.SubscribeDeviceUplink("", "", handler) +} + +// UnsubscribeDeviceUplink unsubscribes from the uplink messages for the given application and device +func (c *DefaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, DeviceUplink, ""} + return c.unsubscribe(topic.String()) +} + +// UnsubscribeAppUplink unsubscribes from the uplink messages for the given application +func (c *DefaultClient) UnsubscribeAppUplink(appID string) Token { + return c.UnsubscribeDeviceUplink(appID, "") +} + +// UnsubscribeUplink unsubscribes from the uplink messages that the current user has access to +func (c *DefaultClient) UnsubscribeUplink() Token { + return c.UnsubscribeDeviceUplink("", "") +} diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go new file mode 100644 index 000000000..285e8c042 --- /dev/null +++ b/mqtt/uplink_test.go @@ -0,0 +1,220 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + MQTT "github.com/eclipse/paho.mqtt.golang" + . "github.com/smartystreets/assertions" +) + +// Uplink pub/sub + +func TestPublishUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + dataUp := types.UplinkMessage{ + AppID: "someid", + DevID: "someid", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + } + + token := c.PublishUplink(dataUp) + waitForOK(token, a) +} + +func TestPublishUplinkFields(t *testing.T) { + a := New(t) + ctx := getLogger(t, "Test") + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s", host)) + + c.Connect() + defer c.Disconnect() + + subChan := make(chan bool) + waitChan := make(chan bool) + go func() { + for i := 10; i > 0; i-- { + <-subChan + } + close(subChan) + waitChan <- true + }() + subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", SubscribeQoS, func(_ MQTT.Client, msg MQTT.Message) { + switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { + case "battery": + a.So(string(msg.Payload()), ShouldEqual, "90") + case "sensors": + a.So(string(msg.Payload()), ShouldContainSubstring, `people":["`) + case "sensors/color": + a.So(string(msg.Payload()), ShouldEqual, `"blue"`) + case "sensors/people": + a.So(string(msg.Payload()), ShouldEqual, `["bob","alice"]`) + case "sensors/water": + a.So(string(msg.Payload()), ShouldEqual, "true") + case "sensors/analog": + a.So(string(msg.Payload()), ShouldEqual, `[0,255,500,1000]`) + case "sensors/history": + a.So(string(msg.Payload()), ShouldContainSubstring, `today":"`) + case "sensors/history/today": + a.So(string(msg.Payload()), ShouldEqual, `"not yet"`) + case "sensors/history/yesterday": + a.So(string(msg.Payload()), ShouldEqual, `"absolutely"`) + case "gps": + a.So(string(msg.Payload()), ShouldEqual, "[52.3736735,4.886663]") + default: + t.Errorf("Should not have received message on topic %s", msg.Topic()) + t.Fail() + } + subChan <- true + }) + waitForOK(subToken, a) + + fields := map[string]interface{}{ + "battery": 90, + "sensors": map[string]interface{}{ + "color": "blue", + "people": []string{"bob", "alice"}, + "water": true, + "analog": []int{0, 255, 500, 1000}, + "history": map[string]interface{}{ + "today": "not yet", + "yesterday": "absolutely", + }, + }, + "gps": []float64{52.3736735, 4.886663}, + } + + pubToken := c.PublishUplinkFields("fields-app", "fields-dev", fields) + waitForOK(pubToken, a) + + select { + case <-waitChan: + case <-time.After(1 * time.Second): + panic("Did not receive fields") + } +} + +func TestSubscribeDeviceUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req types.UplinkMessage) { + + }) + waitForOK(subToken, a) + + unsubToken := c.UnsubscribeDeviceUplink("someid", "someid") + waitForOK(unsubToken, a) +} + +func TestSubscribeAppUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req types.UplinkMessage) { + + }) + waitForOK(subToken, a) + + unsubToken := c.UnsubscribeAppUplink("someid") + waitForOK(unsubToken, a) +} + +func TestSubscribeUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.SubscribeUplink(func(client Client, appID string, devID string, req types.UplinkMessage) { + + }) + waitForOK(subToken, a) + + unsubToken := c.UnsubscribeUplink() + waitForOK(unsubToken, a) +} + +func TestPubSubUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + waitChan := make(chan bool, 1) + + subToken := c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req types.UplinkMessage) { + a.So(appID, ShouldResemble, "app1") + a.So(devID, ShouldResemble, "dev1") + + waitChan <- true + }) + waitForOK(subToken, a) + + pubToken := c.PublishUplink(types.UplinkMessage{ + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app1", + DevID: "dev1", + }) + waitForOK(pubToken, a) + + select { + case <-waitChan: + case <-time.After(1 * time.Second): + panic("Did not receive Uplink") + } + + unsubToken := c.UnsubscribeDeviceUplink("app1", "dev1") + waitForOK(unsubToken, a) +} + +func TestPubSubAppUplink(t *testing.T) { + a := New(t) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(2) + + subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req types.UplinkMessage) { + a.So(appID, ShouldResemble, "app2") + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishUplink(types.UplinkMessage{ + AppID: "app2", + DevID: "dev1", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + pubToken = c.PublishUplink(types.UplinkMessage{ + AppID: "app2", + DevID: "dev2", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeAppUplink("app1") + waitForOK(unsubToken, a) +} diff --git a/mqtt/utils_test.go b/mqtt/utils_test.go new file mode 100644 index 000000000..de65f0f70 --- /dev/null +++ b/mqtt/utils_test.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + tt "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func getLogger(t *testing.T, tag string) log.Interface { + return apex.Wrap(tt.GetLogger(t, tag)) +} diff --git a/ttnctl/README.md b/ttnctl/README.md new file mode 100644 index 000000000..bfe6aa753 --- /dev/null +++ b/ttnctl/README.md @@ -0,0 +1,34 @@ +# The Things Network Control Utility - `ttnctl` + +`ttnctl` can be used to manage The Things Network from the command line. + +[Documentation](https://www.thethingsnetwork.org/docs/cli/) + +## Configuration File + +Configuration is done with: + +* Command line arguments +* Environment variables +* Configuration file + +The following configuration options can be set: + +| CLI flag / yaml key | Environment Var | Description | +|-----------------------|-----------------------------|--------------| +| `app-id` | `TTNCTL_APP_ID` | The application ID that should be used | +| `app-eui` | `TTNCTL_APP_EUI` | The LoRaWAN AppEUI that should be used | +| `debug` | `TTNCTL_DEBUG` | Print debug logs | +| `discovery-address` | `TTNCTL_DISCOVERY_ADDRESS` | The address and port of the discovery server | +| `router-id` | `TTNCTL_TTN_ROUTER` | The id of the router | +| `handler-id` | `TTNCTL_TTN_HANDLER` | The id of the handler | +| `mqtt-address` | `TTNCTL_MQTT_ADDRESS` | The address and port of the MQTT broker | +| `auth-server` | `TTNCTL_AUTH_SERVER` | The protocol (http/https), address and port of the auth server | + +## Development + +**Configuration for Development:** Copy `../.env/ttnctl.yml.dev-example` to `~/.ttnctl.yml` + +## License + +Source code for The Things Network is released under the MIT License, which can be found in the [LICENSE](../LICENSE) file. A list of authors can be found in the [AUTHORS](../AUTHORS) file. diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go new file mode 100644 index 000000000..2a6c18f4b --- /dev/null +++ b/ttnctl/cmd/applications.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var applicationsCmd = &cobra.Command{ + Use: "applications", + Aliases: []string{"application"}, + Short: "Manage applications", + Long: `ttnctl applications can be used to manage applications.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) + }, +} + +func init() { + RootCmd.AddCommand(applicationsCmd) +} diff --git a/ttnctl/cmd/applications_add.go b/ttnctl/cmd/applications_add.go new file mode 100644 index 000000000..96589a2e1 --- /dev/null +++ b/ttnctl/cmd/applications_add.go @@ -0,0 +1,65 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var applicationsAddCmd = &cobra.Command{ + Use: "add [AppID] [Description]", + Short: "Add a new application", + Long: `ttnctl applications add can be used to add a new application to your account.`, + Example: `$ ttnctl applications add test "Test application" + INFO Added Application + INFO Selected Current Application +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + var euis []types.AppEUI + euiStrings, err := cmd.Flags().GetStringSlice("app-eui") + if err != nil { + ctx.WithError(err).Fatal("Could not read app-eui options") + } + for _, euiString := range euiStrings { + eui, err := types.ParseAppEUI(euiString) + if err != nil { + ctx.WithError(err).Fatal("Could not read app-eui") + } + euis = append(euis, eui) + } + + account := util.GetAccount(ctx) + + app, err := account.CreateApplication(args[0], args[1], euis) + if err != nil { + ctx.WithError(err).Fatal("Could not add application") + } + + util.ForceRefreshToken(ctx) + + ctx.Info("Added Application") + + skipSelect, _ := cmd.Flags().GetBool("skip-select") + if !skipSelect { + util.SetApp(ctx, app.ID, app.EUIs[0]) + } + + ctx.Info("Selected Current Application") + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsAddCmd) + applicationsAddCmd.Flags().StringSlice("app-eui", []string{}, "LoRaWAN AppEUI to register with application") + applicationsAddCmd.Flags().Bool("skip-select", false, "Do not select this application (also adds --skip-register)") + applicationsAddCmd.Flags().Bool("skip-register", false, "Do not register application with the Handler") +} diff --git a/ttnctl/cmd/applications_delete.go b/ttnctl/cmd/applications_delete.go new file mode 100644 index 000000000..0a1752c8d --- /dev/null +++ b/ttnctl/cmd/applications_delete.go @@ -0,0 +1,39 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var applicationsDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete an application", + Long: `ttnctl devices delete can be used to delete an application.`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + appID := util.GetAppID(ctx) + + if !confirm(fmt.Sprintf("Are you sure you want to delete application %s?", appID)) { + ctx.Info("Not doing anything") + return + } + + err := account.DeleteApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not delete application") + } + util.ForceRefreshToken(ctx) + + ctx.Info("Deleted Application") + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsDeleteCmd) +} diff --git a/ttnctl/cmd/applications_info.go b/ttnctl/cmd/applications_info.go new file mode 100644 index 000000000..8715540b6 --- /dev/null +++ b/ttnctl/cmd/applications_info.go @@ -0,0 +1,94 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var applicationsInfoCmd = &cobra.Command{ + Use: "info [AppID]", + Short: "Get information about an application", + Long: `ttnctl applications info can be used to info applications.`, + Example: `$ ttnctl applications info + INFO Found application + +AppID: test +Name: Test application +EUIs: + - 0000000000000000 + +Access Keys: + - Name: default key + Key: FZYr01cUhdhY1KBiMghUl+/gXyqXhrF6y+1ww7+DzHg= + Rights: messages:up:r, messages:down:w + +Collaborators: + - Name: yourname + Rights: settings, delete, collaborators +`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + + var appID string + if len(args) == 1 { + appID = args[0] + } else { + appID = util.GetAppID(ctx) + } + + app, err := account.FindApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not find application") + } + + ctx.Info("Found application") + + fmt.Println() + + fmt.Printf("AppID: %s\n", app.ID) + fmt.Printf("Name: %s\n", app.Name) + + fmt.Println("EUIs:") + for _, eui := range app.EUIs { + fmt.Printf(" - %s\n", eui) + } + fmt.Println() + fmt.Println("Access Keys:") + for _, key := range app.AccessKeys { + fmt.Printf(" - Name: %s\n", key.Name) + fmt.Printf(" Key: %s\n", key.Key) + fmt.Print(" Rights: ") + for i, right := range key.Rights { + if i != 0 { + fmt.Print(", ") + } + fmt.Print(right) + } + fmt.Println() + } + fmt.Println() + fmt.Println("Collaborators:") + for _, collaborator := range app.Collaborators { + fmt.Printf(" - Name: %s\n", collaborator.Username) + fmt.Print(" Rights: ") + for i, right := range collaborator.Rights { + if i != 0 { + fmt.Print(", ") + } + fmt.Print(right) + } + fmt.Println() + } + fmt.Println() + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsInfoCmd) +} diff --git a/ttnctl/cmd/applications_list.go b/ttnctl/cmd/applications_list.go new file mode 100644 index 000000000..efa4b90b8 --- /dev/null +++ b/ttnctl/cmd/applications_list.go @@ -0,0 +1,58 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +var applicationsListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List applications", + Long: `ttnctl applications list can be used to list applications.`, + Example: `$ ttnctl applications list + INFO Found one application: + + ID Description EUIs Access Keys Collaborators +1 test Test application 1 1 1 +`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + + apps, err := account.ListApplications() + if err != nil { + ctx.WithError(err).Fatal("Could not list applications") + } + + switch len(apps) { + case 0: + ctx.Info("You don't have any applications") + return + case 1: + ctx.Info("Found one application:") + default: + ctx.Infof("Found %d applications:", len(apps)) + } + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "ID", "Description", "EUIs", "Access Keys", "Collaborators") + for i, app := range apps { + table.AddRow(i+1, app.ID, app.Name, len(app.EUIs), len(app.AccessKeys), len(app.Collaborators)) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsListCmd) +} diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go new file mode 100644 index 000000000..4eb3e1b89 --- /dev/null +++ b/ttnctl/cmd/applications_pf.go @@ -0,0 +1,80 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var applicationsPayloadFunctionsCmd = &cobra.Command{ + Use: "pf", + Short: "Show the payload functions", + Long: `ttnctl applications pf shows the payload functions for decoding, +converting and validating binary payload.`, + Example: `$ ttnctl applications pf + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found Application + INFO Decoder function +function Decoder(bytes, port) { + var decoded = {}; + if (port === 1) { + decoded.led = bytes[0]; + } + return decoded; +} + INFO No converter function + INFO No validator function + INFO No encoder function +`, + Run: func(cmd *cobra.Command, args []string) { + + appID := util.GetAppID(ctx) + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + app, err := manager.GetApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not get application.") + } + + ctx.Info("Found Application") + + if app.Decoder != "" { + ctx.Info("Decoder function") + fmt.Println(app.Decoder) + } else { + ctx.Info("No decoder function") + } + + if app.Converter != "" { + ctx.Info("Converter function") + fmt.Println(app.Converter) + } else { + ctx.Info("No converter function") + } + + if app.Validator != "" { + ctx.Info("Validator function") + fmt.Println(app.Validator) + } else { + ctx.Info("No validator function") + } + + if app.Encoder != "" { + ctx.Info("Encoder function") + fmt.Println(app.Encoder) + } else { + ctx.Info("No encoder function") + } + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsPayloadFunctionsCmd) +} diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go new file mode 100644 index 000000000..cd3a6303c --- /dev/null +++ b/ttnctl/cmd/applications_pf_set.go @@ -0,0 +1,170 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var applicationsPayloadFunctionsSetCmd = &cobra.Command{ + Use: "set [decoder/converter/validator/encoder] [file.js]", + Short: "Set payload functions of an application", + Long: `ttnctl pf set can be used to get or set payload functions of an application. +The functions are read from the supplied file or from STDIN.`, + Example: `$ ttnctl applications pf set decoder + INFO Discovering Handler... + INFO Connecting with Handler... +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; + + // if (port === 1) { + // decoded.led = bytes[0]; + // } + + return decoded; +} +########## Write your Decoder here and end with Ctrl+D (EOF): +function Decoder(bytes, port) { + var decoded = {}; + + // if (port === 1) { + // decoded.led = bytes[0]; + // } + + return decoded; +} + INFO Updated application AppID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + appID := util.GetAppID(ctx) + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + app, err := manager.GetApplication(appID) + if err != nil && strings.Contains(err.Error(), "not found") { + app = &handler.Application{AppId: appID} + } else if err != nil { + ctx.WithError(err).Fatal("Could not get existing application.") + } + + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + function := args[0] + + if len(args) == 2 { + content, err := ioutil.ReadFile(args[1]) + if err != nil { + ctx.WithError(err).Fatal("Could not read function file") + } + switch function { + case "decoder": + app.Decoder = string(content) + case "converter": + app.Converter = string(content) + case "validator": + app.Validator = string(content) + case "encoder": + app.Encoder = string(content) + default: + ctx.Fatalf("Function %s does not exist", function) + } + } else { + switch function { + case "decoder": + fmt.Println(`function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; + + // if (port === 1) { + // decoded.led = bytes[0]; + // } + + return decoded; +} +########## Write your Decoder here and end with Ctrl+D (EOF):`) + app.Decoder = readFunction() + case "converter": + fmt.Println(`function Converter(decoded, port) { + // Merge, split or otherwise + // mutate decoded fields. + var converted = decoded; + + // if (port === 1 && (converted.led === 0 || converted.led === 1)) { + // converted.led = Boolean(converted.led); + // } + + return converted; +} +########## Write your Converter here and end with Ctrl+D (EOF):`) + app.Converter = readFunction() + case "validator": + fmt.Println(`function Validator(converted, port) { + // Return false if the decoded, converted + // message is invalid and should be dropped. + + // if (port === 1 && typeof converted.led !== 'boolean') { + // return false; + // } + + return true; +} +########## Write your Validator here and end with Ctrl+D (EOF):`) + app.Validator = readFunction() + case "encoder": + fmt.Println(`function Encoder(object, port) { + // Encode downlink messages sent as + // object to an array or buffer of bytes. + var bytes = []; + + // if (port === 1) { + // bytes[0] = object.led ? 1 : 0; + // } + + return bytes; +} +########## Write your Encoder here and end with Ctrl+D (EOF):`) + app.Encoder = readFunction() + default: + ctx.Fatalf("Function %s does not exist", function) + } + } + + err = manager.SetApplication(app) + if err != nil { + ctx.WithError(err).Fatal("Could not update application") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Updated application") + }, +} + +func readFunction() string { + content, err := ioutil.ReadAll(os.Stdin) + if err != nil { + ctx.WithError(err).Fatal("Could not read function from STDIN.") + } + return strings.TrimSpace(string(content)) +} + +func init() { + applicationsPayloadFunctionsCmd.AddCommand(applicationsPayloadFunctionsSetCmd) +} diff --git a/ttnctl/cmd/applications_register.go b/ttnctl/cmd/applications_register.go new file mode 100644 index 000000000..b4c7b6971 --- /dev/null +++ b/ttnctl/cmd/applications_register.go @@ -0,0 +1,41 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var applicationsRegisterCmd = &cobra.Command{ + Use: "register", + Short: "Register this application with the handler", + Long: `ttnctl register can be used to register this application with the handler.`, + Example: `$ ttnctl applications register + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered application AppID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + appID := util.GetAppID(ctx) + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + err := manager.RegisterApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not register application") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Registered application") + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsRegisterCmd) +} diff --git a/ttnctl/cmd/applications_select.go b/ttnctl/cmd/applications_select.go new file mode 100644 index 000000000..5679333c0 --- /dev/null +++ b/ttnctl/cmd/applications_select.go @@ -0,0 +1,103 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +var applicationsSelectCmd = &cobra.Command{ + Use: "select", + Short: "select the application to use", + Long: `ttnctl applications select can be used to select the application to use in next commands.`, + Example: `$ ttnctl applications select + INFO Found one application "test", selecting that one. + INFO Found one EUI "0000000000000000", selecting that one. + INFO Updated configuration +`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + + apps, err := account.ListApplications() + if err != nil { + ctx.WithError(err).Fatal("Could not select applications") + } + + var appIdx int + var euiIdx int + + switch len(apps) { + case 0: + ctx.Info("You don't have any applications, not doing anything.") + return + case 1: + ctx.Infof("Found one application \"%s\", selecting that one.", apps[0].ID) + default: + ctx.Infof("Found %d applications:", len(apps)) + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "ID", "Description") + for i, app := range apps { + table.AddRow(i+1, app.ID, app.Name) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + fmt.Println("Which one do you want to use?") + fmt.Printf("Enter the number (1 - %d) > ", len(apps)) + fmt.Scanf("%d", &appIdx) + appIdx-- + } + + if appIdx < 0 || appIdx >= len(apps) { + ctx.Fatal("Invalid choice for application") + } + app := apps[appIdx] + + switch len(app.EUIs) { + case 0: + ctx.Info("You don't have any EUIs in your application") + case 1: + ctx.Infof("Found one EUI \"%s\", selecting that one.", app.EUIs[0]) + default: + ctx.Infof("Found %d EUIs", len(app.EUIs)) + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "EUI") + for i, eui := range app.EUIs { + table.AddRow(i+1, eui) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + fmt.Println("Which one do you want to use?") + fmt.Printf("Enter the number (1 - %d) > ", len(app.EUIs)) + fmt.Scanf("%d", &euiIdx) + euiIdx-- + } + + if euiIdx < 0 || euiIdx >= len(apps) { + ctx.Fatal("Invalid choice for EUI") + } + eui := app.EUIs[euiIdx] + + util.SetApp(ctx, app.ID, eui) + + ctx.Info("Updated configuration") + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsSelectCmd) +} diff --git a/ttnctl/cmd/applications_unregister.go b/ttnctl/cmd/applications_unregister.go new file mode 100644 index 000000000..6555f7fb3 --- /dev/null +++ b/ttnctl/cmd/applications_unregister.go @@ -0,0 +1,50 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var applicationsUnregisterCmd = &cobra.Command{ + Use: "unregister", + Short: "Unregister this application from the handler", + Long: `ttnctl unregister can be used to unregister this application from the handler.`, + Example: `$ ttnctl applications unregister +Are you sure you want to unregister application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Unregistered application AppID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + appID := util.GetAppID(ctx) + + if !confirm(fmt.Sprintf("Are you sure you want to unregister application %s?", appID)) { + ctx.Info("Not doing anything") + return + } + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + err := manager.DeleteApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not unregister application") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Unregistered application") + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsUnregisterCmd) +} diff --git a/ttnctl/cmd/components.go b/ttnctl/cmd/components.go new file mode 100644 index 000000000..212f81d61 --- /dev/null +++ b/ttnctl/cmd/components.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsCmd = &cobra.Command{ + Use: "components", + Hidden: true, + Aliases: []string{"component"}, + Short: "Manage network components", + Long: `ttnctl applications can be used to manage network components.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) + }, +} + +func init() { + RootCmd.AddCommand(componentsCmd) +} diff --git a/ttnctl/cmd/components_add.go b/ttnctl/cmd/components_add.go new file mode 100644 index 000000000..a5162dca6 --- /dev/null +++ b/ttnctl/cmd/components_add.go @@ -0,0 +1,39 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsAddCmd = &cobra.Command{ + Use: "add [Type] [ComponentID]", + Short: "Add a new network component", + Long: `ttnctl components add can be used to add a new network component.`, + Example: `$ ttnctld components add handler test 146 ! + INFO Added network component id=test type=handler +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + account := util.GetAccount(ctx) + + err := account.CreateComponent(args[0], args[1]) + if err != nil { + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could not add component") + } + + util.ForceRefreshToken(ctx) + + ctx.WithField("type", args[0]).WithField("id", args[1]).Info("Added network component") + }, +} + +func init() { + componentsCmd.AddCommand(componentsAddCmd) +} diff --git a/ttnctl/cmd/components_check.go b/ttnctl/cmd/components_check.go new file mode 100644 index 000000000..5f6c7b8fe --- /dev/null +++ b/ttnctl/cmd/components_check.go @@ -0,0 +1,74 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/health" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/spf13/cobra" +) + +var checkCmd = &cobra.Command{ + Use: "check [ServiceType] [ServiceID]", + Short: "Check routing services", + Long: `ttnctl components check is used to check the status of routing services`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + serviceType := args[0] + switch serviceType { + case "router", "broker", "handler": + default: + ctx.Fatalf("Service type %s unknown", serviceType) + } + + serviceID := args[1] + if !api.ValidID(serviceID) { + ctx.Fatalf("Service ID %s invalid", serviceID) + } + + dscConn, client := util.GetDiscovery(ctx) + defer dscConn.Close() + + res, err := client.Get(util.GetContext(ctx), &discovery.GetRequest{ + ServiceName: serviceType, + Id: serviceID, + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not get %s %s", serviceType, serviceID) + } + + conn, err := res.Dial() + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not dial %s %s", serviceType, serviceID) + } + defer conn.Close() + + start := time.Now() + ok, err := health.Check(conn) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not check %s %s", serviceType, serviceID) + } + ctx = ctx.WithField("Duration", time.Now().Sub(start)) + + if ok { + ctx.Infof("%s %s is up and running", serviceType, serviceID) + } else { + ctx.Warnf("%s %s is not feeling well", serviceType, serviceID) + } + + }, +} + +func init() { + componentsCmd.AddCommand(checkCmd) +} diff --git a/ttnctl/cmd/components_info.go b/ttnctl/cmd/components_info.go new file mode 100644 index 000000000..12fa44b95 --- /dev/null +++ b/ttnctl/cmd/components_info.go @@ -0,0 +1,49 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsInfoCmd = &cobra.Command{ + Use: "info [Type] [ComponentID]", + Short: "Get information about a network component.", + Long: `components info can be used to retrieve information about a network component.`, + Example: `$ ttnctl components info handler test + INFO Found network component + +Component ID: test +Type: handler +Created: 2016-10-06 09:52:28.766 +0000 UTC +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + account := util.GetAccount(ctx) + + component, err := account.FindComponent(args[0], args[1]) + if err != nil { + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could not find component") + } + + ctx.Info("Found network component") + + fmt.Println() + fmt.Printf("Component ID: %s\n", component.ID) + fmt.Printf("Type: %s\n", component.Type) + fmt.Printf("Created: %s\n", component.Created) + fmt.Println() + }, +} + +func init() { + componentsCmd.AddCommand(componentsInfoCmd) +} diff --git a/ttnctl/cmd/components_list.go b/ttnctl/cmd/components_list.go new file mode 100644 index 000000000..780f1870e --- /dev/null +++ b/ttnctl/cmd/components_list.go @@ -0,0 +1,122 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +func plural(n int, name string) string { + if n == 1 { + return name + } + return fmt.Sprintf("%ss", name) +} + +var componentsListCmd = &cobra.Command{ + Use: "list [Type]", + Short: "Get the token for a network component.", + Long: `components token gets a singed token for the component.`, + Example: `$ ttnctld components list 146 ! + INFO Found 0 routers + INFO Found 0 brokers + INFO Found 1 handler + + Type ID +1 handler test +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 1 { + cmd.UsageFunc()(cmd) + return + } + + act := util.GetAccount(ctx) + + components, err := act.ListComponents() + if err != nil { + ctx.WithError(err).Fatal("Could get list network components") + } + + typ := "" + if len(args) == 1 { + typ = args[0] + } + + routers := make([]account.Component, 0) + handlers := make([]account.Component, 0) + brokers := make([]account.Component, 0) + + for _, component := range components { + switch component.Type { + case string(account.Handler): + handlers = append(handlers, component) + case string(account.Broker): + brokers = append(brokers, component) + case string(account.Router): + routers = append(routers, component) + } + } + + if typ == "" || typ == "routers" || typ == "router" { + ctx.Info(fmt.Sprintf("Found %v %s", len(routers), plural(len(routers), "router"))) + + if len(routers) > 0 { + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Type", "ID") + for i, router := range routers { + table.AddRow(i, router.Type, router.ID) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + } + } + + if typ == "" || typ == "brokers" || typ == "broker" { + ctx.Info(fmt.Sprintf("Found %v %s", len(brokers), plural(len(brokers), "broker"))) + + if len(brokers) > 0 { + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Type", "ID") + for i, broker := range brokers { + table.AddRow(i, broker.Type, broker.ID) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + } + } + + if typ == "" || typ == "handlers" || typ == "handler" { + ctx.Info(fmt.Sprintf("Found %v %s", len(handlers), plural(len(handlers), "handler"))) + + if len(handlers) > 0 { + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Type", "ID") + for i, handler := range handlers { + table.AddRow(i, handler.Type, handler.ID) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + } + } + }, +} + +func init() { + componentsCmd.AddCommand(componentsListCmd) +} diff --git a/ttnctl/cmd/components_token.go b/ttnctl/cmd/components_token.go new file mode 100644 index 000000000..d9fc125ad --- /dev/null +++ b/ttnctl/cmd/components_token.go @@ -0,0 +1,45 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsTokenCmd = &cobra.Command{ + Use: "token [Type] [ComponentID]", + Short: "Get the token for a network component.", + Long: `components token gets a signed token for the component.`, + Example: `$ ttnctld components token handler test 146 ! + INFO Got component token id=test type=handler + +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudCIsInN1YiI6InRlc3QxyzJ0eXBlIjoiaGFuZGxlciIsImlhdCI6MTQ3NTc0NzY3MywiZXhwIjoxNDgzNzgyODczfQ.Bf6Gy6xTE2m7fkYSd4WHs3UgRaAEXkox2jjJeaBahVNU365n_wI4_oWX_B3mkMOa1ZL3IB2JagAybo50mTApPtnGiRjDczGjqkkbBiXPcwA8SvmyKTKNkPkrpzGIioq9itjpYDuMJixgLh4gYlK0B_1jkH23ZFoslzn7WfYYe3AKC0JZAhePgQygJ2Zn3w6cGZOqgRvblIIcGynSEqqP3aKyKRhtnwofao-w-jzWqINGvAcMt1iW7JN3hX9yW4IXRicB4_-L0Aaq1sqvRpoh8z9SmpkkE8oBmWqPsUAXTECuoYc4kezjGcDg4YnBfBQtT-itPTfdb8-vq2izxyztsw +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + account := util.GetAccount(ctx) + + token, err := account.ComponentToken(args[0], args[1]) + if err != nil { + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could not get component token") + } + + ctx.WithField("type", args[0]).WithField("id", args[1]).Info("Got component token") + + fmt.Println() + fmt.Println(token) + fmt.Println() + }, +} + +func init() { + componentsCmd.AddCommand(componentsTokenCmd) +} diff --git a/ttnctl/cmd/config.go b/ttnctl/cmd/config.go new file mode 100644 index 000000000..1aece71d3 --- /dev/null +++ b/ttnctl/cmd/config.go @@ -0,0 +1,22 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var configCmd = &cobra.Command{ + Use: "config", + Short: "Print the used configuration", + Long: `ttnctl config prints the configuration that is used`, + Run: func(cmd *cobra.Command, args []string) { + util.PrintConfig(ctx, false) + }, +} + +func init() { + RootCmd.AddCommand(configCmd) +} diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go new file mode 100644 index 000000000..e3301ef37 --- /dev/null +++ b/ttnctl/cmd/devices.go @@ -0,0 +1,34 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var devicesCmd = &cobra.Command{ + Use: "devices", + Aliases: []string{"device"}, + Short: "Manage devices", + Long: `ttnctl devices can be used to manage devices.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) + ctx.WithFields(log.Fields{ + "AppID": util.GetAppID(ctx), + "AppEUI": util.GetAppEUI(ctx), + }).Info("Using Application") + }, +} + +func init() { + RootCmd.AddCommand(devicesCmd) + devicesCmd.PersistentFlags().String("app-id", "", "The app ID to use") + viper.BindPFlag("app-id", devicesCmd.PersistentFlags().Lookup("app-id")) + devicesCmd.PersistentFlags().String("app-eui", "", "The app EUI to use") + viper.BindPFlag("app-eui", devicesCmd.PersistentFlags().Lookup("app-eui")) +} diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go new file mode 100644 index 000000000..274843db7 --- /dev/null +++ b/ttnctl/cmd/devices_delete.go @@ -0,0 +1,63 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var devicesDeleteCmd = &cobra.Command{ + Use: "delete [Device ID]", + Short: "Delete a device", + Long: `ttnctl devices delete can be used to delete a device.`, + Example: `$ ttnctl devices delete test + INFO Using Application AppID=test +Are you sure you want to delete device test from application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Deleted device AppID=test DevID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := util.GetAppID(ctx) + + if !confirm(fmt.Sprintf("Are you sure you want to delete device %s from application %s?", devID, appID)) { + ctx.Info("Not doing anything") + return + } + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + err := manager.DeleteDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not delete device.") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + }).Info("Deleted device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesDeleteCmd) +} diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go new file mode 100644 index 000000000..44388e04d --- /dev/null +++ b/ttnctl/cmd/devices_info.go @@ -0,0 +1,157 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var devicesInfoCmd = &cobra.Command{ + Use: "info [Device ID]", + Short: "Get information about a device", + Long: `ttnctl devices info can be used to get information about a device.`, + Example: `$ ttnctl devices info test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found device + + Application ID: test + Device ID: test + Last Seen: never + + LoRaWAN Info: + + AppEUI: 70B3D57EF0000024 + DevEUI: 0001D544B2936FCE + DevAddr: 26001ADA + AppKey: + AppSKey: D8DD37B4B709BA76C6FEC62CAD0CCE51 + NwkSKey: 3382A3066850293421ED8D392B9BF4DF + FCntUp: 0 + FCntDown: 0 + Options: +`, + Run: func(cmd *cobra.Command, args []string) { + + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := util.GetAppID(ctx) + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + dev, err := manager.GetDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not get existing device.") + } + + byteFormat, _ := cmd.Flags().GetString("format") + + ctx.Info("Found device") + + fmt.Println() + + fmt.Printf(" Application ID: %s\n", dev.AppId) + fmt.Printf(" Device ID: %s\n", dev.DevId) + if lorawan := dev.GetLorawanDevice(); lorawan != nil { + lastSeen := "never" + if lorawan.LastSeen > 0 { + lastSeen = fmt.Sprintf("%s", time.Unix(0, 0).Add(time.Duration(lorawan.LastSeen))) + } + + fmt.Printf(" Last Seen: %s\n", lastSeen) + fmt.Println() + fmt.Println(" LoRaWAN Info:") + fmt.Println() + fmt.Printf(" AppEUI: %s\n", formatBytes(lorawan.AppEui, byteFormat)) + fmt.Printf(" DevEUI: %s\n", formatBytes(lorawan.DevEui, byteFormat)) + fmt.Printf(" DevAddr: %s\n", formatBytes(lorawan.DevAddr, byteFormat)) + fmt.Printf(" AppKey: %s\n", formatBytes(lorawan.AppKey, byteFormat)) + fmt.Printf(" AppSKey: %s\n", formatBytes(lorawan.AppSKey, byteFormat)) + fmt.Printf(" NwkSKey: %s\n", formatBytes(lorawan.NwkSKey, byteFormat)) + + fmt.Printf(" FCntUp: %d\n", lorawan.FCntUp) + fmt.Printf(" FCntDown: %d\n", lorawan.FCntDown) + options := []string{} + if lorawan.DisableFCntCheck { + options = append(options, "FCntCheckDisabled") + } else { + options = append(options, "FCntCheckEnabled") + } + if lorawan.Uses32BitFCnt { + options = append(options, "32BitFCnt") + } else { + options = append(options, "16BitFCnt") + } + fmt.Printf(" Options: %s\n", strings.Join(options, ", ")) + } + + }, +} + +type formattableBytes interface { + IsEmpty() bool + Bytes() []byte +} + +func formatBytes(toPrint interface{}, format string) string { + if i, ok := toPrint.(formattableBytes); ok { + if i.IsEmpty() { + return "" + } + switch format { + case "msb": + return cStyle(i.Bytes(), true) + " (msb first)" + case "lsb": + return cStyle(i.Bytes(), false) + " (lsb first)" + case "hex": + return fmt.Sprintf("%X", i.Bytes()) + } + } + return fmt.Sprintf("%s", toPrint) +} + +// cStyle prints the byte slice in C-Style +func cStyle(bytes []byte, msbf bool) string { + output := "{" + if !msbf { + bytes = reverse(bytes) + } + for i, b := range bytes { + if i != 0 { + output += ", " + } + output += fmt.Sprintf("0x%02X", b) + } + output += "}" + return output +} + +// reverse is used to convert between MSB-first and LSB-first +func reverse(in []byte) (out []byte) { + for i := len(in) - 1; i >= 0; i-- { + out = append(out, in[i]) + } + return +} + +func init() { + devicesCmd.AddCommand(devicesInfoCmd) + devicesInfoCmd.Flags().String("format", "hex", "Formatting: hex/msb/lsb") +} diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go new file mode 100644 index 000000000..7fa1cf7d9 --- /dev/null +++ b/ttnctl/cmd/devices_list.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +var devicesListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all devices for the current application", + Long: `ttnctl devices list can be used to list all devices for the current application.`, + Example: `$ ttnctl devices list + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + +DevID AppEUI DevEUI DevAddr +test 70B3D57EF0000024 0001D544B2936FCE 26001ADA + + INFO Listed 1 devices AppID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + appID := util.GetAppID(ctx) + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + devices, err := manager.GetDevicesForApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not get devices.") + } + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("DevID", "AppEUI", "DevEUI", "DevAddr") + for _, dev := range devices { + if lorawan := dev.GetLorawanDevice(); lorawan != nil { + devAddr := lorawan.DevAddr + if devAddr.IsEmpty() { + devAddr = nil + } + table.AddRow(dev.DevId, lorawan.AppEui, lorawan.DevEui, devAddr) + } else { + table.AddRow(dev.DevId) + } + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Listed %d devices", len(devices)) + }, +} + +func init() { + devicesCmd.AddCommand(devicesListCmd) +} diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go new file mode 100644 index 000000000..006d48e60 --- /dev/null +++ b/ttnctl/cmd/devices_personalize.go @@ -0,0 +1,114 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "strings" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var devicesPersonalizeCmd = &cobra.Command{ + Use: "personalize [Device ID] [NwkSKey] [AppSKey]", + Short: "Personalize a device", + Long: `ttnctl devices personalize can be used to personalize a device (ABP).`, + Example: `$ ttnctl devices personalize test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random NwkSKey... + INFO Generating random AppSKey... + INFO Discovering Handler... Handler=ttn-handler-eu + INFO Connecting with Handler... Handler=eu.thethings.network:1904 + INFO Requesting DevAddr for device... + INFO Personalized device AppID=test AppSKey=D8DD37B4B709BA76C6FEC62CAD0CCE51 DevAddr=26001ADA DevID=test NwkSKey=3382A3066850293421ED8D392B9BF4DF +`, + Run: func(cmd *cobra.Command, args []string) { + + var err error + + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := util.GetAppID(ctx) + + var nwkSKey types.NwkSKey + if len(args) > 1 { + nwkSKey, err = types.ParseNwkSKey(args[1]) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + } else { + ctx.Info("Generating random NwkSKey...") + copy(nwkSKey[:], random.Bytes(16)) + } + + var appSKey types.AppSKey + if len(args) > 2 { + appSKey, err = types.ParseAppSKey(args[2]) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + } else { + ctx.Info("Generating random AppSKey...") + copy(appSKey[:], random.Bytes(16)) + } + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + dev, err := manager.GetDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not get existing device.") + } + + ctx.Info("Requesting DevAddr for device...") + + var constraints []string + if lorawan := dev.GetLorawanDevice(); lorawan != nil && lorawan.ActivationConstraints != "" { + constraints = strings.Split(lorawan.ActivationConstraints, ",") + } + constraints = append(constraints, "abp") + + devAddr, err := manager.GetDevAddr(constraints...) + if err != nil { + ctx.WithError(err).Fatal("Could not request device address") + } + + var emptyAppKey types.AppKey + dev.GetLorawanDevice().AppKey = &emptyAppKey + dev.GetLorawanDevice().DevAddr = &devAddr + dev.GetLorawanDevice().NwkSKey = &nwkSKey + dev.GetLorawanDevice().AppSKey = &appSKey + dev.GetLorawanDevice().FCntUp = 0 + dev.GetLorawanDevice().FCntDown = 0 + + err = manager.SetDevice(dev) + if err != nil { + ctx.WithError(err).Fatal("Could not update Device") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + "DevAddr": devAddr, + "NwkSKey": nwkSKey, + "AppSKey": appSKey, + }).Info("Personalized device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesPersonalizeCmd) +} diff --git a/ttnctl/cmd/devices_register.go b/ttnctl/cmd/devices_register.go new file mode 100644 index 000000000..4aa97a60b --- /dev/null +++ b/ttnctl/cmd/devices_register.go @@ -0,0 +1,98 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var devicesRegisterCmd = &cobra.Command{ + Use: "register [Device ID] [DevEUI] [AppKey]", + Short: "Register a new device", + Long: `ttnctl devices register can be used to register a new device.`, + Example: `$ ttnctl devices register test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random DevEUI... + INFO Generating random AppKey... + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + var err error + + if len(args) == 0 { + ctx.Fatalf("Device ID is required") + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := util.GetAppID(ctx) + appEUI := util.GetAppEUI(ctx) + + var devEUI types.DevEUI + if len(args) > 1 { + devEUI, err = types.ParseDevEUI(args[1]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + } else { + ctx.Info("Generating random DevEUI...") + copy(devEUI[1:], random.Bytes(7)) + } + + var appKey types.AppKey + if len(args) > 2 { + appKey, err = types.ParseAppKey(args[2]) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + } else { + ctx.Info("Generating random AppKey...") + copy(appKey[:], random.Bytes(16)) + } + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + err = manager.SetDevice(&handler.Device{ + AppId: appID, + DevId: devID, + Device: &handler.Device_LorawanDevice{LorawanDevice: &lorawan.Device{ + AppId: appID, + DevId: devID, + AppEui: &appEUI, + DevEui: &devEUI, + AppKey: &appKey, + Uses32BitFCnt: true, + }}, + }) + if err != nil { + ctx.WithError(err).Fatal("Could not register Device") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + "AppEUI": appEUI, + "DevEUI": devEUI, + "AppKey": appKey, + }).Info("Registered device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesRegisterCmd) +} diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go new file mode 100644 index 000000000..1a38c4e92 --- /dev/null +++ b/ttnctl/cmd/devices_set.go @@ -0,0 +1,174 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "os" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +var devicesSetCmd = &cobra.Command{ + Use: "set [Device ID]", + Short: "Set properties of a device", + Long: `ttnctl devices set can be used to set properties of a device.`, + Example: `$ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Updated device AppID=test DevID=test +`, + Run: func(cmd *cobra.Command, args []string) { + + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := util.GetAppID(ctx) + + conn, manager := util.GetHandlerManager(ctx, appID) + defer conn.Close() + + dev, err := manager.GetDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not get existing device.") + } + + // Do all updates + + if in, err := cmd.Flags().GetString("app-eui"); err == nil && in != "" { + + ctx.Warn("Manually changing the AppEUI of a device might break routing for this device") + if override, _ := cmd.Flags().GetBool("override"); !override { + ctx.Warnf("Use the --override flag if you're really sure you want to do this") + os.Exit(0) + } + + appEUI, err := types.ParseAppEUI(in) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + dev.GetLorawanDevice().AppEui = &appEUI + } + + if in, err := cmd.Flags().GetString("dev-eui"); err == nil && in != "" { + + ctx.Warn("Manually changing the DevEUI of a device might break routing for this device") + if override, _ := cmd.Flags().GetBool("override"); !override { + ctx.Warnf("Use the --override flag if you're really sure you want to do this") + os.Exit(0) + } + + devEUI, err := types.ParseDevEUI(in) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + dev.GetLorawanDevice().DevEui = &devEUI + } + + if in, err := cmd.Flags().GetString("dev-addr"); err == nil && in != "" { + + ctx.Warn("Manually changing the DevAddr of a device might break routing for this device") + if override, _ := cmd.Flags().GetBool("override"); !override { + ctx.Warnf("Use the --override flag if you're really sure you want to do this") + os.Exit(0) + } + + devAddr, err := types.ParseDevAddr(in) + if err != nil { + ctx.Fatalf("Invalid DevAddr: %s", err) + } + dev.GetLorawanDevice().DevAddr = &devAddr + } + + if in, err := cmd.Flags().GetString("nwk-s-key"); err == nil && in != "" { + key, err := types.ParseNwkSKey(in) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + dev.GetLorawanDevice().NwkSKey = &key + } + + if in, err := cmd.Flags().GetString("app-s-key"); err == nil && in != "" { + key, err := types.ParseAppSKey(in) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + dev.GetLorawanDevice().AppSKey = &key + } + + if in, err := cmd.Flags().GetString("app-key"); err == nil && in != "" { + key, err := types.ParseAppKey(in) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + dev.GetLorawanDevice().AppKey = &key + } + + if in, err := cmd.Flags().GetInt("fcnt-up"); err == nil && in != -1 { + dev.GetLorawanDevice().FCntUp = uint32(in) + } + + if in, err := cmd.Flags().GetInt("fcnt-down"); err == nil && in != -1 { + dev.GetLorawanDevice().FCntDown = uint32(in) + } + + if in, err := cmd.Flags().GetBool("enable-fcnt-check"); err == nil && in { + dev.GetLorawanDevice().DisableFCntCheck = false + } + + if in, err := cmd.Flags().GetBool("disable-fcnt-check"); err == nil && in { + dev.GetLorawanDevice().DisableFCntCheck = true + } + + if in, err := cmd.Flags().GetBool("32-bit-fcnt"); err == nil && in { + dev.GetLorawanDevice().Uses32BitFCnt = true + } + + if in, err := cmd.Flags().GetBool("16-bit-fcnt"); err == nil && in { + dev.GetLorawanDevice().Uses32BitFCnt = false + } + + err = manager.SetDevice(dev) + if err != nil { + ctx.WithError(err).Fatal("Could not update Device") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + }).Info("Updated device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesSetCmd) + + devicesSetCmd.Flags().Bool("override", false, "Override protection against breaking changes") + + devicesSetCmd.Flags().String("app-eui", "", "Set AppEUI") + devicesSetCmd.Flags().String("dev-eui", "", "Set DevEUI") + devicesSetCmd.Flags().String("dev-addr", "", "Set DevAddr") + devicesSetCmd.Flags().String("nwk-s-key", "", "Set NwkSKey") + devicesSetCmd.Flags().String("app-s-key", "", "Set AppSKey") + devicesSetCmd.Flags().String("app-key", "", "Set AppKey") + + devicesSetCmd.Flags().Int("fcnt-up", -1, "Set FCnt Up") + devicesSetCmd.Flags().Int("fcnt-down", -1, "Set FCnt Down") + + devicesSetCmd.Flags().Bool("disable-fcnt-check", false, "Disable FCnt check") + devicesSetCmd.Flags().Bool("enable-fcnt-check", false, "Enable FCnt check (default)") + devicesSetCmd.Flags().Bool("32-bit-fcnt", false, "Use 32 bit FCnt (default)") + devicesSetCmd.Flags().Bool("16-bit-fcnt", false, "Use 16 bit FCnt") +} diff --git a/ttnctl/cmd/discover.go b/ttnctl/cmd/discover.go new file mode 100644 index 000000000..a4cba21f9 --- /dev/null +++ b/ttnctl/cmd/discover.go @@ -0,0 +1,91 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strings" + + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/spf13/cobra" +) + +const serviceFmt = "%-36s %-36s %-20s %-6s\n" + +func crop(in string, length int) string { + if len(in) > length { + return in[:length] + } + return in +} + +var discoverCmd = &cobra.Command{ + Use: "discover [ServiceType]", + Short: "Discover routing services", + Long: `ttnctl discover is used to discover routing services`, + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + serviceType := strings.TrimRight(args[0], "s") // Allow both singular and plural + + switch serviceType { + case "router": + ctx.Info("Discovering routers...") + case "broker": + ctx.Info("Discovering brokers and their prefixes...") + case "handler": + ctx.Info("Discovering handlers and their apps...") + default: + ctx.Fatalf("Service type %s unknown", serviceType) + } + + conn, client := util.GetDiscovery(ctx) + defer conn.Close() + + res, err := client.GetAll(util.GetContext(ctx), &discovery.GetServiceRequest{ + ServiceName: serviceType, + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not get %ss", serviceType) + } + + ctx.Infof("Discovered %d %ss", len(res.Services), serviceType) + + fmt.Printf(serviceFmt, "ID", "ADDRESS", "VERSION", "PUBLIC") + fmt.Printf(serviceFmt, "==", "=======", "=======", "======") + fmt.Println() + for _, service := range res.Services { + fmt.Printf(serviceFmt, service.Id, crop(service.NetAddress, 36), crop(service.ServiceVersion, 20), fmt.Sprintf("%v", service.Public)) + if showMetadata, _ := cmd.Flags().GetBool("metadata"); showMetadata { + switch serviceType { + case "broker": + fmt.Println(" DevAddr Prefixes:") + for _, prefix := range service.DevAddrPrefixes() { + min := types.DevAddr{0x00, 0x00, 0x00, 0x00}.WithPrefix(prefix) + max := types.DevAddr{0xff, 0xff, 0xff, 0xff}.WithPrefix(prefix) + fmt.Printf(" %s (%s-%s)\n", prefix, min, max) + } + case "handler": + fmt.Println(" AppIDs:") + for _, appID := range service.AppIDs() { + fmt.Println(" ", appID) + } + } + fmt.Println() + } + } + }, +} + +func init() { + RootCmd.AddCommand(discoverCmd) + discoverCmd.Flags().Bool("metadata", false, "Show additional metadata") +} diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md new file mode 100644 index 000000000..005e6aa5c --- /dev/null +++ b/ttnctl/cmd/docs/README.md @@ -0,0 +1,565 @@ +# API Reference + +Control The Things Network from the command line. + +**Options** + +``` + --auth-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --config string config file (default is $HOME/.ttnctl.yml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode + --discovery-address string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --handler-id string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --mqtt-address string The address of the MQTT broker (default "eu.thethings.network:1883") + --mqtt-password string The password for the MQTT broker + --mqtt-username string The username for the MQTT broker + --router-id string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications + +ttnctl applications can be used to manage applications. + +### ttnctl applications add + +ttnctl applications add can be used to add a new application to your account. + +**Usage:** `ttnctl applications add [AppID] [Description]` + +**Options** + +``` + --app-eui stringSlice LoRaWAN AppEUI to register with application + --skip-register Do not register application with the Handler + --skip-select Do not select this application (also adds --skip-register) +``` + +**Example** + +``` +$ ttnctl applications add test "Test application" + INFO Added Application + INFO Selected Current Application +``` + +### ttnctl applications delete + +ttnctl devices delete can be used to delete an application. + +**Usage:** `ttnctl applications delete` + +### ttnctl applications info + +ttnctl applications info can be used to info applications. + +**Usage:** `ttnctl applications info [AppID]` + +**Example** + +``` +$ ttnctl applications info + INFO Found application + +AppID: test +Name: Test application +EUIs: + - 0000000000000000 + +Access Keys: + - Name: default key + Key: FZYr01cUhdhY1KBiMghUl+/gXyqXhrF6y+1ww7+DzHg= + Rights: messages:up:r, messages:down:w + +Collaborators: + - Name: yourname + Rights: settings, delete, collaborators +``` + +### ttnctl applications list + +ttnctl applications list can be used to list applications. + +**Usage:** `ttnctl applications list` + +**Example** + +``` +$ ttnctl applications list + INFO Found one application: + + ID Description EUIs Access Keys Collaborators +1 test Test application 1 1 1 +``` + +### ttnctl applications pf + +ttnctl applications pf shows the payload functions for decoding, +converting and validating binary payload. + +**Usage:** `ttnctl applications pf` + +**Example** + +``` +$ ttnctl applications pf + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found Application + INFO Decoder function +function Decoder(bytes, port) { + var decoded = {}; + if (port === 1) { + decoded.led = bytes[0]; + } + return decoded; +} + INFO No converter function + INFO No validator function + INFO No encoder function +``` + +#### ttnctl applications pf set + +ttnctl pf set can be used to get or set payload functions of an application. +The functions are read from the supplied file or from STDIN. + +**Usage:** `ttnctl applications pf set [decoder/converter/validator/encoder] [file.js]` + +**Example** + +``` +$ ttnctl applications pf set decoder + INFO Discovering Handler... + INFO Connecting with Handler... +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; + + // if (port === 1) { + // decoded.led = bytes[0]; + // } + + return decoded; +} +########## Write your Decoder here and end with Ctrl+D (EOF): +function Decoder(bytes, port) { + var decoded = {}; + + // if (port === 1) { + // decoded.led = bytes[0]; + // } + + return decoded; +} + INFO Updated application AppID=test +``` + +### ttnctl applications register + +ttnctl register can be used to register this application with the handler. + +**Usage:** `ttnctl applications register` + +**Example** + +``` +$ ttnctl applications register + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered application AppID=test +``` + +### ttnctl applications select + +ttnctl applications select can be used to select the application to use in next commands. + +**Usage:** `ttnctl applications select` + +**Example** + +``` +$ ttnctl applications select + INFO Found one application "test", selecting that one. + INFO Found one EUI "0000000000000000", selecting that one. + INFO Updated configuration +``` + +### ttnctl applications unregister + +ttnctl unregister can be used to unregister this application from the handler. + +**Usage:** `ttnctl applications unregister` + +**Example** + +``` +$ ttnctl applications unregister +Are you sure you want to unregister application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Unregistered application AppID=test +``` + +## ttnctl config + +ttnctl config prints the configuration that is used + +**Usage:** `ttnctl config` + +## ttnctl devices + +ttnctl devices can be used to manage devices. + +**Options** + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use +``` + +### ttnctl devices delete + +ttnctl devices delete can be used to delete a device. + +**Usage:** `ttnctl devices delete [Device ID]` + +**Example** + +``` +$ ttnctl devices delete test + INFO Using Application AppID=test +Are you sure you want to delete device test from application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Deleted device AppID=test DevID=test +``` + +### ttnctl devices info + +ttnctl devices info can be used to get information about a device. + +**Usage:** `ttnctl devices info [Device ID]` + +**Options** + +``` + --format string Formatting: hex/msb/lsb (default "hex") +``` + +**Example** + +``` +$ ttnctl devices info test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found device + + Application ID: test + Device ID: test + Last Seen: never + + LoRaWAN Info: + + AppEUI: 70B3D57EF0000024 + DevEUI: 0001D544B2936FCE + DevAddr: 26001ADA + AppKey: + AppSKey: D8DD37B4B709BA76C6FEC62CAD0CCE51 + NwkSKey: 3382A3066850293421ED8D392B9BF4DF + FCntUp: 0 + FCntDown: 0 + Options: +``` + +### ttnctl devices list + +ttnctl devices list can be used to list all devices for the current application. + +**Usage:** `ttnctl devices list` + +**Example** + +``` +$ ttnctl devices list + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + +DevID AppEUI DevEUI DevAddr +test 70B3D57EF0000024 0001D544B2936FCE 26001ADA + + INFO Listed 1 devices AppID=test +``` + +### ttnctl devices personalize + +ttnctl devices personalize can be used to personalize a device (ABP). + +**Usage:** `ttnctl devices personalize [Device ID] [NwkSKey] [AppSKey]` + +**Example** + +``` +$ ttnctl devices personalize test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random NwkSKey... + INFO Generating random AppSKey... + INFO Discovering Handler... Handler=ttn-handler-eu + INFO Connecting with Handler... Handler=eu.thethings.network:1904 + INFO Requesting DevAddr for device... + INFO Personalized device AppID=test AppSKey=D8DD37B4B709BA76C6FEC62CAD0CCE51 DevAddr=26001ADA DevID=test NwkSKey=3382A3066850293421ED8D392B9BF4DF +``` + +### ttnctl devices register + +ttnctl devices register can be used to register a new device. + +**Usage:** `ttnctl devices register [Device ID] [DevEUI] [AppKey]` + +**Example** + +``` +$ ttnctl devices register test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random DevEUI... + INFO Generating random AppKey... + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test +``` + +### ttnctl devices set + +ttnctl devices set can be used to set properties of a device. + +**Usage:** `ttnctl devices set [Device ID]` + +**Options** + +``` + --16-bit-fcnt Use 16 bit FCnt + --32-bit-fcnt Use 32 bit FCnt (default) + --app-eui string Set AppEUI + --app-key string Set AppKey + --app-s-key string Set AppSKey + --dev-addr string Set DevAddr + --dev-eui string Set DevEUI + --disable-fcnt-check Disable FCnt check + --enable-fcnt-check Enable FCnt check (default) + --fcnt-down int Set FCnt Down (default -1) + --fcnt-up int Set FCnt Up (default -1) + --nwk-s-key string Set NwkSKey + --override Override protection against breaking changes +``` + +**Example** + +``` +$ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Updated device AppID=test DevID=test +``` + +## ttnctl downlink + +ttnctl downlink can be used to send a downlink message to a device. + +**Usage:** `ttnctl downlink [DevID] [Payload]` + +**Options** + +``` + --fport int FPort for downlink (default 1) + --json Provide the payload as JSON +``` + +**Example** + +``` +$ ttnctl downlink test aabc + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test + +$ ttnctl downlink test --json '{"led":"on"}' + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test +``` + +## ttnctl gateways + +ttnctl gateways can be used to manage gateways. + +### ttnctl gateways delete + +ttnctl gateways delete can be used to delete a gateway + +**Usage:** `ttnctl gateways delete [GatewayID]` + +**Example** + +``` +$ ttnctl gateways delete test + INFO Deleted gateway Gateway ID=test +``` + +### ttnctl gateways edit + +ttnctl gateways edit can be used to edit settings of a gateway + +**Usage:** `ttnctl gateways edit [GatewayID]` + +**Options** + +``` + --frequency-plan string The frequency plan to use on the gateway + --location string The location of the gateway +``` + +**Example** + +``` +$ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU + INFO Edited gateway Gateway ID=test +``` + +### ttnctl gateways info + +ttnctl gateways info can be used to get information about a gateway + +**Usage:** `ttnctl gateways info [GatewayID]` + +### ttnctl gateways list + +ttnctl gateways list can be used to list the gateways you have access to + +**Usage:** `ttnctl gateways list` + +**Example** + +``` +$ ttnctl gateways list + ID Activated Frequency Plan Coordinates +1 test true US (52.3740, 4.8896) +``` + +### ttnctl gateways register + +ttnctl gateways register can be used to register a gateway + +**Usage:** `ttnctl gateways register [GatewayID] [FrequencyPlan] [Location]` + +**Example** + +``` +$ ttnctl gateways register test US 52.37403,4.88968 + INFO Registered gateway Gateway ID=test +``` + +### ttnctl gateways status + +ttnctl gateways status can be used to get status of gateways. + +**Usage:** `ttnctl gateways status [gatewayID]` + +**Example** + +``` +$ ttnctl gateways status test + INFO Discovering Router... + INFO Connecting with Router... + INFO Connected to Router + INFO Received status + + Last seen: 2016-09-20 08:25:27.94138808 +0200 CEST + Timestamp: 0 + Reported time: 2016-09-20 08:25:26 +0200 CEST + GPS coordinates: (52.372791 4.900300) + Rtt: not available + Rx: (in: 0; ok: 0) + Tx: (in: 0; ok: 0) +``` + +## ttnctl selfupdate + +ttnctl selfupdate updates the current ttnctl to the latest version + +**Usage:** `ttnctl selfupdate` + +## ttnctl subscribe + +ttnctl subscribe can be used to subscribe to events for this application. + +**Usage:** `ttnctl subscribe` + +## ttnctl user + +ttnctl user shows the current logged on user's profile + +**Usage:** `ttnctl user` + +**Example** + +``` +$ ttnctl user + INFO Found user profile: + + Username: yourname + Name: Your Name + Email: your@email.org + + INFO Login credentials valid until Sep 20 09:04:12 +``` + +### ttnctl user login + +ttnctl user login allows you to log in to your TTN account. + +**Usage:** `ttnctl user login [access code]` + +**Example** + +``` +First get an access code from your TTN profile by going to +https://account.thethingsnetwork.org and clicking "ttnctl access code". + +$ ttnctl user login [paste the access code you requested above] + INFO Successfully logged in as yourname (your@email.org) +``` + +### ttnctl user logout + +ttnctl user logout logs out the current user + +**Usage:** `ttnctl user logout` + +### ttnctl user register + +ttnctl user register allows you to register a new user in the account server + +**Usage:** `ttnctl user register [username] [e-mail]` + +**Example** + +``` +$ ttnctl user register yourname your@email.org +Password: + INFO Registered user + WARN You might have to verify your email before you can login +``` + +## ttnctl version + +ttnctl version gets the build and version information of ttnctl + +**Usage:** `ttnctl version` + diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go new file mode 100644 index 000000000..4541452bf --- /dev/null +++ b/ttnctl/cmd/docs/generate.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/cmd" + "github.com/TheThingsNetwork/ttn/utils/docs" +) + +func main() { + fmt.Println(`# API Reference + +Control The Things Network from the command line. +`) + fmt.Print(docs.Generate(cmd.RootCmd)) +} diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go new file mode 100644 index 000000000..fb408c6ec --- /dev/null +++ b/ttnctl/cmd/downlink.go @@ -0,0 +1,106 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "encoding/json" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var downlinkCmd = &cobra.Command{ + Use: "downlink [DevID] [Payload]", + Short: "Send a downlink message to a device", + Long: `ttnctl downlink can be used to send a downlink message to a device.`, + Example: `$ ttnctl downlink test aabc + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test + +$ ttnctl downlink test --json '{"led":"on"}' + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test +`, + Run: func(cmd *cobra.Command, args []string) { + client := util.GetMQTT(ctx) + defer client.Disconnect() + + if len(args) < 2 { + ctx.Info("Not enough arguments. Please, provide a devId and a Payload") + cmd.UsageFunc()(cmd) + return + } + + appID := util.GetAppID(ctx) + ctx = ctx.WithField("AppID", appID) + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + ctx = ctx.WithField("DevID", devID) + + jsonflag, err := cmd.Flags().GetBool("json") + + if err != nil { + ctx.WithError(err).Fatal("Failed to read json flag") + } + + fPort, err := cmd.Flags().GetInt("fport") + + if err != nil { + ctx.WithError(err).Fatal("Failed to read fport flag") + } + + message := types.DownlinkMessage{ + AppID: appID, + DevID: devID, + FPort: uint8(fPort), + } + + if args[1] == "" { + ctx.Info("Invalid command") + cmd.UsageFunc()(cmd) + return + } + + if jsonflag { + // Valid payload provided + json flag + _, err := types.ParseHEX(args[1], len(args[1])/2) + if err == nil { + ctx.WithError(err).Fatal("You are providing a valid HEX payload while sending payload in JSON.") + } + + err = json.Unmarshal([]byte(args[1]), &message.PayloadFields) + + if err != nil { + ctx.WithError(err).Fatal("Invalid json string") + return + } + } else { // Payload provided + payload, err := types.ParseHEX(args[1], len(args[1])/2) + if err != nil { + ctx.WithError(err).Fatal("Invalid Payload") + } + + message.PayloadRaw = payload + } + token := client.PublishDownlink(message) + token.Wait() + if token.Error() != nil { + ctx.WithError(token.Error()).Fatal("Could not enqueue downlink") + } + ctx.Info("Enqueued downlink") + }, +} + +func init() { + RootCmd.AddCommand(downlinkCmd) + downlinkCmd.Flags().Int("fport", 1, "FPort for downlink") + downlinkCmd.Flags().Bool("json", false, "Provide the payload as JSON") +} diff --git a/ttnctl/cmd/gateways.go b/ttnctl/cmd/gateways.go new file mode 100644 index 000000000..8ef1574b0 --- /dev/null +++ b/ttnctl/cmd/gateways.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysCmd = &cobra.Command{ + Use: "gateways", + Aliases: []string{"gateway"}, + Short: "Manage gateways", + Long: `ttnctl gateways can be used to manage gateways.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) + }, +} + +func init() { + RootCmd.AddCommand(gatewaysCmd) +} diff --git a/ttnctl/cmd/gateways_delete.go b/ttnctl/cmd/gateways_delete.go new file mode 100644 index 000000000..48d1c80ba --- /dev/null +++ b/ttnctl/cmd/gateways_delete.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysDeleteCmd = &cobra.Command{ + Use: "delete [GatewayID]", + Short: "Delete a gateway", + Long: `ttnctl gateways delete can be used to delete a gateway`, + Example: `$ ttnctl gateways delete test + INFO Deleted gateway Gateway ID=test +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + account := util.GetAccount(ctx) + err := account.DeleteGateway(gatewayID) + if err != nil { + ctx.WithError(err).Fatal("Could not list gateways") + } + + ctx.WithField("Gateway ID", gatewayID).Info("Deleted gateway") + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysDeleteCmd) +} diff --git a/ttnctl/cmd/gateways_edit.go b/ttnctl/cmd/gateways_edit.go new file mode 100644 index 000000000..8601bcee2 --- /dev/null +++ b/ttnctl/cmd/gateways_edit.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysEditCmd = &cobra.Command{ + Use: "edit [GatewayID]", + Short: "edit a gateway", + Long: `ttnctl gateways edit can be used to edit settings of a gateway`, + Example: `$ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU + INFO Edited gateway Gateway ID=test +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + var edits account.GatewayEdits + + frequencyPlan, err := cmd.Flags().GetString("frequency-plan") + if err != nil { + ctx.WithError(err).Fatal("Invalid frequency-plan") + } + + if frequencyPlan != "" { + edits.FrequencyPlan = frequencyPlan + } + + locationStr, err := cmd.Flags().GetString("location") + if err != nil { + ctx.WithError(err).Fatal("Invalid location") + } + + if locationStr != "" { + location, err := util.ParseLocation(locationStr) + if err != nil { + ctx.WithError(err).Fatal("Invalid location") + } + edits.Location = location + } + + act := util.GetAccount(ctx) + err = act.EditGateway(gatewayID, edits) + if err != nil { + ctx.WithError(err).Fatal("Failure editing gateway") + } + + ctx.WithField("Gateway ID", gatewayID).Info("Edited gateway") + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysEditCmd) + gatewaysEditCmd.Flags().String("frequency-plan", "", "The frequency plan to use on the gateway") + gatewaysEditCmd.Flags().String("location", "", "The location of the gateway") +} diff --git a/ttnctl/cmd/gateways_info.go b/ttnctl/cmd/gateways_info.go new file mode 100644 index 000000000..43a40773f --- /dev/null +++ b/ttnctl/cmd/gateways_info.go @@ -0,0 +1,64 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysInfoCmd = &cobra.Command{ + Use: "info [GatewayID]", + Short: "get info about a gateway", + Long: `ttnctl gateways info can be used to get information about a gateway`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + account := util.GetAccount(ctx) + + gateway, err := account.FindGateway(gatewayID) + if err != nil { + ctx.WithError(err).WithField("id", gatewayID).Fatal("Could not find gateway") + } + + ctx.Info("Found gateway") + + fmt.Println() + fmt.Printf("Gateway ID: %s\n", gateway.ID) + fmt.Printf("Activated: %v\n", gateway.Activated) + fmt.Printf("Frequency Plan: %s\n", gateway.FrequencyPlan) + locationAccess := "private" + if gateway.LocationPublic { + locationAccess = "public" + } + if gateway.Location != nil { + fmt.Printf("Location Info : (%f, %f) (%s) \n", gateway.Location.Latitude, gateway.Location.Longitude, locationAccess) + } + if gateway.StatusPublic { + fmt.Printf("Status Info: public (see ttnctl gateways status %s)\n", gatewayID) + } else { + fmt.Print("Status Info: private\n") + } + if gateway.Key != "" { + fmt.Printf("Access Key : %s\n", gateway.Key) + } + + fmt.Println() + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysInfoCmd) +} diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go new file mode 100644 index 000000000..b6b85b9e9 --- /dev/null +++ b/ttnctl/cmd/gateways_list.go @@ -0,0 +1,51 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +var gatewaysListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List your gateways", + Long: `ttnctl gateways list can be used to list the gateways you have access to`, + Example: `$ ttnctl gateways list + ID Activated Frequency Plan Coordinates +1 test true US (52.3740, 4.8896) +`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + gateways, err := account.ListGateways() + if err != nil { + ctx.WithError(err).Fatal("Could not list gateways") + } + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "ID", "Activated", "Frequency Plan", "Coordinates") + for i, gateway := range gateways { + var lat float64 + var lng float64 + if gateway.Location != nil { + lat = gateway.Location.Latitude + lng = gateway.Location.Longitude + } + table.AddRow(i+1, gateway.ID, gateway.Activated, gateway.FrequencyPlan, fmt.Sprintf("(%f, %f)", lat, lng)) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysListCmd) +} diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go new file mode 100644 index 000000000..7aad010d7 --- /dev/null +++ b/ttnctl/cmd/gateways_register.go @@ -0,0 +1,56 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysRegisterCmd = &cobra.Command{ + Use: "register [GatewayID] [FrequencyPlan] [Location]", + Short: "Register a gateway", + Long: `ttnctl gateways register can be used to register a gateway`, + Example: `$ ttnctl gateways register test US 52.37403,4.88968 + INFO Registered gateway Gateway ID=test +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 && len(args) != 3 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + frequencyPlan := args[1] + + var err error + var location *account.Location + if len(args) == 3 { + location, err = util.ParseLocation(args[2]) + if err != nil { + ctx.WithError(err).Fatal("Invalid location") + } + } + + act := util.GetAccount(ctx) + gateway, err := act.RegisterGateway(gatewayID, frequencyPlan, location) + if err != nil { + ctx.WithError(err).Fatal("Could not register gateway") + } + + util.ForceRefreshToken(ctx) + + ctx.WithField("Gateway ID", gateway.ID).Info("Registered Gateway") + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysRegisterCmd) +} diff --git a/ttnctl/cmd/gateways_status.go b/ttnctl/cmd/gateways_status.go new file mode 100644 index 000000000..44433f088 --- /dev/null +++ b/ttnctl/cmd/gateways_status.go @@ -0,0 +1,91 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/spf13/cobra" +) + +var gatewaysStatusCmd = &cobra.Command{ + Use: "status [gatewayID]", + Short: "Get status of a gateway", + Long: `ttnctl gateways status can be used to get status of gateways.`, + Example: `$ ttnctl gateways status test + INFO Discovering Router... + INFO Connecting with Router... + INFO Connected to Router + INFO Received status + + Last seen: 2016-09-20 08:25:27.94138808 +0200 CEST + Timestamp: 0 + Reported time: 2016-09-20 08:25:26 +0200 CEST + GPS coordinates: (52.372791 4.900300) + Rtt: not available + Rx: (in: 0; ok: 0) + Tx: (in: 0; ok: 0) +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gtwID := args[0] + if !api.ValidID(gtwID) { + ctx.Fatal("Invalid Gateway ID") + } + + conn, manager := util.GetRouterManager(ctx) + defer conn.Close() + + ctx = ctx.WithField("GatewayID", gtwID) + + resp, err := manager.GatewayStatus(util.GetContext(ctx), &router.GatewayStatusRequest{ + GatewayId: gtwID, + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get status of gateway.") + } + + ctx.Infof("Received status") + fmt.Println() + printKV("Last seen", time.Unix(0, resp.LastSeen)) + printKV("Timestamp", resp.Status.Timestamp) + if t := resp.Status.Time; t != 0 { + printKV("Reported time", time.Unix(0, t)) + } + printKV("Description", resp.Status.Description) + printKV("Platform", resp.Status.Platform) + printKV("Contact email", resp.Status.ContactEmail) + printKV("Region", resp.Status.Region) + printKV("IP Address", strings.Join(resp.Status.Ip, ", ")) + printKV("GPS coordinates", func() interface{} { + if gps := resp.Status.Gps; gps != nil && !(gps.Latitude == 0 && gps.Longitude == 0) { + return fmt.Sprintf("(%.6f %.6f)", gps.Latitude, gps.Longitude) + } + return "not available" + }()) + printKV("Rtt", func() interface{} { + if t := resp.Status.Rtt; t != 0 { + return time.Duration(t) + } + return "not available" + }()) + printKV("Rx", fmt.Sprintf("(in: %d; ok: %d)", resp.Status.RxIn, resp.Status.RxOk)) + printKV("Tx", fmt.Sprintf("(in: %d; ok: %d)", resp.Status.TxIn, resp.Status.TxOk)) + fmt.Println() + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysStatusCmd) +} diff --git a/ttnctl/cmd/gateways_token.go b/ttnctl/cmd/gateways_token.go new file mode 100644 index 000000000..4736fcd79 --- /dev/null +++ b/ttnctl/cmd/gateways_token.go @@ -0,0 +1,55 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysTokenCmd = &cobra.Command{ + Use: "token [GatewayID]", + Hidden: true, + Short: "Get the token for a gateway.", + Long: `gateways token gets a signed token for the gateway.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + ctx = ctx.WithField("id", gatewayID) + + account := util.GetAccount(ctx) + + token, err := account.GetGatewayToken(gatewayID) + if err != nil { + ctx.WithError(err).Fatal("Could not get gateway token") + } + if token.AccessToken == "" { + ctx.Fatal("Gateway token was empty") + } + + ctx.Info("Got gateway token") + + fmt.Println() + fmt.Println(token.AccessToken) + fmt.Println() + if !token.Expiry.IsZero() { + fmt.Printf("Expires %s\n", token.Expiry) + fmt.Println() + } + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysTokenCmd) +} diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go new file mode 100644 index 000000000..c7bb75ad8 --- /dev/null +++ b/ttnctl/cmd/join.go @@ -0,0 +1,138 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "time" + + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/otaa" + "github.com/apex/log" + "github.com/brocaar/lorawan" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var joinCmd = &cobra.Command{ + Hidden: true, + Use: "join [AppEUI] [DevEUI] [AppKey] [DevNonce]", + Short: "Simulate an join message to the network", + Long: `ttnctl join simulates an join message to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + cmd.UsageFunc()(cmd) + return + } + + appEUI, err := types.ParseAppEUI(args[0]) + if err != nil { + ctx.WithError(err).Fatal("Could not parse AppEUI") + } + + devEUI, err := types.ParseDevEUI(args[1]) + if err != nil { + ctx.WithError(err).Fatal("Could not parse DevEUI") + } + + appKey, err := types.ParseAppKey(args[2]) + if err != nil { + ctx.WithError(err).Fatal("Could not parse AppKey") + } + + devNonceSlice, err := types.ParseHEX(args[3], 2) + if err != nil { + ctx.WithError(err).Fatal("Could not parse DevNonce") + } + devNonce := [2]byte{devNonceSlice[0], devNonceSlice[1]} + + rtrConn, rtrClient := util.GetRouter(ctx) + defer rtrConn.Close() + + gatewayID := viper.GetString("gateway-id") + gatewayToken := viper.GetString("gateway-token") + + if gatewayID != "dev" { + account := util.GetAccount(ctx) + token, err := account.GetGatewayToken(gatewayID) + if err != nil { + ctx.WithError(err).Warn("Could not get gateway token") + ctx.Warn("Trying without token. Your message may not be processed by the router") + gatewayToken = "" + } else if token != nil && token.AccessToken != "" { + gatewayToken = token.AccessToken + } + } + + gtwClient := router.NewRouterClientForGateway(rtrClient, gatewayID, gatewayToken) + defer gtwClient.Close() + + downlinkStream := router.NewMonitoredDownlinkStream(gtwClient) + defer downlinkStream.Close() + time.Sleep(100 * time.Millisecond) + + joinReq := &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{MType: pb_lorawan.MType_JOIN_REQUEST, Major: pb_lorawan.Major_LORAWAN_R1}, + Payload: &pb_lorawan.Message_JoinRequestPayload{JoinRequestPayload: &pb_lorawan.JoinRequestPayload{ + AppEui: appEUI, + DevEui: devEUI, + DevNonce: types.DevNonce(devNonce), + }}} + joinPhy := joinReq.PHYPayload() + joinPhy.SetMIC(lorawan.AES128Key(appKey)) + bytes, _ := joinPhy.MarshalBinary() + + uplink := &router.UplinkMessage{ + Payload: bytes, + GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), + ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), + } + uplink.UnmarshalPayload() + + uplinkStream := router.NewMonitoredUplinkStream(gtwClient) + defer uplinkStream.Close() + + err = uplinkStream.Send(uplink) + if err != nil { + ctx.WithError(err).Fatal("Could not send uplink to Router") + } + + time.Sleep(100 * time.Millisecond) + + ctx.Info("Sent uplink to Router") + + select { + case downlinkMessage, ok := <-downlinkStream.Channel(): + if !ok { + ctx.Info("Did not receive downlink") + break + } + downlinkMessage.UnmarshalPayload() + resPhy := downlinkMessage.Message.GetLorawan().PHYPayload() + resPhy.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) + res := pb_lorawan.MessageFromPHYPayload(resPhy) + accept := res.GetJoinAcceptPayload() + + appSKey, nwkSKey, _ := otaa.CalculateSessionKeys(appKey, accept.AppNonce, accept.NetId, devNonce) + + ctx.WithFields(log.Fields{ + "DevAddr": accept.DevAddr, + "NwkSKey": nwkSKey, + "AppSKey": appSKey, + }).Info("Received JoinAccept") + case <-time.After(6 * time.Second): + ctx.Info("Did not receive downlink") + } + + }, +} + +func init() { + RootCmd.AddCommand(joinCmd) + + joinCmd.Flags().String("gateway-id", "", "The ID of the gateway that you are faking (you can only fake gateways that you own)") + viper.BindPFlag("gateway-id", joinCmd.Flags().Lookup("gateway-id")) +} diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go new file mode 100644 index 000000000..3eb70219e --- /dev/null +++ b/ttnctl/cmd/root.go @@ -0,0 +1,151 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + "strings" + + cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" + ttnlog "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/go-utils/log/grpc" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc/grpclog" +) + +var cfgFile string +var dataDir string +var debug bool + +var ctx log.Interface + +// RootCmd is the entrypoint for ttnctl +var RootCmd = &cobra.Command{ + Use: "ttnctl", + Short: "Control The Things Network from the command line", + Long: `ttnctl controls The Things Network from the command line.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var logLevel = log.InfoLevel + if viper.GetBool("debug") { + logLevel = log.DebugLevel + } + ctx = &log.Logger{ + Level: logLevel, + Handler: cliHandler.New(os.Stdout), + } + + if viper.GetBool("debug") { + util.PrintConfig(ctx, true) + } + + ttnlog.Set(apex.Wrap(ctx)) + grpclog.SetLogger(grpc.Wrap(ttnlog.Get())) + + }, +} + +// Execute runs on start +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +// init initializes the configuration and command line flags +func init() { + cobra.OnInitialize(initConfig) + + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yml)") + + RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") + viper.BindPFlag("data", RootCmd.PersistentFlags().Lookup("data")) + + RootCmd.PersistentFlags().String("discovery-address", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") + viper.BindPFlag("discovery-address", RootCmd.PersistentFlags().Lookup("discovery-address")) + + RootCmd.PersistentFlags().String("router-id", "ttn-router-eu", "The ID of the TTN Router as announced in the Discovery server") + viper.BindPFlag("router-id", RootCmd.PersistentFlags().Lookup("router-id")) + + RootCmd.PersistentFlags().String("handler-id", "ttn-handler-eu", "The ID of the TTN Handler as announced in the Discovery server") + viper.BindPFlag("handler-id", RootCmd.PersistentFlags().Lookup("handler-id")) + + RootCmd.PersistentFlags().String("mqtt-address", "eu.thethings.network:1883", "The address of the MQTT broker") + viper.BindPFlag("mqtt-address", RootCmd.PersistentFlags().Lookup("mqtt-address")) + + RootCmd.PersistentFlags().String("mqtt-username", "", "The username for the MQTT broker") + viper.BindPFlag("mqtt-username", RootCmd.PersistentFlags().Lookup("mqtt-username")) + + RootCmd.PersistentFlags().String("mqtt-password", "", "The password for the MQTT broker") + viper.BindPFlag("mqtt-password", RootCmd.PersistentFlags().Lookup("mqtt-password")) + + RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") + viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) + + viper.SetDefault("gateway-id", "dev") + viper.SetDefault("gateway-token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImdhdGV3YXkiLCJpYXQiOjE0NzY0Mzk0Mzh9.kEOiLe9j4qRElZOt_bAXmZlva1nV6duIL0MDVa3bx2SEWC3qredaBWXWq4FmV4PKeI_zndovQtOoValH0B_6MW6vXuWL1wYzV6foTH5gQdxmn-iuQ1AmAIYbZeyHl9a-NPqDgkXLwKmo2iB1hUi9wV6HXfIOalguDtGJbmMfJ2tommsgmuNCXd-2zqhStSy8ArpROFXPm7voGDTcgm4hfchr7zhn-Er76R-eJa3RZ1Seo9BsiWrQ0N3VDSuh7ycCakZtkaLD4OTutAemcbzbrNJSOCvvZr8Asn-RmMkjKUdTN4Bgn3qlacIQ9iZikPLT8XyjFkj-8xjs3KAobWg40A") +} + +func printKV(key, t interface{}) { + var val string + switch t := t.(type) { + case []byte: + val = fmt.Sprintf("%X", t) + default: + val = fmt.Sprintf("%v", t) + } + + if val != "" { + fmt.Printf("%20s: %s\n", key, val) + } +} + +func confirm(prompt string) bool { + fmt.Println(prompt) + fmt.Print("> ") + var answer string + fmt.Scanf("%s", &answer) + switch strings.ToLower(answer) { + case "yes", "y": + return true + case "no", "n", "": + return false + default: + fmt.Println("When you make up your mind, please answer yes or no.") + return confirm(prompt) + } +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile == "" { + cfgFile = util.GetConfigFile() + } + + if dataDir == "" { + dataDir = util.GetDataDir() + } + + viper.SetConfigType("yaml") + viper.SetConfigFile(cfgFile) + + viper.SetEnvPrefix("ttnctl") // set environment prefix + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) + viper.AutomaticEnv() + + viper.BindEnv("debug") + + // If a config file is found, read it in. + if _, err := os.Stat(cfgFile); err == nil { + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Error when reading config file:", err) + } + } +} diff --git a/ttnctl/cmd/selfupdate.go b/ttnctl/cmd/selfupdate.go new file mode 100644 index 000000000..c49d46cdf --- /dev/null +++ b/ttnctl/cmd/selfupdate.go @@ -0,0 +1,24 @@ +// +build !homebrew + +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/spf13/cobra" +) + +var selfUpdateCmd = &cobra.Command{ + Use: "selfupdate", + Short: "Update ttnctl to the latest version", + Long: `ttnctl selfupdate updates the current ttnctl to the latest version`, + Run: func(cmd *cobra.Command, args []string) { + version.Selfupdate(ctx, "ttnctl") + }, +} + +func init() { + RootCmd.AddCommand(selfUpdateCmd) +} diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go new file mode 100644 index 000000000..005e84572 --- /dev/null +++ b/ttnctl/cmd/subscribe.go @@ -0,0 +1,72 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var subscribeCmd = &cobra.Command{ + Use: "subscribe", + Short: "Subscribe to events for this application", + Long: `ttnctl subscribe can be used to subscribe to events for this application.`, + Run: func(cmd *cobra.Command, args []string) { + util.GetAccount(ctx) + + client := util.GetMQTT(ctx) + defer client.Disconnect() + + token := client.SubscribeActivations(func(client mqtt.Client, appID string, devID string, req types.Activation) { + ctx.Info("Activation") + printKV("AppID", appID) + printKV("DevID", devID) + printKV("AppEUI", req.AppEUI) + printKV("DevEUI", req.DevEUI) + printKV("DevAddr", req.DevAddr) + fmt.Println() + }) + token.Wait() + if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe to activations") + } + ctx.Info("Subscribed to activations") + + token = client.SubscribeUplink(func(client mqtt.Client, appID string, devID string, req types.UplinkMessage) { + ctx.Info("Uplink Message") + printKV("AppID", appID) + printKV("DevID", devID) + printKV("Port", req.FPort) + printKV("FCnt", req.FCnt) + printKV("Payload (hex)", req.PayloadRaw) + if len(req.PayloadFields) > 0 { + ctx.Info("Decoded fields") + for k, v := range req.PayloadFields { + printKV(k, v) + } + } + fmt.Println() + }) + token.Wait() + if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe to uplink") + } + ctx.Info("Subscribed to uplink") + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + }, +} + +func init() { + RootCmd.AddCommand(subscribeCmd) +} diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go new file mode 100644 index 000000000..bcbdc008c --- /dev/null +++ b/ttnctl/cmd/uplink.go @@ -0,0 +1,139 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "strconv" + "time" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var uplinkCmd = &cobra.Command{ + Hidden: true, + Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload]", + Short: "Simulate an uplink message to the network", + Long: `ttnctl uplink simulates an uplink message to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 5 { + cmd.UsageFunc()(cmd) + return + } + + devAddr, err := types.ParseDevAddr(args[0]) + if err != nil { + ctx.WithError(err).Fatal("Invalid DevAddr") + } + + nwkSKey, err := types.ParseNwkSKey(args[1]) + if err != nil { + ctx.WithError(err).Fatal("Invalid NwkSKey") + } + + appSKey, err := types.ParseAppSKey(args[2]) + if err != nil { + ctx.WithError(err).Fatal("Invalid AppSKey") + } + + fCnt, err := strconv.Atoi(args[3]) + if err != nil { + ctx.WithError(err).Fatal("Invalid FCnt") + } + + payload, err := types.ParseHEX(args[4], len(args[4])/2) + if err != nil { + ctx.WithError(err).Fatal("Invalid Payload") + } + + withDownlink, _ := cmd.Flags().GetBool("downlink") + + confirmed, _ := cmd.Flags().GetBool("confirmed") + if confirmed { + withDownlink = true + } + + rtrConn, rtrClient := util.GetRouter(ctx) + defer rtrConn.Close() + + gatewayID := viper.GetString("gateway-id") + gatewayToken := viper.GetString("gateway-token") + + if gatewayID != "dev" { + account := util.GetAccount(ctx) + token, err := account.GetGatewayToken(gatewayID) + if err != nil { + ctx.WithError(err).Warn("Could not get gateway token") + ctx.Warn("Trying without token. Your message may not be processed by the router") + gatewayToken = "" + } else if token != nil && token.AccessToken != "" { + gatewayToken = token.AccessToken + } + } + + gtwClient := router.NewRouterClientForGateway(rtrClient, gatewayID, gatewayToken) + defer gtwClient.Close() + + var downlinkStream router.DownlinkStream + if withDownlink { + downlinkStream = router.NewMonitoredDownlinkStream(gtwClient) + defer downlinkStream.Close() + time.Sleep(100 * time.Millisecond) + } + + m := &util.Message{} + m.SetDevice(devAddr, nwkSKey, appSKey) + m.SetMessage(confirmed, fCnt, payload) + bytes := m.Bytes() + + uplinkStream := router.NewMonitoredUplinkStream(gtwClient) + defer uplinkStream.Close() + + err = uplinkStream.Send(&router.UplinkMessage{ + Payload: bytes, + GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), + ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not send uplink to Router") + } + + time.Sleep(100 * time.Millisecond) + + ctx.Info("Sent uplink to Router") + + if downlinkStream != nil { + select { + case downlinkMessage, ok := <-downlinkStream.Channel(): + if !ok { + ctx.Info("Did not receive downlink") + break + } + if err := m.Unmarshal(downlinkMessage.Payload); err != nil { + ctx.WithError(err).Fatal("Could not unmarshal downlink") + } + ctx.WithFields(log.Fields{ + "Payload": m.Payload, + "FCnt": m.FCnt, + "FPort": m.FPort, + }).Info("Received Downlink") + case <-time.After(2 * time.Second): + ctx.Info("Did not receive downlink") + } + } + }, +} + +func init() { + RootCmd.AddCommand(uplinkCmd) + uplinkCmd.Flags().Bool("downlink", false, "Also start downlink (unstable)") + uplinkCmd.Flags().Bool("confirmed", false, "Use confirmed uplink (this also sets --downlink)") + + uplinkCmd.Flags().String("gateway-id", "", "The ID of the gateway that you are faking (you can only fake gateways that you own)") + viper.BindPFlag("gateway-id", uplinkCmd.Flags().Lookup("gateway-id")) +} diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go new file mode 100644 index 000000000..d8af553ed --- /dev/null +++ b/ttnctl/cmd/user.go @@ -0,0 +1,67 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var userCmd = &cobra.Command{ + Use: "user", + Aliases: []string{"users"}, + Short: "Show the current user", + Long: `ttnctl user shows the current logged on user's profile`, + Example: `$ ttnctl user + INFO Found user profile: + + Username: yourname + Name: Your Name + Email: your@email.org + + INFO Login credentials valid until Sep 20 09:04:12 +`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + profile, err := account.Profile() + if err != nil { + ctx.WithError(err).Fatal("Could not get user profile") + } + + ctx.Info("Found user profile:") + fmt.Println() + printKV("Username", profile.Username) + printKV("Name", profile.Name) + printKV("Email", profile.Email) + fmt.Println() + + token, err := util.GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get access token") + } + + claims, err := claims.FromTokenWithoutValidation(token.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not parse token") + } + + if claims.ExpiresAt != 0 { + expires := time.Unix(claims.ExpiresAt, 0) + if expires.After(time.Now()) { + ctx.Infof("Login credentials valid until %s", expires.Format(time.Stamp)) + } else { + ctx.Warnf("Login credentials expired %s", expires.Format(time.Stamp)) + } + } + + }, +} + +func init() { + RootCmd.AddCommand(userCmd) +} diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go new file mode 100644 index 000000000..5f9d169ef --- /dev/null +++ b/ttnctl/cmd/user_login.go @@ -0,0 +1,50 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/go-account-lib/auth" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var userLoginCmd = &cobra.Command{ + Use: "login [access code]", + Short: "Log in with your TTN account", + Long: `ttnctl user login allows you to log in to your TTN account.`, + Example: `First get an access code from your TTN profile by going to +https://account.thethingsnetwork.org and clicking "ttnctl access code". + +$ ttnctl user login [paste the access code you requested above] + INFO Successfully logged in as yourname (your@email.org) +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.UsageFunc()(cmd) + return + } + + code := args[0] + token, err := util.Login(ctx, code) + if err != nil { + ctx.WithError(err).Fatal("Login failed") + } + + acc := account.New(viper.GetString("auth-server")).WithHeader("User-Agent", util.GetUserAgent()) + acc.WithAuth(auth.AccessToken(token.AccessToken)) + profile, err := acc.Profile() + if err != nil { + ctx.WithError(err).Fatal("Could not get user profile") + } + ctx.Info(fmt.Sprintf("Successfully logged in as %s (%s)", profile.Username, profile.Email)) + }, +} + +func init() { + userCmd.AddCommand(userLoginCmd) +} diff --git a/ttnctl/cmd/user_logout.go b/ttnctl/cmd/user_logout.go new file mode 100644 index 000000000..a31d5ac61 --- /dev/null +++ b/ttnctl/cmd/user_logout.go @@ -0,0 +1,31 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "os" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var userLogoutCmd = &cobra.Command{ + Use: "logout", + Short: "Logout the current user", + Long: `ttnctl user logout logs out the current user`, + Run: func(cmd *cobra.Command, args []string) { + err := util.Logout() + if err != nil { + if os.IsNotExist(err) { + ctx.Info("You were not logged in") + return + } + ctx.WithError(err).Fatal("Could not delete credentials") + } + }, +} + +func init() { + userCmd.AddCommand(userLogoutCmd) +} diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go new file mode 100644 index 000000000..834731486 --- /dev/null +++ b/ttnctl/cmd/user_register.go @@ -0,0 +1,49 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/howeyc/gopass" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var userRegisterCmd = &cobra.Command{ + Use: "register [username] [e-mail]", + Short: "Register", + Long: `ttnctl user register allows you to register a new user in the account server`, + Example: `$ ttnctl user register yourname your@email.org +Password: + INFO Registered user + WARN You might have to verify your email before you can login +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + cmd.UsageFunc()(cmd) + return + } + username := args[0] + email := args[1] + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal(err.Error()) + } + acc := account.New(viper.GetString("auth-server")).WithHeader("User-Agent", util.GetUserAgent()) + err = acc.RegisterUser(username, email, string(password)) + if err != nil { + ctx.WithError(err).Fatal("Could not register user") + } + ctx.Info("Registered user") + ctx.Warn("You might have to verify your email before you can login") + }, +} + +func init() { + userCmd.AddCommand(userRegisterCmd) +} diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go new file mode 100644 index 000000000..d97bbf2e0 --- /dev/null +++ b/ttnctl/cmd/version.go @@ -0,0 +1,66 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const unknown = "unknown" + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get build and version information", + Long: `ttnctl version gets the build and version information of ttnctl`, + Run: func(cmd *cobra.Command, args []string) { + gitBranch := viper.GetString("gitBranch") + gitCommit := viper.GetString("gitCommit") + buildDate := viper.GetString("buildDate") + + ctx.WithFields(log.Fields{ + "Version": viper.GetString("version"), + "Branch": gitBranch, + "Commit": gitCommit, + "BuildDate": buildDate, + }).Info("Got build information") + + if gitBranch == unknown || gitCommit == unknown || buildDate == unknown { + ctx.Warn("This is not an official ttnctl build") + ctx.Warn("If you're building ttnctl from source, you should use the Makefile") + return + } + + latest, err := version.GetLatestInfo() + if err != nil { + ctx.WithError(err).Warn("Could not get latest version information") + return + } + + if latest.Commit == gitCommit { + ctx.Info("This is an up-to-date ttnctl build") + return + } + + if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { + ctx.Warn("This is not the latest official ttnctl build") + if buildDate.Before(latest.Date) { + ctx.Warnf("The newest ttnctl build is %s newer.", latest.Date.Sub(buildDate)) + } else { + ctx.Warn("Your ttnctl build is newer than the latest official one, which is fine if you're a developer") + } + } else { + ctx.Warn("This ttnctl contains invalid build information") + } + + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/ttnctl/main.go b/ttnctl/main.go new file mode 100644 index 000000000..4e359d272 --- /dev/null +++ b/ttnctl/main.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/cmd" + "github.com/spf13/viper" +) + +var ( + version = "2.x.x" + gitBranch = "unknown" + gitCommit = "unknown" + buildDate = "unknown" +) + +func main() { + viper.Set("version", version) + viper.Set("gitBranch", gitBranch) + viper.Set("gitCommit", gitCommit) + viper.Set("buildDate", buildDate) + cmd.Execute() +} diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go new file mode 100644 index 000000000..56155e730 --- /dev/null +++ b/ttnctl/util/account.go @@ -0,0 +1,197 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "encoding/json" + "fmt" + "os" + "path" + "runtime" + "strings" + "time" + + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/go-account-lib/cache" + "github.com/TheThingsNetwork/go-account-lib/oauth" + "github.com/TheThingsNetwork/go-account-lib/tokens" + "github.com/apex/log" + "github.com/spf13/viper" + "golang.org/x/oauth2" +) + +var tokenSource oauth2.TokenSource + +func tokenFile() string { + return getServerKey() + ".token" +} +func derivedTokenFile() string { + return getServerKey() + ".derived-tokens" +} + +func tokenName() string { + return viper.GetString("auth-server") +} + +func getServerKey() string { + replacer := strings.NewReplacer("https:", "", "http:", "", "/", "", ".", "") + return replacer.Replace(viper.GetString("auth-server")) +} + +func tokenFilename(name string) string { + return tokenFile() +} + +// GetCache get's the cache that will store our tokens +func GetTokenCache() cache.Cache { + return cache.FileCacheWithNameFn(GetDataDir(), tokenFilename) +} + +func GetUserAgent() string { + return fmt.Sprintf( + "ttnctl/%s-%s (%s-%s) (%s)", + viper.GetString("version"), + viper.GetString("gitCommit"), + runtime.GOOS, + runtime.GOARCH, + GetID(), + ) +} + +// getOAuth gets the OAuth client +func getOAuth() *oauth.Config { + return oauth.OAuth(viper.GetString("auth-server"), &oauth.Client{ + ID: "ttnctl", + Secret: "ttnctl", + ExtraHeaders: map[string]string{ + "User-Agent": GetUserAgent(), + }, + }) +} + +func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { + return getOAuth().TokenSource(token) +} + +func getStoredToken(ctx log.Interface) *oauth2.Token { + tokenCache := GetTokenCache() + data, err := tokenCache.Get(tokenName()) + if err != nil { + ctx.WithError(err).Fatal("Could not read stored token") + } + if data == nil { + ctx.Fatal("No account information found. Please login with ttnctl user login [access code]") + } + + token := &oauth2.Token{} + err = json.Unmarshal(data, token) + if err != nil { + ctx.Fatal("Account information invalid. Please login with ttnctl user login [access code]") + } + return token +} + +func saveToken(ctx log.Interface, token *oauth2.Token) { + data, err := json.Marshal(token) + if err != nil { + ctx.WithError(err).Fatal("Could not save access token") + } + err = GetTokenCache().Set(tokenName(), data) + if err != nil { + ctx.WithError(err).Fatal("Could not save access token") + } +} + +type ttnctlTokenSource struct { + ctx log.Interface + source oauth2.TokenSource +} + +func (s *ttnctlTokenSource) Token() (*oauth2.Token, error) { + token, err := s.source.Token() + if err != nil { + return nil, err + } + saveToken(s.ctx, token) + return token, nil +} + +// ForceRefreshToken forces a refresh of the access token +func ForceRefreshToken(ctx log.Interface) { + tokenSource := GetTokenSource(ctx).(*ttnctlTokenSource) + token, err := tokenSource.Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get access token") + } + token.Expiry = time.Now().Add(-1 * time.Second) + tokenSource.source = oauth2.ReuseTokenSource(token, getAccountServerTokenSource(token)) + tokenSource.Token() +} + +// GetTokenSource builds a new oauth2.TokenSource that uses the ttnctl config to store the token +func GetTokenSource(ctx log.Interface) oauth2.TokenSource { + if tokenSource != nil { + return tokenSource + } + token := getStoredToken(ctx) + source := oauth2.ReuseTokenSource(token, getAccountServerTokenSource(token)) + tokenSource = &ttnctlTokenSource{ctx, source} + return tokenSource +} + +func GetTokenManager(accessToken string) tokens.Manager { + server := viper.GetString("auth-server") + return tokens.HTTPManager(server, accessToken, tokens.FileStore(path.Join(GetDataDir(), derivedTokenFile()))) +} + +// GetAccount gets a new Account server client for ttnctl +func GetAccount(ctx log.Interface) *account.Account { + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get access token") + } + + server := viper.GetString("auth-server") + manager := GetTokenManager(token.AccessToken) + + return account.NewWithManager(server, token.AccessToken, manager).WithHeader("User-Agent", GetUserAgent()) +} + +// Login does a login to the Account server with the given username and password +func Login(ctx log.Interface, code string) (*oauth2.Token, error) { + config := getOAuth() + token, err := config.Exchange(code) + if err != nil { + return nil, err + } + saveToken(ctx, token) + return token, nil +} + +func TokenForScope(ctx log.Interface, scope string) string { + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get token") + } + + restricted, err := GetTokenManager(token.AccessToken).TokenForScope(scope) + if err != nil { + ctx.WithError(err).Fatal("Could not get correct rights") + } + + return restricted +} + +func Logout() error { + err := os.Remove(path.Join(GetDataDir(), tokenFile())) + if err != nil { + return err + } + + err = os.Remove(path.Join(GetDataDir(), derivedTokenFile())) + if err != nil && !os.IsNotExist(err) { + return err + } + return nil +} diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go new file mode 100644 index 000000000..91d3a53e1 --- /dev/null +++ b/ttnctl/util/config.go @@ -0,0 +1,198 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + "io/ioutil" + "os" + "path" + + yaml "gopkg.in/yaml.v2" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" + "github.com/spf13/viper" +) + +const ( + appFilename = "app" + euiKey = "eui" + idKey = "id" +) + +// GetConfigFile returns the location of the configuration file. +// It checks the following (in this order): +// the --config flag +// $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set) +// $HOME/.ttnctl.yml +func GetConfigFile() string { + flag := viper.GetString("config") + + xdg := os.Getenv("XDG_CONFIG_HOME") + if xdg != "" { + xdg = path.Join(xdg, "ttnctl", "config.yml") + } + + home := os.Getenv("HOME") + homeyml := "" + homeyaml := "" + + if home != "" { + homeyml = path.Join(home, ".ttnctl.yml") + homeyaml = path.Join(home, ".ttnctl.yaml") + } + + try_files := []string{ + flag, + xdg, + homeyml, + homeyaml, + } + + // find a file that exists, and use that + for _, file := range try_files { + if file != "" { + if _, err := os.Stat(file); err == nil { + return file + } + } + } + + // no file found, set up correct fallback + if os.Getenv("XDG_CONFIG_HOME") != "" { + return xdg + } else { + return homeyml + } +} + +// GetDataDir returns the location of the data directory used for +// sotring data. +// It checks the following (in this order): +// the --data flag +// $XDG_DATA_HOME/ttnctl (if $XDG_DATA_HOME is set) +// $XDG_CACHE_HOME/ttnctl (if $XDG_CACHE_HOME is set) +// $HOME/.ttnctl +func GetDataDir() string { + file := viper.GetString("data") + if file != "" { + return file + } + + xdg := os.Getenv("XDG_DATA_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl") + } + + xdg = os.Getenv("XDG_CACHE_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl") + } + + return path.Join(os.Getenv("HOME"), ".ttnctl") +} + +func readData(file string) map[string]interface{} { + fullpath := path.Join(GetDataDir(), file) + + c := make(map[string]interface{}) + + // Read config file + data, err := ioutil.ReadFile(fullpath) + if err != nil { + return c + } + + err = yaml.Unmarshal(data, &c) + if err != nil { + return c + } + + return c +} + +func writeData(file string, data map[string]interface{}) error { + fullpath := path.Join(GetDataDir(), file) + + // Generate yaml contents + d, err := yaml.Marshal(&data) + if err != nil { + return fmt.Errorf("Could not generate configiguration file contents: %s", err.Error()) + } + + // Write to file + err = ioutil.WriteFile(fullpath, d, 0644) + if err != nil { + return fmt.Errorf("Could not write configuration file: %s", err.Error()) + } + + return nil +} + +func setData(file, key string, data interface{}) error { + config := readData(file) + config[key] = data + return writeData(file, config) +} + +// GetAppEUI returns the AppEUI that must be set in the command options or config +func GetAppEUI(ctx log.Interface) types.AppEUI { + appEUIString := viper.GetString("app-eui") + if appEUIString == "" { + appData := readData(appFilename) + eui, ok := appData[euiKey].(string) + if !ok { + ctx.Fatal("Invalid AppEUI in config file") + } + appEUIString = eui + } + + if appEUIString == "" { + ctx.Fatal("Missing AppEUI. You should select an application to use with \"ttnctl applications select\"") + } + + eui, err := types.ParseAppEUI(appEUIString) + if err != nil { + ctx.WithError(err).Fatal("Invalid AppEUI") + } + return eui +} + +// SetApp stores the app EUI preference +func SetAppEUI(ctx log.Interface, appEUI types.AppEUI) { + err := setData(appFilename, euiKey, appEUI.String()) + if err != nil { + ctx.WithError(err).Fatal("Could not save app EUI") + } +} + +// GetAppID returns the AppID that must be set in the command options or config +func GetAppID(ctx log.Interface) string { + appID := viper.GetString("app-id") + if appID == "" { + appData := readData(appFilename) + id, ok := appData[idKey].(string) + if !ok { + ctx.Fatal("Invalid appID in config file.") + } + appID = id + } + + if appID == "" { + ctx.Fatal("Missing AppID. You should select an application to use with \"ttnctl applications select\"") + } + return appID +} + +// SetApp stores the app ID and app EUI preferences +func SetApp(ctx log.Interface, appID string, appEUI types.AppEUI) { + config := readData(appFilename) + config[idKey] = appID + config[euiKey] = appEUI.String() + err := writeData(appFilename, config) + if err != nil { + ctx.WithError(err).Fatal("Could not save app preference") + } +} diff --git a/ttnctl/util/context.go b/ttnctl/util/context.go new file mode 100644 index 000000000..99bdfcbda --- /dev/null +++ b/ttnctl/util/context.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + "os" + "os/user" + + "github.com/apex/log" + "github.com/spf13/viper" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc/metadata" +) + +// GetID retrns the ID of this ttnctl +func GetID() string { + id := "ttnctl" + if user, err := user.Current(); err == nil { + id += "-" + user.Username + } + if hostname, err := os.Hostname(); err == nil { + id += "@" + hostname + } + return id +} + +// GetContext returns a new context +func GetContext(ctx log.Interface, extraPairs ...string) context.Context { + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get token") + } + md := metadata.Pairs( + "id", GetID(), + "service-name", "ttnctl", + "service-version", fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), + "token", token.AccessToken, + ) + return metadata.NewContext(context.Background(), md) +} diff --git a/ttnctl/util/discovery.go b/ttnctl/util/discovery.go new file mode 100644 index 000000000..9caf83f3f --- /dev/null +++ b/ttnctl/util/discovery.go @@ -0,0 +1,30 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "io/ioutil" + "path" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/apex/log" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// GetDiscovery gets the Discovery client for ttnctl +func GetDiscovery(ctx log.Interface) (*grpc.ClientConn, discovery.DiscoveryClient) { + path := path.Join(GetDataDir(), "/ca.cert") + cert, err := ioutil.ReadFile(path) + if err == nil && !api.RootCAs.AppendCertsFromPEM(cert) { + ctx.Warnf("Could not add root certificates from %s", path) + } + + conn, err := api.Dial(viper.GetString("discovery-address")) + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Discovery server") + } + return conn, discovery.NewDiscoveryClient(conn) +} diff --git a/ttnctl/util/handler.go b/ttnctl/util/handler.go new file mode 100644 index 000000000..eaf3725a1 --- /dev/null +++ b/ttnctl/util/handler.go @@ -0,0 +1,41 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/go-account-lib/scope" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// GetHandlerManager gets a new HandlerManager for ttnctl +func GetHandlerManager(ctx log.Interface, appID string) (*grpc.ClientConn, *handler.ManagerClient) { + ctx.WithField("Handler", viper.GetString("handler-id")).Info("Discovering Handler...") + dscConn, client := GetDiscovery(ctx) + defer dscConn.Close() + handlerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ + ServiceName: "handler", + Id: viper.GetString("handler-id"), + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not find Handler") + } + + token := TokenForScope(ctx, scope.App(appID)) + + ctx.WithField("Handler", handlerAnnouncement.NetAddress).Info("Connecting with Handler...") + hdlConn, err := handlerAnnouncement.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Handler") + } + managerClient, err := handler.NewManagerClient(hdlConn, token) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler Manager") + } + return hdlConn, managerClient +} diff --git a/ttnctl/util/location.go b/ttnctl/util/location.go new file mode 100644 index 000000000..3c1bf4dbc --- /dev/null +++ b/ttnctl/util/location.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "errors" + "strconv" + "strings" + + "github.com/TheThingsNetwork/go-account-lib/account" +) + +func ParseLocation(locationStr string) (*account.Location, error) { + parts := strings.Split(locationStr, ",") + if len(parts) != 2 { + return nil, errors.New("Location should be on the , format") + } + + lat, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + return nil, err + } + + if lat < -90 || lat > 90 { + return nil, errors.New("Latitude should be in range [90, 90]") + } + + lng, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + return nil, err + } + + if lng < -180 || lng > 180 { + return nil, errors.New("Longitude should be in range [-180, 180]") + } + + return &account.Location{ + Latitude: lat, + Longitude: lng, + }, nil +} diff --git a/ttnctl/util/location_test.go b/ttnctl/util/location_test.go new file mode 100644 index 000000000..37913bca2 --- /dev/null +++ b/ttnctl/util/location_test.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "testing" + + "github.com/TheThingsNetwork/go-account-lib/account" + . "github.com/smartystreets/assertions" +) + +func TestParseLocation(t *testing.T) { + a := New(t) + + str := "10.5,33.4" + loc := &account.Location{ + Latitude: float64(10.5), + Longitude: float64(33.4), + } + parsed, err := ParseLocation(str) + a.So(err, ShouldBeNil) + a.So(loc.Latitude, ShouldEqual, parsed.Latitude) + a.So(loc.Longitude, ShouldEqual, parsed.Longitude) +} diff --git a/ttnctl/util/lorawan.go b/ttnctl/util/lorawan.go new file mode 100644 index 000000000..e15e42dbd --- /dev/null +++ b/ttnctl/util/lorawan.go @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/utils/errors" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" +) + +// Message struct is used to construct an uplink message +type Message struct { + devAddr types.DevAddr + nwkSKey types.NwkSKey + appSKey types.AppSKey + FCnt int + FPort int + confirmed bool + Payload []byte +} + +// SetDevice with the LoRaWAN options +func (m *Message) SetDevice(devAddr types.DevAddr, nwkSKey types.NwkSKey, appSKey types.AppSKey) { + m.devAddr = devAddr + m.nwkSKey = nwkSKey + m.appSKey = appSKey +} + +// SetMessage with some options +func (m *Message) SetMessage(confirmed bool, fCnt int, payload []byte) { + m.confirmed = confirmed + m.FCnt = fCnt + m.Payload = payload +} + +// Bytes returns the bytes +func (m *Message) Bytes() []byte { + if m.FPort == 0 { + m.FPort = 1 + } + macPayload := &lorawan.MACPayload{} + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr(m.devAddr), + FCnt: uint32(m.FCnt), + } + macPayload.FPort = pointer.Uint8(uint8(m.FPort)) + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: m.Payload}} + phyPayload := &lorawan.PHYPayload{} + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + if m.confirmed { + phyPayload.MHDR.MType = lorawan.ConfirmedDataUp + } + phyPayload.MACPayload = macPayload + phyPayload.EncryptFRMPayload(lorawan.AES128Key(m.appSKey)) + phyPayload.SetMIC(lorawan.AES128Key(m.nwkSKey)) + uplinkBytes, _ := phyPayload.MarshalBinary() + return uplinkBytes +} + +// Unmarshal a byte slice into a Message +func (m *Message) Unmarshal(bytes []byte) error { + payload := &lorawan.PHYPayload{} + payload.UnmarshalBinary(bytes) + if micOK, _ := payload.ValidateMIC(lorawan.AES128Key(m.nwkSKey)); !micOK { + return errors.New("Invalid MIC") + } + macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.New("No MACPayload") + } + m.FCnt = int(macPayload.FHDR.FCnt) + m.FPort = -1 + if macPayload.FPort != nil { + m.FPort = int(*macPayload.FPort) + } + m.Payload = []byte{} + if len(macPayload.FRMPayload) > 0 { + payload.DecryptFRMPayload(lorawan.AES128Key(m.appSKey)) + m.Payload = macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes + } + return nil +} diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go new file mode 100644 index 000000000..17555ce3e --- /dev/null +++ b/ttnctl/util/mqtt.go @@ -0,0 +1,102 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "errors" + "fmt" + "strings" + + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" + "github.com/gosuri/uitable" + "github.com/spf13/viper" +) + +// GetMQTT connects a new MQTT clients with the specified credentials +func GetMQTT(ctx log.Interface) mqtt.Client { + username, password, err := getMQTTCredentials(ctx) + if err != nil { + ctx.WithError(err).Fatal("Failed to get MQTT credentials") + } + + mqttProto := "tcp" + if strings.HasSuffix(viper.GetString("mqtt-address"), ":8883") { + mqttProto = "ssl" + ctx.Fatal("TLS connections are not yet supported by ttnctl") + } + broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-address")) + client := mqtt.NewClient(apex.Wrap(ctx), "ttnctl", username, password, broker) + + ctx.WithFields(log.Fields{ + "MQTT Broker": broker, + "Username": username, + }).Info("Connecting to MQTT...") + + if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") + } + + return client +} + +func getMQTTCredentials(ctx log.Interface) (username string, password string, err error) { + username = viper.GetString("mqtt-username") + password = viper.GetString("mqtt-password") + if username != "" { + return + } + + // Do not use authentication on local MQTT + if strings.HasPrefix(viper.GetString("mqtt-address"), "localhost") { + return + } + + return getAppMQTTCredentials(ctx) +} + +func getAppMQTTCredentials(ctx log.Interface) (string, string, error) { + appID := GetAppID(ctx) + + account := GetAccount(ctx) + app, err := account.FindApplication(appID) + if err != nil { + return "", "", err + } + + var keyIdx int + switch len(app.AccessKeys) { + case 0: + return "", "", errors.New("Can not connect to MQTT. Your application does not have any access keys.") + case 1: + default: + ctx.Infof("Found %d access keys for your application:", len(app.AccessKeys)) + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Name", "Rights") + for i, key := range app.AccessKeys { + rightStrings := make([]string, 0, len(key.Rights)) + for _, i := range key.Rights { + rightStrings = append(rightStrings, string(i)) + } + table.AddRow(i+1, key.Name, strings.Join(rightStrings, ",")) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + fmt.Println("Which one do you want to use?") + fmt.Printf("Enter the number (1 - %d) > ", len(app.AccessKeys)) + fmt.Scanf("%d", &keyIdx) + keyIdx-- + } + + if keyIdx < 0 || keyIdx >= len(app.AccessKeys) { + return "", "", errors.New("Invalid choice for access key") + } + return appID, app.AccessKeys[keyIdx].Key, nil +} diff --git a/ttnctl/util/print_config.go b/ttnctl/util/print_config.go new file mode 100644 index 000000000..26e1be341 --- /dev/null +++ b/ttnctl/util/print_config.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + + "github.com/apex/log" + "github.com/spf13/viper" +) + +func PrintConfig(ctx log.Interface, debug bool) { + prt := ctx.Infof + if debug { + prt = ctx.Debugf + } + + prt("Using config:") + fmt.Println() + printKV("config file", viper.ConfigFileUsed()) + printKV("data dir", viper.GetString("data")) + fmt.Println() + + for key, val := range viper.AllSettings() { + switch key { + case "builddate": + fallthrough + case "gitcommit": + fallthrough + case "gitbranch": + fallthrough + case "version": + continue + default: + printKV(key, val) + } + } + fmt.Println() +} + +func printKV(key, val interface{}) { + fmt.Printf("%20s: %v\n", key, val) +} diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go new file mode 100644 index 000000000..ca76a0766 --- /dev/null +++ b/ttnctl/util/router.go @@ -0,0 +1,53 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// GetRouter starts a connection with the router +func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { + ctx.Info("Discovering Router...") + dscConn, client := GetDiscovery(ctx) + defer dscConn.Close() + routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ + ServiceName: "router", + Id: viper.GetString("router-id"), + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") + } + ctx.Info("Connecting with Router...") + rtrConn, err := routerAnnouncement.Dial() + ctx.Info("Connected to Router") + rtrClient := router.NewRouterClient(rtrConn) + return rtrConn, rtrClient +} + +// GetRouterManager starts a management connection with the router +func GetRouterManager(ctx log.Interface) (*grpc.ClientConn, router.RouterManagerClient) { + ctx.Info("Discovering Router...") + dscConn, client := GetDiscovery(ctx) + defer dscConn.Close() + routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ + ServiceName: "router", + Id: viper.GetString("router-id"), + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") + } + ctx.Info("Connecting with Router...") + rtrConn, err := routerAnnouncement.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Router") + } + ctx.Info("Connected to Router") + return rtrConn, router.NewRouterManagerClient(rtrConn) +} diff --git a/ttnctl/util/uplink_metadata.go b/ttnctl/util/uplink_metadata.go new file mode 100644 index 000000000..824b549b1 --- /dev/null +++ b/ttnctl/util/uplink_metadata.go @@ -0,0 +1,30 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" +) + +// GetProtocolMetadata returns protocol metadata for the given datarate +func GetProtocolMetadata(dataRate string) *protocol.RxMetadata { + return &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &lorawan.Metadata{ + CodingRate: "4/5", + DataRate: dataRate, + Modulation: lorawan.Modulation_LORA, + }}} +} + +// GetGatewayMetadata returns gateway metadata for the given gateway ID and frequency +func GetGatewayMetadata(id string, freq uint64) *gateway.RxMetadata { + return &gateway.RxMetadata{ + GatewayId: id, + Timestamp: 0, + Frequency: freq, + Rssi: -25.0, + Snr: 5.0, + } +} diff --git a/utils/backoff/backoff.go b/utils/backoff/backoff.go new file mode 100644 index 000000000..5fe896743 --- /dev/null +++ b/utils/backoff/backoff.go @@ -0,0 +1,60 @@ +// Package backoff is a slightly changed version of the backoff algorithm that comes with gRPC +// See: vendor/google.golang.org/grpc/LICENSE for the license +package backoff + +import ( + "math/rand" + "time" +) + +// DefaultConfig is the default backoff configuration +var ( + DefaultConfig = Config{ + MaxDelay: 120 * time.Second, + BaseDelay: 1.0 * time.Second, + Factor: 1.6, + Jitter: 0.2, + } +) + +// Config defines the parameters for backoff +type Config struct { + // MaxDelay is the upper bound of backoff delay. + MaxDelay time.Duration + + // BaseDelay is the amount of time to wait before retrying after the first failure. + BaseDelay time.Duration + + // factor is applied to the backoff after each retry. + Factor float64 + + // jitter provides a range to randomize backoff delays. + Jitter float64 +} + +// Backoff returns the delay for the current amount of retries +func (bc Config) Backoff(retries int) time.Duration { + if retries == 0 { + return bc.BaseDelay + } + backoff, max := float64(bc.BaseDelay), float64(bc.MaxDelay) + for backoff < max && retries > 0 { + backoff *= bc.Factor + retries-- + } + if backoff > max { + backoff = max + } + // Randomize backoff delays so that if a cluster of requests start at + // the same time, they won't operate in lockstep. + backoff *= 1 + bc.Jitter*(rand.Float64()*2-1) + if backoff < 0 { + return 0 + } + return time.Duration(backoff) +} + +// Backoff returns the delay for the current amount of retries +func Backoff(retries int) time.Duration { + return DefaultConfig.Backoff(retries) +} diff --git a/utils/docs/generate.go b/utils/docs/generate.go new file mode 100644 index 000000000..30817c15f --- /dev/null +++ b/utils/docs/generate.go @@ -0,0 +1,79 @@ +package docs + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + + "github.com/spf13/cobra" +) + +type byName []*cobra.Command + +func (s byName) Len() int { return len(s) } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPath() } + +// Generate prints API docs for a command +func Generate(cmd *cobra.Command) string { + buf := new(bytes.Buffer) + + cmds := genCmdList(cmd) + sort.Sort(byName(cmds)) + for _, cmd := range cmds { + if len(strings.Split(cmd.CommandPath(), " ")) == 1 { + fmt.Fprint(buf, "**Options**\n\n") + printOptions(buf, cmd) + fmt.Fprintln(buf) + continue + } + + depth := len(strings.Split(cmd.CommandPath(), " ")) + + fmt.Fprint(buf, header(depth, cmd.CommandPath())) + + fmt.Fprint(buf, cmd.Long, "\n\n") + + if cmd.Runnable() { + fmt.Fprint(buf, "**Usage:** ", "`", cmd.UseLine(), "`", "\n\n") + } + + if cmd.HasLocalFlags() || cmd.HasPersistentFlags() { + fmt.Fprint(buf, "**Options**\n\n") + printOptions(buf, cmd) + } + + if cmd.Example != "" { + fmt.Fprint(buf, "**Example**\n\n") + fmt.Fprint(buf, "```", "\n", cmd.Example, "```", "\n\n") + } + } + + return buf.String() +} + +func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { + cmds = append(cmds, cmd) + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsHelpCommand() { + continue + } + cmds = append(cmds, genCmdList(c)...) + } + return cmds +} + +func header(depth int, header string) string { + return fmt.Sprint(strings.Repeat("#", depth), " ", header, "\n\n") +} + +func printOptions(w io.Writer, cmd *cobra.Command) { + fmt.Fprintln(w, "```") + flags := cmd.NonInheritedFlags() + flags.SetOutput(w) + flags.PrintDefaults() + fmt.Fprintln(w, "```") + fmt.Fprintln(w, "") +} diff --git a/utils/elasticsearch/handler/elasticsearch.go b/utils/elasticsearch/handler/elasticsearch.go new file mode 100644 index 000000000..1fadf0a7e --- /dev/null +++ b/utils/elasticsearch/handler/elasticsearch.go @@ -0,0 +1,106 @@ +// Package elasticsearch implements an Elasticsearch batch handler. +package elasticsearch + +import ( + "fmt" + "io" + stdlog "log" + "os" + "sync" + "time" + + "github.com/apex/log" + "github.com/tj/go-elastic/batch" +) + +// Elasticsearch interface. +type Elasticsearch interface { + Bulk(io.Reader) error +} + +// Config for handler. +type Config struct { + BufferSize int // BufferSize is the number of logs to buffer before flush (default: 100) + Client Elasticsearch // Client for ES + Prefix string // Prefix for the index - The index will be prefix-YY-MM-DD (default: logs) + Hostname string // Hostname to add to the logs +} + +// defaults applies defaults to the config. +func (c *Config) defaults() { + if c.BufferSize == 0 { + c.BufferSize = 100 + } + if c.Prefix == "" { + c.Prefix = "logs" + } + if c.Hostname == "" { + c.Hostname, _ = os.Hostname() + } +} + +// Handler implementation. +type Handler struct { + *Config + + mu sync.Mutex + batch *batch.Batch +} + +// indexName returns the index for the configured +func (h *Handler) indexName() string { + return fmt.Sprintf("%s-%s", h.Config.Prefix, time.Now().Format("2006.01.02")) +} + +// New handler with BufferSize +func New(config *Config) *Handler { + config.defaults() + return &Handler{ + Config: config, + } +} + +// HandleLog implements log.Handler. +func (h *Handler) HandleLog(e *log.Entry) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.batch == nil { + h.batch = &batch.Batch{ + Elastic: h.Client, + Index: h.indexName(), + Type: "log", + } + } + + // Map fields + for k, v := range e.Fields { + switch t := v.(type) { + case []byte: // Convert []byte to HEX-string + e.Fields[k] = fmt.Sprintf("%X", t) + } + } + + e.Timestamp = e.Timestamp.UTC() + + if h.Hostname != "" { + e.Fields["hostname"] = h.Hostname + } + + h.batch.Add(e) + + if h.batch.Size() >= h.BufferSize { + go h.flush(h.batch) + h.batch = nil + } + + return nil +} + +// flush the given `batch` asynchronously. +func (h *Handler) flush(batch *batch.Batch) { + size := batch.Size() + if err := batch.Flush(); err != nil { + stdlog.Printf("log/elastic: failed to flush %d logs: %s", size, err) + } +} diff --git a/utils/errors/errors.go b/utils/errors/errors.go new file mode 100644 index 000000000..7e02ed2cb --- /dev/null +++ b/utils/errors/errors.go @@ -0,0 +1,197 @@ +package errors + +import ( + "fmt" + "io" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + errs "github.com/pkg/errors" + "golang.org/x/net/context" +) + +type ErrType string + +// These constants represent error types +const ( + AlreadyExists ErrType = "already exists" + Internal ErrType = "internal" + InvalidArgument ErrType = "invalid argument" + NotFound ErrType = "not found" + OutOfRange ErrType = "out of range" + PermissionDenied ErrType = "permission denied" + Unknown ErrType = "unknown" +) + +// GetErrType returns the type of err +func GetErrType(err error) ErrType { + switch errs.Cause(err).(type) { + case *ErrAlreadyExists: + return AlreadyExists + case *ErrInternal: + return Internal + case *ErrInvalidArgument: + return InvalidArgument + case *ErrNotFound: + return NotFound + case *ErrPermissionDenied: + return PermissionDenied + } + return Unknown +} + +// BuildGRPCError returns the error with a GRPC code +func BuildGRPCError(err error) error { + if err == nil { + return nil + } + code := grpc.Code(err) + if code != codes.Unknown { + return err // it already is a gRPC error + } + switch errs.Cause(err).(type) { + case *ErrAlreadyExists: + code = codes.AlreadyExists + case *ErrInternal: + code = codes.Internal + case *ErrInvalidArgument: + code = codes.InvalidArgument + case *ErrNotFound: + code = codes.NotFound + case *ErrPermissionDenied: + code = codes.PermissionDenied + } + switch err { + case context.Canceled: + code = codes.Canceled + case io.EOF: + code = codes.OutOfRange + } + return grpc.Errorf(code, err.Error()) +} + +// FromGRPCError creates a regular error with the same type as the gRPC error +func FromGRPCError(err error) error { + if err == nil { + return nil + } + code := grpc.Code(err) + desc := grpc.ErrorDesc(err) + switch code { + case codes.AlreadyExists: + return NewErrAlreadyExists(strings.TrimSuffix(desc, " already exists")) + case codes.Internal: + return NewErrInternal(strings.TrimPrefix(desc, "Internal error: ")) + case codes.InvalidArgument: + if split := strings.Split(desc, " not valid: "); len(split) == 2 { + return NewErrInvalidArgument(split[0], split[1]) + } + return NewErrInvalidArgument("Argument", desc) + case codes.NotFound: + return NewErrNotFound(strings.TrimSuffix(desc, " not found")) + case codes.PermissionDenied: + return NewErrPermissionDenied(strings.TrimPrefix(desc, "permission denied: ")) + case codes.Unknown: // This also includes all non-gRPC errors + if desc == "EOF" { + return io.EOF + } + return errs.New(desc) + } + return NewErrInternal(fmt.Sprintf("[%s] %s", code, desc)) +} + +// NewErrAlreadyExists returns a new ErrAlreadyExists for the given entitiy +func NewErrAlreadyExists(entity string) error { + return &ErrAlreadyExists{entity: entity} +} + +// ErrAlreadyExists indicates that an entity already exists +type ErrAlreadyExists struct { + entity string +} + +// Error implements the error interface +func (err ErrAlreadyExists) Error() string { + return fmt.Sprintf("%s already exists", err.entity) +} + +// NewErrInternal returns a new ErrInternal with the given message +func NewErrInternal(message string) error { + return &ErrInternal{message: message} +} + +// ErrInternal indicates that an internal error occured +type ErrInternal struct { + message string +} + +// Error implements the error interface +func (err ErrInternal) Error() string { + return fmt.Sprintf("Internal error: %s", err.message) +} + +// NewErrInvalidArgument returns a new ErrInvalidArgument for the given entitiy +func NewErrInvalidArgument(argument string, reason string) error { + return &ErrInvalidArgument{argument: argument, reason: reason} +} + +// ErrInvalidArgument indicates that an argument was invalid +type ErrInvalidArgument struct { + argument string + reason string +} + +// Error implements the error interface +func (err ErrInvalidArgument) Error() string { + return fmt.Sprintf("%s not valid: %s", err.argument, err.reason) +} + +// NewErrNotFound returns a new ErrNotFound for the given entitiy +func NewErrNotFound(entity string) error { + return &ErrNotFound{entity: entity} +} + +// ErrNotFound indicates that an entity was not found +type ErrNotFound struct { + entity string +} + +// Error implements the error interface +func (err ErrNotFound) Error() string { + return fmt.Sprintf("%s not found", err.entity) +} + +// NewErrPermissionDenied returns a new ErrPermissionDenied with the given reason +func NewErrPermissionDenied(reason string) error { + return &ErrPermissionDenied{reason: reason} +} + +// ErrPermissionDenied indicates that permissions were not sufficient +type ErrPermissionDenied struct { + reason string +} + +// Error implements the error interface +func (err ErrPermissionDenied) Error() string { + return fmt.Sprintf("permission denied: %s", err.reason) +} + +// Wrapf returns an error annotating err with the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + return errs.Wrapf(err, format, args...) +} + +// Wrap returns an error annotating err with message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + return errs.Wrap(err, message) +} + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return errs.New(message) +} diff --git a/utils/fcnt/fcnt.go b/utils/fcnt/fcnt.go new file mode 100644 index 000000000..a2529fab9 --- /dev/null +++ b/utils/fcnt/fcnt.go @@ -0,0 +1,17 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package fcnt + +const maxUint16 = (1 << 16) + +// GetFull calculates the full 32-bit frame counter +func GetFull(full uint32, lsb uint16) uint32 { + if int(lsb)-int(full) > 0 { + return uint32(lsb) + } + if uint16(full%maxUint16) <= lsb { + return uint32(lsb) + (full/maxUint16)*maxUint16 + } + return uint32(lsb) + ((full/maxUint16)+1)*maxUint16 +} diff --git a/utils/fcnt/fcnt_test.go b/utils/fcnt/fcnt_test.go new file mode 100644 index 000000000..33091b34e --- /dev/null +++ b/utils/fcnt/fcnt_test.go @@ -0,0 +1,30 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package fcnt + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestGetFullFCnt(t *testing.T) { + a := New(t) + + a.So(GetFull(0, 0), ShouldEqual, 0) + a.So(GetFull(0, 1), ShouldEqual, 1) + a.So(GetFull(0, 65535), ShouldEqual, 65535) + + a.So(GetFull(2000, 0), ShouldEqual, 65536) + a.So(GetFull(2000, 1), ShouldEqual, 65537) + + a.So(GetFull(65536, 0), ShouldEqual, 65536) + a.So(GetFull(65536, 1), ShouldEqual, 65537) + + a.So(GetFull(524287, 0), ShouldEqual, 524288) + a.So(GetFull(524287, 1), ShouldEqual, 524289) + + a.So(GetFull(524288, 0), ShouldEqual, 524288) + a.So(GetFull(524288, 1), ShouldEqual, 524289) +} diff --git a/utils/otaa/session_keys.go b/utils/otaa/session_keys.go new file mode 100644 index 000000000..2838fcadb --- /dev/null +++ b/utils/otaa/session_keys.go @@ -0,0 +1,37 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package otaa + +import ( + "crypto/aes" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// CalculateSessionKeys calculates the AppSKey and NwkSKey +// All arguments are MSB-first +func CalculateSessionKeys(appKey types.AppKey, appNonce [3]byte, netID [3]byte, devNonce [2]byte) (appSKey types.AppSKey, nwkSKey types.NwkSKey, err error) { + + buf := make([]byte, 16) + copy(buf[1:4], reverse(appNonce[:])) + copy(buf[4:7], reverse(netID[:])) + copy(buf[7:9], reverse(devNonce[:])) + + block, _ := aes.NewCipher(appKey[:]) + + buf[0] = 0x1 + block.Encrypt(nwkSKey[:], buf) + buf[0] = 0x2 + block.Encrypt(appSKey[:], buf) + + return +} + +// reverse is used to convert between MSB-first and LSB-first +func reverse(in []byte) (out []byte) { + for i := len(in) - 1; i >= 0; i-- { + out = append(out, in[i]) + } + return +} diff --git a/utils/otaa/session_keys_test.go b/utils/otaa/session_keys_test.go new file mode 100644 index 000000000..e3251fb56 --- /dev/null +++ b/utils/otaa/session_keys_test.go @@ -0,0 +1,31 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package otaa + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestCalculateSessionKeys(t *testing.T) { + a := New(t) + + // MSB first + appKey := types.AppKey{0xBE, 0xC4, 0x99, 0xC6, 0x9E, 0x9C, 0x93, 0x9E, 0x41, 0x3B, 0x66, 0x39, 0x61, 0x63, 0x6C, 0x61} + devNonce := [2]byte{0x73, 0x69} + netID := [3]byte{0x00, 0x00, 0x00} + appNonce := [3]byte{0xAE, 0x3B, 0x1C} + + appSKey, nwkSKey, err := CalculateSessionKeys(appKey, appNonce, netID, devNonce) + a.So(err, ShouldBeNil) + + // MSB first + expectedNwkSKey := types.NwkSKey{0x33, 0xD5, 0xF3, 0x74, 0x29, 0xDA, 0x60, 0xF0, 0xA5, 0x7A, 0xB5, 0xAA, 0x06, 0x95, 0xE4, 0x98} + expectedAppSKey := types.AppSKey{0x71, 0x4F, 0xA5, 0x53, 0x03, 0x07, 0xD6, 0x03, 0xE8, 0x7C, 0x78, 0x65, 0xDF, 0x86, 0x2A, 0x85} + + a.So(appSKey, ShouldResemble, expectedAppSKey) + a.So(nwkSKey, ShouldResemble, expectedNwkSKey) +} diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go new file mode 100644 index 000000000..2817fd594 --- /dev/null +++ b/utils/pointer/pointer.go @@ -0,0 +1,214 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package pointer provides helper method to quickly define pointer from basic go types +package pointer + +import ( + "fmt" + "reflect" + "strings" + "time" +) + +// String creates a pointer to a string from a string value +func String(v string) *string { + p := new(string) + *p = v + return p +} + +// Int creates a pointer to an int from an int value +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// Int8 creates a pointer to an int8 from an int8 value +func Int8(v int8) *int8 { + p := new(int8) + *p = v + return p +} + +// Int16 creates a pointer to an int16 from an int16 value +func Int16(v int16) *int16 { + p := new(int16) + *p = v + return p +} + +// Int32 creates a pointer to an int32 from an int32 value +func Int32(v int32) *int32 { + p := new(int32) + *p = v + return p +} + +// Int64 creates a pointer to an int64 from an int64 value +func Int64(v int64) *int64 { + p := new(int64) + *p = v + return p +} + +// Uint creates a pointer to an unsigned int from an unsigned int value +func Uint(v uint) *uint { + p := new(uint) + *p = v + return p +} + +// Uint8 creates a pointer to an unsigned int from an unsigned int8 value +func Uint8(v uint8) *uint8 { + p := new(uint8) + *p = v + return p +} + +// Uint16 creates a pointer to an unsigned int from an unsigned int16 value +func Uint16(v uint16) *uint16 { + p := new(uint16) + *p = v + return p +} + +// Uint32 creates a pointer to an unsigned int from an unsigned int32 value +func Uint32(v uint32) *uint32 { + p := new(uint32) + *p = v + return p +} + +// Uint64 creates a pointer to an unsigned int from an unsigned int64 value +func Uint64(v uint64) *uint64 { + p := new(uint64) + *p = v + return p +} + +// Float32 creates a pointer to a float32 from a float32 value +func Float32(v float32) *float32 { + p := new(float32) + *p = v + return p +} + +// Float64 creates a pointer to a float64 from a float64 value +func Float64(v float64) *float64 { + p := new(float64) + *p = v + return p +} + +// Bool creates a pointer to a boolean from a boolean value +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} + +// Time creates a pointer to a time.Time from a time.Time value +func Time(v time.Time) *time.Time { + p := new(time.Time) + *p = v + return p +} + +// DumpStruct prints the content of a struct of pointers +func DumpPStruct(s interface{}, multiline bool) string { + v := reflect.ValueOf(s) + + if v.Kind() != reflect.Struct { + return "Not a struct" + } + + nl := "," + str := fmt.Sprintf("%s{", v.Type().Name()) + if multiline { + nl = "\n\t" + str += nl + } + + for k := 0; k < v.NumField(); k += 1 { + name := v.Type().Field(k).Name + if name[0] == strings.ToLower(name)[0] { // Unexported field + continue + } + i := v.Field(k).Interface() + key := fmt.Sprintf("%v", v.Type().Field(k).Name) + + switch t := i.(type) { + case *bool: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int8: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int16: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int32: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int64: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint8: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint16: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint32: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint64: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *string: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *float32: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *float64: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *time.Time: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + default: + str += fmt.Sprintf("%s:unknown%s", key, nl) + } + } + + if multiline { + str += "\n}" + } else { + str += "}" + } + return str +} diff --git a/utils/protoc-gen-ttndoc/README.md b/utils/protoc-gen-ttndoc/README.md new file mode 100644 index 000000000..408faa757 --- /dev/null +++ b/utils/protoc-gen-ttndoc/README.md @@ -0,0 +1,15 @@ +# protoc-gen-ttndoc + +Generate docs for TTN API + +## Installation + +``` +go install +``` + +## Usage + +``` +protoc -I/usr/local/include -I$GOPATH/src -I$GOPATH/src/github.com/TheThingsNetwork -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --ttndoc_out=logtostderr=true,.handler.ApplicationManager=all:. $GOPATH/src/github.com/TheThingsNetwork/ttn/api/handler/handler.proto +``` diff --git a/utils/protoc-gen-ttndoc/build_tree.go b/utils/protoc-gen-ttndoc/build_tree.go new file mode 100644 index 000000000..a03711a3d --- /dev/null +++ b/utils/protoc-gen-ttndoc/build_tree.go @@ -0,0 +1,184 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strconv" + "strings" + + protobuf "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + gateway "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" +) + +func buildTree(files []*descriptor.FileDescriptorProto) *tree { + tree := &tree{ + services: make(map[string]*service), + methods: make(map[string]*method), + messages: make(map[string]*message), + fields: make(map[string]*field), + enums: make(map[string]*enum), + } + for _, file := range files { + fillTreeWithFile(tree, file) + } + return tree +} + +func fillTreeWithFile(tree *tree, file *descriptor.FileDescriptorProto) { + key := fmt.Sprintf(".%s", file.GetPackage()) + locs := make(map[string]*descriptor.SourceCodeInfo_Location) + for _, loc := range file.GetSourceCodeInfo().GetLocation() { + if loc.LeadingComments == nil { + continue + } + var p []string + for _, n := range loc.Path { + p = append(p, strconv.Itoa(int(n))) + } + locs[strings.Join(p, ",")] = loc + } + + // Messages + for idx, proto := range file.GetMessageType() { + fillTreeWithMessage(tree, key, proto, fmt.Sprintf("4,%d", idx), locs) + } + + // Enums + for idx, proto := range file.GetEnumType() { + fillTreeWithEnum(tree, key, proto, fmt.Sprintf("5,%d", idx), locs) + } + + // Services + for idx, proto := range file.GetService() { + fillTreeWithService(tree, key, proto, fmt.Sprintf("6,%d", idx), locs) + } +} + +func fillTreeWithService(tree *tree, key string, proto *descriptor.ServiceDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *service { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + tree.services[key] = &service{key: key, comment: getComment(loc, locs), ServiceDescriptorProto: proto} + + // Methods + for idx, proto := range proto.GetMethod() { + method := fillTreeWithMethod(tree, key, proto, fmt.Sprintf("%s,2,%d", loc, idx), locs) + tree.services[key].methods = append(tree.services[key].methods, method) + } + + return tree.services[key] +} + +func fillTreeWithMethod(tree *tree, key string, proto *descriptor.MethodDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *method { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + tree.methods[key] = &method{key: key, comment: getComment(loc, locs), MethodDescriptorProto: proto} + if input, ok := tree.messages[proto.GetInputType()]; ok { + tree.methods[key].input = input + } + if proto.GetClientStreaming() { + tree.methods[key].inputStream = true + } + if output, ok := tree.messages[proto.GetOutputType()]; ok { + tree.methods[key].output = output + } + if proto.GetServerStreaming() { + tree.methods[key].outputStream = true + } + if proto.Options != nil && protobuf.HasExtension(proto.Options, gateway.E_Http) { + ext, err := protobuf.GetExtension(proto.Options, gateway.E_Http) + if err == nil { + if opts, ok := ext.(*gateway.HttpRule); ok { + if endpoint := newEndpoint(opts); endpoint != nil { + tree.methods[key].endpoints = append(tree.methods[key].endpoints, endpoint) + } + for _, opts := range opts.AdditionalBindings { + if endpoint := newEndpoint(opts); endpoint != nil { + tree.methods[key].endpoints = append(tree.methods[key].endpoints, endpoint) + } + } + } + } + } + return tree.methods[key] +} + +func fillTreeWithMessage(tree *tree, key string, proto *descriptor.DescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *message { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + tree.messages[key] = &message{key: key, comment: getComment(loc, locs), DescriptorProto: proto} + + // Oneofs + for idx, proto := range proto.GetOneofDecl() { + tree.messages[key].oneofs = append(tree.messages[key].oneofs, &oneof{ + index: int32(idx), + OneofDescriptorProto: proto, + }) + } + + // Fields + for idx, proto := range proto.GetField() { + field := fillTreeWithField(tree, key, proto, fmt.Sprintf("%s,2,%d", loc, idx), locs) + tree.messages[key].fields = append(tree.messages[key].fields, field) + } + + // Nested + for idx, proto := range proto.GetNestedType() { + message := fillTreeWithMessage(tree, key, proto, fmt.Sprintf("%s,3,%d", loc, idx), locs) + tree.messages[key].nested = append(tree.messages[key].nested, message) + } + + // Enums + for idx, proto := range proto.GetEnumType() { + fillTreeWithEnum(tree, key, proto, fmt.Sprintf("%s,4,%d", loc, idx), locs) + } + + return tree.messages[key] +} + +func fillTreeWithField(tree *tree, parent string, proto *descriptor.FieldDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *field { + key := fmt.Sprintf("%s.%s", parent, proto.GetName()) + tree.fields[key] = &field{key: key, comment: getComment(loc, locs), FieldDescriptorProto: proto} + if proto.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED { + tree.fields[key].repeated = true + } + if proto.OneofIndex != nil { + if parent, ok := tree.messages[parent]; ok { + for _, oneof := range parent.oneofs { + if oneof.index == proto.GetOneofIndex() { + oneof.fields = append(oneof.fields, tree.fields[key]) + tree.fields[key].isOneOf = true + } + } + } + } + return tree.fields[key] +} + +func fillTreeWithEnum(tree *tree, key string, proto *descriptor.EnumDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *enum { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + + tree.enums[key] = &enum{key: key, comment: getComment(loc, locs), EnumDescriptorProto: proto} + + // Values + for idx, proto := range proto.GetValue() { + tree.enums[key].values = append(tree.enums[key].values, &enumValue{ + getComment(fmt.Sprintf("%s,2,%d", loc, idx), locs), + proto, + }) + } + + return tree.enums[key] +} + +func getComment(loc string, locs map[string]*descriptor.SourceCodeInfo_Location) (comment string) { + if loc, ok := locs[loc]; ok { + var lines []string + for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") { + line = strings.TrimPrefix(line, " ") + line = strings.Replace(line, "```", "", -1) + lines = append(lines, line) + } + return strings.Join(lines, "\n") + } + return "" +} diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go new file mode 100644 index 000000000..be5ca236a --- /dev/null +++ b/utils/protoc-gen-ttndoc/json.go @@ -0,0 +1,99 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "encoding/json" + "strings" +) + +var exampleValues = map[string]interface{}{ + ".discovery.*.id": "ttn-handler-eu", + ".discovery.*.service_name": "handler", + ".discovery.Announcement.api_address": "http://eu.thethings.network:8084", + ".discovery.Announcement.certificate": "-----BEGIN CERTIFICATE-----\n...", + ".discovery.Announcement.net_address": "eu.thethings.network:1904", + ".discovery.Announcement.public_key": "-----BEGIN PUBLIC KEY-----\n...", + ".discovery.Announcement.public": true, + ".discovery.Announcement.service_version": "2.0.0-abcdef...", + ".discovery.Metadata.dev_addr_prefix": "AAAAAAA=", + ".discovery.Metadata.app_id": "some-app-id", + ".handler.*.app_id": "some-app-id", + ".handler.*.dev_id": "some-dev-id", + ".handler.*.fields": `{"light":100}`, + ".handler.*.payload": "ZA==", + ".handler.*.port": 1, + ".handler.Application.converter": "function Converter(decoded, port) {...", + ".handler.Application.decoder": "function Decoder(bytes, port) {...", + ".handler.Application.encoder": "Encoder(object, port) {...", + ".handler.Application.validator": "Validator(converted, port) {...", + ".handler.DryDownlinkMessage.payload": "", + ".handler.LogEntry.fields": `["TTN",123]`, + ".handler.LogEntry.function": "decoder", + ".lorawan.Device.activation_constraints": "local", + ".lorawan.Device.app_eui": "0102030405060708", + ".lorawan.Device.app_id": "some-app-id", + ".lorawan.Device.app_key": "01020304050607080102030405060708", + ".lorawan.Device.app_s_key": "01020304050607080102030405060708", + ".lorawan.Device.dev_addr": "01020304", + ".lorawan.Device.dev_eui": "0102030405060708", + ".lorawan.Device.dev_id": "some-dev-id", + ".lorawan.Device.nwk_s_key": "01020304050607080102030405060708", + ".lorawan.Device.uses32_bit_f_cnt": true, +} + +func (m *message) MapExample(tree *tree) map[string]interface{} { + example := make(map[string]interface{}) + for _, field := range m.fields { + typ := strings.ToLower(strings.TrimPrefix(field.GetType().String(), "TYPE_")) + var val interface{} + + if exampleValue, ok := exampleValues[field.key]; ok { + val = exampleValue + } else if exampleValue, ok := exampleValues[field.key[:strings.Index(field.key[1:], ".")+1]+".*."+field.GetName()]; ok { + val = exampleValue + } else { + switch typ { + case "message": + if message, ok := tree.messages[field.GetTypeName()]; ok { + val = message.MapExample(tree) + } + case "enum": + if enums, ok := tree.enums[field.GetTypeName()]; ok { + val = enums.MapExample(tree) + } + case "string": + val = "" + case "bool": + val = false + case "bytes": + val = "" + case "int64", "int32", "uint64", "uint32", "sint64", "sint32", "fixed64", "fixed32", "sfixed32", "sfixed64": + val = 0 + case "double", "float": + val = 0.0 + default: + } + } + if field.repeated { + example[field.GetName()] = []interface{}{val} + } else { + example[field.GetName()] = val + } + } + return example +} + +func (m *enum) MapExample(tree *tree) string { + if len(m.values) == 0 { + return "" + } + return m.values[len(m.values)-1].GetName() +} + +func (m *message) JSONExample(tree *tree) string { + example := m.MapExample(tree) + exampleBytes, _ := json.MarshalIndent(example, "", " ") + return string(exampleBytes) +} diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go new file mode 100644 index 000000000..066b53df6 --- /dev/null +++ b/utils/protoc-gen-ttndoc/main.go @@ -0,0 +1,293 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "path" + "sort" + "strings" + + "regexp" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/generator" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +func debug(msgs ...string) { + if logtostderr { + s := strings.Join(msgs, " ") + fmt.Fprintln(os.Stderr, "protoc-gen-ttndoc: ", s) + } +} + +func failWithError(err error, msgs ...string) { + s := strings.Join(msgs, " ") + ":" + err.Error() + log.Print("protoc-gen-ttndoc: error:", s) + os.Exit(1) +} + +func fail(msgs ...string) { + s := strings.Join(msgs, " ") + log.Print("protoc-gen-ttndoc: fail:", s) + os.Exit(1) +} + +var logtostderr bool + +// Read from standard input +func main() { + g := generator.New() + + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + failWithError(err, "reading input") + } + + if err := proto.Unmarshal(data, g.Request); err != nil { + failWithError(err, "parsing input proto") + } + + if len(g.Request.FileToGenerate) == 0 { + fail("no files to generate") + } + + g.CommandLineParameters(g.Request.GetParameter()) + + if val, ok := g.Param["logtostderr"]; ok && val == "true" { + logtostderr = true + } + + tree := buildTree(g.Request.GetProtoFile()) + + packageLocations := make(map[string]string) + for _, file := range g.Request.GetProtoFile() { + loc := file.GetName()[:strings.LastIndex(file.GetName(), "/")] + packageLocations[file.GetPackage()] = loc + } + + selectedServices := make(map[string]string) + for k, v := range g.Param { + switch { + case k == "logtostderr": + if v == "true" { + logtostderr = true + } + case strings.HasPrefix(k, "."): + selectedServices[k] = v + } + } + + for serviceKey := range selectedServices { + service, ok := tree.services[serviceKey] + if !ok { + fail("Service", serviceKey, "unknown") + } + + usedMessages := make(map[string]*message) + usedEnums := make(map[string]*enum) + + content := new(bytes.Buffer) + + fmt.Fprintf(content, "# %s API Reference\n\n", service.GetName()) + if service.comment != "" { + fmt.Fprintf(content, "%s\n\n", service.comment) + } + + if len(service.methods) > 0 { + fmt.Fprint(content, "## Methods\n\n") + + for _, method := range service.methods { + fmt.Fprintf(content, "### `%s`\n\n", method.GetName()) + if method.comment != "" { + fmt.Fprintf(content, "%s\n\n", method.comment) + } + if method.inputStream { + fmt.Fprintf(content, "- Client stream of [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) + } else { + fmt.Fprintf(content, "- Request: [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) + } + useMessage(tree, method.input, usedMessages, usedEnums) + if method.outputStream { + fmt.Fprintf(content, "- Server stream of [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) + } else { + fmt.Fprintf(content, "- Response: [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) + } + useMessage(tree, method.output, usedMessages, usedEnums) + + fmt.Fprintln(content) + + if len(method.endpoints) != 0 { + if len(method.endpoints) == 1 { + fmt.Fprint(content, "#### HTTP Endpoint\n\n") + } else { + fmt.Fprint(content, "#### HTTP Endpoints\n\n") + } + for _, endpoint := range method.endpoints { + fmt.Fprintf(content, "- `%s` `%s`", endpoint.method, endpoint.url) + var params []string + for _, match := range regexp.MustCompile(`\{([a-z_]+)\}`).FindAllStringSubmatch(endpoint.url, -1) { + if match != nil && len(match) == 2 && match[1] != "" { + params = append(params, fmt.Sprintf("`%s`", match[1])) + } + } + if len(params) > 0 { + fmt.Fprintf(content, "(%s can be left out of the request body)", strings.Join(params, ", ")) + } + fmt.Fprintln(content) + } + fmt.Fprintln(content) + + fmt.Fprint(content, "#### JSON Request Format\n\n") + fmt.Fprintf(content, "```json\n%s\n```\n\n", method.input.JSONExample(tree)) + + fmt.Fprint(content, "#### JSON Response Format\n\n") + fmt.Fprintf(content, "```json\n%s\n```\n\n", method.output.JSONExample(tree)) + } + } + } + + if len(usedMessages) > 0 { + fmt.Fprint(content, "## Messages\n\n") + + var messageKeys []string + for key := range usedMessages { + messageKeys = append(messageKeys, key) + } + sort.Strings(messageKeys) + + for _, messageKey := range messageKeys { + message := usedMessages[messageKey] + fmt.Fprintf(content, "### `%s`\n\n", message.key) + if strings.HasPrefix(messageKey, ".google") { + fmt.Fprintf(content, "%s\n\n", strings.SplitAfter(message.comment, ".")[0]) + } else if message.comment != "" { + fmt.Fprintf(content, "%s\n\n", message.comment) + } + if len(message.fields) > 0 { + fmt.Fprintln(content, "| Field Name | Type | Description |") + fmt.Fprintln(content, "| ---------- | ---- | ----------- |") + for idx, field := range message.fields { + if field.isOneOf { + if idx == 0 || !message.fields[idx-1].isOneOf || message.fields[idx-1].GetOneofIndex() != field.GetOneofIndex() { + oneof := message.GetOneof(field.GetOneofIndex()) + if len(oneof.fields) > 1 { + fmt.Fprintf(content, "| **%s** | **oneof %d** | one of the following %d |\n", oneof.GetName(), len(oneof.fields), len(oneof.fields)) + } + } + } else { + if idx > 0 && message.fields[idx-1].isOneOf { + oneof := message.GetOneof(message.fields[idx-1].GetOneofIndex()) + if len(oneof.fields) > 1 { + fmt.Fprintf(content, "| **%s** | **end oneof %d** | |\n", oneof.GetName(), len(oneof.fields)) + } + } + } + + var fieldType string + if field.repeated { + fieldType += "_repeated_ " + } + typ := strings.ToLower(strings.TrimPrefix(field.GetType().String(), "TYPE_")) + switch typ { + case "message": + friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] + fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) + case "enum": + // TODO(htdvisser): test this + if enum, ok := tree.enums[field.GetTypeName()]; ok { + usedEnums[field.GetTypeName()] = enum + } + friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] + fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) + default: + fieldType += fmt.Sprintf("`%s`", typ) + } + fmt.Fprintf(content, "| `%s` | %s | %s |\n", field.GetName(), fieldType, strings.Replace(field.comment, "\n", " ", -1)) + } + fmt.Fprintln(content) + } + } + } + + if len(usedEnums) > 0 { + fmt.Fprint(content, "## Used Enums\n\n") + + var enumKeys []string + for key := range usedEnums { + enumKeys = append(enumKeys, key) + } + sort.Strings(enumKeys) + + for _, enumKey := range enumKeys { + enum := usedEnums[enumKey] + + fmt.Fprintf(content, "### `%s`\n\n", enum.key) + if enum.comment != "" { + fmt.Fprintf(content, "%s\n\n", enum.comment) + } + + if len(enum.values) > 0 { + fmt.Fprintln(content, "| Value | Description |") + fmt.Fprintln(content, "| ----- | ----------- |") + for _, value := range enum.values { + fmt.Fprintf(content, "| `%s` | %s |\n", value.GetName(), strings.Replace(value.comment, "\n", " ", -1)) + } + fmt.Fprintln(content) + } + } + } + + packageService := strings.TrimPrefix(service.key, ".") + packageName := packageService[:strings.Index(packageService, ".")] + location, ok := packageLocations[packageName] + if !ok { + fail("Could not find location of package", packageName) + } + fileName := path.Join(location, service.GetName()+".md") + contentString := content.String() + g.Response.File = append(g.Response.File, &plugin.CodeGeneratorResponse_File{ + Name: &fileName, + Content: &contentString, + }) + } + + // Send back the results. + data, err = proto.Marshal(g.Response) + if err != nil { + failWithError(err, "failed to marshal output proto") + } + _, err = os.Stdout.Write(data) + if err != nil { + failWithError(err, "failed to write output proto") + } +} + +func stringPtr(str string) *string { + return &str +} + +func heading(str string) string { + return strings.ToLower(strings.NewReplacer(".", "").Replace(str)) +} + +func useMessage(tree *tree, msg *message, messages map[string]*message, enums map[string]*enum) { + messages[msg.key] = msg + for _, msg := range msg.nested { + useMessage(tree, msg, messages, enums) + } + for _, field := range msg.fields { + if msg, ok := tree.messages[field.GetTypeName()]; ok { + useMessage(tree, msg, messages, enums) + } + if enum, ok := tree.enums[field.GetTypeName()]; ok { + enums[enum.key] = enum + } + } +} diff --git a/utils/protoc-gen-ttndoc/types.go b/utils/protoc-gen-ttndoc/types.go new file mode 100644 index 000000000..514efa3e4 --- /dev/null +++ b/utils/protoc-gen-ttndoc/types.go @@ -0,0 +1,158 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strings" + + "github.com/golang/protobuf/protoc-gen-go/descriptor" + gateway "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" +) + +type service struct { + key string + comment string + *descriptor.ServiceDescriptorProto + methods []*method +} + +func (s *service) GoString() string { + return s.key +} + +type method struct { + key string + comment string + *descriptor.MethodDescriptorProto + input *message + inputStream bool + output *message + outputStream bool + endpoints []*endpoint +} + +func (m *method) GoString() string { + var inputType, outputType string + if m.inputStream { + inputType += "stream " + } + inputType += m.input.GoString() + + if m.outputStream { + outputType += "stream " + } + outputType += m.output.GoString() + return fmt.Sprintf("%s (%s) -> (%s)", m.key, inputType, outputType) +} + +type endpoint struct { + method string + url string +} + +func newEndpoint(opts *gateway.HttpRule) *endpoint { + if opts == nil { + return nil + } + switch opt := opts.GetPattern().(type) { + case *gateway.HttpRule_Get: + return &endpoint{"GET", opt.Get} + case *gateway.HttpRule_Put: + return &endpoint{"PUT", opt.Put} + case *gateway.HttpRule_Post: + return &endpoint{"POST", opt.Post} + case *gateway.HttpRule_Delete: + return &endpoint{"DELETE", opt.Delete} + case *gateway.HttpRule_Patch: + return &endpoint{"PATCH", opt.Patch} + } + return nil +} + +func (e *endpoint) GoString() string { + return fmt.Sprintf("%s %s", e.method, e.url) +} + +type message struct { + key string + comment string + *descriptor.DescriptorProto + fields []*field + nested []*message + oneofs []*oneof +} + +func (m *message) GoString() string { + return m.key +} + +func (m *message) GetOneof(idx int32) *oneof { + for _, oneof := range m.oneofs { + if oneof.index == idx { + return oneof + } + } + return nil +} + +type oneof struct { + index int32 + *descriptor.OneofDescriptorProto + fields []*field +} + +func (o *oneof) GoString() string { + return o.GetName() +} + +type field struct { + key string + comment string + repeated bool + isOneOf bool + *descriptor.FieldDescriptorProto +} + +func (f *field) GoString() string { + var fieldInfo string + if f.repeated { + fieldInfo += "repeated " + } + typ := strings.ToLower(strings.TrimPrefix(f.GetType().String(), "TYPE_")) + if typ == "message" { + fieldInfo += f.GetTypeName() + } else { + fieldInfo += typ + } + return fmt.Sprintf("%s (%s)", f.key, fieldInfo) +} + +type enumValue struct { + comment string + *descriptor.EnumValueDescriptorProto +} + +func (e *enumValue) GoString() string { + return e.GetName() +} + +type enum struct { + key string + comment string + *descriptor.EnumDescriptorProto + values []*enumValue +} + +func (e *enum) GoString() string { + return e.key +} + +type tree struct { + services map[string]*service + methods map[string]*method + messages map[string]*message + fields map[string]*field + enums map[string]*enum +} diff --git a/utils/random/random.go b/utils/random/random.go new file mode 100644 index 000000000..b929ef21e --- /dev/null +++ b/utils/random/random.go @@ -0,0 +1,201 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package random + +import ( + "encoding/binary" + "fmt" + "math" + "math/rand" + "sync" + "time" +) + +// Source: http://stackoverflow.com/a/31832326 + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = r.rand.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} + +// Token generate a random 2-bytes token +func (r *TTNRandom) Token() []byte { + r.mu.Lock() + defer r.mu.Unlock() + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, r.rand.Uint32()) + return b[0:2] +} + +// Rssi generates RSSI signal between -120 < rssi < 0 +func (r *TTNRandom) Rssi() int32 { + r.mu.Lock() + defer r.mu.Unlock() + // Generate RSSI. Tend towards generating great signal strength. + x := float64(r.rand.Int31()) * float64(2e-9) + return int32(-1.6 * math.Exp(x)) +} + +var euFreqs = []float32{ + 868.1, + 868.3, + 868.5, + 868.8, + 867.1, + 867.3, + 867.5, + 867.7, + 867.9, +} + +var usFreqs = []float32{ + 903.9, + 904.1, + 904.3, + 904.5, + 904.7, + 904.9, + 905.1, + 905.3, + 904.6, +} + +// Freq generates a frequency between 865.0 and 870.0 Mhz +func (r *TTNRandom) Freq() float32 { + r.mu.Lock() + defer r.mu.Unlock() + return usFreqs[r.rand.Intn(len(usFreqs))] +} + +// Datr generates Datr for instance: SF4BW125 +func (r *TTNRandom) Datr() string { + r.mu.Lock() + defer r.mu.Unlock() + // Spread Factor from 12 to 7 + sf := 12 - r.rand.Intn(7) + var bw int + if sf == 6 { + // DR6 -> SF7@250Khz + sf = 7 + bw = 250 + } else { + bw = 125 + } + return fmt.Sprintf("SF%dBW%d", sf, bw) +} + +// Codr generates Codr for instance: 4/6 +func (r *TTNRandom) Codr() string { + r.mu.Lock() + defer r.mu.Unlock() + d := r.rand.Intn(4) + 5 + return fmt.Sprintf("4/%d", d) +} + +// Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise +func (r *TTNRandom) Lsnr() float32 { + r.mu.Lock() + defer r.mu.Unlock() + x := float64(r.rand.Int31()) * float64(2e-9) + return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) +} + +// Bytes generates a random byte slice of length n +func (r *TTNRandom) Bytes(n int) []byte { + r.mu.Lock() + defer r.mu.Unlock() + p := make([]byte, n) + r.rand.Read(p) + return p +} + +// Intn returns random int with max n +func Intn(n int) int { + return global.Intn(n) +} + +// String returns random string of length n +func String(n int) string { + return global.String(n) +} + +// Token generate a random 2-bytes token +func Token() []byte { + return global.Token() +} + +// Rssi generates RSSI signal between -120 < rssi < 0 +func Rssi() int32 { + return global.Rssi() +} + +// Freq generates a frequency between 865.0 and 870.0 Mhz +func Freq() float32 { + return global.Freq() +} + +// Datr generates Datr for instance: SF4BW125 +func Datr() string { + return global.Datr() +} + +// Codr generates Codr for instance: 4/6 +func Codr() string { + return global.Codr() +} + +// Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise +func Lsnr() float32 { + return global.Lsnr() +} + +// Bytes generates a random byte slice of length n +func Bytes(n int) []byte { + return global.Bytes(n) +} diff --git a/utils/security/convert_keys.go b/utils/security/convert_keys.go new file mode 100644 index 000000000..6d98dcb49 --- /dev/null +++ b/utils/security/convert_keys.go @@ -0,0 +1,33 @@ +package security + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" +) + +// PublicPEM returns the PEM-encoded public key +func PublicPEM(key *ecdsa.PrivateKey) ([]byte, error) { + pubBytes, err := x509.MarshalPKIXPublicKey(key.Public()) + if err != nil { + return nil, err + } + pubPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubBytes, + }) + return pubPEM, nil +} + +// PrivatePEM returns the PEM-encoded private key +func PrivatePEM(key *ecdsa.PrivateKey) ([]byte, error) { + privBytes, err := x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + privPEM := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: privBytes, + }) + return privPEM, nil +} diff --git a/utils/security/generate_keys.go b/utils/security/generate_keys.go new file mode 100644 index 000000000..3f0942fbc --- /dev/null +++ b/utils/security/generate_keys.go @@ -0,0 +1,101 @@ +package security + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "net" + "path/filepath" + "time" +) + +var ( + validFor = 365 * 24 * time.Hour +) + +// GenerateKeypair generates a new keypair in the given location +func GenerateKeypair(location string) error { + // Generate private key + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + privPEM, err := PrivatePEM(key) + if err != nil { + return err + } + pubPEM, err := PublicPEM(key) + if err != nil { + return err + } + + err = ioutil.WriteFile(filepath.Clean(location+"/server.pub"), pubPEM, 0644) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Clean(location+"/server.key"), privPEM, 0600) + if err != nil { + return err + } + + return nil +} + +// GenerateCert generates a certificate for the given hostnames in the given location +func GenerateCert(location string, hostnames ...string) error { + privKey, err := LoadKeypair(location) + if err != nil { + return err + } + + // Build Certificate + notBefore := time.Now() + notAfter := notBefore.Add(validFor) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return err + } + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"The Things Network"}, + }, + IsCA: true, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + } + for _, h := range hostnames { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + // Generate certificate + certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, privKey.Public(), privKey) + if err != nil { + return err + } + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + err = ioutil.WriteFile(filepath.Clean(location+"/server.cert"), certPEM, 0644) + if err != nil { + return err + } + + return nil +} diff --git a/utils/security/jwt.go b/utils/security/jwt.go new file mode 100644 index 000000000..69fd69353 --- /dev/null +++ b/utils/security/jwt.go @@ -0,0 +1,52 @@ +package security + +import ( + "crypto/ecdsa" + "fmt" + "time" + + "github.com/dgrijalva/jwt-go" +) + +// BuildJWT builds a JSON Web Token for the given subject and ttl, and signs it with the given private key +func BuildJWT(subject string, ttl time.Duration, privateKey []byte) (token string, err error) { + claims := jwt.StandardClaims{ + Issuer: subject, + Subject: subject, + IssuedAt: time.Now().Add(-20 * time.Second).Unix(), + NotBefore: time.Now().Add(-20 * time.Second).Unix(), + } + if ttl > 0 { + claims.ExpiresAt = time.Now().Add(ttl).Unix() + } + tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + var key *ecdsa.PrivateKey + key, err = jwt.ParseECPrivateKeyFromPEM(privateKey) + if err != nil { + return + } + token, err = tokenBuilder.SignedString(key) + if err != nil { + return + } + return +} + +// ValidateJWT validates a JSON Web Token with the given public key +func ValidateJWT(token string, publicKey []byte) (*jwt.StandardClaims, error) { + claims := &jwt.StandardClaims{} + _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok { + return nil, fmt.Errorf("Unexpected JWT signing method: %v", token.Header["alg"]) + } + key, err := jwt.ParseECPublicKeyFromPEM(publicKey) + if err != nil { + return nil, err + } + return key, nil + }) + if err != nil { + return nil, fmt.Errorf("Unable to verify JWT: %s", err.Error()) + } + return claims, nil +} diff --git a/utils/security/jwt_test.go b/utils/security/jwt_test.go new file mode 100644 index 000000000..d93bf7393 --- /dev/null +++ b/utils/security/jwt_test.go @@ -0,0 +1,48 @@ +package security + +import ( + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +var privKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEII97KXBANi9c3EjrYmjAGOqTG40yIBnIsGRHjHJpZp2ToAoGCCqGSM49 +AwEHoUQDQgAEjpDmYI4+tNGyOncpxWKfPs8mirDYOft1TEC43DTCN5vCSfupyBS7 +ZKgUUjg4E0Aq5SIJENqeRP3tTko8O3VZYQ== +-----END EC PRIVATE KEY-----` + +var pubKey = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjpDmYI4+tNGyOncpxWKfPs8mirDY +Oft1TEC43DTCN5vCSfupyBS7ZKgUUjg4E0Aq5SIJENqeRP3tTko8O3VZYQ== +-----END PUBLIC KEY-----` + +func TestJWT(t *testing.T) { + a := New(t) + + jwt, err := BuildJWT("the-subject", time.Second, []byte(privKey)) + a.So(err, ShouldBeNil) + + claims, err := ValidateJWT(jwt, []byte(pubKey)) + a.So(err, ShouldBeNil) + + a.So(claims.Subject, ShouldEqual, "the-subject") + a.So(claims.Issuer, ShouldEqual, "the-subject") + + // Wrong private key + _, err = ValidateJWT(jwt, []byte("this is no key")) + a.So(err, ShouldNotBeNil) + + // Wrong algorithm + _, err = ValidateJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I", []byte(pubKey)) + a.So(err, ShouldNotBeNil) + + // Wrong signature + _, err = ValidateJWT("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzY4MDE0MDUsImlhdCI6MTQ3NjgwMTMyNSwiaXNzIjoidGhlLXN1YmplY3QiLCJuYmYiOjE0NzY4MDEzMjUsInN1YiI6InRoZS1zdWJqZWN0In0.4YudFUVQL4ODy8MMHWZrdB3CCgZedCD7FMUu2iPF4O1WIvptKaUyp9lBu-Eo2SfuNXcTIa1CiOiye36aeelCEw", []byte(pubKey)) + a.So(err, ShouldNotBeNil) + + // Expired + _, err = ValidateJWT("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzY4MDE1NzEsImlhdCI6MTQ3NjgwMTU1MCwiaXNzIjoidGhlLXN1YmplY3QiLCJuYmYiOjE0NzY4MDE1NTAsInN1YiI6InRoZS1zdWJqZWN0In0.AsKUzs9kenfqmDEtLXvNk3Akf_dkfU-8Zy8brHRawsOr64LxA0Mfb2Ufxwzk0JQr5Rtigw2RAVGirFyZI5meBQ", []byte(pubKey)) + a.So(err, ShouldNotBeNil) +} diff --git a/utils/security/load_keys.go b/utils/security/load_keys.go new file mode 100644 index 000000000..1a558106e --- /dev/null +++ b/utils/security/load_keys.go @@ -0,0 +1,36 @@ +package security + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" + "io/ioutil" + "path/filepath" +) + +// LoadKeypair loads the keypair in the given location +func LoadKeypair(location string) (*ecdsa.PrivateKey, error) { + priv, err := ioutil.ReadFile(filepath.Clean(location + "/server.key")) + if err != nil { + return nil, err + } + privBlock, _ := pem.Decode(priv) + if privBlock == nil { + return nil, errors.New("No private key data found") + } + privKey, err := x509.ParseECPrivateKey(privBlock.Bytes) + if err != nil { + return nil, err + } + return privKey, nil +} + +// LoadCert loads the certificate in the given location +func LoadCert(location string) (cert []byte, err error) { + cert, err = ioutil.ReadFile(filepath.Clean(location + "/server.cert")) + if err != nil { + return + } + return +} diff --git a/utils/security/security_test.go b/utils/security/security_test.go new file mode 100644 index 000000000..1a99d38bb --- /dev/null +++ b/utils/security/security_test.go @@ -0,0 +1,48 @@ +package security + +import ( + "io/ioutil" + "os" + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestKeyPairFuncs(t *testing.T) { + a := New(t) + + location := os.TempDir() + + err := GenerateKeypair(location) + a.So(err, ShouldBeNil) + + _, err = LoadKeypair(location + "/derp") + a.So(err, ShouldNotBeNil) + + key, err := LoadKeypair(location) + a.So(err, ShouldBeNil) + a.So(key, ShouldNotBeNil) + + ioutil.WriteFile(location+"/server.key", []byte{}, 0644) + + _, err = LoadKeypair(location) + a.So(err, ShouldNotBeNil) +} + +func TestCertFuncs(t *testing.T) { + a := New(t) + + location := os.TempDir() + + GenerateKeypair(location) + + err := GenerateCert(location, "localhost") + a.So(err, ShouldBeNil) + + _, err = LoadCert(location + "/derp") + a.So(err, ShouldNotBeNil) + + cert, err := LoadCert(location) + a.So(err, ShouldBeNil) + a.So(cert, ShouldNotBeNil) +} diff --git a/utils/testing/log_handler.go b/utils/testing/log_handler.go new file mode 100644 index 000000000..8e8a54a0e --- /dev/null +++ b/utils/testing/log_handler.go @@ -0,0 +1,98 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package testing + +import ( + "bytes" + "sort" + "sync" + "testing" + + "fmt" + + "github.com/apex/log" +) + +// colors. +const ( + none = 0 + red = 31 + green = 32 + yellow = 33 + blue = 34 + gray = 90 +) + +// Colors mapping. +var Colors = [...]int{ + log.DebugLevel: gray, + log.InfoLevel: blue, + log.WarnLevel: yellow, + log.ErrorLevel: red, + log.FatalLevel: red, +} + +// Strings mapping. +var Strings = [...]string{ + log.DebugLevel: "DEBUG", + log.InfoLevel: "INFO", + log.WarnLevel: "WARN", + log.ErrorLevel: "ERROR", + log.FatalLevel: "FATAL", +} + +// field used for sorting. +type field struct { + Name string + Value interface{} +} + +// by sorts projects by call count. +type byName []field + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } + +// LogHandler implementation. +type LogHandler struct { + mu sync.Mutex + T *testing.T +} + +// NewLogHandler handler. +func NewLogHandler(t *testing.T) *LogHandler { + return &LogHandler{ + T: t, + } +} + +// HandleLog implements log.Handler. +func (h *LogHandler) HandleLog(e *log.Entry) error { + color := Colors[e.Level] + level := Strings[e.Level] + + var fields []field + + for k, v := range e.Fields { + fields = append(fields, field{k, v}) + } + + sort.Sort(byName(fields)) + + h.mu.Lock() + defer h.mu.Unlock() + + buf := bytes.NewBuffer([]byte{}) + + fmt.Fprintf(buf, "\033[%dm%6s\033[0m %-25s", color, level, e.Message) + + for _, f := range fields { + fmt.Fprintf(buf, " \033[%dm%s\033[0m=%v", color, f.Name, f.Value) + } + + h.T.Log(buf.String()) + + return nil +} diff --git a/utils/testing/redis.go b/utils/testing/redis.go new file mode 100644 index 000000000..caa729a16 --- /dev/null +++ b/utils/testing/redis.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package testing + +import ( + "fmt" + "os" + + redis "gopkg.in/redis.v5" +) + +// GetRedisClient returns a redis client that can be used for testing +func GetRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: 1, // use default DB + }) +} diff --git a/utils/testing/testing.go b/utils/testing/testing.go new file mode 100644 index 000000000..24824ddae --- /dev/null +++ b/utils/testing/testing.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package testing offers some handy methods to display check and cross symbols with colors in test +// logs. +package testing + +import ( + "errors" + "sync" + "testing" + "time" + + "github.com/apex/log" +) + +func GetLogger(t *testing.T, tag string) log.Interface { + logger := &log.Logger{ + Handler: NewLogHandler(t), + Level: log.DebugLevel, + } + return logger.WithField("tag", tag) +} + +// WaitGroup is an extension of sync.WaitGroup with a WaitFor function for testing +type WaitGroup struct { + sync.WaitGroup +} + +// WaitFor waits for the specified duration +func (wg *WaitGroup) WaitFor(d time.Duration) error { + waitChan := make(chan bool) + go func() { + wg.Wait() + waitChan <- true + close(waitChan) + }() + select { + case <-waitChan: + return nil + case <-time.After(d): + return errors.New("Wait timeout expired") + } +} diff --git a/utils/toa/toa.go b/utils/toa/toa.go new file mode 100644 index 000000000..c42dc99f7 --- /dev/null +++ b/utils/toa/toa.go @@ -0,0 +1,63 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package toa + +import ( + "errors" + "math" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// ComputeLoRa computes the time-on-air given a PHY payload size in bytes, a datr +// identifier and LoRa coding rate identifier. Note that this function operates +// on the PHY payload size and does not add the LoRaWAN header. +// +// See http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf, page 7 +func ComputeLoRa(payloadSize uint, datr string, codr string) (time.Duration, error) { + // Determine CR + var cr float64 + switch codr { + case "4/5": + cr = 1 + case "4/6": + cr = 2 + case "4/7": + cr = 3 + case "4/8": + cr = 4 + default: + return 0, errors.New("Invalid Codr") + } + // Determine DR + dr, err := types.ParseDataRate(datr) + if err != nil { + return 0, err + } + // Determine DE + var de float64 + if dr.Bandwidth == 125 && (dr.SpreadingFactor == 11 || dr.SpreadingFactor == 12) { + de = 1.0 + } + pl := float64(payloadSize) + sf := float64(dr.SpreadingFactor) + bw := float64(dr.Bandwidth) + h := 0.0 // 0 means header is enabled + + tSym := math.Pow(2, float64(dr.SpreadingFactor)) / bw + + payloadNb := 8.0 + math.Max(0.0, math.Ceil((8.0*pl-4.0*sf+28.0+16.0-20.0*h)/(4.0*(sf-2.0*de)))*(cr+4.0)) + timeOnAir := (payloadNb + 12.25) * tSym * 1000000 // in nanoseconds + + return time.Duration(timeOnAir), nil +} + +// ComputeFSK computes the time-on-air given a PHY payload size in bytes and a +// bitrate, Note that this function operates on the PHY payload size and does +// not add the LoRaWAN header. +func ComputeFSK(payloadSize uint, bitrate int) (time.Duration, error) { + tPkt := int(time.Second) * (int(payloadSize) + 5 + 3 + 1 + 2) * 8 / bitrate + return time.Duration(tPkt), nil +} diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go new file mode 100644 index 000000000..e86671d54 --- /dev/null +++ b/utils/toa/toa_test.go @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package toa + +import ( + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestComputeLoRa(t *testing.T) { + a := New(t) + + var toa time.Duration + var err error + + _, err = ComputeLoRa(10, "SFWUT", "4/5") + a.So(err, ShouldNotBeNil) + + _, err = ComputeLoRa(10, "SF10BW125", "1/9") + a.So(err, ShouldNotBeNil) + + // Test different SFs + sfTests := map[string]uint{ + "SF7BW125": 41216, + "SF8BW125": 72192, + "SF9BW125": 144384, + "SF10BW125": 288768, + "SF11BW125": 577536, + "SF12BW125": 991232, + } + for dr, us := range sfTests { + toa, err = ComputeLoRa(10, dr, "4/5") + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + + // Test different BWs + bwTests := map[string]uint{ + "SF7BW125": 41216, + "SF7BW250": 20608, + "SF7BW500": 10304, + } + for dr, us := range bwTests { + toa, err = ComputeLoRa(10, dr, "4/5") + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + + // Test different CRs + crTests := map[string]uint{ + "4/5": 41216, + "4/6": 45312, + "4/7": 49408, + "4/8": 53504, + } + for cr, us := range crTests { + toa, err = ComputeLoRa(10, "SF7BW125", cr) + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + + // Test different payload sizes + plTests := map[uint]uint{ + 13: 46336, + 14: 46336, + 15: 46336, + 16: 51456, + 17: 51456, + 18: 51456, + 19: 51456, + } + for size, us := range plTests { + toa, err = ComputeLoRa(size, "SF7BW125", "4/5") + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + +} + +func TestComputeFSK(t *testing.T) { + a := New(t) + toa, err := ComputeFSK(200, 50000) + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, 33760*time.Microsecond) +} diff --git a/utils/version/version.go b/utils/version/version.go new file mode 100644 index 000000000..ddc66b781 --- /dev/null +++ b/utils/version/version.go @@ -0,0 +1,175 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package version + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "runtime" + "strings" + "time" + + "github.com/apex/log" + "github.com/kardianos/osext" + "github.com/spf13/viper" +) + +// ErrNotFound indicates that a version was not found +var ErrNotFound = errors.New("Not Found") + +// ReleaseHost is where we publish our releases +const ReleaseHost = "ttnreleases.blob.core.windows.net/release" + +// Info contains version information +type Info struct { + Version string + Commit string + Date time.Time +} + +// GetLatestInfo gets information about the latest release for the current branch +func GetLatestInfo() (*Info, error) { + location := fmt.Sprintf("https://%s/%s/info", ReleaseHost, viper.GetString("gitBranch")) + resp, err := http.Get(location) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Status %d was not OK", resp.StatusCode) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var info Info + for _, line := range strings.Split(string(body), "\n") { + infoLine := strings.SplitAfterN(line, " ", 2) + if len(infoLine) != 2 { + continue + } + switch strings.TrimSpace(infoLine[0]) { + case "version": + info.Version = infoLine[1] + case "commit": + info.Commit = infoLine[1] + case "date": + if date, err := time.Parse(time.RFC3339, infoLine[1]); err == nil { + info.Date = date + } + } + } + + return &info, nil +} + +// GetLatest gets the latest release binary +func GetLatest(binary string) ([]byte, error) { + exe := "" + if runtime.GOARCH == "windows" { + exe = ".exe" + } + filename := fmt.Sprintf("%s-%s-%s%s", binary, runtime.GOOS, runtime.GOARCH, exe) + location := fmt.Sprintf("https://%s/%s/%s.tar.gz", ReleaseHost, viper.GetString("gitBranch"), filename) + resp, err := http.Get(location) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + gr, err := gzip.NewReader(bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + archive, err := ioutil.ReadAll(gr) + if err != nil { + return nil, err + } + tr := tar.NewReader(bytes.NewBuffer(archive)) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if hdr.Name == filename { + binary, err := ioutil.ReadAll(tr) + if err != nil { + return nil, err + } + return binary, nil + } + } + return nil, ErrNotFound +} + +// Selfupdate runs a self-update for the current binary +func Selfupdate(ctx log.Interface, component string) { + if viper.GetString("gitBranch") == "unknown" { + ctx.Infof("You are not using an official %s build. Not proceeding with the update", component) + return + } + + info, err := GetLatestInfo() + if err != nil { + ctx.WithError(err).Fatal("Could not get version information from the server") + } + + if viper.GetString("gitCommit") == info.Commit { + ctx.Info("The git commit of the build on the server is the same as yours") + ctx.Info("Not proceeding with the update") + return + } + + if date, err := time.Parse(time.RFC3339, viper.GetString("buildDate")); err == nil { + if date.Equal(info.Date) { + ctx.Infof("You have the latest version of %s", component) + ctx.Info("Nothing to update") + return + } + if date.After(info.Date) { + ctx.Infof("Your build is %s newer than the build on the server", date.Sub(info.Date)) + ctx.Info("Not proceeding with the update") + return + } + ctx.Infof("The build on the server is %s newer than yours", info.Date.Sub(date)) + } + + ctx.Infof("Downloading the latest %s...", component) + binary, err := GetLatest(component) + if err != nil { + ctx.WithError(err).Fatal("Could not download latest binary") + } + filename, err := osext.Executable() + if err != nil { + ctx.WithError(err).Fatal("Could not get path to local binary") + } + stat, err := os.Stat(filename) + if err != nil { + ctx.WithError(err).Fatal("Could not stat local binary") + } + ctx.Info("Replacing local binary...") + if err := ioutil.WriteFile(filename+".new", binary, stat.Mode()); err != nil { + ctx.WithError(err).Fatal("Could not write new binary to filesystem") + } + if err := os.Rename(filename, filename+".old"); err != nil { + ctx.WithError(err).Fatal("Could not rename binary") + } + if err := os.Rename(filename+".new", filename); err != nil { + ctx.WithError(err).Fatal("Could not rename binary") + } + ctx.Infof("Updated %s to the latest version", component) +} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 000000000..2cc6ab566 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,913 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "h5Q1o+kA7VMAR5QpBzVJZbBEgC0=", + "path": "github.com/Sirupsen/logrus", + "revision": "881bee4e20a5d11a6a88a5667c6f292072ac1963", + "revisionTime": "2016-12-02T02:35:07Z" + }, + { + "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", + "path": "github.com/StackExchange/wmi", + "revision": "e54cbda6595d7293a7a468ccf9525f6bc8887f99", + "revisionTime": "2016-08-11T21:45:55Z" + }, + { + "checksumSHA1": "nIPYBc7nATMXCsoZLRtygo0EuTU=", + "path": "github.com/TheThingsNetwork/go-account-lib/account", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "EDBQe1RJEmQmVvdMp6MHXW5Px9c=", + "path": "github.com/TheThingsNetwork/go-account-lib/auth", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", + "path": "github.com/TheThingsNetwork/go-account-lib/cache", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "iXtQRpCIHmyEn70SWf5ZdNqtkhk=", + "path": "github.com/TheThingsNetwork/go-account-lib/claims", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", + "path": "github.com/TheThingsNetwork/go-account-lib/keys", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "waV2cDv9k1cvbWiom6kgnVDWrQo=", + "path": "github.com/TheThingsNetwork/go-account-lib/oauth", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", + "path": "github.com/TheThingsNetwork/go-account-lib/rights", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", + "path": "github.com/TheThingsNetwork/go-account-lib/scope", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "RpKXQd5sp9/jsWM991S7OhE9/ME=", + "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "R0TfhR++1b5YqHsMso4QKiR3IAI=", + "path": "github.com/TheThingsNetwork/go-account-lib/tokens", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "42dAT68+66q7c7Dwm5AVxYi8rIE=", + "path": "github.com/TheThingsNetwork/go-account-lib/util", + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" + }, + { + "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", + "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, + { + "checksumSHA1": "aXt7ZSqIfsHWBbJPgHFjqtyxyQ0=", + "path": "github.com/TheThingsNetwork/go-utils/log", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, + { + "checksumSHA1": "fLBAMyMsGRd2c9t2FifyyZVtvlk=", + "path": "github.com/TheThingsNetwork/go-utils/log/apex", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, + { + "checksumSHA1": "sQ0vy3MCGY1WgK9xldn1V6pMeZk=", + "path": "github.com/TheThingsNetwork/go-utils/log/grpc", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, + { + "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", + "path": "github.com/apex/log", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "LjQdQscNb25c2HxbREjmFFOoyx4=", + "path": "github.com/apex/log/handlers/json", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "AHCiF3VnEqmXyZDeH+z/IGsAtnI=", + "path": "github.com/apex/log/handlers/level", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "rxQUkWqruIZKpRzdwqrkxfcZvyw=", + "path": "github.com/apex/log/handlers/multi", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", + "path": "github.com/asaskevich/govalidator", + "revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", + "revisionTime": "2016-10-01T16:31:30Z" + }, + { + "checksumSHA1": "h/y88wOmQRm6qE29txL9LndV1yY=", + "path": "github.com/bluele/gcache", + "revision": "d920a928be099e4b9a6272f41699f4693cdcee5b", + "revisionTime": "2016-12-12T14:19:04Z" + }, + { + "checksumSHA1": "7K5mkZjohicK/GIgyVwLCkX7vO4=", + "path": "github.com/brocaar/lorawan", + "revision": "4e299458950055dd794109951c3b3e3e6f8785ec", + "revisionTime": "2016-11-23T19:29:51Z" + }, + { + "checksumSHA1": "q51jt7CAzQoh6JWSV7gRpuKKKHU=", + "path": "github.com/brocaar/lorawan/band", + "revision": "4e299458950055dd794109951c3b3e3e6f8785ec", + "revisionTime": "2016-11-23T19:29:51Z" + }, + { + "checksumSHA1": "2Fy1Y6Z3lRRX1891WF/+HT4XS2I=", + "path": "github.com/dgrijalva/jwt-go", + "revision": "9ed569b5d1ac936e6494082958d63a6aa4fff99a", + "revisionTime": "2016-11-01T19:39:35Z" + }, + { + "checksumSHA1": "7x4aKJIFmr5uno+npHbfMVoCFU4=", + "path": "github.com/eclipse/paho.mqtt.golang", + "revision": "cf795e7e56f38805f30998d74674b207fa47ee3c", + "revisionTime": "2016-12-07T10:34:25Z" + }, + { + "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", + "path": "github.com/eclipse/paho.mqtt.golang/packets", + "revision": "cf795e7e56f38805f30998d74674b207fa47ee3c", + "revisionTime": "2016-12-07T10:34:25Z" + }, + { + "checksumSHA1": "KCWVxG+J8SxHGlGiUghe0KBGsa8=", + "path": "github.com/fatih/structs", + "revision": "dc3312cb1a4513a366c4c9e622ad55c32df12ed3", + "revisionTime": "2016-08-07T23:55:29Z" + }, + { + "checksumSHA1": "hveFTNQ9YEyYRs6SWuXM+XU9qRI=", + "path": "github.com/fsnotify/fsnotify", + "revision": "fd9ec7deca8bf46ecd2a795baaacf2b3a9be1197", + "revisionTime": "2016-10-26T20:31:22Z" + }, + { + "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", + "path": "github.com/go-ole/go-ole", + "revision": "5e9c030faf78847db7aa77e3661b9cc449de29b7", + "revisionTime": "2016-11-16T06:46:58Z" + }, + { + "checksumSHA1": "IvdiJE1NIogRmGi3WmteEKZQJB8=", + "path": "github.com/go-ole/go-ole/oleutil", + "revision": "5e9c030faf78847db7aa77e3661b9cc449de29b7", + "revisionTime": "2016-11-16T06:46:58Z" + }, + { + "checksumSHA1": "3yco0089CSJ4qbyUccpbDC2+dPg=", + "path": "github.com/gogo/protobuf/gogoproto", + "revision": "06ec6c31ff1bac6ed4e205a547a3d72934813ef3", + "revisionTime": "2016-12-10T18:20:26Z" + }, + { + "checksumSHA1": "6ZxSmrIx3Jd15aou16oG0HPylP4=", + "path": "github.com/gogo/protobuf/proto", + "revision": "06ec6c31ff1bac6ed4e205a547a3d72934813ef3", + "revisionTime": "2016-12-10T18:20:26Z" + }, + { + "checksumSHA1": "EaY86bsi1nucvO0/UKvp/A72aC8=", + "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", + "revision": "06ec6c31ff1bac6ed4e205a547a3d72934813ef3", + "revisionTime": "2016-12-10T18:20:26Z" + }, + { + "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", + "path": "github.com/golang/mock/gomock", + "revision": "bd3c8e81be01eef76d4b503f5e687d2d1354d2d9", + "revisionTime": "2016-01-21T18:51:14Z" + }, + { + "checksumSHA1": "APDDi2ohrU7OkChQCekD9tSVUhs=", + "path": "github.com/golang/protobuf/jsonpb", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" + }, + { + "checksumSHA1": "kBeNcaKk56FguvPSUCEaH6AxpRc=", + "path": "github.com/golang/protobuf/proto", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" + }, + { + "checksumSHA1": "AjyXQ5eohrCPS/jSWZFPn5E8wnQ=", + "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" + }, + { + "checksumSHA1": "T/EqMkqzvjQUL1c+yN32kketgfE=", + "path": "github.com/golang/protobuf/protoc-gen-go/generator", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" + }, + { + "checksumSHA1": "zps2+aJoFhpFf2F8TsU9zCGXL2c=", + "path": "github.com/golang/protobuf/protoc-gen-go/plugin", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" + }, + { + "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", + "path": "github.com/golang/protobuf/ptypes/empty", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" + }, + { + "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", + "path": "github.com/gosuri/uitable", + "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", + "revisionTime": "2016-04-04T20:39:58Z" + }, + { + "checksumSHA1": "hfL7iFULaUity86NGidQt/AiYyo=", + "path": "github.com/gosuri/uitable/util/strutil", + "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", + "revisionTime": "2016-04-04T20:39:58Z" + }, + { + "checksumSHA1": "xUvDrGeY4HijGKl+W8WSvWl1Evs=", + "path": "github.com/gosuri/uitable/util/wordwrap", + "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", + "revisionTime": "2016-04-04T20:39:58Z" + }, + { + "checksumSHA1": "LoEQ+t5UoMm4InaYVPVn0XqHPwA=", + "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" + }, + { + "checksumSHA1": "x396LPNfci/5x8aVJbliQHH11HQ=", + "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" + }, + { + "checksumSHA1": "NCyVGekDqPMTHHK4ZbEDPZeiN2s=", + "path": "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api", + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" + }, + { + "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", + "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" + }, + { + "checksumSHA1": "Ok3Csn6Voou7pQT6Dv2mkwpqFtw=", + "path": "github.com/hashicorp/hcl", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", + "path": "github.com/hashicorp/hcl/hcl/ast", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "vF6LLywGDoAaccTcAGrcY7mYvZc=", + "path": "github.com/hashicorp/hcl/hcl/parser", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "z6wdP4mRw4GVjShkNHDaOWkbxS0=", + "path": "github.com/hashicorp/hcl/hcl/scanner", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "oS3SCN9Wd6D8/LG0Yx1fu84a7gI=", + "path": "github.com/hashicorp/hcl/hcl/strconv", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", + "path": "github.com/hashicorp/hcl/hcl/token", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "138aCV5n8n7tkGYMsMVQQnnLq+0=", + "path": "github.com/hashicorp/hcl/json/parser", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", + "path": "github.com/hashicorp/hcl/json/scanner", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", + "path": "github.com/hashicorp/hcl/json/token", + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" + }, + { + "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", + "path": "github.com/howeyc/gopass", + "revision": "f5387c492211eb133053880d23dfae62aa14123d", + "revisionTime": "2016-10-03T13:09:00Z" + }, + { + "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", + "path": "github.com/inconshreveable/mousetrap", + "revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75", + "revisionTime": "2014-10-17T20:07:13Z" + }, + { + "checksumSHA1": "7PLlrIaGI1TKWB96RkizgkTtOtQ=", + "path": "github.com/jacobsa/crypto/cmac", + "revision": "293ce0c192fb4f59cd879b46544922b9ed09a13a", + "revisionTime": "2016-11-11T03:08:13Z" + }, + { + "checksumSHA1": "NBvtX91AEKxFLmj8mwwhXEKl6d0=", + "path": "github.com/jacobsa/crypto/common", + "revision": "293ce0c192fb4f59cd879b46544922b9ed09a13a", + "revisionTime": "2016-11-11T03:08:13Z" + }, + { + "checksumSHA1": "sKheT5xw89Tbu2Q071FQO27CVmE=", + "path": "github.com/juju/ratelimit", + "revision": "77ed1c8a01217656d2080ad51981f6e99adaa177", + "revisionTime": "2015-11-25T20:19:25Z" + }, + { + "checksumSHA1": "g+afVQQVopBLiLB5pFZp/8s6aBs=", + "path": "github.com/kardianos/osext", + "revision": "c2c54e542fb797ad986b31721e1baedf214ca413", + "revisionTime": "2016-08-11T00:15:26Z" + }, + { + "checksumSHA1": "506eXGmFfB7mgzbMcsdT/UAXJgI=", + "path": "github.com/magiconair/properties", + "revision": "9c47895dc1ce54302908ab8a43385d1f5df2c11c", + "revisionTime": "2016-11-28T00:34:34Z" + }, + { + "checksumSHA1": "yf185lmVPcvXjLZuPT1s4JzxI18=", + "path": "github.com/mattn/go-runewidth", + "revision": "737072b4e32b7a5018b4a7125da8d12de90e8045", + "revisionTime": "2016-10-12T01:35:12Z" + }, + { + "checksumSHA1": "V/quM7+em2ByJbWBLOsEwnY3j/Q=", + "path": "github.com/mitchellh/go-homedir", + "revision": "b8bc1bf767474819792c23f32d8286a45736f1c6", + "revisionTime": "2016-12-03T19:45:07Z" + }, + { + "checksumSHA1": "LAR/G/IY1GviHYkGAoi6kVXq1Jg=", + "path": "github.com/mitchellh/mapstructure", + "revision": "bfdb1a85537d60bc7e954e600c250219ea497417", + "revisionTime": "2016-12-11T22:23:15Z" + }, + { + "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", + "path": "github.com/mwitkow/go-grpc-middleware", + "revision": "591df76e9998aebfb14fc2c4d96ca3216180596c", + "revisionTime": "2016-11-20T07:58:21Z" + }, + { + "checksumSHA1": "8Y05Pz7onrQPcVWW6JStSsYRh6E=", + "path": "github.com/pelletier/go-buffruneio", + "revision": "df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d", + "revisionTime": "2016-01-24T19:35:03Z" + }, + { + "checksumSHA1": "8HO0U5Iblu0bhhmmp6kSGwHBBak=", + "path": "github.com/pelletier/go-toml", + "revision": "017119f7a78a0b5fc0ea39ef6be09f03acf3345d", + "revisionTime": "2016-12-13T14:20:06Z" + }, + { + "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", + "path": "github.com/pkg/errors", + "revision": "248dadf4e9068a0b3e79f02ed0a610d935de5302", + "revisionTime": "2016-10-29T09:36:37Z" + }, + { + "checksumSHA1": "KAzbLjI9MzW2tjfcAsK75lVRp6I=", + "path": "github.com/rcrowley/go-metrics", + "revision": "1f30fe9094a513ce4c700b9a54458bbb0c96996c", + "revisionTime": "2016-11-28T21:05:44Z" + }, + { + "checksumSHA1": "5qwv3yDROEz5ZV8HztOBmQxen8c=", + "path": "github.com/robertkrimen/otto", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=", + "path": "github.com/robertkrimen/otto/ast", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "L0KsB2EzTlPgv0iae3q3SukNW7U=", + "path": "github.com/robertkrimen/otto/dbg", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "euDLJKhw4doeTSxjEoezjxYXLzs=", + "path": "github.com/robertkrimen/otto/file", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "LLuLITFO8chqSG0+APJIy5NtOHU=", + "path": "github.com/robertkrimen/otto/parser", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "7J/7NaYRqKhBvZ+dTIutsEoEgFw=", + "path": "github.com/robertkrimen/otto/registry", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "/jMXYuXycBpTqWhRyJ2xsqvHvQI=", + "path": "github.com/robertkrimen/otto/token", + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" + }, + { + "checksumSHA1": "ujFjoR6H3TDeiuY9kvvwSwBMcJk=", + "path": "github.com/shirou/gopsutil/cpu", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "A5OQcD4rTEhWAfWvp6lzFBA9lfs=", + "path": "github.com/shirou/gopsutil/host", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "sNAWEDfrq5thpuxsiTvRNJ1fii0=", + "path": "github.com/shirou/gopsutil/internal/common", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "jB8En6qWQ7G2yPJey4uY1FvOjWM=", + "path": "github.com/shirou/gopsutil/load", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "sOBIj+eocRSO0xtX8vkJDZKTDl8=", + "path": "github.com/shirou/gopsutil/mem", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "OHn1U/wFzUGaaBqpsPt5d90nucY=", + "path": "github.com/shirou/gopsutil/net", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "+4bS/41wTuGe0oczEwrBCWp6YO8=", + "path": "github.com/shirou/gopsutil/process", + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" + }, + { + "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", + "path": "github.com/shirou/w32", + "revision": "bb4de0191aa41b5507caa14b0650cdbddcd9280b", + "revisionTime": "2016-09-30T03:27:40Z" + }, + { + "checksumSHA1": "RPVh5/wbvFG0q0CWHXifXpkYTDg=", + "path": "github.com/smartystreets/assertions", + "revision": "26acb9229f421449ac63d014995b282d59261a8b", + "revisionTime": "2016-12-13T22:48:10Z" + }, + { + "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", + "path": "github.com/smartystreets/assertions/internal/go-render/render", + "revision": "26acb9229f421449ac63d014995b282d59261a8b", + "revisionTime": "2016-12-13T22:48:10Z" + }, + { + "checksumSHA1": "dSZQzhiGN0tEILHxUZcrFFNW2Xw=", + "path": "github.com/smartystreets/assertions/internal/oglematchers", + "revision": "26acb9229f421449ac63d014995b282d59261a8b", + "revisionTime": "2016-12-13T22:48:10Z" + }, + { + "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", + "path": "github.com/smartystreets/go-aws-auth", + "revision": "2043e6d0bb7e4c18464a7bba562acbe482e3cabd", + "revisionTime": "2016-07-22T04:48:03Z" + }, + { + "checksumSHA1": "KEzQv4I7c+tcoTizQM+tavqKsuM=", + "path": "github.com/spf13/afero", + "revision": "2f30b2a92c0e5700bcfe4715891adb1f2a7a406d", + "revisionTime": "2016-12-08T18:21:42Z" + }, + { + "checksumSHA1": "u6B0SEgZ/TUEfIvF6w/HnFVQbII=", + "path": "github.com/spf13/afero/mem", + "revision": "2f30b2a92c0e5700bcfe4715891adb1f2a7a406d", + "revisionTime": "2016-12-08T18:21:42Z" + }, + { + "checksumSHA1": "+mfjYfgvbP8vg0ubsMOw/iTloo8=", + "path": "github.com/spf13/cast", + "revision": "24b6558033ffe202bf42f0f3b870dcc798dd2ba8", + "revisionTime": "2016-11-16T01:33:54Z" + }, + { + "checksumSHA1": "FZ0r4TzEy9UxXLkFVXFygApni4M=", + "path": "github.com/spf13/cobra", + "revision": "9495bc009a56819bdb0ddbc1a373e29c140bc674", + "revisionTime": "2016-11-16T13:20:53Z" + }, + { + "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", + "path": "github.com/spf13/jwalterweatherman", + "revision": "33c24e77fb80341fe7130ee7c594256ff08ccc46", + "revisionTime": "2016-03-01T12:00:06Z" + }, + { + "checksumSHA1": "AxfxmmBpbjQoaQKXbERcEw9pv+U=", + "path": "github.com/spf13/pflag", + "revision": "25f8b5b07aece3207895bf19f7ab517eb3b22a40", + "revisionTime": "2016-12-14T04:49:49Z" + }, + { + "checksumSHA1": "Ru5RGygreAp5OW9B6kO5Zawjt08=", + "path": "github.com/spf13/viper", + "revision": "5ed0fc31f7f453625df314d8e66b9791e8d13003", + "revisionTime": "2016-12-13T09:38:49Z" + }, + { + "checksumSHA1": "BKXPIqMlpj20qhEM+7luvOl2PpM=", + "path": "github.com/streadway/amqp", + "revision": "cb4fb930736ebd61a54da180a6aa4e92b206ff13", + "revisionTime": "2016-12-10T20:40:49Z" + }, + { + "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", + "path": "github.com/tj/go-elastic", + "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", + "revisionTime": "2016-06-07T20:24:39Z" + }, + { + "checksumSHA1": "nL4enNHknemOmxcaPTIJCrJc0/I=", + "path": "github.com/tj/go-elastic/aliases", + "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", + "revisionTime": "2016-06-07T20:24:39Z" + }, + { + "checksumSHA1": "SgbyhOvKGvet/Nw70Rxa8d3gLZ0=", + "path": "github.com/tj/go-elastic/batch", + "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", + "revisionTime": "2016-06-07T20:24:39Z" + }, + { + "checksumSHA1": "VE+WBfxeMNC5a98uLXK2Iu80hOU=", + "path": "golang.org/x/crypto/ssh/terminal", + "revision": "9a6f0a01987842989747adff311d80750ba25530", + "revisionTime": "2016-12-10T14:54:14Z" + }, + { + "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", + "path": "golang.org/x/net/context", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "BgKnt50LaV97izvgLqONWKIbeMw=", + "path": "golang.org/x/net/http2", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", + "path": "golang.org/x/net/http2/hpack", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", + "path": "golang.org/x/net/idna", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", + "path": "golang.org/x/net/internal/timeseries", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", + "path": "golang.org/x/net/lex/httplex", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "P9qTIn8a6L6Q9wd1IJBCuhno1Q8=", + "path": "golang.org/x/net/trace", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=", + "path": "golang.org/x/net/websocket", + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" + }, + { + "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", + "path": "golang.org/x/oauth2", + "revision": "96382aa079b72d8c014eb0c50f6c223d1e6a2de0", + "revisionTime": "2016-12-13T06:27:07Z" + }, + { + "checksumSHA1": "Wqm34oALxi3GkTSHIBa/EcfE37Y=", + "path": "golang.org/x/oauth2/internal", + "revision": "96382aa079b72d8c014eb0c50f6c223d1e6a2de0", + "revisionTime": "2016-12-13T06:27:07Z" + }, + { + "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", + "path": "golang.org/x/sys/unix", + "revision": "478fcf54317e52ab69f40bb4c7a1520288d7f7ea", + "revisionTime": "2016-12-05T15:46:50Z" + }, + { + "checksumSHA1": "kv3jbPJGCczHVQ7g51am1MxlD1c=", + "path": "golang.org/x/text/internal/gen", + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" + }, + { + "checksumSHA1": "47nwiUyVBY2RKoEGXmCSvusY4Js=", + "path": "golang.org/x/text/internal/triegen", + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" + }, + { + "checksumSHA1": "Yd5wMObzagIfCiKLpZbtBIrOUA4=", + "path": "golang.org/x/text/internal/ucd", + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" + }, + { + "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", + "path": "golang.org/x/text/transform", + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" + }, + { + "checksumSHA1": "i14IZXKECObKRUNvTr7xivSL1IU=", + "path": "golang.org/x/text/unicode/cldr", + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" + }, + { + "checksumSHA1": "Vircurgvsnt4k26havmxPM67PUA=", + "path": "golang.org/x/text/unicode/norm", + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" + }, + { + "checksumSHA1": "gYHoPrPncGO926bN0jr1rzDxBQU=", + "path": "google.golang.org/appengine/internal", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", + "path": "google.golang.org/appengine/internal/base", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", + "path": "google.golang.org/appengine/internal/datastore", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", + "path": "google.golang.org/appengine/internal/log", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", + "path": "google.golang.org/appengine/internal/remote_api", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", + "path": "google.golang.org/appengine/internal/urlfetch", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", + "path": "google.golang.org/appengine/urlfetch", + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" + }, + { + "checksumSHA1": "F/69y+hieV9WDDBwLOTdsLJg1q8=", + "path": "google.golang.org/grpc", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", + "path": "google.golang.org/grpc/codes", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", + "path": "google.golang.org/grpc/credentials", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", + "path": "google.golang.org/grpc/grpclog", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "d0iunsiWfA0qXxLMNkTC4tGJnOo=", + "path": "google.golang.org/grpc/health", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "pSFXzfvPlaDBK2RsMcTiIeks4ok=", + "path": "google.golang.org/grpc/health/grpc_health_v1", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", + "path": "google.golang.org/grpc/internal", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "XXpD8+S3gLrfmCLOf+RbxblOQkU=", + "path": "google.golang.org/grpc/metadata", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", + "path": "google.golang.org/grpc/naming", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", + "path": "google.golang.org/grpc/peer", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "4zzPK1BUgnOcugiN2vnkhUal4ls=", + "path": "google.golang.org/grpc/stats", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "N0TftT6/CyWqp6VRi2DqDx60+Fo=", + "path": "google.golang.org/grpc/tap", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "x+eyD2YGMYn973r3dQwGOMdi4mA=", + "path": "google.golang.org/grpc/transport", + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" + }, + { + "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", + "path": "gopkg.in/bsm/ratelimit.v1", + "revision": "db14e161995a5177acef654cb0dd785e8ee8bc22", + "revisionTime": "2016-02-20T15:49:07Z" + }, + { + "checksumSHA1": "39B/Bm5H2lA8P/j1wEY2sAryXzA=", + "path": "gopkg.in/redis.v5", + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" + }, + { + "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", + "path": "gopkg.in/redis.v5/internal", + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" + }, + { + "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", + "path": "gopkg.in/redis.v5/internal/consistenthash", + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" + }, + { + "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", + "path": "gopkg.in/redis.v5/internal/hashtag", + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" + }, + { + "checksumSHA1": "VnsHRPAMRMuhz7/n/85MZwMrchQ=", + "path": "gopkg.in/redis.v5/internal/pool", + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" + }, + { + "checksumSHA1": "604uyPTNWLBNAnAyNRMiwYHXknA=", + "path": "gopkg.in/redis.v5/internal/proto", + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" + }, + { + "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", + "path": "gopkg.in/sourcemap.v1", + "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", + "revisionTime": "2016-06-02T08:55:44Z" + }, + { + "checksumSHA1": "DPyTTxwhl5mrDF15nLNtshc0cWs=", + "path": "gopkg.in/sourcemap.v1/base64vlq", + "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", + "revisionTime": "2016-06-02T08:55:44Z" + }, + { + "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", + "path": "gopkg.in/yaml.v2", + "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", + "revisionTime": "2016-09-28T15:37:09Z" + } + ], + "rootPath": "github.com/TheThingsNetwork/ttn" +}