diff --git a/.env/broker/server.cert b/.env/broker/server.cert index d28985938..680837f68 100644 --- a/.env/broker/server.cert +++ b/.env/broker/server.cert @@ -1,12 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIB2jCCAYGgAwIBAgIRAOEgVqmjthE4051Is5AjJxgwCgYIKoZIzj0EAwIwMjEb -MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMRMwEQYDVQQDEwpkZXYgYnJva2Vy -MB4XDTE3MTIwNTEwMzI1OVoXDTE4MTIwNTEwMzI1OVowMjEbMBkGA1UEChMSVGhl -IFRoaW5ncyBOZXR3b3JrMRMwEQYDVQQDEwpkZXYgYnJva2VyMFkwEwYHKoZIzj0C -AQYIKoZIzj0DAQcDQgAESiYgtb2pwLqmzrVWD8EjPA+qErHgDnlK2uXR4l7ae9ne -ffpIGhH2umYKdCkmnrPg9n8RPkSeBur+w2GyoFjhraN4MHYwDgYDVR0PAQH/BAQD -AgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTAD -AQH/MDQGA1UdEQQtMCuCBmJyb2tlcoIJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAA -AAAAAAAAAAABMAoGCCqGSM49BAMCA0cAMEQCIERR6p5t532l9pDKuM9HyQHrIHYY -GaWyRohtthN4YMbIAiAIlpmbxNvzUWKc6f3417NZrw0lP94IvmvgoNNarHQtNg== +MIIB2zCCAYCgAwIBAgIQB23IZZbRR4hwPEKqOw3pTTAKBggqhkjOPQQDAjAyMRsw +GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmsxEzARBgNVBAMTCmRldiBicm9rZXIw +HhcNMTgwOTI3MTA1NjA5WhcNMTkwOTI3MTA1NjA5WjAyMRswGQYDVQQKExJUaGUg +VGhpbmdzIE5ldHdvcmsxEzARBgNVBAMTCmRldiBicm9rZXIwWTATBgcqhkjOPQIB +BggqhkjOPQMBBwNCAARKJiC1vanAuqbOtVYPwSM8D6oSseAOeUra5dHiXtp72d59 ++kgaEfa6Zgp0KSaes+D2fxE+RJ4G6v7DYbKgWOGto3gwdjAOBgNVHQ8BAf8EBAMC +AqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMB +Af8wNAYDVR0RBC0wK4IGYnJva2Vygglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA +AAAAAAAAAAEwCgYIKoZIzj0EAwIDSQAwRgIhAPhJ2I1f08cua9ZywtsbtadYdVgv +tzdKWtGdmlATCfkcAiEAvgIXD5SO4HgVm+nByFfCI4DSJbjSbON4IuOkDDeSTeg= -----END CERTIFICATE----- diff --git a/.env/discovery/server.cert b/.env/discovery/server.cert index 8828e1abf..5a61a0b5e 100644 --- a/.env/discovery/server.cert +++ b/.env/discovery/server.cert @@ -1,13 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIB4zCCAYmgAwIBAgIQCAY2zSW5hUFebALOGEj/HTAKBggqhkjOPQQDAjA1MRsw +MIIB4zCCAYmgAwIBAgIQeT/AP7jodA2aPfeYbKfiXzAKBggqhkjOPQQDAjA1MRsw GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmsxFjAUBgNVBAMTDWRldiBkaXNjb3Zl -cnkwHhcNMTcxMjA1MTAzMjU4WhcNMTgxMjA1MTAzMjU4WjA1MRswGQYDVQQKExJU +cnkwHhcNMTgwOTI3MTA1NjA4WhcNMTkwOTI3MTA1NjA4WjA1MRswGQYDVQQKExJU aGUgVGhpbmdzIE5ldHdvcmsxFjAUBgNVBAMTDWRldiBkaXNjb3ZlcnkwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAAQZ3BU/el/lQZz4hJ2/NAb+oOPNtCJm5dV+WbIS Q+RRIXHKo9G6mDMEhuwrFA4LyikZ1yftNxKoJR7LIJ3wkdR+o3sweTAOBgNVHQ8B Af8EBAMCAqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB /wQFMAMBAf8wNwYDVR0RBDAwLoIJZGlzY292ZXJ5gglsb2NhbGhvc3SHBH8AAAGH -EAAAAAAAAAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDSAAwRQIgCdCD2Ccb5n231cPP -Fgd9Xi2xrOb3sK/f+m0klDWWP/oCIQDqY1WmnnIxFglfP9ok2Xkk3738qfZXyTgI -IE+kjNUapQ== +EAAAAAAAAAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDSAAwRQIhAMVbCPtdKYyzJs/1 +Idt+pqA42Ip/iRT9ptv22jMhd5imAiA1fjIURLDQc/kzSO/3nrSfrVBaGfcJXK1u +lsrRKMoY0w== -----END CERTIFICATE----- diff --git a/.env/gencerts.sh b/.env/gencerts.sh index f6d8de1f4..5b9524487 100755 --- a/.env/gencerts.sh +++ b/.env/gencerts.sh @@ -4,5 +4,11 @@ env=$(dirname $0) for service in discovery router broker networkserver handler do - ttn $service gen-cert --config "$env/$service/dev.yml" --key-dir "$env/$service" "localhost" "127.0.0.1" "::1" + ttn $service gen-cert --config "$env/$service/dev.yml" --key-dir "$env/$service" "$service" "localhost" "127.0.0.1" "::1" done + +pushd "$env/mqtt" > /dev/null + +go run generate.go + +popd > /dev/null diff --git a/.env/handler/server.cert b/.env/handler/server.cert index 605aa0e86..e837d59f9 100644 --- a/.env/handler/server.cert +++ b/.env/handler/server.cert @@ -1,13 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIB3jCCAYSgAwIBAgIRAKckPrHkSq1X+ZAUt1AEnpowCgYIKoZIzj0EAwIwMzEb -MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMRQwEgYDVQQDEwtkZXYgaGFuZGxl -cjAeFw0xNzEyMDUxMDMyNTlaFw0xODEyMDUxMDMyNTlaMDMxGzAZBgNVBAoTElRo -ZSBUaGluZ3MgTmV0d29yazEUMBIGA1UEAxMLZGV2IGhhbmRsZXIwWTATBgcqhkjO -PQIBBggqhkjOPQMBBwNCAASJdta/JiM4w/h5tNi1W90iwHBL5TeVpaW1HtWjHj6R -BZwYssSD6y4j1JFnIor5+uHD0k8ptU0iAS/9LnRgp8wRo3kwdzAOBgNVHQ8BAf8E -BAMCAqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQF -MAMBAf8wNQYDVR0RBC4wLIIHaGFuZGxlcoIJbG9jYWxob3N0hwR/AAABhxAAAAAA -AAAAAAAAAAAAAAABMAoGCCqGSM49BAMCA0gAMEUCIHX7j/63NchWx2FE/OQ79pej -I971zOnT5ijhsHr3aOehAiEAzxQBqLUXjIULbtaoahd/DuFCMncfqtWMxS16XwD9 -dnQ= +MIIB3DCCAYOgAwIBAgIQZvzP/XEG934mVuYXxIDiDjAKBggqhkjOPQQDAjAzMRsw +GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmsxFDASBgNVBAMTC2RldiBoYW5kbGVy +MB4XDTE4MDkyNzEwNTYwOVoXDTE5MDkyNzEwNTYwOVowMzEbMBkGA1UEChMSVGhl +IFRoaW5ncyBOZXR3b3JrMRQwEgYDVQQDEwtkZXYgaGFuZGxlcjBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABIl21r8mIzjD+Hm02LVb3SLAcEvlN5WlpbUe1aMePpEF +nBiyxIPrLiPUkWciivn64cPSTym1TSIBL/0udGCnzBGjeTB3MA4GA1UdDwEB/wQE +AwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUw +AwEB/zA1BgNVHREELjAsggdoYW5kbGVygglsb2NhbGhvc3SHBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDRwAwRAIgA8JsGTiUR4S8lBgheN+5NdNC +qJP84AQKLIIS6fPU1/0CIEZ/b7/OkYYiiTwG5wntXx/O7aj+icVrLhSKAYz+vRZo -----END CERTIFICATE----- diff --git a/.env/mqtt/ca-key.pem b/.env/mqtt/ca-key.pem index f859933f2..176bd8529 100644 --- a/.env/mqtt/ca-key.pem +++ b/.env/mqtt/ca-key.pem @@ -1,51 +1,51 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/.env/mqtt/ca.pem b/.env/mqtt/ca.pem index d5b476e21..7c9bf6740 100644 --- a/.env/mqtt/ca.pem +++ b/.env/mqtt/ca.pem @@ -1,30 +1,30 @@ -----BEGIN CERTIFICATE----- -MIIFITCCAwmgAwIBAgIRAN09cDFJLEh3IbYt+vEklwswDQYJKoZIhvcNAQELBQAw +MIIFITCCAwmgAwIBAgIRAPsGNI/6/jsfHYxm4oNPxc8wDQYJKoZIhvcNAQELBQAw LzEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMRAwDgYDVQQDEwdUZXN0IENB -MB4XDTE3MDgyMTE1MDI0NVoXDTE4MDgyMTE1MDI0NVowLzEbMBkGA1UEChMSVGhl +MB4XDTE4MDkxMDA5NTA1MVoXDTE5MDkxMDA5NTA1MVowLzEbMBkGA1UEChMSVGhl IFRoaW5ncyBOZXR3b3JrMRAwDgYDVQQDEwdUZXN0IENBMIICIjANBgkqhkiG9w0B -AQEFAAOCAg8AMIICCgKCAgEAwhGOV45BMvmD96ZzfBWSTI7oeHPJi55BHTZWIovo -H74XEjPe/6/v880AAIU80K5iMEjmlSI6et2NAMsJzOiiiKgFUXFk0rnLv1NRPHb6 -csbZnv6PMiD/zqbtEtofTSNM/bBlV/iC3rmwl2ZRbpZldwEqfc2TJ+2xILv+h4id -7QGu6GDhpiHpjLuzbbRGP7c45Ej13K72qAxsS3C7lXUJKyFMwe2F4NshdP6nFSo0 -+lieURMEDyvjwJjVof6DPAGgdeYRG3DuP5vH02sL+jZgsxi2wLx0oy3WIRBO1tIp -OFKHnQ86aqDO4lVxv9DO99mntxPCkfJK4EnU5ugH9Ox204bKm0rMXmm9rVu6Cj8S -YjhP08EBQ94QWnLuejEpe7mpyqj4CvfpEqPWx2tWfCmAe+v6U/zaTZlLoQPg/eeS -BNE/g+BmXPiZFdTSJtI/H9mykZwXPiTI7/ML++wSErIZvTq+NrVA7KsNHBmMLOHk -06L6/568tgk2zsd4YrNcGamDEVHfG8CDBpUk29VXXJKLZ5P8J4J8tNvjTP7VCsmy -YQEw1M3ouTUYpgbq/8fvHi51QY56qU/Zzd/zPucV7AOAQQwNkvfx9Ns0pKUezb7N -PvBZoGycF2z5e3025u5WFUrdsLPMV5sBLVwe23krUMyDnAJf+GuQl3rp0Zq99x+h -0JECAwEAAaM4MDYwDgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMB -MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBACxndiLUu8r5Cpv0 -FN3L8JlUUQUzCqIlAT8cnW+7yOTuD3nilCYhzoPhShsG5GUqxE4nelWR7TAbYxYW -x6fGTmC2pbkOsQEAKAjPvsEaTqhqxAofjWu6XS8UamgondLXDpyDBafRrXqlVEEp -KaNF54LhuLycKZm/TrO1vrpGXehpojCNtaH54X3vn7gAlJo7QoxRzHmBMRSKUlSq -Gb1kEFm8K8f8adpsNtbGZSnSSyAmv4+c+xZsyjTDXnRi9cQfVebZsd8n1lI4m3WS -byYkq04RYLQhDnb9vrGBWNHLS5cLTHJLjZ3gxlY9Ro0kGsnytwb8+SABj3HhgC6u -NcPs0gAP62B63s/fbonVSdmKebX2WMHj/99kl2Y2lwxRQSyYAPXbl7iciCfZnR62 -ufGEJPNn5ChXlM+oeYR/CWit03drgDbZhlQjlNknoWoSUxN4Z4s8dmL9WwZDrAOy -VaXQzkVRPKUUjdMfU5oWgUeXSN95NFbXVFZYdgvSB5qOJsrXmPNLxyeZZZCF7xnm -5Kb1Xi6PGmrqKl8KMfnQpzcoP65jOsXjeapaoA73PZceN/ZIPD2ciXUEUqkKsb8s -4vTck/SFUXlFKpEL2068ka+S8H11i+ygNyf8bq2oO5TNbxffHzGZ00AzJ1+R42R9 -I+urAYo4GrHake6OAVpoEW1QWW00 +AQEFAAOCAg8AMIICCgKCAgEA5wnYv5E7aTxjeVruC6LIAz37dZikc9chdwurpD1F +rHd4lnhI9flSvfhVi0nPFmhlVM7E2ptfxrvLY9vAPWLAMK0+wwSOSZ2Unc7ONCmh +n8W5wGjXrAjcNFmG0Kz7tXljcigDgzKMOL7UYruEVy99uplNR3bWTbtQAXwsUkmP +vVJ+FFRIlpDHPZhaETrDE/5pvDUV3blbddoPm+f5Lt94/5ykBt488CQawD3vdbzX +YW+/Z1tmCz4CoZpODKziecaXlhHpEV6n61ZBo3zPPzrWF8zrUWWJSrajHkMm695k +9YIWarEVUMnyE8MCzV9KBXqdXeWEjuThB3Kbwh0+pdbsmZwuxPvK+cZewwwifRxm +hPdesMSsUtzc6Fd7BgiLDdHvBt+7E1bb9AfcyUgMfZoqzqO9dmIhymhZ5ZFAv0yV +ef58GiyqYIWYpPnTMazDa5FVgXhvxnxAwRTsvRWMHh/goVZU96px/q60n3wrvKQM +G20bh4tHm2BH9Q9WulFFODDRMihERwbWTj8JI6iv2+WXnaaJnjDmfPa/lZUbxm50 +9PZ70nBusFdRt2/PgF/CWua49XqwTckN0XnInHfTCAaTtZu/n0LRCants9LybptH +f5kuIh7YFEPD5tFrroK1jlMuhBVnbvegtqI+zJxLoK0/T2wKMOanKBM1I4t2Fb5x +iRcCAwEAAaM4MDYwDgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMB +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAJ4SsVCe+fw/t95V +lreLVPfOSQWYSMq0Zh+mbR8rfX4QYMoVvycsbjd0QTAOcymuEtENIbejSdQCIUlu +ktE+mPURD+vWY+H9Lr+gUs7zK0jX93vC7UbsXYZ7TkUIa1tGH8saAuuWKpL+peJh +uxF5xijZPbzta9L9BRPWCB5P9xHXf9v3x4bL+RVzTSowaXhYRZkSe8RUBwbcX5W+ +vsttUYyfPdWBhuYWtTlFXMTV5G74t65i5NBicaRYQyI873LjWjH5JHSMvpwVB8mx +734HZc7ylxGfx7+bj86q7QOJ85e+JcnjLY1vVO2b9JrtMtFDtQpe5uCL+NUATIW6 +VSywT9NhGRRO1aRXabXfj/YylGRS1K9i/UocC4gdVLbGw9IiLhtWx2rta2siepQx +DxPukr57aHDk8Xxh2UKamSp0gsVDJ4JctyH461lsSZhEiX4PXH9aOE8NrmO8dUqn +hR+Y/7OeN+XyHdmf/NVW4enufe/iiNq063hc3Ic8nGsY5Te5KmORr7Z359neogy4 +UwOMn+7T0ML3yeQcClDDYd3R8UT3vLMhfhvXUb1oCS0c7Uj+WN440CpEKXo0Gu65 +nb4uQQVpM14ZHtADejNJHD3j3Fu4oektlsDwKIGfyZDtBF6VFt7CiaVx7s0IkT4J +ryd+/zTMfE9U626KeauS9k6lCCB4 -----END CERTIFICATE----- diff --git a/.env/mqtt/cert.pem b/.env/mqtt/cert.pem index a6bcc50f0..428149b86 100644 --- a/.env/mqtt/cert.pem +++ b/.env/mqtt/cert.pem @@ -1,25 +1,25 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/.env/mqtt/key.pem b/.env/mqtt/key.pem index dfee50643..fb9be2bfa 100644 --- a/.env/mqtt/key.pem +++ b/.env/mqtt/key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/.env/networkserver/server.cert b/.env/networkserver/server.cert index 13c74f254..30a55ddcb 100644 --- a/.env/networkserver/server.cert +++ b/.env/networkserver/server.cert @@ -1,13 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIB6TCCAY+gAwIBAgIQKUqRbh3LvaSiQhHklOgZyTAKBggqhkjOPQQDAjA2MRsw -GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmsxFzAVBgNVBAMTDiBuZXR3b3Jrc2Vy -dmVyMB4XDTE3MTIwNTEwMzI1OVoXDTE4MTIwNTEwMzI1OVowNjEbMBkGA1UEChMS -VGhlIFRoaW5ncyBOZXR3b3JrMRcwFQYDVQQDEw4gbmV0d29ya3NlcnZlcjBZMBMG -ByqGSM49AgEGCCqGSM49AwEHA0IABLHyA2+FWvaHG8BrnN++QccOSAkwl2etJm4H -cdyUslptfQ1uXYoczxawdBM7vpWpPda0xGqJ/VjXFYm4RTRucDKjfzB9MA4GA1Ud -DwEB/wQEAwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0T -AQH/BAUwAwEB/zA7BgNVHREENDAygglsb2NhbGhvc3SCDW5ldHdvcmtzZXJ2ZXKH -BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDSAAwRQIgdWGbN4y7 -bDq47M0tbnlEgDM8WLUk+75Lx/o63aCW+LcCIQDLOIulvrHxcF9a/9Br25j4ReKs -9S3iuClBoK0lK6TZjQ== +MIIB6jCCAZCgAwIBAgIRAKjF+axVFtzJcd+IPCoDWocwCgYIKoZIzj0EAwIwNjEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMRcwFQYDVQQDEw4gbmV0d29ya3Nl +cnZlcjAeFw0xODA5MjcxMDU2MDlaFw0xOTA5MjcxMDU2MDlaMDYxGzAZBgNVBAoT +ElRoZSBUaGluZ3MgTmV0d29yazEXMBUGA1UEAxMOIG5ldHdvcmtzZXJ2ZXIwWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAASx8gNvhVr2hxvAa5zfvkHHDkgJMJdnrSZu +B3HclLJabX0Nbl2KHM8WsHQTO76VqT3WtMRqif1Y1xWJuEU0bnAyo38wfTAOBgNV +HQ8BAf8EBAMCAqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1Ud +EwEB/wQFMAMBAf8wOwYDVR0RBDQwMoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVy +hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMAoGCCqGSM49BAMCA0gAMEUCIQDiXQad +tPHppylaBeYx6hWHbsQ5arSoa0eYRC+XwfiUigIgR4K2abz4HOgrp7jU1qHn2s+V +cmlWJe4RNTsBGI3zCZY= -----END CERTIFICATE----- diff --git a/.env/router/server.cert b/.env/router/server.cert index b2b2ffd54..57e3e94bd 100644 --- a/.env/router/server.cert +++ b/.env/router/server.cert @@ -1,12 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIB2zCCAYCgAwIBAgIQYcoPFRqb7HVDC0JIsmddEjAKBggqhkjOPQQDAjAyMRsw -GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmsxEzARBgNVBAMTCmRldiByb3V0ZXIw -HhcNMTcxMjA1MTAzMjU5WhcNMTgxMjA1MTAzMjU5WjAyMRswGQYDVQQKExJUaGUg -VGhpbmdzIE5ldHdvcmsxEzARBgNVBAMTCmRldiByb3V0ZXIwWTATBgcqhkjOPQIB -BggqhkjOPQMBBwNCAAQrcb9XbpbPrXWn8Qh8kRNxzt+Y3BpxyVgRkeST30VcppXA -v83B64oqklFFTr9BmOSsSXY1iKxcDUV+25TEkuCro3gwdjAOBgNVHQ8BAf8EBAMC -AqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMB -Af8wNAYDVR0RBC0wK4IJbG9jYWxob3N0ggZyb3V0ZXKHBH8AAAGHEAAAAAAAAAAA -AAAAAAAAAAEwCgYIKoZIzj0EAwIDSQAwRgIhAKAcQlgyJTGIYLrj5htTm7ciKmnD -LJeBIWbmFvP+Srf7AiEAtVVvqsmazdK1bYWst5wXaa69Xhr0tRKZ7VZ9yLCqwu8= +MIIB3DCCAYGgAwIBAgIRAI8kwcLmUSPqInw9/FGPfOUwCgYIKoZIzj0EAwIwMjEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMRMwEQYDVQQDEwpkZXYgcm91dGVy +MB4XDTE4MDkyNzEwNTYwOFoXDTE5MDkyNzEwNTYwOFowMjEbMBkGA1UEChMSVGhl +IFRoaW5ncyBOZXR3b3JrMRMwEQYDVQQDEwpkZXYgcm91dGVyMFkwEwYHKoZIzj0C +AQYIKoZIzj0DAQcDQgAEK3G/V26Wz611p/EIfJETcc7fmNwacclYEZHkk99FXKaV +wL/NweuKKpJRRU6/QZjkrEl2NYisXA1FftuUxJLgq6N4MHYwDgYDVR0PAQH/BAQD +AgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTAD +AQH/MDQGA1UdEQQtMCuCCWxvY2FsaG9zdIIGcm91dGVyhwR/AAABhxAAAAAAAAAA +AAAAAAAAAAABMAoGCCqGSM49BAMCA0kAMEYCIQDYyjCLkUQbIRnBKtzleXQJdOur +VefiMujx2U2GQyJAtAIhAJpWnE7/YUf4yWzlD1vOVNn4luLCKnT9d7c6rBiCNb6q -----END CERTIFICATE----- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8bf93c1b7..281c211bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ before_script: tests: stage: test - image: golang:latest + image: golang:1.11 services: - thethingsnetwork/rabbitmq - redis @@ -37,7 +37,7 @@ tests: binaries: stage: build - image: golang:latest + image: golang:1.11 script: - mkdir release - export CI_BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) @@ -57,7 +57,7 @@ sign: - master@thethingsnetwork/ttn - develop@thethingsnetwork/ttn stage: sign - image: golang:latest + image: golang:1.11 script: - pushd release - shasum -a 256 $(ls) > checksums diff --git a/.travis.yml b/.travis.yml index d44257b64..c281483a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ services: - docker go: - - 1.8 + - "1.11" install: - make deps diff --git a/Dockerfile b/Dockerfile index d4183019c..bd67180f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine +FROM alpine:3.8 RUN apk --update --no-cache add ca-certificates ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn RUN chmod 755 /usr/local/bin/ttn diff --git a/Makefile b/Makefile index 05eeaff50..5baf6fd30 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,6 @@ build-deps: deps: build-deps govendor sync -v - git checkout vendor/github.com/brocaar/lorawan/band/band_au915_928.go dev-deps: deps @command -v protoc-gen-grpc-gateway > /dev/null || go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway diff --git a/README.md b/README.md index 312e4926e..3f2bd7930 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Although we're all about building an open, public network, we understand that so First, you'll have to prepare your development environment. Follow the steps below to set up your development machine. -1. Make sure you have [Go](https://golang.org) installed (version 1.8 or later). +1. Make sure you have [Go](https://golang.org) installed (recommended version 1.11, version 1.8 or later is known to work). 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/) diff --git a/api/pool/pool_test.go b/api/pool/pool_test.go index a7e8a6651..d47e70f88 100644 --- a/api/pool/pool_test.go +++ b/api/pool/pool_test.go @@ -4,22 +4,53 @@ package pool import ( + "bytes" "context" + "fmt" "net" + "runtime" "testing" "time" "github.com/TheThingsNetwork/go-utils/log" + wrap "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/api/health" - "github.com/htdvisser/grpc-testing/test" + apex "github.com/apex/log" + "github.com/apex/log/handlers/text" . "github.com/smartystreets/assertions" "google.golang.org/grpc" ) +type Logger struct { + logs bytes.Buffer + log.Interface +} + +func (l *Logger) Print(t *testing.T) { + var loc string + if _, file, line, ok := runtime.Caller(1); ok { + loc = fmt.Sprintf("%s:%d", file, line) + } + + if l.logs.Len() > 0 { + t.Log("Logs " + loc + ": \n" + l.logs.String()) + l.logs.Reset() + } +} + +func NewLogger() *Logger { + l := new(Logger) + l.Interface = wrap.Wrap(&apex.Logger{ + Handler: text.New(&l.logs), + Level: apex.DebugLevel, + }) + return l +} + func TestPool(t *testing.T) { a := New(t) - testLogger := test.NewLogger() + testLogger := NewLogger() log.Set(testLogger) defer testLogger.Print(t) diff --git a/cmd/docs/README.md b/cmd/docs/README.md index e8f5bc10d..e79166dfe 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -5,19 +5,24 @@ The Things Network's backend servers. **Options** ``` - --allow-insecure Allow insecure fallback if TLS unavailable - --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 (default true) + --allow-insecure Allow insecure fallback if TLS unavailable + --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 + --elasticsearch-password string Password used to connect to the Elasticsearch server + --elasticsearch-prefix string Prefix of the ES index for logging - changes the index from "-" to "--" + --elasticsearch-username string Username used to connect to the Elasticsearch server + --eu-rx2-dr int RX2 data rate for the EU band (SF12=0,SF9=3) (default 3) + --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 + --min-tls-version string Minimum TLS version + --no-cli-logs Disable CLI logs + --public Announce this component as part of The Things Network (public community network) + --tls Use TLS (default true) ``` @@ -66,15 +71,15 @@ ttn broker register prefix registers a prefix to this Broker **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-v2]) - --redis-address string Redis server and port (default "localhost:6379") - --redis-db int Redis database - --redis-password string Redis password - --server-address string The IP address to listen for communication (default "0.0.0.0") - --server-port int The port for communication (default 1900) + --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 strings Auth servers that are allowed to manage this network (default [ttn-account-v2]) + --redis-address string Redis server and port (default "localhost:6379") + --redis-db int Redis database + --redis-password string Redis password + --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 @@ -98,25 +103,25 @@ ttn gen-keypair generates a public/private keypair **Options** ``` - --amqp-address string AMQP host and port. Leave empty to disable AMQP - --amqp-address-announce string AMQP address to announce (takes value of server-address-announce if empty while enabled) - --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") - --extra-device-attributes stringSlice Extra device attributes to be whitelisted - --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-address-announce string MQTT address to announce (takes value of server-address-announce if empty while enabled) - --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 - --redis-password string Redis password - --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) + --amqp-address string AMQP host and port. Leave empty to disable AMQP + --amqp-address-announce string AMQP address to announce (takes value of server-address-announce if empty while enabled) + --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") + --extra-device-attributes strings Extra device attributes to be whitelisted + --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-address-announce string MQTT address to announce (takes value of server-address-announce if empty while enabled) + --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 + --redis-password string Redis password + --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 diff --git a/cmd/docs/generate.go b/cmd/docs/generate.go index c96676a8a..7bb336511 100644 --- a/cmd/docs/generate.go +++ b/cmd/docs/generate.go @@ -11,9 +11,9 @@ import ( ) func main() { - fmt.Println(`# API Reference - -The Things Network's backend servers. -`) + fmt.Println("# API Reference") + fmt.Println() + fmt.Println("The Things Network's backend servers.") + fmt.Println() fmt.Print(docs.Generate(cmd.RootCmd)) } diff --git a/cmd/root.go b/cmd/root.go index 05fdfb9c4..da063dfa1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,6 +16,7 @@ import ( ttnlog "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/go-utils/log/grpc" + promlog "github.com/TheThingsNetwork/go-utils/log/prometheus" "github.com/TheThingsNetwork/ttn/api" esHandler "github.com/TheThingsNetwork/ttn/utils/elasticsearch/handler" "github.com/apex/log" @@ -92,9 +93,9 @@ var RootCmd = &cobra.Command{ } // Set the API/gRPC logger - ctx = apex.Wrap(&log.Logger{ + ctx = promlog.Wrap(apex.Wrap(&log.Logger{ Handler: multiHandler.New(logHandlers...), - }) + })) ttnlog.Set(ctx) grpclog.SetLogger(grpc.Wrap(ttnlog.Get())) @@ -163,6 +164,7 @@ func init() { } RootCmd.PersistentFlags().Bool("tls", true, "Use TLS") + RootCmd.PersistentFlags().String("min-tls-version", "", "Minimum TLS version") RootCmd.PersistentFlags().Bool("allow-insecure", false, "Allow insecure fallback if TLS unavailable") RootCmd.PersistentFlags().String("key-dir", path.Clean(dir+"/.ttn/"), "The directory where public/private keys are stored") diff --git a/core/band/band.go b/core/band/band.go index c5677c765..9eeeb5655 100644 --- a/core/band/band.go +++ b/core/band/band.go @@ -169,6 +169,22 @@ func Get(region string) (frequencyPlan FrequencyPlan, err error) { frequencyPlan.CFList = &lorawan.CFList{922700000, 922900000, 923100000, 923300000, 0} case pb_lorawan.FrequencyPlan_IN_865_867.String(): frequencyPlan.Band, err = lora.GetConfig(lora.IN_865_867, false, lorawan.DwellTimeNoLimit) + case pb_lorawan.FrequencyPlan_RU_864_870.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.RU_864_870, false, lorawan.DwellTimeNoLimit) + // Here channels from recommended list for Russia are set which are used by LoRaWAN networks in Russia + // Recommended frequency plan includes extra channels next to the default channels: + frequencyPlan.UplinkChannels = []lora.Channel{ + lora.Channel{Frequency: 868900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 869100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 864100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 864300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 864500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 864700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 864900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + } + frequencyPlan.DownlinkChannels = frequencyPlan.UplinkChannels + frequencyPlan.CFList = &lorawan.CFList{864100000, 864300000, 864500000, 864700000, 864900000} + frequencyPlan.ADR = &ADRConfig{MinDataRate: 0, MaxDataRate: 5, MinTXPower: 2, MaxTXPower: 14, StepTXPower: 3} default: err = errors.NewErrInvalidArgument("Frequency Band", "unknown") } @@ -193,6 +209,7 @@ func init() { pb_lorawan.FrequencyPlan_KR_920_923, pb_lorawan.FrequencyPlan_AU_915_928, pb_lorawan.FrequencyPlan_CN_470_510, + pb_lorawan.FrequencyPlan_RU_864_870, } { region := r.String() frequencyPlans[region], _ = Get(region) diff --git a/core/band/band_test.go b/core/band/band_test.go index 30c2c50fd..3bd33d509 100644 --- a/core/band/band_test.go +++ b/core/band/band_test.go @@ -23,6 +23,7 @@ func TestGuess(t *testing.T) { a.So(Guess(923600000), ShouldEqual, "AS_923_925") a.So(Guess(922100000), ShouldEqual, "KR_920_923") a.So(Guess(865062500), ShouldEqual, "IN_865_867") + a.So(Guess(868900000), ShouldEqual, "RU_864_870") a.So(Guess(922100001), ShouldEqual, "") // Not allowed } @@ -107,6 +108,13 @@ func TestGet(t *testing.T) { a.So(fp.ADR, ShouldBeNil) } + { + fp, err := Get("RU_864_870") + a.So(err, ShouldBeNil) + a.So(fp.CFList, ShouldNotBeNil) + a.So(fp.ADR, ShouldNotBeNil) + } + } func TestGetDataRate(t *testing.T) { diff --git a/core/component/auth.go b/core/component/auth.go index 341c7bf39..44a2ee76b 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -153,7 +153,22 @@ func (c *Component) initTLS() error { return err } - c.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} + c.tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cer}, + } + switch c.Config.MinTLSVersion { + case "": + // use Go default + case "1.0": + c.tlsConfig.MinVersion = tls.VersionTLS10 + case "1.1": + c.tlsConfig.MinVersion = tls.VersionTLS11 + case "1.2": + c.tlsConfig.MinVersion = tls.VersionTLS12 + default: + c.Ctx.Warnf("Could not recognize TLS version %s", c.Config.MinTLSVersion) + } + return nil } diff --git a/core/component/component.go b/core/component/component.go index 49968716e..1c8649068 100644 --- a/core/component/component.go +++ b/core/component/component.go @@ -72,6 +72,8 @@ func New(ctx ttnlog.Interface, serviceName string, announcedAddress string) (*Co Pool: pool.NewPool(context.Background(), pool.DefaultDialOptions...), } + info.WithLabelValues(viper.GetString("buildDate"), viper.GetString("gitCommit"), viper.GetString("id"), viper.GetString("version")).Set(1) + if err := component.initialize(); err != nil { return nil, err } diff --git a/core/component/config.go b/core/component/config.go index 056287fc3..6f51d4e0e 100644 --- a/core/component/config.go +++ b/core/component/config.go @@ -9,16 +9,18 @@ import ( // Config is the configuration for this component type Config struct { - AuthServers map[string]string - KeyDir string - UseTLS bool + AuthServers map[string]string + KeyDir string + UseTLS bool + MinTLSVersion string } // ConfigFromViper imports configuration from Viper func ConfigFromViper() Config { return Config{ - AuthServers: viper.GetStringMapString("auth-servers"), - KeyDir: viper.GetString("key-dir"), - UseTLS: viper.GetBool("tls"), + AuthServers: viper.GetStringMapString("auth-servers"), + KeyDir: viper.GetString("key-dir"), + UseTLS: viper.GetBool("tls"), + MinTLSVersion: viper.GetString("min-tls-version"), } } diff --git a/core/component/metrics.go b/core/component/metrics.go index 837187e1a..911b5520f 100644 --- a/core/component/metrics.go +++ b/core/component/metrics.go @@ -10,6 +10,16 @@ import ( "github.com/prometheus/client_golang/prometheus" ) +var info = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "ttn", + Name: "info", + Help: "Information about the TTN environment.", + }, []string{ + "build_date", "git_commit", "id", "version", + }, +) + var receivedCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: "ttn", @@ -47,6 +57,7 @@ var certificateExpiry = prometheus.NewGaugeVec(prometheus.GaugeOpts{ }, []string{"fingerprint"}) func init() { + prometheus.MustRegister(info) prometheus.MustRegister(receivedCounter) prometheus.MustRegister(handledCounter) prometheus.MustRegister(handledBytes) diff --git a/core/handler/application/store.go b/core/handler/application/store.go index eca61a47c..eaa9ddfc8 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -50,7 +50,7 @@ type RedisApplicationStore struct { // Count all applications in the store func (s *RedisApplicationStore) Count() (int, error) { - return s.store.Count("") + return s.store.Count("", nil) } // List all Applications diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 8c36b9d15..cb5c50b05 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -57,6 +57,8 @@ func NewRedisDeviceStore(client *redis.Client, prefix string) *RedisDeviceStore } queues := storage.NewRedisQueueStore(client, prefix+":"+redisDownlinkQueuePrefix) s := &RedisDeviceStore{ + client: client, + prefix: prefix, store: store, queues: queues, } @@ -68,23 +70,39 @@ func NewRedisDeviceStore(client *redis.Client, prefix string) *RedisDeviceStore // RedisDeviceStore stores Devices in Redis. // - Devices are stored as a Hash type RedisDeviceStore struct { + prefix string + client *redis.Client store *storage.RedisMapStore queues *storage.RedisQueueStore builtinAttibutes []string // sorted } +var listCacheTTL = 5 * time.Minute + +func (s *RedisDeviceStore) listResultCacheKey(appID string) string { + return fmt.Sprintf("%s:_index_:%s", s.prefix, appID) +} + // Count all devices in the store func (s *RedisDeviceStore) Count() (int, error) { - return s.store.Count("") + opts := new(storage.ListOptions) + opts.UseCache(s.listResultCacheKey("_all_"), listCacheTTL) + return s.store.Count("", opts) } // CountForApp counts all devices for an Application func (s *RedisDeviceStore) CountForApp(appID string) (int, error) { - return s.store.Count(fmt.Sprintf("%s:*", appID)) + opts := new(storage.ListOptions) + opts.UseCache(s.listResultCacheKey(appID), listCacheTTL) + return s.store.Count(fmt.Sprintf("%s:*", appID), opts) } // List all Devices func (s *RedisDeviceStore) List(opts *storage.ListOptions) ([]*Device, error) { + if opts == nil { + opts = new(storage.ListOptions) + } + opts.UseCache(s.listResultCacheKey("_all_"), listCacheTTL) devicesI, err := s.store.List("", opts) if err != nil { return nil, err @@ -100,6 +118,10 @@ func (s *RedisDeviceStore) List(opts *storage.ListOptions) ([]*Device, error) { // ListForApp lists all devices for a specific Application func (s *RedisDeviceStore) ListForApp(appID string, opts *storage.ListOptions) ([]*Device, error) { + if opts == nil { + opts = new(storage.ListOptions) + } + opts.UseCache(s.listResultCacheKey(appID), listCacheTTL) devicesI, err := s.store.List(fmt.Sprintf("%s:*", appID), opts) if err != nil { return nil, err @@ -162,6 +184,9 @@ func (s *RedisDeviceStore) Set(new *Device, properties ...string) (err error) { if err != nil { return } + if new.old == nil { + s.client.Del(s.listResultCacheKey(new.AppID), s.listResultCacheKey("_all_")).Err() + } return nil } @@ -171,7 +196,12 @@ func (s *RedisDeviceStore) Delete(appID, devID string) error { if err := s.queues.Delete(key); err != nil { return err } - return s.store.Delete(key) + err := s.store.Delete(key) + if err != nil { + return err + } + s.client.Del(s.listResultCacheKey(appID), s.listResultCacheKey("_all_")).Err() + return nil } // AddBuiltinAttribute adds builtin device attributes to the list. diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 5ff6fc0db..be8900b74 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -126,6 +126,11 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb_handler.DeviceIde pbDev.GetLoRaWANDevice().FCntDown = nsDev.FCntDown pbDev.GetLoRaWANDevice().LastSeen = nsDev.LastSeen + if dev := pbDev.GetLoRaWANDevice(); dev != nil { + dev.UsedAppNonces = nil + dev.UsedDevNonces = nil + } + return pbDev, nil } @@ -323,6 +328,13 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb_ha res.Devices = append(res.Devices, dev.ToPb()) } + for _, dev := range res.Devices { + if dev := dev.GetLoRaWANDevice(); dev != nil { + dev.UsedAppNonces = nil + dev.UsedDevNonces = nil + } + } + total, selected := opts.GetTotalAndSelected() header := metadata.Pairs( "total", strconv.FormatUint(total, 10), diff --git a/core/networkserver/adr.go b/core/networkserver/adr.go index fbaf73b66..4f7680630 100644 --- a/core/networkserver/adr.go +++ b/core/networkserver/adr.go @@ -8,6 +8,7 @@ import ( "sort" pb_broker "github.com/TheThingsNetwork/api/broker" + "github.com/TheThingsNetwork/api/logfields" pb_lorawan "github.com/TheThingsNetwork/api/protocol/lorawan" "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/core/band" @@ -44,6 +45,7 @@ func lossPercentage(frames []*device.Frame) int { const ScheduleMACEvent = "schedule mac command" func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMessage, dev *device.Device) error { + ctx := n.Ctx.WithFields(logfields.ForMessage(message)) lorawanUplinkMAC := message.GetMessage().GetLoRaWAN().GetMACPayload() lorawanDownlinkMAC := message.GetResponseTemplate().GetMessage().GetLoRaWAN().GetMACPayload() @@ -70,7 +72,7 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes SNR: bestSNR(message.GetGatewayMetadata()), GatewayCount: uint32(len(message.GatewayMetadata)), }); err != nil { - n.Ctx.WithError(err).Error("Could not push frame for device") + ctx.WithError(err).Error("Could not push frame for device") } frames, _ := history.Get() @@ -92,6 +94,7 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes scheduleADR = true forceADR = true message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "initial") + ctx.Debug("Schedule ADR [initial]") } } @@ -100,12 +103,14 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes dev.ADR.DataRate = dataRate scheduleADR = true message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "optimize") + ctx.Debug("Schedule ADR [optimize]") } if lorawanUplinkMAC.ADRAckReq { scheduleADR = true lorawanDownlinkMAC.Ack = true message.Trace = message.Trace.WithEvent("set ack", "reason", "adr-ack-req") + ctx.Debug("Schedule ADR [adr-ack-req]") } if scheduleADR { @@ -114,6 +119,7 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes if len(frames) >= device.FramesHistorySize { forceADR = true message.Trace = message.Trace.WithEvent(ScheduleMACEvent, macCMD, "link-adr", "reason", "avoid high sf") + ctx.Debug("Schedule ADR [avoid high sf]") } } } @@ -124,6 +130,7 @@ func (n *networkServer) handleUplinkADR(message *pb_broker.DeduplicatedUplinkMes err := n.setADR(lorawanDownlinkMAC, dev) if err != nil { message.Trace = message.Trace.WithEvent("mac error", macCMD, "link-adr", "error", err.Error()) + ctx.WithError(err).Warn("Could not set ADR") err = nil } } @@ -143,14 +150,27 @@ func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) e return errors.New("too many failed ADR requests") } + ctx := n.Ctx.WithFields(log.Fields{ + "AppEUI": dev.AppEUI, + "DevEUI": dev.DevEUI, + "DevAddr": dev.DevAddr, + "AppID": dev.AppID, + "DevID": dev.DevID, + "DataRate": dev.ADR.DataRate, + "TxPower": dev.ADR.TxPower, + "NbTrans": dev.ADR.NbTrans, + }) + // Check settings if dev.ADR.DataRate == "" { + ctx.Debug("Empty ADR DataRate") return nil } if dev.ADR.Margin == 0 { dev.ADR.Margin = DefaultADRMargin } if dev.ADR.Band == "" { + ctx.Debug("Empty ADR Band") return nil } fp, err := band.Get(dev.ADR.Band) @@ -175,6 +195,7 @@ func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) e } switch { case len(frames) == 0: + ctx.Debug("No historical data for ADR") return nil case len(frames) >= device.FramesHistorySize: frames = frames[:device.FramesHistorySize] @@ -189,6 +210,7 @@ func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) e // Calculate desired ADR settings dataRate, txPower, err := fp.ADRSettings(dev.ADR.DataRate, dev.ADR.TxPower, maxSNR(frames), adrMargin) if err == band.ErrADRUnavailable { + ctx.Debugf("ADR not available in %s", dev.ADR.Band) return nil } if err != nil { @@ -224,33 +246,30 @@ func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) e } if dev.ADR.SentInitial && dev.ADR.DataRate == dataRate && dev.ADR.TxPower == txPower && dev.ADR.NbTrans == nbTrans { + ctx.Debug("No ADR needed") return nil // Nothing to do } dev.ADR.DataRate, dev.ADR.TxPower, dev.ADR.NbTrans = dataRate, txPower, nbTrans payloads := getAdrReqPayloads(dev, &fp, drIdx, powerIdx) if len(payloads) == 0 { + ctx.Debug("No ADR payloads") return nil } - n.Ctx.WithFields(log.Fields{ - "AppEUI": dev.AppEUI, - "DevEUI": dev.DevEUI, - "DevAddr": dev.DevAddr, - "AppID": dev.AppID, - "DevID": dev.DevID, - }).Debug("Sending ADR") - dev.ADR.SentInitial = true dev.ADR.ExpectRes = true mac.ADR = true + var hadADR bool fOpts := make([]pb_lorawan.MACCommand, 0, len(mac.FOpts)+len(payloads)) for _, existing := range mac.FOpts { - if existing.CID != uint32(lorawan.LinkADRReq) { - fOpts = append(fOpts, existing) + if existing.CID == uint32(lorawan.LinkADRReq) { + hadADR = true + continue } + fOpts = append(fOpts, existing) } for _, payload := range payloads { responsePayload, _ := payload.MarshalBinary() @@ -261,6 +280,12 @@ func (n *networkServer) setADR(mac *pb_lorawan.MACPayload, dev *device.Device) e } mac.FOpts = fOpts + if !hadADR { + ctx.Info("Sending ADR Request in Downlink") + } else { + ctx.Debug("Updating ADR Request in Downlink") + } + return nil } @@ -268,6 +293,7 @@ func (n *networkServer) handleDownlinkADR(message *pb_broker.DownlinkMessage, de err := n.setADR(message.GetMessage().GetLoRaWAN().GetMACPayload(), dev) if err != nil { message.Trace = message.Trace.WithEvent("mac error", macCMD, "link-adr", "error", err.Error()) + n.Ctx.WithFields(logfields.ForMessage(message)).WithError(err).Warn("Could not set ADR") err = nil } diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 0235df5d4..4fdb9da91 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -74,7 +74,7 @@ func (s *RedisDeviceStore) key(appEUI types.AppEUI, devEUI types.DevEUI) string // Count all Devices func (s *RedisDeviceStore) Count() (int, error) { - return s.store.Count("") + return s.store.Count("", nil) } // List all Devices diff --git a/core/networkserver/downlink.go b/core/networkserver/downlink.go index 71dbea499..170422177 100644 --- a/core/networkserver/downlink.go +++ b/core/networkserver/downlink.go @@ -4,14 +4,20 @@ package networkserver import ( + "time" + pb_broker "github.com/TheThingsNetwork/api/broker" + "github.com/TheThingsNetwork/api/logfields" "github.com/TheThingsNetwork/api/trace" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) { - err := message.UnmarshalPayload() + var err error + start := time.Now() + + err = message.UnmarshalPayload() if err != nil { return nil, err } @@ -22,6 +28,15 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ n.status.downlink.Mark(1) + ctx := n.Ctx.WithFields(logfields.ForMessage(message)) + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle downlink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled downlink") + } + }() + // Get Device dev, err := n.devices.Get(message.AppEUI, message.DevEUI) if err != nil { diff --git a/core/networkserver/downlink_test.go b/core/networkserver/downlink_test.go index 5d84dd6c4..d37d0855c 100644 --- a/core/networkserver/downlink_test.go +++ b/core/networkserver/downlink_test.go @@ -9,6 +9,7 @@ import ( pb_broker "github.com/TheThingsNetwork/api/broker" pb_protocol "github.com/TheThingsNetwork/api/protocol" pb_lorawan "github.com/TheThingsNetwork/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -19,7 +20,8 @@ import ( func TestHandleDownlink(t *testing.T) { a := New(t) ns := &networkServer{ - devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-downlink"), + Component: &component.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-downlink"), } ns.InitStatus() diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go index 0359c802e..56e38fd96 100644 --- a/core/networkserver/uplink.go +++ b/core/networkserver/uplink.go @@ -7,12 +7,16 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/api/broker" + "github.com/TheThingsNetwork/api/logfields" "github.com/TheThingsNetwork/api/trace" "github.com/TheThingsNetwork/ttn/utils/errors" ) func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) { - err := message.UnmarshalPayload() + var err error + start := time.Now() + + err = message.UnmarshalPayload() if err != nil { return nil, err } @@ -23,6 +27,15 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag n.status.uplink.Mark(1) + ctx := n.Ctx.WithFields(logfields.ForMessage(message)) + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle uplink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") + } + }() + // Get Device dev, err := n.devices.Get(*message.AppEUI, *message.DevEUI) if err != nil { @@ -35,7 +48,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag defer func() { setErr := n.devices.Set(dev) if setErr != nil { - n.Ctx.WithError(setErr).Error("Could not update device state") + ctx.WithError(setErr).Error("Could not update device state") } if err == nil { err = setErr diff --git a/core/networkserver/uplink_mac.go b/core/networkserver/uplink_mac.go index 9147cf0ad..45fcedce5 100644 --- a/core/networkserver/uplink_mac.go +++ b/core/networkserver/uplink_mac.go @@ -4,8 +4,6 @@ package networkserver import ( - "fmt" - pb_broker "github.com/TheThingsNetwork/api/broker" pb_lorawan "github.com/TheThingsNetwork/api/protocol/lorawan" "github.com/TheThingsNetwork/api/trace" @@ -13,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/band" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/brocaar/lorawan" + "github.com/spf13/viper" ) func (n *networkServer) handleUplinkMAC(message *pb_broker.DeduplicatedUplinkMessage, dev *device.Device) error { @@ -22,10 +21,11 @@ func (n *networkServer) handleUplinkMAC(message *pb_broker.DeduplicatedUplinkMes lorawanDownlinkMAC := lorawanDownlinkMsg.GetMACPayload() ctx := n.Ctx.WithFields(log.Fields{ - "AppEUI": dev.AppEUI, - "DevEUI": dev.DevEUI, - "AppID": dev.AppID, - "DevID": dev.DevID, + "AppEUI": dev.AppEUI, + "DevEUI": dev.DevEUI, + "AppID": dev.AppID, + "DevID": dev.DevID, + "DevAddr": dev.DevAddr, }) // Confirmed Uplink @@ -34,9 +34,10 @@ func (n *networkServer) handleUplinkMAC(message *pb_broker.DeduplicatedUplinkMes lorawanDownlinkMAC.Ack = true } + md := message.GetProtocolMetadata() + // MAC Commands for _, cmd := range lorawanUplinkMAC.FOpts { - md := message.GetProtocolMetadata() switch cmd.CID { case uint32(lorawan.LinkCheckReq): response := &lorawan.LinkCheckAnsPayload{ @@ -63,42 +64,56 @@ func (n *networkServer) handleUplinkMAC(message *pb_broker.DeduplicatedUplinkMes if answer.DataRateACK && answer.PowerACK && answer.ChannelMaskACK { dev.ADR.Failed = 0 dev.ADR.SendReq = false + ctx.WithFields(log.Fields{ + "DataRate": dev.ADR.DataRate, + "TxPower": dev.ADR.TxPower, + "NbTrans": dev.ADR.NbTrans, + }).Debug("Positive LinkADRAns") } else { dev.ADR.Failed++ - ctx. - WithField("Answer", fmt.Sprintf("%v/%v/%v", answer.DataRateACK, answer.PowerACK, answer.ChannelMaskACK)). - Warn("Negative LinkADRAns") + ctx.WithFields(log.Fields{ + "DataRate": dev.ADR.DataRate, + "TxPower": dev.ADR.TxPower, + "NbTrans": dev.ADR.NbTrans, + "DataRateACK": answer.DataRateACK, + "PowerACK": answer.PowerACK, + "ChannelMaskACK": answer.ChannelMaskACK, + "FailedReqs": dev.ADR.Failed, + }).Warn("Negative LinkADRAns") } default: } } - // We did not receive an ADR response, the device may have the wrong RX2 settings - if dev.ADR.ExpectRes && dev.ADR.Band == "EU_863_870" { - ctx.Warn("No LinkADRAns received") - dev.ADR.Failed++ - if dev.ADR.Failed > maxADRFails { + if dev.ADR.ExpectRes { + ctx.Warn("Expected LinkADRAns but did not receive any") + if md.GetLoRaWAN().DataRate == dev.ADR.DataRate { dev.ADR.ExpectRes = false - dev.ADR.SendReq = false - } else { - settings := message.GetResponseTemplate().GetDownlinkOption() - if settings.GetGatewayConfiguration().Frequency == 869525000 { - if loraSettings := settings.ProtocolConfiguration.GetLoRaWAN(); loraSettings != nil { - loraSettings.DataRate = "SF12BW125" + ctx.WithFields(log.Fields{ + "DataRate": dev.ADR.DataRate, + }).Debug("DataRate is as expected, assuming LinkADRReq succeeded") + } + } + + // We did not receive an ADR response, the device may have the wrong RX2 settings + if dev.ADR.ExpectRes && dev.ADR.Band == "EU_863_870" && viper.GetInt("eu-rx2-dr") != 0 { + settings := message.GetResponseTemplate().GetDownlinkOption() + if settings.GetGatewayConfiguration().Frequency == 869525000 { + if loraSettings := settings.ProtocolConfiguration.GetLoRaWAN(); loraSettings != nil { + loraSettings.DataRate = "SF12BW125" - band, _ := band.Get("EU_863_870") - payload := lorawan.RX2SetupReqPayload{ - Frequency: uint32(band.RX2Frequency), - DLSettings: lorawan.DLSettings{ - RX2DataRate: uint8(band.RX2DataRate), - }, - } - responsePayload, _ := payload.MarshalBinary() - lorawanDownlinkMAC.FOpts = append(lorawanDownlinkMAC.FOpts, pb_lorawan.MACCommand{ - CID: uint32(lorawan.RXParamSetupReq), - Payload: responsePayload, - }) + band, _ := band.Get("EU_863_870") + payload := lorawan.RX2SetupReqPayload{ + Frequency: uint32(band.RX2Frequency), + DLSettings: lorawan.DLSettings{ + RX2DataRate: uint8(band.RX2DataRate), + }, } + responsePayload, _ := payload.MarshalBinary() + lorawanDownlinkMAC.FOpts = append(lorawanDownlinkMAC.FOpts, pb_lorawan.MACCommand{ + CID: uint32(lorawan.RXParamSetupReq), + Payload: responsePayload, + }) } } } diff --git a/core/router/downlink.go b/core/router/downlink.go index 16fed6c58..4b1b2fded 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -22,6 +22,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/spf13/viper" ) func (r *router) SubscribeDownlink(gatewayID string, subscriptionID string) (<-chan *pb.DownlinkMessage, error) { @@ -83,6 +84,12 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) (err error) option := downlink.DownlinkOption + // NOTE: This option is intentionally undocumented. + if maxTxPower := int32(viper.GetInt("lorawan.max-tx-power")); maxTxPower != 0 && + option.GatewayConfiguration.Power > maxTxPower { + option.GatewayConfiguration.Power = maxTxPower + } + downlinkMessage := &pb.DownlinkMessage{ Payload: downlink.Payload, ProtocolConfiguration: option.ProtocolConfiguration, diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 82b2b64de..6405764de 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "time" + "github.com/TheThingsNetwork/api/logfields" pb_lorawan "github.com/TheThingsNetwork/api/protocol/lorawan" router_pb "github.com/TheThingsNetwork/api/router" ttnlog "github.com/TheThingsNetwork/go-utils/log" @@ -37,8 +38,8 @@ type Schedule interface { // NewSchedule creates a new Schedule func NewSchedule(ctx ttnlog.Interface) Schedule { s := &schedule{ - ctx: ctx, - items: make(map[string]*scheduledItem), + ctx: ctx, + items: make(map[string]*scheduledItem), downlinkSubscriptions: make(map[string]chan *router_pb.DownlinkMessage), } go func() { @@ -162,7 +163,10 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score // see interface func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { - ctx := s.ctx.WithField("Identifier", id) + ctx := s.ctx.WithFields(logfields.ForMessage(downlink)).WithFields(ttnlog.Fields{ + "Identifier": id, + "TxPower": downlink.GatewayConfiguration.Power, + }) s.Lock() defer s.Unlock() diff --git a/core/storage/redis_kv_store.go b/core/storage/redis_kv_store.go index 3e41e8e35..6c2909b42 100644 --- a/core/storage/redis_kv_store.go +++ b/core/storage/redis_kv_store.go @@ -74,7 +74,7 @@ func (s *RedisKVStore) GetAll(keys []string, options *ListOptions) (map[string]s // 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) { - allKeys, err := s.Keys(selector) + allKeys, err := s.Keys(selector, options) if err != nil { return nil, err } diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index 554337c62..f07d66488 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -105,7 +105,7 @@ func (s *RedisMapStore) GetAll(keys []string, options *ListOptions) ([]interface // List all results matching the selector, prepending the prefix to the selector if necessary func (s *RedisMapStore) List(selector string, options *ListOptions) ([]interface{}, error) { - allKeys, err := s.Keys(selector) + allKeys, err := s.Keys(selector, options) if err != nil { return nil, err } diff --git a/core/storage/redis_queue_store.go b/core/storage/redis_queue_store.go index 109357717..02ce2c19b 100644 --- a/core/storage/redis_queue_store.go +++ b/core/storage/redis_queue_store.go @@ -73,7 +73,7 @@ func (s *RedisQueueStore) GetAll(keys []string, options *ListOptions) (map[strin // List all results matching the selector, prepending the prefix to the selector if necessary func (s *RedisQueueStore) List(selector string, options *ListOptions) (map[string][]string, error) { - allKeys, err := s.Keys(selector) + allKeys, err := s.Keys(selector, options) if err != nil { return nil, err } diff --git a/core/storage/redis_set_store.go b/core/storage/redis_set_store.go index 328274d93..7690b5ac4 100644 --- a/core/storage/redis_set_store.go +++ b/core/storage/redis_set_store.go @@ -85,7 +85,7 @@ func (s *RedisSetStore) Count(key string) (int, error) { // 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) { - allKeys, err := s.Keys(selector) + allKeys, err := s.Keys(selector, options) if err != nil { return nil, err } diff --git a/core/storage/redis_store.go b/core/storage/redis_store.go index 3a41ddb79..1535aa2e9 100644 --- a/core/storage/redis_store.go +++ b/core/storage/redis_store.go @@ -27,7 +27,20 @@ func NewRedisStore(client *redis.Client, prefix string) *RedisStore { } // Keys matching the selector, prepending the prefix to the selector if necessary -func (s *RedisStore) Keys(selector string) ([]string, error) { +func (s *RedisStore) Keys(selector string, opts *ListOptions) ([]string, error) { + if opts != nil && opts.cacheKey != "" { + cacheExists, err := s.client.Exists(opts.cacheKey).Result() + if err != nil { + return nil, err + } + if cacheExists { + res, err := s.client.SMembers(opts.cacheKey).Result() + if err == nil { + return res, nil + } + } + } + if selector == "" { selector = "*" } @@ -47,12 +60,30 @@ func (s *RedisStore) Keys(selector string) ([]string, error) { break } } + + if opts != nil && opts.cacheKey != "" { + pipe := s.client.Pipeline() + defer pipe.Close() + pipe.Del(opts.cacheKey) + if len(allKeys) > 0 { + var allKeysAsInterface = make([]interface{}, len(allKeys)) + for i, key := range allKeys { + allKeysAsInterface[i] = key + } + pipe.SAdd(opts.cacheKey, allKeysAsInterface...) + if opts.cacheTTL > 0 { + pipe.Expire(opts.cacheKey, opts.cacheTTL) + } + } + pipe.Exec() + } + return allKeys, nil } // Count the results matching the selector -func (s *RedisStore) Count(selector string) (int, error) { - allKeys, err := s.Keys(selector) +func (s *RedisStore) Count(selector string, options *ListOptions) (int, error) { + allKeys, err := s.Keys(selector, options) if err != nil { return 0, err } diff --git a/core/storage/util.go b/core/storage/util.go index 3a3dacda2..56915d322 100644 --- a/core/storage/util.go +++ b/core/storage/util.go @@ -3,11 +3,16 @@ package storage +import "time" + // ListOptions are options for all list commands type ListOptions struct { Limit uint64 Offset uint64 + cacheKey string + cacheTTL time.Duration + total uint64 selected uint64 } @@ -17,6 +22,11 @@ func (o ListOptions) GetTotalAndSelected() (total, selected uint64) { return o.total, o.selected } +// UseCache instructs the list operation to use a result cache. +func (o *ListOptions) UseCache(key string, ttl time.Duration) { + o.cacheKey, o.cacheTTL = key, ttl +} + func selectKeys(keys []string, options *ListOptions) []string { var start uint64 end := uint64(len(keys)) diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index 676813378..46a0b35a3 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -31,9 +31,9 @@ ttnctl applications add can be used to add a new application to your account. **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) + --app-eui strings 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** @@ -381,24 +381,24 @@ ttnctl devices set can be used to set properties of a device. **Options** ``` - --16-bit-fcnt Use 16 bit FCnt - --32-bit-fcnt Use 32 bit FCnt (default) - --altitude int32 Set altitude - --app-key string Set AppKey - --app-s-key string Set AppSKey - --attr-remove stringSlice Remove device attribute - --attr-set stringSlice Add a device attribute (key:value) - --description string Set Description - --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) - --latitude float32 Set latitude - --longitude float32 Set longitude - --nwk-s-key string Set NwkSKey - --override Override protection against breaking changes + --16-bit-fcnt Use 16 bit FCnt + --32-bit-fcnt Use 32 bit FCnt (default) + --altitude int32 Set altitude + --app-key string Set AppKey + --app-s-key string Set AppSKey + --attr-remove strings Remove device attribute + --attr-set strings Add a device attribute (key:value) + --description string Set Description + --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) + --latitude float32 Set latitude + --longitude float32 Set longitude + --nwk-s-key string Set NwkSKey + --override Override protection against breaking changes ``` **Example** diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go index 33baf7541..bdd35c24b 100644 --- a/ttnctl/cmd/docs/generate.go +++ b/ttnctl/cmd/docs/generate.go @@ -11,9 +11,9 @@ import ( ) func main() { - fmt.Println(`# API Reference - -Control The Things Network from the command line. -`) + fmt.Println("# API Reference") + fmt.Println() + fmt.Println("Control The Things Network from the command line.") + fmt.Println() fmt.Print(docs.Generate(cmd.RootCmd)) } diff --git a/vendor/github.com/brocaar/lorawan/LICENSE b/vendor/github.com/brocaar/lorawan/LICENSE new file mode 100644 index 000000000..6b38cb0ac --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Orne Brocaar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +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/vendor/github.com/brocaar/lorawan/Makefile b/vendor/github.com/brocaar/lorawan/Makefile new file mode 100644 index 000000000..400a19b10 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/Makefile @@ -0,0 +1,12 @@ +PKGS := $(shell go list ./... | grep -v /vendor/) + +lint: + @echo "Running linters" + @for pkg in $(PKGS) ; do \ + golint $$pkg ; \ + done + @go vet $(PKGS) + +test: lint + @echo "Running tests" + @go test -cover -v ./... diff --git a/vendor/github.com/brocaar/lorawan/README.md b/vendor/github.com/brocaar/lorawan/README.md new file mode 100644 index 000000000..fbc5d5c9e --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/README.md @@ -0,0 +1,89 @@ +# LoRaWAN (Go) + +[![Build Status](https://travis-ci.org/brocaar/lorawan.svg?branch=master)](https://travis-ci.org/brocaar/lorawan) +[![GoDoc](https://godoc.org/github.com/brocaar/lorawan?status.svg)](https://godoc.org/github.com/brocaar/lorawan) + +Package lorawan provides structures and tools to read and write LoraWAN +messages from and to a slice of bytes. + +The following structures are implemented (+ fields): + + * PHYPayload (MHDR | MACPayload | MIC) + * MACPayload (FHDR | FPort | FRMPayload) + * FHDR (DevAddr | FCtrl | FCnt | FOpts) + +The Following message types (MType) are implemented: + + * JoinRequest + * JoinAccept + * UnconfirmedDataUp + * UnconfirmedDataDown + * ConfirmedDataUp + * ConfirmedDataDown + * Proprietary (todo: add pluggable function for MIC calculation / validation) + +The following MAC commands (and their optional payloads) are implemented: + + * LinkCheckReq + * LinkCheckAns + * LinkADRReq + * LinkADRAns + * DutyCycleReq + * DutyCycleAns + * RXParamSetupReq + * RXParamSetupAns + * DevStatusReq + * DevStatusAns + * NewChannelReq + * NewChannelAns + * RXTimingSetupReq + * RXTimingSetupAns + * Proprietary commands (0x80 - 0xFF) can be registered with RegisterProprietaryMACCommand + +Support for calculating and setting the MIC is done by calling SetMIC(): + + err := phyPayload.SetMIC(key) + +Validating the MIC is done by calling ValidateMIC(): + + valid, err := phyPayload.ValidateMIC(key) + +Encryption and decryption of the MACPayload (for join-accept) is done by +calling EncryptJoinAcceptPayload() and DecryptJoinAcceptPayload(). Note that you need to +call SetMIC BEFORE encryption. + + err := phyPayload.EncryptJoinAcceptPayload(key) + err := phyPayload.DecryptJoinAcceptPayload(key) + +Encryption and decryption of the FRMPayload is done by calling +EncryptFRMPayload() and DecryptFRMPayload(). After encryption (and thus +before decryption), the bytes are stored in the DataPayload struct. + + err := phyPayload.EncryptFRMPayload(key) + err := phyPayload.DecryptFRMPayload(key) + +All payloads implement the Payload interface. Based on the MIC value, you +should be able to know to which type to cast the Payload value, so you will +be able to access its fields. + +See the examples section of the documentation for more usage examples +of this package. + +When using this package, knowledge about the LoRaWAN specification is needed. +You can request the LoRaWAN specification here: +https://www.lora-alliance.org/For-Developers/LoRaWANDevelopers + +## ISM band configuration + +The LoRaWAN specification defines various region specific defaults and +configuration. These can be found in the ``band`` sub-package. + +## Documentation + +See https://godoc.org/github.com/brocaar/lorawan. There is also an examples +section with usage examples. + +## License + +This package is distributed under the MIT license which can be found in ``LICENSE``. +LoRaWAN is a trademark of the LoRa Alliance Inc. (https://www.lora-alliance.org/). diff --git a/vendor/github.com/brocaar/lorawan/band/band.go b/vendor/github.com/brocaar/lorawan/band/band.go new file mode 100644 index 000000000..49b11d887 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band.go @@ -0,0 +1,478 @@ +// Package band provides band specific defaults and configuration for +// downlink communication with end-nodes. +package band + +import ( + "errors" + "fmt" + "sort" + "time" + + "github.com/brocaar/lorawan" +) + +// Name defines the band-name type. +type Name string + +// Available ISM bands. +const ( + AS_923 Name = "AS_923" + AU_915_928 Name = "AU_915_928" + CN_470_510 Name = "CN_470_510" + CN_779_787 Name = "CN_779_787" + EU_433 Name = "EU_433" + EU_863_870 Name = "EU_863_870" + IN_865_867 Name = "IN_865_867" + KR_920_923 Name = "KR_920_923" + RU_864_870 Name = "RU_864_870" + US_902_928 Name = "US_902_928" +) + +// Modulation defines the modulation type. +type Modulation string + +// Possible modulation types. +const ( + LoRaModulation Modulation = "LORA" + FSKModulation Modulation = "FSK" +) + +// DataRate defines a data rate +type DataRate struct { + Modulation Modulation `json:"modulation"` + SpreadFactor int `json:"spreadFactor,omitempty"` // used for LoRa + Bandwidth int `json:"bandwidth,omitempty"` // in kHz, used for LoRa + BitRate int `json:"bitRate,omitempty"` // bits per second, used for FSK +} + +// MaxPayloadSize defines the max payload size +type MaxPayloadSize struct { + M int // The maximum MACPayload size length + N int // The maximum application payload length in the absence of the optional FOpt control field +} + +// Channel defines the channel structure +type Channel struct { + Frequency int // frequency in Hz + DataRates []int // each int mapping to an index in DataRateConfiguration + userConfigured bool // user-configured channel + deactivated bool // used to deactivate on or multiple channels (e.g. for US ISM band) +} + +// Band defines an region specific ISM band implementation for LoRa. +type Band struct { + // dwellTime defines if dwell time limitation should be taken into account + dwellTime lorawan.DwellTime + + // rx1DataRate defines the RX1 data-rate given the uplink data-rate + // and a RX1DROffset value. These values are retrievable by using + // the GetRX1DataRate method. + rx1DataRate [][]int + + // DefaultTXPower defines the default radiated transmit output power + DefaultTXPower int + + // ImplementsCFlist defines if the band implements the optional channel + // frequency list. + ImplementsCFlist bool + + // RX2Frequency defines the fixed frequency for the RX2 receive window + RX2Frequency int + + // RX2DataRate defines the fixed data-rate for the RX2 receive window + RX2DataRate int + + // MaxFcntGap defines the MAC_FCNT_GAP default value. + MaxFCntGap uint32 + + // ADRACKLimit defines the ADR_ACK_LIMIT default value. + ADRACKLimit int + + // ADRACKDelay defines the ADR_ACK_DELAY default value. + ADRACKDelay int + + // ReceiveDelay1 defines the RECEIVE_DELAY1 default value. + ReceiveDelay1 time.Duration + + // ReceiveDelay2 defines the RECEIVE_DELAY2 default value. + ReceiveDelay2 time.Duration + + // JoinAcceptDelay1 defines the JOIN_ACCEPT_DELAY1 default value. + JoinAcceptDelay1 time.Duration + + // JoinAcceptDelay2 defines the JOIN_ACCEPT_DELAY2 default value. + JoinAcceptDelay2 time.Duration + + // ACKTimeoutMin defines the ACK_TIMEOUT min. default value. + ACKTimeoutMin time.Duration + + // ACKTimeoutMax defines the ACK_TIMEOUT max. default value. + ACKTimeoutMax time.Duration + + // DataRates defines the available data rates. + DataRates []DataRate + + // MaxPayloadSize defines the maximum payload size, per data-rate. + MaxPayloadSize []MaxPayloadSize + + // TXPower defines the TX power configuration. + TXPower []int + + // UplinkChannels defines the list of (default) configured uplink channels. + UplinkChannels []Channel + + // DownlinkChannels defines the list of (default) configured downlink + // channels. + DownlinkChannels []Channel + + // getRX1ChannelFunc implements a function which returns the RX1 channel + // based on the uplink / TX channel. + getRX1ChannelFunc func(txChannel int) int + + // getRX1FrequencyFunc implements a function which returns the RX1 frequency + // given the uplink frequency. + getRX1FrequencyFunc func(band *Band, txFrequency int) (int, error) + + // getRX1DataRateFunc implements a function which returns the RX1 data-rate + // given the uplink data-rate and data-rate offset. + getRX1DataRateFunc func(band *Band, uplinkDR, rx1DROffset int) (int, error) + + // getLinkADRReqPayloadsForEnabledChannelsFunc implements a band-specific + // function which returns the LinkADRReqPayload items needed to activate + // the used channels on the node. + // In case this is set GetLinkADRReqPayloadsForEnabledChannels will + // use both the "naive" algorithm and the algorithm used in this function. + // The smallest result is then returned. + // In case this function is left blank, only the "naive" algorithm is used. + getLinkADRReqPayloadsForEnabledChannelsFunc func(band *Band, nodeChannels []int) []lorawan.LinkADRReqPayload + + // getEnabledChannelsForLinkADRReqPayloadsFunc implements a band-specific + // function (taking ChMaskCntl values with band-specific meaning into + // account). + getEnabledChannelsForLinkADRReqPayloadsFunc func(band *Band, nodeChannels []int, pls []lorawan.LinkADRReqPayload) ([]int, error) +} + +// GetRX1Channel returns the channel to use for RX1 given the channel used +// for uplink. +func (b *Band) GetRX1Channel(txChannel int) int { + return b.getRX1ChannelFunc(txChannel) +} + +// GetRX1Frequency returns the frequency to use for RX1 given the uplink +// frequency. +func (b *Band) GetRX1Frequency(txFrequency int) (int, error) { + return b.getRX1FrequencyFunc(b, txFrequency) +} + +// GetRX1DataRate returns the RX1 data-rate given the uplink data-rate and +// RX1 data-rate offset. +func (b *Band) GetRX1DataRate(uplinkDR, rx1DROffset int) (int, error) { + // use the lookup table when no function has been defined + if b.getRX1DataRateFunc == nil { + if uplinkDR > len(b.rx1DataRate)-1 { + return 0, errors.New("lorawan/band: invalid data-rate") + } + if rx1DROffset > len(b.rx1DataRate[uplinkDR])-1 { + return 0, errors.New("lorawan/band: invalid data-rate offset") + } + return b.rx1DataRate[uplinkDR][rx1DROffset], nil + } + return b.getRX1DataRateFunc(b, uplinkDR, rx1DROffset) +} + +// GetUplinkChannelNumber returns the channel number given a frequency. +func (b *Band) GetUplinkChannelNumber(frequency int) (int, error) { + for chanNum, channel := range b.UplinkChannels { + if frequency == channel.Frequency { + return chanNum, nil + } + } + + return 0, fmt.Errorf("lorawan/band: unknown channel for frequency: %d", frequency) +} + +// GetDataRate returns the index of the given DataRate. +func (b *Band) GetDataRate(dr DataRate) (int, error) { + for i, d := range b.DataRates { + if d == dr { + return i, nil + } + } + return 0, errors.New("lorawan/band: the given data-rate does not exist") +} + +// AddChannel adds an extra (user-configured) channel to the channels. +// The DataRates wil be set to DR 0-5. +// Note: this is only allowed when the band supports a CFList. +func (b *Band) AddChannel(freq int) error { + if !b.ImplementsCFlist { + return errors.New("lorawan/band: band does not implement CFList") + } + + c := Channel{ + Frequency: freq, + DataRates: []int{0, 1, 2, 3, 4, 5}, + userConfigured: true, + deactivated: freq == 0, + } + + b.UplinkChannels = append(b.UplinkChannels, c) + b.DownlinkChannels = append(b.DownlinkChannels, c) + + return nil +} + +// GetCFList returns the CFList used for OTAA activation, or returns nil if +// the band does not implement the CFList or when there are no extra channels. +// Note that this only returns the first 5 extra channels. +func (b *Band) GetCFList() *lorawan.CFList { + if !b.ImplementsCFlist { + return nil + } + + var cFList lorawan.CFList + var i int + for _, c := range b.UplinkChannels { + if c.userConfigured && i < len(cFList) { + cFList[i] = uint32(c.Frequency) + i++ + } + } + + if cFList[0] == 0 { + return nil + } + return &cFList +} + +// DisableUplinkChannel disables the given uplink channel. +func (b *Band) DisableUplinkChannel(i int) error { + if i > len(b.UplinkChannels)-1 { + return ErrChannelDoesNotExist + } + + b.UplinkChannels[i].deactivated = true + return nil +} + +// EnableUplinkChannel enables the given uplink channel. +func (b *Band) EnableUplinkChannel(i int) error { + if i > len(b.UplinkChannels)-1 { + return ErrChannelDoesNotExist + } + + b.UplinkChannels[i].deactivated = false + return nil +} + +// GetUplinkChannels returns all available uplink channels. +func (b *Band) GetUplinkChannels() []int { + var out []int + for i := range b.UplinkChannels { + out = append(out, i) + } + return out +} + +// GetEnabledUplinkChannels returns the enabled uplink channels. +func (b *Band) GetEnabledUplinkChannels() []int { + var out []int + for i, c := range b.UplinkChannels { + if !c.deactivated { + out = append(out, i) + } + } + return out +} + +// GetDisabledUplinkChannels returns the disabled uplink channels. +func (b *Band) GetDisabledUplinkChannels() []int { + var out []int + for i, c := range b.UplinkChannels { + if c.deactivated { + out = append(out, i) + } + } + return out +} + +// GetLinkADRReqPayloadsForEnabledChannels returns the LinkADRReqPayloads to +// reconfigure the node to the current active channels. Note that in case of +// activation, user-defined channels (e.g. CFList) will be ignored as it +// is unknown if the node is aware about these extra frequencies. +func (b *Band) GetLinkADRReqPayloadsForEnabledChannels(nodeChannels []int) []lorawan.LinkADRReqPayload { + enabledChannels := b.GetEnabledUplinkChannels() + + diff := intSliceDiff(nodeChannels, enabledChannels) + var filteredDiff []int + + for _, c := range diff { + if channelIsActive(nodeChannels, c) || !b.UplinkChannels[c].userConfigured { + filteredDiff = append(filteredDiff, c) + } + } + + // nothing to do + if len(diff) == 0 || len(filteredDiff) == 0 { + return nil + } + + // make sure we're dealing with a sorted slice + sort.Ints(diff) + + var payloads []lorawan.LinkADRReqPayload + chMaskCntl := -1 + + // loop over the channel blocks that contain different channels + // note that each payload holds 16 channels and that the chMaskCntl + // defines the block + for _, c := range diff { + if c/16 != chMaskCntl { + chMaskCntl = c / 16 + pl := lorawan.LinkADRReqPayload{ + Redundancy: lorawan.Redundancy{ + ChMaskCntl: uint8(chMaskCntl), + }, + } + + // set enabled channels in this block to active + // note that we don't enable user defined channels (CFList) as + // we have no knowledge if the nodes has been provisioned with + // these frequencies + for _, ec := range enabledChannels { + if (!b.UplinkChannels[ec].userConfigured || channelIsActive(nodeChannels, ec)) && ec >= chMaskCntl*16 && ec < (chMaskCntl+1)*16 { + pl.ChMask[ec%16] = true + } + } + + payloads = append(payloads, pl) + } + } + + // some bands contain band specific logic regarding turning on / off + // channels that might require less commands (using band specific + // ChMaskCntl values) + if b.getLinkADRReqPayloadsForEnabledChannelsFunc != nil { + payloadsB := b.getLinkADRReqPayloadsForEnabledChannelsFunc(b, nodeChannels) + if len(payloadsB) < len(payloads) { + return payloadsB + } + } + + return payloads +} + +// GetEnabledChannelsForLinkADRReqPaylaods returns the enabled after which the +// given LinkADRReqPayloads have been applied to the given node channels. +func (b *Band) GetEnabledChannelsForLinkADRReqPayloads(nodeChannels []int, pls []lorawan.LinkADRReqPayload) ([]int, error) { + // for some bands some the ChMaskCntl values have special meanings + if b.getEnabledChannelsForLinkADRReqPayloadsFunc != nil { + return b.getEnabledChannelsForLinkADRReqPayloadsFunc(b, nodeChannels, pls) + } + + chMask := make([]bool, len(b.UplinkChannels)) + for _, c := range nodeChannels { + // make sure that we don't exceed the chMask length. in case we exceed + // we ignore the channel as it might have been removed from the network + if c < len(chMask) { + chMask[c] = true + } + } + + for _, pl := range pls { + for i, enabled := range pl.ChMask { + if int(pl.Redundancy.ChMaskCntl*16)+i >= len(chMask) && !enabled { + continue + } + + if int(pl.Redundancy.ChMaskCntl*16)+i >= len(chMask) { + return nil, ErrChannelDoesNotExist + } + + chMask[int(pl.Redundancy.ChMaskCntl*16)+i] = enabled + } + } + + // turn the chMask into a slice of enabled channel numbers + var out []int + for i, enabled := range chMask { + if enabled { + out = append(out, i) + } + } + + return out, nil +} + +// intSliceDiff returns all items of x that are not in y and all items of +// y that are not in x. +func intSliceDiff(x, y []int) []int { + var out []int + + for _, cX := range x { + found := false + for _, cY := range y { + if cX == cY { + found = true + break + } + } + if !found { + out = append(out, cX) + } + } + + for _, cY := range y { + found := false + for _, cX := range x { + if cY == cX { + found = true + break + } + } + if !found { + out = append(out, cY) + } + } + + return out +} + +func channelIsActive(channels []int, i int) bool { + for _, c := range channels { + if i == c { + return true + } + } + return false +} + +// GetConfig returns the band configuration for the given band. +// Please refer to the LoRaWAN specification for more details about the effect +// of the repeater and dwell time arguments. +func GetConfig(name Name, repeaterCompatible bool, dt lorawan.DwellTime) (Band, error) { + switch name { + case AS_923: + return newAS923Band(repeaterCompatible, dt) + case AU_915_928: + return newAU915Band(repeaterCompatible) + case CN_470_510: + return newCN470Band() + case CN_779_787: + return newCN779Band(repeaterCompatible) + case EU_433: + return newEU433Band(repeaterCompatible) + case EU_863_870: + return newEU863Band(repeaterCompatible) + case IN_865_867: + return newIN865Band(repeaterCompatible) + case KR_920_923: + return newKR920Band() + case RU_864_870: + return newRU864Band(repeaterCompatible) + case US_902_928: + return newUS902Band(repeaterCompatible) + default: + return Band{}, fmt.Errorf("lorawan/band: band %s is undefined", name) + } +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_as923.go b/vendor/github.com/brocaar/lorawan/band/band_as923.go new file mode 100644 index 000000000..b7e1da83f --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_as923.go @@ -0,0 +1,149 @@ +package band + +import ( + "fmt" + "time" + + "github.com/brocaar/lorawan" +) + +func newAS923Band(repeaterCompatible bool, dt lorawan.DwellTime) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if dt == lorawan.DwellTime400ms { + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 0, N: 0}, + {M: 0, N: 0}, + {M: 19, N: 11}, + {M: 61, N: 53}, + {M: 134, N: 126}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 0, N: 0}, + {M: 0, N: 0}, + {M: 19, N: 11}, + {M: 61, N: 53}, + {M: 134, N: 126}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + } else { + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + } + + return Band{ + dwellTime: dt, + + DefaultTXPower: 14, + ImplementsCFlist: true, + RX2Frequency: 923200000, + RX2DataRate: 2, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 250}, + {Modulation: FSKModulation, BitRate: 50000}, + }, + + MaxPayloadSize: maxPayloadSize, + + TXPower: []int{ + 14, + 14 - 2, + 14 - 4, + 14 - 6, + 14 - 8, + 14 - 10, + }, + + UplinkChannels: []Channel{ + {Frequency: 923200000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 923400000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 923200000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 923400000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1DataRateFunc: func(band *Band, uplinkDR, rx1DROffset int) (int, error) { + if rx1DROffset < 0 || rx1DROffset > 7 { + return 0, fmt.Errorf("lorawan/band: invalid RX1 data-rate offset: %d", rx1DROffset) + } + + if uplinkDR < 0 || uplinkDR > 7 { + return 0, fmt.Errorf("lorawan/band: invalid uplink data-rate: %d", uplinkDR) + } + + minDR := 0 + if band.dwellTime == lorawan.DwellTime400ms { + minDR = 2 + } + + effectiveRX1DROffset := []int{0, 1, 2, 3, 4, 5, -1, -2}[rx1DROffset] + dr := uplinkDR - effectiveRX1DROffset + + if dr < minDR { + dr = minDR + } + + if dr > 5 { + dr = 5 + } + + return dr, nil + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_cn470_510.go b/vendor/github.com/brocaar/lorawan/band/band_cn470_510.go new file mode 100644 index 000000000..0cdee30fb --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_cn470_510.go @@ -0,0 +1,123 @@ +package band + +import "time" + +func newCN470Band() (Band, error) { + band := Band{ + DefaultTXPower: 14, + ImplementsCFlist: false, + RX2Frequency: 505300000, + RX2DataRate: 0, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + {}, // RFU + }, + + MaxPayloadSize: []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + {}, // not defined + }, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0}, + {2, 1, 0, 0, 0, 0}, + {3, 2, 1, 0, 0, 0}, + {4, 3, 2, 1, 0, 0}, + {5, 4, 3, 2, 1, 0}, + }, + + TXPower: []int{ + 17, + 16, + 14, + 12, + 10, + 7, + 5, + 2, + 0, // rfu + 0, // rfu + 0, // rfu + 0, // rfu + 0, // rfu + 0, // rfu + 0, // rfu + 0, // rfu + }, + + UplinkChannels: make([]Channel, 96), + DownlinkChannels: make([]Channel, 48), + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel % 48 + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + uplinkChan, err := b.GetUplinkChannelNumber(txFrequency) + if err != nil { + return 0, err + } + + rx1Chan := b.GetRX1Channel(uplinkChan) + return b.DownlinkChannels[rx1Chan].Frequency, nil + }, + } + + // initialize uplink channels + for i := 0; i < 96; i++ { + band.UplinkChannels[i] = Channel{ + Frequency: 470300000 + (i * 200000), + DataRates: []int{0, 1, 2, 3, 4, 5}, + } + } + + // initialize downlink channels + for i := 0; i < 48; i++ { + band.DownlinkChannels[i] = Channel{ + Frequency: 500300000 + (i * 200000), + DataRates: []int{0, 1, 2, 3, 4, 5}, + } + } + + return band, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_cn779_787.go b/vendor/github.com/brocaar/lorawan/band/band_cn779_787.go new file mode 100644 index 000000000..d63d87857 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_cn779_787.go @@ -0,0 +1,101 @@ +package band + +import "time" + +func newCN779Band(repeaterCompatible bool) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 250, N: 242}, + {M: 230, N: 222}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + + return Band{ + DefaultTXPower: 10, + ImplementsCFlist: true, + RX2Frequency: 786000000, + RX2DataRate: 0, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 250}, + {Modulation: FSKModulation, BitRate: 50000}, + }, + + MaxPayloadSize: maxPayloadSize, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0}, + {2, 1, 0, 0, 0, 0}, + {3, 2, 1, 0, 0, 0}, + {4, 3, 2, 1, 0, 0}, + {5, 4, 3, 2, 1, 0}, + {6, 5, 4, 3, 2, 1}, + {7, 6, 5, 4, 3, 2}, + }, + + TXPower: []int{ + 10, + 7, + 4, + 1, + -2, + -5, + }, + + UplinkChannels: []Channel{ + {Frequency: 779500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 779700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 779900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 779500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 779700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 779900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_eu433.go b/vendor/github.com/brocaar/lorawan/band/band_eu433.go new file mode 100644 index 000000000..21f8076c6 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_eu433.go @@ -0,0 +1,101 @@ +package band + +import "time" + +func newEU433Band(repeaterCompatible bool) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + + return Band{ + DefaultTXPower: 10, + ImplementsCFlist: true, + RX2Frequency: 434665000, + RX2DataRate: 0, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 250}, + {Modulation: FSKModulation, BitRate: 50000}, + }, + + MaxPayloadSize: maxPayloadSize, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0}, + {2, 1, 0, 0, 0, 0}, + {3, 2, 1, 0, 0, 0}, + {4, 3, 2, 1, 0, 0}, + {5, 4, 3, 2, 1, 0}, + {6, 5, 4, 3, 2, 1}, + {7, 6, 5, 4, 3, 2}, + }, + + TXPower: []int{ + 10, + 7, + 4, + 1, + -2, + -5, + }, + + UplinkChannels: []Channel{ + {Frequency: 433175000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 433375000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 433575000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 433175000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 433375000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 433575000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_eu863_870.go b/vendor/github.com/brocaar/lorawan/band/band_eu863_870.go new file mode 100644 index 000000000..5f07dd5f0 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_eu863_870.go @@ -0,0 +1,101 @@ +package band + +import "time" + +func newEU863Band(repeaterCompatible bool) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + + return Band{ + DefaultTXPower: 14, + ImplementsCFlist: true, + RX2Frequency: 869525000, + RX2DataRate: 0, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 250}, + {Modulation: FSKModulation, BitRate: 50000}, + }, + + MaxPayloadSize: maxPayloadSize, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0}, + {2, 1, 0, 0, 0, 0}, + {3, 2, 1, 0, 0, 0}, + {4, 3, 2, 1, 0, 0}, + {5, 4, 3, 2, 1, 0}, + {6, 5, 4, 3, 2, 1}, + {7, 6, 5, 4, 3, 2}, + }, + + TXPower: []int{ + 20, // if supported + 14, + 11, + 8, + 5, + 2, + }, + + UplinkChannels: []Channel{ + {Frequency: 868100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 868300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 868500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 868100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 868300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 868500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_in_865_867.go b/vendor/github.com/brocaar/lorawan/band/band_in_865_867.go new file mode 100644 index 000000000..7b1425a52 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_in_865_867.go @@ -0,0 +1,106 @@ +package band + +import "time" + +func newIN865Band(repeaterCompatible bool) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + + return Band{ + DefaultTXPower: 27, + ImplementsCFlist: true, + RX2Frequency: 866550000, + RX2DataRate: 2, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {}, + {Modulation: FSKModulation, BitRate: 50000}, + }, + + MaxPayloadSize: maxPayloadSize, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0, 1, 2}, + {1, 0, 0, 0, 0, 0, 2, 3}, + {2, 1, 0, 0, 0, 0, 3, 4}, + {3, 2, 1, 0, 0, 0, 4, 5}, + {4, 3, 2, 1, 0, 0, 5, 5}, + {5, 4, 3, 2, 1, 0, 5, 5}, + {}, + {7, 6, 5, 4, 3, 2, 7, 7}, + }, + + TXPower: []int{ + 27, + 27 - 2, + 27 - 4, + 27 - 6, + 27 - 8, + 27 - 10, + 27 - 12, + 27 - 14, + 27 - 16, + 27 - 18, + 27 - 20, + }, + + UplinkChannels: []Channel{ + {Frequency: 865062500, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 865402500, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 865985000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 865062500, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 865402500, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 865985000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_kr920_923.go b/vendor/github.com/brocaar/lorawan/band/band_kr920_923.go new file mode 100644 index 000000000..ffdb3f0ef --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_kr920_923.go @@ -0,0 +1,79 @@ +package band + +import "time" + +func newKR920Band() (Band, error) { + return Band{ + DefaultTXPower: 23, // for gateway + ImplementsCFlist: true, + RX2Frequency: 921900000, + RX2DataRate: 0, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + }, + + MaxPayloadSize: []MaxPayloadSize{ + {M: 73, N: 65}, + {M: 159, N: 151}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + }, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0}, + {2, 1, 0, 0, 0, 0}, + {3, 2, 1, 0, 0, 0}, + {4, 3, 2, 1, 0, 0}, + {5, 4, 3, 2, 1, 0}, + }, + + TXPower: []int{ + 20, + 14, + 10, + 8, + 5, + 2, + 0, + }, + + UplinkChannels: []Channel{ + {Frequency: 922100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 922300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 922500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 922100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 922300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 922500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_ru864_870.go b/vendor/github.com/brocaar/lorawan/band/band_ru864_870.go new file mode 100644 index 000000000..674cc2240 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_ru864_870.go @@ -0,0 +1,99 @@ +package band + +import "time" + +func newRU864Band(repeaterCompatible bool) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 59, N: 51}, + {M: 123, N: 115}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + } + } + + return Band{ + DefaultTXPower: 14, + ImplementsCFlist: true, + RX2Frequency: 869100000, + RX2DataRate: 0, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 250}, + {Modulation: FSKModulation, BitRate: 50000}, + }, + + MaxPayloadSize: maxPayloadSize, + + rx1DataRate: [][]int{ + {0, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0}, + {2, 1, 0, 0, 0, 0}, + {3, 2, 1, 0, 0, 0}, + {4, 3, 2, 1, 0, 0}, + {5, 4, 3, 2, 1, 0}, + {6, 5, 4, 3, 2, 1}, + {7, 6, 5, 4, 3, 2}, + }, + + TXPower: []int{ + 20, + 14, + 11, + 8, + 5, + 2, + }, + + UplinkChannels: []Channel{ + {Frequency: 868900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 869100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + DownlinkChannels: []Channel{ + {Frequency: 868900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + {Frequency: 869100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + }, + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + return txFrequency, nil + }, + }, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/band_us902_928.go b/vendor/github.com/brocaar/lorawan/band/band_us902_928.go new file mode 100644 index 000000000..49faa9c08 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/band_us902_928.go @@ -0,0 +1,256 @@ +package band + +import "github.com/brocaar/lorawan" +import "sort" +import "time" + +func newUS902Band(repeaterCompatible bool) (Band, error) { + var maxPayloadSize []MaxPayloadSize + + if repeaterCompatible { + maxPayloadSize = []MaxPayloadSize{ + {M: 19, N: 11}, + {M: 61, N: 53}, + {M: 133, N: 125}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {}, // Not defined + {}, // Not defined + {}, // Not defined + {M: 41, N: 33}, + {M: 117, N: 109}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {M: 230, N: 222}, + {}, // Not defined + {}, // Not defined + } + } else { + maxPayloadSize = []MaxPayloadSize{ + {M: 19, N: 11}, + {M: 61, N: 53}, + {M: 133, N: 125}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {}, // Not defined + {}, // Not defined + {}, // Not defined + {M: 61, N: 53}, + {M: 137, N: 129}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {M: 250, N: 242}, + {}, // Not defined + {}, // Not defined + } + } + + band := Band{ + DefaultTXPower: 20, + ImplementsCFlist: false, + RX2Frequency: 923300000, + RX2DataRate: 8, + + MaxFCntGap: 16384, + ADRACKLimit: 64, + ADRACKDelay: 32, + ReceiveDelay1: time.Second, + ReceiveDelay2: time.Second * 2, + JoinAcceptDelay1: time.Second * 5, + JoinAcceptDelay2: time.Second * 6, + ACKTimeoutMin: time.Second, + ACKTimeoutMax: time.Second * 3, + + DataRates: []DataRate{ + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 125}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 500}, + {}, // RFU + {}, // RFU + {}, // RFU + {Modulation: LoRaModulation, SpreadFactor: 12, Bandwidth: 500}, + {Modulation: LoRaModulation, SpreadFactor: 11, Bandwidth: 500}, + {Modulation: LoRaModulation, SpreadFactor: 10, Bandwidth: 500}, + {Modulation: LoRaModulation, SpreadFactor: 9, Bandwidth: 500}, + {Modulation: LoRaModulation, SpreadFactor: 8, Bandwidth: 500}, + {Modulation: LoRaModulation, SpreadFactor: 7, Bandwidth: 500}, + {}, // RFU + {}, // RFU + }, + + MaxPayloadSize: maxPayloadSize, + + rx1DataRate: [][]int{ + {10, 9, 8, 8}, + {11, 10, 9, 8}, + {12, 11, 10, 9}, + {13, 12, 11, 10}, + {13, 13, 12, 11}, + {}, // Not defined + {}, // Not defined + {}, // Not defined + {8, 8, 8, 8}, + {9, 8, 8, 8}, + {10, 9, 8, 8}, + {11, 10, 9, 8}, + {12, 11, 10, 9}, + {13, 12, 11, 10}, + }, + + TXPower: []int{ + 30, + 28, + 26, + 24, + 22, + 20, + 18, + 16, + 14, + 12, + 10, + 0, + 0, + 0, + 0, + 0, + }, + + UplinkChannels: make([]Channel, 72), + DownlinkChannels: make([]Channel, 8), + + getRX1ChannelFunc: func(txChannel int) int { + return txChannel % 8 + }, + + getRX1FrequencyFunc: func(b *Band, txFrequency int) (int, error) { + uplinkChan, err := b.GetUplinkChannelNumber(txFrequency) + if err != nil { + return 0, err + } + + rx1Chan := b.GetRX1Channel(uplinkChan) + return b.DownlinkChannels[rx1Chan].Frequency, nil + }, + + getLinkADRReqPayloadsForEnabledChannelsFunc: func(b *Band, nodeChannels []int) []lorawan.LinkADRReqPayload { + enabledChannels := b.GetEnabledUplinkChannels() + sort.Ints(enabledChannels) + + out := []lorawan.LinkADRReqPayload{ + {Redundancy: lorawan.Redundancy{ChMaskCntl: 7}}, // All 125 kHz OFF ChMask applies to channels 64 to 71 + } + + chMaskCntl := -1 + + for _, c := range enabledChannels { + // use the ChMask of the first LinkADRReqPayload, besides + // turning off all 125 kHz this payload contains the ChMask + // for the last block of channels. + if c >= 64 { + out[0].ChMask[c%16] = true + continue + } + + if c/16 != chMaskCntl { + chMaskCntl = c / 16 + pl := lorawan.LinkADRReqPayload{ + Redundancy: lorawan.Redundancy{ + ChMaskCntl: uint8(chMaskCntl), + }, + } + + // set the channel mask for this block + for _, ec := range enabledChannels { + if ec >= chMaskCntl*16 && ec < (chMaskCntl+1)*16 { + pl.ChMask[ec%16] = true + } + } + + out = append(out, pl) + } + } + + return out + }, + + getEnabledChannelsForLinkADRReqPayloadsFunc: func(b *Band, nodeChannels []int, pls []lorawan.LinkADRReqPayload) ([]int, error) { + chMask := make([]bool, len(b.UplinkChannels)) + for _, c := range nodeChannels { + // make sure that we don't exceed the chMask length. in case we exceed + // we ignore the channel as it might have been removed from the network + if c < len(chMask) { + chMask[c] = true + } + } + + for _, pl := range pls { + if pl.Redundancy.ChMaskCntl == 6 || pl.Redundancy.ChMaskCntl == 7 { + for i := 0; i < 64; i++ { + if pl.Redundancy.ChMaskCntl == 6 { + chMask[i] = true + } else { + chMask[i] = false + } + } + + for i, cm := range pl.ChMask[0:8] { + chMask[64+i] = cm + } + } else { + for i, enabled := range pl.ChMask { + if int(pl.Redundancy.ChMaskCntl*16)+i >= len(chMask) && !enabled { + continue + } + + if int(pl.Redundancy.ChMaskCntl*16)+i >= len(chMask) { + return nil, ErrChannelDoesNotExist + } + + chMask[int(pl.Redundancy.ChMaskCntl*16)+i] = enabled + } + } + } + + // turn the chMask into a slice of enabled channel numbers + var out []int + for i, enabled := range chMask { + if enabled { + out = append(out, i) + } + } + + return out, nil + }, + } + + // initialize uplink channel 0 - 63 + for i := 0; i < 64; i++ { + band.UplinkChannels[i] = Channel{ + Frequency: 902300000 + (i * 200000), + DataRates: []int{0, 1, 2, 3}, + } + } + + // initialize uplink channel 64 - 71 + for i := 0; i < 8; i++ { + band.UplinkChannels[i+64] = Channel{ + Frequency: 903000000 + (i * 1600000), + DataRates: []int{4}, + } + } + + // initialize downlink channel 0 - 7 + for i := 0; i < 8; i++ { + band.DownlinkChannels[i] = Channel{ + Frequency: 923300000 + (i * 600000), + DataRates: []int{10, 11, 12, 13}, + } + } + + return band, nil +} diff --git a/vendor/github.com/brocaar/lorawan/band/errors.go b/vendor/github.com/brocaar/lorawan/band/errors.go new file mode 100644 index 000000000..79cafbc5c --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/band/errors.go @@ -0,0 +1,8 @@ +package band + +import "errors" + +// errors +var ( + ErrChannelDoesNotExist = errors.New("lorawan/band: channel does not exist") +) diff --git a/vendor/github.com/brocaar/lorawan/cid_string.go b/vendor/github.com/brocaar/lorawan/cid_string.go new file mode 100644 index 000000000..190fb2352 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/cid_string.go @@ -0,0 +1,17 @@ +// Code generated by "stringer -type=CID"; DO NOT EDIT. + +package lorawan + +import "fmt" + +const _CID_name = "LinkCheckReqLinkADRReqDutyCycleReqRXParamSetupReqDevStatusReqNewChannelReqRXTimingSetupReqTXParamSetupReqDLChannelReq" + +var _CID_index = [...]uint8{0, 12, 22, 34, 49, 61, 74, 90, 105, 117} + +func (i CID) String() string { + i -= 2 + if i >= CID(len(_CID_index)-1) { + return fmt.Sprintf("CID(%d)", i+2) + } + return _CID_name[_CID_index[i]:_CID_index[i+1]] +} diff --git a/vendor/github.com/brocaar/lorawan/doc.go b/vendor/github.com/brocaar/lorawan/doc.go new file mode 100644 index 000000000..f0b190fda --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/doc.go @@ -0,0 +1,74 @@ +/* + +Package lorawan provides structures and tools to read and write LoraWAN +messages from and to a slice of bytes. + +The following structures are implemented (+ fields): + + * PHYPayload (MHDR | MACPayload | MIC) + * MACPayload (FHDR | FPort | FRMPayload) + * FHDR (DevAddr | FCtrl | FCnt | FOpts) + +The Following message types (MType) are implemented: + + * JoinRequest + * JoinAccept + * UnconfirmedDataUp + * UnconfirmedDataDown + * ConfirmedDataUp + * ConfirmedDataDown + * Proprietary + +The following MAC commands (and their optional payloads) are implemented: + + * LinkCheckReq + * LinkCheckAns + * LinkADRReq + * LinkADRAns + * DutyCycleReq + * DutyCycleAns + * RXParamSetupReq + * RXParamSetupAns + * DevStatusReq + * DevStatusAns + * NewChannelReq + * NewChannelAns + * RXTimingSetupReq + * RXTimingSetupAns + * Proprietary commands (0x80 - 0xFF) can be registered with RegisterProprietaryMACCommand + +Support for calculating and setting the MIC is done by calling SetMIC(): + + err := phyPayload.SetMIC(key) + +Validating the MIC is done by calling ValidateMIC(): + + valid, err := phyPayload.ValidateMIC(key) + +Encryption and decryption of the MACPayload (for join-accept) is done by +calling EncryptJoinAcceptPayload() and DecryptJoinAcceptPayload(). Note that you need to +call SetMIC BEFORE encryption. + + err := phyPayload.EncryptJoinAcceptPayload(key) + err := phyPayload.DecryptJoinAcceptPayload(key) + +Encryption and decryption of the FRMPayload is done by calling +EncryptFRMPayload() and DecryptFRMPayload(). After encryption (and thus +before decryption), the bytes are stored in the DataPayload struct. + + err := phyPayload.EncryptFRMPayload(key) + err := phyPayload.DecryptFRMPayload(key) + +All payloads implement the Payload interface. Based on the MIC value, you +should be able to know to which type to cast the Payload value, so you will +be able to access its fields. + +See the examples section of the documentation for concrete usage examples +of this package. + +When using this package, knowledge about the LoRaWAN specification is needed. +You can download the LoRaWAN specification here: +https://www.lora-alliance.org/For-Developers/LoRaWANDevelopers + +*/ +package lorawan diff --git a/vendor/github.com/brocaar/lorawan/fhdr.go b/vendor/github.com/brocaar/lorawan/fhdr.go new file mode 100644 index 000000000..a57398530 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/fhdr.go @@ -0,0 +1,210 @@ +package lorawan + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "log" +) + +// DevAddr represents the device address. +type DevAddr [4]byte + +// NwkID returns the NwkID bits of the DevAddr. +func (a DevAddr) NwkID() byte { + return a[0] >> 1 // 7 msb +} + +// MarshalBinary marshals the object in binary form. +func (a DevAddr) MarshalBinary() ([]byte, error) { + out := make([]byte, len(a)) + for i, v := range a { + // little endian + out[len(a)-i-1] = v + } + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (a *DevAddr) UnmarshalBinary(data []byte) error { + if len(data) != len(a) { + return fmt.Errorf("lorawan: %d bytes of data are expected", len(a)) + } + for i, v := range data { + // little endian + a[len(a)-i-1] = v + } + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (a DevAddr) MarshalText() ([]byte, error) { + return []byte(a.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (a *DevAddr) UnmarshalText(text []byte) error { + b, err := hex.DecodeString(string(text)) + if err != nil { + return err + } + + if len(b) != len(a) { + return fmt.Errorf("lorawan: exactly %d bytes are expected", len(a)) + } + copy(a[:], b) + return nil +} + +// String implements fmt.Stringer. +func (a DevAddr) String() string { + return hex.EncodeToString(a[:]) +} + +// Scan implements sql.Scanner. +func (a *DevAddr) Scan(src interface{}) error { + b, ok := src.([]byte) + if !ok { + return errors.New("lorawan: []byte type expected") + } + if len(b) != len(a) { + return fmt.Errorf("lorawan []byte must have length %d", len(a)) + } + copy(a[:], b) + return nil +} + +// FCtrl represents the FCtrl (frame control) field. +type FCtrl struct { + ADR bool `json:"adr"` + ADRACKReq bool `json:"adrAckReq"` + ACK bool `json:"ack"` + FPending bool `json:"fPending"` // only used for downlink messages + fOptsLen uint8 // will be set automatically by the FHDR when serialized to []byte +} + +// MarshalBinary marshals the object in binary form. +func (c FCtrl) MarshalBinary() ([]byte, error) { + if c.fOptsLen > 15 { + return []byte{}, errors.New("lorawan: max value of FOptsLen is 15") + } + b := byte(c.fOptsLen) + if c.FPending { + b = b ^ (1 << 4) + } + if c.ACK { + b = b ^ (1 << 5) + } + if c.ADRACKReq { + b = b ^ (1 << 6) + } + if c.ADR { + b = b ^ (1 << 7) + } + return []byte{b}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (c *FCtrl) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + c.fOptsLen = data[0] & ((1 << 3) ^ (1 << 2) ^ (1 << 1) ^ (1 << 0)) + c.FPending = data[0]&(1<<4) > 0 + c.ACK = data[0]&(1<<5) > 0 + c.ADRACKReq = data[0]&(1<<6) > 0 + c.ADR = data[0]&(1<<7) > 0 + return nil +} + +// FHDR represents the frame header. +type FHDR struct { + DevAddr DevAddr `json:"devAddr"` + FCtrl FCtrl `json:"fCtrl"` + FCnt uint32 `json:"fCnt"` // only the least-significant 16 bits will be marshalled + FOpts []MACCommand `json:"fOpts"` // max. number of allowed bytes is 15 +} + +// MarshalBinary marshals the object in binary form. +func (h FHDR) MarshalBinary() ([]byte, error) { + var b []byte + var err error + var opts []byte + + for _, mac := range h.FOpts { + b, err = mac.MarshalBinary() + if err != nil { + return []byte{}, err + } + opts = append(opts, b...) + } + h.FCtrl.fOptsLen = uint8(len(opts)) + if h.FCtrl.fOptsLen > 15 { + return []byte{}, errors.New("lorawan: max number of FOpts bytes is 15") + } + + out := make([]byte, 0, 7+h.FCtrl.fOptsLen) + b, err = h.DevAddr.MarshalBinary() + if err != nil { + return []byte{}, err + } + out = append(out, b...) + + b, err = h.FCtrl.MarshalBinary() + if err != nil { + return []byte{}, err + } + out = append(out, b...) + fCntBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(fCntBytes, h.FCnt) + out = append(out, fCntBytes[0:2]...) + out = append(out, opts...) + + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (h *FHDR) UnmarshalBinary(uplink bool, data []byte) error { + if len(data) < 7 { + return errors.New("lorawan: at least 7 bytes are expected") + } + + if err := h.DevAddr.UnmarshalBinary(data[0:4]); err != nil { + return err + } + if err := h.FCtrl.UnmarshalBinary(data[4:5]); err != nil { + return err + } + fCntBytes := make([]byte, 4) + copy(fCntBytes, data[5:7]) + h.FCnt = binary.LittleEndian.Uint32(fCntBytes) + + if len(data) > 7 { + var pLen int + for i := 0; i < len(data[7:]); i++ { + if _, s, err := GetMACPayloadAndSize(uplink, CID(data[7+i])); err != nil { + pLen = 0 + } else { + pLen = s + } + + // check if the remaining bytes are >= CID byte + payload size + if len(data[7+i:]) < pLen+1 { + return errors.New("lorawan: not enough remaining bytes") + } + + mc := MACCommand{} + if err := mc.UnmarshalBinary(uplink, data[7+i:7+i+1+pLen]); err != nil { + log.Printf("warning: unmarshal mac-command error (skipping remaining mac-command bytes): %s", err) + break + } + h.FOpts = append(h.FOpts, mc) + + // go to the next command (skip the payload bytes of the current command) + i = i + pLen + } + } + + return nil +} diff --git a/vendor/github.com/brocaar/lorawan/mac_commands.go b/vendor/github.com/brocaar/lorawan/mac_commands.go new file mode 100644 index 000000000..c26ef4b2a --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/mac_commands.go @@ -0,0 +1,741 @@ +//go:generate stringer -type=CID + +package lorawan + +import ( + "encoding/binary" + "errors" + "fmt" + "sync" +) + +// macPayloadMutex is used when registering proprietary MAC command payloads to +// the macPayloadRegistry. +var macPayloadMutex sync.RWMutex + +// CID defines the MAC command identifier. +type CID byte + +// MarshalText implements encoding.TextMarshaler. +func (c CID) MarshalText() ([]byte, error) { + return []byte(c.String()), nil +} + +// MAC commands as specified by the LoRaWAN R1.0 specs. Note that each *Req / *Ans +// has the same value. Based on the fact if a message is uplink or downlink +// you should use on or the other. +const ( + LinkCheckReq CID = 0x02 + LinkCheckAns CID = 0x02 + LinkADRReq CID = 0x03 + LinkADRAns CID = 0x03 + DutyCycleReq CID = 0x04 + DutyCycleAns CID = 0x04 + RXParamSetupReq CID = 0x05 + RXParamSetupAns CID = 0x05 + DevStatusReq CID = 0x06 + DevStatusAns CID = 0x06 + NewChannelReq CID = 0x07 + NewChannelAns CID = 0x07 + RXTimingSetupReq CID = 0x08 + RXTimingSetupAns CID = 0x08 + TXParamSetupReq CID = 0x09 + TXParamSetupAns CID = 0x09 + DLChannelReq CID = 0x0A + DLChannelAns CID = 0x0A + // 0x80 to 0xFF reserved for proprietary network command extensions +) + +// macPayloadInfo contains the info about a MAC payload +type macPayloadInfo struct { + size int + payload func() MACCommandPayload +} + +// macPayloadRegistry contains the info for uplink and downlink MAC payloads +// in the format map[uplink]map[CID]. +// Note that MAC command that do not have a payload are not included in this +// list. +var macPayloadRegistry = map[bool]map[CID]macPayloadInfo{ + false: map[CID]macPayloadInfo{ + LinkCheckAns: {2, func() MACCommandPayload { return &LinkCheckAnsPayload{} }}, + LinkADRReq: {4, func() MACCommandPayload { return &LinkADRReqPayload{} }}, + DutyCycleReq: {1, func() MACCommandPayload { return &DutyCycleReqPayload{} }}, + RXParamSetupReq: {4, func() MACCommandPayload { return &RX2SetupReqPayload{} }}, + NewChannelReq: {5, func() MACCommandPayload { return &NewChannelReqPayload{} }}, + RXTimingSetupReq: {1, func() MACCommandPayload { return &RXTimingSetupReqPayload{} }}, + TXParamSetupReq: {1, func() MACCommandPayload { return &TXParamSetupReqPayload{} }}, + DLChannelReq: {4, func() MACCommandPayload { return &DLChannelReqPayload{} }}, + }, + true: map[CID]macPayloadInfo{ + LinkADRAns: {1, func() MACCommandPayload { return &LinkADRAnsPayload{} }}, + RXParamSetupAns: {1, func() MACCommandPayload { return &RX2SetupAnsPayload{} }}, + DevStatusAns: {2, func() MACCommandPayload { return &DevStatusAnsPayload{} }}, + NewChannelAns: {1, func() MACCommandPayload { return &NewChannelAnsPayload{} }}, + DLChannelAns: {1, func() MACCommandPayload { return &DLChannelAnsPayload{} }}, + }, +} + +// DwellTime defines the dwell time type. +type DwellTime int + +// Possible dwell time options. +const ( + DwellTimeNoLimit DwellTime = iota + DwellTime400ms +) + +// GetMACPayloadAndSize returns a new MACCommandPayload instance and it's size. +func GetMACPayloadAndSize(uplink bool, c CID) (MACCommandPayload, int, error) { + macPayloadMutex.RLock() + defer macPayloadMutex.RUnlock() + + v, ok := macPayloadRegistry[uplink][c] + if !ok { + return nil, 0, fmt.Errorf("lorawan: payload unknown for uplink=%v and CID=%v", uplink, c) + } + + return v.payload(), v.size, nil +} + +// RegisterProprietaryMACCommand registers a proprietary MAC command. Note +// that there is no need to call this when the size of the payload is > 0 bytes. +func RegisterProprietaryMACCommand(uplink bool, cid CID, payloadSize int) error { + if !(cid >= 128 && cid <= 255) { + return fmt.Errorf("lorawan: invalid CID %x", cid) + } + + if payloadSize == 0 { + // no need to register the payload size + return nil + } + + macPayloadMutex.Lock() + defer macPayloadMutex.Unlock() + + macPayloadRegistry[uplink][cid] = macPayloadInfo{ + size: payloadSize, + payload: func() MACCommandPayload { return &ProprietaryMACCommandPayload{} }, + } + + return nil +} + +// MACCommandPayload is the interface that every MACCommand payload +// must implement. +type MACCommandPayload interface { + MarshalBinary() (data []byte, err error) + UnmarshalBinary(data []byte) error +} + +// MACCommand represents a MAC command with optional payload. +type MACCommand struct { + CID CID `json:"cid"` + Payload MACCommandPayload `json:"payload"` +} + +// MarshalBinary marshals the object in binary form. +func (m MACCommand) MarshalBinary() ([]byte, error) { + if !(m.CID >= 2 && m.CID <= 8) && !(m.CID >= 128) { + return nil, fmt.Errorf("lorawan: invalid CID %x", m.CID) + } + + b := []byte{byte(m.CID)} + if m.Payload != nil { + p, err := m.Payload.MarshalBinary() + if err != nil { + return nil, err + } + b = append(b, p...) + } + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (m *MACCommand) UnmarshalBinary(uplink bool, data []byte) error { + if len(data) == 0 { + return errors.New("lorawan: at least 1 byte of data is expected") + } + + m.CID = CID(data[0]) + if !(m.CID >= 2 && m.CID <= 8) && !(m.CID >= 128) { + return fmt.Errorf("lorawan: invalid CID %x", int(m.CID)) + } + + if len(data) > 1 { + p, _, err := GetMACPayloadAndSize(uplink, m.CID) + if err != nil { + return err + } + m.Payload = p + if err := m.Payload.UnmarshalBinary(data[1:]); err != nil { + return err + } + } + return nil +} + +// ProprietaryMACCommandPayload represents a proprietary payload. +type ProprietaryMACCommandPayload struct { + Bytes []byte `json:"bytes"` +} + +// MarshalBinary marshals the object into a slice of bytes. +func (p ProprietaryMACCommandPayload) MarshalBinary() ([]byte, error) { + return p.Bytes, nil +} + +// UnmarshalBinary decodes the object from a slice of bytes. +func (p *ProprietaryMACCommandPayload) UnmarshalBinary(data []byte) error { + p.Bytes = data + return nil +} + +// LinkCheckAnsPayload represents the LinkCheckAns payload. +type LinkCheckAnsPayload struct { + Margin uint8 `json:"margin"` + GwCnt uint8 `json:"gwCnt"` +} + +// MarshalBinary marshals the object in binary form. +func (p LinkCheckAnsPayload) MarshalBinary() ([]byte, error) { + return []byte{byte(p.Margin), byte(p.GwCnt)}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *LinkCheckAnsPayload) UnmarshalBinary(data []byte) error { + if len(data) != 2 { + return errors.New("lorawan: 2 bytes of data are expected") + } + p.Margin = uint8(data[0]) + p.GwCnt = uint8(data[1]) + return nil +} + +// ChMask encodes the channels usable for uplink access. 0 = channel 1, +// 15 = channel 16. +type ChMask [16]bool + +// MarshalBinary marshals the object in binary form. +func (m ChMask) MarshalBinary() ([]byte, error) { + b := make([]byte, 2) + for i := uint8(0); i < 16; i++ { + if m[i] { + b[i/8] = b[i/8] ^ 1<<(i%8) + } + } + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (m *ChMask) UnmarshalBinary(data []byte) error { + if len(data) != 2 { + return errors.New("lorawan: 2 bytes of data are expected") + } + for i, b := range data { + for j := uint8(0); j < 8; j++ { + if b&(1< 0 { + m[uint8(i)*8+j] = true + } + } + } + return nil +} + +// Redundancy represents the redundancy field. +type Redundancy struct { + ChMaskCntl uint8 `json:"chMaskCntl"` + NbRep uint8 `json:"nbRep"` +} + +// MarshalBinary marshals the object in binary form. +func (r Redundancy) MarshalBinary() ([]byte, error) { + b := make([]byte, 1) + if r.NbRep > 15 { + return b, errors.New("lorawan: max value of NbRep is 15") + } + if r.ChMaskCntl > 7 { + return b, errors.New("lorawan: max value of ChMaskCntl is 7") + } + b[0] = r.NbRep ^ (r.ChMaskCntl << 4) + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (r *Redundancy) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + r.NbRep = data[0] & ((1 << 3) ^ (1 << 2) ^ (1 << 1) ^ (1 << 0)) + r.ChMaskCntl = (data[0] & ((1 << 6) ^ (1 << 5) ^ (1 << 4))) >> 4 + return nil +} + +// LinkADRReqPayload represents the LinkADRReq payload. +type LinkADRReqPayload struct { + DataRate uint8 `json:"dataRate"` + TXPower uint8 `json:"txPower"` + ChMask ChMask `json:"chMask"` + Redundancy Redundancy `json:"redundancy"` +} + +// MarshalBinary marshals the object in binary form. +func (p LinkADRReqPayload) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, 4) + if p.DataRate > 15 { + return b, errors.New("lorawan: the max value of DataRate is 15") + } + if p.TXPower > 15 { + return b, errors.New("lorawan: the max value of TXPower is 15") + } + + cm, err := p.ChMask.MarshalBinary() + if err != nil { + return b, err + } + r, err := p.Redundancy.MarshalBinary() + if err != nil { + return b, err + } + + b = append(b, p.TXPower^(p.DataRate<<4)) + b = append(b, cm...) + b = append(b, r...) + + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *LinkADRReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("lorawan: 4 bytes of data are expected") + } + p.DataRate = (data[0] & ((1 << 7) ^ (1 << 6) ^ (1 << 5) ^ (1 << 4))) >> 4 + p.TXPower = data[0] & ((1 << 3) ^ (1 << 2) ^ (1 << 1) ^ (1 << 0)) + + if err := p.ChMask.UnmarshalBinary(data[1:3]); err != nil { + return err + } + if err := p.Redundancy.UnmarshalBinary(data[3:4]); err != nil { + return err + } + return nil +} + +// LinkADRAnsPayload represents the LinkADRAns payload. +type LinkADRAnsPayload struct { + ChannelMaskACK bool `json:"channelMaskAck"` + DataRateACK bool `json:"dataRateAck"` + PowerACK bool `json:"powerAck"` +} + +// MarshalBinary marshals the object in binary form. +func (p LinkADRAnsPayload) MarshalBinary() ([]byte, error) { + var b byte + if p.ChannelMaskACK { + b = b ^ (1 << 0) + } + if p.DataRateACK { + b = b ^ (1 << 1) + } + if p.PowerACK { + b = b ^ (1 << 2) + } + return []byte{b}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *LinkADRAnsPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + if data[0]&(1<<0) > 0 { + p.ChannelMaskACK = true + } + if data[0]&(1<<1) > 0 { + p.DataRateACK = true + } + if data[0]&(1<<2) > 0 { + p.PowerACK = true + } + return nil +} + +// DutyCycleReqPayload represents the DutyCycleReq payload. +type DutyCycleReqPayload struct { + MaxDCycle uint8 `json:"maxDCycle"` +} + +// MarshalBinary marshals the object in binary form. +func (p DutyCycleReqPayload) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, 1) + if p.MaxDCycle > 15 && p.MaxDCycle < 255 { + return b, errors.New("lorawan: only a MaxDCycle value of 0 - 15 and 255 is allowed") + } + b = append(b, p.MaxDCycle) + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *DutyCycleReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + p.MaxDCycle = data[0] + return nil +} + +// DLSettings represents the DLSettings fields (downlink settings). +type DLSettings struct { + RX2DataRate uint8 `json:"rx2DataRate"` + RX1DROffset uint8 `json:"rx1DROffset"` +} + +// MarshalBinary marshals the object in binary form. +func (s DLSettings) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, 1) + if s.RX2DataRate > 15 { + return b, errors.New("lorawan: max value of RX2DataRate is 15") + } + if s.RX1DROffset > 7 { + return b, errors.New("lorawan: max value of RX1DROffset is 7") + } + b = append(b, s.RX2DataRate^(s.RX1DROffset<<4)) + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (s *DLSettings) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + s.RX2DataRate = data[0] & ((1 << 3) ^ (1 << 2) ^ (1 << 1) ^ (1 << 0)) + s.RX1DROffset = (data[0] & ((1 << 6) ^ (1 << 5) ^ (1 << 4))) >> 4 + return nil +} + +// RX2SetupReqPayload represents the RX2SetupReq payload. +type RX2SetupReqPayload struct { + Frequency uint32 `json:"frequency"` + DLSettings DLSettings `json:"dlSettings"` +} + +// MarshalBinary marshals the object in binary form. +func (p RX2SetupReqPayload) MarshalBinary() ([]byte, error) { + b := make([]byte, 5) + if p.Frequency/100 >= 16777216 { // 2^24 + return b, errors.New("lorawan: max value of Frequency is 2^24-1") + } + if p.Frequency%100 != 0 { + return b, errors.New("lorawan: Frequency must be a multiple of 100") + } + bytes, err := p.DLSettings.MarshalBinary() + if err != nil { + return b, err + } + b[0] = bytes[0] + + binary.LittleEndian.PutUint32(b[1:5], p.Frequency/100) + // we don't return the last octet which is fine since we're only interested + // in the 24 LSB of Frequency + return b[0:4], nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *RX2SetupReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("lorawan: 4 bytes of data are expected") + } + if err := p.DLSettings.UnmarshalBinary(data[0:1]); err != nil { + return err + } + // append one block of empty bits at the end of the slice since the + // binary to uint32 expects 32 bits. + b := make([]byte, len(data)) + copy(b, data) + b = append(b, byte(0)) + p.Frequency = binary.LittleEndian.Uint32(b[1:5]) * 100 + return nil +} + +// RX2SetupAnsPayload represents the RX2SetupAns payload. +type RX2SetupAnsPayload struct { + ChannelACK bool `json:"channelAck"` + RX2DataRateACK bool `json:"rx2DataRateAck"` + RX1DROffsetACK bool `json:"rx1DROffsetAck"` +} + +// MarshalBinary marshals the object in binary form. +func (p RX2SetupAnsPayload) MarshalBinary() ([]byte, error) { + var b byte + if p.ChannelACK { + b = b ^ (1 << 0) + } + if p.RX2DataRateACK { + b = b ^ (1 << 1) + } + if p.RX1DROffsetACK { + b = b ^ (1 << 2) + } + return []byte{b}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *RX2SetupAnsPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + p.ChannelACK = data[0]&(1<<0) > 0 + p.RX2DataRateACK = data[0]&(1<<1) > 0 + p.RX1DROffsetACK = data[0]&(1<<2) > 0 + return nil +} + +// DevStatusAnsPayload represents the DevStatusAns payload. +type DevStatusAnsPayload struct { + Battery uint8 `json:"battery"` + Margin int8 `json:"margin"` +} + +// MarshalBinary marshals the object in binary form. +func (p DevStatusAnsPayload) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, 2) + if p.Margin < -32 { + return b, errors.New("lorawan: min value of Margin is -32") + } + if p.Margin > 31 { + return b, errors.New("lorawan: max value of Margin is 31") + } + + b = append(b, p.Battery) + if p.Margin < 0 { + b = append(b, uint8(64+p.Margin)) + } else { + b = append(b, uint8(p.Margin)) + } + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *DevStatusAnsPayload) UnmarshalBinary(data []byte) error { + if len(data) != 2 { + return errors.New("lorawan: 2 bytes of data are expected") + } + p.Battery = data[0] + if data[1] > 31 { + p.Margin = int8(data[1]) - 64 + } else { + p.Margin = int8(data[1]) + } + return nil +} + +// NewChannelReqPayload represents the NewChannelReq payload. +type NewChannelReqPayload struct { + ChIndex uint8 `json:"chIndex"` + Freq uint32 `json:"freq"` + MaxDR uint8 `json:"maxDR"` + MinDR uint8 `json:"minDR"` +} + +// MarshalBinary marshals the object in binary form. +func (p NewChannelReqPayload) MarshalBinary() ([]byte, error) { + b := make([]byte, 5) + if p.Freq/100 >= 16777216 { // 2^24 + return b, errors.New("lorawan: max value of Freq is 2^24 - 1") + } + if p.Freq%100 != 0 { + return b, errors.New("lorawan: Freq must be a multiple of 100") + } + if p.MaxDR > 15 { + return b, errors.New("lorawan: max value of MaxDR is 15") + } + if p.MinDR > 15 { + return b, errors.New("lorawan: max value of MinDR is 15") + } + + // we're borrowing the last byte b[4] because PutUint32 needs 4 bytes, + // the last byte b[4] will be set to 0 because max Freq = 2^24 - 1 + binary.LittleEndian.PutUint32(b[1:5], p.Freq/100) + b[0] = p.ChIndex + b[4] = p.MinDR ^ (p.MaxDR << 4) + + return b, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *NewChannelReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 5 { + return errors.New("lorawan: 5 bytes of data are expected") + } + p.ChIndex = data[0] + p.MinDR = data[4] & ((1 << 3) ^ (1 << 2) ^ (1 << 1) ^ (1 << 0)) + p.MaxDR = (data[4] & ((1 << 7) ^ (1 << 6) ^ (1 << 5) ^ (1 << 4))) >> 4 + + b := make([]byte, len(data)) + copy(b, data) + b[4] = byte(0) + p.Freq = binary.LittleEndian.Uint32(b[1:5]) * 100 + return nil +} + +// NewChannelAnsPayload represents the NewChannelAns payload. +type NewChannelAnsPayload struct { + ChannelFrequencyOK bool `json:"channelFrequencyOK"` + DataRateRangeOK bool `json:"dataRateRangeOK"` +} + +// MarshalBinary marshals the object in binary form. +func (p NewChannelAnsPayload) MarshalBinary() ([]byte, error) { + var b byte + if p.ChannelFrequencyOK { + b = (1 << 0) + } + if p.DataRateRangeOK { + b = b ^ (1 << 1) + } + return []byte{b}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *NewChannelAnsPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + p.ChannelFrequencyOK = data[0]&(1<<0) > 0 + p.DataRateRangeOK = data[0]&(1<<1) > 0 + return nil +} + +// RXTimingSetupReqPayload represents the RXTimingSetupReq payload. +type RXTimingSetupReqPayload struct { + Delay uint8 `json:"delay"` // 0=1s, 1=1s, 2=2s, ... 15=15s +} + +// MarshalBinary marshals the object in binary form. +func (p RXTimingSetupReqPayload) MarshalBinary() ([]byte, error) { + if p.Delay > 15 { + return []byte{}, errors.New("lorawan: the max value of Delay is 15") + } + return []byte{p.Delay}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *RXTimingSetupReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + p.Delay = data[0] + return nil +} + +// TXParamSetupReqPayload represents the TXParamSetupReq payload. +type TXParamSetupReqPayload struct { + DownlinkDwelltime DwellTime `json:"downlinkDwellTime"` + UplinkDwellTime DwellTime `json:"uplinkDwellTime"` + MaxEIRP uint8 `json:"maxEIRP"` +} + +// MarshalBinary encodes the object into a bytes. +func (p TXParamSetupReqPayload) MarshalBinary() ([]byte, error) { + var b uint8 + for i, v := range []uint8{8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36} { + if v == p.MaxEIRP { + b = uint8(i) + } + } + if b == 0 { + return nil, errors.New("lorawan: invalid MaxEIRP value") + } + + if p.UplinkDwellTime == DwellTime400ms { + b = b ^ (1 << 4) + } + if p.DownlinkDwelltime == DwellTime400ms { + b = b ^ (1 << 5) + } + + return []byte{b}, nil +} + +// UnmarshalBinary decodes the object from bytes. +func (p *TXParamSetupReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + + if data[0]&(1<<4) > 0 { + p.UplinkDwellTime = DwellTime400ms + } + if data[0]&(1<<5) > 0 { + p.DownlinkDwelltime = DwellTime400ms + } + p.MaxEIRP = []uint8{8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36}[data[0]&15] + + return nil +} + +// DLChannelReqPayload represents the DLChannelReq payload. +type DLChannelReqPayload struct { + ChIndex uint8 `json:"chIndex"` + Freq uint32 `json:"freq"` +} + +// MarshalBinary encodes the object into bytes. +func (p DLChannelReqPayload) MarshalBinary() ([]byte, error) { + b := make([]byte, 5) // we need one byte more for PutUint32 + if p.Freq/100 >= 16777216 { // 2^24 + return b, errors.New("lorawan: max value of Freq is 2^24 - 1") + } + + if p.Freq%100 != 0 { + return b, errors.New("lorawan: Freq must be a multiple of 100") + } + + b[0] = p.ChIndex + binary.LittleEndian.PutUint32(b[1:5], p.Freq/100) + + return b[0:4], nil +} + +// UnmarshalBinary decodes the object from bytes. +func (p *DLChannelReqPayload) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("lorawan: 4 bytes of data are expected") + } + + p.ChIndex = data[0] + b := make([]byte, 4) + copy(b, data[1:]) + p.Freq = binary.LittleEndian.Uint32(b) * 100 + return nil +} + +// DLChannelAnsPayload represents the DLChannelAns payload. +type DLChannelAnsPayload struct { + UplinkFrequencyExists bool `json:"uplinkFrequencyExists"` + ChannelFrequencyOK bool `json:"channelFrequencyOK"` +} + +// MarshalBinary encodes the object into bytes. +func (p DLChannelAnsPayload) MarshalBinary() ([]byte, error) { + var b byte + if p.ChannelFrequencyOK { + b = b ^ 1 + } + if p.UplinkFrequencyExists { + b = b ^ (1 << 1) + } + return []byte{b}, nil +} + +// UnmarshalBinary decodes the object from bytes. +func (p *DLChannelAnsPayload) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + + p.ChannelFrequencyOK = data[0]&1 > 0 + p.UplinkFrequencyExists = data[0]&(1<<1) > 0 + return nil +} diff --git a/vendor/github.com/brocaar/lorawan/macpayload.go b/vendor/github.com/brocaar/lorawan/macpayload.go new file mode 100644 index 000000000..431bd3511 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/macpayload.go @@ -0,0 +1,152 @@ +package lorawan + +import ( + "errors" + "fmt" + "log" +) + +// MACPayload represents the MAC payload. Use NewMACPayload for creating a new +// MACPayload. +type MACPayload struct { + FHDR FHDR `json:"fhdr"` + FPort *uint8 `json:"fPort"` // optional, but must be set when FRMPayload is set + FRMPayload []Payload `json:"frmPayload"` +} + +func (p MACPayload) marshalPayload() ([]byte, error) { + var out []byte + var b []byte + var err error + for _, fp := range p.FRMPayload { + if mac, ok := fp.(*MACCommand); ok { + if p.FPort == nil || (p.FPort != nil && *p.FPort != 0) { + return []byte{}, errors.New("lorawan: a MAC command is only allowed when FPort=0") + } + b, err = mac.MarshalBinary() + } else { + b, err = fp.MarshalBinary() + } + if err != nil { + return nil, err + } + out = append(out, b...) + } + return out, nil +} + +func (p *MACPayload) decodeFRMPayloadToMACCommands(uplink bool) error { + if p.FPort == nil || *p.FPort != 0 { + return fmt.Errorf("lorawan: FPort must be 0 when calling decodeFRMPayloadToMACCommands") + } + + if len(p.FRMPayload) != 1 { + return fmt.Errorf("lorawan: exactly 1 Payload was expected in FRMPayload") + } + + dataPL, ok := p.FRMPayload[0].(*DataPayload) + if !ok { + return fmt.Errorf("lorawan: expected *DataPayload, got %T", p.FRMPayload[0]) + } + + var pLen int + p.FRMPayload = make([]Payload, 0) + for i := 0; i < len(dataPL.Bytes); i++ { + if _, s, err := GetMACPayloadAndSize(uplink, CID(dataPL.Bytes[i])); err != nil { + pLen = 0 + } else { + pLen = s + } + + // check if the remaining bytes are >= CID byte + payload size + if len(dataPL.Bytes[i:]) < pLen+1 { + return errors.New("lorawan: not enough remaining bytes") + } + + mc := &MACCommand{} + if err := mc.UnmarshalBinary(uplink, dataPL.Bytes[i:i+1+pLen]); err != nil { + log.Printf("warning: unmarshal mac-command error (skipping remaining mac-command bytes): %s", err) + break + } + p.FRMPayload = append(p.FRMPayload, mc) + + // go to the next command (skip the payload bytes of the current command) + i = i + pLen + } + + return nil +} + +// MarshalBinary marshals the object in binary form. +func (p MACPayload) MarshalBinary() ([]byte, error) { + var b []byte + var out []byte + var err error + + b, err = p.FHDR.MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, b...) + + if p.FPort == nil { + if len(p.FRMPayload) != 0 { + return nil, errors.New("lorawan: FPort must be set when FRMPayload is not empty") + } + return out, nil + } else if len(p.FHDR.FOpts) != 0 && *p.FPort == 0 { + return nil, errors.New("lorawan: FPort must not be 0 when FOpts are set") + } + + out = append(out, *p.FPort) + + if b, err = p.marshalPayload(); err != nil { + return nil, err + } + out = append(out, b...) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *MACPayload) UnmarshalBinary(uplink bool, data []byte) error { + dataLen := len(data) + + // check that there are enough bytes to decode a minimal FHDR + if dataLen < 7 { + return errors.New("lorawan: at least 7 bytes needed to decode FHDR") + } + + // unmarshal FCtrl so we know the FOptsLen + if err := p.FHDR.FCtrl.UnmarshalBinary(data[4:5]); err != nil { + return err + } + + // check that there are at least as many bytes as FOptsLen claims + if dataLen < 7+int(p.FHDR.FCtrl.fOptsLen) { + return errors.New("lorawan: not enough bytes to decode FHDR") + } + + // decode the full FHDR (including optional FOpts) + if err := p.FHDR.UnmarshalBinary(uplink, data[0:7+p.FHDR.FCtrl.fOptsLen]); err != nil { + return err + } + + // decode the optional FPort + if dataLen >= 7+int(p.FHDR.FCtrl.fOptsLen)+1 { + fPort := uint8(data[7+int(p.FHDR.FCtrl.fOptsLen)]) + p.FPort = &fPort + } + + // decode the rest of the payload (if present) + if dataLen > 7+int(p.FHDR.FCtrl.fOptsLen)+1 { + if p.FPort != nil && *p.FPort == 0 && p.FHDR.FCtrl.fOptsLen > 0 { + return errors.New("lorawan: FPort must not be 0 when FOpts are set") + } + + // even when FPort = 0, we store the mac-commands within a DataPayload. + // only after decryption we're able to unmarshal them. + p.FRMPayload = []Payload{&DataPayload{Bytes: data[7+p.FHDR.FCtrl.fOptsLen+1:]}} + } + + return nil +} diff --git a/vendor/github.com/brocaar/lorawan/major_string.go b/vendor/github.com/brocaar/lorawan/major_string.go new file mode 100644 index 000000000..1c3d38f38 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/major_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=Major"; DO NOT EDIT. + +package lorawan + +import "fmt" + +const _Major_name = "LoRaWANR1" + +var _Major_index = [...]uint8{0, 9} + +func (i Major) String() string { + if i >= Major(len(_Major_index)-1) { + return fmt.Sprintf("Major(%d)", i) + } + return _Major_name[_Major_index[i]:_Major_index[i+1]] +} diff --git a/vendor/github.com/brocaar/lorawan/mtype_string.go b/vendor/github.com/brocaar/lorawan/mtype_string.go new file mode 100644 index 000000000..448a8db1d --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/mtype_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=MType"; DO NOT EDIT. + +package lorawan + +import "fmt" + +const _MType_name = "JoinRequestJoinAcceptUnconfirmedDataUpUnconfirmedDataDownConfirmedDataUpConfirmedDataDownRFUProprietary" + +var _MType_index = [...]uint8{0, 11, 21, 38, 57, 72, 89, 92, 103} + +func (i MType) String() string { + if i >= MType(len(_MType_index)-1) { + return fmt.Sprintf("MType(%d)", i) + } + return _MType_name[_MType_index[i]:_MType_index[i+1]] +} diff --git a/vendor/github.com/brocaar/lorawan/netid.go b/vendor/github.com/brocaar/lorawan/netid.go new file mode 100644 index 000000000..a9d1813ce --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/netid.go @@ -0,0 +1,37 @@ +package lorawan + +import ( + "encoding/hex" + "fmt" +) + +// NetID represents the NetID. +type NetID [3]byte + +// String implements fmt.Stringer. +func (n NetID) String() string { + return hex.EncodeToString(n[:]) +} + +// MarshalText implements encoding.TextMarshaler. +func (n NetID) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (n *NetID) UnmarshalText(text []byte) error { + b, err := hex.DecodeString(string(text)) + if err != nil { + return err + } + if len(b) != len(n) { + return fmt.Errorf("lorawan: exactly %d bytes are expected", len(n)) + } + copy(n[:], b) + return nil +} + +// NwkID returns the NwkID bits of the NetID. +func (n NetID) NwkID() byte { + return n[2] & 127 // 7 lsb +} diff --git a/vendor/github.com/brocaar/lorawan/payload.go b/vendor/github.com/brocaar/lorawan/payload.go new file mode 100644 index 000000000..589e624a6 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/payload.go @@ -0,0 +1,287 @@ +package lorawan + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" +) + +// EUI64 data type +type EUI64 [8]byte + +// MarshalText implements encoding.TextMarshaler. +func (e EUI64) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (e *EUI64) UnmarshalText(text []byte) error { + b, err := hex.DecodeString(string(text)) + if err != nil { + return err + } + if len(e) != len(b) { + return fmt.Errorf("lorawan: exactly %d bytes are expected", len(e)) + } + copy(e[:], b) + return nil +} + +// String implement fmt.Stringer. +func (e EUI64) String() string { + return hex.EncodeToString(e[:]) +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (e EUI64) MarshalBinary() ([]byte, error) { + out := make([]byte, len(e)) + // little endian + for i, v := range e { + out[len(e)-i-1] = v + } + return out, nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (e *EUI64) UnmarshalBinary(data []byte) error { + if len(data) != len(e) { + return fmt.Errorf("lorawan: %d bytes of data are expected", len(e)) + } + for i, v := range data { + // little endian + e[len(e)-i-1] = v + } + return nil +} + +// Scan implements sql.Scanner. +func (e *EUI64) Scan(src interface{}) error { + b, ok := src.([]byte) + if !ok { + return errors.New("lorawan: []byte type expected") + } + if len(b) != len(e) { + return fmt.Errorf("lorawan []byte must have length %d", len(e)) + } + copy(e[:], b) + return nil +} + +// DevNonce represents a 2 byte dev-nonce. +type DevNonce [2]byte + +// String implements fmt.Stringer. +func (n DevNonce) String() string { + return hex.EncodeToString(n[:]) +} + +// MarshalText implements encoding.TextMarshaler. +func (n DevNonce) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// AppNonce represents a 3 byte app-nonce. +type AppNonce [3]byte + +// String implements fmt.Stringer. +func (n AppNonce) String() string { + return hex.EncodeToString(n[:]) +} + +// MarshalText implements encoding.TextMarshaler. +func (n AppNonce) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// Payload is the interface that every payload needs to implement. +// Since it might be a MACPayload, an indication must be given if +// the direction is uplink or downlink (it has different payloads +// for the same CID, based on direction). +type Payload interface { + MarshalBinary() (data []byte, err error) + UnmarshalBinary(uplink bool, data []byte) error +} + +// DataPayload represents a slice of bytes. +type DataPayload struct { + Bytes []byte `json:"bytes"` +} + +// MarshalBinary marshals the object in binary form. +func (p DataPayload) MarshalBinary() ([]byte, error) { + return p.Bytes, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *DataPayload) UnmarshalBinary(uplink bool, data []byte) error { + p.Bytes = make([]byte, len(data)) + copy(p.Bytes, data) + return nil +} + +// JoinRequestPayload represents the join-request message payload. +type JoinRequestPayload struct { + AppEUI EUI64 `json:"appEUI"` + DevEUI EUI64 `json:"devEUI"` + DevNonce DevNonce `json:"devNonce"` +} + +// MarshalBinary marshals the object in binary form. +func (p JoinRequestPayload) MarshalBinary() ([]byte, error) { + out := make([]byte, 0, 18) + b, err := p.AppEUI.MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, b...) + b, err = p.DevEUI.MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, b...) + // little endian + out = append(out, p.DevNonce[1], p.DevNonce[0]) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *JoinRequestPayload) UnmarshalBinary(uplink bool, data []byte) error { + if len(data) != 18 { + return errors.New("lorawan: 18 bytes of data are expected") + } + if err := p.AppEUI.UnmarshalBinary(data[0:8]); err != nil { + return err + } + if err := p.DevEUI.UnmarshalBinary(data[8:16]); err != nil { + return err + } + // little endian + p.DevNonce[1] = data[16] + p.DevNonce[0] = data[17] + return nil +} + +// CFList represents a list of channel frequencies. Each frequency is in Hz +// and must be multiple of 100, (since the frequency will be divided by 100 +// on encoding), the max allowed value is 2^24-1 * 100. +type CFList [5]uint32 + +// MarshalBinary marshals the object in binary form. +func (l CFList) MarshalBinary() ([]byte, error) { + out := make([]byte, 0, 16) + for _, f := range l { + if f%100 != 0 { + return nil, errors.New("lorawan: frequency must be a multiple of 100") + } + f = f / 100 + if f > 16777215 { // 2^24 - 1 + return nil, errors.New("lorawan: max value of frequency is 2^24-1") + } + b := make([]byte, 4, 4) + binary.LittleEndian.PutUint32(b, f) + out = append(out, b[:3]...) + } + // last byte is 0 / RFU + return append(out, 0), nil +} + +// UnmarshalBinary decodes the object from binary form. +func (l *CFList) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return errors.New("lorawan: 16 bytes of data are expected") + } + for i := 0; i < 5; i++ { + l[i] = binary.LittleEndian.Uint32([]byte{ + data[i*3], + data[i*3+1], + data[i*3+2], + 0, + }) * 100 + } + + return nil +} + +// JoinAcceptPayload represents the join-accept message payload. +type JoinAcceptPayload struct { + AppNonce AppNonce `json:"appNonce"` + NetID NetID `json:"netID"` + DevAddr DevAddr `json:"devAddr"` + DLSettings DLSettings `json:"dlSettings"` + RXDelay uint8 `json:"rxDelay"` // 0=1s, 1=1s, 2=2s, ... 15=15s + CFList *CFList `json:"cFlist"` +} + +// MarshalBinary marshals the object in binary form. +func (p JoinAcceptPayload) MarshalBinary() ([]byte, error) { + if p.RXDelay > 15 { + return nil, errors.New("lorawan: the max value of RXDelay is 15") + } + + out := make([]byte, 0, 12) + + // little endian + for i := len(p.AppNonce) - 1; i >= 0; i-- { + out = append(out, p.AppNonce[i]) + } + for i := len(p.NetID) - 1; i >= 0; i-- { + out = append(out, p.NetID[i]) + } + + b, err := p.DevAddr.MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, b...) + + b, err = p.DLSettings.MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, b...) + out = append(out, byte(p.RXDelay)) + + if p.CFList != nil { + b, err = p.CFList.MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, b...) + } + + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *JoinAcceptPayload) UnmarshalBinary(uplink bool, data []byte) error { + l := len(data) + if l != 12 && l != 28 { + return errors.New("lorawan: 12 or 28 bytes of data are expected (28 bytes if CFList is present)") + } + + // little endian + for i, v := range data[0:3] { + p.AppNonce[2-i] = v + } + for i, v := range data[3:6] { + p.NetID[2-i] = v + } + + if err := p.DevAddr.UnmarshalBinary(data[6:10]); err != nil { + return err + } + if err := p.DLSettings.UnmarshalBinary(data[10:11]); err != nil { + return err + } + p.RXDelay = uint8(data[11]) + + if l == 28 { + p.CFList = &CFList{} + if err := p.CFList.UnmarshalBinary(data[12:]); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/brocaar/lorawan/phypayload.go b/vendor/github.com/brocaar/lorawan/phypayload.go new file mode 100644 index 000000000..0cfa07029 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan/phypayload.go @@ -0,0 +1,592 @@ +//go:generate stringer -type=MType +//go:generate stringer -type=Major + +package lorawan + +import ( + "crypto/aes" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/jacobsa/crypto/cmac" +) + +// MType represents the message type. +type MType byte + +// MarshalText implements encoding.TextMarshaler. +func (m MType) MarshalText() ([]byte, error) { + return []byte(m.String()), nil +} + +// Major defines the major version of data message. +type Major byte + +// Supported message types (MType) +const ( + JoinRequest MType = iota + JoinAccept + UnconfirmedDataUp + UnconfirmedDataDown + ConfirmedDataUp + ConfirmedDataDown + RFU + Proprietary +) + +// Supported major versions +const ( + LoRaWANR1 Major = 0 +) + +// MarshalText implements encoding.TextMarshaler. +func (m Major) MarshalText() ([]byte, error) { + return []byte(m.String()), nil +} + +// AES128Key represents a 128 bit AES key. +type AES128Key [16]byte + +// String implements fmt.Stringer. +func (k AES128Key) String() string { + return hex.EncodeToString(k[:]) +} + +// MarshalText implements encoding.TextMarshaler. +func (k AES128Key) MarshalText() ([]byte, error) { + return []byte(k.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (k *AES128Key) UnmarshalText(text []byte) error { + b, err := hex.DecodeString(string(text)) + if err != nil { + return err + } + if len(b) != len(k) { + return fmt.Errorf("lorawan: exactly %d bytes are expected", len(k)) + } + copy(k[:], b) + return nil +} + +// Scan implements sql.Scanner. +func (k *AES128Key) Scan(src interface{}) error { + b, ok := src.([]byte) + if !ok { + return errors.New("lorawan: []byte type expected") + } + if len(b) != len(k) { + return fmt.Errorf("lorawan []byte must have length %d", len(k)) + } + copy(k[:], b) + return nil +} + +// MIC represents the message integrity code. +type MIC [4]byte + +// String implements fmt.Stringer. +func (m MIC) String() string { + return hex.EncodeToString(m[:]) +} + +// MarshalText implements encoding.TextMarshaler. +func (m MIC) MarshalText() ([]byte, error) { + return []byte(m.String()), nil +} + +// MHDR represents the MAC header. +type MHDR struct { + MType MType `json:"mType"` + Major Major `json:"major"` +} + +// MarshalBinary marshals the object in binary form. +func (h MHDR) MarshalBinary() ([]byte, error) { + return []byte{byte(h.Major) ^ (byte(h.MType) << 5)}, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (h *MHDR) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("lorawan: 1 byte of data is expected") + } + h.Major = Major(data[0] & 3) + h.MType = MType((data[0] & 224) >> 5) + return nil +} + +// PHYPayload represents the physical payload. +type PHYPayload struct { + MHDR MHDR `json:"mhdr"` + MACPayload Payload `json:"macPayload"` + MIC MIC `json:"mic"` +} + +// calculateMIC calculates and returns the MIC. +func (p PHYPayload) calculateMIC(key AES128Key) ([]byte, error) { + if p.MACPayload == nil { + return []byte{}, errors.New("lorawan: MACPayload should not be empty") + } + + macPayload, ok := p.MACPayload.(*MACPayload) + if !ok { + return []byte{}, errors.New("lorawan: MACPayload should be of type *MACPayload") + } + + var b []byte + var err error + var micBytes []byte + + b, err = p.MHDR.MarshalBinary() + if err != nil { + return nil, err + } + micBytes = append(micBytes, b...) + + b, err = macPayload.MarshalBinary() + if err != nil { + return nil, err + } + micBytes = append(micBytes, b...) + + b0 := make([]byte, 16) + b0[0] = 0x49 + if !p.isUplink() { + b0[5] = 1 + } + b, err = macPayload.FHDR.DevAddr.MarshalBinary() + if err != nil { + return nil, err + } + copy(b0[6:10], b) + binary.LittleEndian.PutUint32(b0[10:14], macPayload.FHDR.FCnt) + b0[15] = byte(len(micBytes)) + + hash, err := cmac.New(key[:]) + if err != nil { + return nil, err + } + + if _, err = hash.Write(b0); err != nil { + return nil, err + } + if _, err = hash.Write(micBytes); err != nil { + return nil, err + } + + hb := hash.Sum([]byte{}) + if len(hb) < 4 { + return nil, errors.New("lorawan: the hash returned less than 4 bytes") + } + return hb[0:4], nil +} + +// calculateJoinRequestMIC calculates and returns the join-request MIC. +func (p PHYPayload) calculateJoinRequestMIC(key AES128Key) ([]byte, error) { + if p.MACPayload == nil { + return []byte{}, errors.New("lorawan: MACPayload should not be empty") + } + jrPayload, ok := p.MACPayload.(*JoinRequestPayload) + if !ok { + return []byte{}, errors.New("lorawan: MACPayload should be of type *JoinRequestPayload") + } + + micBytes := make([]byte, 0, 19) + + b, err := p.MHDR.MarshalBinary() + if err != nil { + return []byte{}, err + } + micBytes = append(micBytes, b...) + + b, err = jrPayload.MarshalBinary() + if err != nil { + return nil, err + } + micBytes = append(micBytes, b...) + + hash, err := cmac.New(key[:]) + if err != nil { + return []byte{}, err + } + if _, err = hash.Write(micBytes); err != nil { + return nil, err + } + hb := hash.Sum([]byte{}) + if len(hb) < 4 { + return []byte{}, errors.New("lorawan: the hash returned less than 4 bytes") + } + return hb[0:4], nil +} + +// calculateJoinAcceptMIC calculates and returns the join-accept MIC. +func (p PHYPayload) calculateJoinAcceptMIC(key AES128Key) ([]byte, error) { + if p.MACPayload == nil { + return []byte{}, errors.New("lorawan: MACPayload should not be empty") + } + jaPayload, ok := p.MACPayload.(*JoinAcceptPayload) + if !ok { + return []byte{}, errors.New("lorawan: MACPayload should be of type *JoinAcceptPayload") + } + + micBytes := make([]byte, 0, 13) + + b, err := p.MHDR.MarshalBinary() + if err != nil { + return []byte{}, err + } + micBytes = append(micBytes, b...) + + b, err = jaPayload.MarshalBinary() + if err != nil { + return nil, err + } + micBytes = append(micBytes, b...) + + hash, err := cmac.New(key[:]) + if err != nil { + return []byte{}, err + } + if _, err = hash.Write(micBytes); err != nil { + return nil, err + } + hb := hash.Sum([]byte{}) + if len(hb) < 4 { + return []byte{}, errors.New("lorawan: the hash returned less than 4 bytes") + } + return hb[0:4], nil +} + +// SetMIC calculates and sets the MIC field. +func (p *PHYPayload) SetMIC(key AES128Key) error { + var mic []byte + var err error + + switch p.MACPayload.(type) { + case *JoinRequestPayload: + mic, err = p.calculateJoinRequestMIC(key) + case *JoinAcceptPayload: + mic, err = p.calculateJoinAcceptMIC(key) + default: + mic, err = p.calculateMIC(key) + } + + if err != nil { + return err + } + if len(mic) != 4 { + return errors.New("lorawan: a MIC of 4 bytes is expected") + } + for i, v := range mic { + p.MIC[i] = v + } + return nil +} + +// ValidateMIC returns if the MIC is valid. +// When using 32 bit frame counters, only the least-signification 16 bits are +// sent / received. In order to validate the MIC, the receiver needs to set +// the FCnt to the full 32 bit value (based on the observation of the traffic). +// See section '4.3.1.5 Frame counter (FCnt)' of the LoRaWAN 1.0 specification +// for more details. +func (p PHYPayload) ValidateMIC(key AES128Key) (bool, error) { + var mic []byte + var err error + + switch p.MACPayload.(type) { + case *JoinRequestPayload: + mic, err = p.calculateJoinRequestMIC(key) + case *JoinAcceptPayload: + mic, err = p.calculateJoinAcceptMIC(key) + default: + mic, err = p.calculateMIC(key) + } + + if err != nil { + return false, err + } + if len(mic) != 4 { + return false, errors.New("lorawan: a MIC of 4 bytes is expected") + } + for i, v := range mic { + if p.MIC[i] != v { + return false, nil + } + } + return true, nil +} + +// EncryptJoinAcceptPayload encrypts the join-accept payload with the given +// AppKey. Note that encrypted must be performed after calling SetMIC +// (sicne the MIC is part of the encrypted payload). +func (p *PHYPayload) EncryptJoinAcceptPayload(appKey AES128Key) error { + if _, ok := p.MACPayload.(*JoinAcceptPayload); !ok { + return errors.New("lorawan: MACPayload value must be of type *JoinAcceptPayload") + } + + pt, err := p.MACPayload.MarshalBinary() + if err != nil { + return err + } + + // in the 1.0 spec instead of DLSettings there is RFU field. the assumption + // is made that this should have been DLSettings. + + pt = append(pt, p.MIC[0:4]...) + if len(pt)%16 != 0 { + return errors.New("lorawan: plaintext must be a multiple of 16 bytes") + } + + block, err := aes.NewCipher(appKey[:]) + if err != nil { + return err + } + if block.BlockSize() != 16 { + return errors.New("lorawan: block-size of 16 bytes is expected") + } + ct := make([]byte, len(pt)) + for i := 0; i < len(ct)/16; i++ { + offset := i * 16 + block.Decrypt(ct[offset:offset+16], pt[offset:offset+16]) + } + p.MACPayload = &DataPayload{Bytes: ct[0 : len(ct)-4]} + copy(p.MIC[:], ct[len(ct)-4:]) + return nil +} + +// DecryptJoinAcceptPayload decrypts the join-accept payload with the given +// AppKey. Note that you need to decrypte before you can validate the MIC. +func (p *PHYPayload) DecryptJoinAcceptPayload(appKey AES128Key) error { + dp, ok := p.MACPayload.(*DataPayload) + if !ok { + return errors.New("lorawan: MACPayload must be of type *DataPayload") + } + + // append MIC to the ciphertext since it is encrypted too + ct := append(dp.Bytes, p.MIC[:]...) + + if len(ct)%16 != 0 { + return errors.New("lorawan: plaintext must be a multiple of 16 bytes") + } + + block, err := aes.NewCipher(appKey[:]) + if err != nil { + return err + } + if block.BlockSize() != 16 { + return errors.New("lorawan: block-size of 16 bytes is expected") + } + pt := make([]byte, len(ct)) + for i := 0; i < len(pt)/16; i++ { + offset := i * 16 + block.Encrypt(pt[offset:offset+16], ct[offset:offset+16]) + } + + p.MACPayload = &JoinAcceptPayload{} + copy(p.MIC[:], pt[len(pt)-4:len(pt)]) // set the decrypted MIC + return p.MACPayload.UnmarshalBinary(p.isUplink(), pt[0:len(pt)-4]) +} + +// EncryptFRMPayload encrypts the FRMPayload with the given key. +func (p *PHYPayload) EncryptFRMPayload(key AES128Key) error { + macPL, ok := p.MACPayload.(*MACPayload) + if !ok { + return errors.New("lorawan: MACPayload must be of type *MACPayload") + } + + // nothing to encrypt + if len(macPL.FRMPayload) == 0 { + return nil + } + + data, err := macPL.marshalPayload() + if err != nil { + return err + } + + data, err = EncryptFRMPayload(key, p.isUplink(), macPL.FHDR.DevAddr, macPL.FHDR.FCnt, data) + if err != nil { + return err + } + + // store the encrypted data in a DataPayload + macPL.FRMPayload = []Payload{&DataPayload{Bytes: data}} + + return nil +} + +// DecryptFRMPayload decrypts the FRMPayload with the given key. +func (p *PHYPayload) DecryptFRMPayload(key AES128Key) error { + if err := p.EncryptFRMPayload(key); err != nil { + return err + } + + macPL, ok := p.MACPayload.(*MACPayload) + if !ok { + return errors.New("lorawan: MACPayload must be of type *MACPayload") + } + + // the FRMPayload contains MAC commands, which we need to unmarshal + if macPL.FPort != nil && *macPL.FPort == 0 { + return macPL.decodeFRMPayloadToMACCommands(p.isUplink()) + } + + return nil +} + +// DecodeFRMPayloadToMACCommands decodes the (decrypted) FRMPayload bytes into +// MAC commands. Note that after calling DecryptFRMPayload, this method is +// called automatically when FPort=0. +// Use this method when unmarshaling a decrypted FRMPayload from a slice +// of bytes and this when DecryptFRMPayload is not called. +func (p *PHYPayload) DecodeFRMPayloadToMACCommands() error { + macPL, ok := p.MACPayload.(*MACPayload) + if !ok { + return errors.New("lorawan: MACPayload must be of type *MACPayload") + } + + return macPL.decodeFRMPayloadToMACCommands(p.isUplink()) +} + +// MarshalBinary marshals the object in binary form. +func (p PHYPayload) MarshalBinary() ([]byte, error) { + if p.MACPayload == nil { + return []byte{}, errors.New("lorawan: MACPayload should not be nil") + } + + var out []byte + var b []byte + var err error + + if b, err = p.MHDR.MarshalBinary(); err != nil { + return []byte{}, err + } + out = append(out, b...) + + if b, err = p.MACPayload.MarshalBinary(); err != nil { + return []byte{}, err + } + out = append(out, b...) + out = append(out, p.MIC[0:len(p.MIC)]...) + return out, nil +} + +// UnmarshalBinary decodes the object from binary form. +func (p *PHYPayload) UnmarshalBinary(data []byte) error { + if len(data) < 5 { + return errors.New("lorawan: at least 5 bytes needed to decode PHYPayload") + } + + // MHDR + if err := p.MHDR.UnmarshalBinary(data[0:1]); err != nil { + return err + } + + // MACPayload + switch p.MHDR.MType { + case JoinRequest: + p.MACPayload = &JoinRequestPayload{} + case JoinAccept: + p.MACPayload = &DataPayload{} + default: + p.MACPayload = &MACPayload{} + } + + isUplink := p.isUplink() + if err := p.MACPayload.UnmarshalBinary(isUplink, data[1:len(data)-4]); err != nil { + return err + } + + // MIC + for i := 0; i < 4; i++ { + p.MIC[i] = data[len(data)-4+i] + } + return nil +} + +// MarshalText encodes the PHYPayload into base64. +func (p PHYPayload) MarshalText() ([]byte, error) { + b, err := p.MarshalBinary() + if err != nil { + return nil, err + } + return []byte(base64.StdEncoding.EncodeToString(b)), nil +} + +// UnmarshalText decodes the PHYPayload from base64. +func (p *PHYPayload) UnmarshalText(text []byte) error { + b, err := base64.StdEncoding.DecodeString(string(text)) + if err != nil { + return err + } + return p.UnmarshalBinary(b) +} + +// MarshalJSON encodes the PHYPayload into JSON. +func (p PHYPayload) MarshalJSON() ([]byte, error) { + type phyAlias PHYPayload + return json.Marshal(phyAlias(p)) +} + +// isUplink returns a bool indicating if the packet is uplink or downlink. +// Note that for MType Proprietary it can't derrive if the packet is uplink +// or downlink. This is fine (I think) since it is also unknown how to +// calculate the MIC and the format of the MACPayload. A pluggable +// MIC calculation and MACPayload for Proprietary MType is still TODO. +func (p PHYPayload) isUplink() bool { + switch p.MHDR.MType { + case JoinRequest, UnconfirmedDataUp, ConfirmedDataUp: + return true + default: + return false + } +} + +// EncryptFRMPayload encrypts the FRMPayload (slice of bytes). +// Note that EncryptFRMPayload is used for both encryption and decryption. +func EncryptFRMPayload(key AES128Key, uplink bool, devAddr DevAddr, fCnt uint32, data []byte) ([]byte, error) { + pLen := len(data) + if pLen%16 != 0 { + // append with empty bytes so that len(data) is a multiple of 16 + data = append(data, make([]byte, 16-(pLen%16))...) + } + + block, err := aes.NewCipher(key[:]) + if err != nil { + return nil, err + } + if block.BlockSize() != 16 { + return nil, errors.New("lorawan: block size of 16 was expected") + } + + s := make([]byte, 16) + a := make([]byte, 16) + a[0] = 0x01 + if !uplink { + a[5] = 0x01 + } + + b, err := devAddr.MarshalBinary() + if err != nil { + return nil, err + } + copy(a[6:10], b) + binary.LittleEndian.PutUint32(a[10:14], uint32(fCnt)) + + for i := 0; i < len(data)/16; i++ { + a[15] = byte(i + 1) + block.Encrypt(s, a) + + for j := 0; j < len(s); j++ { + data[i*16+j] = data[i*16+j] ^ s[j] + } + } + + return data[0:pLen], nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 2f668bd13..49a5aab74 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,532 +9,588 @@ "revisionTime": "2017-04-10T19:29:09Z" }, { - "checksumSHA1": "HvKsUcdxKmlXEQIO5oH8Up+q454=", + "checksumSHA1": "IpQ7q3Gb4c8y0EIXqqEibFH6b4o=", "path": "github.com/TheThingsNetwork/api", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "Qldi0EQWFinjkxfHch10mAsBD48=", + "checksumSHA1": "Qyx2sVDeMNrq4cR+W1yq+Kg68iE=", "path": "github.com/TheThingsNetwork/api/broker", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "tKs2HN1clvlD9HZ4OhaNkT04PgM=", + "checksumSHA1": "Z10EtZP2ZXhlTgZ+VggEI02WPug=", "path": "github.com/TheThingsNetwork/api/broker/brokerclient", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "w46Dzts3lwBI/wVWc8TBLZQfjxg=", + "checksumSHA1": "oA6QqRJPs5FZ5HE5lIyplbWCrTw=", "path": "github.com/TheThingsNetwork/api/discovery", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { "checksumSHA1": "ws6/Hzt73JGd/0tqOXPFBhKAoWw=", "path": "github.com/TheThingsNetwork/api/discovery/discoveryclient", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "F/uBNirJ4oJbv65ZnPaqeWgBIIw=", + "checksumSHA1": "E92HDofEGnYRZ2iX4OnVjI4ExJc=", "path": "github.com/TheThingsNetwork/api/gateway", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "sE6s9aBQGFvMOuNiS71RBd20mlo=", + "checksumSHA1": "zN1ytE8LmTpFuQBQAsFY6Iydvfc=", "path": "github.com/TheThingsNetwork/api/handler", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { "checksumSHA1": "Wf1g3j9HQL33qpz1QpLGRr3JLt0=", "path": "github.com/TheThingsNetwork/api/handler/handlerclient", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { "checksumSHA1": "5ywHe4obsAQglqMJI2F1ecdOC3A=", "path": "github.com/TheThingsNetwork/api/logfields", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "UXeYeDQpDq8uNy1v85eBzljAtmI=", + "checksumSHA1": "rjQjraZh2q6YxYYqx54gQ9xkKSQ=", "path": "github.com/TheThingsNetwork/api/monitor", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { "checksumSHA1": "8308j9lxDoH99REc8EMOZz05vu8=", "path": "github.com/TheThingsNetwork/api/monitor/monitorclient", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "x6VxNzc3Vs1k+WWLiwM8S0t3KIs=", + "checksumSHA1": "jPCAvQg958aUaQ09KNDiQXvHLAo=", "path": "github.com/TheThingsNetwork/api/networkserver", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "OqxCDpa7rIq9mGFXcGqlvb8aNhA=", + "checksumSHA1": "M4k4TzNQ4dF4d3ocaNFm4sVQvOg=", "path": "github.com/TheThingsNetwork/api/protocol", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "XU9nu3DN+yzkZiDnHifRJ+UjSeM=", + "checksumSHA1": "9htsgXY9dXpIrby7HDY+yu1Cyrg=", "path": "github.com/TheThingsNetwork/api/protocol/lorawan", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "FbQxmR3Mu4ooEqD57opscswpQDQ=", + "checksumSHA1": "VypJSgptmS/ctqX5WatbfiCzdHs=", "path": "github.com/TheThingsNetwork/api/router", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "NY7Vvw615PhmCwQ2S7bgecRQUfQ=", + "checksumSHA1": "DQPtILG2kmrt5/ru/oEOpnKlr/A=", "path": "github.com/TheThingsNetwork/api/router/routerclient", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "Dm/o7cjKomQRUFO6uVQoR/ef5/k=", + "checksumSHA1": "kR+KSTeWHuUB7yvaQhJFAJ8Wl1U=", "path": "github.com/TheThingsNetwork/api/trace", - "revision": "f074ae7262444b3fac2149635e16e66b13413510", - "revisionTime": "2017-10-05T11:37:55Z" + "revision": "9be59778d6cf0edcfa83e9aa3e2bfd5e1663f857", + "revisionTime": "2018-10-05T13:18:48Z" }, { - "checksumSHA1": "t0RI2WKlx2C+y/BNqHPrhPcYOPE=", + "checksumSHA1": "xmLs7utDpBB4MjGKc3jZUGJxGXk=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "b6pCShOzSh4N9LujOUFzQDzqhz8=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "d1nFTGUlP4sNEf1lelyh6L59mjE=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "8LlsoZGpmrUfqDNDJKU/IJ/x6TM=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "6B3ymo3fpzd5+rjQDViPKBiOf+M=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "KrmYdSt7rX8Pwk2f8Fp1z/js8QQ=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "RKuX3Wg2m7yvFsFoitY7hL9HcMA=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "RpKXQd5sp9/jsWM991S7OhE9/ME=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "48WYq5L+4Gkl5NXlhDJM0Uzt/7o=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { - "checksumSHA1": "3+2Kl1Qvj59+FAPHDEMXzOACNI0=", + "checksumSHA1": "/eebIyBMhWfiy9TAMvB8ZfyQScE=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "19b357898a9bd97e2524230ada193653c2bcd283", - "revisionTime": "2017-06-23T09:06:20Z" + "revision": "3314753327942c0aed5a2a9233d4f001454c21a9", + "revisionTime": "2018-05-31T09:16:50Z" }, { "checksumSHA1": "i9DF5eLNL67Prly9HZQ1jUe776I=", "path": "github.com/TheThingsNetwork/go-cayenne-lib/cayennelpp", - "revision": "b93f1e68bb8083a2b95c7149fe3b51fc595a717f", - "revisionTime": "2017-05-22T14:59:15Z" + "revision": "ba5c7ac69c198562633ee3ad4c90e16b4b12b88d", + "revisionTime": "2017-05-22T15:00:42Z" }, { "checksumSHA1": "x795Q87cyvAAeuxXxft5iwd1Los=", "path": "github.com/TheThingsNetwork/go-utils/backoff", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "SsIrHnnCvLk9Y+9ope+z6xOfypo=", "path": "github.com/TheThingsNetwork/go-utils/encoding", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "Scfp1hQzYCbaI1fR7VJuYTgrJ/g=", "path": "github.com/TheThingsNetwork/go-utils/grpc/auth", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "Qi7vTZyzffccM1/XZ8cx+pKnGto=", "path": "github.com/TheThingsNetwork/go-utils/grpc/restartstream", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "4iLODl3lRxNw3gtzpjldw3Lk8Ig=", "path": "github.com/TheThingsNetwork/go-utils/grpc/rpcerror", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "G362bCEcpTN6usnS9qgxORA38GE=", "path": "github.com/TheThingsNetwork/go-utils/grpc/rpclog", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "2fmcExbxkfP7BQNDGDqZnL3UBvI=", "path": "github.com/TheThingsNetwork/go-utils/grpc/streambuffer", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "1ZHMKISbo6QBsYLOx8JKgp6uhs4=", "path": "github.com/TheThingsNetwork/go-utils/grpc/ttnctx", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "H/qmI9ogJaP1hx+AJoalahqQ20Q=", "path": "github.com/TheThingsNetwork/go-utils/log", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "FCtEPJzinLIWyAcZzM1TsbkWC7o=", "path": "github.com/TheThingsNetwork/go-utils/log/apex", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "sQ0vy3MCGY1WgK9xldn1V6pMeZk=", "path": "github.com/TheThingsNetwork/go-utils/log/grpc", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" + }, + { + "checksumSHA1": "35KLWRFrraz1ZsE2K2kAfDZT0bw=", + "path": "github.com/TheThingsNetwork/go-utils/log/prometheus", + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "2/v0SMyHM5vgImOb1BEEDWeXZEY=", "path": "github.com/TheThingsNetwork/go-utils/pseudorandom", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { "checksumSHA1": "iYa+qSqzqZwpmoibM8/1X+aC3sI=", "path": "github.com/TheThingsNetwork/go-utils/random", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { - "checksumSHA1": "kLFTtAVcjZbHXybodGAqJ8wxflY=", + "checksumSHA1": "AOXRd2QZp/h0olCTFOnAPA4Wg8E=", "path": "github.com/TheThingsNetwork/go-utils/roots", - "revision": "ed8e7e8c0d30b955a70828e65f414c83721e16c2", - "revisionTime": "2017-07-12T07:22:03Z" + "revision": "8b4e2f02426ee7bd5ebacc97b6d840632d0f7e60", + "revisionTime": "2018-09-12T07:29:26Z" }, { - "checksumSHA1": "FoqocZyUeKZYU4tyko+oaIAPpO8=", + "checksumSHA1": "4IearLMTAsdyT2kExofJKklO7k4=", "path": "github.com/apex/log", - "revision": "7259ed75bd8d8113a7ac9831ecb0558cac33ceed", - "revisionTime": "2017-05-06T22:51:38Z" + "revision": "941dea75d3ebfbdd905a5d8b7b232965c5e5c684", + "revisionTime": "2018-07-02T15:59:52Z" }, { "checksumSHA1": "rkMrOC6qQhEkDCYmmUErg8qvjGA=", "path": "github.com/apex/log/handlers/json", - "revision": "7259ed75bd8d8113a7ac9831ecb0558cac33ceed", - "revisionTime": "2017-05-06T22:51:38Z" + "revision": "941dea75d3ebfbdd905a5d8b7b232965c5e5c684", + "revisionTime": "2018-07-02T15:59:52Z" }, { "checksumSHA1": "AHCiF3VnEqmXyZDeH+z/IGsAtnI=", "path": "github.com/apex/log/handlers/level", - "revision": "7259ed75bd8d8113a7ac9831ecb0558cac33ceed", - "revisionTime": "2017-05-06T22:51:38Z" + "revision": "941dea75d3ebfbdd905a5d8b7b232965c5e5c684", + "revisionTime": "2018-07-02T15:59:52Z" }, { "checksumSHA1": "rxQUkWqruIZKpRzdwqrkxfcZvyw=", "path": "github.com/apex/log/handlers/multi", - "revision": "7259ed75bd8d8113a7ac9831ecb0558cac33ceed", - "revisionTime": "2017-05-06T22:51:38Z" + "revision": "941dea75d3ebfbdd905a5d8b7b232965c5e5c684", + "revisionTime": "2018-07-02T15:59:52Z" }, { "checksumSHA1": "+urkX0ZY02ty7TvsBsYzcboRxVg=", "path": "github.com/apex/log/handlers/text", - "revision": "7259ed75bd8d8113a7ac9831ecb0558cac33ceed", - "revisionTime": "2017-05-06T22:51:38Z" + "revision": "941dea75d3ebfbdd905a5d8b7b232965c5e5c684", + "revisionTime": "2018-07-02T15:59:52Z" }, { - "checksumSHA1": "ddYc7mKe3g1x1UUKBrGR4vArJs8=", + "checksumSHA1": "5gnBrB9ISzgkQe5rLOlFp2MqA/8=", "path": "github.com/asaskevich/govalidator", - "revision": "065ea97278837088c52c0cd0d963473f61b2d98c", - "revisionTime": "2017-05-13T08:31:01Z" + "revision": "f9ffefc3facfbe0caee3fea233cbb6e8208f4541", + "revisionTime": "2018-07-20T11:50:03Z" }, { - "checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=", + "checksumSHA1": "0rido7hYHQtfq3UJzVT5LClLAWc=", "path": "github.com/beorn7/perks/quantile", - "revision": "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9", - "revisionTime": "2016-08-04T10:47:26Z" + "revision": "3a771d992973f24aa725d07868b467d1ddfceafb", + "revisionTime": "2018-03-21T16:47:47Z" }, { - "checksumSHA1": "bz+hbEtuZsntWgYkShEoFuieR4I=", + "checksumSHA1": "RLjW2GoEu83LAZLuLSlzLdiyHYQ=", "path": "github.com/bluele/gcache", - "revision": "b9993a9fff7b10a1fd0404d8aa4a039cb625534d", - "revisionTime": "2017-04-13T07:40:36Z" - }, - { - "checksumSHA1": "u52BzfoKG0x7CgBgg/PB1AEVSag=", - "path": "github.com/brocaar/lorawan", - "revision": "aa09ef2fd7be6786b853675bb208c26ceeaf9bfd", - "revisionTime": "2017-06-27T06:31:26Z" - }, - { - "checksumSHA1": "YA+7KDpuddpz3XkOEFAWMxM4bYU=", - "path": "github.com/brocaar/lorawan/band", - "revision": "a64aca28516d1024d898a412b2657b35b3539329", - "revisionTime": "2017-06-26T12:36:36Z" + "revision": "472614239ac7e5bc6461e237c798a6ebd5aff8c1", + "revisionTime": "2017-10-10T15:56:17Z" }, { - "checksumSHA1": "FwjxyOGlOWp5TqkqLSYfrdxC1E4=", + "checksumSHA1": "mUeojTdLEyzYOki70VUAeeYr/wQ=", "path": "github.com/dgrijalva/jwt-go", - "revision": "6c8dedd55f8a2e41f605de6d5d66e51ed1f299fc", - "revisionTime": "2017-05-08T16:54:58Z" + "revision": "0b96aaa707760d6ab28d9b9d1913ff5993328bae", + "revisionTime": "2018-07-19T21:18:23Z" }, { - "checksumSHA1": "PzqUa3HAaeyzDw87wACb15/tMkI=", + "checksumSHA1": "zsBTX3L2J8TXlQB2HiIX6aQVqB0=", "path": "github.com/eclipse/paho.mqtt.golang", - "revision": "c37a0a275c2de30bf1c7eb072dd6bef09a93c666", - "revisionTime": "2017-04-13T21:40:49Z" + "revision": "88c4622b8e24c52f64a0caaa28e40b91629bb6e6", + "revisionTime": "2018-06-14T10:22:24Z" }, { - "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", + "checksumSHA1": "ITCnG3uKUnqNXrnnulKokjQKfN4=", "path": "github.com/eclipse/paho.mqtt.golang/packets", - "revision": "c37a0a275c2de30bf1c7eb072dd6bef09a93c666", - "revisionTime": "2017-04-13T21:40:49Z" + "revision": "88c4622b8e24c52f64a0caaa28e40b91629bb6e6", + "revisionTime": "2018-06-14T10:22:24Z" }, { - "checksumSHA1": "40Ns85VYa4smQPcewZ7SOdfLnKU=", + "checksumSHA1": "5BP5xofo0GoFi6FtgqFFbmHyUKI=", "path": "github.com/fatih/structs", - "revision": "a720dfa8df582c51dee1b36feabb906bde1588bd", - "revisionTime": "2017-01-03T08:10:50Z" + "revision": "ebf56d35bba727c68ac77f56f2fcf90b181851aa", + "revisionTime": "2018-01-23T06:50:59Z" }, { - "checksumSHA1": "x2Km0Qy3WgJJnV19Zv25VwTJcBM=", + "checksumSHA1": "7NP1qUMF8Kx1y0zANxx0e+oq9Oo=", "path": "github.com/fsnotify/fsnotify", - "revision": "4da3e2cfbabc9f751898f250b49f2439785783a1", - "revisionTime": "2017-03-29T04:21:07Z" + "revision": "ccc981bf80385c528a65fbfdd49bf2d8da22aa23", + "revisionTime": "2018-08-30T22:02:26Z" }, { - "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", + "checksumSHA1": "Kqv7bA4oJG0nPwQvGWDwGGaKONo=", "path": "github.com/go-ole/go-ole", - "revision": "de8695c8edbf8236f30d6e1376e20b198a028d42", - "revisionTime": "2017-02-09T15:13:32Z" + "revision": "7a0fa49edf48165190530c675167e2f319a05268", + "revisionTime": "2018-06-25T08:58:08Z" }, { - "checksumSHA1": "Q0ZOcJW0fqOefDzEdn+PJHOeSgI=", + "checksumSHA1": "PArleDBtadu2qO4hJwHR8a3IOTA=", "path": "github.com/go-ole/go-ole/oleutil", - "revision": "de8695c8edbf8236f30d6e1376e20b198a028d42", - "revisionTime": "2017-02-09T15:13:32Z" + "revision": "7a0fa49edf48165190530c675167e2f319a05268", + "revisionTime": "2018-06-25T08:58:08Z" }, { - "checksumSHA1": "FhLvgtYfuKY0ow9wtLJRoeg7d6w=", + "checksumSHA1": "Gay+mu0LGL3M53eDVwTEbe2CjuY=", "path": "github.com/gogo/protobuf/gogoproto", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "8UEp6v0Dczw/SlasE0DivB0mAHA=", + "checksumSHA1": "nDdhX1iBJuMhtp//Z9B7C2srOAE=", "path": "github.com/gogo/protobuf/jsonpb", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "iJy58JkOJxhuyWW0RDbzE65Ms7A=", "path": "github.com/gogo/protobuf/plugin/compare", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "TJCl1W3nXbpBqeL0FqUbLxvLj2Y=", "path": "github.com/gogo/protobuf/plugin/defaultcheck", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "30CshOdSNU5CWITX3ZXYrDpcWPI=", "path": "github.com/gogo/protobuf/plugin/description", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "qpx3EauUe8vv5UcgvERiZSl1M7U=", + "checksumSHA1": "Gg0c45e19o70P6vvs3i5pv/erqo=", "path": "github.com/gogo/protobuf/plugin/embedcheck", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "knMp74dhZW43DcXWo3yykjhLWck=", "path": "github.com/gogo/protobuf/plugin/enumstringer", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "HG7Fv9xqq2bgJH5viYmdgedeBSU=", "path": "github.com/gogo/protobuf/plugin/equal", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "oW5qWSA/x87Xa5ZjBcgWMwdL99I=", "path": "github.com/gogo/protobuf/plugin/face", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "8m1gX6RlnF7QcFw622iWZXDI+mU=", + "checksumSHA1": "HBqXDxv1LO9I8BoqX8l21oER1KU=", "path": "github.com/gogo/protobuf/plugin/gostring", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "t+jrpPS8bbWp032PcILrfoIJY+c=", "path": "github.com/gogo/protobuf/plugin/marshalto", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "EM1zgWiW8983vDSmliZC4gIjDRw=", "path": "github.com/gogo/protobuf/plugin/oneofcheck", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "SPft+aRUAOZ7tZqP0HYFk0IUuBs=", + "checksumSHA1": "rL+cCEUlnJErjN9ydV30bBhakzM=", "path": "github.com/gogo/protobuf/plugin/populate", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "17YWrOjOL2kWMl/dd7mOVEw7KcQ=", "path": "github.com/gogo/protobuf/plugin/size", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "2Glqjvx+smBDPeLuGU2Rp7EsrSc=", "path": "github.com/gogo/protobuf/plugin/stringer", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "pMp4Z/1OFvgCkOCaMZGUmaJltQo=", "path": "github.com/gogo/protobuf/plugin/testgen", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "COGfoC5aOQVxC7C88A5ndBCbcsA=", + "checksumSHA1": "2TxfbXOOPHSKCUdU17i+Vl+ZJQs=", "path": "github.com/gogo/protobuf/plugin/union", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "vv0D6y5rAW8i3hvj4Kk7yrbg9fY=", "path": "github.com/gogo/protobuf/plugin/unmarshal", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "wn2shNJMwRZpvuvkf1s7h0wvqHI=", + "checksumSHA1": "GYschMPw/Zi5C4QnGKRfgljnx/c=", "path": "github.com/gogo/protobuf/proto", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "F+PKpdY6PyIrxQ8b20TzsM+1JuI=", + "checksumSHA1": "4R7X2wRYYOxdXsoXOEcyBoCakb8=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "SuN7KlJvfhPrVbIyQUFr0tumC6c=", + "checksumSHA1": "4Pks3G+XIkNOiwVhaYkzt/eICG0=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/generator", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" + }, + { + "checksumSHA1": "ekXJtMvDVvfO+juihvauPgtlpnQ=", + "path": "github.com/gogo/protobuf/protoc-gen-gogo/generator/internal/remap", + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "yzj4Gxq6V02t7bGv/s1y9cLGjJE=", + "checksumSHA1": "Cy+ls8DgFNBoxMDwOtHwglAvRC0=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/grpc", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "curkzoP3cQRB7lxDrDgb7dCRhVg=", + "checksumSHA1": "q0ydDnVTRID+SJ7IVXdev2vejrA=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/plugin", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "HPVQZu059/Rfw2bAWM538bVTcUc=", "path": "github.com/gogo/protobuf/sortkeys", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "RFxAlnHGwHp7EnZnywy+MlFfqv4=", + "checksumSHA1": "8+7Pli2T4Tt7NNSUstW5bV/Zm3w=", "path": "github.com/gogo/protobuf/types", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { - "checksumSHA1": "vSkpCp6Zy2iSbXpJC+KQQnnShlA=", + "checksumSHA1": "tyIyCME5JLRJ5bjIMBtLDjJPwss=", "path": "github.com/gogo/protobuf/vanity", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "GeMa6Un5ZjE3zXyCF13pti4UD0Q=", "path": "github.com/gogo/protobuf/vanity/command", - "revision": "41168f6614b7bb144818ec8967b8c702705df564", - "revisionTime": "2017-12-04T08:42:57Z" + "revision": "636bf0302bc95575d69441b25a2603156ffdddf1", + "revisionTime": "2018-07-17T14:19:46Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "L1EnSrVt8iXe1VtEfYopRet5+N8=", @@ -543,70 +599,100 @@ "revisionTime": "2017-09-16T22:02:29Z" }, { - "checksumSHA1": "iVfdaLxIDjfk2KLP8dCMIbsxZZM=", + "checksumSHA1": "NPefLsS2pv0+YlXAuJJvkNOMgyE=", "path": "github.com/golang/protobuf/jsonpb", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "yqF125xVSkmfLpIVGrLlfE05IUk=", + "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=", "path": "github.com/golang/protobuf/proto", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "XNHQiRltA7NQJV0RvUroY+cf+zg=", + "checksumSHA1": "DA2cyOt1W92RTyXAqKQ4JWKGR8U=", "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "dbkgZT1dlQxBBeqNb74+pdbOMbc=", + "checksumSHA1": "DifSE44TpfgLs4XBFJHiPy1YBoM=", "path": "github.com/golang/protobuf/protoc-gen-go/generator", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" + }, + { + "checksumSHA1": "uY4dEtqaAe5gsU8gbpCI1JgEIII=", + "path": "github.com/golang/protobuf/protoc-gen-go/generator/internal/remap", + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "KxpGuQ5LSMaImXONLN/YM3shGcQ=", + "checksumSHA1": "h4PLbJDYnRmcUuf56USJ5K3xJOg=", "path": "github.com/golang/protobuf/protoc-gen-go/plugin", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "VfkiItDBFFkZluaAMAzJipDXNBY=", + "checksumSHA1": "/s0InJhSrxhTpqw5FIKgSMknCfM=", "path": "github.com/golang/protobuf/ptypes", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "UB9scpDxeFjQe5tEthuR4zCLRu4=", + "checksumSHA1": "3eqU9o+NMZSLM/coY5WDq7C1uKg=", "path": "github.com/golang/protobuf/ptypes/any", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "hUjAj0dheFVDl84BAnSWj9qy2iY=", + "checksumSHA1": "ZIF0rnVzNLluFPqUebtJrVonMr4=", "path": "github.com/golang/protobuf/ptypes/duration", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "K61Qz4x9QeD2mACYgMP9fcpBqNg=", + "checksumSHA1": "7Az4Zl9T11I+xOfjgs/3/YMJ24I=", "path": "github.com/golang/protobuf/ptypes/empty", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "Ylq6kq3KWBy6mu68oyEwenhNMdg=", + "checksumSHA1": "QwgMLtaLi/6xHaNiDFwHN844AkI=", "path": "github.com/golang/protobuf/ptypes/struct", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "O2ItP5rmfrgxPufhjJXbFlXuyL8=", + "checksumSHA1": "1FJvuT0UllZaaS43kmPlx8oNiCs=", "path": "github.com/golang/protobuf/ptypes/timestamp", - "revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845", - "revisionTime": "2017-11-13T18:07:20Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", @@ -627,22 +713,22 @@ "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "s7J8PdKJFnoBFg6MowXLTvix7ug=", + "checksumSHA1": "3iVD2sJv4uYnA8YgkR8yzZiUF7o=", "path": "github.com/grpc-ecosystem/go-grpc-prometheus", - "revision": "0dafe0d496ea71181bf2dd039e7e3f44b6bd11a7", - "revisionTime": "2017-08-26T09:06:48Z" + "revision": "93bf4626fba73b751b0f3cdf2649be4ce0c420cd", + "revisionTime": "2018-08-20T15:04:22Z" }, { - "checksumSHA1": "c/b4KgxzvuuwE1izwwy13w5Res4=", + "checksumSHA1": "XHlYXwszEnUvx+vWdKYexz2Yris=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", - "revision": "8db8c1ac6f97d8ee1b7a0d7198c39f687e19a937", - "revisionTime": "2017-12-08T02:50:44Z" + "revision": "7916b03771e9edb045fcfc7db5f516f8b6a29c39", + "revisionTime": "2018-09-10T11:43:36Z" }, { - "checksumSHA1": "V1qChZAWS+xzlEBSyz2r8JlzmM8=", + "checksumSHA1": "0nkLVed+YhQE6+7fFbir+gCrcSs=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", - "revision": "8db8c1ac6f97d8ee1b7a0d7198c39f687e19a937", - "revisionTime": "2017-12-08T02:50:44Z" + "revision": "7916b03771e9edb045fcfc7db5f516f8b6a29c39", + "revisionTime": "2018-09-10T11:43:36Z" }, { "checksumSHA1": "CKrlwtWMXiznQrgS2o/obvP7HTM=", @@ -651,64 +737,70 @@ "revisionTime": "2017-03-15T21:35:39Z" }, { - "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", + "checksumSHA1": "QuHQq2+nPPMMJ3qoJxlQ8ZCVEfk=", "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", - "revision": "8db8c1ac6f97d8ee1b7a0d7198c39f687e19a937", - "revisionTime": "2017-12-08T02:50:44Z" + "revision": "7916b03771e9edb045fcfc7db5f516f8b6a29c39", + "revisionTime": "2018-09-10T11:43:36Z" }, { - "checksumSHA1": "7JBkp3EZoc0MSbiyWfzVhO4RYoY=", + "checksumSHA1": "bWGI7DqWg6IdhfamV96OD+Esa+8=", "path": "github.com/hashicorp/hcl", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { - "checksumSHA1": "teokXoyRXEJ0vZHOWBD11l5YFNI=", + "checksumSHA1": "1GmX7G0Pgf5XprOh+T3zXMXX0dc=", "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { - "checksumSHA1": "z6wdP4mRw4GVjShkNHDaOWkbxS0=", + "checksumSHA1": "encY+ZtDf4nJaMvsVL2c+EJ2r3Q=", + "path": "github.com/hashicorp/hcl/hcl/printer", + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" + }, + { + "checksumSHA1": "+qJTCxhkwC7r+VZlPlZz8S74KmU=", "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { "checksumSHA1": "oS3SCN9Wd6D8/LG0Yx1fu84a7gI=", "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { "checksumSHA1": "PwlfXt7mFS8UYzWxOK5DOq0yxS0=", "path": "github.com/hashicorp/hcl/json/parser", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { - "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", + "checksumSHA1": "afrZ8VmAwfTdDAYVgNSXbxa4GsA=", "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" + "revision": "65a6292f0157eff210d03ed1bf6c59b190b8b906", + "revisionTime": "2018-09-06T18:38:39Z" }, { "checksumSHA1": "K6exl2ouL7d8cR2i378EzZOdRVI=", @@ -716,12 +808,6 @@ "revision": "bf9dde6d0d2c004a008c27aaee91170c786f6db8", "revisionTime": "2017-01-09T16:22:49Z" }, - { - "checksumSHA1": "08LACigKIDFNAdk6+ZX9VVlV1S8=", - "path": "github.com/htdvisser/grpc-testing/test", - "revision": "f0a186b6b52b6987645cf6e35a422d6bd7fcac8f", - "revisionTime": "2017-03-03T12:50:20Z" - }, { "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", "path": "github.com/inconshreveable/mousetrap", @@ -731,20 +817,20 @@ { "checksumSHA1": "7PLlrIaGI1TKWB96RkizgkTtOtQ=", "path": "github.com/jacobsa/crypto/cmac", - "revision": "293ce0c192fb4f59cd879b46544922b9ed09a13a", - "revisionTime": "2016-11-11T03:08:13Z" + "revision": "c73681c634de898c869684602cf0c0d2ce938c4d", + "revisionTime": "2017-10-18T23:08:03Z" }, { "checksumSHA1": "NBvtX91AEKxFLmj8mwwhXEKl6d0=", "path": "github.com/jacobsa/crypto/common", - "revision": "293ce0c192fb4f59cd879b46544922b9ed09a13a", - "revisionTime": "2016-11-11T03:08:13Z" + "revision": "c73681c634de898c869684602cf0c0d2ce938c4d", + "revisionTime": "2017-10-18T23:08:03Z" }, { - "checksumSHA1": "+NJzbj9fa71GPyEhyYcB2urBiXY=", + "checksumSHA1": "dqtKfXGotqkiYaS328PpWeusig8=", "path": "github.com/juju/ratelimit", - "revision": "acf38b000a03e4ab89e40f20f1e548f4e6ac7f72", - "revisionTime": "2017-03-14T01:17:55Z" + "revision": "59fac5042749a5afb9af70e813da1dd5474f0167", + "revisionTime": "2017-10-26T09:04:26Z" }, { "checksumSHA1": "gEjGS03N1eysvpQ+FCHTxPcbxXc=", @@ -753,16 +839,16 @@ "revisionTime": "2017-05-10T13:15:34Z" }, { - "checksumSHA1": "UvboqgDDSAbGORdtr5tBpkwYR0A=", + "checksumSHA1": "AYZGozgchqe4WcKH/jCZI1flnP8=", "path": "github.com/magiconair/properties", - "revision": "51463bfca2576e06c62a8504b5c0f06d61312647", - "revisionTime": "2017-03-21T09:30:39Z" + "revision": "c2353362d570a7bfa228149c62842019201cfb71", + "revisionTime": "2018-05-15T20:40:05Z" }, { - "checksumSHA1": "cJE7dphDlam/i7PhnsyosNWtbd4=", + "checksumSHA1": "KGCifxxR5cm2gz94i2iha+RIZlk=", "path": "github.com/mattn/go-runewidth", - "revision": "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d", - "revisionTime": "2017-05-10T07:48:58Z" + "revision": "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb", + "revisionTime": "2018-04-08T05:53:51Z" }, { "checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=", @@ -771,179 +857,203 @@ "revisionTime": "2016-04-24T11:30:07Z" }, { - "checksumSHA1": "V/quM7+em2ByJbWBLOsEwnY3j/Q=", + "checksumSHA1": "a58zUNtDH/gEd6F6KI3FqT2iEo0=", "path": "github.com/mitchellh/go-homedir", - "revision": "b8bc1bf767474819792c23f32d8286a45736f1c6", - "revisionTime": "2016-12-03T19:45:07Z" + "revision": "ae18d6b8b3205b561c79e8e5f69bff09736185f4", + "revisionTime": "2018-08-24T00:42:36Z" }, { - "checksumSHA1": "c1JIqjo91nD1RMFImnW69n8jtj8=", + "checksumSHA1": "4VVcCBCqn1hvMaURZyPSKqkMc4o=", "path": "github.com/mitchellh/mapstructure", - "revision": "cc8532a8e9a55ea36402aa21efdf403a60d34096", - "revisionTime": "2017-04-22T00:02:51Z" + "revision": "fa473d140ef3c6adf42d6b391fe76707f1f243c8", + "revisionTime": "2018-08-24T00:32:02Z" }, { - "checksumSHA1": "4DPLQd1N7e3XkwfrTXoUgXdqOxo=", + "checksumSHA1": "jIedWzdqaQakO3DMTGZoXcmm3F4=", "path": "github.com/mwitkow/go-grpc-middleware", - "revision": "fb1b862bbda2067865911d847adb45124329afa2", - "revisionTime": "2017-05-13T12:29:38Z" + "revision": "498ae206fc3cfe81cd82e48c1d4354026fa5f9ec", + "revisionTime": "2018-08-30T09:29:08Z" }, { - "checksumSHA1": "F1IYMLBLAZaTOWnmXsgaxTGvrWI=", + "checksumSHA1": "yekxthsOXToQ/tMNaHEQvf9d5nY=", "path": "github.com/pelletier/go-buffruneio", - "revision": "c37440a7cf42ac63b919c752ca73a85067e05992", - "revisionTime": "2017-02-27T22:03:11Z" + "revision": "de1592c34d9c6055a32fc9ebe2b3ee50ca468ebe", + "revisionTime": "2018-08-27T16:26:05Z" }, { - "checksumSHA1": "T0tpikeX0qojuKoRcJ2IzcSc2iI=", + "checksumSHA1": "amsJse38+uT7Z7UkvtMzVefgZOM=", "path": "github.com/pelletier/go-toml", - "revision": "685a1f1cb7a66b9cadbe8f1ac49d9f8f567d6a9d", - "revisionTime": "2017-05-11T00:53:23Z" + "revision": "c2dbbc24a97911339e01bda0b8cabdbd8f13b602", + "revisionTime": "2018-07-24T18:49:33Z" }, { - "checksumSHA1": "rJab1YdNhQooDiBWNnt7TLWPyBU=", + "checksumSHA1": "18YrywDvb67HU8xYF5vqKMgelx0=", "path": "github.com/pkg/errors", - "revision": "c605e284fe17294bda444b34710735b29d1a9d90", - "revisionTime": "2017-05-05T04:36:39Z" + "revision": "c059e472caf75dbe73903f6521a20abac245b17f", + "revisionTime": "2018-09-11T06:21:13Z" }, { - "checksumSHA1": "ty3Y0hPtRphsqcykY9ihV6F02Fk=", + "checksumSHA1": "jN66lrLtNTo3Qe6+WtCbNcvznAo=", "path": "github.com/prometheus/client_golang/prometheus", - "revision": "353b8c3f3776541879f9abfd8fa8b1ae162ab394", - "revisionTime": "2017-09-21T10:55:43Z" + "revision": "7858729281ec582767b20e0d696b6041d995d5e0", + "revisionTime": "2018-09-07T10:25:42Z" }, { - "checksumSHA1": "wsAkYlRRUNx+OAuUOIqdjO7dICM=", + "checksumSHA1": "UBqhkyjCz47+S19MVTigxJ2VjVQ=", + "path": "github.com/prometheus/client_golang/prometheus/internal", + "revision": "7858729281ec582767b20e0d696b6041d995d5e0", + "revisionTime": "2018-09-07T10:25:42Z" + }, + { + "checksumSHA1": "d5BiEvD8MrgpWQ6PQJUvawJsMak=", "path": "github.com/prometheus/client_golang/prometheus/promhttp", - "revision": "353b8c3f3776541879f9abfd8fa8b1ae162ab394", - "revisionTime": "2017-09-21T10:55:43Z" + "revision": "7858729281ec582767b20e0d696b6041d995d5e0", + "revisionTime": "2018-09-07T10:25:42Z" }, { - "checksumSHA1": "DvwvOlPNAgRntBzt3b3OSRMS2N4=", + "checksumSHA1": "V8xkqgmP66sq2ZW4QO5wi9a4oZE=", "path": "github.com/prometheus/client_model/go", - "revision": "6f3806018612930941127f2a7c6c453ba2c527d2", - "revisionTime": "2017-02-16T18:52:47Z" + "revision": "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f", + "revisionTime": "2018-07-12T10:51:10Z" }, { - "checksumSHA1": "xfnn0THnqNwjwimeTClsxahYrIo=", + "checksumSHA1": "Q0mjhUEjAklUQvPkrOChWGLpvRY=", "path": "github.com/prometheus/common/expfmt", - "revision": "2f17f4a9d485bf34b4bfaccc273805040e4f86c8", - "revisionTime": "2017-09-08T16:18:22Z" + "revision": "c7de2306084e37d54b8be01f3541a8464345e9a5", + "revisionTime": "2018-08-01T06:44:54Z" }, { "checksumSHA1": "GWlM3d2vPYyNATtTFgftS10/A9w=", "path": "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg", - "revision": "2f17f4a9d485bf34b4bfaccc273805040e4f86c8", - "revisionTime": "2017-09-08T16:18:22Z" + "revision": "c7de2306084e37d54b8be01f3541a8464345e9a5", + "revisionTime": "2018-08-01T06:44:54Z" }, { - "checksumSHA1": "3VoqH7TFfzA6Ds0zFzIbKCUvBmw=", + "checksumSHA1": "EXTRY7DL9gFW8c341Dk6LDXCBn8=", "path": "github.com/prometheus/common/model", - "revision": "2f17f4a9d485bf34b4bfaccc273805040e4f86c8", - "revisionTime": "2017-09-08T16:18:22Z" + "revision": "c7de2306084e37d54b8be01f3541a8464345e9a5", + "revisionTime": "2018-08-01T06:44:54Z" }, { - "checksumSHA1": "ihxJIjxtbEYdQKwA0D0nRipj95I=", + "checksumSHA1": "jo/zxF+Pfj5yZjReTKGOACq9IBs=", "path": "github.com/prometheus/procfs", - "revision": "e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2", - "revisionTime": "2017-07-03T10:12:42Z" + "revision": "05ee40e3a273f7245e8777337fc7b46e533a9a92", + "revisionTime": "2018-07-25T12:39:19Z" + }, + { + "checksumSHA1": "lv9rIcjbVEGo8AT1UCUZXhXrfQc=", + "path": "github.com/prometheus/procfs/internal/util", + "revision": "05ee40e3a273f7245e8777337fc7b46e533a9a92", + "revisionTime": "2018-07-25T12:39:19Z" + }, + { + "checksumSHA1": "HSP5hVT0CNMRa8+Xtz4z2Ic5U0E=", + "path": "github.com/prometheus/procfs/nfs", + "revision": "05ee40e3a273f7245e8777337fc7b46e533a9a92", + "revisionTime": "2018-07-25T12:39:19Z" }, { - "checksumSHA1": "xCiFAAwVTrjsfZT1BIJQ3DgeNCY=", + "checksumSHA1": "yItvTQLUVqm/ArLEbvEhqG0T5a0=", "path": "github.com/prometheus/procfs/xfs", - "revision": "e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2", - "revisionTime": "2017-07-03T10:12:42Z" + "revision": "05ee40e3a273f7245e8777337fc7b46e533a9a92", + "revisionTime": "2018-07-25T12:39:19Z" }, { - "checksumSHA1": "KAzbLjI9MzW2tjfcAsK75lVRp6I=", + "checksumSHA1": "an5RM8wjgPPloolUUYkvEncbHu4=", "path": "github.com/rcrowley/go-metrics", - "revision": "1f30fe9094a513ce4c700b9a54458bbb0c96996c", - "revisionTime": "2016-11-28T21:05:44Z" + "revision": "e2704e165165ec55d062f5919b4b29494e9fa790", + "revisionTime": "2018-05-03T17:46:38Z" }, { - "checksumSHA1": "x0wagx+fR074F3K0SDb8MudiEoA=", - "origin": "github.com/htdvisser/otto", + "checksumSHA1": "o0+FxYUsE4W0478XA9AnSIB5wfs=", + "origin": "github.com/TheThingsIndustries/otto", "path": "github.com/robertkrimen/otto", - "revision": "4c9f00e2ef7b56d46b9c699c30b94ad93f348dae", - "revisionTime": "2017-05-08T09:23:08Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { - "checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=", + "checksumSHA1": "q1HKpLIWi3tTT5W5LqMlwPcGmyQ=", + "origin": "github.com/TheThingsIndustries/otto/ast", "path": "github.com/robertkrimen/otto/ast", - "revision": "21ec96599b1279b5673e4df0097dd56bb8360068", - "revisionTime": "2017-04-24T10:46:44Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { "checksumSHA1": "L0KsB2EzTlPgv0iae3q3SukNW7U=", + "origin": "github.com/TheThingsIndustries/otto/dbg", "path": "github.com/robertkrimen/otto/dbg", - "revision": "21ec96599b1279b5673e4df0097dd56bb8360068", - "revisionTime": "2017-04-24T10:46:44Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { "checksumSHA1": "euDLJKhw4doeTSxjEoezjxYXLzs=", + "origin": "github.com/TheThingsIndustries/otto/file", "path": "github.com/robertkrimen/otto/file", - "revision": "21ec96599b1279b5673e4df0097dd56bb8360068", - "revisionTime": "2017-04-24T10:46:44Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { - "checksumSHA1": "LLuLITFO8chqSG0+APJIy5NtOHU=", + "checksumSHA1": "w5wu2avr5PWCJy5dlEhlY3rwb+w=", + "origin": "github.com/TheThingsIndustries/otto/parser", "path": "github.com/robertkrimen/otto/parser", - "revision": "21ec96599b1279b5673e4df0097dd56bb8360068", - "revisionTime": "2017-04-24T10:46:44Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { "checksumSHA1": "7J/7NaYRqKhBvZ+dTIutsEoEgFw=", + "origin": "github.com/TheThingsIndustries/otto/registry", "path": "github.com/robertkrimen/otto/registry", - "revision": "21ec96599b1279b5673e4df0097dd56bb8360068", - "revisionTime": "2017-04-24T10:46:44Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { "checksumSHA1": "/jMXYuXycBpTqWhRyJ2xsqvHvQI=", + "origin": "github.com/TheThingsIndustries/otto/token", "path": "github.com/robertkrimen/otto/token", - "revision": "21ec96599b1279b5673e4df0097dd56bb8360068", - "revisionTime": "2017-04-24T10:46:44Z" + "revision": "571d0fa31a277d8a63de1fd0afee431d627fbbaf", + "revisionTime": "2017-11-07T14:36:49Z" }, { - "checksumSHA1": "rAY560vBxgXVz9M/J6WKwUztGYY=", + "checksumSHA1": "q14d3C3xvWevU3dSv4P5K0+OSD0=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { - "checksumSHA1": "CwrO9QBCltE965zskPoNMoB3LfQ=", + "checksumSHA1": "jqSn6muASnTMX4iMHv7Oep0suwk=", "path": "github.com/shirou/gopsutil/host", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { - "checksumSHA1": "YfGdUgCoLdHBhfG+kXczYXu/rKk=", + "checksumSHA1": "GR7l7Ez4ppxIfonl7aJayYRVPWc=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { - "checksumSHA1": "jB8En6qWQ7G2yPJey4uY1FvOjWM=", + "checksumSHA1": "q0o4cpjNszHkZqfSHi83a668CSw=", "path": "github.com/shirou/gopsutil/load", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { - "checksumSHA1": "/VB3F8T81eg3lj/UG1bFlfkTQIc=", + "checksumSHA1": "ehcscnqRvxDZYb1nBDVzZi8vFi8=", "path": "github.com/shirou/gopsutil/mem", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { - "checksumSHA1": "xDm4do0IiP2rkeB52oOIj66AzNQ=", + "checksumSHA1": "LB73cgL0zYjR/hmcxQldCVH+AfI=", "path": "github.com/shirou/gopsutil/net", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { - "checksumSHA1": "Nlw5knolH58GaYUwW/h+AWfZJVQ=", + "checksumSHA1": "KWfiNGPeauBf2ujk9uGHnoOQLI0=", "path": "github.com/shirou/gopsutil/process", - "revision": "3dd8bd46d9a1ccbd37b3ba6e3dc1dc7d37ba8dc5", - "revisionTime": "2017-06-05T13:30:45Z" + "revision": "eb1f1ab16f2ef35f23024e10888dfdb5c17fcb92", + "revisionTime": "2018-09-01T13:42:34Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -952,136 +1062,148 @@ "revisionTime": "2016-09-30T03:27:40Z" }, { - "checksumSHA1": "AikWjAOvWHSJ8G1iU+wNReZnyCk=", + "checksumSHA1": "IFtQxH02Qj36QooV+Z41enAh/hc=", "path": "github.com/smartystreets/assertions", - "revision": "f8459f92181409546d2762c9e9c4bedc23c92d76", - "revisionTime": "2017-05-12T16:53:30Z" + "revision": "7c9eb446e3cf06e83049467a5f25c38730ce7034", + "revisionTime": "2018-08-20T20:17:07Z" }, { - "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", + "checksumSHA1": "v6W3GIQMzr3QSXB2NtBa9X7SwiI=", "path": "github.com/smartystreets/assertions/internal/go-render/render", - "revision": "f8459f92181409546d2762c9e9c4bedc23c92d76", - "revisionTime": "2017-05-12T16:53:30Z" + "revision": "7c9eb446e3cf06e83049467a5f25c38730ce7034", + "revisionTime": "2018-08-20T20:17:07Z" }, { "checksumSHA1": "r6FauVdOTFnwYQgrKGFuWUbIAJE=", "path": "github.com/smartystreets/assertions/internal/oglematchers", - "revision": "f8459f92181409546d2762c9e9c4bedc23c92d76", - "revisionTime": "2017-05-12T16:53:30Z" + "revision": "7c9eb446e3cf06e83049467a5f25c38730ce7034", + "revisionTime": "2018-08-20T20:17:07Z" }, { - "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", + "checksumSHA1": "+FDdVX1IlqzH3tSuA95LH2XZoOU=", "path": "github.com/smartystreets/go-aws-auth", - "revision": "8ef1316913ee4f44bc48c2456e44a5c1c68ea53b", - "revisionTime": "2017-05-04T20:50:21Z" + "revision": "0c1422d1fdb9fef5a1ad5b2e13ac663467eb767e", + "revisionTime": "2018-05-15T14:38:44Z" }, { - "checksumSHA1": "lBehULzb2/kIK3wZ0gz2yNmHq9s=", + "checksumSHA1": "RXdGcEWihBEokWLc+LTy/UnI8OM=", "path": "github.com/spf13/afero", - "revision": "9be650865eab0c12963d8753212f4f9c66cdcf12", - "revisionTime": "2017-02-17T16:41:46Z" + "revision": "d40851caa0d747393da1ffb28f7f9d8b4eeffebd", + "revisionTime": "2018-09-07T09:23:51Z" }, { - "checksumSHA1": "5KRbEQ28dDaQmKwAYTD0if/aEvg=", + "checksumSHA1": "HcOjO9+cwl+xYnkiGuIeesqwBs8=", "path": "github.com/spf13/afero/mem", - "revision": "9be650865eab0c12963d8753212f4f9c66cdcf12", - "revisionTime": "2017-02-17T16:41:46Z" + "revision": "d40851caa0d747393da1ffb28f7f9d8b4eeffebd", + "revisionTime": "2018-09-07T09:23:51Z" }, { - "checksumSHA1": "Sq0QP4JywTr7UM4hTK1cjCi7jec=", + "checksumSHA1": "Hc2i9OOK34PAImuNftTaHdbdLgs=", "path": "github.com/spf13/cast", - "revision": "acbeb36b902d72a7a4c18e8f3241075e7ab763e4", - "revisionTime": "2017-04-13T08:50:28Z" + "revision": "8965335b8c7107321228e3e3702cab9832751bac", + "revisionTime": "2018-02-14T17:35:30Z" }, { - "checksumSHA1": "mVpg0aL1S+GCSHhkeuns8Y+a2AE=", + "checksumSHA1": "YMGD3aMJlxhrWBzLlTqPhTLyxG4=", "path": "github.com/spf13/cobra", - "revision": "4cdb38c072b86bf795d2c81de50784d9fdd6eb77", - "revisionTime": "2017-05-15T07:49:18Z" + "revision": "99dc123558852f67743bd0b2caf8383cb3c6d720", + "revisionTime": "2018-08-29T16:07:45Z" }, { - "checksumSHA1": "HWxbQY/EA/SXXGRZBlA2mruX5Uc=", + "checksumSHA1": "fL/aH2pPCg51QUFedpS6EcT94KU=", "path": "github.com/spf13/jwalterweatherman", - "revision": "8f07c835e5cc1450c082fe3a439cf87b0cbb2d99", - "revisionTime": "2017-05-10T07:11:02Z" + "revision": "4a4406e478ca629068e7768fc33f3f044173c0a6", + "revisionTime": "2018-09-07T09:17:59Z" }, { - "checksumSHA1": "STxYqRb4gnlSr3mRpT+Igfdz/kM=", + "checksumSHA1": "WVKhLVXQFxXwmuRX+IRzebUmxjE=", "path": "github.com/spf13/pflag", - "revision": "e57e3eeb33f795204c1ca35f56c44f83227c6e66", - "revisionTime": "2017-05-08T18:43:26Z" + "revision": "298182f68c66c05229eb03ac171abe6e309ee79a", + "revisionTime": "2018-08-31T15:14:32Z" }, { - "checksumSHA1": "QMYYbMeFaLNL/UezRGhNVax5EBg=", + "checksumSHA1": "Pz7ShJAOvkfJIBon5qZROV2r3Cg=", "path": "github.com/spf13/viper", - "revision": "0967fc9aceab2ce9da34061253ac10fb99bba5b2", - "revisionTime": "2017-04-17T08:08:15Z" + "revision": "3171ef9a229903ce60a9513ec3899b63c003e91c", + "revisionTime": "2018-09-07T13:06:02Z" }, { - "checksumSHA1": "PvE2uMJ8DkcVZ1WTp9xjVeqKdCM=", + "checksumSHA1": "JPPv7nnUo01Mlyqb0g+Qk5/URR4=", "path": "github.com/streadway/amqp", - "revision": "6063341da5df18c5abcb1f62373016ab9858b7b0", - "revisionTime": "2017-05-13T11:23:37Z" + "revision": "70e15c650864f4fc47f5d3c82ea117285480895d", + "revisionTime": "2018-08-06T23:38:56Z" }, { - "checksumSHA1": "1JX1/uhqbe/6OYJ7Phdh96Rup+o=", + "checksumSHA1": "brhkaHb0nqCGr7e5GUePL4ZvcsY=", "path": "github.com/tj/go-elastic", - "revision": "4e3970d1a73193d3d4eb1791280cb8b5c26566f6", - "revisionTime": "2017-07-21T14:46:56Z" + "revision": "36157cbbebc210c55c1c8a82c8655ccbb9f41aa0", + "revisionTime": "2017-12-21T16:09:35Z" }, { "checksumSHA1": "nL4enNHknemOmxcaPTIJCrJc0/I=", "path": "github.com/tj/go-elastic/aliases", - "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", - "revisionTime": "2016-06-07T20:24:39Z" + "revision": "36157cbbebc210c55c1c8a82c8655ccbb9f41aa0", + "revisionTime": "2017-12-21T16:09:35Z" }, { "checksumSHA1": "SgbyhOvKGvet/Nw70Rxa8d3gLZ0=", "path": "github.com/tj/go-elastic/batch", - "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", - "revisionTime": "2016-06-07T20:24:39Z" + "revision": "36157cbbebc210c55c1c8a82c8655ccbb9f41aa0", + "revisionTime": "2017-12-21T16:09:35Z" }, { - "checksumSHA1": "ZaU56svwLgiJD0y8JOB3+/mpYBA=", + "checksumSHA1": "BGm8lKZmvJbf/YOJLeL1rw2WVjA=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "ab89591268e0c8b748cbe4047b00197516011af5", - "revisionTime": "2017-05-12T12:41:09Z" + "revision": "0e37d006457bf46f9e6692014ba72ef82c33022c", + "revisionTime": "2018-06-14T19:19:50Z" }, { - "checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=", + "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", "path": "golang.org/x/net/context", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", "path": "golang.org/x/net/context/ctxhttp", - "revision": "f01ecb60fe3835d80d9a0b7b2bf24b228c89260e", - "revisionTime": "2017-07-11T11:58:19Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" + }, + { + "checksumSHA1": "pCY4YtdNKVBYRbNvODjx8hj0hIs=", + "path": "golang.org/x/net/http/httpguts", + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { - "checksumSHA1": "Wl71V4Lh2iajiRnVAMPtd276vTg=", + "checksumSHA1": "3p4xISa2iLZULxYfVsIUlHJ+PUk=", "path": "golang.org/x/net/http2", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { - "checksumSHA1": "4n0osCZJbirWKtbcyAg+Mb5PQ0k=", + "checksumSHA1": "KZniwnfpWkaTPhUQDUTvgex/7y0=", "path": "golang.org/x/net/http2/hpack", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { - "checksumSHA1": "VrzPJyWI6disCgYuVEQzkjqUsJk=", + "checksumSHA1": "RcrB7tgYS/GMW4QrwVdMOTNqIU8=", "path": "golang.org/x/net/idna", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" + }, + { + "checksumSHA1": "f3Y7JIZH61oMmp8nphqe8Mg+XoU=", + "path": "golang.org/x/net/internal/socks", + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { "checksumSHA1": "UxahDzW2v4mf/+aFxruuupaoIwo=", "path": "golang.org/x/net/internal/timeseries", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", @@ -1090,166 +1212,196 @@ "revisionTime": "2017-05-12T21:27:43Z" }, { - "checksumSHA1": "9EZG3s2eOREO7WkBvigjk57wK/8=", + "checksumSHA1": "UrFs0T7kkYsUxJG5bsJt7KB8ejI=", + "path": "golang.org/x/net/proxy", + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" + }, + { + "checksumSHA1": "6ckrK99wkirarIfFNX4+AHWBEHM=", "path": "golang.org/x/net/trace", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { - "checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=", + "checksumSHA1": "F+tqxPGFt5x7DKZakbbMmENX1oQ=", "path": "golang.org/x/net/websocket", - "revision": "84f0e6f92b10139f986b1756e149a7d9de270cdc", - "revisionTime": "2017-05-12T21:27:43Z" + "revision": "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2", + "revisionTime": "2018-09-11T21:37:47Z" }, { - "checksumSHA1": "SjCoL7KD7qBmgSuqGTCAuUhigDk=", + "checksumSHA1": "j0z/2h06wsvTkGiLaZ5XFLbMKfo=", "path": "golang.org/x/oauth2", - "revision": "ad516a297a9f2a74ecc244861b298c94bdd28b9d", - "revisionTime": "2017-05-10T21:51:24Z" + "revision": "d2e6202438beef2727060aa7cabdd924d92ebfd9", + "revisionTime": "2018-08-21T21:02:52Z" }, { - "checksumSHA1": "Umx4Fmn5FyXJyUVzKWhnQdvFNkc=", + "checksumSHA1": "6M+WzHfPmZjznMhi8CzsRYWjJ9U=", "path": "golang.org/x/oauth2/internal", - "revision": "ad516a297a9f2a74ecc244861b298c94bdd28b9d", - "revisionTime": "2017-05-10T21:51:24Z" + "revision": "d2e6202438beef2727060aa7cabdd924d92ebfd9", + "revisionTime": "2018-08-21T21:02:52Z" }, { - "checksumSHA1": "43xyNnME4wkw48ohL9USDMTEpDo=", + "checksumSHA1": "GKC8IFNbSdF5HKJVx+Cm/3ASlH8=", "path": "golang.org/x/sys/unix", - "revision": "1e99a4f9d247b28c670884b9a8d6801f39a47b77", - "revisionTime": "2017-05-14T19:20:32Z" + "revision": "d0be0721c37eeb5299f245a996a483160fc36940", + "revisionTime": "2018-09-09T03:34:56Z" }, { - "checksumSHA1": "GUzvy5+6gTwjQ2PaXxjHfbqjOVw=", + "checksumSHA1": "wvpEa7a7bm3i833AmLpSarI51Yc=", "path": "golang.org/x/sys/windows", - "revision": "90796e5a05ce440b41c768bd9af257005e470461", - "revisionTime": "2017-06-27T07:55:33Z" + "revision": "d0be0721c37eeb5299f245a996a483160fc36940", + "revisionTime": "2018-09-09T03:34:56Z" }, { - "checksumSHA1": "faFDXp++cLjLBlvsr+izZ+go1WU=", + "checksumSHA1": "CbpjEkkOeh0fdM/V8xKDdI0AA88=", "path": "golang.org/x/text/secure/bidirule", - "revision": "19e51611da83d6be54ddafce4a4af510cb3e9ea4", - "revisionTime": "2017-04-21T08:09:44Z" + "revision": "905a57155faa8230500121607930ebb9dd8e139c", + "revisionTime": "2018-09-08T17:02:15Z" }, { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "19e51611da83d6be54ddafce4a4af510cb3e9ea4", - "revisionTime": "2017-04-21T08:09:44Z" + "revision": "905a57155faa8230500121607930ebb9dd8e139c", + "revisionTime": "2018-09-08T17:02:15Z" }, { - "checksumSHA1": "MRLtTu/vpd18le8/HPLxOdjO5RE=", + "checksumSHA1": "Qw4qdlZHCnBurAPPrSt+EKPIngM=", "path": "golang.org/x/text/unicode/bidi", - "revision": "19e51611da83d6be54ddafce4a4af510cb3e9ea4", - "revisionTime": "2017-04-21T08:09:44Z" + "revision": "905a57155faa8230500121607930ebb9dd8e139c", + "revisionTime": "2018-09-08T17:02:15Z" }, { - "checksumSHA1": "kKylzIrLEnH8NKyeVAL0dq5gjVQ=", + "checksumSHA1": "XJr6+rzzxASewSbC/SCStyGlmuw=", "path": "golang.org/x/text/unicode/norm", - "revision": "19e51611da83d6be54ddafce4a4af510cb3e9ea4", - "revisionTime": "2017-04-21T08:09:44Z" + "revision": "905a57155faa8230500121607930ebb9dd8e139c", + "revisionTime": "2018-09-08T17:02:15Z" }, { - "checksumSHA1": "4o2JkeR2LyUfZ7BQIzHUejyqKno=", + "checksumSHA1": "YiWH1fpJRu3R4w4LuGI63c9N9dg=", "path": "google.golang.org/appengine/internal", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { - "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", + "checksumSHA1": "5PakGXEgSbyFptkhGO8MnGf7uH0=", "path": "google.golang.org/appengine/internal/base", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { - "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", + "checksumSHA1": "3DZ+Ah5hFQb1/nh1+li2VE+kkfk=", "path": "google.golang.org/appengine/internal/datastore", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { - "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", + "checksumSHA1": "HJQ4JM9YWfwIe4vmAgXC7J/1T3E=", "path": "google.golang.org/appengine/internal/log", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { - "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", + "checksumSHA1": "hApgRLSl7w9XG2waJxdH/o0A398=", "path": "google.golang.org/appengine/internal/remote_api", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { - "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", + "checksumSHA1": "ZnEUFEjcGAVZNDPOOc+VLN7x4pI=", "path": "google.golang.org/appengine/internal/urlfetch", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", "path": "google.golang.org/appengine/urlfetch", - "revision": "170382fa85b10b94728989dfcf6cc818b335c952", - "revisionTime": "2017-04-10T19:43:55Z" + "revision": "03cac3b07182cfb08c0d0c0b6ee72a1ceb151c92", + "revisionTime": "2018-08-27T17:01:13Z" }, { - "checksumSHA1": "kGGAT2jr6A8fgFrrpP3c3BYmnpY=", + "checksumSHA1": "YNqziavfZHurG6wrwR5Uf9SnI4s=", "path": "google.golang.org/genproto/googleapis/api/annotations", - "revision": "411e09b969b1170a9f0c467558eb4c4c110d9c77", - "revisionTime": "2017-04-04T13:20:09Z" + "revision": "36d5787dc5356b711fe8f3040271aaf51c35955b", + "revisionTime": "2018-09-11T21:11:18Z" }, { - "checksumSHA1": "61oRC/n7DFqHNu6Z+4fAKY1FVCY=", + "checksumSHA1": "oUD15OBRSXt0t4P0s6HMjH/+iQo=", "path": "google.golang.org/genproto/googleapis/rpc/status", - "revision": "411e09b969b1170a9f0c467558eb4c4c110d9c77", - "revisionTime": "2017-04-04T13:20:09Z" + "revision": "36d5787dc5356b711fe8f3040271aaf51c35955b", + "revisionTime": "2018-09-11T21:11:18Z" }, { - "checksumSHA1": "u5xUzAequNhWqPxl9cIufU3MBMw=", + "checksumSHA1": "HV/AvwdtsJE9iQjBxmZ89+75O1Q=", "path": "google.golang.org/grpc", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "qkqcMp2ECAtqJDCO13G+qe6ihTc=", + "checksumSHA1": "B+kZFVP8zRiQMpoEb39Mp2oSmqg=", "path": "google.golang.org/grpc/balancer", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "CPWX/IgaQSR3+78j4sPrvHNkW+U=", + "checksumSHA1": "lw+L836hLeH8+//le+C+ycddCCU=", "path": "google.golang.org/grpc/balancer/base", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "dgsesZr1kWAbuOMpgf/5bJrZan8=", + "checksumSHA1": "DJ1AtOk4Pu7bqtUMob95Hw8HPNw=", "path": "google.golang.org/grpc/balancer/roundrobin", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "bfmh2m3qW8bb6qpfS/D4Wcl4hZE=", + "checksumSHA1": "R3tuACGAPyK4lr+oSNt1saUzC0M=", "path": "google.golang.org/grpc/codes", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "XH2WYcDNwVO47zYShREJjcYXm0Y=", "path": "google.golang.org/grpc/connectivity", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "4DnDX81AOSyVP3UJ5tQmlNcG1MI=", + "checksumSHA1": "wA6y5rkH1v4bWBe5M1r/Hdtgma4=", "path": "google.golang.org/grpc/credentials", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "9DImIDqmAMPO24loHJ77UVJTDxQ=", + "checksumSHA1": "cfLb+pzWB+Glwp82rgfcEST1mv8=", "path": "google.golang.org/grpc/encoding", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" + }, + { + "checksumSHA1": "LKKkn7EYA+Do9Qwb2/SUKLFNxoo=", + "path": "google.golang.org/grpc/encoding/proto", + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "XbDzI4GeUt2/8WAS5mLKm5ghVWo=", @@ -1261,103 +1413,177 @@ "checksumSHA1": "H7SuPUqbPcdbNqgl+k3ohuwMAwE=", "path": "google.golang.org/grpc/grpclb/grpc_lb_v1/messages", "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revisionTime": "2017-12-11T17:35:50Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "ntHev01vgZgeIh5VFRmbLx/BSTo=", + "checksumSHA1": "ZPPSFisPDz2ANO4FBZIft+fRxyk=", "path": "google.golang.org/grpc/grpclog", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "DyM0uqLtknaI4THSc3spn9XlL+g=", + "checksumSHA1": "MRdkTRX18dr+tG+Q2s+BHox0T64=", "path": "google.golang.org/grpc/health", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "6vY7tYjV84pnr3sDctzx53Bs8b0=", + "checksumSHA1": "aOU41miZwmHdMbzZNB4H2xz5wWI=", "path": "google.golang.org/grpc/health/grpc_health_v1", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "Qvf3zdmRCSsiM/VoBv0qB/naHtU=", + "checksumSHA1": "cSdzm5GhbalJbWUNrN8pRdW0uks=", "path": "google.golang.org/grpc/internal", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" + }, + { + "checksumSHA1": "uDJA7QK2iGnEwbd9TPqkLaM+xuU=", + "path": "google.golang.org/grpc/internal/backoff", + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" + }, + { + "checksumSHA1": "fvj+rPmX5++NHHTTwG9gHlTEGow=", + "path": "google.golang.org/grpc/internal/channelz", + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" + }, + { + "checksumSHA1": "5dFUCEaPjKwza9kwKqgljp8ckU4=", + "path": "google.golang.org/grpc/internal/envconfig", + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" + }, + { + "checksumSHA1": "70gndc/uHwyAl3D45zqp7vyHWlo=", + "path": "google.golang.org/grpc/internal/grpcrand", + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" + }, + { + "checksumSHA1": "F/QkfMhNApNewEK84d9CM3fT6js=", + "path": "google.golang.org/grpc/internal/transport", + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "hcuHgKp8W0wIzoCnNfKI8NUss5o=", "path": "google.golang.org/grpc/keepalive", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "KeUmTZV+2X46C49cKyjp+xM7fvw=", + "checksumSHA1": "OjIAi5AzqlQ7kLtdAyjvdgMf6hc=", "path": "google.golang.org/grpc/metadata", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "5dwF592DPvhF2Wcex3m7iV6aGRQ=", + "checksumSHA1": "VvGBoawND0urmYDy11FT+U1IHtU=", "path": "google.golang.org/grpc/naming", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "n5EgDdBqFMa2KQFhtl+FF/4gIFo=", "path": "google.golang.org/grpc/peer", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "y8Ta+ctMP9CUTiPyPyxiD154d8w=", + "checksumSHA1": "GEq6wwE1qWLmkaM02SjxBmmnHDo=", "path": "google.golang.org/grpc/resolver", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "WpWF+bDzObsHf+bjoGpb/abeFxo=", + "checksumSHA1": "3aaLCpBkkCNQvVPHyvPi+G0vnQI=", "path": "google.golang.org/grpc/resolver/dns", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "+JzDc+wsEWeYhqsKV4AzAK14SZg=", + "checksumSHA1": "7z5yY6Lq4/iWiMdFkAOm21wKWFo=", "path": "google.golang.org/grpc/resolver/manual", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "zs9M4xE8Lyg4wvuYvR00XoBxmuw=", "path": "google.golang.org/grpc/resolver/passthrough", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "G9lgXNi7qClo5sM2s6TbTHLFR3g=", + "checksumSHA1": "YclPgme2gT3S0hTkHVdE1zAxJdo=", "path": "google.golang.org/grpc/stats", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { - "checksumSHA1": "3Dwz4RLstDHMPyDA7BUsYe+JP4w=", + "checksumSHA1": "t/NhHuykWsxY0gEBd2WIv5RVBK8=", "path": "google.golang.org/grpc/status", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "qvArRhlrww5WvRmbyMF2mUfbJew=", "path": "google.golang.org/grpc/tap", - "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revision": "32fb0ac620c32ba40a4626ddf94d90d12cce3455", + "revisionTime": "2018-07-31T16:43:25Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "7bfElBKfFxSer7dh/CdZS0jC2iQ=", "path": "google.golang.org/grpc/transport", "revision": "cce0e436e5c428f2094edd926779788f1024938d", - "revisionTime": "2017-12-11T17:35:50Z" + "revisionTime": "2017-12-11T17:35:50Z", + "version": "v1.14.0", + "versionExact": "v1.14.0" }, { "checksumSHA1": "OU/wHTJqhyQfyRnXMVWx1Ox06kQ=", @@ -1408,10 +1634,10 @@ "revisionTime": "2017-02-13T14:20:43Z" }, { - "checksumSHA1": "fALlQNY1fM99NesfLJ50KguWsio=", + "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", "path": "gopkg.in/yaml.v2", - "revision": "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b", - "revisionTime": "2017-04-07T17:21:22Z" + "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", + "revisionTime": "2018-03-28T19:50:20Z" } ], "rootPath": "github.com/TheThingsNetwork/ttn"